Files
+28 4c9a4309ce chore: merge dev into v2 (#37996)
Co-authored-by: opencode <opencode@sst.dev>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: Aiden Cline <rekram1-node@users.noreply.github.com>
Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com>
Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com>
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com>
Co-authored-by: Victor Navarro <vn4varro@gmail.com>
Co-authored-by: Aarav Sareen <96787824+arvsrn@users.noreply.github.com>
Co-authored-by: Frank <frank@anoma.ly>
Co-authored-by: Dax Raad <d@ironbay.co>
Co-authored-by: Dax <mail@thdxr.com>
Co-authored-by: Nabs <nabil@instafork.com>
Co-authored-by: usrnk1 <7547651+usrnk1@users.noreply.github.com>
Co-authored-by: Brendan Allan <git@brendonovich.dev>
Co-authored-by: Vladimir Glafirov <vglafirov@gitlab.com>
Co-authored-by: AidenGeunGeun <eastlandwyvern@gmail.com>
Co-authored-by: Mark <geraint0923@users.noreply.github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
Co-authored-by: David Hill <1879069+iamdavidhill@users.noreply.github.com>
Co-authored-by: Jay <air@live.ca>
Co-authored-by: Jay <53023+jayair@users.noreply.github.com>
Co-authored-by: BB84 <110078428+BB-84C@users.noreply.github.com>
Co-authored-by: Dustin Deus <deusdustin@gmail.com>
Co-authored-by: Jack <jack@anoma.ly>
Co-authored-by: Sebastian <hasta84@gmail.com>
Co-authored-by: Jérôme Benoit <jerome.benoit@sap.com>
Co-authored-by: Test User <test@test.com>
Co-authored-by: Simon Klee <hello@simonklee.dk>
Co-authored-by: Rahul A Mistry <149420892+ProdigyRahul@users.noreply.github.com>
Co-authored-by: Qiping Li <liqiping1991@gmail.com>
Co-authored-by: liqiping <liqiping@msh.team>
Co-authored-by: OpeOginni <107570612+OpeOginni@users.noreply.github.com>
Co-authored-by: Matthias Reso <13337103+mreso@users.noreply.github.com>
Co-authored-by: tobwen <1864057+tobwen@users.noreply.github.com>
2026-07-20 15:20:59 -05:00
..
2026-07-20 15:20:59 -05:00
2026-07-10 15:11:29 -05:00

@opencode-ai/codemode

This is our take on code mode: a lightweight, pure interpreter for a JavaScript-like language built around calling tools. It supports familiar JavaScript syntax with a few key differences and limitations. See the interpreter support checklist for more details.

Rather than trying to sandbox arbitrary JavaScript, CodeMode only runs the language features we implement. Programs cannot directly access the network, filesystem, processes, or application APIs. They can interact with the outside world only through tools provided by the host, which can also limit execution time, tool calls, output size, and data.

The idea of code mode was originally introduced by Cloudflare. See their post to learn more about the concept and their isolate-based approach.

How it differs from JavaScript

  • Only supported APIs are available. Programs can use the provided tools and supported JavaScript built-ins. APIs such as fetch, timers, process, filesystem access, imports, and modules are unavailable.
  • Unfinished work is interrupted. Tool calls and async functions start when called. When the program finishes, anything still running is interrupted. Unhandled rejections from un-awaited promises are returned as warnings.
  • REPL-style results. Without an explicit return, the final top-level expression becomes the result. undefined becomes null.

Unsupported syntax returns an UnsupportedSyntax diagnostic with a source location. Current gaps are tracked in the interpreter support checklist.

Quick Start

import { CodeMode, Tool } from "@opencode-ai/codemode"
import { Effect, Schema } from "effect"

const lookupOrder = Tool.make({
  description: "Look up an order by ID",
  input: Schema.Struct({ id: Schema.String }),
  output: Schema.Struct({ id: Schema.String, status: Schema.String }),
  run: ({ id }) => Effect.succeed({ id, status: "open" }),
})

const runtime = CodeMode.make({
  tools: { orders: { lookup: lookupOrder } },
})

const result = await Effect.runPromise(
  runtime.execute(`
    const order = await tools.orders.lookup({ id: "order_42" })
    return { id: order.id, needsAttention: order.status !== "complete" }
  `),
)

result is always a CodeMode.Result.

API

Tool.make

input and output accept either an Effect Schema or a render-only JSON Schema document. Effect Schema input is decoded before run; Effect Schema output is decoded and safely copied before the program sees it. JSON Schemas only shape the model-visible signature. Without output, the signature uses Promise<unknown>.

Descriptions and schemas are model-visible contracts. Authorization belongs in run.

Dots in tool names create namespaces: { "issues.list": tool } and { issues: { list: tool } } both expose tools.issues.list(...). Other characters use bracket notation, such as tools.context7["resolve-library-id"](...).

CodeMode.execute and CodeMode.make

CodeMode.execute({ ...options, code }) runs once. CodeMode.make(options) creates a reusable runtime:

const runtime = CodeMode.make({ tools, limits: { timeoutMs: 30_000 } })

runtime.catalog() // structured tool descriptions
runtime.instructions() // model-facing syntax and tool guide
runtime.execute(source) // Effect<CodeMode.Result, never, ToolServices>

The Effect environment is inferred from the supplied tools. onToolCallStart observes admitted calls with decoded input; onToolCallEnd observes settled outcomes and duration. Both hooks return Effects and must not fail.

OpenAPI tools

OpenAPI.fromSpec converts an OpenAPI 3.x document into one tool per supported operation. Dotted operationId values create namespaces:

const api = OpenAPI.fromSpec({ spec, auth: { resolve } })
const runtime = CodeMode.make({ tools: { opencode: api.tools } })

The synchronous result is { tools, skipped }. Operations with unsupported parameter encodings, request bodies without JSON content, WebSocket or SSE semantics, or binary responses are reported in skipped.

Authentication is resolved by the host and never shown to the model. Generated tools require HttpClient.HttpClient. Request signatures omit readOnly properties; response signatures omit writeOnly properties. These JSON Schemas shape model-visible signatures but do not filter runtime values: nested JSON body properties and decoded server responses pass through unchanged. See src/openapi/types.ts for option details.

Results

Every execution returns:

type Result =
  | {
      readonly ok: true
      readonly value: CodeMode.DataValue
      readonly warnings?: ReadonlyArray<CodeMode.Diagnostic>
      readonly logs?: ReadonlyArray<string>
      readonly truncated?: boolean
      readonly toolCalls: ReadonlyArray<CodeMode.ToolCall>
    }
  | {
      readonly ok: false
      readonly error: CodeMode.Diagnostic
      readonly logs?: ReadonlyArray<string>
      readonly truncated?: boolean
      readonly toolCalls: ReadonlyArray<CodeMode.ToolCall>
    }

value is JSON-safe. warnings are non-fatal diagnostics, logs contain program console output, and truncated indicates that retained output was cut by maxOutputBytes. toolCalls retains admitted calls in order, including after failure.

Diagnostic kinds:

Kind Meaning
ParseError Source is empty or cannot be parsed.
UnsupportedSyntax Parsed JavaScript is outside the supported subset.
UnknownTool The program referenced an unavailable tool.
InvalidToolInput Tool input failed schema decoding or safe-data copying.
InvalidToolOutput Tool output failed schema decoding or safe-data copying.
InvalidDataValue Program data violated the plain-data contract.
ToolCallLimitExceeded The program exceeded maxToolCalls.
TimeoutExceeded Execution timed out; as a warning, background work was interrupted after the program returned.
ToolFailure A tool refused or failed.
ExecutionFailure The program threw or another execution error occurred.
Truncated Warning only: additional warnings were omitted by maxOutputBytes.

Unknown host failures, defects, and invalid outputs are sanitized. toolError("safe message") explicitly exposes a safe refusal to the model; its optional cause remains private.

Discovery

Generated instructions contain a tool catalog with a default budget of 2,000 estimated tokens. Configure it with discovery: { catalogBudget }. Every namespace remains visible, and the instructions say whether the catalog is complete or partial.

The synchronous search(...) built-in is always available and advertised when the catalog is partial. It supports exact-path lookup, namespace-scoped search, empty-query browsing, and pagination, and returns callable paths with full signatures. Search counts toward maxToolCalls.

Execution Limits

Limit Default Controls
timeoutMs unlimited Total execution time.
maxToolCalls unlimited Admitted tool calls.
maxOutputBytes unlimited Retained result value and logs.

Execution limits have no default values.

Invalid limit configuration throws RangeError. Warnings receive a separate budget equal to maxOutputBytes. Truncation does not fail execution; an oversized value becomes a string with an in-band marker. Timeouts interrupt tool calls and busy loops, while a result returned before cleanup times out remains successful with a TimeoutExceeded warning. Tool-call concurrency is unrestricted. Boundary data is limited to 32 nested levels.