Skip to content

Commit bbb2426

Browse files
committed
computer: read callable state from the runtime, not tool options
The exec tool learned which backends were callable from a `callable` flag on each backend description the caller passed to createAITools. That duplicated a fact the Workspace already holds: it derives the callable set from each backend's own `callable` flag at construction. Declaring it a second time on the tool let the two drift. Expose `isCallable(id)` on the runtime and have the tool ask it, dropping `callable` from the backend description. The runtime answers from the same set that guards its own structured-input check, so the tool and the runtime can no longer disagree about which backends accept input.
1 parent d2ee5c5 commit bbb2426

3 files changed

Lines changed: 28 additions & 14 deletions

File tree

packages/computer/src/runtime/runtime.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ export class WorkspaceRuntime {
2626
this.#options = options;
2727
}
2828

29+
// Whether the named backend accepts a structured `input` value and
30+
// returns a structured result. Consumers such as the exec tool ask
31+
// this to know whether a backend is callable without the caller
32+
// having to declare it a second time.
33+
isCallable(id: string): boolean {
34+
return this.#options.callableBackendIds.has(id);
35+
}
36+
2937
exec(source: string): Promise<WorkspaceRuntimeExecHandle<undefined>>;
3038
exec(
3139
source: string,
@@ -41,7 +49,7 @@ export class WorkspaceRuntime {
4149
): Promise<WorkspaceRuntimeExecHandle<E>> {
4250
if (options.id !== undefined) assertExecutionId(options.id);
4351
const backend = this.#backend(options.backend);
44-
if (options.input !== undefined && !this.#options.callableBackendIds.has(backend)) {
52+
if (options.input !== undefined && !this.isCallable(backend)) {
4553
throw new Error(
4654
`Backend ${JSON.stringify(backend)} is not callable; it does not accept structured input.`,
4755
);

packages/computer/src/tools/ai.test.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -552,14 +552,15 @@ describe("createAITools callable exec", () => {
552552
}),
553553
};
554554
},
555+
isCallable: (id: string) => id === "js",
555556
},
556557
};
557558
const tools = createAITools({
558559
workspace,
559560
shell: {
560561
defaultBackend: "js",
561562
backends: {
562-
js: { description: "JavaScript module runtime", callable: true },
563+
js: { description: "JavaScript module runtime" },
563564
},
564565
},
565566
});
@@ -597,13 +598,14 @@ describe("createAITools callable exec", () => {
597598
result: async () => ({ exitCode: 0, stdout: "ok", stderr: "" }),
598599
};
599600
},
601+
isCallable: (id: string) => id === "js",
600602
},
601603
};
602604
const tools = createAITools({
603605
workspace,
604606
shell: {
605607
defaultBackend: "js",
606-
backends: { js: { description: "JavaScript module runtime", callable: true } },
608+
backends: { js: { description: "JavaScript module runtime" } },
607609
},
608610
});
609611

@@ -620,6 +622,7 @@ describe("createAITools callable exec", () => {
620622
called = true;
621623
return { result: async () => ({ exitCode: 0, stdout: "", stderr: "" }) };
622624
},
625+
isCallable: (id: string) => id === "js",
623626
},
624627
};
625628
const tools = createAITools({
@@ -628,7 +631,7 @@ describe("createAITools callable exec", () => {
628631
defaultBackend: "shell",
629632
backends: {
630633
shell: { description: "fast shell" },
631-
js: { description: "JavaScript module runtime", callable: true },
634+
js: { description: "JavaScript module runtime" },
632635
},
633636
},
634637
});
@@ -674,13 +677,14 @@ describe("createAITools callable exec", () => {
674677
async exec() {
675678
throw new Error("not used");
676679
},
680+
isCallable: (id: string) => id === "js",
677681
},
678682
};
679683
const tools = createAITools({
680684
workspace,
681685
shell: {
682686
defaultBackend: "js",
683-
backends: { js: { description: "JavaScript module runtime", callable: true } },
687+
backends: { js: { description: "JavaScript module runtime" } },
684688
},
685689
});
686690

@@ -749,13 +753,14 @@ describe("createAITools exec streaming", () => {
749753
{ name: "exit", value: 0 },
750754
]);
751755
},
756+
isCallable: (id: string) => id === "js",
752757
},
753758
};
754759
const tools = createAITools({
755760
workspace,
756761
shell: {
757762
defaultBackend: "js",
758-
backends: { js: { description: "JavaScript module runtime", callable: true } },
763+
backends: { js: { description: "JavaScript module runtime" } },
759764
},
760765
});
761766

@@ -784,13 +789,14 @@ describe("createAITools exec streaming", () => {
784789
{ name: "exit", value: 0, result: { ok: true } },
785790
]);
786791
},
792+
isCallable: (id: string) => id === "js",
787793
},
788794
};
789795
const tools = createAITools({
790796
workspace,
791797
shell: {
792798
defaultBackend: "js",
793-
backends: { js: { description: "JavaScript module runtime", callable: true } },
799+
backends: { js: { description: "JavaScript module runtime" } },
794800
},
795801
});
796802

packages/computer/src/tools/exec.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,16 @@ export interface ExecWorkspaceLike {
4040
input?: unknown;
4141
},
4242
): Promise<ExecRuntimeHandle>;
43+
// Whether a backend accepts a structured `input` value and returns
44+
// a structured result. The tool asks this to know which backends
45+
// are callable; the runtime derives it from each backend's
46+
// `callable` flag. Omit when no backend is callable.
47+
isCallable?(id: string): boolean;
4348
};
4449
}
4550

4651
export interface ExecBackendDescription {
4752
description: string;
48-
// Whether the backend accepts a structured `input` value and
49-
// returns a structured `result` value. When false or omitted the
50-
// tool rejects `input` for this backend before touching the wire.
51-
callable?: boolean;
5253
}
5354

5455
export interface ExecToolOptions {
@@ -97,9 +98,8 @@ export function createExecTool(options: ExecToolOptions): Tool<
9798
);
9899
}
99100

100-
const callableBackendIds = new Set(
101-
backendIds.filter((id) => options.backends[id].callable === true),
102-
);
101+
const isCallable = options.workspace.runtime.isCallable?.bind(options.workspace.runtime);
102+
const callableBackendIds = new Set(backendIds.filter((id) => isCallable?.(id) === true));
103103
const backendGuidance = backendIds
104104
.map((id) => {
105105
const suffix = callableBackendIds.has(id) ? " (callable)" : "";

0 commit comments

Comments
 (0)