History

Route tool defects opaquely; remove legacy envelope shim (#826)

* 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

Rhys Sullivan committed 6 months ago

Commit 278ca7c1 parent 8d986ff35 files · +1322 −340Browse files

Showing the changes to apps (2 of 35 files). Show all

Unified · Split

2 files
  1. apps/cloud/src/services/sources-api.node.test.ts +12 −5
  2. apps/local/src/server/migrate-oauth-connections.test.ts +24 −24
apps/cloud/src/services/sources-api.node.test.ts+12−5
@@ -207,9 +207,15 @@ describe("sources api (HTTP)", () => { · 206 unmodified lines
207207 expect(execution.structured).toMatchObject({
208208 status: "completed",
209209 result: {
210− message: "hello",
211− suffix: "world",
212− path: "/echo/hello",
210+ ok: true,
211+ data: {
212+ status: 200,
213+ data: {
214+ message: "hello",
215+ suffix: "world",
216+ path: "/echo/hello",
217+ },
218+ },
213219 },
214220 logs: [],
215221 });
@@ -315,7 +321,7 @@ describe("sources api (HTTP)", () => { · 99 unmodified lines
315321 expect(execution.isError).toBe(false);
316322 expect(execution.structured).toMatchObject({
317323 status: "completed",
318− result: { hello: "Hello Ada" },
324+ result: { ok: true, data: { hello: "Hello Ada" } },
319325 });
320326 const requests = yield* server.requests;
321327 expect(requests.some((request) => request.payload.query?.includes("__schema"))).toBe(true);
@@ -391,7 +397,8 @@ describe("sources api (HTTP)", () => { · 69 unmodified lines
391397 expect(execution.structured).toMatchObject({
392398 status: "completed",
393399 result: {
394− content: [{ type: "text", text: "cloud-mcp-ok" }],
400+ ok: true,
401+ data: { content: [{ type: "text", text: "cloud-mcp-ok" }] },
395402 },
396403 });
397404 expect((yield* server.requests).length).toBeGreaterThanOrEqual(2);
apps/local/src/server/migrate-oauth-connections.test.ts+24−24
@@ -159,7 +159,7 @@ describe("0009_repair_openapi_oauth_cutover_residue", () => { · 158 unmodified lines
159159 `);
160160
161161 db.prepare("INSERT INTO `openapi_source` (id, scope_id, oauth2) VALUES (?, ?, ?)").run(
162− "dealcloud_api",
162+ "example_api",
163163 "org-1",
164164 JSON.stringify({
165165 kind: "oauth2",
@@ -174,27 +174,27 @@ describe("0009_repair_openapi_oauth_cutover_residue", () => { · 8 unmodified lines
174174 "INSERT INTO `connection` (id, scope_id, provider, provider_state, scope, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
175175 );
176176 insertConnection.run(
177− "openapi-oauth2-app-dealcloud_api",
177+ "openapi-oauth2-app-example_api",
178178 "org-1",
179179 "oauth2",
180180 JSON.stringify({
181181 kind: "client-credentials",
182− tokenEndpoint: "https://resolve.dealcloud.com/oauth/token",
183− clientIdSecretId: "dealcloud-client-id",
184− clientSecretSecretId: "dealcloud-client-secret",
182+ tokenEndpoint: "https://auth.example.test/oauth/token",
183+ clientIdSecretId: "example-client-id",
184+ clientSecretSecretId: "example-client-secret",
185185 }),
186186 null,
187187 now,
188188 );
189189 insertConnection.run(
190− "openapi-oauth2-app-dealcloud_api",
190+ "openapi-oauth2-app-example_api",
191191 "user-org:user-jd:org-1",
192192 "openapi:oauth2",
193193 JSON.stringify({
194194 kind: "client-credentials",
195− tokenEndpoint: "https://resolve.dealcloud.com/oauth/token",
196− clientIdSecretId: "dealcloud-client-id-jd",
197− clientSecretSecretId: "dealcloud-client-secret-jd",
195+ tokenEndpoint: "https://auth.example.test/oauth/token",
196+ clientIdSecretId: "example-client-id-jd",
197+ clientSecretSecretId: "example-client-secret-jd",
198198 }),
199199 null,
200200 now,
@@ -207,12 +207,12 @@ describe("0009_repair_openapi_oauth_cutover_residue", () => { · 6 unmodified lines
207207 "org-client-id",
208208 "org-1",
209209 "openapi",
210− "dealcloud_api",
210+ "example_api",
211211 "org-1",
212212 "oauth2:oauth2:client-id",
213213 "secret",
214214 null,
215− "dealcloud-client-id-jd",
215+ "example-client-id-jd",
216216 null,
217217 now,
218218 now,
@@ -221,12 +221,12 @@ describe("0009_repair_openapi_oauth_cutover_residue", () => { · 2 unmodified lines
221221 "org-client-secret",
222222 "org-1",
223223 "openapi",
224− "dealcloud_api",
224+ "example_api",
225225 "org-1",
226226 "oauth2:oauth2:client-secret",
227227 "secret",
228228 null,
229− "dealcloud-client-secret-jd",
229+ "example-client-secret-jd",
230230 null,
231231 now,
232232 now,
@@ -235,13 +235,13 @@ describe("0009_repair_openapi_oauth_cutover_residue", () => { · 2 unmodified lines
235235 "org-connection",
236236 "org-1",
237237 "openapi",
238− "dealcloud_api",
238+ "example_api",
239239 "org-1",
240240 "oauth2:oauth2:connection",
241241 "connection",
242242 null,
243243 null,
244− "openapi-oauth2-app-dealcloud_api",
244+ "openapi-oauth2-app-example_api",
245245 now,
246246 now,
247247 );
@@ -249,13 +249,13 @@ describe("0009_repair_openapi_oauth_cutover_residue", () => { · 1 unmodified line
249249 "jd-connection",
250250 "user-org:user-jd:org-1",
251251 "openapi",
252− "dealcloud_api",
252+ "example_api",
253253 "org-1",
254254 "oauth2:oauth2:connection",
255255 "connection",
256256 null,
257257 null,
258− "openapi-oauth2-app-dealcloud_api",
258+ "openapi-oauth2-app-example_api",
259259 now,
260260 now,
261261 );
@@ -271,20 +271,20 @@ describe("0009_repair_openapi_oauth_cutover_residue", () => { · 9 unmodified lines
271271 .prepare(
272272 "SELECT scope_id, slot_key, kind, secret_id, connection_id FROM `credential_binding` WHERE source_id = ? ORDER BY scope_id, slot_key",
273273 )
274− .all("dealcloud_api");
274+ .all("example_api");
275275 expect(bindings).toEqual([
276276 {
277277 scope_id: "org-1",
278278 slot_key: "oauth2:oauth2:client-id",
279279 kind: "secret",
280− secret_id: "dealcloud-client-id",
280+ secret_id: "example-client-id",
281281 connection_id: null,
282282 },
283283 {
284284 scope_id: "org-1",
285285 slot_key: "oauth2:oauth2:client-secret",
286286 kind: "secret",
287− secret_id: "dealcloud-client-secret",
287+ secret_id: "example-client-secret",
288288 connection_id: null,
289289 },
290290 {
@@ -292,20 +292,20 @@ describe("0009_repair_openapi_oauth_cutover_residue", () => { · 1 unmodified line
292292 slot_key: "oauth2:oauth2:connection",
293293 kind: "connection",
294294 secret_id: null,
295− connection_id: "openapi-oauth2-app-dealcloud_api",
295+ connection_id: "openapi-oauth2-app-example_api",
296296 },
297297 {
298298 scope_id: "user-org:user-jd:org-1",
299299 slot_key: "oauth2:oauth2:client-id",
300300 kind: "secret",
301− secret_id: "dealcloud-client-id-jd",
301+ secret_id: "example-client-id-jd",
302302 connection_id: null,
303303 },
304304 {
305305 scope_id: "user-org:user-jd:org-1",
306306 slot_key: "oauth2:oauth2:client-secret",
307307 kind: "secret",
308− secret_id: "dealcloud-client-secret-jd",
308+ secret_id: "example-client-secret-jd",
309309 connection_id: null,
310310 },
311311 {
@@ -313,7 +313,7 @@ describe("0009_repair_openapi_oauth_cutover_residue", () => { · 1 unmodified line
313313 slot_key: "oauth2:oauth2:connection",
314314 kind: "connection",
315315 secret_id: null,
316− connection_id: "openapi-oauth2-app-dealcloud_api",
316+ connection_id: "openapi-oauth2-app-example_api",
317317 },
318318 ]);
319319 });

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t