@isomorph.ai/app-sdk 1.41.0 → 1.43.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/README.md
CHANGED
|
@@ -448,11 +448,11 @@ export default async function viewComputer(input: { taskId: string; pauseId: str
|
|
|
448
448
|
|
|
449
449
|
Keep that table protected by the app's user row rules. A trusted action must still check `owner_id` against `ctx.user.id`; never use workload identity as proof of human ownership.
|
|
450
450
|
|
|
451
|
-
- `requestTakeover(reason, requiredInput?)` requests a pause; `requiredInput` is `human` (default) or `login`. Poll `check()` until `waiting_for_input` gives `
|
|
452
|
-
- `view(pauseId)` returns `{status:'
|
|
453
|
-
- `input(pauseId, action)` sends the same `SignInAction` click/type/key/scroll commands used by `sendSignInInput
|
|
454
|
-
- `completeLogin(pauseId, {allowedHosts, username, password})`, or `completeLogin(pauseId, {otp})`, queues the separate browser-action-only login agent on this computer. **Call only from private server code, never expose this method or credentials as model tool inputs, prompts, progress, logs or job input/results.** Poll `check()` for `login.status: success | needs_input | human_help_needed`, with `input:'otp'` when another code is needed. Helper success leaves the main agent paused.
|
|
455
|
-
- `resume(pauseId)` explicitly resumes
|
|
451
|
+
- `requestTakeover(reason, requiredInput?)` requests a pause; `requiredInput` is `human` (default) or `login`. It answers the pause (`taskId`, `pauseId`, `status`, `reason`, `requiredInput`; the same pause again if one exists). Poll `check()` until `waiting_for_input` gives the pause plus the optional login result; while the task is not waiting, `check()` answers its progress (`status`, `steps`, `lastStep`, `summary`, `finalUrl`, `waiting`, `message`).
|
|
452
|
+
- `view(pauseId)` returns `{status:'ready', screenshot, url}`; while the task is still pausing, or once it resumed, the API answers 409 and the call throws. Reuse the existing sign-in screenshot UI: display the base64 PNG as an image, map clicks to image pixels, and forward keyboard/scroll events through app actions. Viewing does not itself resume the agent.
|
|
453
|
+
- `input(pauseId, action)` sends the same `SignInAction` click/type/key/scroll commands used by `sendSignInInput` (`typeText` may be up to 4096 characters here) and answers what the computer did. The CUA accepts human input only while the task is paused; it blocks the helper and main agent from acting at the same time.
|
|
454
|
+
- `completeLogin(pauseId, {allowedHosts, username, password})`, or `completeLogin(pauseId, {otp})`, queues the separate browser-action-only login agent on this computer. **Call only from private server code, never expose this method or credentials as model tool inputs, prompts, progress, logs or job input/results.** Poll `check()` for `login.status: success | needs_input | human_help_needed`, with `input:'otp'` when another code is needed, `reason` (`unverified_success`, `helper_gave_up`, `second_code_requested`, `helper_error:<type>`) when a person is needed, and the helper's short `evidence` (never a private value). Helper success leaves the main agent paused.
|
|
455
|
+
- `resume(pauseId)` explicitly resumes and answers the pause with `status: 'resume_queued'`; it does not wait for completion. On Continue, call this from the authorized app action, then enqueue the app's continuation job. That job creates `createComputerUseTool({...jobOptions, runKey: originalRunKey, attach:true})`: it polls the same task without sending `/tasks` again. Keep the original saved-session and download options if needed. Stale/duplicate resumes fail with 409; do not silently retry with a new pause ID. If enqueue fails, retry attaching to the already resumed task, not another resume.
|
|
456
456
|
- `cancel()` stops the task, including while waiting.
|
|
457
457
|
|
|
458
458
|
The outer agent may use app-defined tools for takeover and opening the authorized view. Resume requires an explicit user or app decision. Keep any outer-agent conversation in the app's existing job/task storage when ending a waiting job; this SDK does not persist the outer model's messages. The CUA persists its own conversation/resume state under the same task folder as screenshots and progress in its existing result bucket, without passwords or OTPs.
|
|
@@ -41,6 +41,8 @@ export declare function createComputerUseHandoffTools(options: ComputerUseHandof
|
|
|
41
41
|
login?: {
|
|
42
42
|
status: "success" | "needs_input" | "human_help_needed";
|
|
43
43
|
input?: "otp";
|
|
44
|
+
reason?: string;
|
|
45
|
+
evidence?: string;
|
|
44
46
|
};
|
|
45
47
|
}>;
|
|
46
48
|
}) => import("@ai-sdk/provider-utils").ToolResultOutput | PromiseLike<import("@ai-sdk/provider-utils").ToolResultOutput>) | undefined;
|
|
@@ -55,6 +57,8 @@ export declare function createComputerUseHandoffTools(options: ComputerUseHandof
|
|
|
55
57
|
login?: {
|
|
56
58
|
status: "success" | "needs_input" | "human_help_needed";
|
|
57
59
|
input?: "otp";
|
|
60
|
+
reason?: string;
|
|
61
|
+
evidence?: string;
|
|
58
62
|
};
|
|
59
63
|
}> | undefined;
|
|
60
64
|
execute: import("ai").ToolExecuteFunction<{
|
|
@@ -69,6 +73,8 @@ export declare function createComputerUseHandoffTools(options: ComputerUseHandof
|
|
|
69
73
|
login?: {
|
|
70
74
|
status: "success" | "needs_input" | "human_help_needed";
|
|
71
75
|
input?: "otp";
|
|
76
|
+
reason?: string;
|
|
77
|
+
evidence?: string;
|
|
72
78
|
};
|
|
73
79
|
}, NoInfer<import("@ai-sdk/provider-utils").Context>>;
|
|
74
80
|
} & {
|
|
@@ -101,6 +107,8 @@ export declare function createComputerUseHandoffTools(options: ComputerUseHandof
|
|
|
101
107
|
login?: {
|
|
102
108
|
status: "success" | "needs_input" | "human_help_needed";
|
|
103
109
|
input?: "otp";
|
|
110
|
+
reason?: string;
|
|
111
|
+
evidence?: string;
|
|
104
112
|
};
|
|
105
113
|
}, NoInfer<import("@ai-sdk/provider-utils").Context>>;
|
|
106
114
|
}) | ({
|
|
@@ -139,6 +147,8 @@ export declare function createComputerUseHandoffTools(options: ComputerUseHandof
|
|
|
139
147
|
login?: {
|
|
140
148
|
status: "success" | "needs_input" | "human_help_needed";
|
|
141
149
|
input?: "otp";
|
|
150
|
+
reason?: string;
|
|
151
|
+
evidence?: string;
|
|
142
152
|
};
|
|
143
153
|
}>;
|
|
144
154
|
}) => import("@ai-sdk/provider-utils").ToolResultOutput | PromiseLike<import("@ai-sdk/provider-utils").ToolResultOutput>) | undefined;
|
|
@@ -153,6 +163,8 @@ export declare function createComputerUseHandoffTools(options: ComputerUseHandof
|
|
|
153
163
|
login?: {
|
|
154
164
|
status: "success" | "needs_input" | "human_help_needed";
|
|
155
165
|
input?: "otp";
|
|
166
|
+
reason?: string;
|
|
167
|
+
evidence?: string;
|
|
156
168
|
};
|
|
157
169
|
}> | undefined;
|
|
158
170
|
execute: import("ai").ToolExecuteFunction<{
|
|
@@ -167,6 +179,8 @@ export declare function createComputerUseHandoffTools(options: ComputerUseHandof
|
|
|
167
179
|
login?: {
|
|
168
180
|
status: "success" | "needs_input" | "human_help_needed";
|
|
169
181
|
input?: "otp";
|
|
182
|
+
reason?: string;
|
|
183
|
+
evidence?: string;
|
|
170
184
|
};
|
|
171
185
|
}, NoInfer<import("@ai-sdk/provider-utils").Context>>;
|
|
172
186
|
} & {
|
|
@@ -199,6 +213,8 @@ export declare function createComputerUseHandoffTools(options: ComputerUseHandof
|
|
|
199
213
|
login?: {
|
|
200
214
|
status: "success" | "needs_input" | "human_help_needed";
|
|
201
215
|
input?: "otp";
|
|
216
|
+
reason?: string;
|
|
217
|
+
evidence?: string;
|
|
202
218
|
};
|
|
203
219
|
}, NoInfer<import("@ai-sdk/provider-utils").Context>>;
|
|
204
220
|
}) | ({
|
|
@@ -237,6 +253,8 @@ export declare function createComputerUseHandoffTools(options: ComputerUseHandof
|
|
|
237
253
|
login?: {
|
|
238
254
|
status: "success" | "needs_input" | "human_help_needed";
|
|
239
255
|
input?: "otp";
|
|
256
|
+
reason?: string;
|
|
257
|
+
evidence?: string;
|
|
240
258
|
};
|
|
241
259
|
}>;
|
|
242
260
|
}) => import("@ai-sdk/provider-utils").ToolResultOutput | PromiseLike<import("@ai-sdk/provider-utils").ToolResultOutput>) | undefined;
|
|
@@ -251,6 +269,8 @@ export declare function createComputerUseHandoffTools(options: ComputerUseHandof
|
|
|
251
269
|
login?: {
|
|
252
270
|
status: "success" | "needs_input" | "human_help_needed";
|
|
253
271
|
input?: "otp";
|
|
272
|
+
reason?: string;
|
|
273
|
+
evidence?: string;
|
|
254
274
|
};
|
|
255
275
|
}> | undefined;
|
|
256
276
|
execute: import("ai").ToolExecuteFunction<{
|
|
@@ -265,6 +285,8 @@ export declare function createComputerUseHandoffTools(options: ComputerUseHandof
|
|
|
265
285
|
login?: {
|
|
266
286
|
status: "success" | "needs_input" | "human_help_needed";
|
|
267
287
|
input?: "otp";
|
|
288
|
+
reason?: string;
|
|
289
|
+
evidence?: string;
|
|
268
290
|
};
|
|
269
291
|
}, NoInfer<import("@ai-sdk/provider-utils").Context>>;
|
|
270
292
|
} & {
|
|
@@ -290,6 +312,8 @@ export declare function createComputerUseHandoffTools(options: ComputerUseHandof
|
|
|
290
312
|
login?: {
|
|
291
313
|
status: "success" | "needs_input" | "human_help_needed";
|
|
292
314
|
input?: "otp";
|
|
315
|
+
reason?: string;
|
|
316
|
+
evidence?: string;
|
|
293
317
|
};
|
|
294
318
|
}, NoInfer<import("@ai-sdk/provider-utils").Context>>;
|
|
295
319
|
}) | ({
|
|
@@ -328,6 +352,8 @@ export declare function createComputerUseHandoffTools(options: ComputerUseHandof
|
|
|
328
352
|
login?: {
|
|
329
353
|
status: "success" | "needs_input" | "human_help_needed";
|
|
330
354
|
input?: "otp";
|
|
355
|
+
reason?: string;
|
|
356
|
+
evidence?: string;
|
|
331
357
|
};
|
|
332
358
|
}>;
|
|
333
359
|
}) => import("@ai-sdk/provider-utils").ToolResultOutput | PromiseLike<import("@ai-sdk/provider-utils").ToolResultOutput>) | undefined;
|
|
@@ -342,6 +368,8 @@ export declare function createComputerUseHandoffTools(options: ComputerUseHandof
|
|
|
342
368
|
login?: {
|
|
343
369
|
status: "success" | "needs_input" | "human_help_needed";
|
|
344
370
|
input?: "otp";
|
|
371
|
+
reason?: string;
|
|
372
|
+
evidence?: string;
|
|
345
373
|
};
|
|
346
374
|
}> | undefined;
|
|
347
375
|
execute: import("ai").ToolExecuteFunction<{
|
|
@@ -356,6 +384,8 @@ export declare function createComputerUseHandoffTools(options: ComputerUseHandof
|
|
|
356
384
|
login?: {
|
|
357
385
|
status: "success" | "needs_input" | "human_help_needed";
|
|
358
386
|
input?: "otp";
|
|
387
|
+
reason?: string;
|
|
388
|
+
evidence?: string;
|
|
359
389
|
};
|
|
360
390
|
}, NoInfer<import("@ai-sdk/provider-utils").Context>>;
|
|
361
391
|
} & {
|
|
@@ -381,6 +411,8 @@ export declare function createComputerUseHandoffTools(options: ComputerUseHandof
|
|
|
381
411
|
login?: {
|
|
382
412
|
status: "success" | "needs_input" | "human_help_needed";
|
|
383
413
|
input?: "otp";
|
|
414
|
+
reason?: string;
|
|
415
|
+
evidence?: string;
|
|
384
416
|
};
|
|
385
417
|
}, NoInfer<import("@ai-sdk/provider-utils").Context>>;
|
|
386
418
|
});
|
|
@@ -13,9 +13,13 @@ export interface ComputerUseWaiting {
|
|
|
13
13
|
reason: string;
|
|
14
14
|
requiredInput: 'login' | 'otp' | 'human';
|
|
15
15
|
message?: ComputerUseMessage;
|
|
16
|
+
/** The private login helper's answer; `reason` says why a person is needed (`unverified_success`, `helper_gave_up`,
|
|
17
|
+
* `second_code_requested`, `helper_error:<type>`) and `evidence` is the helper's short note on the page, never a private value. */
|
|
16
18
|
login?: {
|
|
17
19
|
status: 'success' | 'needs_input' | 'human_help_needed';
|
|
18
20
|
input?: 'otp';
|
|
21
|
+
reason?: string;
|
|
22
|
+
evidence?: string;
|
|
19
23
|
};
|
|
20
24
|
}
|
|
21
25
|
export declare function waitingInput(answer: {
|
|
@@ -27,12 +31,34 @@ export declare function waitingInput(answer: {
|
|
|
27
31
|
message?: unknown;
|
|
28
32
|
}): ComputerUseWaiting;
|
|
29
33
|
export type ComputerUsePage = {
|
|
30
|
-
status: 'pausing';
|
|
31
|
-
} | {
|
|
32
34
|
status: 'ready';
|
|
33
35
|
screenshot: string;
|
|
34
36
|
url: string;
|
|
35
37
|
};
|
|
38
|
+
/** A pause as the computer-use API describes it on takeover, login and resume; `status` is the task's, not only `waiting_for_input`. */
|
|
39
|
+
export type ComputerUsePause = {
|
|
40
|
+
taskId: string;
|
|
41
|
+
pauseId: string;
|
|
42
|
+
status: string;
|
|
43
|
+
reason: string;
|
|
44
|
+
requiredInput: 'login' | 'otp' | 'human';
|
|
45
|
+
};
|
|
46
|
+
/** What the paused computer answered to one input: the desktop function's own result, passed through. */
|
|
47
|
+
export type ComputerUseInputResult = Record<string, unknown>;
|
|
48
|
+
/** Progress of a task that is not waiting: the same fields `createComputerUseTool` reads while it polls. */
|
|
49
|
+
export type ComputerUseProgress = {
|
|
50
|
+
status: string;
|
|
51
|
+
steps?: number;
|
|
52
|
+
lastStep?: string | null;
|
|
53
|
+
summary?: string | null;
|
|
54
|
+
finalUrl?: string | null;
|
|
55
|
+
waiting?: {
|
|
56
|
+
reason: string;
|
|
57
|
+
since: string;
|
|
58
|
+
attempts: number;
|
|
59
|
+
} | null;
|
|
60
|
+
message?: ComputerUseMessage;
|
|
61
|
+
};
|
|
36
62
|
export type ComputerUseLoginInput = {
|
|
37
63
|
allowedHosts: readonly string[];
|
|
38
64
|
username: string;
|
|
@@ -49,23 +75,14 @@ export interface ComputerUseHandoffOptions extends ComputerUseTransport {
|
|
|
49
75
|
authorize(): Promise<boolean> | boolean;
|
|
50
76
|
}
|
|
51
77
|
export declare function createComputerUseHandoff(options: ComputerUseHandoffOptions): {
|
|
52
|
-
requestTakeover(reason: string, requiredInput?: "login" | "human"): Promise<
|
|
53
|
-
status: string;
|
|
54
|
-
}>;
|
|
78
|
+
requestTakeover(reason: string, requiredInput?: "login" | "human"): Promise<ComputerUsePause>;
|
|
55
79
|
view(pauseId: string): Promise<ComputerUsePage>;
|
|
56
|
-
input(pauseId: string, action: SignInAction): Promise<
|
|
57
|
-
status: "accepted";
|
|
58
|
-
}>;
|
|
80
|
+
input(pauseId: string, action: SignInAction): Promise<ComputerUseInputResult>;
|
|
59
81
|
completeLogin(pauseId: string, input: ComputerUseLoginInput): Promise<{
|
|
60
82
|
status: "login_queued";
|
|
61
83
|
}>;
|
|
62
|
-
check(): Promise<ComputerUseWaiting |
|
|
63
|
-
|
|
64
|
-
status: string;
|
|
65
|
-
}>;
|
|
66
|
-
resume(pauseId: string): Promise<{
|
|
67
|
-
status: "resume_queued";
|
|
68
|
-
}>;
|
|
84
|
+
check(): Promise<ComputerUseWaiting | ComputerUseProgress>;
|
|
85
|
+
resume(pauseId: string): Promise<ComputerUsePause>;
|
|
69
86
|
cancel(): Promise<{
|
|
70
87
|
status: string;
|
|
71
88
|
}>;
|
|
@@ -22,7 +22,11 @@ export function waitingInput(answer) {
|
|
|
22
22
|
result.message = message;
|
|
23
23
|
const login = answer.login;
|
|
24
24
|
if (login && ['success', 'needs_input', 'human_help_needed'].includes(login.status)) {
|
|
25
|
-
result.login = {
|
|
25
|
+
result.login = {
|
|
26
|
+
status: login.status, ...(login.input === 'otp' ? { input: 'otp' } : {}),
|
|
27
|
+
...(typeof login.reason === 'string' && login.reason ? { reason: login.reason.slice(0, 200) } : {}),
|
|
28
|
+
...(typeof login.evidence === 'string' && login.evidence ? { evidence: login.evidence.slice(0, 300) } : {}),
|
|
29
|
+
};
|
|
26
30
|
}
|
|
27
31
|
return result;
|
|
28
32
|
}
|
|
@@ -46,6 +50,11 @@ export function createComputerUseHandoff(options) {
|
|
|
46
50
|
throw refused(path, answer.status, privateInput ? undefined : answer.body);
|
|
47
51
|
return answer.body;
|
|
48
52
|
};
|
|
53
|
+
const pauseOf = (answer) => {
|
|
54
|
+
if (typeof answer.taskId !== 'string' || typeof answer.pauseId !== 'string' || !['login', 'otp', 'human'].includes(String(answer.requiredInput)))
|
|
55
|
+
throw new Error('The computer use API returned an incomplete pause.');
|
|
56
|
+
return { taskId: answer.taskId, pauseId: answer.pauseId, status: String(answer.status), reason: typeof answer.reason === 'string' ? answer.reason : 'Outside input needed.', requiredInput: answer.requiredInput };
|
|
57
|
+
};
|
|
49
58
|
const paused = (pauseId) => {
|
|
50
59
|
if (!pauseId || typeof pauseId !== 'string')
|
|
51
60
|
throw new Error('Computer use handoff needs the current pauseId.');
|
|
@@ -55,12 +64,11 @@ export function createComputerUseHandoff(options) {
|
|
|
55
64
|
requestTakeover(reason, requiredInput = 'human') {
|
|
56
65
|
if (!reason.trim() || !['login', 'human'].includes(requiredInput))
|
|
57
66
|
throw new Error('Takeover needs a reason and login or human input.');
|
|
58
|
-
return post('/tasks/takeover', { reason, requiredInput });
|
|
67
|
+
return post('/tasks/takeover', { reason, requiredInput }).then(pauseOf);
|
|
59
68
|
},
|
|
69
|
+
// The API answers 409 while the task is still pausing or after it resumed; `ready` is the only 200.
|
|
60
70
|
async view(pauseId) {
|
|
61
71
|
const page = await post('/tasks/view', paused(pauseId));
|
|
62
|
-
if (page.status === 'pausing')
|
|
63
|
-
return { status: 'pausing' };
|
|
64
72
|
if (page.status !== 'ready' || typeof page.screenshot !== 'string' || typeof page.url !== 'string')
|
|
65
73
|
throw new Error('The computer use API returned an incomplete screen.');
|
|
66
74
|
return { status: 'ready', screenshot: page.screenshot, url: page.url };
|
|
@@ -88,10 +96,23 @@ export function createComputerUseHandoff(options) {
|
|
|
88
96
|
if (answer.status === 'waiting_for_input')
|
|
89
97
|
return waitingInput(answer);
|
|
90
98
|
const message = computerUseMessage(answer.message);
|
|
91
|
-
|
|
99
|
+
const progress = { status: String(answer.status) };
|
|
100
|
+
if (typeof answer.steps === 'number')
|
|
101
|
+
progress.steps = answer.steps;
|
|
102
|
+
if (answer.last_step !== undefined)
|
|
103
|
+
progress.lastStep = answer.last_step;
|
|
104
|
+
if (answer.summary !== undefined)
|
|
105
|
+
progress.summary = answer.summary;
|
|
106
|
+
if (answer.final_url !== undefined)
|
|
107
|
+
progress.finalUrl = answer.final_url;
|
|
108
|
+
if (answer.waiting !== undefined)
|
|
109
|
+
progress.waiting = answer.waiting;
|
|
110
|
+
if (message)
|
|
111
|
+
progress.message = message;
|
|
112
|
+
return progress;
|
|
92
113
|
},
|
|
93
114
|
resume(pauseId) {
|
|
94
|
-
return post('/tasks/resume', paused(pauseId));
|
|
115
|
+
return post('/tasks/resume', paused(pauseId)).then(pauseOf);
|
|
95
116
|
},
|
|
96
117
|
cancel() {
|
|
97
118
|
return post('/tasks/cancel');
|
package/dist/computer-use-otp.js
CHANGED
|
@@ -69,16 +69,15 @@ export async function signInWithOtp(handoff, pauseId, login, otp, options = {})
|
|
|
69
69
|
const check = await handoff.check();
|
|
70
70
|
if (check.status === 'done' || check.status === 'failed')
|
|
71
71
|
return { status: 'ended', taskStatus: check.status };
|
|
72
|
-
if (
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
if (now() >= deadline)
|
|
73
|
+
return { status: 'human_help_needed', reason: 'The sign-in helper did not finish in time.' };
|
|
74
|
+
if (check.status !== 'waiting_for_input')
|
|
75
75
|
continue;
|
|
76
|
-
}
|
|
77
76
|
const login = check.login;
|
|
78
77
|
if (login?.status === 'success')
|
|
79
78
|
return { status: 'success' };
|
|
80
79
|
if (login?.status === 'human_help_needed')
|
|
81
|
-
return { status: 'human_help_needed', reason:
|
|
80
|
+
return { status: 'human_help_needed', reason: `The sign-in helper could not finish on its own${login.reason ? ` (${login.reason})` : ''}.` };
|
|
82
81
|
if (login?.status === 'needs_input' && login.input === 'otp') {
|
|
83
82
|
if (answered)
|
|
84
83
|
return { status: 'human_help_needed', reason: 'The website asked for a second code.' };
|