automify 0.1.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +401 -0
  4. package/SECURITY.md +17 -0
  5. package/examples/anthropic-provider.js +18 -0
  6. package/examples/browser-basic.js +30 -0
  7. package/examples/browser-with-safety.js +38 -0
  8. package/examples/claude-model-adapter.js +141 -0
  9. package/examples/cli-basic.js +20 -0
  10. package/examples/cli-docker.js +42 -0
  11. package/examples/custom-computer.js +18 -0
  12. package/examples/custom-model-adapter.js +48 -0
  13. package/examples/desktop-docker.js +37 -0
  14. package/examples/desktop-local.js +28 -0
  15. package/examples/evaluate-image.js +26 -0
  16. package/examples/files-and-shared-folder.js +42 -0
  17. package/package.json +74 -0
  18. package/scripts/generate-argument-reference.js +17 -0
  19. package/scripts/install-browser.js +12 -0
  20. package/scripts/install-desktop.js +281 -0
  21. package/src/index.d.ts +1049 -0
  22. package/src/index.js +83 -0
  23. package/src/lib/adapter-locks.js +93 -0
  24. package/src/lib/adapter-toolkit.js +239 -0
  25. package/src/lib/anthropic-model-adapter.js +451 -0
  26. package/src/lib/argument-reference.js +98 -0
  27. package/src/lib/automify.js +938 -0
  28. package/src/lib/browser-automify.js +89 -0
  29. package/src/lib/cli-automify.js +520 -0
  30. package/src/lib/computer-automify.js +103 -0
  31. package/src/lib/docker-cli-automify.js +517 -0
  32. package/src/lib/docker-desktop-computer.js +725 -0
  33. package/src/lib/errors.js +24 -0
  34. package/src/lib/file-data.js +140 -0
  35. package/src/lib/init.js +217 -0
  36. package/src/lib/local-desktop-computer.js +963 -0
  37. package/src/lib/model-adapter.js +32 -0
  38. package/src/lib/openai-responses-client.js +162 -0
  39. package/src/lib/output.js +57 -0
  40. package/src/lib/playwright-computer.js +363 -0
  41. package/src/lib/presets.js +141 -0
  42. package/src/lib/result.js +95 -0
  43. package/src/lib/runtime.js +471 -0
  44. package/src/lib/virtual-shared-folder.js +109 -0
  45. package/src/lib/zod-output.js +26 -0
  46. package/src/zod.d.ts +12 -0
  47. package/src/zod.js +5 -0
@@ -0,0 +1,141 @@
1
+ import { AutomifyError } from "./errors.js";
2
+
3
+ const REPO_COMMAND = {
4
+ cwd: process.cwd(),
5
+ allow: ["git", "node", "npm", "pnpm", "yarn", "bun"],
6
+ block: [/^rm\s+-rf\b/]
7
+ };
8
+
9
+ export function applyBrowserPreset(options = {}) {
10
+ switch (options.preset) {
11
+ case undefined:
12
+ case null:
13
+ return options;
14
+ case "browser-review":
15
+ return mergePreset(
16
+ {
17
+ limits: { steps: 50 },
18
+ screenshot: { detail: "high" }
19
+ },
20
+ options
21
+ );
22
+ default:
23
+ throw unknownPreset("browser", options.preset, ["browser-review"]);
24
+ }
25
+ }
26
+
27
+ export function applyCliPreset(options = {}) {
28
+ switch (options.preset) {
29
+ case undefined:
30
+ case null:
31
+ return options;
32
+ case "repo":
33
+ return mergePreset({ command: REPO_COMMAND }, options);
34
+ case "locked-down-cli":
35
+ return mergePreset(
36
+ {
37
+ command: {
38
+ approval: "always",
39
+ allow: [],
40
+ block: [/^rm\b/, /^sudo\b/, /^curl\b/, /^wget\b/]
41
+ },
42
+ limits: { steps: 20 }
43
+ },
44
+ options
45
+ );
46
+ default:
47
+ throw unknownPreset("cli", options.preset, ["repo", "locked-down-cli"]);
48
+ }
49
+ }
50
+
51
+ export function applyDockerCliPreset(options = {}) {
52
+ switch (options.preset) {
53
+ case undefined:
54
+ case null:
55
+ return options;
56
+ case "repo":
57
+ return mergePreset(
58
+ {
59
+ command: REPO_COMMAND,
60
+ shared: {
61
+ hostPath: process.cwd(),
62
+ containerPath: "/workspace"
63
+ }
64
+ },
65
+ options
66
+ );
67
+ case "locked-down-cli":
68
+ return mergePreset(
69
+ {
70
+ command: {
71
+ approval: "always",
72
+ allow: [],
73
+ block: [/^rm\b/, /^sudo\b/, /^curl\b/, /^wget\b/]
74
+ },
75
+ container: {
76
+ network: "none",
77
+ sandbox: true,
78
+ readOnly: true
79
+ },
80
+ limits: { steps: 20 }
81
+ },
82
+ options
83
+ );
84
+ default:
85
+ throw unknownPreset("Docker CLI", options.preset, ["repo", "locked-down-cli"]);
86
+ }
87
+ }
88
+
89
+ export const applyVirtualCliPreset = applyDockerCliPreset;
90
+
91
+ export function applyDockerDesktopPreset(options = {}) {
92
+ switch (options.preset) {
93
+ case undefined:
94
+ case null:
95
+ return options;
96
+ case "desktop-review":
97
+ return mergePreset(
98
+ {
99
+ viewport: { width: 1440, height: 900 },
100
+ waitMs: 750,
101
+ screenshotSettleMs: 500
102
+ },
103
+ options
104
+ );
105
+ default:
106
+ throw unknownPreset("Docker desktop", options.preset, ["desktop-review"]);
107
+ }
108
+ }
109
+
110
+ export const applyVirtualDesktopPreset = applyDockerDesktopPreset;
111
+
112
+ function mergePreset(defaults, options) {
113
+ return {
114
+ ...defaults,
115
+ ...options,
116
+ command: mergeObject(defaults.command, options.command ?? options.commands),
117
+ commands: options.commands,
118
+ container: mergeObject(defaults.container, options.container),
119
+ desktop: mergeObject(defaults.desktop, options.desktop),
120
+ limits: mergeObject(defaults.limits, options.limits),
121
+ safety: mergeObject(defaults.safety, options.safety),
122
+ screenshot: mergeObject(defaults.screenshot, options.screenshot),
123
+ screenshots: mergeObject(defaults.screenshots, options.screenshots),
124
+ viewport: mergeObject(defaults.viewport, options.viewport),
125
+ shared: options.shared ?? options.sharedFolder ?? defaults.shared
126
+ };
127
+ }
128
+
129
+ function mergeObject(defaults, overrides) {
130
+ if (defaults == null && overrides == null) return undefined;
131
+ return {
132
+ ...(defaults ?? {}),
133
+ ...(overrides ?? {})
134
+ };
135
+ }
136
+
137
+ function unknownPreset(surface, preset, allowed) {
138
+ return new AutomifyError(
139
+ `Unknown ${surface} preset ${JSON.stringify(preset)}. Available presets: ${allowed.map((name) => JSON.stringify(name)).join(", ")}.`
140
+ );
141
+ }
@@ -0,0 +1,95 @@
1
+ import { getOutputText, parseOutputJson } from "./adapter-toolkit.js";
2
+ import { AutomifyError } from "./errors.js";
3
+
4
+ export function buildRunResult(response, steps, output) {
5
+ const text = getOutputText(response);
6
+ const result = {
7
+ response,
8
+ steps,
9
+ ok: true,
10
+ status: "succeeded",
11
+ completed: true,
12
+ stopReason: "done",
13
+ text
14
+ };
15
+
16
+ if (shouldParseStructuredOutput(output, text)) {
17
+ const parsed = parseOutputJson(response);
18
+ result.parsed = typeof output.parseResult === "function" ? output.parseResult(parsed) : parsed;
19
+ }
20
+
21
+ return result;
22
+ }
23
+
24
+ export function buildTextConfig(output) {
25
+ if (!output) return undefined;
26
+
27
+ if (output.type === "text") {
28
+ return { format: { type: "text" } };
29
+ }
30
+
31
+ if (output.type === "json_object") {
32
+ return { format: { type: "json_object" } };
33
+ }
34
+
35
+ if (output.type === "json_schema") {
36
+ if (typeof output.name !== "string" || output.name.trim() === "") {
37
+ throw new AutomifyError("Structured output requires a non-empty output.name.");
38
+ }
39
+
40
+ if (!output.schema || typeof output.schema !== "object") {
41
+ throw new AutomifyError("Structured output requires an output.schema object.");
42
+ }
43
+
44
+ return {
45
+ format: removeUndefined({
46
+ type: "json_schema",
47
+ name: output.name,
48
+ description: output.description,
49
+ schema: output.schema,
50
+ strict: output.strict
51
+ })
52
+ };
53
+ }
54
+
55
+ throw new AutomifyError(`Unsupported output.type: ${output.type}`);
56
+ }
57
+
58
+ export function buildOutputInstruction(textConfig) {
59
+ const format = textConfig?.format;
60
+ if (!format || format.type === "text") return "";
61
+
62
+ if (format.type === "json_object") {
63
+ return [
64
+ "Return only a valid JSON object.",
65
+ "Do not wrap it in Markdown.",
66
+ "Do not include prose before or after the JSON."
67
+ ].join(" ");
68
+ }
69
+
70
+ if (format.type === "json_schema") {
71
+ return [
72
+ "Return only valid JSON matching this schema.",
73
+ "Do not wrap it in Markdown.",
74
+ "Do not include prose before or after the JSON.",
75
+ JSON.stringify({
76
+ name: format.name,
77
+ description: format.description,
78
+ schema: format.schema,
79
+ strict: format.strict
80
+ })
81
+ ].join(" ");
82
+ }
83
+
84
+ return "";
85
+ }
86
+
87
+ function shouldParseStructuredOutput(output, text) {
88
+ return Boolean(
89
+ text && output && (output.type === "json_schema" || output.type === "json_object") && output.parse !== false
90
+ );
91
+ }
92
+
93
+ function removeUndefined(object) {
94
+ return Object.fromEntries(Object.entries(object).filter(([, value]) => value !== undefined));
95
+ }
@@ -0,0 +1,471 @@
1
+ import { appendFileSync, mkdirSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+
4
+ import { AutomifyError } from "./errors.js";
5
+
6
+ export const AUTOMIFY_OPTION_KEYS = new Set([
7
+ "openaiApiKey",
8
+ "client",
9
+ "computer",
10
+ "model",
11
+ "baseURL",
12
+ "fetchImpl",
13
+ "maxSteps",
14
+ "limits",
15
+ "request",
16
+ "requestOptions",
17
+ "viewport",
18
+ "displayWidth",
19
+ "displayHeight",
20
+ "environment",
21
+ "reasoning",
22
+ "safety",
23
+ "safetyIdentifier",
24
+ "allowedDomains",
25
+ "hooks",
26
+ "onStep",
27
+ "onRequest",
28
+ "onResponse",
29
+ "onComplete",
30
+ "screenshot",
31
+ "redactScreenshot",
32
+ "screenshotDetail",
33
+ "screenshotMaxWidth",
34
+ "screenshotMaxHeight",
35
+ "screenshotResize",
36
+ "sendInitialScreenshot",
37
+ "initialScreenshot",
38
+ "finalScreenshot",
39
+ "actionScreenshots",
40
+ "screenshots",
41
+ "trace",
42
+ "silent",
43
+ "debug",
44
+ "logFile"
45
+ ]);
46
+
47
+ export async function callHook(hook, ...args) {
48
+ if (typeof hook === "function") {
49
+ await hook(...args);
50
+ }
51
+ }
52
+
53
+ export function debugLog(debug, scope, message, details, options = {}) {
54
+ if (options.silent || !debug) return;
55
+ const label = `[${scope}] ${message}`;
56
+ if (typeof debug === "function") {
57
+ debug(label, details);
58
+ return;
59
+ }
60
+ if (options.full) {
61
+ console.error(formatFullLog(label, details));
62
+ return;
63
+ }
64
+ console.error(formatDefaultLog(label, details));
65
+ }
66
+
67
+ export function logLogger(debug, options = {}) {
68
+ if (options.silent || !debug) return null;
69
+ if (typeof debug === "function") return debug;
70
+ return console.error;
71
+ }
72
+
73
+ export function writeDebugLogFile(logFile, scope, message, details, options = {}) {
74
+ if (options.silent || !logFile) return;
75
+
76
+ try {
77
+ mkdirSync(dirname(logFile), { recursive: true });
78
+ appendFileSync(
79
+ logFile,
80
+ `${JSON.stringify({
81
+ at: new Date().toISOString(),
82
+ scope,
83
+ message,
84
+ label: `[${scope}] ${message}`,
85
+ details
86
+ })}\n`
87
+ );
88
+ } catch {
89
+ // Logging must not change automation behavior.
90
+ }
91
+ }
92
+
93
+ export function normalizeLogFile(value, scope = "logFile") {
94
+ if (value == null || value === false) return undefined;
95
+ if (typeof value !== "string" || value.trim() === "") {
96
+ throw new AutomifyError(`${scope} must be a non-empty file path.`);
97
+ }
98
+ return value;
99
+ }
100
+
101
+ function formatDefaultLog(label, details) {
102
+ const summary = summarizeLogDetails(details);
103
+ return summary ? `${label} ${summary}` : label;
104
+ }
105
+
106
+ function formatFullLog(label, details) {
107
+ if (details === undefined) return label;
108
+ return `${label} ${JSON.stringify(details, null, 2)}`;
109
+ }
110
+
111
+ function summarizeLogDetails(details) {
112
+ if (!details || typeof details !== "object") return "";
113
+ const parts = [];
114
+ const add = (key, value) => {
115
+ if (value == null || value === "") return;
116
+ parts.push(`${key}=${value}`);
117
+ };
118
+
119
+ if (details.label) parts.push(details.label);
120
+ if (details.index != null) add("step", details.index);
121
+ add("phase", details.phase);
122
+ add("step", details.step);
123
+ add("action", describeAction(details.action));
124
+ if (details.executableAction && JSON.stringify(details.executableAction) !== JSON.stringify(details.action)) {
125
+ add("executed", describeAction(details.executableAction));
126
+ }
127
+ if (Array.isArray(details.actions) && details.actions.length > 1) {
128
+ add(
129
+ "actions",
130
+ details.actions
131
+ .map((action) => describeAction(action))
132
+ .filter(Boolean)
133
+ .join(",")
134
+ );
135
+ }
136
+ add("call", details.call?.call_id ?? details.callId);
137
+ if (details.safetyChecks?.length || details.call?.pending_safety_checks?.length) {
138
+ add("safetyChecks", details.safetyChecks?.length ?? details.call.pending_safety_checks.length);
139
+ }
140
+ if (details.currentUrl) add("url", JSON.stringify(details.currentUrl));
141
+
142
+ if (details.payload) {
143
+ add("phase", details.meta?.phase);
144
+ add("step", details.meta?.step);
145
+ add("model", details.payload.model);
146
+ add("previous", shortenId(details.payload.previous_response_id));
147
+ add("tools", details.payload.toolCount);
148
+ add("inputs", details.payload.inputCount);
149
+ }
150
+
151
+ if (details.response) {
152
+ add("phase", details.meta?.phase);
153
+ add("step", details.meta?.step);
154
+ add("response", shortenId(details.response.id));
155
+ if (details.response.outputTypes?.length) add("outputs", details.response.outputTypes.join(","));
156
+ if (details.response.actions?.length) add("actions", details.response.actions.join(","));
157
+ }
158
+
159
+ if (details.command?.command) add("command", JSON.stringify(details.command.command));
160
+ if (details.command?.cwd) add("cwd", JSON.stringify(details.command.cwd));
161
+ add("cwd", details.cwd ? JSON.stringify(details.cwd) : undefined);
162
+ add("timeoutMs", details.command?.timeoutMs ?? details.timeoutMs);
163
+ add("exitCode", details.exitCode);
164
+ add("signal", details.signal);
165
+ if (details.timedOut != null) add("timedOut", details.timedOut);
166
+ add("stdoutLength", details.stdoutLength);
167
+ add("stderrLength", details.stderrLength);
168
+ if (typeof details.stdout === "string" && details.stdout.length > 0) add("stdout", previewText(details.stdout));
169
+ if (typeof details.stderr === "string" && details.stderr.length > 0) add("stderr", previewText(details.stderr));
170
+ if (details.status) add("status", details.status);
171
+ if (details.ok != null) add("ok", details.ok);
172
+ if (details.completed != null) add("completed", details.completed);
173
+ if (details.stopReason) add("stop", details.stopReason);
174
+ if (Array.isArray(details.steps)) add("steps", details.steps.length);
175
+ if (details.containerName) add("container", details.containerName);
176
+ if (details.image) add("image", details.image);
177
+ if (details.width && details.height) add("size", `${details.width}x${details.height}`);
178
+ if (details.display) add("display", details.display);
179
+ if (details.installDependencies != null) add("installDeps", details.installDependencies);
180
+ if (Array.isArray(details.args)) add("args", JSON.stringify(details.args));
181
+ if (details.path) add("path", JSON.stringify(details.path));
182
+ add("bytes", details.bytes);
183
+ add("writtenBytes", details.writtenBytes);
184
+ if (details.originalWidth && details.originalHeight)
185
+ add("original", `${details.originalWidth}x${details.originalHeight}`);
186
+ if (details.resized != null) add("resized", details.resized);
187
+ if (details.reused != null) add("reused", details.reused);
188
+ add("detail", details.detail);
189
+ add("durationMs", details.durationMs);
190
+
191
+ return parts.join(" ");
192
+ }
193
+
194
+ export function mergeRequestOptions(requestOptions, payload) {
195
+ if (!requestOptions) return payload;
196
+ return {
197
+ ...requestOptions,
198
+ ...payload
199
+ };
200
+ }
201
+
202
+ export const DO_OPTION_KEYS = new Set([
203
+ "data",
204
+ "evaluate",
205
+ "filesToEvaluate",
206
+ "model",
207
+ "maxSteps",
208
+ "limits",
209
+ "request",
210
+ "requestOptions",
211
+ "output",
212
+ "displayWidth",
213
+ "displayHeight",
214
+ "environment",
215
+ "reasoning",
216
+ "safetyIdentifier",
217
+ "allowedDomains",
218
+ "safety",
219
+ "onStep",
220
+ "onComplete",
221
+ "hooks",
222
+ "redactScreenshot",
223
+ "screenshotDetail",
224
+ "screenshotMaxWidth",
225
+ "screenshotMaxHeight",
226
+ "screenshotResize",
227
+ "sendInitialScreenshot",
228
+ "initialScreenshot",
229
+ "finalScreenshot",
230
+ "actionScreenshots",
231
+ "screenshots",
232
+ "screenshot",
233
+ "trace",
234
+ "silent",
235
+ "onSafetyCheck",
236
+ "cwd",
237
+ "env",
238
+ "shell",
239
+ "timeoutMs",
240
+ "approval",
241
+ "allowedCommands",
242
+ "blockedCommands",
243
+ "instructions",
244
+ "confirmCommand",
245
+ "command",
246
+ "commands"
247
+ ]);
248
+ export const COMMAND_OPTION_KEYS = new Set([
249
+ "cwd",
250
+ "env",
251
+ "shell",
252
+ "timeoutMs",
253
+ "timeout",
254
+ "approval",
255
+ "allow",
256
+ "allowed",
257
+ "allowedCommands",
258
+ "block",
259
+ "blocked",
260
+ "blockedCommands",
261
+ "confirm",
262
+ "confirmCommand"
263
+ ]);
264
+
265
+ export function normalizeDoArguments(dataOrOptions, maybeOptions) {
266
+ if (maybeOptions !== undefined) {
267
+ throw new AutomifyError("do() now accepts a single run object: do(instruction, { data, output, ...options }).");
268
+ }
269
+
270
+ if (dataOrOptions === undefined) {
271
+ return { data: {}, options: {} };
272
+ }
273
+
274
+ if (!dataOrOptions || typeof dataOrOptions !== "object" || Array.isArray(dataOrOptions)) {
275
+ throw new AutomifyError("do() run options must be an object, for example { data: {...}, output }.");
276
+ }
277
+
278
+ const unknownKeys = Object.keys(dataOrOptions).filter((key) => !DO_OPTION_KEYS.has(key));
279
+ if (unknownKeys.length > 0) {
280
+ throw new AutomifyError(
281
+ `${unknownOptionMessage("do()", unknownKeys[0], DO_OPTION_KEYS)} Put input values under data: { ${unknownKeys[0]}: ... }.`
282
+ );
283
+ }
284
+
285
+ const { data = {}, ...rawOptions } = dataOrOptions;
286
+ return {
287
+ data: data ?? {},
288
+ options: normalizeDoOptionAliases(rawOptions)
289
+ };
290
+ }
291
+
292
+ export function assertKnownOptions(scope, options, allowedKeys) {
293
+ if (options == null) return;
294
+ if (typeof options !== "object" || Array.isArray(options)) {
295
+ throw new AutomifyError(`${scope} options must be an object.`);
296
+ }
297
+
298
+ const allowed = allowedKeys instanceof Set ? allowedKeys : new Set(allowedKeys);
299
+ const unknownKey = Object.keys(options).find((key) => !allowed.has(key));
300
+ if (unknownKey) {
301
+ throw new AutomifyError(unknownOptionMessage(scope, unknownKey, allowed));
302
+ }
303
+ }
304
+
305
+ export function mergeOptionKeys(...sets) {
306
+ return new Set(sets.flatMap((set) => [...set]));
307
+ }
308
+
309
+ export function pickKnownOptions(options, allowedKeys) {
310
+ const allowed = allowedKeys instanceof Set ? allowedKeys : new Set(allowedKeys);
311
+ return Object.fromEntries(Object.entries(options ?? {}).filter(([key]) => allowed.has(key)));
312
+ }
313
+
314
+ export function normalizeAutomifyOptions(options = {}) {
315
+ assertKnownOptions("Automify", options, AUTOMIFY_OPTION_KEYS);
316
+ const { viewport, limits, request, safety, hooks, screenshots, screenshot, ...rest } = options;
317
+ const viewportOptions = viewport ?? {};
318
+ const limitOptions = limits ?? {};
319
+ const safetyOptions = safety ?? {};
320
+ const hookOptions = hooks ?? {};
321
+ const screenshotPaths = screenshots ?? {};
322
+ const screenshotOptions = screenshot ?? {};
323
+
324
+ return cleanUndefined({
325
+ ...rest,
326
+ debug: rest.debug ?? false,
327
+ logFile: normalizeLogFile(rest.logFile, "Automify logFile"),
328
+ maxSteps: rest.maxSteps ?? limitOptions.steps ?? limitOptions.maxSteps,
329
+ requestOptions: rest.requestOptions ?? request,
330
+ displayWidth: rest.displayWidth ?? viewportOptions.width,
331
+ displayHeight: rest.displayHeight ?? viewportOptions.height,
332
+ safetyIdentifier: rest.safetyIdentifier ?? safetyOptions.identifier ?? safetyOptions.safetyIdentifier,
333
+ allowedDomains: rest.allowedDomains ?? safetyOptions.domains ?? safetyOptions.allowedDomains,
334
+ onStep: rest.onStep ?? hookOptions.step ?? hookOptions.onStep,
335
+ onComplete: rest.onComplete ?? hookOptions.complete ?? hookOptions.onComplete,
336
+ initialScreenshot: rest.initialScreenshot ?? screenshotPaths.initial,
337
+ finalScreenshot: rest.finalScreenshot ?? screenshotPaths.final,
338
+ actionScreenshots: rest.actionScreenshots ?? screenshotPaths.actions ?? screenshotPaths.actionScreenshots,
339
+ screenshotDetail: rest.screenshotDetail ?? screenshotOptions.detail,
340
+ screenshotMaxWidth: rest.screenshotMaxWidth ?? screenshotOptions.maxWidth ?? screenshotOptions.screenshotMaxWidth,
341
+ screenshotMaxHeight:
342
+ rest.screenshotMaxHeight ?? screenshotOptions.maxHeight ?? screenshotOptions.screenshotMaxHeight,
343
+ screenshotResize: rest.screenshotResize ?? screenshotOptions.resize ?? screenshotOptions.screenshotResize,
344
+ sendInitialScreenshot: rest.sendInitialScreenshot ?? screenshotOptions.sendInitialScreenshot,
345
+ redactScreenshot: rest.redactScreenshot ?? screenshotOptions.redact ?? screenshotOptions.redactScreenshot
346
+ });
347
+ }
348
+
349
+ function normalizeDoOptionAliases(options) {
350
+ const { evaluate, limits, request, safety, hooks, screenshots, screenshot, command, commands, ...rest } = options;
351
+
352
+ const commandOptions = commands ?? command;
353
+ assertKnownOptions("do() command", commandOptions, COMMAND_OPTION_KEYS);
354
+ return cleanUndefined({
355
+ ...rest,
356
+ filesToEvaluate: rest.filesToEvaluate ?? evaluate,
357
+ maxSteps: rest.maxSteps ?? limits?.steps ?? limits?.maxSteps,
358
+ requestOptions: rest.requestOptions ?? request,
359
+ safetyIdentifier: rest.safetyIdentifier ?? safety?.identifier ?? safety?.safetyIdentifier,
360
+ allowedDomains: rest.allowedDomains ?? safety?.domains ?? safety?.allowedDomains,
361
+ onSafetyCheck: rest.onSafetyCheck ?? safety?.onCheck ?? safety?.onSafetyCheck,
362
+ onStep: rest.onStep ?? hooks?.step ?? hooks?.onStep,
363
+ onComplete: rest.onComplete ?? hooks?.complete ?? hooks?.onComplete,
364
+ initialScreenshot: rest.initialScreenshot ?? screenshots?.initial,
365
+ finalScreenshot: rest.finalScreenshot ?? screenshots?.final,
366
+ actionScreenshots: rest.actionScreenshots ?? screenshots?.actions ?? screenshots?.actionScreenshots,
367
+ screenshotDetail: rest.screenshotDetail ?? screenshot?.detail,
368
+ screenshotMaxWidth: rest.screenshotMaxWidth ?? screenshot?.maxWidth ?? screenshot?.screenshotMaxWidth,
369
+ screenshotMaxHeight: rest.screenshotMaxHeight ?? screenshot?.maxHeight ?? screenshot?.screenshotMaxHeight,
370
+ screenshotResize: rest.screenshotResize ?? screenshot?.resize ?? screenshot?.screenshotResize,
371
+ sendInitialScreenshot: rest.sendInitialScreenshot ?? screenshot?.sendInitialScreenshot,
372
+ redactScreenshot: rest.redactScreenshot ?? screenshot?.redact ?? screenshot?.redactScreenshot,
373
+ cwd: rest.cwd ?? commandOptions?.cwd,
374
+ env: rest.env ?? commandOptions?.env,
375
+ shell: rest.shell ?? commandOptions?.shell,
376
+ timeoutMs: rest.timeoutMs ?? commandOptions?.timeoutMs ?? commandOptions?.timeout,
377
+ approval: rest.approval ?? commandOptions?.approval,
378
+ allowedCommands:
379
+ rest.allowedCommands ?? commandOptions?.allow ?? commandOptions?.allowed ?? commandOptions?.allowedCommands,
380
+ blockedCommands:
381
+ rest.blockedCommands ?? commandOptions?.block ?? commandOptions?.blocked ?? commandOptions?.blockedCommands,
382
+ confirmCommand: rest.confirmCommand ?? commandOptions?.confirm ?? commandOptions?.confirmCommand
383
+ });
384
+ }
385
+
386
+ function cleanUndefined(value) {
387
+ return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
388
+ }
389
+
390
+ function unknownOptionMessage(scope, unknownKey, allowedKeys) {
391
+ const suggestion = closestOption(unknownKey, [...allowedKeys]);
392
+ return `Unknown ${scope} option ${JSON.stringify(unknownKey)}.${suggestion ? ` Did you mean ${JSON.stringify(suggestion)}?` : ""}`;
393
+ }
394
+
395
+ function closestOption(input, candidates) {
396
+ let best = null;
397
+ let bestDistance = Infinity;
398
+ for (const candidate of candidates) {
399
+ const distance = editDistance(input.toLowerCase(), candidate.toLowerCase());
400
+ if (distance < bestDistance) {
401
+ best = candidate;
402
+ bestDistance = distance;
403
+ }
404
+ }
405
+
406
+ return bestDistance <= Math.max(2, Math.floor(input.length / 3)) ? best : null;
407
+ }
408
+
409
+ function editDistance(a, b) {
410
+ const previous = Array.from({ length: b.length + 1 }, (_, index) => index);
411
+ const current = Array.from({ length: b.length + 1 }, () => 0);
412
+
413
+ for (let i = 1; i <= a.length; i += 1) {
414
+ current[0] = i;
415
+ for (let j = 1; j <= b.length; j += 1) {
416
+ current[j] = Math.min(previous[j] + 1, current[j - 1] + 1, previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
417
+ }
418
+ previous.splice(0, previous.length, ...current);
419
+ }
420
+
421
+ return previous[b.length];
422
+ }
423
+
424
+ export function summarizePayload(payload) {
425
+ return {
426
+ model: payload.model,
427
+ previous_response_id: payload.previous_response_id,
428
+ toolCount: payload.tools?.length ?? 0,
429
+ inputCount: Array.isArray(payload.input) ? payload.input.length : undefined
430
+ };
431
+ }
432
+
433
+ export function summarizeResponse(response) {
434
+ return {
435
+ id: response?.id,
436
+ outputTypes: response?.output?.map((item) => item.type) ?? [],
437
+ actions:
438
+ response?.output?.flatMap((item) => {
439
+ if (item?.type !== "computer_call") return [];
440
+ const actions =
441
+ Array.isArray(item.actions) && item.actions.length > 0 ? item.actions : [item.action].filter(Boolean);
442
+ return actions.map((action) => describeAction(action)).filter(Boolean);
443
+ }) ?? []
444
+ };
445
+ }
446
+
447
+ function describeAction(action) {
448
+ if (!action?.type) return "";
449
+ const parts = [action.type];
450
+ if (action.x != null || action.y != null) parts.push(`@${action.x ?? "?"},${action.y ?? "?"}`);
451
+ if (action.button) parts.push(`button:${action.button}`);
452
+ const keys = action.keys ?? [action.key].filter(Boolean);
453
+ if (keys?.length) parts.push(`keys:${keys.join("+")}`);
454
+ if (action.text != null) parts.push(`text:${JSON.stringify(String(action.text).slice(0, 80))}`);
455
+ if (action.ms != null || action.duration_ms != null) parts.push(`ms:${action.ms ?? action.duration_ms}`);
456
+ if (action.scroll_x != null || action.scroll_y != null)
457
+ parts.push(`scroll:${action.scroll_x ?? 0},${action.scroll_y ?? 0}`);
458
+ if (action.delta_x != null || action.delta_y != null)
459
+ parts.push(`delta:${action.delta_x ?? 0},${action.delta_y ?? 0}`);
460
+ return parts.join(":");
461
+ }
462
+
463
+ function shortenId(value) {
464
+ if (typeof value !== "string" || value.length <= 18) return value;
465
+ return `${value.slice(0, 10)}...${value.slice(-6)}`;
466
+ }
467
+
468
+ function previewText(value) {
469
+ const compact = value.replace(/\s+/g, " ").trim();
470
+ return JSON.stringify(compact.length > 120 ? `${compact.slice(0, 117)}...` : compact);
471
+ }