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.
- package/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/README.md +401 -0
- package/SECURITY.md +17 -0
- package/examples/anthropic-provider.js +18 -0
- package/examples/browser-basic.js +30 -0
- package/examples/browser-with-safety.js +38 -0
- package/examples/claude-model-adapter.js +141 -0
- package/examples/cli-basic.js +20 -0
- package/examples/cli-docker.js +42 -0
- package/examples/custom-computer.js +18 -0
- package/examples/custom-model-adapter.js +48 -0
- package/examples/desktop-docker.js +37 -0
- package/examples/desktop-local.js +28 -0
- package/examples/evaluate-image.js +26 -0
- package/examples/files-and-shared-folder.js +42 -0
- package/package.json +74 -0
- package/scripts/generate-argument-reference.js +17 -0
- package/scripts/install-browser.js +12 -0
- package/scripts/install-desktop.js +281 -0
- package/src/index.d.ts +1049 -0
- package/src/index.js +83 -0
- package/src/lib/adapter-locks.js +93 -0
- package/src/lib/adapter-toolkit.js +239 -0
- package/src/lib/anthropic-model-adapter.js +451 -0
- package/src/lib/argument-reference.js +98 -0
- package/src/lib/automify.js +938 -0
- package/src/lib/browser-automify.js +89 -0
- package/src/lib/cli-automify.js +520 -0
- package/src/lib/computer-automify.js +103 -0
- package/src/lib/docker-cli-automify.js +517 -0
- package/src/lib/docker-desktop-computer.js +725 -0
- package/src/lib/errors.js +24 -0
- package/src/lib/file-data.js +140 -0
- package/src/lib/init.js +217 -0
- package/src/lib/local-desktop-computer.js +963 -0
- package/src/lib/model-adapter.js +32 -0
- package/src/lib/openai-responses-client.js +162 -0
- package/src/lib/output.js +57 -0
- package/src/lib/playwright-computer.js +363 -0
- package/src/lib/presets.js +141 -0
- package/src/lib/result.js +95 -0
- package/src/lib/runtime.js +471 -0
- package/src/lib/virtual-shared-folder.js +109 -0
- package/src/lib/zod-output.js +26 -0
- package/src/zod.d.ts +12 -0
- package/src/zod.js +5 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export class AutomifyError extends Error {
|
|
2
|
+
constructor(message, options = {}) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = "AutomifyError";
|
|
5
|
+
if (options.cause) this.cause = options.cause;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class SafetyCheckError extends AutomifyError {
|
|
10
|
+
constructor(checks, action) {
|
|
11
|
+
super("Computer-use returned pending safety checks that were not acknowledged.");
|
|
12
|
+
this.name = "SafetyCheckError";
|
|
13
|
+
this.checks = checks;
|
|
14
|
+
this.action = action;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class MaxStepsExceededError extends AutomifyError {
|
|
19
|
+
constructor(maxSteps) {
|
|
20
|
+
super(`Computer-use exceeded the configured maxSteps limit of ${maxSteps}.`);
|
|
21
|
+
this.name = "MaxStepsExceededError";
|
|
22
|
+
this.maxSteps = maxSteps;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { readFile, stat } from "node:fs/promises";
|
|
2
|
+
import { basename, extname, resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { AutomifyError } from "./errors.js";
|
|
5
|
+
|
|
6
|
+
export async function fileToData(file, options = {}) {
|
|
7
|
+
const descriptor = normalizeFileDescriptor(file);
|
|
8
|
+
const path = resolve(descriptor.path);
|
|
9
|
+
const info = await stat(path);
|
|
10
|
+
if (!info.isFile()) {
|
|
11
|
+
throw new AutomifyError(`fileToData expected a file: ${path}`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const mediaType = descriptor.mediaType ?? options.mediaType ?? mediaTypeForPath(path);
|
|
15
|
+
const format = descriptor.format ?? options.format ?? "text";
|
|
16
|
+
const entry = {
|
|
17
|
+
name: descriptor.name ?? basename(path),
|
|
18
|
+
path,
|
|
19
|
+
mediaType,
|
|
20
|
+
size: info.size
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
if (format === "metadata") return entry;
|
|
24
|
+
|
|
25
|
+
const buffer = await readFile(path);
|
|
26
|
+
if (format === "base64") {
|
|
27
|
+
return { ...entry, base64: buffer.toString("base64") };
|
|
28
|
+
}
|
|
29
|
+
if (format === "data_url" || format === "dataUrl") {
|
|
30
|
+
return { ...entry, dataUrl: `data:${mediaType};base64,${buffer.toString("base64")}` };
|
|
31
|
+
}
|
|
32
|
+
if (format === "buffer") {
|
|
33
|
+
return { ...entry, buffer };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return { ...entry, text: buffer.toString(descriptor.encoding ?? options.encoding ?? "utf8") };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function filesToData(files, options = {}) {
|
|
40
|
+
const list = Array.isArray(files) ? files : [files];
|
|
41
|
+
return Promise.all(list.map((file) => fileToData(file, options)));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function fileToEvaluate(file, options = {}) {
|
|
45
|
+
const descriptor = normalizeFileDescriptor(file);
|
|
46
|
+
const path = resolve(descriptor.path);
|
|
47
|
+
const info = await stat(path);
|
|
48
|
+
if (!info.isFile()) {
|
|
49
|
+
throw new AutomifyError(`fileToEvaluate expected a file: ${path}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const name = descriptor.name ?? basename(path);
|
|
53
|
+
const mediaType = descriptor.mediaType ?? options.mediaType ?? mediaTypeForPath(path);
|
|
54
|
+
const detail = descriptor.detail ?? options.detail;
|
|
55
|
+
|
|
56
|
+
if (mediaType.startsWith("image/")) {
|
|
57
|
+
const buffer = await readFile(path);
|
|
58
|
+
return {
|
|
59
|
+
type: "input_image",
|
|
60
|
+
image_url: `data:${mediaType};base64,${buffer.toString("base64")}`,
|
|
61
|
+
...(detail ? { detail } : {})
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (isTextMediaType(mediaType)) {
|
|
66
|
+
const maxBytes = positiveInteger(descriptor.maxBytes ?? options.maxBytes) ?? 200_000;
|
|
67
|
+
const buffer = await readFile(path);
|
|
68
|
+
const truncated = buffer.byteLength > maxBytes;
|
|
69
|
+
const text = buffer.subarray(0, maxBytes).toString(descriptor.encoding ?? options.encoding ?? "utf8");
|
|
70
|
+
return {
|
|
71
|
+
type: "input_text",
|
|
72
|
+
text: [
|
|
73
|
+
`File for evaluation: ${name}`,
|
|
74
|
+
`Path: ${path}`,
|
|
75
|
+
`Media type: ${mediaType}`,
|
|
76
|
+
truncated ? `Content truncated to ${maxBytes} bytes from ${buffer.byteLength} bytes.` : null,
|
|
77
|
+
"",
|
|
78
|
+
text
|
|
79
|
+
].filter((part) => part != null).join("\n")
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
type: "input_text",
|
|
85
|
+
text: [
|
|
86
|
+
`File for evaluation: ${name}`,
|
|
87
|
+
`Path: ${path}`,
|
|
88
|
+
`Media type: ${mediaType}`,
|
|
89
|
+
`Size: ${info.size} bytes`,
|
|
90
|
+
"This file type is represented as metadata only."
|
|
91
|
+
].join("\n")
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function filesToEvaluate(files, options = {}) {
|
|
96
|
+
const list = Array.isArray(files) ? files : [files];
|
|
97
|
+
return Promise.all(list.map((file) => fileToEvaluate(file, options)));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function normalizeFileDescriptor(file) {
|
|
101
|
+
if (typeof file === "string") return { path: file };
|
|
102
|
+
if (file && typeof file === "object" && typeof file.path === "string") return file;
|
|
103
|
+
throw new AutomifyError("fileToData files must be paths or objects with a path.");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function mediaTypeForPath(path) {
|
|
107
|
+
const ext = extname(path).toLowerCase();
|
|
108
|
+
const types = {
|
|
109
|
+
".csv": "text/csv",
|
|
110
|
+
".gif": "image/gif",
|
|
111
|
+
".html": "text/html",
|
|
112
|
+
".jpeg": "image/jpeg",
|
|
113
|
+
".jpg": "image/jpeg",
|
|
114
|
+
".json": "application/json",
|
|
115
|
+
".md": "text/markdown",
|
|
116
|
+
".pdf": "application/pdf",
|
|
117
|
+
".png": "image/png",
|
|
118
|
+
".svg": "image/svg+xml",
|
|
119
|
+
".txt": "text/plain",
|
|
120
|
+
".webp": "image/webp",
|
|
121
|
+
".xml": "application/xml",
|
|
122
|
+
".yaml": "application/yaml",
|
|
123
|
+
".yml": "application/yaml"
|
|
124
|
+
};
|
|
125
|
+
return types[ext] ?? "application/octet-stream";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function isTextMediaType(mediaType) {
|
|
129
|
+
return mediaType.startsWith("text/") || [
|
|
130
|
+
"application/json",
|
|
131
|
+
"application/xml",
|
|
132
|
+
"application/yaml"
|
|
133
|
+
].includes(mediaType);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function positiveInteger(value) {
|
|
137
|
+
const number = Number(value);
|
|
138
|
+
if (!Number.isFinite(number) || number <= 0) return null;
|
|
139
|
+
return Math.floor(number);
|
|
140
|
+
}
|
package/src/lib/init.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { createAutomify } from "./automify.js";
|
|
2
|
+
import { createBrowserAutomify, withBrowserAutomify } from "./browser-automify.js";
|
|
3
|
+
import { createCliAutomify } from "./cli-automify.js";
|
|
4
|
+
import { createDockerCliAutomify } from "./docker-cli-automify.js";
|
|
5
|
+
import {
|
|
6
|
+
createComputerAutomify,
|
|
7
|
+
createDockerComputerAutomify,
|
|
8
|
+
createLocalComputerAutomify
|
|
9
|
+
} from "./computer-automify.js";
|
|
10
|
+
import { createAnthropicModelAdapter } from "./anthropic-model-adapter.js";
|
|
11
|
+
import { AutomifyError } from "./errors.js";
|
|
12
|
+
import { createModelAdapter } from "./model-adapter.js";
|
|
13
|
+
import { OpenAIResponsesClient } from "./openai-responses-client.js";
|
|
14
|
+
|
|
15
|
+
const ANTHROPIC_COMPUTER_SCREENSHOT_MAX_WIDTH = 1280;
|
|
16
|
+
const ANTHROPIC_COMPUTER_SCREENSHOT_MAX_HEIGHT = 800;
|
|
17
|
+
|
|
18
|
+
export function initAutomify(options = {}) {
|
|
19
|
+
const provider = normalizeProvider(options);
|
|
20
|
+
const client = createClient(provider);
|
|
21
|
+
const limits = options.limits ?? {};
|
|
22
|
+
const safety = options.safety ?? {};
|
|
23
|
+
const hooks = options.hooks ?? {};
|
|
24
|
+
const screenshots = options.screenshots ?? {};
|
|
25
|
+
const screenshot = options.screenshot ?? {};
|
|
26
|
+
const providerScreenshotDefaults = screenshotDefaultsForProvider(provider);
|
|
27
|
+
const defaults = {
|
|
28
|
+
model: provider.model,
|
|
29
|
+
maxSteps: options.maxSteps ?? limits.steps ?? limits.maxSteps,
|
|
30
|
+
requestOptions: options.requestOptions,
|
|
31
|
+
reasoning: options.reasoning,
|
|
32
|
+
safetyIdentifier: options.safetyIdentifier ?? safety.identifier ?? safety.safetyIdentifier,
|
|
33
|
+
allowedDomains: options.allowedDomains ?? safety.domains ?? safety.allowedDomains,
|
|
34
|
+
onStep: options.onStep ?? hooks.step ?? hooks.onStep,
|
|
35
|
+
onRequest: options.onRequest,
|
|
36
|
+
onResponse: options.onResponse,
|
|
37
|
+
onComplete: options.onComplete ?? hooks.complete ?? hooks.onComplete,
|
|
38
|
+
redactScreenshot: options.redactScreenshot ?? screenshot.redact ?? screenshot.redactScreenshot,
|
|
39
|
+
screenshotDetail: options.screenshotDetail ?? screenshot.detail,
|
|
40
|
+
screenshotMaxWidth:
|
|
41
|
+
options.screenshotMaxWidth ??
|
|
42
|
+
screenshot.maxWidth ??
|
|
43
|
+
screenshot.screenshotMaxWidth ??
|
|
44
|
+
providerScreenshotDefaults.maxWidth,
|
|
45
|
+
screenshotMaxHeight:
|
|
46
|
+
options.screenshotMaxHeight ??
|
|
47
|
+
screenshot.maxHeight ??
|
|
48
|
+
screenshot.screenshotMaxHeight ??
|
|
49
|
+
providerScreenshotDefaults.maxHeight,
|
|
50
|
+
screenshotResize: options.screenshotResize ?? screenshot.resize ?? screenshot.screenshotResize,
|
|
51
|
+
initialScreenshot: options.initialScreenshot ?? screenshots.initial,
|
|
52
|
+
finalScreenshot: options.finalScreenshot ?? screenshots.final,
|
|
53
|
+
actionScreenshots: options.actionScreenshots ?? screenshots.actions ?? screenshots.actionScreenshots,
|
|
54
|
+
trace: options.trace,
|
|
55
|
+
silent: options.silent,
|
|
56
|
+
debug: options.debug ?? false
|
|
57
|
+
};
|
|
58
|
+
const computerDefaults = {
|
|
59
|
+
...defaults,
|
|
60
|
+
model: options.computerModel ?? provider.computerModel ?? provider.model
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
client,
|
|
65
|
+
|
|
66
|
+
browser(browserOptions = {}) {
|
|
67
|
+
return createBrowserAutomify({
|
|
68
|
+
...computerDefaults,
|
|
69
|
+
...browserOptions,
|
|
70
|
+
client
|
|
71
|
+
});
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
withBrowser(browserOptions = {}, run) {
|
|
75
|
+
return withBrowserAutomify(
|
|
76
|
+
{
|
|
77
|
+
...computerDefaults,
|
|
78
|
+
...browserOptions,
|
|
79
|
+
client
|
|
80
|
+
},
|
|
81
|
+
run
|
|
82
|
+
);
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
cli(cliOptions = {}) {
|
|
86
|
+
return createCliAutomify({
|
|
87
|
+
...defaults,
|
|
88
|
+
...cliOptions,
|
|
89
|
+
client
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
dockerCli(cliOptions = {}) {
|
|
94
|
+
return createDockerCliAutomify({
|
|
95
|
+
...defaults,
|
|
96
|
+
...cliOptions,
|
|
97
|
+
client
|
|
98
|
+
});
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
dockerComputer(computerOptions = {}) {
|
|
102
|
+
return createDockerComputerAutomify({
|
|
103
|
+
...computerDefaults,
|
|
104
|
+
...computerOptions,
|
|
105
|
+
client
|
|
106
|
+
});
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
localComputer(computerOptions = {}) {
|
|
110
|
+
return createLocalComputerAutomify({
|
|
111
|
+
...computerDefaults,
|
|
112
|
+
...computerOptions,
|
|
113
|
+
client
|
|
114
|
+
});
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
virtualCli(cliOptions = {}) {
|
|
118
|
+
return createDockerCliAutomify({
|
|
119
|
+
...defaults,
|
|
120
|
+
...cliOptions,
|
|
121
|
+
client
|
|
122
|
+
});
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
computer(computerOptions = {}) {
|
|
126
|
+
return createComputerAutomify({
|
|
127
|
+
...computerDefaults,
|
|
128
|
+
...computerOptions,
|
|
129
|
+
client
|
|
130
|
+
});
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
custom(automifyOptions = {}) {
|
|
134
|
+
return createAutomify({
|
|
135
|
+
...defaults,
|
|
136
|
+
...automifyOptions,
|
|
137
|
+
client
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function screenshotDefaultsForProvider(provider) {
|
|
144
|
+
if (provider.type !== "anthropic") return {};
|
|
145
|
+
return {
|
|
146
|
+
maxWidth: ANTHROPIC_COMPUTER_SCREENSHOT_MAX_WIDTH,
|
|
147
|
+
maxHeight: ANTHROPIC_COMPUTER_SCREENSHOT_MAX_HEIGHT
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function createClient(provider) {
|
|
152
|
+
if (provider.type === "custom") {
|
|
153
|
+
if (provider.client) return provider.client;
|
|
154
|
+
return createModelAdapter(provider.adapter, provider.options ?? {});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (provider.type === "anthropic") {
|
|
158
|
+
return createAnthropicModelAdapter({
|
|
159
|
+
...withoutProviderKeys(provider),
|
|
160
|
+
anthropicApiKey: provider.apiKey
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (provider.type !== "openai") {
|
|
165
|
+
throw new AutomifyError(`Unsupported provider.type: ${provider.type}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return new OpenAIResponsesClient({
|
|
169
|
+
...withoutProviderKeys(provider),
|
|
170
|
+
openaiApiKey: provider.apiKey
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function normalizeProvider(options) {
|
|
175
|
+
if (options.provider && typeof options.provider === "object") {
|
|
176
|
+
const provider = {
|
|
177
|
+
...options.provider,
|
|
178
|
+
type: options.provider.type ?? options.provider.name
|
|
179
|
+
};
|
|
180
|
+
validateProvider(provider);
|
|
181
|
+
return provider;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (typeof options.provider === "string") {
|
|
185
|
+
throw new AutomifyError("initAutomify provider must be an object, for example { type: 'openai', apiKey, model }.");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
throw new AutomifyError(
|
|
189
|
+
"initAutomify requires provider: { type, apiKey, model } or provider: { type: 'custom', adapter, model }."
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function withoutProviderKeys(provider) {
|
|
194
|
+
const { type, name, apiKey, model, adapter, client, options, ...rest } = provider;
|
|
195
|
+
return rest;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function validateProvider(provider) {
|
|
199
|
+
if (!provider.type || typeof provider.type !== "string") {
|
|
200
|
+
throw new AutomifyError("provider.type is required.");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (!provider.model || typeof provider.model !== "string") {
|
|
204
|
+
throw new AutomifyError("provider.model is required.");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (provider.type === "custom") {
|
|
208
|
+
if (!provider.adapter && !provider.client) {
|
|
209
|
+
throw new AutomifyError("provider.adapter or provider.client is required when provider.type is 'custom'.");
|
|
210
|
+
}
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (!provider.apiKey || typeof provider.apiKey !== "string") {
|
|
215
|
+
throw new AutomifyError("provider.apiKey is required.");
|
|
216
|
+
}
|
|
217
|
+
}
|