@shardflux/sdk 0.14.0 → 0.15.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 +45 -3
- package/README.md +127 -6
- package/dist/cell.d.ts +25 -0
- package/dist/cell.js +69 -0
- package/dist/client.d.ts +29 -4
- package/dist/client.js +57 -5
- package/dist/computer.d.ts +143 -0
- package/dist/computer.js +146 -0
- package/dist/generated/app-api.d.ts +452 -68
- package/dist/generated/cell-api.d.ts +332 -0
- package/dist/http.d.ts +1 -1
- package/dist/http.js +1 -1
- package/dist/index.d.ts +8 -4
- package/dist/index.js +4 -1
- package/dist/templates.d.ts +12 -0
- package/dist/templates.js +9 -0
- package/dist/tools.d.ts +16 -3
- package/dist/tools.js +111 -22
- package/dist/workspace-ref.d.ts +83 -0
- package/dist/workspace-ref.js +149 -0
- package/dist/workspace.d.ts +23 -1
- package/dist/workspace.js +36 -0
- package/package.json +1 -1
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Computer use (0.15.0+, contracts §45): the workspace desktop, which the platform starts on the first call that needs
|
|
3
|
+
* it. `workspace.computer` drives it; `computerToolset(workspace)` answers Claude's computer toolset
|
|
4
|
+
* (`computer_toolset_20260801`) with one batch per model turn.
|
|
5
|
+
*
|
|
6
|
+
* await workspace.setComputerUse(true); // or open({ ..., computerUse: true })
|
|
7
|
+
* const shot = await workspace.computer.screenshot(); // { format: 'png', width: 1280, height: 800, data }
|
|
8
|
+
* await workspace.computer.act([{ action: 'left_click', coordinate: [640, 400] }, { action: 'type', text: 'hello' }]);
|
|
9
|
+
* const { url } = await workspace.computer.stream(); // a private link to watch the screen
|
|
10
|
+
*/
|
|
11
|
+
import type { CellClient, ComputerAction, ComputerActionsResult, ComputerImage, ComputerStatus } from './cell.js';
|
|
12
|
+
import type { ComputerUse } from './client.js';
|
|
13
|
+
import type { WorkspacePorts } from './ports.js';
|
|
14
|
+
/** A decoded screen image. */
|
|
15
|
+
export interface ComputerScreenshot {
|
|
16
|
+
format: 'png' | 'jpeg';
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
data: Uint8Array;
|
|
20
|
+
}
|
|
21
|
+
export interface ComputerActOptions {
|
|
22
|
+
/** Append a screenshot after the last action that ran (also after a failure). */
|
|
23
|
+
screenshot?: boolean;
|
|
24
|
+
/** Wait this long before that screenshot when an action changed the screen (default 250). */
|
|
25
|
+
settleMs?: number;
|
|
26
|
+
format?: 'png' | 'jpeg';
|
|
27
|
+
/** JPEG quality 1..100 (default 80). */
|
|
28
|
+
quality?: number;
|
|
29
|
+
signal?: AbortSignal;
|
|
30
|
+
}
|
|
31
|
+
export interface ComputerStreamOptions {
|
|
32
|
+
/** Let the viewer use the mouse and keyboard (default false: view only, enforced in the workspace). */
|
|
33
|
+
interactive?: boolean;
|
|
34
|
+
/** How long the link works (60..604800 s; default 86400). */
|
|
35
|
+
ttlSeconds?: number;
|
|
36
|
+
}
|
|
37
|
+
/** A private link to the desktop's viewer: open it in a browser or an iframe. */
|
|
38
|
+
export interface ComputerStream {
|
|
39
|
+
url: string;
|
|
40
|
+
expiresAt: string;
|
|
41
|
+
port: number;
|
|
42
|
+
interactive: boolean;
|
|
43
|
+
}
|
|
44
|
+
/** Decodes a result image (base64 in the API) to bytes. */
|
|
45
|
+
export declare function decodeComputerImage(img: ComputerImage): ComputerScreenshot;
|
|
46
|
+
interface ComputerDeps {
|
|
47
|
+
cell: () => CellClient;
|
|
48
|
+
ports: () => WorkspacePorts;
|
|
49
|
+
setEnabled: (enabled: boolean | null) => Promise<ComputerUse>;
|
|
50
|
+
}
|
|
51
|
+
/** The workspace desktop (workspace.computer). */
|
|
52
|
+
export declare class WorkspaceComputer {
|
|
53
|
+
#private;
|
|
54
|
+
constructor(deps: ComputerDeps);
|
|
55
|
+
/**
|
|
56
|
+
* Runs actions in order (the first failure stops the batch; later actions come back `skipped`), optionally ending
|
|
57
|
+
* with a screenshot. The actions are Claude's computer toolset members with their parameter names.
|
|
58
|
+
*/
|
|
59
|
+
act(actions: ComputerAction[], opts?: ComputerActOptions): Promise<ComputerActionsResult>;
|
|
60
|
+
/** The screen now (the pointer drawn in). */
|
|
61
|
+
screenshot(opts?: Pick<ComputerActOptions, 'format' | 'quality' | 'signal'>): Promise<ComputerScreenshot>;
|
|
62
|
+
/** Whether the desktop runs, its size and its viewers. Never starts it. */
|
|
63
|
+
status(): Promise<ComputerStatus>;
|
|
64
|
+
/** Starts the desktop (a no-op while it runs; the size applies to a start only: 640x480..2560x1600, default 1280x800). */
|
|
65
|
+
start(size?: {
|
|
66
|
+
width?: number;
|
|
67
|
+
height?: number;
|
|
68
|
+
}): Promise<ComputerStatus>;
|
|
69
|
+
/** Stops the desktop; its windows close. The next call that needs it starts a fresh one. */
|
|
70
|
+
stop(): Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* A private link to watch the desktop (or use it, with `interactive`): starts the viewer in the workspace, exposes
|
|
73
|
+
* its port and signs a link to the viewer page (inbound ports, contracts §39). Anyone with the link can open it until
|
|
74
|
+
* it expires; `stopStream()` ends every view.
|
|
75
|
+
*/
|
|
76
|
+
stream(opts?: ComputerStreamOptions): Promise<ComputerStream>;
|
|
77
|
+
/** Ends every view and closes the viewer ports (their links stop working). */
|
|
78
|
+
stopStream(): Promise<void>;
|
|
79
|
+
/** Switches computer use on or off for this workspace (null follows the template). */
|
|
80
|
+
setEnabled(enabled: boolean | null): Promise<ComputerUse>;
|
|
81
|
+
}
|
|
82
|
+
/** The `tools` entry of Claude's computer toolset (no name, no display size). */
|
|
83
|
+
export declare const COMPUTER_TOOLSET: {
|
|
84
|
+
readonly type: "computer_toolset_20260801";
|
|
85
|
+
};
|
|
86
|
+
/** A `tool_use` content block (structurally the Anthropic SDK's, so its blocks pass as they are). */
|
|
87
|
+
export interface ToolUseBlockLike {
|
|
88
|
+
type: string;
|
|
89
|
+
id: string;
|
|
90
|
+
name: string;
|
|
91
|
+
input: unknown;
|
|
92
|
+
toolset_name?: string | null;
|
|
93
|
+
}
|
|
94
|
+
type ResultContent = Array<{
|
|
95
|
+
type: 'text';
|
|
96
|
+
text: string;
|
|
97
|
+
} | {
|
|
98
|
+
type: 'image';
|
|
99
|
+
source: {
|
|
100
|
+
type: 'base64';
|
|
101
|
+
media_type: 'image/png' | 'image/jpeg';
|
|
102
|
+
data: string;
|
|
103
|
+
};
|
|
104
|
+
}>;
|
|
105
|
+
/** A `tool_result` block answering a toolset call (each echoes `toolset_name: "computer"`, as the API requires). */
|
|
106
|
+
export interface ComputerToolResult {
|
|
107
|
+
type: 'tool_result';
|
|
108
|
+
tool_use_id: string;
|
|
109
|
+
toolset_name: 'computer';
|
|
110
|
+
content: ResultContent;
|
|
111
|
+
is_error?: true;
|
|
112
|
+
}
|
|
113
|
+
export interface ComputerToolsetOptions {
|
|
114
|
+
/** Screenshot format (default png). */
|
|
115
|
+
format?: 'png' | 'jpeg';
|
|
116
|
+
quality?: number;
|
|
117
|
+
settleMs?: number;
|
|
118
|
+
signal?: AbortSignal;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Answers Claude's computer toolset (`tools: [COMPUTER_TOOLSET]`, Claude Opus 5.5 / Sonnet 5.5 and later on the
|
|
122
|
+
* Claude API) from this workspace's desktop:
|
|
123
|
+
*
|
|
124
|
+
* const computer = computerToolset(workspace);
|
|
125
|
+
* const msg = await anthropic.messages.create({ model, max_tokens, tools: [computer.definition], messages });
|
|
126
|
+
* messages.push({ role: 'assistant', content: msg.content });
|
|
127
|
+
* messages.push({ role: 'user', content: await computer.run(msg.content) });
|
|
128
|
+
*
|
|
129
|
+
* `run` takes the response content, runs every `toolset_name: "computer"` call of the turn as one batch (in order;
|
|
130
|
+
* the first failure stops it and the rest are answered "Not executed"), and returns one `tool_result` per call:
|
|
131
|
+
* images for screenshot and zoom, the position for cursor_position, `OK` otherwise. When the batch does not end with a
|
|
132
|
+
* look, a screenshot is attached to the last result, so the model sees the outcome without another round trip. A
|
|
133
|
+
* batch the desktop refuses as invalid (a coordinate outside the screen) is answered with the reason on every call.
|
|
134
|
+
*/
|
|
135
|
+
export declare function computerToolset(workspace: {
|
|
136
|
+
computer: WorkspaceComputer;
|
|
137
|
+
}): {
|
|
138
|
+
definition: {
|
|
139
|
+
readonly type: "computer_toolset_20260801";
|
|
140
|
+
};
|
|
141
|
+
run(content: readonly unknown[], opts?: ComputerToolsetOptions): Promise<ComputerToolResult[]>;
|
|
142
|
+
};
|
|
143
|
+
export {};
|
package/dist/computer.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { ShardfluxApiError, ShardfluxProtocolError } from "./errors.js";
|
|
2
|
+
/** Decodes a result image (base64 in the API) to bytes. */
|
|
3
|
+
export function decodeComputerImage(img) {
|
|
4
|
+
return { format: img.format, width: img.width, height: img.height, data: new Uint8Array(Buffer.from(img.data, 'base64')) };
|
|
5
|
+
}
|
|
6
|
+
/** The workspace desktop (workspace.computer). */
|
|
7
|
+
export class WorkspaceComputer {
|
|
8
|
+
#deps;
|
|
9
|
+
constructor(deps) {
|
|
10
|
+
this.#deps = deps;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Runs actions in order (the first failure stops the batch; later actions come back `skipped`), optionally ending
|
|
14
|
+
* with a screenshot. The actions are Claude's computer toolset members with their parameter names.
|
|
15
|
+
*/
|
|
16
|
+
act(actions, opts = {}) {
|
|
17
|
+
return this.#deps.cell().computer.act({
|
|
18
|
+
actions,
|
|
19
|
+
...(opts.screenshot !== undefined ? { screenshot: opts.screenshot } : {}),
|
|
20
|
+
...(opts.settleMs !== undefined ? { settle_ms: opts.settleMs } : {}),
|
|
21
|
+
...(opts.format !== undefined ? { format: opts.format } : {}),
|
|
22
|
+
...(opts.quality !== undefined ? { quality: opts.quality } : {}),
|
|
23
|
+
}, opts.signal);
|
|
24
|
+
}
|
|
25
|
+
/** The screen now (the pointer drawn in). */
|
|
26
|
+
async screenshot(opts = {}) {
|
|
27
|
+
const r = await this.act([], { ...opts, screenshot: true });
|
|
28
|
+
if (!r.screenshot)
|
|
29
|
+
throw new ShardfluxProtocolError('POST /computer/actions: the desktop returned no screenshot', 200, 'cell');
|
|
30
|
+
return decodeComputerImage(r.screenshot);
|
|
31
|
+
}
|
|
32
|
+
/** Whether the desktop runs, its size and its viewers. Never starts it. */
|
|
33
|
+
status() {
|
|
34
|
+
return this.#deps.cell().computer.status();
|
|
35
|
+
}
|
|
36
|
+
/** Starts the desktop (a no-op while it runs; the size applies to a start only: 640x480..2560x1600, default 1280x800). */
|
|
37
|
+
start(size = {}) {
|
|
38
|
+
return this.#deps.cell().computer.start(size);
|
|
39
|
+
}
|
|
40
|
+
/** Stops the desktop; its windows close. The next call that needs it starts a fresh one. */
|
|
41
|
+
stop() {
|
|
42
|
+
return this.#deps.cell().computer.stop();
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A private link to watch the desktop (or use it, with `interactive`): starts the viewer in the workspace, exposes
|
|
46
|
+
* its port and signs a link to the viewer page (inbound ports, contracts §39). Anyone with the link can open it until
|
|
47
|
+
* it expires; `stopStream()` ends every view.
|
|
48
|
+
*/
|
|
49
|
+
async stream(opts = {}) {
|
|
50
|
+
const info = await this.#deps.cell().computer.streamStart({ interactive: opts.interactive ?? false });
|
|
51
|
+
const ports = this.#deps.ports();
|
|
52
|
+
await ports.expose(info.port);
|
|
53
|
+
const link = await ports.link(info.port, { path: info.path, ...(opts.ttlSeconds !== undefined ? { ttlSeconds: opts.ttlSeconds } : {}) });
|
|
54
|
+
return { url: link.url, expiresAt: link.expiresAt, port: info.port, interactive: info.interactive };
|
|
55
|
+
}
|
|
56
|
+
/** Ends every view and closes the viewer ports (their links stop working). */
|
|
57
|
+
async stopStream() {
|
|
58
|
+
const ports = this.#deps.ports();
|
|
59
|
+
await Promise.all([ports.close(61002), ports.close(61003)]);
|
|
60
|
+
await this.#deps.cell().computer.streamStop();
|
|
61
|
+
}
|
|
62
|
+
/** Switches computer use on or off for this workspace (null follows the template). */
|
|
63
|
+
setEnabled(enabled) {
|
|
64
|
+
return this.#deps.setEnabled(enabled);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// ---- Claude's computer toolset ------------------------------------------------------------------------------------------
|
|
68
|
+
/** The `tools` entry of Claude's computer toolset (no name, no display size). */
|
|
69
|
+
export const COMPUTER_TOOLSET = { type: 'computer_toolset_20260801' };
|
|
70
|
+
/** Parameters the toolset members take; anything else a model sends is not forwarded. */
|
|
71
|
+
const PARAMS = ['coordinate', 'start_coordinate', 'region', 'text', 'scroll_direction', 'scroll_amount', 'duration', 'repeat'];
|
|
72
|
+
const LOOKS = new Set(['screenshot', 'zoom']);
|
|
73
|
+
function imageContent(img) {
|
|
74
|
+
return { type: 'image', source: { type: 'base64', media_type: img.format === 'jpeg' ? 'image/jpeg' : 'image/png', data: img.data } };
|
|
75
|
+
}
|
|
76
|
+
function actionOf(block) {
|
|
77
|
+
const input = (block.input !== null && typeof block.input === 'object' ? block.input : {});
|
|
78
|
+
const a = { action: block.name };
|
|
79
|
+
for (const k of PARAMS)
|
|
80
|
+
if (input[k] !== undefined && input[k] !== null)
|
|
81
|
+
a[k] = input[k];
|
|
82
|
+
return a;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Answers Claude's computer toolset (`tools: [COMPUTER_TOOLSET]`, Claude Opus 5.5 / Sonnet 5.5 and later on the
|
|
86
|
+
* Claude API) from this workspace's desktop:
|
|
87
|
+
*
|
|
88
|
+
* const computer = computerToolset(workspace);
|
|
89
|
+
* const msg = await anthropic.messages.create({ model, max_tokens, tools: [computer.definition], messages });
|
|
90
|
+
* messages.push({ role: 'assistant', content: msg.content });
|
|
91
|
+
* messages.push({ role: 'user', content: await computer.run(msg.content) });
|
|
92
|
+
*
|
|
93
|
+
* `run` takes the response content, runs every `toolset_name: "computer"` call of the turn as one batch (in order;
|
|
94
|
+
* the first failure stops it and the rest are answered "Not executed"), and returns one `tool_result` per call:
|
|
95
|
+
* images for screenshot and zoom, the position for cursor_position, `OK` otherwise. When the batch does not end with a
|
|
96
|
+
* look, a screenshot is attached to the last result, so the model sees the outcome without another round trip. A
|
|
97
|
+
* batch the desktop refuses as invalid (a coordinate outside the screen) is answered with the reason on every call.
|
|
98
|
+
*/
|
|
99
|
+
export function computerToolset(workspace) {
|
|
100
|
+
return {
|
|
101
|
+
definition: COMPUTER_TOOLSET,
|
|
102
|
+
async run(content, opts = {}) {
|
|
103
|
+
const calls = content.filter((b) => b !== null && typeof b === 'object' && b.type === 'tool_use' && b.toolset_name === 'computer');
|
|
104
|
+
if (calls.length === 0)
|
|
105
|
+
return [];
|
|
106
|
+
const lastLooks = LOOKS.has(calls[calls.length - 1].name);
|
|
107
|
+
let r;
|
|
108
|
+
try {
|
|
109
|
+
r = await workspace.computer.act(calls.map(actionOf), {
|
|
110
|
+
screenshot: !lastLooks,
|
|
111
|
+
...(opts.format !== undefined ? { format: opts.format } : {}),
|
|
112
|
+
...(opts.quality !== undefined ? { quality: opts.quality } : {}),
|
|
113
|
+
...(opts.settleMs !== undefined ? { settleMs: opts.settleMs } : {}),
|
|
114
|
+
...(opts.signal !== undefined ? { signal: opts.signal } : {}),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
if (err instanceof ShardfluxApiError && err.code === 'validation_failed') {
|
|
119
|
+
const at = typeof err.details?.index === 'number' ? err.details.index : -1;
|
|
120
|
+
return calls.map((c, i) => ({
|
|
121
|
+
type: 'tool_result',
|
|
122
|
+
tool_use_id: c.id,
|
|
123
|
+
toolset_name: 'computer',
|
|
124
|
+
is_error: true,
|
|
125
|
+
content: [{ type: 'text', text: i === at || at < 0 ? err.message : 'Not executed: another computer action in this turn was invalid.' }],
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
throw err;
|
|
129
|
+
}
|
|
130
|
+
const out = calls.map((c, i) => {
|
|
131
|
+
const res = r.results[i];
|
|
132
|
+
if (!res || res.skipped) {
|
|
133
|
+
return { type: 'tool_result', tool_use_id: c.id, toolset_name: 'computer', is_error: true, content: [{ type: 'text', text: 'Not executed: an earlier computer action in this turn failed.' }] };
|
|
134
|
+
}
|
|
135
|
+
if (!res.ok) {
|
|
136
|
+
return { type: 'tool_result', tool_use_id: c.id, toolset_name: 'computer', is_error: true, content: [{ type: 'text', text: res.error?.message ?? 'The action failed.' }] };
|
|
137
|
+
}
|
|
138
|
+
const body = res.image ? [imageContent(res.image)] : [{ type: 'text', text: res.output ?? 'OK' }];
|
|
139
|
+
return { type: 'tool_result', tool_use_id: c.id, toolset_name: 'computer', content: body };
|
|
140
|
+
});
|
|
141
|
+
if (r.screenshot)
|
|
142
|
+
out[out.length - 1].content.push(imageContent(r.screenshot));
|
|
143
|
+
return out;
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|