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,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
+ }
@@ -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
+ }