blob: 28808ee9656f985954066ce1e4e8771590515715 [file]
import assert from 'node:assert/strict';
import { describe, test } from 'node:test';
import type { CuAction } from '@maka/core';
import { runOpenAIComputerLoop } from '../openai-computer-loop.js';
import type { OpenAIComputerRequest } from '../openai-computer-codec.js';
const success = () => ({
outcome: { ok: true as const, tier: 'coordinate-background' as const },
});
const call = (over: Record<string, unknown> = {}) => ({
type: 'computer_call',
id: 'item_1',
call_id: 'call_1',
status: 'completed',
pending_safety_checks: [],
actions: [],
...over,
});
describe('runOpenAIComputerLoop', () => {
test('executes actions[] in order, captures once, and continues with the call id', async () => {
const requests: OpenAIComputerRequest[] = [];
const executed: CuAction[] = [];
const responses = [
{
id: 'resp_1',
output: [
call({
actions: [
{ type: 'move', x: 1, y: 2 },
{ type: 'click', button: 'left', x: 1, y: 2 },
{ type: 'type', text: 'ok' },
],
}),
],
},
{ id: 'resp_2', output: [{ type: 'message', content: [] }] },
];
const result = await runOpenAIComputerLoop({
dialect: 'ga',
model: 'gpt',
prompt: 'go',
transport: {
async create(request) {
requests.push(request);
return responses.shift();
},
},
executor: {
async execute(action) {
executed.push(action);
return success();
},
},
screenshot: {
async capture() {
return { base64: 'AA==', mimeType: 'image/png' };
},
},
allowAction: async () => true,
});
assert.equal(result.status, 'completed');
assert.deepEqual(
executed.map((action) => action.type),
['mouse_move', 'left_click', 'type'],
);
assert.equal(requests[1].previous_response_id, 'resp_1');
assert.equal((requests[1].input as Array<{ call_id: string }>)[0].call_id, 'call_1');
});
test('blocks pending safety checks before executing any action', async () => {
let executions = 0;
const result = await runOpenAIComputerLoop({
dialect: 'ga',
model: 'gpt',
prompt: 'go',
transport: {
async create() {
return {
id: 'resp_1',
output: [
call({
pending_safety_checks: [{ id: 'safe_1', code: 'confirm', message: 'Confirm' }],
actions: [{ type: 'click', button: 'left', x: 1, y: 2 }],
}),
],
};
},
},
executor: {
async execute() {
executions += 1;
return success();
},
},
screenshot: {
async capture() {
throw new Error('must not capture');
},
},
});
assert.equal(result.status, 'safety_blocked');
assert.equal(executions, 0);
});
test('prevalidates the entire batch so an unsupported later action causes zero execution', async () => {
let executions = 0;
const result = await runOpenAIComputerLoop({
dialect: 'ga',
model: 'gpt',
prompt: 'go',
transport: {
async create() {
return {
id: 'resp_1',
output: [
call({
actions: [
{ type: 'click', button: 'left', x: 1, y: 2 },
{ type: 'scroll', x: 1, y: 2, scroll_x: 0, scroll_y: 100 },
],
}),
],
};
},
},
executor: {
async execute() {
executions += 1;
return success();
},
},
screenshot: {
async capture() {
throw new Error('must not capture');
},
},
allowAction: async () => true,
});
assert.equal(result.status, 'unsupported_action');
if (result.status === 'unsupported_action') {
assert.equal(result.actionIndex, 1);
assert.equal(result.failure.code, 'unsupported_scroll_delta');
}
assert.equal(executions, 0);
});
test('rejects a mixed batch before execution when it contains compatibility input', async () => {
let executions = 0;
const result = await runOpenAIComputerLoop({
dialect: 'ga',
model: 'gpt',
prompt: 'go',
transport: {
async create() {
return {
id: 'resp_1',
output: [
call({
actions: [{ type: 'screenshot' }, { type: 'click', button: 'left', x: 1, y: 2 }],
}),
],
};
},
},
executor: {
async execute() {
executions += 1;
return success();
},
},
screenshot: {
async capture() {
throw new Error('must not capture');
},
},
});
assert.equal(result.status, 'unsupported_action');
if (result.status === 'unsupported_action') {
assert.equal(result.actionIndex, 1);
assert.equal(result.failure.code, 'unsupported_action_policy');
}
assert.equal(executions, 0);
});
test('an explicit scenario policy can opt into a converted action', async () => {
const executed: CuAction[] = [];
const responses = [
{
id: 'resp_1',
output: [
call({
actions: [{ type: 'click', button: 'left', x: 1, y: 2 }],
}),
],
},
{ id: 'resp_2', output: [] },
];
const result = await runOpenAIComputerLoop({
dialect: 'ga',
model: 'gpt',
prompt: 'go',
transport: {
async create() {
return responses.shift();
},
},
executor: {
async execute(action) {
executed.push(action);
return success();
},
},
screenshot: {
async capture() {
return { base64: 'AA==', mimeType: 'image/png' };
},
},
allowAction: async () => true,
});
assert.equal(result.status, 'completed');
assert.deepEqual(executed, [
{
type: 'left_click',
coordinate: { x: 1, y: 2 },
},
]);
});
test('does not treat failed or incomplete responses as completion', async () => {
for (const response of [
{
id: 'failed',
status: 'failed',
error: { type: 'server_error', code: 'capacity', message: 'No capacity' },
output: [],
},
{ id: 'incomplete', status: 'incomplete', error: null, output: [] },
]) {
await assert.rejects(
() =>
runOpenAIComputerLoop({
dialect: 'ga',
model: 'gpt',
prompt: 'go',
transport: {
async create() {
return response;
},
},
executor: {
async execute() {
return success();
},
},
screenshot: {
async capture() {
return { base64: 'AA==', mimeType: 'image/png' };
},
},
}),
/openai_computer_response_(failed|not_completed)/,
);
}
});
test('executes an acknowledged safety batch and echoes acknowledgements', async () => {
const requests: OpenAIComputerRequest[] = [];
const responses = [
{
id: 'resp_1',
output: [
call({
pending_safety_checks: [{ id: 'safe_1', code: 'confirm', message: 'Confirm' }],
actions: [{ type: 'screenshot' }],
}),
],
},
{ id: 'resp_2', output: [] },
];
const result = await runOpenAIComputerLoop({
dialect: 'ga',
model: 'gpt',
prompt: 'go',
transport: {
async create(request) {
requests.push(request);
return responses.shift();
},
},
executor: {
async execute() {
return success();
},
},
screenshot: {
async capture() {
return { base64: 'AA==', mimeType: 'image/png' };
},
},
acknowledgeSafetyChecks: async () => true,
});
assert.equal(result.status, 'completed');
const item = (requests[1].input as Array<{ acknowledged_safety_checks?: unknown }>)[0];
assert.deepEqual(item.acknowledged_safety_checks, [
{
id: 'safe_1',
code: 'confirm',
message: 'Confirm',
},
]);
});
test('keeps the preview request contract across the loop', async () => {
const requests: OpenAIComputerRequest[] = [];
const responses = [
{
id: 'resp_1',
output: [
{
type: 'computer_call',
id: 'item_1',
call_id: 'call_1',
status: 'completed',
pending_safety_checks: [],
action: { type: 'wait' },
},
],
},
{ id: 'resp_2', output: [] },
];
const result = await runOpenAIComputerLoop({
dialect: 'preview',
model: 'computer-use-preview',
prompt: 'go',
display: { widthPx: 1024, heightPx: 768, environment: 'browser' },
transport: {
async create(request) {
requests.push(request);
return responses.shift();
},
},
executor: {
async execute() {
return success();
},
},
screenshot: {
async capture() {
return { base64: 'AA==', mimeType: 'image/png' };
},
},
});
assert.equal(result.status, 'completed');
assert.equal(requests[0].truncation, 'auto');
assert.deepEqual(requests[1].tools, [
{
type: 'computer_use_preview',
display_width: 1024,
display_height: 768,
environment: 'browser',
},
]);
assert.equal(requests[1].truncation, 'auto');
});
});