@shardflux/sdk 0.6.1 → 0.7.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 +105 -1
- package/README.md +216 -11
- package/dist/capture-adapters.d.ts +178 -0
- package/dist/capture-adapters.js +432 -0
- package/dist/capture-serialize.d.ts +97 -0
- package/dist/capture-serialize.js +481 -0
- package/dist/capture-text.d.ts +14 -0
- package/dist/capture-text.js +13 -0
- package/dist/capture.d.ts +288 -0
- package/dist/capture.js +1326 -0
- package/dist/cell.d.ts +7 -0
- package/dist/cell.js +13 -0
- package/dist/client.d.ts +24 -2
- package/dist/client.js +24 -5
- package/dist/egress.d.ts +18 -0
- package/dist/errors.d.ts +21 -2
- package/dist/errors.js +19 -2
- package/dist/generated/app-api.d.ts +2900 -193
- package/dist/http.d.ts +1 -1
- package/dist/http.js +1 -1
- package/dist/index.d.ts +10 -3
- package/dist/index.js +4 -1
- package/dist/lifecycle.d.ts +5 -2
- package/dist/lifecycle.js +5 -1
- package/dist/progress.d.ts +17 -3
- package/dist/progress.js +18 -4
- package/dist/tar.d.ts +40 -0
- package/dist/tar.js +150 -0
- package/dist/template-file.d.ts +92 -0
- package/dist/template-file.js +326 -0
- package/dist/templates.d.ts +318 -9
- package/dist/templates.js +432 -13
- package/dist/tools.d.ts +6 -13
- package/dist/tools.js +23 -3
- package/dist/workspace.d.ts +28 -3
- package/dist/workspace.js +52 -3
- package/package.json +24 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,7 +3,111 @@
|
|
|
3
3
|
Every API the README shows is available from the version named here. Below 1.0, a minor release may break
|
|
4
4
|
compatibility; breaking changes are marked **Breaking**.
|
|
5
5
|
|
|
6
|
-
## 0.
|
|
6
|
+
## 0.7.0 (not yet published; npm `latest` is 0.6.2)
|
|
7
|
+
|
|
8
|
+
Needs an API with the template editor (contracts §24); every new field is additive and older fields are unchanged.
|
|
9
|
+
|
|
10
|
+
### Template editor: build a template from template.yaml
|
|
11
|
+
|
|
12
|
+
- `templates.buildFromFile(path, { templateSlug, autoPublish?, description?, displayName?, acknowledgedScanFindings?,
|
|
13
|
+
organizationId?, wait?, onProgress?, root?, parseYaml? })` (Node only): reads template.yaml (or a `.json` file with
|
|
14
|
+
the same document), uploads each local `from` path of `build.files` (folders packed as a reproducible tar, files as
|
|
15
|
+
they are; each distinct path once; bytes the organization already has are not sent), creates the recipe v2 build and
|
|
16
|
+
with `wait` follows it. Returns `{ build, recipe, uploads }`. Progress events: `pack`, `upload`, `build`.
|
|
17
|
+
`templates.buildFromRecipe(doc, { baseDir, ... })` does the same for a document in memory.
|
|
18
|
+
- YAML is read with the optional peer dependency `yaml` (`npm install yaml`) or `parseYaml`; JSON needs nothing. The
|
|
19
|
+
SDK keeps no runtime dependencies, and the Node-only code is loaded with dynamic imports, so browser bundles are
|
|
20
|
+
unaffected.
|
|
21
|
+
- The folder tar is byte-identical to the Python SDK's (`shardflux` 0.3.0) for the same folder: members sorted by UTF-8
|
|
22
|
+
path, mtime 0, uid/gid 0 without names, permission bits kept, symlinks as symlinks, pax records for long or
|
|
23
|
+
non-ASCII names. Absolute or escaping symlinks, devices, FIFOs and sockets are refused before any request, as the
|
|
24
|
+
build host would.
|
|
25
|
+
- `TemplateFileError` (a file that cannot be read, parsed or packed; nothing was sent) and `TemplateUploadError` (the
|
|
26
|
+
storage refused a PUT: `status`, `code` such as `BadDigest`; never the presigned URL). Exported: `packDirectory`,
|
|
27
|
+
`readTemplateFile`, `parseTemplateText`, the tar writer (`tarHeader`, `tarPadding`, `tarEnd`).
|
|
28
|
+
|
|
29
|
+
### Template editor: the API surface (contracts §24.6)
|
|
30
|
+
|
|
31
|
+
- `templates.uploads.request({ sha256, size, kind })`, `templates.uploads.put(bytes | Blob | stream, { kind, sha256?,
|
|
32
|
+
size? })` (PUT with exactly the presigned headers, skipped when the organization has the bytes, confirmed after) and
|
|
33
|
+
`templates.uploads.putPath(path)` (Node).
|
|
34
|
+
- `templates.builds.create()` takes recipe v2 (`TemplateRecipeV2`), `description` and `acknowledgedScanFindings`.
|
|
35
|
+
`TemplateBuild.denied_hosts`. **Breaking (types only):** `TemplateBuild.recipe` is `{dockerfile}` or the stored
|
|
36
|
+
recipe v2 (`TemplateBuildRecipeV2`); narrow with `'dockerfile' in build.recipe` before reading `dockerfile`.
|
|
37
|
+
`waitForBuild()` takes `onChange`.
|
|
38
|
+
- `templates.versions.recipe(slug, version)`: the recipe and settings a version was built from, ready to build again.
|
|
39
|
+
- `templates.versionTestInstances.create(slug, version, { inputs?, key?, caps?, ... })`: a session workspace on a
|
|
40
|
+
registered version, published or not.
|
|
41
|
+
- `templates.languages(base)`: the languages and versions a base offers `build.languages` (`included` when the base
|
|
42
|
+
already has one).
|
|
43
|
+
- `templates.packages.search(ecosystem, query, { base?, limit? })` and `templates.packages.get(ecosystem, name)`.
|
|
44
|
+
- `workspaces.open({ inputs })`, `workspace.inputs()` / `workspaces.inputs(id)`, `workspace.startup` (start commands
|
|
45
|
+
and services: pending, running, ready or failed with the step, exit code and output tail).
|
|
46
|
+
- Drafts: `create({ displayName, inputs })`, `openTestInstance({ inputs })`, `publish({ settings })`;
|
|
47
|
+
`saveAsTemplate({ settings })`.
|
|
48
|
+
- Views: `TemplateVersion.settings`, `TemplateSummary.category` / `TemplateDetail.category`,
|
|
49
|
+
`WorkspaceEgressPolicy.template_egress` and `effective_policy`. Types: `TemplateSettings`, `TemplateSettingsInput`,
|
|
50
|
+
`TemplateInput`, `TemplateStartCommand`, `TemplateService`, `TemplateEgressDefault`, `TemplateUpload*`,
|
|
51
|
+
`TemplateVersionRecipe`, `TemplateLanguages`, `TemplatePackage*`, `WorkspaceStartup`, `WorkspaceInputs`.
|
|
52
|
+
- Error reasons: `invalid_recipe`, `base_not_layered`, `language_unavailable`, `language_conflict`, `upload_required`,
|
|
53
|
+
`upload_missing`, `upload_digest_mismatch`, `upload_too_large`, `invalid_settings`, `services_unsupported`,
|
|
54
|
+
`input_required`, `input_unknown`, `input_invalid`, `egress_widening`, `package_index_unavailable` and the operation
|
|
55
|
+
error `startup_failed` (`KnownErrorReason`).
|
|
56
|
+
|
|
57
|
+
### Tool-call capture
|
|
58
|
+
|
|
59
|
+
`workspace.captureToolCalls(options)` saves the tool calls of your own agent harness (web search, SQL, HTTP APIs, MCP
|
|
60
|
+
servers) into the workspace: each call's input and full output become files under `/home/user/tool-calls/<run>/`
|
|
61
|
+
with a JSON-lines index, so the agent can compute on them with code and snapshots and forks keep them. The format is
|
|
62
|
+
docs/decisions/0006-tool-call-capture.md (shared with the Python SDK 0.3.0).
|
|
63
|
+
|
|
64
|
+
- Invisible: a wrapped tool returns the same value, the same promise object and the same thrown error; a synchronous
|
|
65
|
+
tool stays synchronous; capture never throws into the harness (failures go to `onError` and `capture.stats`).
|
|
66
|
+
Serialization runs in `setImmediate`, writes in the background (4 in parallel, index lines batched every 50 ms).
|
|
67
|
+
- Explicit capture: `capture.record(call)`, `capture.run(toolCall, fn)` (Anthropic `tool_use`, Responses
|
|
68
|
+
`function_call`, Chat `tool_calls`, `{ name, input }`), `capture.wrap(name, fn)` (async generators pass through and
|
|
69
|
+
are stored as `.jsonl`), `capture.tools(toolsObjOrArray)`, and `captureTool(name, fn)` with `capture.activate(fn)`
|
|
70
|
+
for tools defined at import time.
|
|
71
|
+
- Adapters (structural types, no dependency): `capture.aiSdk.tools()` / `.callbacks()` (Vercel AI SDK 7, streaming
|
|
72
|
+
tools included), `capture.mastra.hooks()` / `.tools()`, `capture.anthropic.tools()` (tool runner),
|
|
73
|
+
`capture.openaiAgents.tools()` / `.execute()` / `.attach(runner | agent)`, `capture.claude.hooks()` (Claude Agent
|
|
74
|
+
SDK, with a PreToolUse barrier for `mcp__shardflux__` tools), `capture.langchain.handler()`, `capture.mcp.instrument()`.
|
|
75
|
+
- Selection: explicit capture always records; hook-level adapters apply `include` / `exclude`. Call ids are
|
|
76
|
+
deduplicated (last 10 000), so a wrapper and a hook can be combined.
|
|
77
|
+
- Output encoding: JSON, text, HTML, bytes by magic number (PNG, JPEG, GIF, WebP, PDF, ZIP, gzip, Parquet), MCP results
|
|
78
|
+
and content blocks as a directory of parts; limits `maxOutputBytes` (32 MiB, text cut as `.part`),
|
|
79
|
+
`maxInlineInputBytes` (64 KiB), `maxPendingBytes` (128 MiB of files, index lines and inline inputs, then
|
|
80
|
+
`dropped: "queue_full"` with a small line), `maxPendingCalls` (10 000; past it a call is not recorded and `onError`
|
|
81
|
+
says so); `transform` for redaction (fails closed); streams are never read; out-of-range timestamps are recorded as
|
|
82
|
+
`started_at: null`. A wrapped async tool's promise is marked handled (no `unhandledRejection` if nobody awaits it).
|
|
83
|
+
- Read-your-writes: calls through the same client (exec and files through `workspace.cell()`, `workspaceTools`,
|
|
84
|
+
snapshot, fork, suspend, saveAsTemplate, close; also `cloud.workspaces.*(id)`) wait for capture writes recorded
|
|
85
|
+
before them, bounded by `settleTimeoutMs` (30 s). Lifecycle timing shows the wait as a new `capture_flush` phase.
|
|
86
|
+
`delete` and `reset` drop pending writes. Writes wake a suspended workspace (`wake: null` opts out).
|
|
87
|
+
- `capture.flush()` / `capture.close()` never reject (`{ complete, written, failed, dropped }`); `capture.promptHint()`
|
|
88
|
+
returns a paragraph for your system prompt.
|
|
89
|
+
- `executeToolCall()` passes the call's `id` / `call_id` to `execute` as `toolCallId`, and `WorkspaceTool.execute`'s
|
|
90
|
+
options accept it.
|
|
91
|
+
- **Breaking (types only):** `LifecyclePhase` gains `capture_flush`; an exhaustive `switch` over phases without a
|
|
92
|
+
`default` case needs the new member.
|
|
93
|
+
|
|
94
|
+
## 0.6.2 (2026-09-28)
|
|
95
|
+
|
|
96
|
+
### Starts that wait for capacity end
|
|
97
|
+
|
|
98
|
+
The API no longer lets a start (open, resume, restore, fork) wait in `capacity_pending` forever. One that no host
|
|
99
|
+
could admit 15 minutes after it was created fails with `capacity_unavailable` and `retryable: true`: nothing was
|
|
100
|
+
started, the concurrency slot is released, and a suspended workspace stays suspended with its state. Before, a VM could
|
|
101
|
+
boot (and bill) long after every wait had given up.
|
|
102
|
+
|
|
103
|
+
- `OperationFailedError.retryable`: the operation error's `retryable` flag (`false` when absent). `true` for
|
|
104
|
+
`capacity_unavailable` (retry later), `false` for a definitive failure. The SDK does not retry it itself.
|
|
105
|
+
- `OperationTimeoutError.deadlineAt`: when a wait gives up while the start is still `capacity_pending`, the time it
|
|
106
|
+
stops waiting for a host (`error.details.deadline_at`); the message says so. Null in other states.
|
|
107
|
+
- `onProgress`: a `capacity_pending` `phase` event carries `deadlineAt` when the API reports it.
|
|
108
|
+
- Docs: waits no longer say a pending start continues indefinitely.
|
|
109
|
+
|
|
110
|
+
## 0.6.1 (2026-09-28)
|
|
7
111
|
|
|
8
112
|
- `formatTiming()` joined two phases that followed each other with `∥` (ran together) when their 0.1 ms times summed
|
|
9
113
|
with a floating-point error (1000.2 + 300.1 > 1300.3); it now prints `→`.
|
package/README.md
CHANGED
|
@@ -4,16 +4,18 @@ TypeScript SDK for [Shardflux](https://shardflux.dev): cloud computers for AI ag
|
|
|
4
4
|
|
|
5
5
|
Open a persistent workspace by key, run commands and move files in it, suspend it when idle, resume
|
|
6
6
|
it later with its disk and memory intact, and fork it. Hand your agent framework-neutral workspace
|
|
7
|
-
tools (exec, files, processes, PTY, git, browser) that plug into any model provider
|
|
7
|
+
tools (exec, files, processes, PTY, git, browser) that plug into any model provider, and save your harness's own
|
|
8
|
+
tool calls into the workspace ([tool-call capture](#tool-call-capture-070)).
|
|
8
9
|
|
|
9
10
|
> **Early access.** Shardflux is in early access. The API is versioned (`/v1`), but this SDK is
|
|
10
11
|
> below 1.0: a minor release may contain breaking changes (see [Compatibility](#compatibility)).
|
|
11
12
|
|
|
12
|
-
> **Versions.** This README describes 0.
|
|
13
|
-
> [CHANGELOG.md](./CHANGELOG.md) lists what each version added. Check yours with
|
|
13
|
+
> **Versions.** This README describes 0.7.0. Anything marked **(0.7.0+)** is not in 0.6.x and **(0.6.0+)** not in
|
|
14
|
+
> 0.5.0; [CHANGELOG.md](./CHANGELOG.md) lists what each version added. Check yours with
|
|
14
15
|
> `npm ls @shardflux/sdk` or the exported `SDK_VERSION`.
|
|
15
16
|
|
|
16
|
-
- ESM only, no runtime dependencies, Node.js 24 or later.
|
|
17
|
+
- ESM only, no runtime dependencies, Node.js 24 or later. Reading a YAML template file uses the optional peer
|
|
18
|
+
dependency `yaml` (`npm install yaml`); JSON template files need nothing.
|
|
17
19
|
- Typed from the published OpenAPI documents.
|
|
18
20
|
- Retries, idempotency keys, operation polling and tool-token refresh are handled for you.
|
|
19
21
|
|
|
@@ -127,6 +129,23 @@ means depends on `wait`:
|
|
|
127
129
|
wait again with `cloud.workspaces.waitForOperation(err.operationId)`. Without `wait`, the returned operation is the
|
|
128
130
|
handle for the work in progress: pass its `id` to `waitForOperation()` when you need it finished.
|
|
129
131
|
|
|
132
|
+
**A start waits for capacity for at most 15 minutes.** An open, resume, restore or fork that no host can admit yet
|
|
133
|
+
waits in `capacity_pending`. Its `error.details.deadline_at` says when it gives up; the `phase` progress event carries
|
|
134
|
+
it as `deadlineAt` **(0.6.2+)**, and so does `OperationTimeoutError` when your wait ends first. A start still pending
|
|
135
|
+
at the deadline fails with `capacity_unavailable`: nothing was started, the concurrency slot is released, and a
|
|
136
|
+
suspended workspace stays suspended with its state. `OperationFailedError.retryable` **(0.6.2+)** is `true` for it,
|
|
137
|
+
so you can tell "retry later" from a definitive failure. The SDK does not retry it for you.
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
try {
|
|
141
|
+
await workspace.resume({ wait: true });
|
|
142
|
+
} catch (err) {
|
|
143
|
+
if (err instanceof OperationFailedError && err.retryable) {
|
|
144
|
+
// err.errorCode === 'capacity_unavailable': no host had room; nothing changed. Try again later.
|
|
145
|
+
} else throw err;
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
130
149
|
**Suspended workspaces wake on use.** A tool call on a suspended workspace resumes it (or joins the resume or open
|
|
131
150
|
already running), then runs. A call made during a suspend or resume waits for the transition to finish. The call
|
|
132
151
|
never runs twice: the cell executes nothing it refused.
|
|
@@ -148,9 +167,9 @@ await cell.exec.run(['make', 'test']); // resumes the wo
|
|
|
148
167
|
the response until the operation changes (`Prefer: wait`, at most 20 s per request), so completion arrives within
|
|
149
168
|
one round trip of the commit. Against an API without bounded waits (or with `serverWait: false`) it polls with
|
|
150
169
|
backoff (250 ms doubling to 5 s, ±20 % jitter). If the timeout passes, it throws `OperationTimeoutError` and the
|
|
151
|
-
operation keeps running server side; wait for it again with the
|
|
152
|
-
the same way. A waited `open()` issues the first tool token
|
|
153
|
-
tool call starts at once.
|
|
170
|
+
operation keeps running server side (a start waiting for capacity until its `deadlineAt`); wait for it again with the
|
|
171
|
+
same call. `templates.builds.waitForBuild()` waits the same way. A waited `open()` issues the first tool token
|
|
172
|
+
together with the final workspace read, so the first tool call starts at once.
|
|
154
173
|
|
|
155
174
|
On Node 26 the default fetch sends `Connection: close`: its bundled undici 8 can stall a request on a reused
|
|
156
175
|
keep-alive connection for tens of seconds. Pass your own `fetch`, or set `SHARDFLUX_HTTP_KEEPALIVE=1`, to change that.
|
|
@@ -283,6 +302,80 @@ await draft.discard(); // deletes the draft and en
|
|
|
283
302
|
`states()`, `statesAll()` and `testInstances({ includeEnded })` list the draft's states and test instances. Only
|
|
284
303
|
owners, admins and API keys with a tool permission may change drafts; others get 403 `template_dev_mode_role`.
|
|
285
304
|
|
|
305
|
+
## Build a template from template.yaml (0.7.0+)
|
|
306
|
+
|
|
307
|
+
A template is a recipe v2: a base, what the build adds (languages, apt/pip/npm packages, files, named build steps) and
|
|
308
|
+
the settings a workspace gets when it opens (environment, open-time inputs, start commands, services, defaults).
|
|
309
|
+
`template.yaml` is that document in YAML. A file entry may name a local `from` path (relative to the file): a folder is
|
|
310
|
+
copied as a tar, a file as it is.
|
|
311
|
+
|
|
312
|
+
```yaml
|
|
313
|
+
# acme/template.yaml
|
|
314
|
+
base: ubuntu-24.04@1
|
|
315
|
+
build:
|
|
316
|
+
languages: [{ id: python }, { id: node, version: "22" }]
|
|
317
|
+
packages:
|
|
318
|
+
apt: [jq]
|
|
319
|
+
pip: { packages: [pandas==2.3.2], requirements: [/home/user/app/requirements.txt] }
|
|
320
|
+
files:
|
|
321
|
+
- { from: ./app, to: /home/user/app, owner: user } # a folder: uploaded as a tar
|
|
322
|
+
- { from: ./config/settings.toml, to: /home/user/.config/acme/settings.toml, owner: user, mode: "0600" }
|
|
323
|
+
steps:
|
|
324
|
+
- { name: install, run: npm ci, user: user, cwd: /home/user/app }
|
|
325
|
+
settings:
|
|
326
|
+
env: { APP_ENV: development }
|
|
327
|
+
inputs:
|
|
328
|
+
PROJECT_NAME: { kind: text, required: true }
|
|
329
|
+
OPENAI_API_KEY: { kind: secret } # a stored secret of that name, bound at open
|
|
330
|
+
start: [{ name: seed, when: create, run: python seed.py, user: user, cwd: /home/user/app }]
|
|
331
|
+
services:
|
|
332
|
+
web: { run: npm start, user: user, cwd: /home/user/app, ready: { port: 3000 } }
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
```ts
|
|
336
|
+
const { build, uploads } = await cloud.templates.buildFromFile('acme/template.yaml', {
|
|
337
|
+
templateSlug: 'acme-dev',
|
|
338
|
+
autoPublish: false, // register it unpublished, test it, publish it later
|
|
339
|
+
wait: true, // until registered (or failed); default: return the queued build
|
|
340
|
+
onProgress: (e) => console.log(e.type, e.type === 'build' ? e.build.state : e.from),
|
|
341
|
+
});
|
|
342
|
+
console.log(build.state, build.template_version, build.provenance.recipe_sha256, uploads);
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
`buildFromFile` is Node only (it reads the disk; browsers never load that code). Folders are packed as a
|
|
346
|
+
reproducible tar (sorted, mtime 0, no owner names; symlinks must stay inside the folder), the same bytes as the Python
|
|
347
|
+
SDK packs, so the same inputs give the same `recipe_sha256`. Uploads the organization already has are not sent
|
|
348
|
+
again. It throws `TemplateFileError` before any request for a file it cannot read or pack, and `TemplateUploadError`
|
|
349
|
+
when the storage refuses the bytes. A recipe already in memory: `templates.buildFromRecipe(doc, { templateSlug,
|
|
350
|
+
baseDir })`. Pass `root` to refuse local paths outside a directory, and `parseYaml` to use another YAML parser.
|
|
351
|
+
|
|
352
|
+
The pieces on their own:
|
|
353
|
+
|
|
354
|
+
```ts
|
|
355
|
+
const up = await cloud.templates.uploads.put(bytes, { kind: 'file' }); // Uint8Array, Blob or a stream
|
|
356
|
+
const dir = await cloud.templates.uploads.putPath('./app'); // Node: a folder as the tar
|
|
357
|
+
await cloud.templates.builds.create(orgId, { templateSlug: 'acme-dev', recipe: { schema: 'shardflux.template-recipe.v2',
|
|
358
|
+
base: 'ubuntu-24.04@1', build: { files: [{ upload: up.ref, kind: 'file', to: '/etc/acme.conf' }] }, settings: {} } });
|
|
359
|
+
const exported = await cloud.templates.versions.recipe('acme-dev', 3); // the recipe to build again, and settings
|
|
360
|
+
const langs = await cloud.templates.languages('ubuntu-24.04@1'); // what build.languages offers on a base
|
|
361
|
+
const pkgs = await cloud.templates.packages.search('apt', 'ffmpeg', { base: 'ubuntu-24.04@1' });
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Test a version before publishing it, with its inputs, then open workspaces with inputs:
|
|
365
|
+
|
|
366
|
+
```ts
|
|
367
|
+
const test = await cloud.templates.versionTestInstances.create('acme-dev', 4, { inputs: { PROJECT_NAME: 'demo' } });
|
|
368
|
+
console.log(test.startup); // start commands and services: pending | running | ready | failed
|
|
369
|
+
await test.close();
|
|
370
|
+
const ws = await cloud.workspaces.open({ key: 'customer-42/main', template: 'acme-dev', inputs: { PROJECT_NAME: 'acme' } });
|
|
371
|
+
console.log(await ws.inputs(), ws.startup);
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
A failed start command or service fails the open (`OperationFailedError`, code `startup_failed`, retryable); the
|
|
375
|
+
workspace keeps running so you can inspect it, and `workspace.startup` names the step, its exit code and output tail.
|
|
376
|
+
The next open runs the failed step again. Versions report their `settings`, platform templates their `category`
|
|
377
|
+
(`os` or `stack`), builds the `denied_hosts` their build network refused (add them to `build.network.extra_hosts`).
|
|
378
|
+
|
|
286
379
|
## Agent tools
|
|
287
380
|
|
|
288
381
|
`workspaceTools(workspace)` returns tools with a name, a description, a JSON Schema for the
|
|
@@ -302,6 +395,115 @@ const output = await executeToolCall(tools, { name: call.name, input: call.input
|
|
|
302
395
|
Your agent loop and model calls stay in your application; the workspace is the computer the tools
|
|
303
396
|
act on.
|
|
304
397
|
|
|
398
|
+
## Tool-call capture (0.7.0+)
|
|
399
|
+
|
|
400
|
+
Your harness's tools (web search, SQL, HTTP APIs, MCP servers) run in your application, so their results reach the
|
|
401
|
+
model but not the workspace. `workspace.captureToolCalls()` saves every call's input and full output as files in the
|
|
402
|
+
workspace, where the agent can process them with `jq` or Python, and where snapshots and forks keep them.
|
|
403
|
+
|
|
404
|
+
```ts
|
|
405
|
+
const capture = workspace.captureToolCalls();
|
|
406
|
+
|
|
407
|
+
// A hand-rolled loop (Anthropic tool_use, Responses function_call, Chat tool_calls item, or { name, input }):
|
|
408
|
+
for (const block of message.content) {
|
|
409
|
+
if (block.type !== 'tool_use') continue;
|
|
410
|
+
const output = await capture.run(block, () => myTools[block.name](block.input));
|
|
411
|
+
results.push({ type: 'tool_result', tool_use_id: block.id, content: JSON.stringify(output) });
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
Capture is invisible to the harness. A wrapped tool returns the same value, the same promise object and the same
|
|
416
|
+
thrown error; a synchronous tool stays synchronous. Nothing capture does throws into your code: write failures,
|
|
417
|
+
drops and a throwing `transform` go to `onError` and `capture.stats`. Serialization runs after the call returns and
|
|
418
|
+
the writes run in the background.
|
|
419
|
+
|
|
420
|
+
One line per framework:
|
|
421
|
+
|
|
422
|
+
| Harness | Integration |
|
|
423
|
+
|---|---|
|
|
424
|
+
| Hand-rolled loop | `capture.run(call, () => ...)`, `capture.wrap('name', fn)`, or `capture.record({ tool, input, output, callId })` |
|
|
425
|
+
| Shardflux tools | `executeToolCall(capture.tools(workspaceTools(workspace)), call)` (the call's `id` is recorded) |
|
|
426
|
+
| Vercel AI SDK 7 | `generateText({ tools: capture.aiSdk.tools(tools) })`, or `...capture.aiSdk.callbacks()` (`onToolExecutionEnd`; `{ legacy: true }` for `experimental_onToolCallFinish`) |
|
|
427
|
+
| Mastra | `new Agent({ tools: capture.mastra.tools({ weather }), hooks: capture.mastra.hooks() })` |
|
|
428
|
+
| Anthropic tool runner | `client.beta.messages.toolRunner({ tools: capture.anthropic.tools([weatherTool]) })` |
|
|
429
|
+
| OpenAI Agents JS | `const detach = capture.openaiAgents.attach(runner)`, or `tools: capture.openaiAgents.tools([...])` |
|
|
430
|
+
| Claude Agent SDK | `query({ prompt, options: { hooks: capture.claude.hooks(myHooks) } })` |
|
|
431
|
+
| LangChain.js / LangGraph.js | `agent.invoke(input, { callbacks: [capture.langchain.handler()] })` |
|
|
432
|
+
| MCP client | `const release = capture.mcp.instrument(client, { server: 'github' })` |
|
|
433
|
+
|
|
434
|
+
```ts
|
|
435
|
+
// Vercel AI SDK: the wrapper keeps toolCallId, abortSignal and every other tool property. A streaming tool
|
|
436
|
+
// (async generator execute) passes through unchanged; its final value is recorded.
|
|
437
|
+
const result = await generateText({ model, tools: capture.aiSdk.tools({ weather, search }), prompt });
|
|
438
|
+
|
|
439
|
+
// Claude Agent SDK: your own hooks are kept. PostToolUse / PostToolUseFailure record every tool, and a
|
|
440
|
+
// PreToolUse hook matching ^mcp__shardflux__ waits for pending writes, so the Shardflux MCP server sees them.
|
|
441
|
+
for await (const m of query({ prompt, options: { hooks: capture.claude.hooks(), mcpServers } })) handle(m);
|
|
442
|
+
|
|
443
|
+
// OpenAI Agents JS: agent_tool_end reports a tool's error as the string the model got (status ok). For the exact
|
|
444
|
+
// error, wrap execute: tool({ name: 'get_weather', parameters, execute: capture.openaiAgents.execute('get_weather', fn) }).
|
|
445
|
+
const detach = capture.openaiAgents.attach(runner);
|
|
446
|
+
|
|
447
|
+
// Tools defined at import time in a multi-tenant server: late-bound to the capture active for the request.
|
|
448
|
+
export const lookup = captureTool('lookup', async (id: string) => db.find(id));
|
|
449
|
+
await capture.activate(() => handleRequest(req)); // lookup() records into this capture; elsewhere it passes through
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
**Selection.** Explicit capture (`record`, `run`, `wrap`, `tools`, `captureTool`) always records. Hook-level
|
|
453
|
+
adapters (`callbacks()`, `hooks()`, `attach()`, `handler()`, `instrument()`) see every tool, Shardflux's own
|
|
454
|
+
included, filtered by `include` / `exclude` (names, a RegExp, or `(tool, source) => boolean`). A capture remembers the
|
|
455
|
+
last 10 000 call ids and records each once, so a wrapper and a hook can be combined.
|
|
456
|
+
|
|
457
|
+
**In the workspace** (`dir` defaults to `/home/user/tool-calls`; point it at a subdirectory, e.g.
|
|
458
|
+
`/home/user/tool-calls/conv-123`, to group runs):
|
|
459
|
+
|
|
460
|
+
```
|
|
461
|
+
<dir>/README.md layout and jq recipes, for the agent
|
|
462
|
+
<dir>/<run>/index.jsonl one JSON line per call: seq, call_id, tool, status, input, output_path, ...
|
|
463
|
+
<dir>/<run>/000007-web_search.json the output (.json, .txt, .html, .png, .pdf, ... from its content)
|
|
464
|
+
<dir>/<run>/000010-github.search/ an MCP result or content blocks: part-1.txt, part-2.png, result.json
|
|
465
|
+
<dir>/<run>/000011-sql.input.json an input over 64 KiB
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
`<run>` is `capture.runId` (start time plus a random suffix; `capture.runDir` is the full path). Read the index with
|
|
469
|
+
`jq -cR 'fromjson? // empty' <dir>/*/index.jsonl`, which skips a line torn by a failed append. `capture.promptHint()`
|
|
470
|
+
returns a paragraph that tells the agent where its tool calls are; add it to your system prompt if you want (it is
|
|
471
|
+
never injected).
|
|
472
|
+
|
|
473
|
+
**Read-your-writes.** Calls through the same client first wait for capture writes recorded before them (bounded by
|
|
474
|
+
`settleTimeoutMs`, 30 s; they never fail because of capture): `exec` and files calls through `workspace.cell()`,
|
|
475
|
+
`workspaceTools`, `snapshot`, `fork`, `suspend`, `saveAsTemplate` and `close` (on the workspace handle and on
|
|
476
|
+
`cloud.workspaces.*(id)`). A lifecycle call's timing shows the wait as a `capture_flush` phase. `delete` and `reset`
|
|
477
|
+
drop pending writes. A write to a suspended workspace wakes it (`wake: null` opts out).
|
|
478
|
+
|
|
479
|
+
**Serverless.** Writes finish in the background, so let them finish before the function is frozen:
|
|
480
|
+
`waitUntil(capture.flush())` on Vercel (or Next.js `after(() => capture.flush())`), `await capture.flush()` before
|
|
481
|
+
returning on AWS Lambda. `flush()` and `close()` never reject; they return `{ complete, written, failed, dropped }`.
|
|
482
|
+
|
|
483
|
+
**Redaction.** Nothing is redacted by default (the input came from the model). `transform(event)` gets a copy of each
|
|
484
|
+
call and returns it (changed or not) or `null` to drop it; if it throws, the call is dropped, never written unredacted.
|
|
485
|
+
|
|
486
|
+
```ts
|
|
487
|
+
const capture = workspace.captureToolCalls({
|
|
488
|
+
exclude: ['exec'],
|
|
489
|
+
transform: (e) => (e.tool === 'crm_lookup' ? { ...e, output: redact(e.output) } : e),
|
|
490
|
+
onError: (err) => log.warn(err.kind, err.message),
|
|
491
|
+
});
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
**Limits.** An output over `maxOutputBytes` (32 MiB) is cut when it is text or JSON (`.part`, `truncated: true`) and
|
|
495
|
+
not stored when it is binary (`dropped: "too_large"`). What pending calls hold in memory (files, index lines, inline
|
|
496
|
+
inputs) is bounded by `maxPendingBytes` (128 MiB): over it the output and a large input are dropped and the call keeps
|
|
497
|
+
a small index line with `dropped: "queue_full"`; the call never waits. At most `maxPendingCalls` (10 000) calls are
|
|
498
|
+
pending: past that, while the workspace takes no writes, a call is not recorded at all (`record()` returns null,
|
|
499
|
+
`onError` gets `queue_full`). A write is retried for `retryWindowMs` (120 s) and then dropped (`write_failed`).
|
|
500
|
+
Observing an async tool marks its promise handled: if your code never awaits a wrapped tool's promise and it rejects,
|
|
501
|
+
Node reports no `unhandledRejection` for it (the error is still in the index; Node has no way to observe a rejection
|
|
502
|
+
without handling it). About one write per call: more than roughly 100 calls per second per
|
|
503
|
+
workspace reaches the pending limit. `Response`, `ReadableStream`, Node streams and `Blob` results are never read
|
|
504
|
+
(`meta.note: "stream_not_captured"`). On template v1 the files API writes as root, so the agent can read the captured
|
|
505
|
+
files but not change them. The format is specified in `docs/decisions/0006-tool-call-capture.md`.
|
|
506
|
+
|
|
305
507
|
## Secrets
|
|
306
508
|
|
|
307
509
|
Store credentials once and give them to a workspace's processes as environment variables. Values
|
|
@@ -338,15 +540,18 @@ await workspace.cell().exec.run(['python3', 'agent.py']); // sees $OPEN
|
|
|
338
540
|
`message`, `requestId`, `retryable`, `details`, `operationId`, `retryAfterSeconds`, and `reason`
|
|
339
541
|
(`details.reason`, e.g. `not_session`, `draft_not_found`, `legacy_disk_layout`; see `KnownErrorReason`).
|
|
340
542
|
- `OperationFailedError`: an awaited operation ended `failed` or `canceled` (`errorCode`,
|
|
341
|
-
`operation`, `timing`).
|
|
342
|
-
|
|
543
|
+
`retryable` **(0.6.2+)**, `operation`, `timing`). `retryable` is the operation error's own flag: `true` for
|
|
544
|
+
`capacity_unavailable` (no host could admit the start before its deadline; retry later), `false` for a definitive
|
|
545
|
+
failure.
|
|
546
|
+
- `OperationTimeoutError`: waiting gave up; the operation continues (`operationId`, `lastState`, `lastReason`,
|
|
547
|
+
`deadlineAt` **(0.6.2+)** while it waits for capacity, `timing`).
|
|
343
548
|
- `ShardfluxProtocolError`: a response was not the documented shape.
|
|
344
549
|
|
|
345
550
|
Treat unknown error codes and reasons as generic errors: show `message`, and use `retryable`.
|
|
346
551
|
|
|
347
552
|
## More of the API
|
|
348
553
|
|
|
349
|
-
The `Shardflux` object also has `templates` (including custom template builds), `volumes`
|
|
554
|
+
The `Shardflux` object also has `templates` (including custom template builds and the template editor), `volumes`
|
|
350
555
|
(shared persistent storage attached to workspaces), `secrets` (see above), `egress` (outbound allowlists),
|
|
351
556
|
`usage`, `billing`, `me()`, `entitlements(orgId)` and `request(method, path)` for any `/v1`
|
|
352
557
|
route. The package exports the OpenAPI-generated types as well (`paths`, `components`,
|
|
@@ -356,7 +561,7 @@ route. The package exports the OpenAPI-generated types as well (`paths`, `compon
|
|
|
356
561
|
|
|
357
562
|
- The SDK follows the API's `/v1` contract. New fields, enum values and error codes can appear in
|
|
358
563
|
any release; ignore unknown fields.
|
|
359
|
-
- While below 1.0, a breaking change bumps the minor version (0.
|
|
564
|
+
- While below 1.0, a breaking change bumps the minor version (0.6 to 0.7).
|
|
360
565
|
- `SDK_VERSION` is exported; requests send `User-Agent: shardflux-sdk-ts/<version>`.
|
|
361
566
|
- Examples in this README, in `examples/` and on shardflux.dev name the version they need. The examples on the
|
|
362
567
|
website and in the console are checked against the version published on npm before they ship.
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework adapters for tool-call capture. Structural types only: no framework is a dependency of the SDK, and each
|
|
3
|
+
* adapter's output is checked against the real framework types in test/capture-types.ts.
|
|
4
|
+
*
|
|
5
|
+
* Explicit wrappers (`tools()` of each framework, `capture.tools()`) always record. Hook-level adapters (AI SDK
|
|
6
|
+
* callbacks, Mastra hooks, OpenAI Agents `attach`, Claude Agent SDK hooks, LangChain callbacks, MCP instrumentation)
|
|
7
|
+
* see every tool and apply `include` / `exclude`. A capture remembers the last 10 000 call ids, so a wrapper and a hook
|
|
8
|
+
* on the same call record it once.
|
|
9
|
+
*/
|
|
10
|
+
import type { CallRef, CaptureCall, CaptureFlushResult, CaptureSource, CaptureStatus } from './capture.js';
|
|
11
|
+
/** How the capture observes one call (internal). */
|
|
12
|
+
export interface ObserveSpec {
|
|
13
|
+
tool: string;
|
|
14
|
+
source: CaptureSource;
|
|
15
|
+
input: unknown;
|
|
16
|
+
callId: string | null;
|
|
17
|
+
meta?: Record<string, unknown>;
|
|
18
|
+
/** An async iterable result: record its last item (AI SDK streaming tools) instead of every item. */
|
|
19
|
+
final?: boolean;
|
|
20
|
+
/** Filtered by include/exclude (hook-level adapters). */
|
|
21
|
+
hookLevel?: boolean;
|
|
22
|
+
/** Maps a successful result to what is recorded (LangChain ToolMessage, MCP isError, Mastra ValidationError). */
|
|
23
|
+
post?: (output: unknown) => {
|
|
24
|
+
output: unknown;
|
|
25
|
+
error?: unknown;
|
|
26
|
+
status?: CaptureStatus;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/** What the adapters need from a capture (internal). */
|
|
30
|
+
export interface CaptureCore {
|
|
31
|
+
observe<T>(spec: ObserveSpec, fn: (...args: never[]) => T, thisArg: unknown, args: unknown[]): T;
|
|
32
|
+
hook(call: CaptureCall): CallRef | null;
|
|
33
|
+
settle(): Promise<unknown> | undefined;
|
|
34
|
+
flush(timeoutMs?: number): Promise<CaptureFlushResult>;
|
|
35
|
+
accepts(tool: string, source: CaptureSource): boolean;
|
|
36
|
+
}
|
|
37
|
+
/** A copy of `target` with its prototype and own properties, and `key` replaced by `value` (the original is untouched). */
|
|
38
|
+
export declare function copyWith<T extends object>(target: T, key: string, value: unknown): T;
|
|
39
|
+
/** A LangChain ToolMessage: recorded as its content (plus artifact), status error when the message says so. */
|
|
40
|
+
export declare function isToolMessage(v: unknown): v is {
|
|
41
|
+
content: unknown;
|
|
42
|
+
artifact?: unknown;
|
|
43
|
+
status?: string;
|
|
44
|
+
tool_call_id: string;
|
|
45
|
+
name?: string;
|
|
46
|
+
};
|
|
47
|
+
/** The part of an AI SDK 7 `onToolExecutionEnd` event capture reads (also the v6 `experimental_onToolCallFinish` shape). */
|
|
48
|
+
export interface AiSdkToolEndEvent {
|
|
49
|
+
toolCall?: {
|
|
50
|
+
toolCallId?: string;
|
|
51
|
+
toolName?: string;
|
|
52
|
+
input?: unknown;
|
|
53
|
+
} | undefined;
|
|
54
|
+
toolExecutionMs?: number | undefined;
|
|
55
|
+
toolOutput?: {
|
|
56
|
+
type?: string;
|
|
57
|
+
output?: unknown;
|
|
58
|
+
error?: unknown;
|
|
59
|
+
} | undefined;
|
|
60
|
+
success?: boolean | undefined;
|
|
61
|
+
output?: unknown;
|
|
62
|
+
error?: unknown;
|
|
63
|
+
durationMs?: number | undefined;
|
|
64
|
+
}
|
|
65
|
+
export interface AiSdkAdapter {
|
|
66
|
+
/** `generateText({ tools: capture.aiSdk.tools(tools) })`: wraps each `execute` (keeps toolCallId, abortSignal and every other property). */
|
|
67
|
+
tools<T>(tools: T): T;
|
|
68
|
+
/** `generateText({ ...capture.aiSdk.callbacks() })`: `onToolExecutionEnd` (hook-level). */
|
|
69
|
+
callbacks(opts?: {
|
|
70
|
+
legacy?: false;
|
|
71
|
+
}): {
|
|
72
|
+
onToolExecutionEnd: (event: AiSdkToolEndEvent) => void;
|
|
73
|
+
};
|
|
74
|
+
/** `{ legacy: true }`: the deprecated alias `experimental_onToolCallFinish`. */
|
|
75
|
+
callbacks(opts: {
|
|
76
|
+
legacy: true;
|
|
77
|
+
}): {
|
|
78
|
+
experimental_onToolCallFinish: (event: AiSdkToolEndEvent) => void;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/** Mastra `afterToolCall` context (ToolAfterHookContext). */
|
|
82
|
+
export interface MastraAfterToolCallContext {
|
|
83
|
+
toolName?: string;
|
|
84
|
+
input?: unknown;
|
|
85
|
+
context?: unknown;
|
|
86
|
+
metadata?: unknown;
|
|
87
|
+
output?: unknown;
|
|
88
|
+
error?: unknown;
|
|
89
|
+
}
|
|
90
|
+
export interface MastraHooksLike {
|
|
91
|
+
beforeToolCall?: (...args: any[]) => any;
|
|
92
|
+
afterToolCall?: (ctx: any) => void | Promise<void>;
|
|
93
|
+
}
|
|
94
|
+
export interface MastraAdapter {
|
|
95
|
+
/** `new Agent({ hooks: capture.mastra.hooks(myHooks) })`: records in `afterToolCall`, then calls yours. */
|
|
96
|
+
hooks<H extends MastraHooksLike = Record<never, never>>(existing?: H): H & {
|
|
97
|
+
afterToolCall: (ctx: MastraAfterToolCallContext) => Promise<void>;
|
|
98
|
+
};
|
|
99
|
+
/** `new Agent({ tools: capture.mastra.tools({ weather }) })`: wraps each `execute(inputData, context)`. */
|
|
100
|
+
tools<T>(tools: T): T;
|
|
101
|
+
}
|
|
102
|
+
export interface AnthropicAdapter {
|
|
103
|
+
/** `client.beta.messages.toolRunner({ tools: capture.anthropic.tools([...]) })`: wraps `run(input, context)`. */
|
|
104
|
+
tools<T>(tools: T): T;
|
|
105
|
+
}
|
|
106
|
+
export interface OpenAIAgentsAdapter {
|
|
107
|
+
/** `new Agent({ tools: capture.openaiAgents.tools([...]) })`: wraps each FunctionTool's `invoke`. */
|
|
108
|
+
tools<T>(tools: T): T;
|
|
109
|
+
/**
|
|
110
|
+
* `tool({ execute: capture.openaiAgents.execute('name', fn) })`: records around your execute, with the exact error
|
|
111
|
+
* (a FunctionTool's own error function turns a thrown error into a string result before `invoke` returns).
|
|
112
|
+
*/
|
|
113
|
+
execute<F extends (...args: any[]) => any>(name: string, fn: F): F;
|
|
114
|
+
/** Subscribes `agent_tool_end` on a Runner or an Agent (hook-level); returns the function that unsubscribes. */
|
|
115
|
+
attach(target: {
|
|
116
|
+
on(event: 'agent_tool_end', listener: (...args: any[]) => void): unknown;
|
|
117
|
+
off(event: 'agent_tool_end', listener: (...args: any[]) => void): unknown;
|
|
118
|
+
}): () => void;
|
|
119
|
+
}
|
|
120
|
+
export type ClaudeHookCallback = (input: any, toolUseID: string | undefined, options: {
|
|
121
|
+
signal: AbortSignal;
|
|
122
|
+
}) => Promise<{
|
|
123
|
+
continue?: boolean;
|
|
124
|
+
}>;
|
|
125
|
+
export interface ClaudeHookMatcher {
|
|
126
|
+
matcher?: string;
|
|
127
|
+
hooks: ClaudeHookCallback[];
|
|
128
|
+
timeout?: number;
|
|
129
|
+
}
|
|
130
|
+
export interface ClaudeHookMatcherLike {
|
|
131
|
+
matcher?: string;
|
|
132
|
+
hooks: Array<(input: any, toolUseID: string | undefined, options: {
|
|
133
|
+
signal: AbortSignal;
|
|
134
|
+
}) => Promise<unknown>>;
|
|
135
|
+
timeout?: number;
|
|
136
|
+
}
|
|
137
|
+
export type ClaudeHooksLike = Partial<Record<string, ClaudeHookMatcherLike[]>>;
|
|
138
|
+
export type ClaudeCaptureHooks = {
|
|
139
|
+
PreToolUse: ClaudeHookMatcher[];
|
|
140
|
+
PostToolUse: ClaudeHookMatcher[];
|
|
141
|
+
PostToolUseFailure: ClaudeHookMatcher[];
|
|
142
|
+
};
|
|
143
|
+
export interface ClaudeAdapter {
|
|
144
|
+
/**
|
|
145
|
+
* `query({ prompt, options: { hooks: capture.claude.hooks(myHooks) } })`: PostToolUse and PostToolUseFailure record
|
|
146
|
+
* (hook-level); a PreToolUse hook matching `^mcp__shardflux__` waits for pending writes (bounded) so the Shardflux MCP
|
|
147
|
+
* server sees them. Your hooks are kept and run as before.
|
|
148
|
+
*/
|
|
149
|
+
hooks<H extends ClaudeHooksLike = Record<never, never>>(existing?: H): Omit<H, keyof ClaudeCaptureHooks> & ClaudeCaptureHooks;
|
|
150
|
+
}
|
|
151
|
+
export interface LangChainToolHandler {
|
|
152
|
+
handleToolStart(tool: unknown, input: string, runId: string, parentRunId?: string, tags?: string[], metadata?: Record<string, unknown>, runName?: string, toolCallId?: string): void;
|
|
153
|
+
handleToolEnd(output: unknown, runId: string, parentRunId?: string, tags?: string[]): void;
|
|
154
|
+
handleToolError(err: unknown, runId: string, parentRunId?: string, tags?: string[]): void;
|
|
155
|
+
}
|
|
156
|
+
export interface LangChainAdapter {
|
|
157
|
+
/** `{ callbacks: [capture.langchain.handler()] }` (a plain methods object: LangChain wraps it with fromMethods). */
|
|
158
|
+
handler(): LangChainToolHandler;
|
|
159
|
+
}
|
|
160
|
+
export interface McpAdapter {
|
|
161
|
+
/** Patches this client instance's `callTool` (hook-level). Returns the function that restores it. */
|
|
162
|
+
instrument(client: {
|
|
163
|
+
callTool: (...args: any[]) => any;
|
|
164
|
+
}, opts?: {
|
|
165
|
+
server?: string;
|
|
166
|
+
}): () => void;
|
|
167
|
+
}
|
|
168
|
+
export interface CaptureAdapters {
|
|
169
|
+
wrapAny<T>(tools: T): T;
|
|
170
|
+
aiSdk: AiSdkAdapter;
|
|
171
|
+
mastra: MastraAdapter;
|
|
172
|
+
anthropic: AnthropicAdapter;
|
|
173
|
+
openaiAgents: OpenAIAgentsAdapter;
|
|
174
|
+
claude: ClaudeAdapter;
|
|
175
|
+
langchain: LangChainAdapter;
|
|
176
|
+
mcp: McpAdapter;
|
|
177
|
+
}
|
|
178
|
+
export declare function createAdapters(core: CaptureCore): CaptureAdapters;
|