@shardflux/sdk 0.14.0 → 0.16.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 +85 -23
- package/README.md +226 -7
- package/dist/account.d.ts +2 -0
- package/dist/account.js +6 -0
- package/dist/cell.d.ts +27 -0
- package/dist/cell.js +71 -0
- package/dist/client.d.ts +54 -5
- package/dist/client.js +100 -7
- package/dist/computer.d.ts +153 -0
- package/dist/computer.js +229 -0
- package/dist/errors.d.ts +5 -1
- package/dist/errors.js +9 -0
- package/dist/executions.d.ts +2 -6
- package/dist/executions.js +9 -0
- package/dist/exit-code.d.ts +7 -0
- package/dist/exit-code.js +12 -0
- package/dist/generated/app-api.d.ts +1055 -119
- package/dist/generated/cell-api.d.ts +334 -0
- package/dist/http.d.ts +7 -1
- package/dist/http.js +34 -13
- package/dist/index.d.ts +13 -6
- package/dist/index.js +7 -2
- package/dist/ports.d.ts +7 -0
- package/dist/ports.js +1 -1
- package/dist/progress.d.ts +2 -2
- package/dist/progress.js +1 -1
- package/dist/templates.d.ts +12 -0
- package/dist/templates.js +9 -0
- package/dist/testing/index.d.ts +62 -0
- package/dist/testing/index.js +585 -0
- package/dist/testing/seed.d.ts +433 -0
- package/dist/testing/seed.js +449 -0
- package/dist/tools.d.ts +21 -3
- package/dist/tools.js +113 -22
- package/dist/tunnel-assets/linux-amd64.gz +0 -0
- package/dist/tunnel-assets/linux-arm64.gz +0 -0
- package/dist/tunnel-assets.d.ts +10 -0
- package/dist/tunnel-assets.js +11 -0
- package/dist/tunnel-packet.d.ts +3 -0
- package/dist/tunnel-packet.js +43 -0
- package/dist/tunnel-pty.d.ts +86 -0
- package/dist/tunnel-pty.js +243 -0
- package/dist/tunnels.d.ts +47 -0
- package/dist/tunnels.js +454 -0
- package/dist/workspace-ref.d.ts +87 -0
- package/dist/workspace-ref.js +173 -0
- package/dist/workspace.d.ts +40 -1
- package/dist/workspace.js +111 -2
- package/package.json +7 -2
package/dist/tools.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* application; toOpenAITools/toAnthropicTools export the definitions in those
|
|
11
11
|
* providers' formats and executeToolCall dispatches a model's tool call.
|
|
12
12
|
*/
|
|
13
|
+
import { runComputerActions } from "./computer.js";
|
|
13
14
|
import { CAPTURE_BARRIER, burstFailure } from "./cell.js";
|
|
14
15
|
import { ExecStartError, ShardfluxApiError } from "./errors.js";
|
|
15
16
|
import { newExecutionId } from "./executions.js";
|
|
@@ -82,6 +83,8 @@ export function validateArgs(schema, value, path = '$') {
|
|
|
82
83
|
issues.push(`${path} must be one of ${schema.enum.join(', ')}`);
|
|
83
84
|
return issues;
|
|
84
85
|
}
|
|
86
|
+
// Without a token yet (no grantedTools) and no opts.tools, every tool but computer: computer is offered only when a
|
|
87
|
+
// token grants it (the workspace's computer use is on) or opts.tools names it.
|
|
85
88
|
const ALL = ['exec', 'files', 'pty', 'process', 'git', 'browser'];
|
|
86
89
|
/** The tool permissions whose tools work on a file-first workspace: files and executions. */
|
|
87
90
|
const FILE_FIRST_TOOLS = ['exec', 'files'];
|
|
@@ -97,6 +100,45 @@ const RESOURCE_HINTS = new Set(['auto', 'light', 'heavy']);
|
|
|
97
100
|
/** Tools served from a sleeping workspace's disk without waking it: the runner sends no hint. */
|
|
98
101
|
const DISK_READS = new Set(['read_file', 'list_files', 'search_files']);
|
|
99
102
|
const obj = (properties, required = []) => ({ type: 'object', properties, required, additionalProperties: false });
|
|
103
|
+
/** One computer toolset action's parameters (the computer tool's and each computer_batch item's). */
|
|
104
|
+
const COMPUTER_ACTION = {
|
|
105
|
+
action: {
|
|
106
|
+
type: 'string',
|
|
107
|
+
enum: ['screenshot', 'zoom', 'left_click', 'right_click', 'middle_click', 'double_click', 'triple_click', 'left_click_drag', 'mouse_move',
|
|
108
|
+
'left_mouse_down', 'left_mouse_up', 'cursor_position', 'scroll', 'type', 'key', 'hold_key', 'wait'],
|
|
109
|
+
},
|
|
110
|
+
coordinate: { type: 'array', items: { type: 'integer', minimum: 0 }, minItems: 2, maxItems: 2, description: '[x, y]. Required for mouse_move and as the end of left_click_drag; optional for clicks and scroll (default: at the pointer).' },
|
|
111
|
+
start_coordinate: { type: 'array', items: { type: 'integer', minimum: 0 }, minItems: 2, maxItems: 2, description: 'left_click_drag: where the drag starts.' },
|
|
112
|
+
region: { type: 'array', items: { type: 'integer', minimum: 0 }, minItems: 4, maxItems: 4, description: 'zoom: [x0, y0, x1, y1], shown enlarged.' },
|
|
113
|
+
text: { type: 'string', maxLength: 20_000, description: 'type: the text. key, hold_key: keys such as "Return", "ctrl+s", "alt+Tab" (space-separated for a sequence). Clicks, drag, scroll: modifiers held, e.g. "shift".' },
|
|
114
|
+
scroll_direction: { type: 'string', enum: ['up', 'down', 'left', 'right'] },
|
|
115
|
+
scroll_amount: { type: 'integer', minimum: 1, maximum: 100, description: 'scroll: wheel clicks.' },
|
|
116
|
+
duration: { type: 'number', minimum: 0, maximum: 300, description: 'Seconds: wait, hold_key.' },
|
|
117
|
+
repeat: { type: 'integer', minimum: 1, maximum: 100, description: 'key: presses (default 1).' },
|
|
118
|
+
};
|
|
119
|
+
/** The screenshot after input actions (0.15.0+). */
|
|
120
|
+
const COMPUTER_LOOK = {
|
|
121
|
+
screenshot: { type: 'boolean', description: 'Take a screenshot after the input actions (default true); false when you do not need to look.' },
|
|
122
|
+
settle_ms: { type: 'integer', minimum: 0, maximum: 10_000, description: 'Wait this long before that screenshot when the screen may change (default 250).' },
|
|
123
|
+
format: { type: 'string', enum: ['png', 'jpeg'], description: 'Image format (default png; jpeg is smaller).' },
|
|
124
|
+
quality: { type: 'integer', minimum: 1, maximum: 100, description: 'JPEG quality (default 80).' },
|
|
125
|
+
};
|
|
126
|
+
const COMPUTER_ACTION_KEYS = ['coordinate', 'start_coordinate', 'region', 'text', 'scroll_direction', 'scroll_amount', 'duration', 'repeat'];
|
|
127
|
+
function computerAction(a) {
|
|
128
|
+
const action = { action: a.action };
|
|
129
|
+
for (const k of COMPUTER_ACTION_KEYS)
|
|
130
|
+
if (a[k] !== undefined && a[k] !== null)
|
|
131
|
+
action[k] = a[k];
|
|
132
|
+
return action;
|
|
133
|
+
}
|
|
134
|
+
function computerImage(a) {
|
|
135
|
+
return {
|
|
136
|
+
...(typeof a.settle_ms === 'number' ? { settle_ms: a.settle_ms } : {}),
|
|
137
|
+
...(a.format === 'png' || a.format === 'jpeg' ? { format: a.format } : {}),
|
|
138
|
+
...(typeof a.quality === 'number' ? { quality: a.quality } : {}),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const imageView = (img) => ({ mime_type: img.format === 'jpeg' ? 'image/jpeg' : 'image/png', width: img.width, height: img.height, data_base64: img.data });
|
|
100
142
|
const path = (description = 'Absolute path inside the workspace, e.g. /home/user/project/main.py') => ({ type: 'string', minLength: 1, maxLength: 4096, description });
|
|
101
143
|
const signal = { type: 'string', description: 'Signal name such as SIGTERM, SIGINT or SIGKILL.', minLength: 2, maxLength: 12 };
|
|
102
144
|
function clip(text, max) {
|
|
@@ -245,7 +287,7 @@ async function waitForExit(c, s, waitMs, signal) {
|
|
|
245
287
|
}
|
|
246
288
|
/** Builds the tool list for a workspace. Synchronous: tokens are fetched on first use. */
|
|
247
289
|
export function workspaceTools(workspace, opts = {}) {
|
|
248
|
-
const cell = () => workspace.cell({
|
|
290
|
+
const cell = async () => workspace.cell({
|
|
249
291
|
...(opts.agentLabel !== undefined ? { agentLabel: opts.agentLabel } : {}),
|
|
250
292
|
...(opts.wake !== undefined ? { wake: opts.wake } : {}),
|
|
251
293
|
...(opts.transitionTimeoutMs !== undefined ? { transitionTimeoutMs: opts.transitionTimeoutMs } : {}),
|
|
@@ -297,7 +339,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
297
339
|
run: async (a, o) => {
|
|
298
340
|
const executionId = newExecutionId();
|
|
299
341
|
opts.onExecution?.(executionId);
|
|
300
|
-
const r = await cell().executions.run(['bash', '-lc', String(a.command)], {
|
|
342
|
+
const r = await (await cell()).executions.run(['bash', '-lc', String(a.command)], {
|
|
301
343
|
executionId,
|
|
302
344
|
...(typeof a.cwd === 'string' ? { cwd: a.cwd } : opts.defaultCwd ? { cwd: opts.defaultCwd } : {}),
|
|
303
345
|
timeoutMs: typeof a.timeout_ms === 'number' ? a.timeout_ms : 600_000,
|
|
@@ -343,7 +385,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
343
385
|
if (a.background === true) {
|
|
344
386
|
// A session of its own: the start answers at once, and exec_read / exec_cancel find it by session_id.
|
|
345
387
|
// The command runs until it exits or its timeout_ms, if one is set. (Never a burst: refused above.)
|
|
346
|
-
const s = await cell().exec.start({
|
|
388
|
+
const s = await (await cell()).exec.start({
|
|
347
389
|
argv: ['bash', '-lc', String(a.command)],
|
|
348
390
|
...(cwd !== undefined ? { cwd } : {}),
|
|
349
391
|
...(typeof a.timeout_ms === 'number' ? { timeout_ms: a.timeout_ms } : {}),
|
|
@@ -359,7 +401,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
359
401
|
}
|
|
360
402
|
let r;
|
|
361
403
|
try {
|
|
362
|
-
r = await cell().exec.run(['bash', '-lc', String(a.command)], {
|
|
404
|
+
r = await (await cell()).exec.run(['bash', '-lc', String(a.command)], {
|
|
363
405
|
...(cwd !== undefined ? { cwd } : {}),
|
|
364
406
|
timeoutMs: typeof a.timeout_ms === 'number' ? a.timeout_ms : 600_000,
|
|
365
407
|
...(typeof a.stdin === 'string' ? { stdin: a.stdin } : {}),
|
|
@@ -411,7 +453,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
411
453
|
description: 'Read a command started with exec background: true: its state, exit code and output. Without offsets it returns the end of each stream; pass next_stdout_offset and next_stderr_offset back to read only new output. wait_ms waits up to that long for the command to exit and returns as soon as it does.',
|
|
412
454
|
parameters: obj({ session_id: sessionId, stdout_offset: { type: 'integer', minimum: 0 }, stderr_offset: { type: 'integer', minimum: 0 }, wait_ms: { type: 'integer', minimum: 0, maximum: 60_000 } }, ['session_id']),
|
|
413
455
|
run: async (a, o) => {
|
|
414
|
-
const c = cell();
|
|
456
|
+
const c = await cell();
|
|
415
457
|
const id = String(a.session_id);
|
|
416
458
|
let s = await c.exec.get(id);
|
|
417
459
|
if (typeof a.wait_ms === 'number' && a.wait_ms > 0 && running(s))
|
|
@@ -461,7 +503,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
461
503
|
// The workspace waits 5 s without a grace and at most 60 s.
|
|
462
504
|
parameters: obj({ session_id: sessionId, grace_ms: { type: 'integer', minimum: 1, maximum: 60_000 } }, ['session_id']),
|
|
463
505
|
run: async (a) => {
|
|
464
|
-
const s = await cell().exec.cancel(String(a.session_id), typeof a.grace_ms === 'number' ? a.grace_ms : undefined);
|
|
506
|
+
const s = await (await cell()).exec.cancel(String(a.session_id), typeof a.grace_ms === 'number' ? a.grace_ms : undefined);
|
|
465
507
|
return { session_id: s.session_id, state: s.state, exit_code: running(s) ? null : (s.exit_code ?? null), canceled: s.canceled ?? false };
|
|
466
508
|
},
|
|
467
509
|
},
|
|
@@ -472,7 +514,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
472
514
|
description: 'Read a text file from the workspace (UTF-8). Use offset/length for large files.',
|
|
473
515
|
parameters: obj({ path: path(), offset: { type: 'integer', minimum: 0 }, length: { type: 'integer', minimum: 1, maximum: 10_485_760 } }, ['path']),
|
|
474
516
|
run: async (a) => {
|
|
475
|
-
const bytes = await cell().files.read(String(a.path), {
|
|
517
|
+
const bytes = await (await cell()).files.read(String(a.path), {
|
|
476
518
|
...(typeof a.offset === 'number' ? { offset: a.offset } : {}),
|
|
477
519
|
length: typeof a.length === 'number' ? Math.min(a.length, max) : max + 1,
|
|
478
520
|
});
|
|
@@ -491,7 +533,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
491
533
|
create_parents: { type: 'boolean', description: 'Create missing parent directories (default true).' },
|
|
492
534
|
}, ['path', 'content']),
|
|
493
535
|
run: async (a) => {
|
|
494
|
-
const r = await cell().files.write(String(a.path), String(a.content), { append: a.append === true, createParents: a.create_parents !== false });
|
|
536
|
+
const r = await (await cell()).files.write(String(a.path), String(a.content), { append: a.append === true, createParents: a.create_parents !== false });
|
|
495
537
|
return { path: r.path, bytes_written: r.bytes_written, sha256: r.sha256, durable: r.durable };
|
|
496
538
|
},
|
|
497
539
|
},
|
|
@@ -501,7 +543,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
501
543
|
description: 'List a directory in the workspace.',
|
|
502
544
|
parameters: obj({ path: path('Absolute directory path.'), limit: { type: 'integer', minimum: 1, maximum: 10_000 } }, ['path']),
|
|
503
545
|
run: async (a) => {
|
|
504
|
-
const r = await cell().files.list(String(a.path), typeof a.limit === 'number' ? { limit: a.limit } : { limit: 500 });
|
|
546
|
+
const r = await (await cell()).files.list(String(a.path), typeof a.limit === 'number' ? { limit: a.limit } : { limit: 500 });
|
|
505
547
|
return { entries: r.entries.map((e) => ({ name: e.name, path: e.path, type: e.type, size: e.size, modified_at: e.modified_at })), truncated: r.truncated };
|
|
506
548
|
},
|
|
507
549
|
},
|
|
@@ -520,7 +562,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
520
562
|
context_lines: { type: 'integer', minimum: 0, maximum: 5, description: 'Lines of context to return before and after each match.' },
|
|
521
563
|
}, ['path', 'pattern']),
|
|
522
564
|
run: async (a, o) => {
|
|
523
|
-
const r = await cell().files.search(String(a.path), String(a.pattern), {
|
|
565
|
+
const r = await (await cell()).files.search(String(a.path), String(a.pattern), {
|
|
524
566
|
...(typeof a.regex === 'boolean' ? { regex: a.regex } : {}),
|
|
525
567
|
...(typeof a.case_insensitive === 'boolean' ? { caseInsensitive: a.case_insensitive } : {}),
|
|
526
568
|
...(Array.isArray(a.include) ? { include: a.include } : {}),
|
|
@@ -566,7 +608,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
566
608
|
},
|
|
567
609
|
}, ['path', 'edits']),
|
|
568
610
|
run: async (a, o) => {
|
|
569
|
-
const c = cell();
|
|
611
|
+
const c = await cell();
|
|
570
612
|
const file = String(a.path);
|
|
571
613
|
// Without a revision from the model, pin the edit to the content current now, so a concurrent change between
|
|
572
614
|
// this read and the patch is refused (revision_mismatch) instead of edited blindly.
|
|
@@ -582,7 +624,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
582
624
|
description: 'List processes running in the workspace.',
|
|
583
625
|
parameters: obj({}),
|
|
584
626
|
run: async () => {
|
|
585
|
-
const r = await cell().processes.list();
|
|
627
|
+
const r = await (await cell()).processes.list();
|
|
586
628
|
return { processes: r.data.map((p) => ({ pid: p.pid, ppid: p.ppid, comm: p.comm, cmdline: p.cmdline, state: p.state, rss_bytes: p.rss_bytes })) };
|
|
587
629
|
},
|
|
588
630
|
},
|
|
@@ -592,7 +634,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
592
634
|
description: 'Send a signal to a process in the workspace (e.g. stop a server).',
|
|
593
635
|
parameters: obj({ pid: { type: 'integer', minimum: 2 }, signal }, ['pid', 'signal']),
|
|
594
636
|
run: async (a) => {
|
|
595
|
-
await cell().processes.signal(Number(a.pid), String(a.signal));
|
|
637
|
+
await (await cell()).processes.signal(Number(a.pid), String(a.signal));
|
|
596
638
|
return { signalled: true };
|
|
597
639
|
},
|
|
598
640
|
},
|
|
@@ -602,7 +644,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
602
644
|
description: 'Open an interactive terminal (PTY) in the workspace; returns a session_id for terminal_send/terminal_read.',
|
|
603
645
|
parameters: obj({ command: { type: 'string', maxLength: 10_000, description: 'Program to run (default: login shell).' }, rows: { type: 'integer', minimum: 1, maximum: 1000 }, cols: { type: 'integer', minimum: 1, maximum: 1000 } }),
|
|
604
646
|
run: async (a) => {
|
|
605
|
-
const s = await cell().pty.open({
|
|
647
|
+
const s = await (await cell()).pty.open({
|
|
606
648
|
...(typeof a.command === 'string' ? { argv: ['bash', '-lc', a.command] } : {}),
|
|
607
649
|
...(typeof a.rows === 'number' ? { rows: a.rows } : {}),
|
|
608
650
|
...(typeof a.cols === 'number' ? { cols: a.cols } : {}),
|
|
@@ -616,7 +658,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
616
658
|
description: 'Type input into a terminal session (include "\\n" to press Enter).',
|
|
617
659
|
parameters: obj({ session_id: { type: 'string', minLength: 1, maxLength: 64 }, input: { type: 'string', maxLength: 1_000_000 } }, ['session_id', 'input']),
|
|
618
660
|
run: async (a) => {
|
|
619
|
-
const s = await cell().pty.input(String(a.session_id), String(a.input));
|
|
661
|
+
const s = await (await cell()).pty.input(String(a.session_id), String(a.input));
|
|
620
662
|
return { state: s.state, next_offset: s.output_size };
|
|
621
663
|
},
|
|
622
664
|
},
|
|
@@ -626,7 +668,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
626
668
|
description: 'Read terminal output from an offset (returns next_offset to continue). Waits briefly for new output.',
|
|
627
669
|
parameters: obj({ session_id: { type: 'string', minLength: 1, maxLength: 64 }, offset: { type: 'integer', minimum: 0 }, wait_ms: { type: 'integer', minimum: 0, maximum: 60_000 } }, ['session_id']),
|
|
628
670
|
run: async (a) => {
|
|
629
|
-
const r = await cell().pty.read(String(a.session_id), {
|
|
671
|
+
const r = await (await cell()).pty.read(String(a.session_id), {
|
|
630
672
|
offset: typeof a.offset === 'number' ? a.offset : 0,
|
|
631
673
|
timeoutMs: typeof a.wait_ms === 'number' ? Math.max(100, a.wait_ms) : 3_000,
|
|
632
674
|
maxBytes: max,
|
|
@@ -640,7 +682,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
640
682
|
description: 'Close a terminal session.',
|
|
641
683
|
parameters: obj({ session_id: { type: 'string', minLength: 1, maxLength: 64 } }, ['session_id']),
|
|
642
684
|
run: async (a) => {
|
|
643
|
-
const s = await cell().pty.close(String(a.session_id));
|
|
685
|
+
const s = await (await cell()).pty.close(String(a.session_id));
|
|
644
686
|
return { state: s.state };
|
|
645
687
|
},
|
|
646
688
|
},
|
|
@@ -650,7 +692,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
650
692
|
description: 'Clone a git repository (HTTPS) into the workspace.',
|
|
651
693
|
parameters: obj({ url: { type: 'string', minLength: 9, maxLength: 2048, description: 'https:// remote URL.' }, path: path('Destination directory.'), branch: { type: 'string', maxLength: 255 }, depth: { type: 'integer', minimum: 1 } }, ['url', 'path']),
|
|
652
694
|
run: async (a) => {
|
|
653
|
-
const r = await cell().git.clone({
|
|
695
|
+
const r = await (await cell()).git.clone({
|
|
654
696
|
url: String(a.url),
|
|
655
697
|
path: String(a.path),
|
|
656
698
|
...(typeof a.branch === 'string' ? { branch: a.branch } : {}),
|
|
@@ -664,7 +706,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
664
706
|
permission: 'git',
|
|
665
707
|
description: 'Show the git status of a repository in the workspace.',
|
|
666
708
|
parameters: obj({ path: path('Repository directory.') }, ['path']),
|
|
667
|
-
run: async (a) => cell().git.status(String(a.path)),
|
|
709
|
+
run: async (a) => (await cell()).git.status(String(a.path)),
|
|
668
710
|
},
|
|
669
711
|
{
|
|
670
712
|
name: 'git_commit',
|
|
@@ -672,17 +714,66 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
672
714
|
description: 'Stage all changes and commit in a repository in the workspace.',
|
|
673
715
|
parameters: obj({ path: path('Repository directory.'), message: { type: 'string', minLength: 1, maxLength: 65_536 } }, ['path', 'message']),
|
|
674
716
|
run: async (a) => {
|
|
675
|
-
const r = await cell().git.commit({ path: String(a.path), message: String(a.message), all: true });
|
|
717
|
+
const r = await (await cell()).git.commit({ path: String(a.path), message: String(a.message), all: true });
|
|
676
718
|
return { exit_code: r.exit_code, commit: r.commit ?? null, stdout: clip(r.stdout, max).text, stderr: clip(r.stderr, max).text };
|
|
677
719
|
},
|
|
678
720
|
},
|
|
721
|
+
{
|
|
722
|
+
name: 'computer',
|
|
723
|
+
permission: 'computer',
|
|
724
|
+
description: 'Use the workspace desktop: its screen, mouse and keyboard. One action per call (computer_batch runs several); input actions answer with a screenshot taken after them, screenshot and zoom answer with the image, cursor_position with the pointer. Coordinates are screen pixels from the top-left of the latest screenshot. Start with action "screenshot". Programs started with exec in the background (e.g. "chromium https://example.com &") open on this desktop.',
|
|
725
|
+
parameters: obj({ ...COMPUTER_ACTION, ...COMPUTER_LOOK }, ['action']),
|
|
726
|
+
run: async (a, o) => {
|
|
727
|
+
const look = a.action === 'screenshot' || a.action === 'zoom' || a.action === 'cursor_position';
|
|
728
|
+
const r = await runComputerActions(async (actions, options) => (await cell()).computer.act({ actions, screenshot: options.screenshot, ...computerImage(a) }, o.signal), [computerAction(a)], { screenshot: !look && a.screenshot !== false }, opts.computer?.beforeAction);
|
|
729
|
+
const res = r.results[0];
|
|
730
|
+
const img = res?.image ?? r.screenshot;
|
|
731
|
+
return {
|
|
732
|
+
ok: res?.ok ?? false,
|
|
733
|
+
...(res?.output !== undefined ? { output: res.output } : {}),
|
|
734
|
+
...(res?.error ? { error: res.error.message } : {}),
|
|
735
|
+
cursor: r.cursor,
|
|
736
|
+
display: r.display,
|
|
737
|
+
...(img ? { screenshot: imageView(img) } : {}),
|
|
738
|
+
};
|
|
739
|
+
},
|
|
740
|
+
},
|
|
741
|
+
{
|
|
742
|
+
name: 'computer_batch',
|
|
743
|
+
permission: 'computer',
|
|
744
|
+
description: 'Run several actions of the computer tool on the workspace desktop in one call, in order (e.g. click a field, type, press Return). The first failure stops the batch and the rest come back skipped. Answers with each action\'s result (zoom with its image, cursor_position with the pointer) and one screenshot after the last action that ran (screenshot: false skips it).',
|
|
745
|
+
parameters: obj({
|
|
746
|
+
actions: { type: 'array', minItems: 1, maxItems: 50, items: obj(COMPUTER_ACTION, ['action']), description: 'Actions with the computer tool\'s parameters, run in order.' },
|
|
747
|
+
...COMPUTER_LOOK,
|
|
748
|
+
}, ['actions']),
|
|
749
|
+
run: async (a, o) => {
|
|
750
|
+
const actions = a.actions.map(computerAction);
|
|
751
|
+
const lastLooks = ['screenshot', 'zoom'].includes(actions.at(-1)?.action ?? '');
|
|
752
|
+
const r = await runComputerActions(async (batch, options) => (await cell()).computer.act({ actions: batch, screenshot: options.screenshot, ...computerImage(a) }, o.signal), actions, { screenshot: !lastLooks && a.screenshot !== false }, opts.computer?.beforeAction);
|
|
753
|
+
return {
|
|
754
|
+
ok: r.results.every((x) => x.ok),
|
|
755
|
+
results: r.results.map((x) => ({
|
|
756
|
+
action: x.action,
|
|
757
|
+
ok: x.ok,
|
|
758
|
+
...(x.skipped ? { skipped: true } : {}),
|
|
759
|
+
...(x.output !== undefined ? { output: x.output } : {}),
|
|
760
|
+
...(x.error ? { error: x.error.message } : {}),
|
|
761
|
+
took_ms: x.took_ms,
|
|
762
|
+
...(x.image ? { image: imageView(x.image) } : {}),
|
|
763
|
+
})),
|
|
764
|
+
cursor: r.cursor,
|
|
765
|
+
display: r.display,
|
|
766
|
+
...(r.screenshot ? { screenshot: imageView(r.screenshot) } : {}),
|
|
767
|
+
};
|
|
768
|
+
},
|
|
769
|
+
},
|
|
679
770
|
{
|
|
680
771
|
name: 'browser_screenshot',
|
|
681
772
|
permission: 'browser',
|
|
682
773
|
description: 'Open a URL in the workspace’s headless browser and return a PNG screenshot (base64).',
|
|
683
774
|
parameters: obj({ url: { type: 'string', minLength: 8, maxLength: 8192 }, width: { type: 'integer', minimum: 100, maximum: 3840 }, height: { type: 'integer', minimum: 100, maximum: 2160 } }, ['url']),
|
|
684
775
|
run: async (a) => {
|
|
685
|
-
const png = await cell().browser.screenshot({
|
|
776
|
+
const png = await (await cell()).browser.screenshot({
|
|
686
777
|
url: String(a.url),
|
|
687
778
|
...(typeof a.width === 'number' ? { width: a.width } : {}),
|
|
688
779
|
...(typeof a.height === 'number' ? { height: a.height } : {}),
|
|
@@ -696,7 +787,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
696
787
|
description: 'Open a URL in the workspace’s headless browser and return the rendered page as text or HTML.',
|
|
697
788
|
parameters: obj({ url: { type: 'string', minLength: 8, maxLength: 8192 }, format: { type: 'string', enum: ['text', 'html'] } }, ['url']),
|
|
698
789
|
run: async (a) => {
|
|
699
|
-
const r = await cell().browser.content({ url: String(a.url), format: a.format === 'html' ? 'html' : 'text' });
|
|
790
|
+
const r = await (await cell()).browser.content({ url: String(a.url), format: a.format === 'html' ? 'html' : 'text' });
|
|
700
791
|
const c = clip(r.content, max);
|
|
701
792
|
return { url: r.url, format: r.format, content: c.text, truncated: r.truncated || c.truncated };
|
|
702
793
|
},
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const TUNNEL_ASSETS: {
|
|
2
|
+
readonly x86_64: {
|
|
3
|
+
readonly file: "linux-amd64.gz";
|
|
4
|
+
readonly sha256: "e7b8a88b27c36b2605ee9e8182dae7700f816d9058d92bd87d6ae66781303650";
|
|
5
|
+
};
|
|
6
|
+
readonly aarch64: {
|
|
7
|
+
readonly file: "linux-arm64.gz";
|
|
8
|
+
readonly sha256: "740ce49febb1ab0e614a520d8daff68fa7b4ac32adf18dc93d5ff16b2dc57735";
|
|
9
|
+
};
|
|
10
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Generated by scripts/build-tunnel-helper.py.
|
|
2
|
+
export const TUNNEL_ASSETS = {
|
|
3
|
+
"x86_64": {
|
|
4
|
+
"file": "linux-amd64.gz",
|
|
5
|
+
"sha256": "e7b8a88b27c36b2605ee9e8182dae7700f816d9058d92bd87d6ae66781303650"
|
|
6
|
+
},
|
|
7
|
+
"aarch64": {
|
|
8
|
+
"file": "linux-arm64.gz",
|
|
9
|
+
"sha256": "740ce49febb1ab0e614a520d8daff68fa7b4ac32adf18dc93d5ff16b2dc57735"
|
|
10
|
+
}
|
|
11
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const table = Uint32Array.from({ length: 256 }, (_, value) => {
|
|
2
|
+
let crc = value;
|
|
3
|
+
for (let n = 0; n < 8; n++)
|
|
4
|
+
crc = (crc >>> 1) ^ ((crc & 1) ? 0xedb88320 : 0);
|
|
5
|
+
return crc >>> 0;
|
|
6
|
+
});
|
|
7
|
+
export function packetCrc(bytes) {
|
|
8
|
+
let crc = 0xffffffff;
|
|
9
|
+
for (const b of bytes)
|
|
10
|
+
crc = (crc >>> 8) ^ table[(crc ^ b) & 255];
|
|
11
|
+
return (crc ^ 0xffffffff) >>> 0;
|
|
12
|
+
}
|
|
13
|
+
/** Delimit both ends so a replay discards any truncated preceding packet before decoding. */
|
|
14
|
+
export function tunnelPacket(offset, bytes) {
|
|
15
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || bytes.length > 65536)
|
|
16
|
+
throw new Error('invalid tunnel packet');
|
|
17
|
+
const data = Buffer.alloc(bytes.length + 16);
|
|
18
|
+
data.write('SFRT');
|
|
19
|
+
data.writeBigUInt64BE(BigInt(offset), 4);
|
|
20
|
+
data.set(bytes, 12);
|
|
21
|
+
data.writeUInt32BE(packetCrc(data.subarray(0, -4)), data.length - 4);
|
|
22
|
+
const result = Buffer.alloc(data.length + Math.ceil(data.length / 254) + 3);
|
|
23
|
+
let write = 2, codeAt = 1, code = 1;
|
|
24
|
+
for (const b of data) {
|
|
25
|
+
if (b === 0) {
|
|
26
|
+
result[codeAt] = code;
|
|
27
|
+
codeAt = write++;
|
|
28
|
+
code = 1;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
result[write++] = b;
|
|
32
|
+
code++;
|
|
33
|
+
if (code === 255) {
|
|
34
|
+
result[codeAt] = code;
|
|
35
|
+
codeAt = write++;
|
|
36
|
+
code = 1;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
result[codeAt] = code;
|
|
41
|
+
result[write++] = 0;
|
|
42
|
+
return result.subarray(0, write);
|
|
43
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { WebSocket } from 'undici';
|
|
2
|
+
import type { CellClient, ExecSession, OutputEvent } from './cell.js';
|
|
3
|
+
import type { TunnelTransport } from './tunnels.js';
|
|
4
|
+
/** Native PTY owns the channel; helper acknowledgements own bytes, independently of an HTTP request outcome. */
|
|
5
|
+
export declare class PtyTunnelTransport implements TunnelTransport {
|
|
6
|
+
#private;
|
|
7
|
+
readonly maxInputConcurrency = 64;
|
|
8
|
+
readonly kind: "pty";
|
|
9
|
+
constructor(cell: CellClient, open?: (url: string, opts: NonNullable<ConstructorParameters<typeof WebSocket>[1]>) => WebSocket);
|
|
10
|
+
readonly files: {
|
|
11
|
+
remove: (path: string, opts?: {
|
|
12
|
+
recursive?: boolean;
|
|
13
|
+
}) => Promise<void>;
|
|
14
|
+
};
|
|
15
|
+
readonly exec: {
|
|
16
|
+
start: (req: {
|
|
17
|
+
session_id?: import("./index.js").CellComponents["schemas"]["SessionIdValue"];
|
|
18
|
+
argv: string[];
|
|
19
|
+
env?: {
|
|
20
|
+
[key: string]: string;
|
|
21
|
+
};
|
|
22
|
+
cwd?: string;
|
|
23
|
+
user?: string;
|
|
24
|
+
stdin_open?: boolean;
|
|
25
|
+
stdin?: string;
|
|
26
|
+
timeout_ms?: number;
|
|
27
|
+
kill_grace_ms?: number;
|
|
28
|
+
secret_refs?: import("./index.js").CellComponents["schemas"]["SecretRefs"];
|
|
29
|
+
execution_id?: import("./index.js").CellComponents["schemas"]["ExecutionIdValue"];
|
|
30
|
+
output_limit_bytes?: number;
|
|
31
|
+
burst?: "never" | "always";
|
|
32
|
+
burst_vcpus?: number;
|
|
33
|
+
burst_memory_mib?: number;
|
|
34
|
+
resource_hint?: "auto" | "light" | "heavy";
|
|
35
|
+
}, signal?: AbortSignal | undefined) => Promise<{
|
|
36
|
+
session_id: string;
|
|
37
|
+
state: import("./index.js").CellComponents["schemas"]["SessionState"];
|
|
38
|
+
created: boolean;
|
|
39
|
+
pid?: number;
|
|
40
|
+
exit_code?: number | null;
|
|
41
|
+
term_signal?: number | null;
|
|
42
|
+
timed_out?: boolean;
|
|
43
|
+
canceled?: boolean;
|
|
44
|
+
stdout_size: number;
|
|
45
|
+
stderr_size: number;
|
|
46
|
+
created_at?: string;
|
|
47
|
+
started_at?: string | null;
|
|
48
|
+
ended_at?: string | null;
|
|
49
|
+
error?: string;
|
|
50
|
+
memory_grow?: import("./index.js").CellComponents["schemas"]["MemoryGrow"];
|
|
51
|
+
burst?: import("./index.js").CellComponents["schemas"]["BurstSummary"];
|
|
52
|
+
}>;
|
|
53
|
+
input: (id: string, bytes: string | Uint8Array, opts: {
|
|
54
|
+
offset: number;
|
|
55
|
+
close?: boolean;
|
|
56
|
+
signal?: AbortSignal;
|
|
57
|
+
}) => Promise<{
|
|
58
|
+
offset: number;
|
|
59
|
+
closed: false;
|
|
60
|
+
}>;
|
|
61
|
+
output: (id: string, opts?: {
|
|
62
|
+
stdoutOffset?: number;
|
|
63
|
+
stderrOffset?: number;
|
|
64
|
+
follow?: boolean;
|
|
65
|
+
signal?: AbortSignal;
|
|
66
|
+
}) => Promise<AsyncGenerator<{
|
|
67
|
+
type: "output" | "exit" | "heartbeat" | "error";
|
|
68
|
+
stream?: "stdout" | "stderr";
|
|
69
|
+
offset?: number;
|
|
70
|
+
data?: string;
|
|
71
|
+
session?: import("./index.js").CellComponents["schemas"]["ExecSession"];
|
|
72
|
+
error?: import("./index.js").CellComponents["schemas"]["ErrorBody"];
|
|
73
|
+
}, any, any>>;
|
|
74
|
+
cancel: (id: string) => Promise<ExecSession>;
|
|
75
|
+
};
|
|
76
|
+
input(id: string, value: string | Uint8Array, opts: {
|
|
77
|
+
offset: number;
|
|
78
|
+
signal?: AbortSignal;
|
|
79
|
+
}): Promise<{
|
|
80
|
+
offset: number;
|
|
81
|
+
closed: false;
|
|
82
|
+
}>;
|
|
83
|
+
output(id: string, opts?: {
|
|
84
|
+
signal?: AbortSignal;
|
|
85
|
+
}): Promise<AsyncGenerator<OutputEvent>>;
|
|
86
|
+
}
|