Skip to content

Route tool defects opaquely; remove legacy envelope shim - #826

Merged
RhysSullivan merged 4 commits into
mainfrom
rs/tool-result-defect-routing
May 17, 2026
Merged

Route tool defects opaquely; remove legacy envelope shim#826
RhysSullivan merged 4 commits into
mainfrom
rs/tool-result-defect-routing

Conversation

@RhysSullivan

@RhysSullivan RhysSullivan commented May 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

Final phase of the ToolResult migration. The dispatcher now passes ToolResult<T> straight through to the sandbox and routes escaped Effect failures as opaque generics so internal causes cannot leak.

  • packages/core/execution/src/tool-invoker.ts: removes the old { data, error } envelope detection helpers. Non-ToolResult plain returns are wrapped in ToolResult.ok so the sandbox surface is uniform.
  • Plugin and infrastructure defects now get a short hex correlation id, log the full cause structurally under executor.correlation_id, and surface only Internal tool error [<corrId>] to the sandbox. Cause messages, stacks, URLs, tokens, headers, and connection strings stay out of model-visible output.
  • runtime-quickjs and runtime-dynamic-worker defensively preserve the same opaque defect shape. ExecutionToolError from in-band validators still passes through so model-facing input errors keep useful diagnostics.
  • MCP host execution failures use the same opaque routing.
  • Upstream tool failures remain errors-as-values: plugins emit ToolResult.fail, the invoker passes that value through unchanged, and sandbox code can inspect result.ok === false plus result.error.details.

Tests

  • tool-result.test.ts: covers ToolResult.ok, ToolResult.fail, and isToolResult via the schema-backed discriminated union.
  • tool-invoker.test.ts: covers the described return type against actual sandbox invocation results, including failed ToolResult values and structured upstream error payloads.
  • tool-invoker.leak.test.ts: pins the defect-routing invariant: plugin defects escape only as the opaque generic plus correlation id. Tokens, connection strings, file paths, and stacks stay hidden.
  • QuickJS bridge end-to-end defect coverage confirms the shape at the sandbox boundary.
  • Cloud HTTP integration tests, MCP host tests, dynamic-worker invocation tests, and plugin tests were updated for the new wire shape.

Test plan

  • bun run format:check
  • bun run lint
  • bun run typecheck
  • bun run test
  • CI: Format, Lint, Typecheck, Test, Desktop smoke build, Publish, preview binaries, Continuous Releases, and Worker builds all passed on e38616a3b.
  • Manual: call a known-failing upstream tool and verify the structured body reaches sandbox user code on result.error.details.
  • Manual: trigger a plugin defect and confirm the sandbox only sees Internal tool error [<corrId>] while server logs carry the full cause.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented May 15, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
executor-marketing e38616a Commit Preview URL

Branch Preview URL
May 17 2026, 05:32 AM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented May 15, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
executor-cloud e38616a May 17 2026, 05:33 AM

@pkg-pr-new

pkg-pr-new Bot commented May 15, 2026

Copy link
Copy Markdown

Open in StackBlitz

@executor-js/cli

npm i https://pkg.pr.new/@executor-js/cli@826

@executor-js/config

npm i https://pkg.pr.new/@executor-js/config@826

@executor-js/execution

npm i https://pkg.pr.new/@executor-js/execution@826

@executor-js/sdk

npm i https://pkg.pr.new/@executor-js/sdk@826

@executor-js/codemode-core

npm i https://pkg.pr.new/@executor-js/codemode-core@826

@executor-js/runtime-quickjs

npm i https://pkg.pr.new/@executor-js/runtime-quickjs@826

@executor-js/plugin-file-secrets

npm i https://pkg.pr.new/@executor-js/plugin-file-secrets@826

@executor-js/plugin-google-discovery

npm i https://pkg.pr.new/@executor-js/plugin-google-discovery@826

@executor-js/plugin-graphql

npm i https://pkg.pr.new/@executor-js/plugin-graphql@826

@executor-js/plugin-keychain

npm i https://pkg.pr.new/@executor-js/plugin-keychain@826

@executor-js/plugin-mcp

npm i https://pkg.pr.new/@executor-js/plugin-mcp@826

@executor-js/plugin-onepassword

npm i https://pkg.pr.new/@executor-js/plugin-onepassword@826

@executor-js/plugin-openapi

npm i https://pkg.pr.new/@executor-js/plugin-openapi@826

executor

npm i https://pkg.pr.new/executor@826

commit: e38616a

Adds `ToolResult<T>` / `ToolError` to @executor-js/sdk and a
`ToolResult.ok` / `ToolResult.fail` constructor pair.

Updates the sandbox tool dispatcher to accept both the new typed union
(passes through unchanged) and the legacy `{ data, error }` envelope.
The legacy branch now walks known upstream shapes (Microsoft Graph,
DealCloud, JSON:API, plain message-bearing bodies) before falling back
to a clamped JSON.stringify of the body, so structured 4xx payloads no
longer collapse to 'Tool execution failed'. The cause/defect branch
keeps its strict .message-only discipline.
…covery

Migrates each dynamic plugin's invokeTool to wrap its result in the
typed ToolResult<T> union. OpenAPI and Google Discovery map non-2xx
responses to ToolResult.fail with an extracted upstream message and
the raw body in error.details; 2xx responses to ToolResult.ok with
{ status, headers, data }. GraphQL maps 200-with-errors bodies to
ToolResult.fail({ code: 'graphql_errors', ... }) and bare data to
ToolResult.ok. MCP maps { isError: true } to ToolResult.fail and
content arrays to ToolResult.ok.

Static plugin tools (executor.openapi.previewSpec / addSource,
executor.graphql.addSource) now also wrap their plain values in
ToolResult.ok for shape consistency.

Updates plugin tests that asserted on the old { status, headers, data,
error } envelope or the bare MCP { content } shape. Tests that consume
the OpenAPI plugin's invocation result use the new unwrapInvocation
helper from @executor-js/plugin-openapi/testing.
Removes the legacy { data, error } envelope shim from the sandbox tool
dispatcher. The invoker now passes ToolResult<T> through unchanged and
wraps any other plain-value plugin return in ToolResult.ok so the
sandbox surface is uniform.

Plugin/infra defects no longer pass their message into the sandbox.
The dispatcher generates a short hex correlation id, logs the full
cause with that id under executor.correlation_id, and rejects with
`Internal tool error [<corrId>]`. The QuickJS bridge and the
dynamic-worker module template defensively re-stamp the same opaque
shape; the MCP host server's top-level execute failure path does the
same.

ExecutionToolError in-band messages from the execution package's
built-in validators (tools.search arg checks, etc.) are still passed
through at the QuickJS bridge so model-facing input errors keep their
useful diagnostic.

Tests:
- ToolResult.ok / fail / isToolResult constructor unit tests.
- repro tests assert structured upstream payloads now reach the
  sandbox through ToolResult.error.details (not through .message).
- leak tests pin the new invariant: plugin defects only escape as the
  opaque generic + correlation id; no token / connection string / file
  path leaks into Error.message.
- QuickJS end-to-end defect test confirms the same shape at the
  sandbox boundary.
- Cloud HTTP integration tests, MCP host tests, dynamic-worker
  invocation tests, and plugin tests updated for the new ToolResult
  wire shape and the opaque-generic defect contract.
@RhysSullivan
RhysSullivan force-pushed the rs/tool-result-defect-routing branch from cd16d15 to c35794f Compare May 17, 2026 04:17
@RhysSullivan
RhysSullivan marked this pull request as ready for review May 17, 2026 04:56
@RhysSullivan
RhysSullivan force-pushed the rs/tool-result-defect-routing branch 3 times, most recently from a29cfaa to 96bbeec Compare May 17, 2026 05:20
@RhysSullivan
RhysSullivan force-pushed the rs/tool-result-defect-routing branch from 96bbeec to e38616a Compare May 17, 2026 05:30
@RhysSullivan
RhysSullivan merged commit 278ca7c into main May 17, 2026
11 checks passed
RhysSullivan added a commit that referenced this pull request May 31, 2026
* feat(tool-result): add ToolResult union and dual-shape invoker

Adds `ToolResult<T>` / `ToolError` to @executor-js/sdk and a
`ToolResult.ok` / `ToolResult.fail` constructor pair.

Updates the sandbox tool dispatcher to accept both the new typed union
(passes through unchanged) and the legacy `{ data, error }` envelope.
The legacy branch now walks known upstream shapes (Microsoft Graph,
DealCloud, JSON:API, plain message-bearing bodies) before falling back
to a clamped JSON.stringify of the body, so structured 4xx payloads no
longer collapse to 'Tool execution failed'. The cause/defect branch
keeps its strict .message-only discipline.

* feat(plugins): emit ToolResult from openapi, graphql, mcp, google-discovery

Migrates each dynamic plugin's invokeTool to wrap its result in the
typed ToolResult<T> union. OpenAPI and Google Discovery map non-2xx
responses to ToolResult.fail with an extracted upstream message and
the raw body in error.details; 2xx responses to ToolResult.ok with
{ status, headers, data }. GraphQL maps 200-with-errors bodies to
ToolResult.fail({ code: 'graphql_errors', ... }) and bare data to
ToolResult.ok. MCP maps { isError: true } to ToolResult.fail and
content arrays to ToolResult.ok.

Static plugin tools (executor.openapi.previewSpec / addSource,
executor.graphql.addSource) now also wrap their plain values in
ToolResult.ok for shape consistency.

Updates plugin tests that asserted on the old { status, headers, data,
error } envelope or the bare MCP { content } shape. Tests that consume
the OpenAPI plugin's invocation result use the new unwrapInvocation
helper from @executor-js/plugin-openapi/testing.

* feat(tool-result): make ToolResult mandatory and route defects opaquely

Removes the legacy { data, error } envelope shim from the sandbox tool
dispatcher. The invoker now passes ToolResult<T> through unchanged and
wraps any other plain-value plugin return in ToolResult.ok so the
sandbox surface is uniform.

Plugin/infra defects no longer pass their message into the sandbox.
The dispatcher generates a short hex correlation id, logs the full
cause with that id under executor.correlation_id, and rejects with
`Internal tool error [<corrId>]`. The QuickJS bridge and the
dynamic-worker module template defensively re-stamp the same opaque
shape; the MCP host server's top-level execute failure path does the
same.

ExecutionToolError in-band messages from the execution package's
built-in validators (tools.search arg checks, etc.) are still passed
through at the QuickJS bridge so model-facing input errors keep their
useful diagnostic.

Tests:
- ToolResult.ok / fail / isToolResult constructor unit tests.
- repro tests assert structured upstream payloads now reach the
  sandbox through ToolResult.error.details (not through .message).
- leak tests pin the new invariant: plugin defects only escape as the
  opaque generic + correlation id; no token / connection string / file
  path leaks into Error.message.
- QuickJS end-to-end defect test confirms the same shape at the
  sandbox boundary.
- Cloud HTTP integration tests, MCP host tests, dynamic-worker
  invocation tests, and plugin tests updated for the new ToolResult
  wire shape and the opaque-generic defect contract.

* Fix tool result contracts
@RhysSullivan
RhysSullivan deleted the rs/tool-result-defect-routing branch June 11, 2026 08:01
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