Skip to content

Commit 96bbeec

Browse files
committed
Fix tool result contracts
1 parent 3030c8e commit 96bbeec

27 files changed

Lines changed: 662 additions & 363 deletions

apps/cloud/src/services/sources-api.node.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ describe("sources api (HTTP)", () => {
398398
status: "completed",
399399
result: {
400400
ok: true,
401-
data: [{ type: "text", text: "cloud-mcp-ok" }],
401+
data: { content: [{ type: "text", text: "cloud-mcp-ok" }] },
402402
},
403403
});
404404
expect((yield* server.requests).length).toBeGreaterThanOrEqual(2);

apps/local/src/server/migrate-oauth-connections.test.ts

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ describe("0009_repair_openapi_oauth_cutover_residue", () => {
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", () => {
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", () => {
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", () => {
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,27 +235,27 @@ describe("0009_repair_openapi_oauth_cutover_residue", () => {
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
);
248248
insertBinding.run(
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,49 +271,49 @@ describe("0009_repair_openapi_oauth_cutover_residue", () => {
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
{
291291
scope_id: "org-1",
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
{
312312
scope_id: "user-org:user-jd:org-1",
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
});

packages/core/execution/src/description.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ const formatDescription = (sources: readonly Source[]): string => {
4848
"- `tools.search()` returns paginated, ranked matches: `{ items, total, hasMore, nextOffset }`. Best-first. Use short intent phrases like `github issues`, `repo details`, or `create calendar event`.",
4949
'- When you already know the namespace, narrow with `tools.search({ namespace: "github", query: "issues" })`.',
5050
"- `tools.executor.sources.list()` returns the same paged shape: `{ items: [{ id, toolCount, ... }], total, hasMore, nextOffset }`.",
51+
"- Tool calls return a value union: `{ ok: true, data }` for success or `{ ok: false, error: { code, message, status?, details?, retryable? } }` for expected tool/domain failures. Branch on `result.ok`.",
5152
"- If `hasMore` is true and you didn't find what you need, fetch the next page: `tools.search({ query, offset: nextOffset, limit })`. Same `offset` parameter on `tools.executor.sources.list({ offset, limit })`.",
5253
"- Always use the namespace prefix when calling tools: `tools.<namespace>.<tool>(args)`. Example: `tools.home_assistant_rest_api.states.getState(...)` — not `tools.states.getState(...)`.",
5354
"- The `tools` object is a lazy proxy — `Object.keys(tools)` won't work. Use `tools.search()` or `tools.executor.sources.list()` instead.",

packages/core/execution/src/tool-invoker.leak.test.ts

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ const EmptyInputSchema = Schema.toStandardSchemaV1(
1212

1313
const acceptAll = () => Effect.succeed(ElicitationResponse.make({ action: "accept" }));
1414

15-
// Plugin-internal tagged error whose `cause` carries sensitive internal
16-
// context. The dispatcher must route this through the opaque-generic
17-
// path so none of that context reaches the sandbox via Error.message.
18-
class FakeOpenApiInvocationError extends Data.TaggedError("OpenApiInvocationError")<{
15+
// Plugin-internal tagged error whose `cause` carries internal diagnostics.
16+
// The dispatcher must route this through the opaque-generic path so none of
17+
// that context reaches the sandbox via Error.message.
18+
class FakePluginInvocationError extends Data.TaggedError("PluginInvocationError")<{
1919
readonly message: string;
2020
readonly cause: unknown;
2121
}> {}
@@ -35,18 +35,17 @@ const leakyPlugin = definePlugin(() => ({
3535
inputSchema: EmptyInputSchema,
3636
handler: () =>
3737
Effect.fail(
38-
new FakeOpenApiInvocationError({
39-
message: "HTTP request failed",
38+
new FakePluginInvocationError({
39+
message: "Upstream request failed",
4040
cause: {
41-
_tag: "HttpClientError",
41+
_tag: "InternalTransportError",
4242
request: {
4343
method: "GET",
44-
url: "https://internal.dealcloud/v1/entities?accessToken=SECRET_TOKEN_xyz",
45-
headers: { Authorization: "Bearer SECRET_TOKEN_xyz" },
44+
url: "https://internal.service.local/v1/resources?trace=trace-123",
45+
headers: { "x-internal-routing": "private-cluster" },
4646
},
47-
stack:
48-
"Error: ECONNREFUSED\n at /home/svc/executor/packages/plugins/openapi/...:142:11",
49-
dbConnString: "postgres://app:p@ssw0rd@10.0.0.5:5432/executor",
47+
stack: "Error: connect failed\n at plugin-transport.ts:42:11",
48+
note: "internal diagnostic detail",
5049
},
5150
}),
5251
),
@@ -87,11 +86,11 @@ describe("internal-error leak audit (opaque defects)", () => {
8786
// Must be the canonical opaque shape: "Internal tool error [<hex>]"
8887
expect(msg).toMatch(/^Internal tool error \[[0-9a-f]{8}\]$/);
8988
// Crucially, no internal context leaks
90-
expect(msg).not.toContain("SECRET_TOKEN_xyz");
91-
expect(msg).not.toContain("p@ssw0rd");
92-
expect(msg).not.toContain("packages/plugins");
93-
expect(msg).not.toContain("HttpClientError");
94-
expect(msg).not.toContain("HTTP request failed");
89+
expect(msg).not.toContain("trace-123");
90+
expect(msg).not.toContain("private-cluster");
91+
expect(msg).not.toContain("internal.service.local");
92+
expect(msg).not.toContain("InternalTransportError");
93+
expect(msg).not.toContain("Upstream request failed");
9594
}),
9695
);
9796

0 commit comments

Comments
 (0)