@allternit/computer-driver 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +104 -2
- package/dist/anthropic.d.ts +43 -0
- package/dist/anthropic.js +157 -0
- package/dist/client.d.ts +157 -0
- package/dist/client.js +173 -0
- package/dist/gemini.d.ts +56 -0
- package/dist/gemini.js +107 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +7 -0
- package/dist/openai.d.ts +98 -0
- package/dist/openai.js +113 -0
- package/dist/v2-generated.d.ts +1445 -0
- package/dist/v2-generated.js +1266 -0
- package/dist/v2.d.ts +279 -0
- package/dist/v2.js +154 -0
- package/package.json +58 -4
package/dist/gemini.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type AllternitComputers, type Approval, type ToolsetCall, type ToolsetResult } from "./client.ts";
|
|
2
|
+
export interface GeminiFunctionCall {
|
|
3
|
+
name: string;
|
|
4
|
+
args?: Record<string, unknown>;
|
|
5
|
+
}
|
|
6
|
+
export interface GeminiAdapterOptions {
|
|
7
|
+
client: AllternitComputers;
|
|
8
|
+
computerId: string;
|
|
9
|
+
/** URL used by the `search` function. Default https://www.google.com. */
|
|
10
|
+
searchUrl?: string;
|
|
11
|
+
}
|
|
12
|
+
type Step = Pick<ToolsetCall, "member" | "input">;
|
|
13
|
+
/** "Control+Shift+T" → "ctrl+shift+t". */
|
|
14
|
+
export declare function geminiKeys(keys: string): string;
|
|
15
|
+
/** Contract calls for one Gemini computer_use function call, in order. */
|
|
16
|
+
export declare function geminiToCalls(fc: GeminiFunctionCall, searchUrl?: string): Step[];
|
|
17
|
+
export interface GeminiStepResult {
|
|
18
|
+
/** A `functionResponse` part for the next turn. */
|
|
19
|
+
functionResponse: {
|
|
20
|
+
name: string;
|
|
21
|
+
response: Record<string, unknown>;
|
|
22
|
+
};
|
|
23
|
+
/** The screenshot after the action, as an `inlineData` part. */
|
|
24
|
+
inlineData: {
|
|
25
|
+
mimeType: string;
|
|
26
|
+
data: string;
|
|
27
|
+
};
|
|
28
|
+
error?: ToolsetResult;
|
|
29
|
+
}
|
|
30
|
+
/** Run one Gemini function call and return the parts Gemini expects back. */
|
|
31
|
+
export declare function runGeminiCall(o: GeminiAdapterOptions, fc: GeminiFunctionCall): Promise<GeminiStepResult>;
|
|
32
|
+
/**
|
|
33
|
+
* The `computer_v2` function declaration for Gemini models: the ten structured
|
|
34
|
+
* members next to the `computer_use` declaration. Route matching
|
|
35
|
+
* `functionCall` parts through runGeminiV2Call.
|
|
36
|
+
*/
|
|
37
|
+
export declare function geminiComputerV2Declaration(): {
|
|
38
|
+
name: string;
|
|
39
|
+
description: string;
|
|
40
|
+
parameters: Record<string, unknown>;
|
|
41
|
+
};
|
|
42
|
+
export interface GeminiV2AdapterOptions extends GeminiAdapterOptions {
|
|
43
|
+
/** Called when the server holds the call (409 approval_required). Without it the ApprovalRequiredError propagates, like the pixel adapter. */
|
|
44
|
+
onApproval?: (approval: Approval) => boolean | Promise<boolean>;
|
|
45
|
+
}
|
|
46
|
+
export interface GeminiV2StepResult {
|
|
47
|
+
/** A `functionResponse` part for the next turn. */
|
|
48
|
+
functionResponse: {
|
|
49
|
+
name: string;
|
|
50
|
+
response: Record<string, unknown>;
|
|
51
|
+
};
|
|
52
|
+
error?: ToolsetResult;
|
|
53
|
+
}
|
|
54
|
+
/** Run one `computer_v2` function call (`{action, ...fields}`) and return the function response part. */
|
|
55
|
+
export declare function runGeminiV2Call(o: GeminiV2AdapterOptions, name: string, input: unknown): Promise<GeminiV2StepResult>;
|
|
56
|
+
export {};
|
package/dist/gemini.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Gemini computer_use adapter: runs one predefined function call on an Allternit
|
|
2
|
+
// hosted computer. Gemini coordinates are 0–999 normalized, so every call is sent
|
|
3
|
+
// with coordinate_space "normalized_1000" and the server scales to the screen.
|
|
4
|
+
import { resultImage, resultText } from "./client.js";
|
|
5
|
+
import { computerV2Tool, runComputerV2Member } from "./v2.js";
|
|
6
|
+
const pt = (x, y) => [Math.round(Number(x)), Math.round(Number(y))];
|
|
7
|
+
const key = (text) => ({ member: "key", input: { text } });
|
|
8
|
+
const typed = (text) => ({ member: "type", input: { text } });
|
|
9
|
+
const goTo = (url) => [key("ctrl+l"), typed(url), key("Return")];
|
|
10
|
+
/** "Control+Shift+T" → "ctrl+shift+t". */
|
|
11
|
+
export function geminiKeys(keys) {
|
|
12
|
+
const map = { control: "ctrl", meta: "super", command: "super", enter: "Return", escape: "Escape", backspace: "BackSpace", tab: "Tab", delete: "Delete" };
|
|
13
|
+
return keys.split("+").map((k) => k.trim()).map((k) => map[k.toLowerCase()] ?? (k.length === 1 ? k.toLowerCase() : k)).join("+");
|
|
14
|
+
}
|
|
15
|
+
/** Contract calls for one Gemini computer_use function call, in order. */
|
|
16
|
+
export function geminiToCalls(fc, searchUrl = "https://www.google.com") {
|
|
17
|
+
const a = fc.args ?? {};
|
|
18
|
+
const dir = String(a.direction ?? "down");
|
|
19
|
+
switch (fc.name) {
|
|
20
|
+
case "open_web_browser":
|
|
21
|
+
return [];
|
|
22
|
+
case "wait_5_seconds":
|
|
23
|
+
return [{ member: "wait", input: { duration: 5 } }];
|
|
24
|
+
case "go_back":
|
|
25
|
+
return [key("alt+Left")];
|
|
26
|
+
case "go_forward":
|
|
27
|
+
return [key("alt+Right")];
|
|
28
|
+
case "search":
|
|
29
|
+
return goTo(searchUrl);
|
|
30
|
+
case "navigate":
|
|
31
|
+
return goTo(String(a.url ?? ""));
|
|
32
|
+
case "click_at":
|
|
33
|
+
return [{ member: "left_click", input: { coordinate: pt(a.x, a.y) } }];
|
|
34
|
+
case "hover_at":
|
|
35
|
+
return [{ member: "mouse_move", input: { coordinate: pt(a.x, a.y) } }];
|
|
36
|
+
case "type_text_at": {
|
|
37
|
+
const steps = [{ member: "left_click", input: { coordinate: pt(a.x, a.y) } }];
|
|
38
|
+
if (a.clear_before_typing !== false)
|
|
39
|
+
steps.push(key("ctrl+a"), key("BackSpace"));
|
|
40
|
+
steps.push(typed(String(a.text ?? "")));
|
|
41
|
+
if (a.press_enter !== false)
|
|
42
|
+
steps.push(key("Return"));
|
|
43
|
+
return steps;
|
|
44
|
+
}
|
|
45
|
+
case "key_combination":
|
|
46
|
+
return [key(geminiKeys(String(a.keys ?? "")))];
|
|
47
|
+
case "scroll_document":
|
|
48
|
+
return [{ member: "scroll", input: { coordinate: [500, 500], scroll_direction: dir, scroll_amount: 5 } }];
|
|
49
|
+
case "scroll_at": {
|
|
50
|
+
const amount = Math.max(1, Math.round(Number(a.magnitude ?? 800) / 160));
|
|
51
|
+
return [{ member: "scroll", input: { coordinate: pt(a.x, a.y), scroll_direction: dir, scroll_amount: amount } }];
|
|
52
|
+
}
|
|
53
|
+
case "drag_and_drop":
|
|
54
|
+
return [{ member: "left_click_drag", input: { start_coordinate: pt(a.x, a.y), coordinate: pt(a.destination_x, a.destination_y) } }];
|
|
55
|
+
default:
|
|
56
|
+
throw new Error(`Unsupported Gemini computer_use function: ${fc.name}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Run one Gemini function call and return the parts Gemini expects back. */
|
|
60
|
+
export async function runGeminiCall(o, fc) {
|
|
61
|
+
const base = { toolset: "computer", coordinate_space: "normalized_1000" };
|
|
62
|
+
let error;
|
|
63
|
+
for (const step of geminiToCalls(fc, o.searchUrl)) {
|
|
64
|
+
const r = await o.client.toolset(o.computerId, { ...base, ...step });
|
|
65
|
+
if (r.is_error) {
|
|
66
|
+
error = r;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const s = await o.client.toolset(o.computerId, { ...base, member: "screenshot", input: {} });
|
|
71
|
+
const img = resultImage(s);
|
|
72
|
+
if (!img)
|
|
73
|
+
throw new Error("Allternit computer returned no screenshot.");
|
|
74
|
+
const response = { url: "" };
|
|
75
|
+
if (error)
|
|
76
|
+
response.error = error.content.flatMap((b) => (b.type === "text" ? [b.text] : [])).join("\n");
|
|
77
|
+
return {
|
|
78
|
+
functionResponse: { name: fc.name, response },
|
|
79
|
+
inlineData: { mimeType: img.media_type, data: img.data },
|
|
80
|
+
...(error ? { error } : {}),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
// ------------------------------------------------------------------ computer_v2
|
|
84
|
+
/**
|
|
85
|
+
* The `computer_v2` function declaration for Gemini models: the ten structured
|
|
86
|
+
* members next to the `computer_use` declaration. Route matching
|
|
87
|
+
* `functionCall` parts through runGeminiV2Call.
|
|
88
|
+
*/
|
|
89
|
+
export function geminiComputerV2Declaration() {
|
|
90
|
+
const t = computerV2Tool();
|
|
91
|
+
return { name: t.name, description: t.description, parameters: t.schema };
|
|
92
|
+
}
|
|
93
|
+
/** Run one `computer_v2` function call (`{action, ...fields}`) and return the function response part. */
|
|
94
|
+
export async function runGeminiV2Call(o, name, input) {
|
|
95
|
+
const { action, ...fields } = (input ?? {});
|
|
96
|
+
if (!action)
|
|
97
|
+
throw new Error("The computer_v2 call needs an action.");
|
|
98
|
+
const base = { client: o.client, computerId: o.computerId };
|
|
99
|
+
const res = o.onApproval
|
|
100
|
+
? await runComputerV2Member({ ...base, onApproval: o.onApproval }, action, fields)
|
|
101
|
+
: await o.client.toolset(o.computerId, { toolset: "computer", member: action, input: fields });
|
|
102
|
+
const text = resultText(res);
|
|
103
|
+
const response = { result: text };
|
|
104
|
+
if (res.is_error)
|
|
105
|
+
response.error = text || "The action failed.";
|
|
106
|
+
return { functionResponse: { name, response }, ...(res.is_error ? { error: res } : {}) };
|
|
107
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// @allternit/computer-driver: drive Allternit hosted computers from any model loop.
|
|
2
|
+
// The Anthropic toolsets live on the "@allternit/computer-driver/anthropic" subpath
|
|
3
|
+
// so this entry never loads the optional @anthropic-ai/sdk peer.
|
|
4
|
+
export * from "./client.js";
|
|
5
|
+
export * from "./v2.js";
|
|
6
|
+
export * from "./openai.js";
|
|
7
|
+
export * from "./gemini.js";
|
package/dist/openai.d.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { type AllternitComputers, type Approval, type ToolsetCall, type ToolsetResult } from "./client.ts";
|
|
2
|
+
type Pt = {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
};
|
|
6
|
+
export type OpenAIComputerAction = {
|
|
7
|
+
type: "click";
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
button?: "left" | "right" | "wheel" | "back" | "forward";
|
|
11
|
+
} | {
|
|
12
|
+
type: "double_click";
|
|
13
|
+
x: number;
|
|
14
|
+
y: number;
|
|
15
|
+
} | {
|
|
16
|
+
type: "drag";
|
|
17
|
+
path: Pt[];
|
|
18
|
+
} | {
|
|
19
|
+
type: "keypress";
|
|
20
|
+
keys: string[];
|
|
21
|
+
} | {
|
|
22
|
+
type: "move";
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
} | {
|
|
26
|
+
type: "scroll";
|
|
27
|
+
x: number;
|
|
28
|
+
y: number;
|
|
29
|
+
scroll_x: number;
|
|
30
|
+
scroll_y: number;
|
|
31
|
+
} | {
|
|
32
|
+
type: "type";
|
|
33
|
+
text: string;
|
|
34
|
+
} | {
|
|
35
|
+
type: "wait";
|
|
36
|
+
ms?: number;
|
|
37
|
+
} | {
|
|
38
|
+
type: "screenshot";
|
|
39
|
+
};
|
|
40
|
+
export interface OpenAIAdapterOptions {
|
|
41
|
+
client: AllternitComputers;
|
|
42
|
+
computerId: string;
|
|
43
|
+
/** display_width / display_height you declared on the computer_use_preview tool. */
|
|
44
|
+
display?: {
|
|
45
|
+
width: number;
|
|
46
|
+
height: number;
|
|
47
|
+
};
|
|
48
|
+
/** Pixels of scroll per wheel click when converting scroll_x / scroll_y. Default 100. */
|
|
49
|
+
pixelsPerScrollClick?: number;
|
|
50
|
+
}
|
|
51
|
+
/** ["CTRL","C"] → "ctrl+c" (the contract's xdotool-style key text). */
|
|
52
|
+
export declare function openaiKeys(keys: string[]): string;
|
|
53
|
+
/** Contract calls for one OpenAI action, in order. */
|
|
54
|
+
export declare function openaiToCalls(a: OpenAIComputerAction, perClick?: number): Array<Pick<ToolsetCall, "member" | "input">>;
|
|
55
|
+
export interface OpenAIStepResult {
|
|
56
|
+
/** Ready for the Responses API `input`: `{type:"computer_call_output", call_id, output}`. */
|
|
57
|
+
output: {
|
|
58
|
+
type: "computer_call_output";
|
|
59
|
+
call_id: string;
|
|
60
|
+
output: {
|
|
61
|
+
type: "computer_screenshot";
|
|
62
|
+
image_url: string;
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
/** The last failed action result, if any action failed. */
|
|
66
|
+
error?: ToolsetResult;
|
|
67
|
+
}
|
|
68
|
+
/** Run one OpenAI `computer_call` (its `call_id` and `action`) and return the output item. */
|
|
69
|
+
export declare function runOpenAIAction(o: OpenAIAdapterOptions, callId: string, action: OpenAIComputerAction): Promise<OpenAIStepResult>;
|
|
70
|
+
/**
|
|
71
|
+
* The `computer_v2` function tool for the Responses API: the ten structured
|
|
72
|
+
* members next to the `computer_use_preview` tool. Register it in `tools`;
|
|
73
|
+
* route matching `function_call` items through runOpenAIV2Call.
|
|
74
|
+
*/
|
|
75
|
+
export declare function openaiComputerV2Tool(): {
|
|
76
|
+
type: "function";
|
|
77
|
+
name: string;
|
|
78
|
+
description: string;
|
|
79
|
+
parameters: Record<string, unknown>;
|
|
80
|
+
strict: false;
|
|
81
|
+
};
|
|
82
|
+
export interface OpenAIV2AdapterOptions extends OpenAIAdapterOptions {
|
|
83
|
+
/** Called when the server holds the call (409 approval_required). Without it the ApprovalRequiredError propagates, like the pixel adapter. */
|
|
84
|
+
onApproval?: (approval: Approval) => boolean | Promise<boolean>;
|
|
85
|
+
}
|
|
86
|
+
export interface OpenAIV2StepResult {
|
|
87
|
+
/** Ready for the Responses API `input`: `{type:"function_call_output", call_id, output}`. */
|
|
88
|
+
output: {
|
|
89
|
+
type: "function_call_output";
|
|
90
|
+
call_id: string;
|
|
91
|
+
output: string;
|
|
92
|
+
};
|
|
93
|
+
/** The failed action result, if the action failed. */
|
|
94
|
+
error?: ToolsetResult;
|
|
95
|
+
}
|
|
96
|
+
/** Run one `computer_v2` function call (`{action, ...fields}`) and return the `function_call_output` item. */
|
|
97
|
+
export declare function runOpenAIV2Call(o: OpenAIV2AdapterOptions, callId: string, input: unknown): Promise<OpenAIV2StepResult>;
|
|
98
|
+
export {};
|
package/dist/openai.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// OpenAI computer-use adapter: runs one `computer_call` action on an Allternit
|
|
2
|
+
// hosted computer and returns a `computer_call_output` with the next screenshot.
|
|
3
|
+
import { resultImage } from "./client.js";
|
|
4
|
+
import { computerV2Tool, runComputerV2Member } from "./v2.js";
|
|
5
|
+
const KEYS = {
|
|
6
|
+
CTRL: "ctrl", CONTROL: "ctrl", ALT: "alt", OPTION: "alt", SHIFT: "shift", CMD: "super", META: "super", SUPER: "super",
|
|
7
|
+
WIN: "super", ENTER: "Return", RETURN: "Return", ESC: "Escape", ESCAPE: "Escape", TAB: "Tab", SPACE: "space",
|
|
8
|
+
BACKSPACE: "BackSpace", DELETE: "Delete", UP: "Up", DOWN: "Down", LEFT: "Left", RIGHT: "Right",
|
|
9
|
+
ARROWUP: "Up", ARROWDOWN: "Down", ARROWLEFT: "Left", ARROWRIGHT: "Right", HOME: "Home", END: "End",
|
|
10
|
+
PAGEUP: "Page_Up", PAGEDOWN: "Page_Down",
|
|
11
|
+
};
|
|
12
|
+
/** ["CTRL","C"] → "ctrl+c" (the contract's xdotool-style key text). */
|
|
13
|
+
export function openaiKeys(keys) {
|
|
14
|
+
return keys.map((k) => KEYS[k.toUpperCase()] ?? (k.length === 1 ? k.toLowerCase() : k)).join("+");
|
|
15
|
+
}
|
|
16
|
+
/** Contract calls for one OpenAI action, in order. */
|
|
17
|
+
export function openaiToCalls(a, perClick = 100) {
|
|
18
|
+
const c = (x, y) => [Math.round(x), Math.round(y)];
|
|
19
|
+
switch (a.type) {
|
|
20
|
+
case "click": {
|
|
21
|
+
const b = a.button ?? "left";
|
|
22
|
+
if (b === "back")
|
|
23
|
+
return [{ member: "key", input: { text: "alt+Left" } }];
|
|
24
|
+
if (b === "forward")
|
|
25
|
+
return [{ member: "key", input: { text: "alt+Right" } }];
|
|
26
|
+
const member = b === "right" ? "right_click" : b === "wheel" ? "middle_click" : "left_click";
|
|
27
|
+
return [{ member, input: { coordinate: c(a.x, a.y) } }];
|
|
28
|
+
}
|
|
29
|
+
case "double_click":
|
|
30
|
+
return [{ member: "double_click", input: { coordinate: c(a.x, a.y) } }];
|
|
31
|
+
case "drag": {
|
|
32
|
+
const p = a.path;
|
|
33
|
+
if (p.length < 2)
|
|
34
|
+
return [];
|
|
35
|
+
if (p.length === 2)
|
|
36
|
+
return [{ member: "left_click_drag", input: { start_coordinate: c(p[0].x, p[0].y), coordinate: c(p[1].x, p[1].y) } }];
|
|
37
|
+
return [
|
|
38
|
+
{ member: "mouse_move", input: { coordinate: c(p[0].x, p[0].y) } },
|
|
39
|
+
{ member: "left_mouse_down", input: {} },
|
|
40
|
+
...p.slice(1).map((q) => ({ member: "mouse_move", input: { coordinate: c(q.x, q.y) } })),
|
|
41
|
+
{ member: "left_mouse_up", input: {} },
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
case "keypress":
|
|
45
|
+
return [{ member: "key", input: { text: openaiKeys(a.keys) } }];
|
|
46
|
+
case "move":
|
|
47
|
+
return [{ member: "mouse_move", input: { coordinate: c(a.x, a.y) } }];
|
|
48
|
+
case "scroll": {
|
|
49
|
+
const out = [];
|
|
50
|
+
const amt = (v) => Math.max(1, Math.round(Math.abs(v) / perClick));
|
|
51
|
+
if (a.scroll_y)
|
|
52
|
+
out.push({ member: "scroll", input: { coordinate: c(a.x, a.y), scroll_direction: a.scroll_y > 0 ? "down" : "up", scroll_amount: amt(a.scroll_y) } });
|
|
53
|
+
if (a.scroll_x)
|
|
54
|
+
out.push({ member: "scroll", input: { coordinate: c(a.x, a.y), scroll_direction: a.scroll_x > 0 ? "right" : "left", scroll_amount: amt(a.scroll_x) } });
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
case "type":
|
|
58
|
+
return [{ member: "type", input: { text: a.text } }];
|
|
59
|
+
case "wait":
|
|
60
|
+
return [{ member: "wait", input: { duration: Math.max(1, Math.round((a.ms ?? 2000) / 1000)) } }];
|
|
61
|
+
case "screenshot":
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** Run one OpenAI `computer_call` (its `call_id` and `action`) and return the output item. */
|
|
66
|
+
export async function runOpenAIAction(o, callId, action) {
|
|
67
|
+
const frame = o.display ? { model_frame: o.display, coordinate_space: "pixels" } : {};
|
|
68
|
+
let error;
|
|
69
|
+
for (const step of openaiToCalls(action, o.pixelsPerScrollClick)) {
|
|
70
|
+
const r = await o.client.toolset(o.computerId, { toolset: "computer", ...step, ...frame });
|
|
71
|
+
if (r.is_error) {
|
|
72
|
+
error = r;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const s = await o.client.toolset(o.computerId, { toolset: "computer", member: "screenshot", input: {}, ...frame });
|
|
77
|
+
const img = resultImage(s);
|
|
78
|
+
if (!img)
|
|
79
|
+
throw new Error("Allternit computer returned no screenshot.");
|
|
80
|
+
return {
|
|
81
|
+
output: {
|
|
82
|
+
type: "computer_call_output",
|
|
83
|
+
call_id: callId,
|
|
84
|
+
output: { type: "computer_screenshot", image_url: `data:${img.media_type};base64,${img.data}` },
|
|
85
|
+
},
|
|
86
|
+
...(error ? { error } : {}),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
// ------------------------------------------------------------------ computer_v2
|
|
90
|
+
/**
|
|
91
|
+
* The `computer_v2` function tool for the Responses API: the ten structured
|
|
92
|
+
* members next to the `computer_use_preview` tool. Register it in `tools`;
|
|
93
|
+
* route matching `function_call` items through runOpenAIV2Call.
|
|
94
|
+
*/
|
|
95
|
+
export function openaiComputerV2Tool() {
|
|
96
|
+
const t = computerV2Tool();
|
|
97
|
+
return { type: "function", name: t.name, description: t.description, parameters: t.schema, strict: false };
|
|
98
|
+
}
|
|
99
|
+
/** Run one `computer_v2` function call (`{action, ...fields}`) and return the `function_call_output` item. */
|
|
100
|
+
export async function runOpenAIV2Call(o, callId, input) {
|
|
101
|
+
const { action, ...fields } = (input ?? {});
|
|
102
|
+
if (!action)
|
|
103
|
+
throw new Error("The computer_v2 call needs an action.");
|
|
104
|
+
const base = { client: o.client, computerId: o.computerId };
|
|
105
|
+
const res = o.onApproval
|
|
106
|
+
? await runComputerV2Member({ ...base, onApproval: o.onApproval }, action, fields)
|
|
107
|
+
: await o.client.toolset(o.computerId, { toolset: "computer", member: action, input: fields });
|
|
108
|
+
const text = res.content.flatMap((b) => (b.type === "text" ? [b.text] : [])).join("\n");
|
|
109
|
+
return {
|
|
110
|
+
output: { type: "function_call_output", call_id: callId, output: text || "Done." },
|
|
111
|
+
...(res.is_error ? { error: res } : {}),
|
|
112
|
+
};
|
|
113
|
+
}
|