Browse Source

refactor(codemode): namespace public types (#35425)

Aiden Cline 1 month ago
parent
commit
be73f465df

+ 16 - 12
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<string>
   readonly truncated?: boolean
-  readonly toolCalls: ReadonlyArray<ToolCall>
+  readonly toolCalls: ReadonlyArray<CodeMode.ToolCall>
 }
 
-interface ExecuteFailure {
+interface Failure {
   readonly ok: false
-  readonly error: Diagnostic
+  readonly error: CodeMode.Diagnostic
   readonly logs?: ReadonlyArray<string>
   readonly truncated?: boolean
-  readonly toolCalls: ReadonlyArray<ToolCall>
+  readonly toolCalls: ReadonlyArray<CodeMode.ToolCall>
 }
 ```
 
@@ -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
 

+ 7 - 7
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<unknown>` (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).

+ 51 - 84
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<Tools extends Record<string, unknown> = {}> = {
   onToolCallEnd?: (call: ToolRuntime.ToolCallEnded) => Effect.Effect<void, never, Services<Tools>>
 }
 
-/** 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<string>
-}
-
 /** 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<string>
-  /** Present when the value or logs were truncated to fit `maxOutputBytes`. */
-  readonly truncated?: boolean
-  readonly toolCalls: ReadonlyArray<ToolCall>
-}
-
-/** Failed execution with calls admitted before the diagnostic was produced. */
-export type ExecuteFailure = {
-  readonly ok: false
-  readonly error: Diagnostic
-  readonly logs?: ReadonlyArray<string>
-  /** Present when the logs were truncated to fit `maxOutputBytes`. */
-  readonly truncated?: boolean
-  readonly toolCalls: ReadonlyArray<ToolCall>
-}
-
-/** 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<Tools extends Record<string, unknown> = {}> = Omit<ExecuteOptions<Tools>, "code"> & {
+export type Options<Tools extends Record<string, unknown> = {}> = Omit<ExecuteOptions<Tools>, "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<R = never> = {
+export type Runtime<R = never> = {
   /** Lists schema-described tool paths provided by the host. */
   readonly catalog: () => ReadonlyArray<ToolDescription>
   /** 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<ExecuteResult, never, R>
+  readonly execute: (code: string) => Effect.Effect<Result, never, R>
 }
 
 type SourcePosition = {
@@ -275,19 +258,6 @@ const errorBrandName = (value: unknown): string | undefined =>
     ? ((value as Record<PropertyKey, unknown>)[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 = <const Tools extends Record<string, unknown>>(
   options: ExecuteOptions<Tools>,
   limits: ResolvedExecutionLimits,
   searchIndex: ToolRuntime.DiscoveryPlan["searchIndex"],
-): Effect.Effect<ExecuteResult, never, Services<Tools>> => {
+): Effect.Effect<Result, never, Services<Tools>> => {
   const hooks = {
     ...(options.onToolCallStart === undefined ? {} : { onToolCallStart: options.onToolCallStart }),
     ...(options.onToolCallEnd === undefined ? {} : { onToolCallEnd: options.onToolCallEnd }),
@@ -3975,7 +3945,7 @@ const executeWithLimits = <const Tools extends Record<string, unknown>>(
       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 = <const Tools extends Record<string, unknown>>(
             error: { kind: "TimeoutExceeded", message: `Execution timed out after ${timeoutMs}ms.` },
             ...logged(),
             toolCalls: tools.calls,
-          } satisfies ExecuteResult),
+          } satisfies Result),
       }),
     )
   })
@@ -4002,7 +3972,7 @@ const executeWithLimits = <const Tools extends Record<string, unknown>>(
             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 = <const Tools extends Record<string, unknown>>(
   options: ExecuteOptions<Tools>,
-): Effect.Effect<ExecuteResult, never, Services<Tools>> => {
+): Effect.Effect<Result, never, Services<Tools>> => {
   const tools = (options.tools ?? {}) as HostTools<Services<Tools>>
   ToolRuntime.assertValidTools(tools)
   return executeWithLimits(options, resolveExecutionLimits(options.limits), ToolRuntime.searchIndex(tools))
@@ -4086,8 +4056,8 @@ export const execute = <const Tools extends Record<string, unknown>>(
  * ```
  */
 export const make = <const Tools extends Record<string, unknown> = {}>(
-  options: CodeModeOptions<Tools> = {} as CodeModeOptions<Tools>,
-): CodeModeRuntime<Services<Tools>> => {
+  options: Options<Tools> = {} as Options<Tools>,
+): Runtime<Services<Tools>> => {
   const tools = (options.tools ?? {}) as HostTools<Services<Tools>>
   ToolRuntime.assertValidTools(tools)
   const limits = resolveExecutionLimits(options.limits)
@@ -4102,6 +4072,3 @@ export const make = <const Tools extends Record<string, unknown> = {}>(
     execute: executeProgram,
   }
 }
-
-/** Constructors for one-shot and reusable CodeMode execution. */
-export const CodeMode = { Input, Result, make, execute }

+ 3 - 20
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"

+ 2 - 0
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"

+ 6 - 6
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<never>) =>
+const run = (tool: Tool.Definition<never>) =>
   Effect.runPromise(CodeMode.make({ tools: { host: { call: tool } } }).execute("return await tools.host.call({})"))
 
 class UnsafeHostError extends Schema.TaggedErrorClass<UnsafeHostError>()("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)
   })
 

+ 1 - 1
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.
 //

+ 7 - 4
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<ExecuteResult> => {
+const run = (
+  code: string,
+  options: { trace?: Trace; limits?: CodeMode.ExecutionLimits } = {},
+): Promise<CodeMode.Result> => {
   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

+ 5 - 12
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<unknown, unknown>
 
 function toolTree(catalog: readonly CatalogEntry[], run: (entry: CatalogEntry) => Run) {
-  const tree: Record<string, Record<string, ToolDefinition>> = {}
+  const tree: Record<string, Record<string, SandboxTool.Definition>> = {}
   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 })),