Skip to content

sdk: author provisioned tools against typed capabilities - #121

Merged
weilueluo merged 1 commit into
mainfrom
capability-provisioned-path
Aug 31, 2026
Merged

sdk: author provisioned tools against typed capabilities#121
weilueluo merged 1 commit into
mainfrom
capability-provisioned-path

Conversation

@weilueluo

Copy link
Copy Markdown
Contributor

The provisioned half of the capability contract (#119): typed tool authoring, environment capability providers, the tool artifact build, and the SDK-owned ESM host runtime.

What

  • Typed tool authoringtool({ ..., requires, bindings }) derives the run context type from the declaration (Pick<CapabilityHandles, requires[number]> plus schema-typed bindings values), so using an undeclared capability does not compile. Handle interfaces per the contract (ExecHandle, FsHandle, NetHandle, JsHandle, PageHandle) surface failures as typed CapabilityErrors; grant policy stays invisible to tools.
  • Environment capability providersprovide.exec(...), provide.fs(...), … register providers whose factory receives { instance, grants }; the registered set becomes the provides list on the setup and attach receipts (replacing the hardcoded provides: []), feeding the kernel's requires ⊆ provides check. A shared clamp helper covers exec bounds (clamp(opts, grants.exec)) and fs-root confinement (clamp.path rejects any resolved path escaping the root with a path_escape CapabilityError).
  • Artifact buildbrain build emits <name>.tool.json per tool: the tool/v1 manifest (schemas from zod, requires, binding_names, hosting: "provisioned", payload identity = sha-256 of the bundle) plus the self-contained single-file ESM bundle. Binding values are structurally impossible in the artifact. Build warns — never errors — on a declared capability whose handle never appears in the authored code, checked against a dependencies-external bundle so capability names inside the SDK or third-party packages cannot mask it.
  • Host runtimehost.esm({ artifacts }) opts an environment into hosting. Payloads are cached by content identity and imported + initialized once at provision, so a throwing bundle, an unprovided capability, or a missing binding value fails the attach receipt, never the first model call. At invoke the host validates input against the tool's schema (invalid_input), wires handles in-process to the environment's providers for exactly the tool's requires, injects binding values from attach, and maps result / thrown error (identifier-shaped code preserved) / deadline (timeout) / cancel (cancelled) onto the one Outcome envelope.

Confinement choice

Plain import() of the bundle (data: URL) under a trusted-artifact assumption, stated in the code: worker/vm isolation would need an RPC layer to carry capability handles across the boundary, which is not worth its weight until an untrusted-tool story needs it. Payload identity is verified before import.

Deviations from the interface draft

  • provide.* and host.esm() hang off the object author.open(...) returns (alongside the existing run/close/method/stream), not the top-level author — that is where Instance is typed, so provider factories get a typed instance.
  • The run context keeps options, requestId (alias of callId), workspace?, and progress() from the existing surface alongside the draft's handles/bindings/signal/deadline/callId.
  • Payload bytes travel out of band: attach names payloads by identity per the wire contract, and host.esm({ artifacts }) is how a process registers what it can serve.

Tests

Type-level fixture compiled by tsc in-test (undeclared capability and undeclared binding fail via @ts-expect-error), provision-fail-at-attach (broken bundle, missing capability, missing binding, unknown identity), invoke happy path + input-validation failure + thrown error + deadline timeout, fs-root escape, exec clamp, unused-requires warning, and a manifest golden test that also runs the emitted artifact end to end on a hosting environment.

Verification

npm run gen clean · npm run build · npm test green (SDK 17/17, examples build) · cargo test --workspace --all-targets green (no Rust changes).

Note: overlaps with the in-flight callback PR (capability-callback-path) in extensions.ts, index.ts, and docs/concepts/tool.mdx; whichever lands second needs a small merge.

🤖 Generated with Claude Code

@weilueluo
weilueluo force-pushed the capability-provisioned-path branch from 75cea0e to cb0173c Compare August 31, 2026 13:36
@weilueluo
weilueluo merged commit 32ccb60 into main Aug 31, 2026
14 checks passed
@weilueluo
weilueluo deleted the capability-provisioned-path branch August 31, 2026 13:44
weilueluo added a commit that referenced this pull request Sep 1, 2026
Carries the #118-#121 typed-input contract and the client-hosted tool surface;
both moved the session/v1 and tool/v1 contract digests, so the bump is breaking
by pre-1.0 convention.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant