diff --git a/packages/codemode/README.md b/packages/codemode/README.md index 54d31b4e31..0be6d769c7 100644 --- a/packages/codemode/README.md +++ b/packages/codemode/README.md @@ -60,7 +60,7 @@ const result = `) ``` -`result` is always an `ExecuteResult`. Program, validation, limit, and tool failures are returned as diagnostics rather than failing the Effect. Host interruption remains interruption. +`result` is always a `CodeMode.Result`. Program, validation, limit, and tool failures are returned as diagnostics rather than failing the Effect. Host interruption remains interruption. Successful result values are JSON-safe data. A program that returns `undefined`, including by reaching the end without `return`, produces `null`; nested `undefined` values are normalized to `null` as well. @@ -83,6 +83,8 @@ const tool = Tool.make({ The description and schemas are part of the model-visible tool contract. Keep descriptions concrete and put authorization in `run` or in the service it calls. +Public tool types are grouped under the same namespace: `Tool.Definition`, `Tool.Options`, `Tool.SchemaType`, and `Tool.JsonSchema`. + ### `CodeMode.execute` Use `CodeMode.execute` for a single execution: @@ -113,30 +115,32 @@ const runtime = CodeMode.make({ runtime.catalog() // structured tool descriptions runtime.instructions() // model-facing syntax and tool guide -runtime.execute(source) // ExecuteResult +runtime.execute(source) // CodeMode.Result ``` -`CodeMode.Input` and `CodeMode.Result` are Effect schemas for the execution request and result. Hosts can combine them with `runtime.instructions()` and `runtime.execute()` when constructing a framework-specific agent tool. +`CodeMode.Input`, `CodeMode.Result`, `CodeMode.Success`, `CodeMode.Failure`, `CodeMode.Diagnostic`, and `CodeMode.DiagnosticKind` are both Effect schemas and their inferred TypeScript types. Hosts can combine `CodeMode.Input` and `CodeMode.Result` with `runtime.instructions()` and `runtime.execute()` when constructing a framework-specific agent tool. + +All other CodeMode types use the same namespace: `CodeMode.Options`, `CodeMode.ExecuteOptions`, `CodeMode.Runtime`, `CodeMode.ExecutionLimits`, `CodeMode.DiscoveryOptions`, `CodeMode.DataValue`, `CodeMode.ToolDescription`, and the `CodeMode.ToolCall*` observation types. ### Results ```ts -type ExecuteResult = ExecuteSuccess | ExecuteFailure +type Result = Success | Failure -interface ExecuteSuccess { +interface Success { readonly ok: true - readonly value: Schema.Json + readonly value: CodeMode.DataValue readonly logs?: ReadonlyArray readonly truncated?: boolean - readonly toolCalls: ReadonlyArray + readonly toolCalls: ReadonlyArray } -interface ExecuteFailure { +interface Failure { readonly ok: false - readonly error: Diagnostic + readonly error: CodeMode.Diagnostic readonly logs?: ReadonlyArray readonly truncated?: boolean - readonly toolCalls: ReadonlyArray + readonly toolCalls: ReadonlyArray } ``` @@ -300,7 +304,7 @@ import { toolError } from "@opencode-ai/codemode" run: ({ id }) => (authorized(id) ? loadOrder(id) : Effect.fail(toolError("Order is unavailable"))) ``` -Only the supplied message is model-visible. The optional cause is never returned in `ExecuteResult`; hosts should perform any required internal logging before crossing this boundary. +Only the supplied message is model-visible. The optional cause is never returned in `CodeMode.Result`; hosts should perform any required internal logging before crossing this boundary. ## Authority Boundary @@ -332,7 +336,7 @@ The public contract is guided by these equivalences: - A tool implementation is not invoked unless its input has decoded successfully. - A tool result is not visible to the program unless its output has decoded and crossed the plain-data boundary successfully. - Unknown host failures do not become model-visible diagnostics; `ToolError` is the explicit safe-message channel. -- Host interruption remains interruption rather than an `ExecuteFailure`. +- Host interruption remains interruption rather than a `CodeMode.Failure`. ## Non-Goals diff --git a/packages/codemode/codemode.md b/packages/codemode/codemode.md index 2ae45c56bf..732c565b72 100644 --- a/packages/codemode/codemode.md +++ b/packages/codemode/codemode.md @@ -222,7 +222,7 @@ wave; both packages typecheck clean. result is exposed as-is. Discrimination via `Schema.isSchema`. New helpers exported from `tool.ts`: `inputTypeScript`/`outputTypeScript`/`decodeInput`/`decodeOutput`/ `jsonSchemaToTypeScript`; `tool-runtime.ts` consumes them (no direct `Schema.*` use there - anymore). Types `JsonSchema`/`ToolSchema` exported from the index. Note: an empty + anymore). Types `Tool.JsonSchema`/`Tool.SchemaType` exported from the index. Note: an empty `Schema.Struct({})` renders as `{ } | Array` (effect's JSON Schema emission) - cosmetic, fixed in Wave 4. - **`output.*` API deleted**: `OutputItem`(+Schema), result `output` fields, the `output` @@ -237,7 +237,7 @@ wave; both packages typecheck clean. so failures are typed and observable). `message` is the model-safe failure message (`ToolError`/`ToolRuntimeError` message, else "Tool execution failed"). Interrupted calls fire no end event (timeout kills the whole execution anyway). -- **Limits collapse**: public `ExecutionLimits` = `{ timeoutMs?, maxToolCalls?, +- **Limits collapse**: public `CodeMode.ExecutionLimits` = `{ timeoutMs?, maxToolCalls?, maxOutputBytes? }` (defaults 10_000 / 100 / 32_000). This wave kept the other knobs as internal defaults reachable through an `@internal` `InternalExecutionLimits` type; Fix 5 later deleted that type and the internal limit system entirely. @@ -313,7 +313,7 @@ real MCP config. Package still 101 tests / 0 fail; opencode adapter suites still packages typecheck clean. - **Budgeted catalog** (`discoveryPlan` in `tool-runtime.ts`): the all-or-nothing - inline/search modes are gone - `DiscoveryMode` deleted, `DiscoveryOptions` is just + inline/search modes are gone - `DiscoveryMode` deleted, `CodeMode.DiscoveryOptions` is just `{ maxInlineCatalogBytes? }` (default 16,000 UTF-8 bytes; later converted to `maxInlineCatalogTokens`, default 4,000 estimated tokens - see Post-wave fixes). Port of the old opencode @@ -542,7 +542,7 @@ budget; namespaces must always be present): - `src/token.ts` added: copy of `@opencode-ai/core/util/token` (`round(chars / 4)`), so the package stays dependency-free; keep in sync if the core heuristic changes. -- `DiscoveryOptions.maxInlineCatalogBytes` -> `maxInlineCatalogTokens` (default 4,000 +- `CodeMode.DiscoveryOptions.maxInlineCatalogBytes` -> `maxInlineCatalogTokens` (default 4,000 estimated tokens ~ the old 16,000 bytes at 4 chars/token - behavior parity, not a size reduction). `discoveryPlan` charges `estimate(catalogLine(tool))` per line; cheapest-first - stop-on-first-miss unchanged at the time (stop-on-first-miss replaced by round-robin in @@ -558,7 +558,7 @@ budget; namespaces must always be present): **Fix 5 - internal limits removed** (user direction: only the three PUBLIC limits survive as configurable knobs; the internal limit system dies): -- `ExecutionLimits` (`timeoutMs` 10_000 / `maxToolCalls` 100 / `maxOutputBytes` 32_000 at +- `CodeMode.ExecutionLimits` (`timeoutMs` 10_000 / `maxToolCalls` 100 / `maxOutputBytes` 32_000 at the time; Fix 6 later removed the first two defaults. Same validation: safe integers, timeoutMs >= 1, others >= 0, RangeError otherwise) is now the ENTIRE limit surface - exactly the shape section 2's original locked spec named. @@ -602,7 +602,7 @@ configurable knobs; the internal limit system dies): enumeration operation-budget, codemode maxDataBytes/maxSourceBytes/maxOperations/ maxConcurrency-RangeError assertions, and the adapter's runaway-loop-via-operation-limit test - superseded by the package timeout regression test); rewrote the helpers that used - `InternalExecutionLimits` as a convenience to plain `ExecutionLimits` + `InternalExecutionLimits` as a convenience to plain `CodeMode.ExecutionLimits` (promise/enumeration/stdlib run helpers). Package suite: 154 pass / 0 fail; adapter suites: 34 + 16. @@ -849,7 +849,7 @@ section 4 outer-truncation item the OPPOSITE way from "kill the outer one"): that relied on the old default now asserts the oversized result reaches the shared wrapper un-truncated. Suites: 210 + 50, tsgo clean both. -**Docs polish** (post-API-review): stale `DiscoveryOptions` JSDoc fixed (claimed default +**Docs polish** (post-API-review): stale `CodeMode.DiscoveryOptions` JSDoc fixed (claimed default 4,000 and alphabetical cheapest-first - now 2,000 and round-robin, matching Fix 8/9 reality) and the README's incorrect "`effect` as a peer dependency" line corrected (`effect` is a regular dependency; hosts depend on it themselves because the API surface is Effect-typed). diff --git a/packages/codemode/src/codemode.ts b/packages/codemode/src/codemode.ts index 6aa030e521..b207efe46a 100644 --- a/packages/codemode/src/codemode.ts +++ b/packages/codemode/src/codemode.ts @@ -19,8 +19,7 @@ import { ToolError } from "./tool-error.js" import { isSandboxValue, SandboxDate, SandboxMap, SandboxPromise, SandboxRegExp, SandboxSet } from "./values.js" /** A tool call admitted during an execution. */ -export type { ToolCall, ToolCallStarted, ToolDescription } from "./tool-runtime.js" -export { ToolError, toolError } from "./tool-error.js" +export type { ToolCall, ToolCallEnded, ToolCallHooks, ToolCallStarted, ToolDescription } from "./tool-runtime.js" /** Resource budgets enforced independently during each CodeMode program execution. */ export type ExecutionLimits = { @@ -74,50 +73,20 @@ export type ExecuteOptions = {}> = { onToolCallEnd?: (call: ToolRuntime.ToolCallEnded) => Effect.Effect> } -/** A normalized program diagnostic safe to return across an agent tool boundary. */ -export type Diagnostic = { - readonly kind: DiagnosticKind - readonly message: string - readonly location?: { readonly line: number; readonly column: number } - readonly suggestions?: ReadonlyArray -} - /** A JSON value that can cross the confined interpreter boundary. */ export type DataValue = Schema.Json -/** Successful execution after the result has crossed the plain-data boundary. */ -export type ExecuteSuccess = { - readonly ok: true - readonly value: DataValue - readonly logs?: ReadonlyArray - /** Present when the value or logs were truncated to fit `maxOutputBytes`. */ - readonly truncated?: boolean - readonly toolCalls: ReadonlyArray -} - -/** Failed execution with calls admitted before the diagnostic was produced. */ -export type ExecuteFailure = { - readonly ok: false - readonly error: Diagnostic - readonly logs?: ReadonlyArray - /** Present when the logs were truncated to fit `maxOutputBytes`. */ - readonly truncated?: boolean - readonly toolCalls: ReadonlyArray -} - -/** Result of executing a CodeMode program. Program failures are data, not Effect failures. */ -export type ExecuteResult = ExecuteSuccess | ExecuteFailure - /** Configuration shared by `CodeMode.make` and `CodeMode.execute`. */ -export type CodeModeOptions = {}> = Omit, "code"> & { +export type Options = {}> = Omit, "code"> & { /** Progressive-disclosure configuration for the agent-facing tool catalog. */ readonly discovery?: DiscoveryOptions } -/** Schema for a CodeMode execution request. */ -const Input = Schema.Struct({ code: Schema.String }) +/** Schema for a host tool input containing CodeMode source. */ +export const Input = Schema.Struct({ code: Schema.String }) +export type Input = typeof Input.Type -const DiagnosticKindSchema = Schema.Literals([ +export const DiagnosticKind = Schema.Literals([ "ParseError", "UnsupportedSyntax", "UnknownTool", @@ -129,38 +98,52 @@ const DiagnosticKindSchema = Schema.Literals([ "ToolFailure", "ExecutionFailure", ]) +/** Stable categories produced by program, schema, tool, and limit failures. */ +export type DiagnosticKind = typeof DiagnosticKind.Type + +export const Diagnostic = Schema.Struct({ + kind: DiagnosticKind, + message: Schema.String, + location: Schema.optionalKey(Schema.Struct({ line: Schema.Number, column: Schema.Number })), + suggestions: Schema.optionalKey(Schema.Array(Schema.String)), +}) +/** A normalized program diagnostic safe to return across an agent tool boundary. */ +export type Diagnostic = typeof Diagnostic.Type + +const ToolCallSchema = Schema.Struct({ name: Schema.String }) +export const Success = Schema.Struct({ + ok: Schema.Literal(true), + value: Schema.Json, + logs: Schema.optionalKey(Schema.Array(Schema.String)), + truncated: Schema.optionalKey(Schema.Boolean), + toolCalls: Schema.Array(ToolCallSchema), +}) +/** Successful execution after the result has crossed the plain-data boundary. */ +export type Success = typeof Success.Type + +export const Failure = Schema.Struct({ + ok: Schema.Literal(false), + error: Diagnostic, + logs: Schema.optionalKey(Schema.Array(Schema.String)), + truncated: Schema.optionalKey(Schema.Boolean), + toolCalls: Schema.Array(ToolCallSchema), +}) +/** Failed execution with calls admitted before the diagnostic was produced. */ +export type Failure = typeof Failure.Type /** Schema for the structured success or diagnostic returned by CodeMode execution. */ -const Result = Schema.Union([ - Schema.Struct({ - ok: Schema.Literal(true), - value: Schema.Json, - logs: Schema.optionalKey(Schema.Array(Schema.String)), - truncated: Schema.optionalKey(Schema.Boolean), - toolCalls: Schema.Array(Schema.Struct({ name: Schema.String })), - }), - Schema.Struct({ - ok: Schema.Literal(false), - error: Schema.Struct({ - kind: DiagnosticKindSchema, - message: Schema.String, - location: Schema.optionalKey(Schema.Struct({ line: Schema.Number, column: Schema.Number })), - suggestions: Schema.optionalKey(Schema.Array(Schema.String)), - }), - logs: Schema.optionalKey(Schema.Array(Schema.String)), - truncated: Schema.optionalKey(Schema.Boolean), - toolCalls: Schema.Array(Schema.Struct({ name: Schema.String })), - }), -]) +export const Result = Schema.Union([Success, Failure]) +/** Result of executing a CodeMode program. Program failures are data, not Effect failures. */ +export type Result = typeof Result.Type /** Reusable confined runtime over one explicit tool tree. */ -export type CodeModeRuntime = { +export type Runtime = { /** Lists schema-described tool paths provided by the host. */ readonly catalog: () => ReadonlyArray /** Builds model-facing syntax guidance and visible tool signatures. */ readonly instructions: () => string /** Executes a program using this runtime's configured host tools. */ - readonly execute: (code: string) => Effect.Effect + readonly execute: (code: string) => Effect.Effect } type SourcePosition = { @@ -275,19 +258,6 @@ const errorBrandName = (value: unknown): string | undefined => ? ((value as Record)[ErrorBrand] as string | undefined) : undefined -/** Stable categories produced by program, schema, tool, and limit failures. */ -export type DiagnosticKind = - | "ParseError" - | "UnsupportedSyntax" - | "UnknownTool" - | "InvalidToolInput" - | "InvalidToolOutput" - | "InvalidDataValue" - | "ToolCallLimitExceeded" - | "TimeoutExceeded" - | "ToolFailure" - | "ExecutionFailure" - const arrayMethods = new Set([ "map", "filter", @@ -3943,7 +3913,7 @@ const executeWithLimits = >( options: ExecuteOptions, limits: ResolvedExecutionLimits, searchIndex: ToolRuntime.DiscoveryPlan["searchIndex"], -): Effect.Effect> => { +): Effect.Effect> => { const hooks = { ...(options.onToolCallStart === undefined ? {} : { onToolCallStart: options.onToolCallStart }), ...(options.onToolCallEnd === undefined ? {} : { onToolCallEnd: options.onToolCallEnd }), @@ -3975,7 +3945,7 @@ const executeWithLimits = >( value: result, ...logged(), toolCalls: tools.calls, - } satisfies ExecuteResult + } satisfies Result }).pipe((program) => { const timeoutMs = limits.timeoutMs if (timeoutMs === undefined) return program @@ -3988,7 +3958,7 @@ const executeWithLimits = >( error: { kind: "TimeoutExceeded", message: `Execution timed out after ${timeoutMs}ms.` }, ...logged(), toolCalls: tools.calls, - } satisfies ExecuteResult), + } satisfies Result), }), ) }) @@ -4002,7 +3972,7 @@ const executeWithLimits = >( error: normalizeError(Cause.squash(cause)), ...logged(), toolCalls: tools.calls, - } satisfies ExecuteResult), + } satisfies Result), ), Effect.map((result) => (limits.maxOutputBytes === undefined ? result : boundOutput(result, limits.maxOutputBytes))), ) @@ -4026,7 +3996,7 @@ const utf8Truncate = (value: string, maxBytes: number): string => { * fails the execution; `truncated: true` marks affected results. Only runs when the host set * `maxOutputBytes` - with the limit absent, output passes through unbounded. */ -const boundOutput = (result: ExecuteResult, maxOutputBytes: number): ExecuteResult => { +const boundOutput = (result: Result, maxOutputBytes: number): Result => { let truncated = false let value: DataValue = null @@ -4068,7 +4038,7 @@ const boundOutput = (result: ExecuteResult, maxOutputBytes: number): ExecuteResu export const execute = >( options: ExecuteOptions, -): Effect.Effect> => { +): Effect.Effect> => { const tools = (options.tools ?? {}) as HostTools> ToolRuntime.assertValidTools(tools) return executeWithLimits(options, resolveExecutionLimits(options.limits), ToolRuntime.searchIndex(tools)) @@ -4086,8 +4056,8 @@ export const execute = >( * ``` */ export const make = = {}>( - options: CodeModeOptions = {} as CodeModeOptions, -): CodeModeRuntime> => { + options: Options = {} as Options, +): Runtime> => { const tools = (options.tools ?? {}) as HostTools> ToolRuntime.assertValidTools(tools) const limits = resolveExecutionLimits(options.limits) @@ -4102,6 +4072,3 @@ export const make = = {}>( execute: executeProgram, } } - -/** Constructors for one-shot and reusable CodeMode execution. */ -export const CodeMode = { Input, Result, make, execute } diff --git a/packages/codemode/src/index.ts b/packages/codemode/src/index.ts index 9a21e9f640..3fdfcd45e7 100644 --- a/packages/codemode/src/index.ts +++ b/packages/codemode/src/index.ts @@ -1,21 +1,4 @@ -export { ToolError, CodeMode, toolError } from "./codemode.js" -export { Tool } from "./tool.js" +export * as CodeMode from "./codemode.js" +export * as Tool from "./tool-api.js" export * as OpenAPI from "./openapi/index.js" -export type { Definition as ToolDefinition, JsonSchema, ToolSchema } from "./tool.js" -export type { ToolCallEnded, ToolCallHooks } from "./tool-runtime.js" -export type { - CodeModeOptions, - CodeModeRuntime, - DataValue, - Diagnostic, - DiagnosticKind, - DiscoveryOptions, - ExecuteFailure, - ExecuteOptions, - ExecuteResult, - ExecuteSuccess, - ExecutionLimits, - ToolCall, - ToolCallStarted, - ToolDescription, -} from "./codemode.js" +export { ToolError, toolError } from "./tool-error.js" diff --git a/packages/codemode/src/tool-api.ts b/packages/codemode/src/tool-api.ts new file mode 100644 index 0000000000..96ce68d046 --- /dev/null +++ b/packages/codemode/src/tool-api.ts @@ -0,0 +1,2 @@ +export { isDefinition, make } from "./tool.js" +export type { Definition, JsonSchema, Options, ToolSchema as SchemaType } from "./tool.js" diff --git a/packages/codemode/test/codemode.test.ts b/packages/codemode/test/codemode.test.ts index 9db7739296..087678c778 100644 --- a/packages/codemode/test/codemode.test.ts +++ b/packages/codemode/test/codemode.test.ts @@ -1,9 +1,8 @@ import { describe, expect, test } from "bun:test" import { Cause, Effect, Schema } from "effect" -import { CodeMode, Tool, toolError, type ExecutionLimits } from "../src/index.js" -import type { Definition } from "../src/tool.js" +import { CodeMode, Tool, toolError } from "../src/index.js" -const run = (tool: Definition) => +const run = (tool: Tool.Definition) => Effect.runPromise(CodeMode.make({ tools: { host: { call: tool } } }).execute("return await tools.host.call({})")) class UnsafeHostError extends Schema.TaggedErrorClass()("UnsafeHostError", { @@ -349,7 +348,7 @@ describe("CodeMode output budget", () => { }) test("truncates an oversized result value with a marker instead of failing", async () => { - const limits: ExecutionLimits = { maxOutputBytes: 40 } + const limits: CodeMode.ExecutionLimits = { maxOutputBytes: 40 } const result = await Effect.runPromise( CodeMode.execute({ code: `return { data: "${"x".repeat(200)}" }`, @@ -368,7 +367,7 @@ describe("CodeMode output budget", () => { }) test("keeps leading logs within the remaining budget and marks the cut", async () => { - const limits: ExecutionLimits = { maxOutputBytes: 40 } + const limits: CodeMode.ExecutionLimits = { maxOutputBytes: 40 } const result = await Effect.runPromise( CodeMode.execute({ code: ` @@ -502,7 +501,8 @@ describe("CodeMode public contract", () => { ]) expect(reusable).toStrictEqual(oneShot) - expect(Schema.decodeUnknownSync(CodeMode.Input)({ code: source })).toStrictEqual({ code: source }) + const input: CodeMode.Input = { code: source } + expect(Schema.decodeUnknownSync(CodeMode.Input)(input)).toStrictEqual(input) expect(Schema.decodeUnknownSync(CodeMode.Result)(JSON.parse(JSON.stringify(reusable)))).toStrictEqual(reusable) }) diff --git a/packages/codemode/test/parity.test.ts b/packages/codemode/test/parity.test.ts index e5c1d83822..dfa8583183 100644 --- a/packages/codemode/test/parity.test.ts +++ b/packages/codemode/test/parity.test.ts @@ -3,7 +3,7 @@ import { Effect } from "effect" import { CodeMode } from "../src/index.js" import { ToolRuntime } from "../src/tool-runtime.js" -// Runs a CodeMode program with no host tools and returns the ExecuteResult. These tests pin the +// Runs a CodeMode program with no host tools and returns the CodeMode.Result. These tests pin the // JS-parity behaviors for the "99% of ordinary defensive JavaScript just works" goal: cases where // a strict interpreter would throw but idiomatic JS yields undefined / succeeds. // diff --git a/packages/codemode/test/promise.test.ts b/packages/codemode/test/promise.test.ts index 952b2fdc50..545d463abf 100644 --- a/packages/codemode/test/promise.test.ts +++ b/packages/codemode/test/promise.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test" import { Effect, Schema } from "effect" -import { CodeMode, Tool, toolError, type ExecuteResult, type ExecutionLimits } from "../src/index.js" +import { CodeMode, Tool, toolError } from "../src/index.js" // Wave 5 acceptance suite: first-class promise values. Un-awaited tool calls start eagerly on // supervised fibers, `await` settles them, and Promise.all/allSettled/race/resolve/reject are @@ -48,7 +48,10 @@ const failingTool = Tool.make({ run: () => Effect.fail(toolError("Lookup refused")), }) -const run = (code: string, options: { trace?: Trace; limits?: ExecutionLimits } = {}): Promise => { +const run = ( + code: string, + options: { trace?: Trace; limits?: CodeMode.ExecutionLimits } = {}, +): Promise => { const trace = options.trace ?? makeTrace() return Effect.runPromise( CodeMode.execute({ @@ -59,13 +62,13 @@ const run = (code: string, options: { trace?: Trace; limits?: ExecutionLimits } ) } -const value = async (code: string, options: { trace?: Trace; limits?: ExecutionLimits } = {}) => { +const value = async (code: string, options: { trace?: Trace; limits?: CodeMode.ExecutionLimits } = {}) => { const result = await run(code, options) if (!result.ok) throw new Error(`expected success, got ${result.error.kind}: ${result.error.message}`) return result.value } -const error = async (code: string, options: { trace?: Trace; limits?: ExecutionLimits } = {}) => { +const error = async (code: string, options: { trace?: Trace; limits?: CodeMode.ExecutionLimits } = {}) => { const result = await run(code, options) if (result.ok) throw new Error(`expected failure, got value ${JSON.stringify(result.value)}`) return result.error diff --git a/packages/opencode/src/tool/code-mode.ts b/packages/opencode/src/tool/code-mode.ts index a97dee93ef..0f566a49ad 100644 --- a/packages/opencode/src/tool/code-mode.ts +++ b/packages/opencode/src/tool/code-mode.ts @@ -1,14 +1,7 @@ import * as Tool from "./tool" import { CallToolResultSchema, type CallToolResult } from "@modelcontextprotocol/sdk/types.js" import { Cause, Effect, Schema } from "effect" -import { - CodeMode, - Tool as SandboxTool, - toolError, - type ExecuteResult, - type JsonSchema, - type ToolDefinition, -} from "@opencode-ai/codemode" +import { CodeMode, Tool as SandboxTool, toolError } from "@opencode-ai/codemode" import { MCP } from "@/mcp" import { McpCatalog } from "@/mcp/catalog" import { Agent } from "@/agent/agent" @@ -132,13 +125,13 @@ function projectMcpResult(result: CallToolResult, collect: (attachment: Attachme type Run = (input: unknown) => Effect.Effect function toolTree(catalog: readonly CatalogEntry[], run: (entry: CatalogEntry) => Run) { - const tree: Record> = {} + const tree: Record> = {} for (const entry of catalog) { const namespace = (tree[entry.server] ??= {}) namespace[entry.local] = SandboxTool.make({ description: entry.tool.def.description ?? "", - input: entry.tool.def.inputSchema as JsonSchema, - output: entry.tool.def.outputSchema as JsonSchema | undefined, + input: entry.tool.def.inputSchema as SandboxTool.JsonSchema, + output: entry.tool.def.outputSchema as SandboxTool.JsonSchema | undefined, run: run(entry), }) } @@ -279,7 +272,7 @@ export const CodeModeTool = Tool.define( ctx.abort.addEventListener("abort", handler, { once: true }) return Effect.sync(() => ctx.abort.removeEventListener("abort", handler)) }) - const cancelled = (): ExecuteResult => ({ + const cancelled = (): CodeMode.Result => ({ ok: false, error: { kind: "ExecutionFailure", message: "Execution cancelled." }, toolCalls: calls.map((call) => ({ name: call.tool })),