@shardflux/sdk 0.6.2 → 0.8.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/dist/tools.js CHANGED
@@ -1,3 +1,16 @@
1
+ /**
2
+ * Framework-neutral agent tools for a workspace (TEMPLATES.md):
3
+ *
4
+ * await agent.run({ input, tools: workspaceTools(workspace) });
5
+ *
6
+ * Each tool is { name, description, parameters (JSON Schema 2020-12), execute }.
7
+ * `execute` validates its arguments against `parameters` (the same schema the
8
+ * model saw), then calls the cell gateway through the workspace's managed tool
9
+ * token. The customer's model and agent loop stay in the customer's
10
+ * application; toOpenAITools/toAnthropicTools export the definitions in those
11
+ * providers' formats and executeToolCall dispatches a model's tool call.
12
+ */
13
+ import { CAPTURE_BARRIER } from "./cell.js";
1
14
  export class ToolArgumentError extends Error {
2
15
  tool;
3
16
  issues;
@@ -283,11 +296,15 @@ export function workspaceTools(workspace, opts = {}) {
283
296
  const issues = validateArgs(d.parameters, args);
284
297
  if (issues.length > 0)
285
298
  throw new ToolArgumentError(`${prefix}${d.name}`, issues);
286
- return d.run(args, options);
299
+ // Read-your-writes: tool calls captured before this one are in the workspace before it runs (bounded).
300
+ const barrier = workspace[CAPTURE_BARRIER];
301
+ const pending = typeof barrier === 'function' ? barrier.call(workspace) : undefined;
302
+ if (pending)
303
+ await pending;
304
+ return d.run(args, options.signal ? { signal: options.signal } : {});
287
305
  },
288
306
  }));
289
307
  }
290
- /** OpenAI tool definitions: Chat Completions (`{type:'function', function:{...}}`) or Responses API. */
291
308
  export function toOpenAITools(tools, opts = {}) {
292
309
  return opts.api === 'responses'
293
310
  ? tools.map((t) => ({ type: 'function', name: t.name, description: t.description, parameters: t.parameters, strict: false }))
@@ -299,7 +316,10 @@ export function toAnthropicTools(tools) {
299
316
  }
300
317
  /**
301
318
  * Runs a model's tool call: `arguments` may be the JSON string (OpenAI) or an object (Anthropic
302
- * `input`). Throws for unknown tools and invalid arguments (ToolArgumentError).
319
+ * `input`). Throws for unknown tools and invalid arguments (ToolArgumentError). The call's `id` / `call_id` is passed
320
+ * to `execute` as `toolCallId` (0.7.0+), so `executeToolCall(capture.tools(tools), call)` records it. `input` is
321
+ * `unknown` (0.8.0+), as in the Anthropic SDK's `ToolUseBlock`, so a tool_use block is passed as it is; `execute`
322
+ * validates it.
303
323
  */
304
324
  export async function executeToolCall(tools, call, options = {}) {
305
325
  const tool = tools.find((t) => t.name === call.name);
@@ -313,5 +333,6 @@ export async function executeToolCall(tools, call, options = {}) {
313
333
  catch {
314
334
  throw new ToolArgumentError(call.name, ['arguments are not valid JSON']);
315
335
  }
316
- return tool.execute(args, options);
336
+ const toolCallId = call.call_id ?? call.id;
337
+ return tool.execute(args, toolCallId === undefined ? options : { ...options, toolCallId });
317
338
  }
@@ -3,13 +3,15 @@
3
3
  * tool tokens and cell clients (one per agent label / tool set).
4
4
  */
5
5
  import type { ClientContext, DiskLayout, ForkTarget, Operation, WaitOptions, WorkspaceLifetime, WorkspaceOrigin, WorkspacePurpose, WorkspaceView } from './client.js';
6
- import { CellClient } from './cell.js';
6
+ import { CAPTURE_BARRIER, CellClient } from './cell.js';
7
+ import { ToolCallCapture } from './capture.js';
8
+ import type { ToolCallCaptureOptions } from './capture.js';
7
9
  import type { FinishedOperation, LifecycleOptions, WaitedLifecycleOptions } from './lifecycle.js';
8
10
  import { Trace } from './progress.js';
9
11
  import type { LifecycleTiming, ProgressListener } from './progress.js';
10
12
  import { WorkspaceSecrets } from './secrets.js';
11
13
  import type { CellClientOptions, WorkspaceChangesPage, WorkspaceChangesParams } from './cell.js';
12
- import type { SaveAsTemplateParams, SaveAsTemplateResponse } from './templates.js';
14
+ import type { SaveAsTemplateParams, SaveAsTemplateResponse, WorkspaceStartup } from './templates.js';
13
15
  import { ToolTokenManager } from './tokens.js';
14
16
  import type { ToolName, ToolToken } from './tokens.js';
15
17
  export interface WakeOptions {
@@ -59,10 +61,18 @@ export declare class Workspace {
59
61
  get origin(): WorkspaceOrigin | null;
60
62
  /** How a session ended (closed, idle_timeout, draft_discarded); null while live or for a plain delete. */
61
63
  get endedReason(): WorkspaceView['ended_reason'];
64
+ /**
65
+ * Start commands and services of the workspace's template version (0.7.0; contracts §24.4): `state` pending, running,
66
+ * ready or failed (the failed step, its exit code, reason and output tail). Null when the version has neither, or on
67
+ * an older API. A failed startup leaves the workspace running for inspection; the next open runs the failed step again.
68
+ */
69
+ get startup(): WorkspaceStartup | null;
62
70
  /** The raw view (GET /v1/workspaces/{id}). */
63
71
  get data(): WorkspaceView;
64
72
  /** Secret names bound to this workspace (injected into every exec/PTY start): `get()`, `set(names)`. */
65
73
  get secrets(): WorkspaceSecrets;
74
+ /** The workspace's text inputs `{NAME: value}` (0.7.0; secret inputs are bound secrets, never listed here). */
75
+ inputs(): Promise<Record<string, string>>;
66
76
  refresh(): Promise<this>;
67
77
  /** Waits for the active operation (if any) and refreshes. */
68
78
  waitUntilReady(opts?: WaitOptions): Promise<this>;
@@ -123,6 +133,19 @@ export declare class Workspace {
123
133
  */
124
134
  reset(opts: WaitedLifecycleOptions): Promise<FinishedOperation>;
125
135
  reset(opts?: LifecycleOptions): Promise<Operation>;
136
+ /**
137
+ * Captures the tool calls of your agent harness into this workspace (docs/decisions/0006-tool-call-capture.md): each
138
+ * call's input and full output become files under `dir` (default `/home/user/tool-calls/<run>/`), so the agent can
139
+ * work on them with code, and they are in snapshots and forks. Wrapped tools return exactly what they returned before;
140
+ * capture never throws into the harness. Shardflux calls through this client (exec, files, workspaceTools, snapshot,
141
+ * fork, suspend, close, saveAsTemplate) first wait for writes recorded before them; delete and reset drop them.
142
+ *
143
+ * const capture = workspace.captureToolCalls();
144
+ * const out = await capture.run(block, () => myTools[block.name](block.input));
145
+ */
146
+ captureToolCalls(opts?: ToolCallCaptureOptions): ToolCallCapture;
147
+ /** Internal: waits for tool-call capture writes recorded so far (workspaceTools calls it before each tool). */
148
+ [CAPTURE_BARRIER](): Promise<void> | undefined;
126
149
  /** Saves this layered workspace as the next version of an organization template (contracts §19.8). */
127
150
  saveAsTemplate(params: SaveAsTemplateParams): Promise<SaveAsTemplateResponse>;
128
151
  /**
package/dist/workspace.js CHANGED
@@ -1,6 +1,7 @@
1
- import { CellClient, DEFAULT_TRANSITION_TIMEOUT_MS } from "./cell.js";
1
+ import { CAPTURE_BARRIER, CellClient, DEFAULT_TRANSITION_TIMEOUT_MS } from "./cell.js";
2
+ import { ToolCallCapture } from "./capture.js";
2
3
  import { OperationFailedError, ShardfluxApiError } from "./errors.js";
3
- import { randomId } from "./http.js";
4
+ import { defaultSleep, randomId } from "./http.js";
4
5
  import { AFTER_WAIT, TRACE } from "./lifecycle.js";
5
6
  import { Trace, combineListeners, traced } from "./progress.js";
6
7
  import { WorkspaceSecrets } from "./secrets.js";
@@ -107,6 +108,14 @@ export class Workspace {
107
108
  get endedReason() {
108
109
  return this.#view.ended_reason ?? null;
109
110
  }
111
+ /**
112
+ * Start commands and services of the workspace's template version (0.7.0; contracts §24.4): `state` pending, running,
113
+ * ready or failed (the failed step, its exit code, reason and output tail). Null when the version has neither, or on
114
+ * an older API. A failed startup leaves the workspace running for inspection; the next open runs the failed step again.
115
+ */
116
+ get startup() {
117
+ return this.#view.startup ?? null;
118
+ }
110
119
  /** The raw view (GET /v1/workspaces/{id}). */
111
120
  get data() {
112
121
  return this.#view;
@@ -115,6 +124,10 @@ export class Workspace {
115
124
  get secrets() {
116
125
  return new WorkspaceSecrets(this.#ctx, this.id);
117
126
  }
127
+ /** The workspace's text inputs `{NAME: value}` (0.7.0; secret inputs are bound secrets, never listed here). */
128
+ inputs() {
129
+ return this.#ctx.workspaces.inputs(this.id);
130
+ }
118
131
  async refresh() {
119
132
  this.#view = await this.#ctx.http.json('GET', `/v1/workspaces/${encodeURIComponent(this.id)}`, {}, this.#ctx.authorization);
120
133
  return this;
@@ -142,6 +155,8 @@ export class Workspace {
142
155
  return this.#ctx.workspaces.fork(this.id, target, this.#tracked(opts));
143
156
  }
144
157
  async close(opts = {}) {
158
+ // Closing aborts in-flight cell requests: tool-call capture writes recorded before it are flushed first (bounded).
159
+ await this.#ctx.captures.settle(this.id);
145
160
  for (const c of this.#cells.values())
146
161
  c.close();
147
162
  this.#cells.clear();
@@ -159,6 +174,37 @@ export class Workspace {
159
174
  m.invalidate();
160
175
  return op;
161
176
  }
177
+ /**
178
+ * Captures the tool calls of your agent harness into this workspace (docs/decisions/0006-tool-call-capture.md): each
179
+ * call's input and full output become files under `dir` (default `/home/user/tool-calls/<run>/`), so the agent can
180
+ * work on them with code, and they are in snapshots and forks. Wrapped tools return exactly what they returned before;
181
+ * capture never throws into the harness. Shardflux calls through this client (exec, files, workspaceTools, snapshot,
182
+ * fork, suspend, close, saveAsTemplate) first wait for writes recorded before them; delete and reset drop them.
183
+ *
184
+ * const capture = workspace.captureToolCalls();
185
+ * const out = await capture.run(block, () => myTools[block.name](block.input));
186
+ */
187
+ captureToolCalls(opts = {}) {
188
+ const defaults = this.#defaults.tools;
189
+ const tools = defaults && !defaults.includes('files') ? [...defaults, 'files'] : defaults;
190
+ const agentLabel = opts.agentLabel ?? this.#defaults.agentLabel;
191
+ const manager = this.tokens({ ...(agentLabel !== undefined ? { agentLabel } : {}), ...(tools !== undefined ? { tools } : {}) });
192
+ // The capture's own client: not in #cells (close() flushes it before aborting those) and without the barrier.
193
+ const cell = new CellClient(this.id, manager, {
194
+ fetch: this.#ctx.fetch,
195
+ userAgent: this.#ctx.userAgent,
196
+ sleep: this.#ctx.sleep,
197
+ maxRetries: 0,
198
+ ...(opts.transitionTimeoutMs !== undefined ? { transitionTimeoutMs: opts.transitionTimeoutMs } : {}),
199
+ ...(this.#ctx.onProgress ? { onProgress: this.#ctx.onProgress } : {}),
200
+ wake: opts.wake === undefined ? (timeoutMs, signal) => this.wake({ timeoutMs, ...(signal ? { signal } : {}) }) : opts.wake,
201
+ });
202
+ return new ToolCallCapture({ workspaceId: this.id, cell, registry: this.#ctx.captures, ...(this.#ctx.sleep !== defaultSleep ? { sleep: this.#ctx.sleep } : {}) }, opts);
203
+ }
204
+ /** Internal: waits for tool-call capture writes recorded so far (workspaceTools calls it before each tool). */
205
+ [CAPTURE_BARRIER]() {
206
+ return this.#ctx.captures.settle(this.id);
207
+ }
162
208
  /** Saves this layered workspace as the next version of an organization template (contracts §19.8). */
163
209
  saveAsTemplate(params) {
164
210
  return this.#ctx.workspaces.saveAsTemplate(this.id, params);
@@ -205,6 +251,7 @@ export class Workspace {
205
251
  ...cellOpts,
206
252
  ...(listener ? { onProgress: listener } : {}),
207
253
  wake: wake === undefined ? (timeoutMs, signal) => this.wake({ timeoutMs, ...(signal ? { signal } : {}), ...(onProgress ? { onProgress } : {}) }) : wake,
254
+ [CAPTURE_BARRIER]: () => this.#ctx.captures.settle(this.id),
208
255
  });
209
256
  this.#cells.set(k, c);
210
257
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shardflux/sdk",
3
- "version": "0.6.2",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "description": "Shardflux TypeScript SDK: open persistent agent workspaces by key and give your agent workspace tools (exec, files, processes, PTY, git, browser).",
6
6
  "license": "Apache-2.0",
@@ -36,13 +36,36 @@
36
36
  "publishConfig": {
37
37
  "access": "public"
38
38
  },
39
+ "peerDependencies": {
40
+ "yaml": "^2.4.0"
41
+ },
42
+ "peerDependenciesMeta": {
43
+ "yaml": {
44
+ "optional": true
45
+ }
46
+ },
39
47
  "devDependencies": {
48
+ "@ai-sdk/provider": "4.0.18",
49
+ "@anthropic-ai/claude-agent-sdk": "0.3.283",
50
+ "@anthropic-ai/sdk": "0.128.0",
40
51
  "@eslint/js": "10.0.1",
52
+ "@langchain/core": "1.2.13",
53
+ "@langchain/langgraph": "1.4.18",
54
+ "@mastra/core": "1.71.0",
55
+ "@modelcontextprotocol/client": "2.1.0",
56
+ "@modelcontextprotocol/sdk": "1.30.0",
57
+ "@openai/agents-core": "0.18.0",
41
58
  "@types/node": "24.13.6",
59
+ "ai": "7.0.118",
60
+ "ajv": "8.20.0",
42
61
  "eslint": "10.11.0",
62
+ "langchain": "1.5.14",
63
+ "openai": "7.23.0",
43
64
  "openapi-typescript": "7.13.0",
44
65
  "typescript": "5.9.3",
45
- "typescript-eslint": "8.70.1"
66
+ "typescript-eslint": "8.70.1",
67
+ "yaml": "2.9.1",
68
+ "zod": "4.6.5"
46
69
  },
47
70
  "scripts": {
48
71
  "build": "node scripts/build.mjs",