@shardflux/sdk 0.13.1 → 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.
@@ -18,7 +18,7 @@
18
18
  import type { components } from './generated/app-api.js';
19
19
  type Operation = components['schemas']['Operation'];
20
20
  /** The SDK call a trace follows. `wait` is a direct waitForOperation(); `token` a tool token fetched for tool calls. */
21
- export type LifecycleAction = 'open' | 'suspend' | 'resume' | 'snapshot' | 'fork' | 'delete' | 'close' | 'reset' | 'wake' | 'wait' | 'token';
21
+ export type LifecycleAction = 'open' | 'suspend' | 'resume' | 'snapshot' | 'fork' | 'delete' | 'close' | 'reset' | 'resize' | 'wake' | 'wait' | 'token';
22
22
  /**
23
23
  * - `request`: an API request that starts or joins the operation. A held open (`Prefer: wait`) spends the server's
24
24
  * hold here (reason `held`), so the states inside it show only in the server timing.
@@ -89,8 +89,9 @@ export interface ServerTiming {
89
89
  memoryRestored?: boolean | null;
90
90
  /**
91
91
  * `result.cold_boot_reason` (0.11.0+), with `resumePath` `cold_boot`: why the memory could not be restored:
92
- * `runtime_changed` (the platform's VM runtime changed after the suspend) or `host_lost` (0.13.1+: the machine the
93
- * workspace ran on failed; it booted from its disk, files kept, see `hostLost`). Null otherwise.
92
+ * `runtime_changed` (the platform's VM runtime changed after the suspend), `host_lost` (0.13.1+: the machine the
93
+ * workspace ran on failed; it booted from its disk, files kept, see `hostLost`) or `runtime_retired` (0.14.0+: the
94
+ * runtime the workspace was suspended on was retired after an announced window). Null otherwise.
94
95
  */
95
96
  coldBootReason?: ColdBootReason | null;
96
97
  /**
@@ -152,11 +153,14 @@ export interface LostSuspend {
152
153
  stateAsOf: string | null;
153
154
  }
154
155
  /**
155
- * `result.cold_boot_reason` (0.11.0+): `runtime_changed` (the platform's VM runtime changed after the suspend) or
156
- * `host_lost` (0.13.1+: the machine the workspace ran on failed). Any other string is a reason this version does not
157
- * know.
156
+ * `result.cold_boot_reason` (0.11.0+): `runtime_changed` (the platform's VM runtime changed after the suspend),
157
+ * `host_lost` (0.13.1+: the machine the workspace ran on failed) or `runtime_retired` (0.14.0+: the runtime the
158
+ * workspace was suspended on was retired after an announced window). Any other string is a reason this version does
159
+ * not know.
158
160
  */
159
- export type ColdBootReason = 'runtime_changed' | 'host_lost' | (string & {});
161
+ export type ColdBootReason = (typeof COLD_BOOT_REASONS)[number] | (string & {});
162
+ /** Every {@link ColdBootReason} this version knows (0.14.0+). */
163
+ export declare const COLD_BOOT_REASONS: readonly ["runtime_changed", "host_lost", "runtime_retired"];
160
164
  /**
161
165
  * `result.host_lost` (0.13.1+), camelCased: the machine the workspace ran on failed. The workspace was moved to
162
166
  * `suspended` at that moment, and its next use (a resume, a tool call's wake, an open) restored it. See the lifecycle
package/dist/progress.js CHANGED
@@ -1,3 +1,5 @@
1
+ /** Every {@link ColdBootReason} this version knows (0.14.0+). */
2
+ export const COLD_BOOT_REASONS = ['runtime_changed', 'host_lost', 'runtime_retired'];
1
3
  const DURABILITY_STATES = new Set(['pending', 'durable', 'lost']);
2
4
  /**
3
5
  * The durable copy of a suspend or fork operation (0.12.0+): `result.durability` camelCased, or null when the result has
@@ -215,6 +215,8 @@ export interface TemplateSummary {
215
215
  memory_mib: number | null;
216
216
  idle_policy: string | null;
217
217
  };
218
+ /** Computer use (0.15.0+, contracts §45.1): the template's switch for its workspaces (a workspace may override it). */
219
+ computer_use: boolean;
218
220
  open_version: TemplateVersion | null;
219
221
  /** The live draft (organization templates in dev mode), or null. */
220
222
  draft: TemplateDraftSummary | null;
@@ -900,6 +902,16 @@ export declare class TemplatesApi {
900
902
  includeArchived?: boolean;
901
903
  owner?: TemplateOwner;
902
904
  }): Promise<TemplateDetail>;
905
+ /**
906
+ * Computer use (0.15.0+, contracts §45.1): switches it on or off for every workspace of one of your organization's
907
+ * templates (a workspace may override it with setComputerUse). 403 for platform templates (switch a workspace on
908
+ * instead). Owners and admins (sessions) or API keys of the organization with a tool permission.
909
+ */
910
+ setComputerUse(slug: string, enabled: boolean, params?: {
911
+ organizationId?: string;
912
+ }): Promise<{
913
+ enabled: boolean;
914
+ }>;
903
915
  /** Like get() but resolves to null on 404 (unknown slug or outside the organization). */
904
916
  find(slug: string, params?: {
905
917
  organizationId?: string;
package/dist/templates.js CHANGED
@@ -693,6 +693,15 @@ export class TemplatesApi {
693
693
  const path = params.organizationId === undefined ? `/v1/templates/${enc(slug)}` : `/v1/organizations/${enc(params.organizationId)}/templates/${enc(slug)}`;
694
694
  return this.#ctx().http.json('GET', path, { query: { include_archived: params.includeArchived, owner: params.owner } }, this.#ctx().authorization);
695
695
  }
696
+ /**
697
+ * Computer use (0.15.0+, contracts §45.1): switches it on or off for every workspace of one of your organization's
698
+ * templates (a workspace may override it with setComputerUse). 403 for platform templates (switch a workspace on
699
+ * instead). Owners and admins (sessions) or API keys of the organization with a tool permission.
700
+ */
701
+ async setComputerUse(slug, enabled, params = {}) {
702
+ const path = params.organizationId === undefined ? `/v1/templates/${enc(slug)}/computer-use` : `/v1/organizations/${enc(params.organizationId)}/templates/${enc(slug)}/computer-use`;
703
+ return this.#ctx().http.json('PUT', path, { json: { enabled } }, this.#ctx().authorization);
704
+ }
696
705
  /** Like get() but resolves to null on 404 (unknown slug or outside the organization). */
697
706
  async find(slug, params = {}) {
698
707
  try {
package/dist/tools.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import type { CellClientOptions } from './cell.js';
1
+ import type { CellClient, CellClientOptions } from './cell.js';
2
2
  import type { WorkspaceMode } from './errors.js';
3
3
  import type { ToolName } from './tokens.js';
4
- import type { Workspace } from './workspace.js';
4
+ import type { HintOptions, HintResult } from './workspace.js';
5
5
  /**
6
6
  * The JSON Schema subset of the tool definitions. A type alias rather than an interface (0.8.0+), so a schema is
7
7
  * assignable to the providers' open schema types (`{ [key: string]: unknown }`) without a cast, and an object literal
@@ -33,6 +33,8 @@ export interface WorkspaceTool<A extends Record<string, unknown> = Record<string
33
33
  };
34
34
  /** Which workspace tool permission the call needs (exec, files, pty, process, git, browser). */
35
35
  permission: ToolName;
36
+ /** Opt-in input-start hook (0.14.0+): call when the model starts this tool's input. Returns immediately. */
37
+ onInputStart?: () => void;
36
38
  /** `toolCallId` (0.7.0+): the model's call id, recorded by tool-call capture (executeToolCall passes it). */
37
39
  execute(args: A, options?: {
38
40
  signal?: AbortSignal;
@@ -46,6 +48,19 @@ export declare class ToolArgumentError extends Error {
46
48
  }
47
49
  /** Validates the JSON-Schema subset used by the tool definitions. Returns problems (empty = valid). */
48
50
  export declare function validateArgs(schema: JsonSchema, value: unknown, path?: string): string[];
51
+ /**
52
+ * What workspace tools act on: an opened `Workspace`, or a `WorkspaceRef` (0.15.0+) whose first tool call opens the key.
53
+ * `cell()` may return a promise (a ref opens first); `mode` and `grantedTools` are read when the tools are built.
54
+ */
55
+ export interface ToolTarget {
56
+ readonly mode: WorkspaceMode;
57
+ readonly grantedTools: ToolName[] | null;
58
+ cell(opts?: {
59
+ agentLabel?: string;
60
+ tools?: ToolName[];
61
+ } & CellClientOptions): CellClient | Promise<CellClient>;
62
+ hint(opts?: HintOptions): Promise<HintResult>;
63
+ }
49
64
  export interface WorkspaceToolsOptions {
50
65
  /**
51
66
  * Tool permissions to expose (default: the tools of the workspace's last token, else all). A file-first workspace
@@ -74,6 +89,12 @@ export interface WorkspaceToolsOptions {
74
89
  * suspended workspace (or restore a hibernated one) that the read does not need.
75
90
  */
76
91
  hint?: boolean;
92
+ /**
93
+ * Add `onInputStart` hooks (0.14.0+) to tools that need the VM. Invoke the matching hook when the model starts
94
+ * streaming that tool's input, ahead of execute. Default false: no hook, timer or request is installed.
95
+ * Offline file reads and file-first workspaces have no hook.
96
+ */
97
+ prewake?: boolean;
77
98
  /**
78
99
  * The mode to build the tools for (0.9.0+; default `workspace.mode`). Given, the workspace is not touched until a tool
79
100
  * runs, so definitions can be built without one (e.g. to publish them before any workspace exists).
@@ -94,7 +115,7 @@ export interface WorkspaceToolsOptions {
94
115
  burst?: boolean;
95
116
  }
96
117
  /** Builds the tool list for a workspace. Synchronous: tokens are fetched on first use. */
97
- export declare function workspaceTools(workspace: Workspace, opts?: WorkspaceToolsOptions): WorkspaceTool[];
118
+ export declare function workspaceTools(workspace: ToolTarget, opts?: WorkspaceToolsOptions): WorkspaceTool[];
98
119
  /** Anthropic Messages API tool definition; assignable to `Anthropic.Tool` (0.8.0+). */
99
120
  export type AnthropicToolDefinition = {
100
121
  name: string;
package/dist/tools.js CHANGED
@@ -82,14 +82,62 @@ export function validateArgs(schema, value, path = '$') {
82
82
  issues.push(`${path} must be one of ${schema.enum.join(', ')}`);
83
83
  return issues;
84
84
  }
85
+ // Without a token yet (no grantedTools) and no opts.tools, every tool but computer: computer is offered only when a
86
+ // token grants it (the workspace's computer use is on) or opts.tools names it.
85
87
  const ALL = ['exec', 'files', 'pty', 'process', 'git', 'browser'];
86
88
  /** The tool permissions whose tools work on a file-first workspace: files and executions. */
87
89
  const FILE_FIRST_TOOLS = ['exec', 'files'];
88
90
  /** Changed paths returned to the model per execution (the rest is flagged `changed_truncated`). */
89
91
  const MAX_CHANGED_LISTED = 200;
92
+ /** The processful exec's memory hint (0.14.0; RunOptions.resourceHint). Sent only when the model sets it. */
93
+ const RESOURCE_HINT_SCHEMA = {
94
+ type: 'string',
95
+ enum: ['auto', 'light', 'heavy'],
96
+ description: 'heavy: give this command more memory before it starts (a build, a test suite, a package install, a training run); light: start it at once. Default auto: decided from the command.',
97
+ };
98
+ const RESOURCE_HINTS = new Set(['auto', 'light', 'heavy']);
90
99
  /** Tools served from a sleeping workspace's disk without waking it: the runner sends no hint. */
91
100
  const DISK_READS = new Set(['read_file', 'list_files', 'search_files']);
92
101
  const obj = (properties, required = []) => ({ type: 'object', properties, required, additionalProperties: false });
102
+ /** One computer toolset action's parameters (the computer tool's and each computer_batch item's). */
103
+ const COMPUTER_ACTION = {
104
+ action: {
105
+ type: 'string',
106
+ enum: ['screenshot', 'zoom', 'left_click', 'right_click', 'middle_click', 'double_click', 'triple_click', 'left_click_drag', 'mouse_move',
107
+ 'left_mouse_down', 'left_mouse_up', 'cursor_position', 'scroll', 'type', 'key', 'hold_key', 'wait'],
108
+ },
109
+ 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).' },
110
+ start_coordinate: { type: 'array', items: { type: 'integer', minimum: 0 }, minItems: 2, maxItems: 2, description: 'left_click_drag: where the drag starts.' },
111
+ region: { type: 'array', items: { type: 'integer', minimum: 0 }, minItems: 4, maxItems: 4, description: 'zoom: [x0, y0, x1, y1], shown enlarged.' },
112
+ 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".' },
113
+ scroll_direction: { type: 'string', enum: ['up', 'down', 'left', 'right'] },
114
+ scroll_amount: { type: 'integer', minimum: 1, maximum: 100, description: 'scroll: wheel clicks.' },
115
+ duration: { type: 'number', minimum: 0, maximum: 300, description: 'Seconds: wait, hold_key.' },
116
+ repeat: { type: 'integer', minimum: 1, maximum: 100, description: 'key: presses (default 1).' },
117
+ };
118
+ /** The screenshot after input actions (0.15.0+). */
119
+ const COMPUTER_LOOK = {
120
+ screenshot: { type: 'boolean', description: 'Take a screenshot after the input actions (default true); false when you do not need to look.' },
121
+ settle_ms: { type: 'integer', minimum: 0, maximum: 10_000, description: 'Wait this long before that screenshot when the screen may change (default 250).' },
122
+ format: { type: 'string', enum: ['png', 'jpeg'], description: 'Image format (default png; jpeg is smaller).' },
123
+ quality: { type: 'integer', minimum: 1, maximum: 100, description: 'JPEG quality (default 80).' },
124
+ };
125
+ const COMPUTER_ACTION_KEYS = ['coordinate', 'start_coordinate', 'region', 'text', 'scroll_direction', 'scroll_amount', 'duration', 'repeat'];
126
+ function computerAction(a) {
127
+ const action = { action: a.action };
128
+ for (const k of COMPUTER_ACTION_KEYS)
129
+ if (a[k] !== undefined && a[k] !== null)
130
+ action[k] = a[k];
131
+ return action;
132
+ }
133
+ function computerImage(a) {
134
+ return {
135
+ ...(typeof a.settle_ms === 'number' ? { settle_ms: a.settle_ms } : {}),
136
+ ...(a.format === 'png' || a.format === 'jpeg' ? { format: a.format } : {}),
137
+ ...(typeof a.quality === 'number' ? { quality: a.quality } : {}),
138
+ };
139
+ }
140
+ const imageView = (img) => ({ mime_type: img.format === 'jpeg' ? 'image/jpeg' : 'image/png', width: img.width, height: img.height, data_base64: img.data });
93
141
  const path = (description = 'Absolute path inside the workspace, e.g. /home/user/project/main.py') => ({ type: 'string', minLength: 1, maxLength: 4096, description });
94
142
  const signal = { type: 'string', description: 'Signal name such as SIGTERM, SIGINT or SIGKILL.', minLength: 2, maxLength: 12 };
95
143
  function clip(text, max) {
@@ -238,7 +286,7 @@ async function waitForExit(c, s, waitMs, signal) {
238
286
  }
239
287
  /** Builds the tool list for a workspace. Synchronous: tokens are fetched on first use. */
240
288
  export function workspaceTools(workspace, opts = {}) {
241
- const cell = () => workspace.cell({
289
+ const cell = async () => workspace.cell({
242
290
  ...(opts.agentLabel !== undefined ? { agentLabel: opts.agentLabel } : {}),
243
291
  ...(opts.wake !== undefined ? { wake: opts.wake } : {}),
244
292
  ...(opts.transitionTimeoutMs !== undefined ? { transitionTimeoutMs: opts.transitionTimeoutMs } : {}),
@@ -267,6 +315,7 @@ export function workspaceTools(workspace, opts = {}) {
267
315
  type: 'boolean',
268
316
  description: 'Start the command and return its session_id at once instead of waiting for it to exit (default false). Use it for anything that may run longer than a few minutes, such as a build, a test suite, a training run or a server. Keep using the other tools meanwhile, read it with exec_read and stop it with exec_cancel. Set timeout_ms to the longest it may run: the workspace stays awake until the command ends or that time passes (1 hour when unset).',
269
317
  },
318
+ resource_hint: RESOURCE_HINT_SCHEMA,
270
319
  ...(opts.burst
271
320
  ? {
272
321
  burst: {
@@ -289,7 +338,7 @@ export function workspaceTools(workspace, opts = {}) {
289
338
  run: async (a, o) => {
290
339
  const executionId = newExecutionId();
291
340
  opts.onExecution?.(executionId);
292
- const r = await cell().executions.run(['bash', '-lc', String(a.command)], {
341
+ const r = await (await cell()).executions.run(['bash', '-lc', String(a.command)], {
293
342
  executionId,
294
343
  ...(typeof a.cwd === 'string' ? { cwd: a.cwd } : opts.defaultCwd ? { cwd: opts.defaultCwd } : {}),
295
344
  timeoutMs: typeof a.timeout_ms === 'number' ? a.timeout_ms : 600_000,
@@ -330,15 +379,17 @@ export function workspaceTools(workspace, opts = {}) {
330
379
  const burst = opts.burst && (a.burst === 'always' || a.burst === 'never') ? a.burst : undefined;
331
380
  const burstVcpus = opts.burst && typeof a.burst_vcpus === 'number' ? a.burst_vcpus : undefined;
332
381
  const burstMemoryMib = opts.burst && typeof a.burst_memory_mib === 'number' ? a.burst_memory_mib : undefined;
382
+ const resourceHint = RESOURCE_HINTS.has(a.resource_hint) ? a.resource_hint : undefined;
333
383
  const cwd = typeof a.cwd === 'string' ? a.cwd : opts.defaultCwd;
334
384
  if (a.background === true) {
335
385
  // A session of its own: the start answers at once, and exec_read / exec_cancel find it by session_id.
336
386
  // The command runs until it exits or its timeout_ms, if one is set. (Never a burst: refused above.)
337
- const s = await cell().exec.start({
387
+ const s = await (await cell()).exec.start({
338
388
  argv: ['bash', '-lc', String(a.command)],
339
389
  ...(cwd !== undefined ? { cwd } : {}),
340
390
  ...(typeof a.timeout_ms === 'number' ? { timeout_ms: a.timeout_ms } : {}),
341
391
  ...(typeof a.stdin === 'string' ? { stdin: Buffer.from(a.stdin, 'utf8').toString('base64') } : {}),
392
+ ...(resourceHint !== undefined ? { resource_hint: resourceHint } : {}),
342
393
  ...(burst !== undefined ? { burst } : {}),
343
394
  ...(burstVcpus !== undefined ? { burst_vcpus: burstVcpus } : {}),
344
395
  ...(burstMemoryMib !== undefined ? { burst_memory_mib: burstMemoryMib } : {}),
@@ -349,10 +400,11 @@ export function workspaceTools(workspace, opts = {}) {
349
400
  }
350
401
  let r;
351
402
  try {
352
- r = await cell().exec.run(['bash', '-lc', String(a.command)], {
403
+ r = await (await cell()).exec.run(['bash', '-lc', String(a.command)], {
353
404
  ...(cwd !== undefined ? { cwd } : {}),
354
405
  timeoutMs: typeof a.timeout_ms === 'number' ? a.timeout_ms : 600_000,
355
406
  ...(typeof a.stdin === 'string' ? { stdin: a.stdin } : {}),
407
+ ...(resourceHint !== undefined ? { resourceHint } : {}),
356
408
  ...(burst !== undefined ? { burst } : {}),
357
409
  ...(burstVcpus !== undefined ? { burstVcpus } : {}),
358
410
  ...(burstMemoryMib !== undefined ? { burstMemoryMib } : {}),
@@ -400,7 +452,7 @@ export function workspaceTools(workspace, opts = {}) {
400
452
  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.',
401
453
  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']),
402
454
  run: async (a, o) => {
403
- const c = cell();
455
+ const c = await cell();
404
456
  const id = String(a.session_id);
405
457
  let s = await c.exec.get(id);
406
458
  if (typeof a.wait_ms === 'number' && a.wait_ms > 0 && running(s))
@@ -450,7 +502,7 @@ export function workspaceTools(workspace, opts = {}) {
450
502
  // The workspace waits 5 s without a grace and at most 60 s.
451
503
  parameters: obj({ session_id: sessionId, grace_ms: { type: 'integer', minimum: 1, maximum: 60_000 } }, ['session_id']),
452
504
  run: async (a) => {
453
- const s = await cell().exec.cancel(String(a.session_id), typeof a.grace_ms === 'number' ? a.grace_ms : undefined);
505
+ const s = await (await cell()).exec.cancel(String(a.session_id), typeof a.grace_ms === 'number' ? a.grace_ms : undefined);
454
506
  return { session_id: s.session_id, state: s.state, exit_code: running(s) ? null : (s.exit_code ?? null), canceled: s.canceled ?? false };
455
507
  },
456
508
  },
@@ -461,7 +513,7 @@ export function workspaceTools(workspace, opts = {}) {
461
513
  description: 'Read a text file from the workspace (UTF-8). Use offset/length for large files.',
462
514
  parameters: obj({ path: path(), offset: { type: 'integer', minimum: 0 }, length: { type: 'integer', minimum: 1, maximum: 10_485_760 } }, ['path']),
463
515
  run: async (a) => {
464
- const bytes = await cell().files.read(String(a.path), {
516
+ const bytes = await (await cell()).files.read(String(a.path), {
465
517
  ...(typeof a.offset === 'number' ? { offset: a.offset } : {}),
466
518
  length: typeof a.length === 'number' ? Math.min(a.length, max) : max + 1,
467
519
  });
@@ -480,7 +532,7 @@ export function workspaceTools(workspace, opts = {}) {
480
532
  create_parents: { type: 'boolean', description: 'Create missing parent directories (default true).' },
481
533
  }, ['path', 'content']),
482
534
  run: async (a) => {
483
- const r = await cell().files.write(String(a.path), String(a.content), { append: a.append === true, createParents: a.create_parents !== false });
535
+ const r = await (await cell()).files.write(String(a.path), String(a.content), { append: a.append === true, createParents: a.create_parents !== false });
484
536
  return { path: r.path, bytes_written: r.bytes_written, sha256: r.sha256, durable: r.durable };
485
537
  },
486
538
  },
@@ -490,7 +542,7 @@ export function workspaceTools(workspace, opts = {}) {
490
542
  description: 'List a directory in the workspace.',
491
543
  parameters: obj({ path: path('Absolute directory path.'), limit: { type: 'integer', minimum: 1, maximum: 10_000 } }, ['path']),
492
544
  run: async (a) => {
493
- const r = await cell().files.list(String(a.path), typeof a.limit === 'number' ? { limit: a.limit } : { limit: 500 });
545
+ const r = await (await cell()).files.list(String(a.path), typeof a.limit === 'number' ? { limit: a.limit } : { limit: 500 });
494
546
  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 };
495
547
  },
496
548
  },
@@ -509,7 +561,7 @@ export function workspaceTools(workspace, opts = {}) {
509
561
  context_lines: { type: 'integer', minimum: 0, maximum: 5, description: 'Lines of context to return before and after each match.' },
510
562
  }, ['path', 'pattern']),
511
563
  run: async (a, o) => {
512
- const r = await cell().files.search(String(a.path), String(a.pattern), {
564
+ const r = await (await cell()).files.search(String(a.path), String(a.pattern), {
513
565
  ...(typeof a.regex === 'boolean' ? { regex: a.regex } : {}),
514
566
  ...(typeof a.case_insensitive === 'boolean' ? { caseInsensitive: a.case_insensitive } : {}),
515
567
  ...(Array.isArray(a.include) ? { include: a.include } : {}),
@@ -555,7 +607,7 @@ export function workspaceTools(workspace, opts = {}) {
555
607
  },
556
608
  }, ['path', 'edits']),
557
609
  run: async (a, o) => {
558
- const c = cell();
610
+ const c = await cell();
559
611
  const file = String(a.path);
560
612
  // Without a revision from the model, pin the edit to the content current now, so a concurrent change between
561
613
  // this read and the patch is refused (revision_mismatch) instead of edited blindly.
@@ -571,7 +623,7 @@ export function workspaceTools(workspace, opts = {}) {
571
623
  description: 'List processes running in the workspace.',
572
624
  parameters: obj({}),
573
625
  run: async () => {
574
- const r = await cell().processes.list();
626
+ const r = await (await cell()).processes.list();
575
627
  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 })) };
576
628
  },
577
629
  },
@@ -581,7 +633,7 @@ export function workspaceTools(workspace, opts = {}) {
581
633
  description: 'Send a signal to a process in the workspace (e.g. stop a server).',
582
634
  parameters: obj({ pid: { type: 'integer', minimum: 2 }, signal }, ['pid', 'signal']),
583
635
  run: async (a) => {
584
- await cell().processes.signal(Number(a.pid), String(a.signal));
636
+ await (await cell()).processes.signal(Number(a.pid), String(a.signal));
585
637
  return { signalled: true };
586
638
  },
587
639
  },
@@ -591,7 +643,7 @@ export function workspaceTools(workspace, opts = {}) {
591
643
  description: 'Open an interactive terminal (PTY) in the workspace; returns a session_id for terminal_send/terminal_read.',
592
644
  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 } }),
593
645
  run: async (a) => {
594
- const s = await cell().pty.open({
646
+ const s = await (await cell()).pty.open({
595
647
  ...(typeof a.command === 'string' ? { argv: ['bash', '-lc', a.command] } : {}),
596
648
  ...(typeof a.rows === 'number' ? { rows: a.rows } : {}),
597
649
  ...(typeof a.cols === 'number' ? { cols: a.cols } : {}),
@@ -605,7 +657,7 @@ export function workspaceTools(workspace, opts = {}) {
605
657
  description: 'Type input into a terminal session (include "\\n" to press Enter).',
606
658
  parameters: obj({ session_id: { type: 'string', minLength: 1, maxLength: 64 }, input: { type: 'string', maxLength: 1_000_000 } }, ['session_id', 'input']),
607
659
  run: async (a) => {
608
- const s = await cell().pty.input(String(a.session_id), String(a.input));
660
+ const s = await (await cell()).pty.input(String(a.session_id), String(a.input));
609
661
  return { state: s.state, next_offset: s.output_size };
610
662
  },
611
663
  },
@@ -615,7 +667,7 @@ export function workspaceTools(workspace, opts = {}) {
615
667
  description: 'Read terminal output from an offset (returns next_offset to continue). Waits briefly for new output.',
616
668
  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']),
617
669
  run: async (a) => {
618
- const r = await cell().pty.read(String(a.session_id), {
670
+ const r = await (await cell()).pty.read(String(a.session_id), {
619
671
  offset: typeof a.offset === 'number' ? a.offset : 0,
620
672
  timeoutMs: typeof a.wait_ms === 'number' ? Math.max(100, a.wait_ms) : 3_000,
621
673
  maxBytes: max,
@@ -629,7 +681,7 @@ export function workspaceTools(workspace, opts = {}) {
629
681
  description: 'Close a terminal session.',
630
682
  parameters: obj({ session_id: { type: 'string', minLength: 1, maxLength: 64 } }, ['session_id']),
631
683
  run: async (a) => {
632
- const s = await cell().pty.close(String(a.session_id));
684
+ const s = await (await cell()).pty.close(String(a.session_id));
633
685
  return { state: s.state };
634
686
  },
635
687
  },
@@ -639,7 +691,7 @@ export function workspaceTools(workspace, opts = {}) {
639
691
  description: 'Clone a git repository (HTTPS) into the workspace.',
640
692
  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']),
641
693
  run: async (a) => {
642
- const r = await cell().git.clone({
694
+ const r = await (await cell()).git.clone({
643
695
  url: String(a.url),
644
696
  path: String(a.path),
645
697
  ...(typeof a.branch === 'string' ? { branch: a.branch } : {}),
@@ -653,7 +705,7 @@ export function workspaceTools(workspace, opts = {}) {
653
705
  permission: 'git',
654
706
  description: 'Show the git status of a repository in the workspace.',
655
707
  parameters: obj({ path: path('Repository directory.') }, ['path']),
656
- run: async (a) => cell().git.status(String(a.path)),
708
+ run: async (a) => (await cell()).git.status(String(a.path)),
657
709
  },
658
710
  {
659
711
  name: 'git_commit',
@@ -661,17 +713,65 @@ export function workspaceTools(workspace, opts = {}) {
661
713
  description: 'Stage all changes and commit in a repository in the workspace.',
662
714
  parameters: obj({ path: path('Repository directory.'), message: { type: 'string', minLength: 1, maxLength: 65_536 } }, ['path', 'message']),
663
715
  run: async (a) => {
664
- const r = await cell().git.commit({ path: String(a.path), message: String(a.message), all: true });
716
+ const r = await (await cell()).git.commit({ path: String(a.path), message: String(a.message), all: true });
665
717
  return { exit_code: r.exit_code, commit: r.commit ?? null, stdout: clip(r.stdout, max).text, stderr: clip(r.stderr, max).text };
666
718
  },
667
719
  },
720
+ {
721
+ name: 'computer',
722
+ permission: 'computer',
723
+ 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.',
724
+ parameters: obj({ ...COMPUTER_ACTION, ...COMPUTER_LOOK }, ['action']),
725
+ run: async (a, o) => {
726
+ const look = a.action === 'screenshot' || a.action === 'zoom' || a.action === 'cursor_position';
727
+ const r = await (await cell()).computer.act({ actions: [computerAction(a)], screenshot: !look && a.screenshot !== false, ...computerImage(a) }, o.signal);
728
+ const res = r.results[0];
729
+ const img = res?.image ?? r.screenshot;
730
+ return {
731
+ ok: res?.ok ?? false,
732
+ ...(res?.output !== undefined ? { output: res.output } : {}),
733
+ ...(res?.error ? { error: res.error.message } : {}),
734
+ cursor: r.cursor,
735
+ display: r.display,
736
+ ...(img ? { screenshot: imageView(img) } : {}),
737
+ };
738
+ },
739
+ },
740
+ {
741
+ name: 'computer_batch',
742
+ permission: 'computer',
743
+ 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).',
744
+ parameters: obj({
745
+ actions: { type: 'array', minItems: 1, maxItems: 50, items: obj(COMPUTER_ACTION, ['action']), description: 'Actions with the computer tool\'s parameters, run in order.' },
746
+ ...COMPUTER_LOOK,
747
+ }, ['actions']),
748
+ run: async (a, o) => {
749
+ const actions = a.actions.map(computerAction);
750
+ const r = await (await cell()).computer.act({ actions, screenshot: a.screenshot !== false, ...computerImage(a) }, o.signal);
751
+ return {
752
+ ok: r.results.every((x) => x.ok),
753
+ results: r.results.map((x) => ({
754
+ action: x.action,
755
+ ok: x.ok,
756
+ ...(x.skipped ? { skipped: true } : {}),
757
+ ...(x.output !== undefined ? { output: x.output } : {}),
758
+ ...(x.error ? { error: x.error.message } : {}),
759
+ took_ms: x.took_ms,
760
+ ...(x.image ? { image: imageView(x.image) } : {}),
761
+ })),
762
+ cursor: r.cursor,
763
+ display: r.display,
764
+ ...(r.screenshot ? { screenshot: imageView(r.screenshot) } : {}),
765
+ };
766
+ },
767
+ },
668
768
  {
669
769
  name: 'browser_screenshot',
670
770
  permission: 'browser',
671
771
  description: 'Open a URL in the workspace’s headless browser and return a PNG screenshot (base64).',
672
772
  parameters: obj({ url: { type: 'string', minLength: 8, maxLength: 8192 }, width: { type: 'integer', minimum: 100, maximum: 3840 }, height: { type: 'integer', minimum: 100, maximum: 2160 } }, ['url']),
673
773
  run: async (a) => {
674
- const png = await cell().browser.screenshot({
774
+ const png = await (await cell()).browser.screenshot({
675
775
  url: String(a.url),
676
776
  ...(typeof a.width === 'number' ? { width: a.width } : {}),
677
777
  ...(typeof a.height === 'number' ? { height: a.height } : {}),
@@ -685,7 +785,7 @@ export function workspaceTools(workspace, opts = {}) {
685
785
  description: 'Open a URL in the workspace’s headless browser and return the rendered page as text or HTML.',
686
786
  parameters: obj({ url: { type: 'string', minLength: 8, maxLength: 8192 }, format: { type: 'string', enum: ['text', 'html'] } }, ['url']),
687
787
  run: async (a) => {
688
- const r = await cell().browser.content({ url: String(a.url), format: a.format === 'html' ? 'html' : 'text' });
788
+ const r = await (await cell()).browser.content({ url: String(a.url), format: a.format === 'html' ? 'html' : 'text' });
689
789
  const c = clip(r.content, max);
690
790
  return { url: r.url, format: r.format, content: c.text, truncated: r.truncated || c.truncated };
691
791
  },
@@ -698,6 +798,15 @@ export function workspaceTools(workspace, opts = {}) {
698
798
  description: d.description,
699
799
  parameters: d.parameters,
700
800
  permission: d.permission,
801
+ ...(opts.prewake === true && !fileFirst && !DISK_READS.has(d.name) ? {
802
+ onInputStart: () => {
803
+ workspace.hint({
804
+ ...(opts.agentLabel !== undefined ? { agentLabel: opts.agentLabel } : {}),
805
+ wake: opts.wake ?? null,
806
+ ...(opts.transitionTimeoutMs !== undefined ? { wakeTimeoutMs: opts.transitionTimeoutMs } : {}),
807
+ }).catch(() => undefined);
808
+ },
809
+ } : {}),
701
810
  execute: async (args, options = {}) => {
702
811
  const issues = validateArgs(d.parameters, args);
703
812
  if (issues.length === 0 && d.refuse)
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Workspaces by key (0.15.0+, contracts §46). `cloud.workspace(key, { template })` names a workspace without a request.
3
+ * Its first call opens the key (the workspace is created on first use and resumed afterwards, one held request that
4
+ * returns it with a tool token), and later calls go straight to the workspace, which wakes on use.
5
+ */
6
+ import type { CellClient, CellClientOptions, RunOptions, RunResult } from './cell.js';
7
+ import { CAPTURE_BARRIER } from './cell.js';
8
+ import type { OpenParams, WorkspacesApi } from './client.js';
9
+ import type { WorkspaceMode } from './errors.js';
10
+ import type { ExecutionGetOptions, ExecutionResult, ExecutionRunOptions } from './executions.js';
11
+ import type { ToolName } from './tokens.js';
12
+ import type { ToolTarget, WorkspaceTool, WorkspaceToolsOptions } from './tools.js';
13
+ import type { HintOptions, HintResult, Workspace } from './workspace.js';
14
+ /**
15
+ * `cloud.workspace(key, params)`: the open's parameters without the key. `template` is required, unless `create: false`:
16
+ * then the first call finds the key's live workspace (a lookup, no VM start) and fails with ShardfluxApiError 404
17
+ * `not_found` when there is none. Default: the first call opens the key, creating the workspace.
18
+ */
19
+ export type WorkspaceRefParams = (Omit<OpenParams, 'key'> & {
20
+ create?: true;
21
+ }) | (Omit<OpenParams, 'key' | 'template'> & {
22
+ template?: string;
23
+ create: false;
24
+ });
25
+ type Files = CellClient['files'];
26
+ type FileMethod = 'read' | 'readText' | 'readWithInfo' | 'write' | 'list' | 'stat' | 'remove' | 'mkdir' | 'move' | 'search' | 'patch';
27
+ /** The workspace's files, as on `cell().files`; each call opens the key first if needed. */
28
+ export type WorkspaceRefFiles = Pick<Files, FileMethod>;
29
+ /**
30
+ * A workspace named by its key. Nothing is requested until the first call: `exec()`, `files`, a tool from `tools()`,
31
+ * `hint()` or `open()`. Concurrent first calls share one open; a failed open is not kept, so the next call opens again.
32
+ * A workspace deleted under the ref (409 `workspace_deleted`, `isWorkspaceGone`) fails the call that meets it and is
33
+ * forgotten: the next call opens the key again (a new workspace, as `open()` would). `open()` returns the full
34
+ * `Workspace` (suspend, fork, ports, computer, tool-call capture).
35
+ */
36
+ export declare class WorkspaceRef implements ToolTarget {
37
+ #private;
38
+ readonly key: string;
39
+ /** Internal: use `cloud.workspace(key, params)` or the module-level `workspace(key, params)`. */
40
+ constructor(api: WorkspacesApi, key: string, params: WorkspaceRefParams, grants: () => Promise<ToolName[] | null>);
41
+ /**
42
+ * Whether the open that produced the current workspace created it (contracts §46.2): undefined until the ref has
43
+ * opened (or when the API does not report it), false for `create: false`.
44
+ */
45
+ get created(): boolean | undefined;
46
+ /** The opened workspace's mode, else the params' (`processful` when neither says). */
47
+ get mode(): WorkspaceMode;
48
+ /** Tools granted by the opened workspace's last token; null before the open. */
49
+ get grantedTools(): ToolName[] | null;
50
+ /** The workspace: opened (or, with `create: false`, found) on the first call and kept. */
51
+ open(): Promise<Workspace>;
52
+ /**
53
+ * Runs a command and collects its output (`cell().exec.run()`). A string runs through `bash -lc`, so shell syntax
54
+ * works; an argv array runs without a shell. A file-first workspace runs commands with `executions.run()`.
55
+ */
56
+ exec(command: string | readonly string[], opts?: RunOptions): Promise<RunResult>;
57
+ /** The workspace's files, as on `cell().files`. */
58
+ readonly files: WorkspaceRefFiles;
59
+ /** A file-first workspace's executions (`run`, `get`), as on `Workspace.executions`. */
60
+ readonly executions: {
61
+ run: (argv: string[], opts?: ExecutionRunOptions) => Promise<ExecutionResult>;
62
+ get: (executionId: string, opts?: ExecutionGetOptions) => Promise<ExecutionResult>;
63
+ };
64
+ /** The workspace's cell client (`Workspace.cell()`), opening the key first if needed. */
65
+ cell(opts?: {
66
+ agentLabel?: string;
67
+ tools?: ToolName[];
68
+ } & CellClientOptions): Promise<CellClient>;
69
+ /**
70
+ * Says a tool call is coming. Before the first open it starts the open in the background (`wake` resolves when the
71
+ * workspace runs; nothing has to await it), unless `wake: null`; afterwards it is `Workspace.hint()`.
72
+ */
73
+ hint(opts?: HintOptions): Promise<HintResult>;
74
+ /**
75
+ * Workspace tools for this key (`workspaceTools`), built before any VM exists: the definitions come from the API
76
+ * key's tool permissions (`GET /v1/me`, read once per client), and `computer` only with `computerUse: true` in the
77
+ * params or on an opened workspace whose computer use is on. The first tool call opens the key.
78
+ */
79
+ tools(opts?: WorkspaceToolsOptions): Promise<WorkspaceTool[]>;
80
+ /** Internal: tool-call capture's read-your-writes barrier, once the workspace is open. */
81
+ [CAPTURE_BARRIER](): Promise<void> | undefined;
82
+ }
83
+ export {};