Jelajahi Sumber

chore: generate

opencode-agent[bot] 1 bulan lalu
induk
melakukan
83c638eaac

+ 36 - 35
packages/codemode/README.md

@@ -8,11 +8,11 @@ The package is currently private to this workspace. Its API is designed around t
 
 ```ts
 // One execution
-yield* CodeMode.execute({ tools, code })
+yield * CodeMode.execute({ tools, code })
 
 // A reusable runtime
 const runtime = CodeMode.make({ tools, limits })
-yield* runtime.execute(code)
+yield * runtime.execute(code)
 
 // One agent-facing code tool
 const codeTool = runtime.agentTool()
@@ -55,7 +55,9 @@ const runtime = CodeMode.make({
   },
 })
 
-const result = yield* runtime.execute(`
+const result =
+  yield *
+  runtime.execute(`
   const order = await tools.orders.lookup({ id: "order_42" })
   return { id: order.id, needsAttention: order.status !== "complete" }
 `)
@@ -72,8 +74,8 @@ Successful result values are JSON-safe data. A program that returns `undefined`,
 ```ts
 const tool = Tool.make({
   description,
-  input,   // Effect Schema (validating) or JSON Schema (render-only)
-  output,  // optional; same choice
+  input, // Effect Schema (validating) or JSON Schema (render-only)
+  output, // optional; same choice
   run,
 })
 ```
@@ -89,13 +91,15 @@ The description and schemas are part of the model-visible tool contract. Keep de
 Use `CodeMode.execute` for a single execution:
 
 ```ts
-const result = yield* CodeMode.execute({
-  tools: { orders: { lookup: lookupOrder } },
-  code: `return await tools.orders.lookup({ id: "order_42" })`,
-  limits: { maxToolCalls: 10 },
-  onToolCallStart: (call) => Effect.logDebug("CodeMode tool started", call),
-  onToolCallEnd: (call) => Effect.logDebug("CodeMode tool settled", call),
-})
+const result =
+  yield *
+  CodeMode.execute({
+    tools: { orders: { lookup: lookupOrder } },
+    code: `return await tools.orders.lookup({ id: "order_42" })`,
+    limits: { maxToolCalls: 10 },
+    onToolCallStart: (call) => Effect.logDebug("CodeMode tool started", call),
+    onToolCallEnd: (call) => Effect.logDebug("CodeMode tool settled", call),
+  })
 ```
 
 The Effect environment is inferred from the supplied tools. CodeMode does not erase service requirements introduced by tool implementations.
@@ -110,10 +114,10 @@ const runtime = CodeMode.make({
   limits: { timeoutMs: 30_000 },
 })
 
-runtime.catalog()       // structured tool descriptions
-runtime.instructions()  // model-facing syntax and tool guide
+runtime.catalog() // structured tool descriptions
+runtime.instructions() // model-facing syntax and tool guide
 runtime.execute(source) // ExecuteResult
-runtime.agentTool()     // { name, description, input, output, execute }
+runtime.agentTool() // { name, description, input, output, execute }
 ```
 
 `catalog`, `instructions`, and `agentTool` are projections of the same configured tool tree. `agentTool().description` is exactly `instructions()`.
@@ -222,10 +226,10 @@ CodeMode is an orchestration language, not a general JavaScript runtime.
 
 The limits are exactly three knobs:
 
-| Limit | Default | Bounds |
-| --- | ---: | --- |
-| `timeoutMs` | none — no timeout | Wall-clock execution time. |
-| `maxToolCalls` | none — unlimited | Tool calls admitted during the execution. |
+| Limit            |              Default | Bounds                                                               |
+| ---------------- | -------------------: | -------------------------------------------------------------------- |
+| `timeoutMs`      |    none — no timeout | Wall-clock execution time.                                           |
+| `maxToolCalls`   |     none — unlimited | Tool calls admitted during the execution.                            |
 | `maxOutputBytes` | none — no truncation | Model-facing output: the serialized result value plus captured logs. |
 
 No limit has a default, on purpose: execution budgets are host policy, not library policy — a host that wants a bound sets one; a host that can interrupt the execution fiber (as OpenCode does on user cancel) may set no timeout, and a host with its own tool-output truncation (as OpenCode has) may leave `maxOutputBytes` unset. A host with neither should set `maxOutputBytes`, or oversized results silently flood model context.
@@ -254,28 +258,25 @@ Two interpreter internals are fixed constants rather than knobs: at most 8 tool
 
 Failures are data:
 
-| Kind | Meaning |
-| --- | --- |
-| `ParseError` | Source is empty or cannot be parsed. |
-| `UnsupportedSyntax` | Parsed JavaScript is outside the supported subset. |
-| `UnknownTool` | A program referenced a tool the host did not provide. |
-| `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 (depth, circularity, blocked properties, non-data values). |
-| `ToolCallLimitExceeded` | Calls exceeded `maxToolCalls`. |
-| `TimeoutExceeded` | Execution exceeded `timeoutMs`. |
-| `ToolFailure` | A tool refused or failed. |
-| `ExecutionFailure` | The program threw or another execution error occurred. |
+| Kind                    | Meaning                                                                                                  |
+| ----------------------- | -------------------------------------------------------------------------------------------------------- |
+| `ParseError`            | Source is empty or cannot be parsed.                                                                     |
+| `UnsupportedSyntax`     | Parsed JavaScript is outside the supported subset.                                                       |
+| `UnknownTool`           | A program referenced a tool the host did not provide.                                                    |
+| `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 (depth, circularity, blocked properties, non-data values). |
+| `ToolCallLimitExceeded` | Calls exceeded `maxToolCalls`.                                                                           |
+| `TimeoutExceeded`       | Execution exceeded `timeoutMs`.                                                                          |
+| `ToolFailure`           | A tool refused or failed.                                                                                |
+| `ExecutionFailure`      | The program threw or another execution error occurred.                                                   |
 
 Unknown host failures, defects, invalid outputs, and copying failures are sanitized. To return a safe operational refusal, fail with `toolError`:
 
 ```ts
 import { toolError } from "@opencode-ai/codemode"
 
-run: ({ id }) =>
-  authorized(id)
-    ? loadOrder(id)
-    : Effect.fail(toolError("Order is unavailable"))
+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.

File diff ditekan karena terlalu besar
+ 402 - 373
packages/codemode/codemode.md


File diff ditekan karena terlalu besar
+ 458 - 153
packages/codemode/src/codemode.ts


+ 1 - 7
packages/codemode/src/index.ts

@@ -1,10 +1,4 @@
-export {
-  ToolError,
-  CodeMode,
-  ExecuteInputSchema,
-  ExecuteResultSchema,
-  toolError,
-} from "./codemode.js"
+export { ToolError, CodeMode, ExecuteInputSchema, ExecuteResultSchema, toolError } from "./codemode.js"
 export { Tool } from "./tool.js"
 export type { Definition as ToolDefinition, JsonSchema, ToolSchema } from "./tool.js"
 export type { ToolCallEnded, ToolCallHooks } from "./tool-runtime.js"

+ 153 - 70
packages/codemode/src/tool-runtime.ts

@@ -21,11 +21,16 @@ export type HostTools<R = never> = {
 
 export type Services<Tools> = Tools extends (...args: Array<unknown>) => Effect.Effect<unknown, unknown, infer R>
   ? R
-  : Tools extends { readonly _tag: "CodeModeTool"; readonly run: (input: unknown) => Effect.Effect<unknown, unknown, infer R> }
+  : Tools extends {
+        readonly _tag: "CodeModeTool"
+        readonly run: (input: unknown) => Effect.Effect<unknown, unknown, infer R>
+      }
     ? R
-  : Tools extends object
-    ? string extends keyof Tools ? never : Services<Tools[keyof Tools]>
-    : never
+    : Tools extends object
+      ? string extends keyof Tools
+        ? never
+        : Services<Tools[keyof Tools]>
+      : never
 
 /** Minimal audit record retained for each admitted tool call. */
 export type ToolCall = {
@@ -68,11 +73,13 @@ export type SafeObject = Record<string, unknown>
 const reservedNamespace = "$codemode"
 const defaultMaxInlineCatalogTokens = 2_000
 const defaultSearchLimit = 10
-const searchSignature = "tools.$codemode.search({ query?: string, namespace?: string, limit?: number }): Promise<{ items: Array<{ path: string; description: string; signature: string }>; total: number }>"
+const searchSignature =
+  "tools.$codemode.search({ query?: string, namespace?: string, limit?: number }): Promise<{ items: Array<{ path: string; description: string; signature: string }>; total: number }>"
 const toolExpression = (path: string) =>
-  "tools" + path
+  "tools" +
+  path
     .split(".")
-    .map((segment) => identifierSegment.test(segment) ? `.${segment}` : `[${JSON.stringify(segment)}]`)
+    .map((segment) => (identifierSegment.test(segment) ? `.${segment}` : `[${JSON.stringify(segment)}]`))
     .join("")
 
 export class ToolReference {
@@ -88,7 +95,12 @@ const MAX_VALUE_DEPTH = 32
 
 export class ToolRuntimeError extends Error {
   constructor(
-    readonly kind: "UnknownTool" | "InvalidToolInput" | "InvalidToolOutput" | "InvalidDataValue" | "ToolCallLimitExceeded",
+    readonly kind:
+      | "UnknownTool"
+      | "InvalidToolInput"
+      | "InvalidToolOutput"
+      | "InvalidDataValue"
+      | "ToolCallLimitExceeded",
     message: string,
     readonly suggestions: ReadonlyArray<string> = [],
   ) {
@@ -131,7 +143,13 @@ export const isBlockedMember = (name: string): boolean => blockedMemberNames.has
 export const copyIn = (value: unknown, label: string, preserveSandboxValues = false): unknown =>
   copyBounded(value, label, 0, new Set(), preserveSandboxValues)
 
-const copyBounded = (value: unknown, label: string, depth: number, seen: Set<object>, preserveSandboxValues: boolean): unknown => {
+const copyBounded = (
+  value: unknown,
+  label: string,
+  depth: number,
+  seen: Set<object>,
+  preserveSandboxValues: boolean,
+): unknown => {
   if (depth > MAX_VALUE_DEPTH) {
     throw new ToolRuntimeError("InvalidDataValue", `${label} exceeds the maximum value depth of ${MAX_VALUE_DEPTH}.`)
   }
@@ -166,7 +184,12 @@ const copyBounded = (value: unknown, label: string, depth: number, seen: Set<obj
     // Intra-sandbox checkpoints keep sandbox value instances alive as leaves; their contents
     // are never walked here (Map/Set members are validated where mutation happens, and the
     // real boundary still serializes them below).
-    if (value instanceof SandboxDate || value instanceof SandboxRegExp || value instanceof SandboxMap || value instanceof SandboxSet) {
+    if (
+      value instanceof SandboxDate ||
+      value instanceof SandboxRegExp ||
+      value instanceof SandboxMap ||
+      value instanceof SandboxSet
+    ) {
       return value
     }
     // Host instances cannot normally reach an intra-sandbox checkpoint (tool results cross
@@ -197,8 +220,12 @@ const copyBounded = (value: unknown, label: string, depth: number, seen: Set<obj
     return Number.isFinite(value.getTime()) ? value.toISOString() : null
   }
   if (
-    value instanceof SandboxRegExp || value instanceof SandboxMap || value instanceof SandboxSet ||
-    value instanceof RegExp || value instanceof Map || value instanceof Set
+    value instanceof SandboxRegExp ||
+    value instanceof SandboxMap ||
+    value instanceof SandboxSet ||
+    value instanceof RegExp ||
+    value instanceof Map ||
+    value instanceof Set
   ) {
     return Object.create(null) as SafeObject
   }
@@ -250,7 +277,10 @@ export const copyOut = (value: unknown, undefinedAsNull = false): unknown => {
   return value
 }
 
-const definitions = <R>(tools: HostTools<R>, path: ReadonlyArray<string> = []): Array<{ path: string; definition: Definition<R> }> => {
+const definitions = <R>(
+  tools: HostTools<R>,
+  path: ReadonlyArray<string> = [],
+): Array<{ path: string; definition: Definition<R> }> => {
   const entries: Array<{ path: string; definition: Definition<R> }> = []
   for (const [name, value] of Object.entries(tools)) {
     const next = [...path, name]
@@ -342,8 +372,11 @@ const toSearchEntry = <R>(path: string, definition: Definition<R>, description:
     path,
     definition.description,
     ...inputProperties(definition).flatMap(({ name, description: property }) =>
-      property === undefined ? [name] : [name, property]),
-  ].join("\n").toLowerCase(),
+      property === undefined ? [name] : [name, property],
+    ),
+  ]
+    .join("\n")
+    .toLowerCase(),
 })
 
 /** The runtime search index over every described tool. Search is always registered. */
@@ -396,7 +429,8 @@ export const discoveryPlan = <R>(
     namespace,
     picked: new Set<ToolDescription>(),
     queue: [...group].sort(
-      (left, right) => estimate(catalogLine(left)) - estimate(catalogLine(right)) || left.path.localeCompare(right.path),
+      (left, right) =>
+        estimate(catalogLine(left)) - estimate(catalogLine(right)) || left.path.localeCompare(right.path),
     ),
   }))
   let used = 0
@@ -472,7 +506,9 @@ export const discoveryPlan = <R>(
         "- A result typed `Promise<unknown>` has no guaranteed shape — verify what actually came back before relying on its fields.",
         "- Run independent calls in parallel: `await Promise.all(items.map((item) => tools.<namespace>.<tool>(item)))`.",
         "- `Object.keys(tools)` lists namespaces; `Object.keys(tools.<namespace>)` lists its tools; `for...in` works on both.",
-        ...(complete ? [] : ['- Browse one namespace: `await tools.$codemode.search({ query: "", namespace: "<name>" })`.']),
+        ...(complete
+          ? []
+          : ['- Browse one namespace: `await tools.$codemode.search({ query: "", namespace: "<name>" })`.']),
       ]
 
   const syntax = [
@@ -500,26 +536,21 @@ export const discoveryPlan = <R>(
       const count = `${group.length} tool${group.length === 1 ? "" : "s"}`
       // Annotate only when a namespace is not fully shown, so a comprehensive
       // namespace reads cleanly and a truncated one is unambiguous.
-      const label = picked.size === group.length ? count : picked.size === 0 ? `${count}, none shown` : `${count}, ${picked.size} shown`
+      const label =
+        picked.size === group.length
+          ? count
+          : picked.size === 0
+            ? `${count}, none shown`
+            : `${count}, ${picked.size} shown`
       toolSection.push(`- ${namespace} (${label})`)
       for (const tool of group) if (picked.has(tool)) toolSection.push(catalogLine(tool))
     }
     if (!complete) {
-      toolSection.push(
-        "",
-        "Search returns complete callable signatures:",
-        `- ${searchSignature}`,
-      )
+      toolSection.push("", "Search returns complete callable signatures:", `- ${searchSignature}`)
     }
   }
 
-  const lines = [
-    ...intro,
-    ...workflow,
-    ...rules,
-    ...syntax,
-    ...toolSection,
-  ]
+  const lines = [...intro, ...workflow, ...rules, ...syntax, ...toolSection]
   return {
     catalog: described,
     instructions: lines.join("\n"),
@@ -534,18 +565,29 @@ export const discoveryPlan = <R>(
  * function in JS). An unknown path is an `UnknownTool` error pointing at the working
  * discovery idioms, mirroring how calling an unknown tool fails.
  */
-const namespaceKeys = <R>(tools: HostTools<R>, path: ReadonlyArray<string>, searchEnabled: boolean): ReadonlyArray<string> => {
+const namespaceKeys = <R>(
+  tools: HostTools<R>,
+  path: ReadonlyArray<string>,
+  searchEnabled: boolean,
+): ReadonlyArray<string> => {
   // The reserved discovery namespace is virtual (never present in the host tree); enumerate
   // it explicitly so `Object.keys(tools.$codemode)` matches the callable surface.
   if (searchEnabled && path.length === 1 && path[0] === reservedNamespace) return ["search"]
   let value: HostTool<R> | Definition<R> | HostTools<R> = tools
   for (const segment of path) {
-    if (isBlockedMember(segment) || typeof value === "function" || isDefinition(value) || !Object.hasOwn(value, segment)) {
+    if (
+      isBlockedMember(segment) ||
+      typeof value === "function" ||
+      isDefinition(value) ||
+      !Object.hasOwn(value, segment)
+    ) {
       throw new ToolRuntimeError(
         "UnknownTool",
         `Unknown tool namespace '${path.join(".")}'.`,
         searchEnabled
-          ? ["Object.keys(tools) lists the available namespaces; tools.$codemode.search({ query }) finds described tools."]
+          ? [
+              "Object.keys(tools) lists the available namespaces; tools.$codemode.search({ query }) finds described tools.",
+            ]
           : ["Object.keys(tools) lists the available namespaces."],
       )
     }
@@ -555,12 +597,25 @@ const namespaceKeys = <R>(tools: HostTools<R>, path: ReadonlyArray<string>, sear
   return Object.keys(value)
 }
 
-const resolve = <R>(tools: HostTools<R>, path: ReadonlyArray<string>, searchEnabled: boolean): HostTool<R> | Definition<R> => {
+const resolve = <R>(
+  tools: HostTools<R>,
+  path: ReadonlyArray<string>,
+  searchEnabled: boolean,
+): HostTool<R> | Definition<R> => {
   let value: HostTool<R> | Definition<R> | HostTools<R> = tools
 
   for (const segment of path) {
-    if (isBlockedMember(segment) || typeof value === "function" || isDefinition(value) || !Object.hasOwn(value, segment)) {
-      throw new ToolRuntimeError("UnknownTool", `Unknown tool '${path.join(".")}'.`, searchEnabled ? ["Use tools.$codemode.search({ query }) to find available described tools."] : [])
+    if (
+      isBlockedMember(segment) ||
+      typeof value === "function" ||
+      isDefinition(value) ||
+      !Object.hasOwn(value, segment)
+    ) {
+      throw new ToolRuntimeError(
+        "UnknownTool",
+        `Unknown tool '${path.join(".")}'.`,
+        searchEnabled ? ["Use tools.$codemode.search({ query }) to find available described tools."] : [],
+      )
     }
     value = value[segment] as HostTool<R> | Definition<R> | HostTools<R>
   }
@@ -602,7 +657,8 @@ export const make = <R>(
     return effect.pipe(
       Effect.tap(() => onEnd({ ...call, durationMs: Date.now() - startedAt, outcome: "success" })),
       Effect.tapError((error) =>
-        onEnd({ ...call, durationMs: Date.now() - startedAt, outcome: "failure", message: failureMessage(error) })),
+        onEnd({ ...call, durationMs: Date.now() - startedAt, outcome: "failure", message: failureMessage(error) }),
+      ),
     )
   }
 
@@ -624,7 +680,7 @@ export const make = <R>(
     calls,
     keys: (path) => namespaceKeys(tools, path, searchEnabled),
     invoke: (path, args) =>
-      Effect.gen(function*() {
+      Effect.gen(function* () {
         const name = path.join(".")
         const externalArgs = args.map((arg) => copyOut(copyIn(arg, `Arguments for tool '${name}'`)))
         const call = { name }
@@ -637,17 +693,32 @@ export const make = <R>(
           if (!searchEnabled) throw new ToolRuntimeError("UnknownTool", `Unknown tool '${name}'.`)
           const input = externalArgs[0]
           if (externalArgs.length !== 1 || input === null || typeof input !== "object" || Array.isArray(input)) {
-            throw new ToolRuntimeError("InvalidToolInput", "tools.$codemode.search expects { query?: string; namespace?: string; limit?: number }.")
+            throw new ToolRuntimeError(
+              "InvalidToolInput",
+              "tools.$codemode.search expects { query?: string; namespace?: string; limit?: number }.",
+            )
           }
           const request = input as { query?: unknown; namespace?: unknown; limit?: unknown }
           if (request.query !== undefined && typeof request.query !== "string") {
-            throw new ToolRuntimeError("InvalidToolInput", "tools.$codemode.search query must be a string when provided.")
+            throw new ToolRuntimeError(
+              "InvalidToolInput",
+              "tools.$codemode.search query must be a string when provided.",
+            )
           }
           if (request.namespace !== undefined && typeof request.namespace !== "string") {
-            throw new ToolRuntimeError("InvalidToolInput", "tools.$codemode.search namespace must be a string when provided.")
+            throw new ToolRuntimeError(
+              "InvalidToolInput",
+              "tools.$codemode.search namespace must be a string when provided.",
+            )
           }
-          if (request.limit !== undefined && (typeof request.limit !== "number" || !Number.isSafeInteger(request.limit) || request.limit <= 0)) {
-            throw new ToolRuntimeError("InvalidToolInput", "tools.$codemode.search limit must be a positive safe integer when provided.")
+          if (
+            request.limit !== undefined &&
+            (typeof request.limit !== "number" || !Number.isSafeInteger(request.limit) || request.limit <= 0)
+          ) {
+            throw new ToolRuntimeError(
+              "InvalidToolInput",
+              "tools.$codemode.search limit must be a positive safe integer when provided.",
+            )
           }
           const query = typeof request.query === "string" ? request.query : ""
           const namespace = typeof request.namespace === "string" ? request.namespace : undefined
@@ -656,40 +727,50 @@ export const make = <R>(
             Effect.try({
               try: () => {
                 const limit = typeof request.limit === "number" ? request.limit : defaultSearchLimit
-                const scoped = namespace === undefined ? searchIndex : searchIndex.filter((entry) => entry.namespace === namespace)
+                const scoped =
+                  namespace === undefined ? searchIndex : searchIndex.filter((entry) => entry.namespace === namespace)
                 // A query that names one tool path exactly (canonical path or rendered
                 // JavaScript expression) is a lookup, not a search: return that tool alone.
                 const trimmed = query.trim()
                 const pathQuery = trimmed.startsWith("tools.") ? trimmed.slice("tools.".length) : trimmed
-                const exact = pathQuery === "" ? undefined : scoped.find((entry) =>
-                  entry.description.path === pathQuery || toolExpression(entry.description.path) === trimmed)
+                const exact =
+                  pathQuery === ""
+                    ? undefined
+                    : scoped.find(
+                        (entry) =>
+                          entry.description.path === pathQuery || toolExpression(entry.description.path) === trimmed,
+                      )
                 const terms = tokenize(query).map(termForms)
                 // Additive field-weighted scoring, summed across terms: exact path or path
                 // segment (20) > path substring (8) > description substring (4) > any
                 // searchable text, incl. input parameter names/descriptions (2). Each term
                 // matches a field when any of its forms (the term or a singular variant)
                 // does. An empty query browses everything, alphabetical by path.
-                const ranked = exact !== undefined
-                  ? [exact]
-                  : scoped
-                      .map((entry) => {
-                        const path = entry.description.path.toLowerCase()
-                        const description = entry.description.description.toLowerCase()
-                        const score = terms.reduce(
-                          (total, forms) =>
-                            total +
-                            (forms.some((form) => path === form || path.endsWith(`.${form}`)) ? 20 : 0) +
-                            (forms.some((form) => path.includes(form)) ? 8 : 0) +
-                            (forms.some((form) => description.includes(form)) ? 4 : 0) +
-                            (forms.some((form) => entry.searchText.includes(form)) ? 2 : 0),
-                          0,
+                const ranked =
+                  exact !== undefined
+                    ? [exact]
+                    : scoped
+                        .map((entry) => {
+                          const path = entry.description.path.toLowerCase()
+                          const description = entry.description.description.toLowerCase()
+                          const score = terms.reduce(
+                            (total, forms) =>
+                              total +
+                              (forms.some((form) => path === form || path.endsWith(`.${form}`)) ? 20 : 0) +
+                              (forms.some((form) => path.includes(form)) ? 8 : 0) +
+                              (forms.some((form) => description.includes(form)) ? 4 : 0) +
+                              (forms.some((form) => entry.searchText.includes(form)) ? 2 : 0),
+                            0,
+                          )
+                          return { entry, score }
+                        })
+                        .filter(({ score }) => terms.length === 0 || score > 0)
+                        .sort(
+                          (left, right) =>
+                            right.score - left.score ||
+                            left.entry.description.path.localeCompare(right.entry.description.path),
                         )
-                        return { entry, score }
-                      })
-                      .filter(({ score }) => terms.length === 0 || score > 0)
-                      .sort((left, right) =>
-                        right.score - left.score || left.entry.description.path.localeCompare(right.entry.description.path))
-                      .map(({ entry }) => entry)
+                        .map(({ entry }) => entry)
                 // Result paths are rendered as JavaScript expressions so each `path` is
                 // directly usable as the call site (`await tools.github.list({ ... })` or
                 // `await tools.ns["dashed-name"]({ ... })`). The signature is the pretty,
@@ -711,10 +792,12 @@ export const make = <R>(
         const tool = resolve(tools, path, searchEnabled)
         let describedInput: unknown
         if (isDefinition(tool)) {
-          if (externalArgs.length !== 1) throw new ToolRuntimeError("InvalidToolInput", `Tool '${name}' expects exactly one input object.`)
+          if (externalArgs.length !== 1)
+            throw new ToolRuntimeError("InvalidToolInput", `Tool '${name}' expects exactly one input object.`)
           describedInput = yield* Effect.try({
             try: () => decodeToolInput(tool, externalArgs[0]),
-            catch: (cause) => new ToolRuntimeError("InvalidToolInput", `Invalid input for tool '${name}': ${String(cause)}`),
+            catch: (cause) =>
+              new ToolRuntimeError("InvalidToolInput", `Invalid input for tool '${name}': ${String(cause)}`),
           })
         }
         const input = isDefinition(tool) ? describedInput : externalArgs
@@ -722,7 +805,7 @@ export const make = <R>(
         const currentCall = { index, name, input }
         if (isDefinition(tool)) {
           return yield* observeEnd(
-            Effect.gen(function*() {
+            Effect.gen(function* () {
               const raw = yield* runHost(Effect.suspend(() => tool.run(describedInput)))
               const result = yield* Effect.try({
                 try: () => decodeToolOutput(tool, raw),
@@ -734,7 +817,7 @@ export const make = <R>(
           )
         }
         return yield* observeEnd(
-          Effect.gen(function*() {
+          Effect.gen(function* () {
             return yield* decodeOutput(yield* runHost(Effect.suspend(() => tool(...externalArgs))), name)
           }),
           currentCall,

+ 27 - 12
packages/codemode/src/tool.ts

@@ -57,8 +57,7 @@ export type Options<I extends ToolSchema, O extends ToolSchema | undefined, R =
 export const isDefinition = <R = never>(value: unknown): value is Definition<R> =>
   typeof value === "object" && value !== null && "_tag" in value && value._tag === "CodeModeTool"
 
-const isEffectSchema = (schema: ToolSchema): schema is Schema.Decoder<unknown> & Schema.Top =>
-  Schema.isSchema(schema)
+const isEffectSchema = (schema: ToolSchema): schema is Schema.Decoder<unknown> & Schema.Top => Schema.isSchema(schema)
 
 const renderLiteral = (value: unknown): string => JSON.stringify(value) ?? "unknown"
 
@@ -69,10 +68,12 @@ const renderLiteral = (value: unknown): string => JSON.stringify(value) ?? "unkn
 export const identifierSegment = /^[A-Za-z_$][A-Za-z0-9_$]*$/
 
 /** Renders a property name as a valid TS object key: bare when an identifier, quoted otherwise. */
-const renderKey = (name: string): string => identifierSegment.test(name) ? name : JSON.stringify(name)
+const renderKey = (name: string): string => (identifierSegment.test(name) ? name : JSON.stringify(name))
 
 const effectNumberSentinel = (schema: JsonSchema) =>
-  schema.type === "string" && Array.isArray(schema.enum) && schema.enum.length === 1 &&
+  schema.type === "string" &&
+  Array.isArray(schema.enum) &&
+  schema.enum.length === 1 &&
   (schema.enum[0] === "NaN" || schema.enum[0] === "Infinity" || schema.enum[0] === "-Infinity")
 
 /**
@@ -118,7 +119,8 @@ const docTags = (schema: JsonSchema): Array<string> => {
  */
 const jsdoc = (description: string | undefined, tags: ReadonlyArray<string>, pad: string): string => {
   const lines = [...(description === undefined ? [] : description.split("\n")), ...tags].map((line) =>
-    line.replaceAll("*/", "* /").replace(/\s+$/, ""))
+    line.replaceAll("*/", "* /").replace(/\s+$/, ""),
+  )
   while (lines.length > 0 && lines[0]!.trim() === "") lines.shift()
   while (lines.length > 0 && lines[lines.length - 1]!.trim() === "") lines.pop()
   if (lines.length === 0) return ""
@@ -127,7 +129,12 @@ const jsdoc = (description: string | undefined, tags: ReadonlyArray<string>, pad
   return `${pad}/**\n${body}\n${pad} */\n`
 }
 
-const renderSchema = (schema: JsonSchema, ctx: RenderContext, depth = 0, seen: ReadonlySet<string> = new Set()): string => {
+const renderSchema = (
+  schema: JsonSchema,
+  ctx: RenderContext,
+  depth = 0,
+  seen: ReadonlySet<string> = new Set(),
+): string => {
   if (depth > MAX_RENDER_DEPTH) return "unknown"
   if (schema.$ref) {
     const name = schema.$ref.split("/").pop()
@@ -146,13 +153,16 @@ const renderSchema = (schema: JsonSchema, ctx: RenderContext, depth = 0, seen: R
     if (
       alternatives.some((item) => item.type === "number") &&
       alternatives.every((item) => item.type === "number" || effectNumberSentinel(item))
-    ) return "number"
+    )
+      return "number"
     // An empty Schema.Struct({}) emits `anyOf: [{ type: "object" }, { type: "array" }]`
     // (no properties/items); render the bare shape as {} instead of `{} | Array<unknown>`.
     if (
       alternatives.length === 2 &&
-      alternatives[0]?.type === "object" && alternatives[0].properties === undefined &&
-      alternatives[1]?.type === "array" && alternatives[1].items === undefined
+      alternatives[0]?.type === "object" &&
+      alternatives[0].properties === undefined &&
+      alternatives[1]?.type === "array" &&
+      alternatives[1].items === undefined
     ) {
       return "{}"
     }
@@ -170,7 +180,8 @@ const renderSchema = (schema: JsonSchema, ctx: RenderContext, depth = 0, seen: R
     const required = new Set(schema.required ?? [])
     const properties = Object.entries(schema.properties ?? {})
     const additional = schema.additionalProperties
-    const indexType = additional && typeof additional === "object" ? renderSchema(additional, ctx, depth + 1, seen) : undefined
+    const indexType =
+      additional && typeof additional === "object" ? renderSchema(additional, ctx, depth + 1, seen) : undefined
     const field = ([name, value]: readonly [string, JsonSchema]) =>
       `${renderKey(name)}${required.has(name) ? "" : "?"}: ${renderSchema(value, ctx, depth + 1, seen)}`
 
@@ -183,7 +194,9 @@ const renderSchema = (schema: JsonSchema, ctx: RenderContext, depth = 0, seen: R
     // Pretty: an indented block, each described field preceded by its JSDoc comment.
     if (properties.length === 0 && indexType === undefined) return "{}"
     const pad = "  ".repeat(depth + 1)
-    const lines = properties.map((entry) => `${jsdoc(entry[1].description, docTags(entry[1]), pad)}${pad}${field(entry)}`)
+    const lines = properties.map(
+      (entry) => `${jsdoc(entry[1].description, docTags(entry[1]), pad)}${pad}${field(entry)}`,
+    )
     if (indexType !== undefined) lines.push(`${pad}[key: string]: ${indexType}`)
     return `{\n${lines.join("\n")}\n${"  ".repeat(depth)}}`
   }
@@ -262,7 +275,9 @@ export const inputProperties = <R>(definition: Definition<R>): Array<InputProper
  * fields; the default stays the compact single-line form.
  */
 export const inputTypeScript = <R>(definition: Definition<R>, pretty = false): string =>
-  isEffectSchema(definition.input) ? toTypeScript(definition.input, false, pretty) : jsonSchemaToTypeScript(definition.input, pretty)
+  isEffectSchema(definition.input)
+    ? toTypeScript(definition.input, false, pretty)
+    : jsonSchemaToTypeScript(definition.input, pretty)
 
 /**
  * The model-visible TypeScript type of a tool's result; tools without an output schema

+ 4 - 1
packages/codemode/src/values.ts

@@ -49,4 +49,7 @@ export class SandboxSet {
 }
 
 export const isSandboxValue = (value: unknown): value is SandboxDate | SandboxRegExp | SandboxMap | SandboxSet =>
-  value instanceof SandboxDate || value instanceof SandboxRegExp || value instanceof SandboxMap || value instanceof SandboxSet
+  value instanceof SandboxDate ||
+  value instanceof SandboxRegExp ||
+  value instanceof SandboxMap ||
+  value instanceof SandboxSet

+ 267 - 172
packages/codemode/test/codemode.test.ts

@@ -1,6 +1,13 @@
 import { describe, expect, test } from "bun:test"
 import { Cause, Effect, Schema } from "effect"
-import { CodeMode, ExecuteInputSchema, ExecuteResultSchema, Tool, toolError, type ExecutionLimits } from "../src/index.js"
+import {
+  CodeMode,
+  ExecuteInputSchema,
+  ExecuteResultSchema,
+  Tool,
+  toolError,
+  type ExecutionLimits,
+} from "../src/index.js"
 import type { Definition } from "../src/tool.js"
 
 const run = (tool: Definition<never>) =>
@@ -75,11 +82,14 @@ describe("CodeMode host failure boundary", () => {
         output: Schema.Unknown,
         run: () =>
           Effect.succeed(
-            new Proxy({}, {
-              ownKeys: () => {
-                throw new Error("host-output-secret")
+            new Proxy(
+              {},
+              {
+                ownKeys: () => {
+                  throw new Error("host-output-secret")
+                },
               },
-            }),
+            ),
           ),
       }),
     )
@@ -162,9 +172,7 @@ describe("CodeMode tool-call observation", () => {
     )
 
     expect(result.ok).toBe(true)
-    expect(calls).toStrictEqual([
-      { index: 0, name: "context.lookup", input: { query: "deployment failure" } },
-    ])
+    expect(calls).toStrictEqual([{ index: 0, name: "context.lookup", input: { query: "deployment failure" } }])
   })
 
   test("observes settled calls with outcome and duration", async () => {
@@ -173,25 +181,26 @@ describe("CodeMode tool-call observation", () => {
       description: "Look up a value",
       input: Schema.Struct({ query: Schema.String }),
       output: Schema.String,
-      run: ({ query }) =>
-        query === "boom" ? Effect.fail(toolError("Lookup refused")) : Effect.succeed(query),
+      run: ({ query }) => (query === "boom" ? Effect.fail(toolError("Lookup refused")) : Effect.succeed(query)),
     })
 
     const runtime = CodeMode.make({
       tools: { context: { lookup } },
-      onToolCallStart: (call) => Effect.sync(() => {
-        events.push({ phase: "start", index: call.index, name: call.name })
-      }),
-      onToolCallEnd: (call) => Effect.sync(() => {
-        expect(call.durationMs).toBeGreaterThanOrEqual(0)
-        events.push({
-          phase: "end",
-          index: call.index,
-          name: call.name,
-          outcome: call.outcome,
-          ...(call.message === undefined ? {} : { message: call.message }),
-        })
-      }),
+      onToolCallStart: (call) =>
+        Effect.sync(() => {
+          events.push({ phase: "start", index: call.index, name: call.name })
+        }),
+      onToolCallEnd: (call) =>
+        Effect.sync(() => {
+          expect(call.durationMs).toBeGreaterThanOrEqual(0)
+          events.push({
+            phase: "end",
+            index: call.index,
+            name: call.name,
+            outcome: call.outcome,
+            ...(call.message === undefined ? {} : { message: call.message }),
+          })
+        }),
     })
 
     const success = await Effect.runPromise(runtime.execute(`return await tools.context.lookup({ query: "ok" })`))
@@ -210,13 +219,15 @@ describe("CodeMode tool-call observation", () => {
 
 describe("CodeMode console capture", () => {
   test("captures console output as bounded result logs", async () => {
-    const result = await Effect.runPromise(CodeMode.execute({
-      code: `
+    const result = await Effect.runPromise(
+      CodeMode.execute({
+        code: `
         const returned = console.log("Thread info:", { name: "Demo", count: 2 })
         console.warn("careful")
         return returned
       `,
-    }))
+      }),
+    )
 
     expect(result).toStrictEqual({
       ok: true,
@@ -228,39 +239,45 @@ describe("CodeMode console capture", () => {
   })
 
   test("keeps logs captured before failures", async () => {
-    const result = await Effect.runPromise(CodeMode.execute({
-      code: `
+    const result = await Effect.runPromise(
+      CodeMode.execute({
+        code: `
         console.log("before failure")
         throw new Error("boom")
       `,
-    }))
+      }),
+    )
 
     expect(result.ok ? undefined : result.logs).toStrictEqual(["before failure"])
     expect(result.ok ? undefined : result.error.message).toBe("Uncaught: boom")
   })
 
   test("prints NaN and Infinity literally instead of the JSON null", async () => {
-    const result = await Effect.runPromise(CodeMode.execute({
-      code: `
+    const result = await Effect.runPromise(
+      CodeMode.execute({
+        code: `
         console.log(NaN)
         console.log(Infinity, -Infinity)
         console.log({ ratio: NaN, bounds: [Infinity] })
         return null
       `,
-    }))
+      }),
+    )
 
     expect(result.ok).toBe(true)
     expect(result.logs).toStrictEqual(["NaN", "Infinity -Infinity", '{"ratio":NaN,"bounds":[Infinity]}'])
   })
 
   test("renders sandbox values nested inside logged containers", async () => {
-    const result = await Effect.runPromise(CodeMode.execute({
-      code: `
+    const result = await Effect.runPromise(
+      CodeMode.execute({
+        code: `
         console.log({ m: new Map([["a", 1]]), when: new Date(0), r: /ab/g, s: new Set([1, 2]) })
         console.log([new Date(0)])
         return null
       `,
-    }))
+      }),
+    )
 
     expect(result.ok).toBe(true)
     expect(result.logs).toStrictEqual([
@@ -270,38 +287,40 @@ describe("CodeMode console capture", () => {
   })
 
   test("console formatting is total: cycles and opaque references render as markers", async () => {
-    const result = await Effect.runPromise(CodeMode.execute({
-      code: `
+    const result = await Effect.runPromise(
+      CodeMode.execute({
+        code: `
         const m = new Map()
         m.set("self", m)
         console.log({ box: m })
         console.log({ fn: (x) => x, ok: 1 })
         return null
       `,
-    }))
+      }),
+    )
 
     expect(result.ok).toBe(true)
-    expect(result.logs).toStrictEqual([
-      '{"box":Map(1) [["self",[Circular]]]}',
-      '{"fn":[CodeMode reference],"ok":1}',
-    ])
+    expect(result.logs).toStrictEqual(['{"box":Map(1) [["self",[Circular]]]}', '{"fn":[CodeMode reference],"ok":1}'])
   })
 
   test("console.table renders sandbox value cells", async () => {
-    const result = await Effect.runPromise(CodeMode.execute({
-      code: `
+    const result = await Effect.runPromise(
+      CodeMode.execute({
+        code: `
         console.table([{ when: new Date(0), n: NaN }])
         return null
       `,
-    }))
+      }),
+    )
 
     expect(result.ok).toBe(true)
     expect(result.logs).toStrictEqual(["(index)\twhen\tn\n0\t1970-01-01T00:00:00.000Z\tNaN"])
   })
 
   test("captures console.dir and console.table output", async () => {
-    const result = await Effect.runPromise(CodeMode.execute({
-      code: `
+    const result = await Effect.runPromise(
+      CodeMode.execute({
+        code: `
         console.dir({ nested: { ok: true } })
         console.table([
           { name: "Kit", count: 1, hidden: "x" },
@@ -309,15 +328,13 @@ describe("CodeMode console capture", () => {
         ], ["name", "count"])
         return "done"
       `,
-    }))
+      }),
+    )
 
     expect(result).toStrictEqual({
       ok: true,
       value: "done",
-      logs: [
-        '{"nested":{"ok":true}}',
-        "(index)\tname\tcount\n0\tKit\t1\n1\tOlive\t2",
-      ],
+      logs: ['{"nested":{"ok":true}}', "(index)\tname\tcount\n0\tKit\t1\n1\tOlive\t2"],
       toolCalls: [],
     })
   })
@@ -325,9 +342,11 @@ describe("CodeMode console capture", () => {
 
 describe("CodeMode output budget", () => {
   test("absent maxOutputBytes means no truncation at all", async () => {
-    const result = await Effect.runPromise(CodeMode.execute({
-      code: `console.log("z".repeat(50_000)); return "x".repeat(100_000)`,
-    }))
+    const result = await Effect.runPromise(
+      CodeMode.execute({
+        code: `console.log("z".repeat(50_000)); return "x".repeat(100_000)`,
+      }),
+    )
 
     expect(result.ok).toBe(true)
     if (!result.ok) return
@@ -338,29 +357,35 @@ describe("CodeMode output budget", () => {
 
   test("truncates an oversized result value with a marker instead of failing", async () => {
     const limits: ExecutionLimits = { maxOutputBytes: 40 }
-    const result = await Effect.runPromise(CodeMode.execute({
-      code: `return { data: "${"x".repeat(200)}" }`,
-      limits,
-    }))
+    const result = await Effect.runPromise(
+      CodeMode.execute({
+        code: `return { data: "${"x".repeat(200)}" }`,
+        limits,
+      }),
+    )
 
     expect(result.ok).toBe(true)
     if (!result.ok) return
     expect(result.truncated).toBe(true)
     expect(typeof result.value).toBe("string")
-    expect(result.value).toMatch(/^\{"data":"x+ \[result truncated: \d+ bytes exceeds the 40-byte output limit; return a smaller value\]$/)
+    expect(result.value).toMatch(
+      /^\{"data":"x+ \[result truncated: \d+ bytes exceeds the 40-byte output limit; return a smaller value\]$/,
+    )
     expect(Schema.decodeUnknownSync(ExecuteResultSchema)(JSON.parse(JSON.stringify(result)))).toStrictEqual(result)
   })
 
   test("keeps leading logs within the remaining budget and marks the cut", async () => {
     const limits: ExecutionLimits = { maxOutputBytes: 40 }
-    const result = await Effect.runPromise(CodeMode.execute({
-      code: `
+    const result = await Effect.runPromise(
+      CodeMode.execute({
+        code: `
         console.log("first line")
         console.log("${"y".repeat(200)}")
         return "ok"
       `,
-      limits,
-    }))
+        limits,
+      }),
+    )
 
     expect(result.ok).toBe(true)
     if (!result.ok) return
@@ -370,12 +395,14 @@ describe("CodeMode output budget", () => {
   })
 
   test("does not mark results within the budget", async () => {
-    const result = await Effect.runPromise(CodeMode.execute({
-      code: `
+    const result = await Effect.runPromise(
+      CodeMode.execute({
+        code: `
         console.log("fits")
         return { fits: true }
       `,
-    }))
+      }),
+    )
     expect(result).toStrictEqual({
       ok: true,
       value: { fits: true },
@@ -395,18 +422,21 @@ describe("CodeMode schema flexibility", () => {
         properties: { id: { type: "string" }, count: { type: "number" } },
         required: ["id"],
       },
-      run: (input) => Effect.sync(() => {
-        observed.push(input)
-        return { echoed: input }
-      }),
+      run: (input) =>
+        Effect.sync(() => {
+          observed.push(input)
+          return { echoed: input }
+        }),
     })
     const runtime = CodeMode.make({ tools: { adapter: { call } } })
 
-    expect(runtime.catalog()).toStrictEqual([{
-      path: "adapter.call",
-      description: "Call an adapter-described tool",
-      signature: "tools.adapter.call(input: { id: string; count?: number }): Promise<unknown>",
-    }])
+    expect(runtime.catalog()).toStrictEqual([
+      {
+        path: "adapter.call",
+        description: "Call an adapter-described tool",
+        signature: "tools.adapter.call(input: { id: string; count?: number }): Promise<unknown>",
+      },
+    ])
 
     // JSON Schema is render-only: mistyped input passes through unvalidated.
     const result = await Effect.runPromise(runtime.execute(`return await tools.adapter.call({ id: 42 })`))
@@ -421,17 +451,25 @@ describe("CodeMode schema flexibility", () => {
       input: { type: "object", properties: { login: { type: "string" } }, required: ["login"] },
       output: {
         $ref: "#/$defs/User",
-        $defs: { User: { type: "object", properties: { login: { type: "string" }, id: { type: "number" } }, required: ["login", "id"] } },
+        $defs: {
+          User: {
+            type: "object",
+            properties: { login: { type: "string" }, id: { type: "number" } },
+            required: ["login", "id"],
+          },
+        },
       },
       run: () => Effect.succeed({ login: "kit", id: 7 }),
     })
     const runtime = CodeMode.make({ tools: { users: { lookup } } })
 
-    expect(runtime.catalog()).toStrictEqual([{
-      path: "users.lookup",
-      description: "Look up a user",
-      signature: "tools.users.lookup(input: { login: string }): Promise<{ login: string; id: number }>",
-    }])
+    expect(runtime.catalog()).toStrictEqual([
+      {
+        path: "users.lookup",
+        description: "Look up a user",
+        signature: "tools.users.lookup(input: { login: string }): Promise<{ login: string; id: number }>",
+      },
+    ])
 
     const result = await Effect.runPromise(runtime.execute(`return await tools.users.lookup({ login: "kit" })`))
     expect(result.ok).toBe(true)
@@ -478,16 +516,20 @@ describe("CodeMode public contract", () => {
     expect(agentTool.input).toBe(ExecuteInputSchema)
     expect(agentTool.output).toBe(ExecuteResultSchema)
     expect(agentTool.description).toBe(runtime.instructions())
-    expect(Schema.decodeUnknownSync(ExecuteResultSchema)(JSON.parse(JSON.stringify(projected)))).toStrictEqual(projected)
+    expect(Schema.decodeUnknownSync(ExecuteResultSchema)(JSON.parse(JSON.stringify(projected)))).toStrictEqual(
+      projected,
+    )
   })
 
   test("inlines a COMPLETE small catalog and keeps search registered but unadvertised", async () => {
     const runtime = CodeMode.make({ tools })
-    expect(runtime.catalog()).toStrictEqual([{
-      path: "orders.lookup",
-      description: "Look up an order by ID",
-      signature: "tools.orders.lookup(input: { id: string }): Promise<{ id: string; status: string }>",
-    }])
+    expect(runtime.catalog()).toStrictEqual([
+      {
+        path: "orders.lookup",
+        description: "Look up an order by ID",
+        signature: "tools.orders.lookup(input: { id: string }): Promise<{ id: string; status: string }>",
+      },
+    ])
     expect(runtime.instructions()).toContain("Available tools (COMPLETE list")
     expect(runtime.instructions()).toContain("- orders (1 tool)")
     expect(runtime.instructions()).toContain(
@@ -502,11 +544,13 @@ describe("CodeMode public contract", () => {
     expect(result.ok).toBe(true)
     if (result.ok) {
       expect(result.value).toStrictEqual({
-        items: [{
-          path: "tools.orders.lookup",
-          description: "Look up an order by ID",
-          signature: "tools.orders.lookup(input: {\n  id: string\n}): Promise<{\n  id: string\n  status: string\n}>",
-        }],
+        items: [
+          {
+            path: "tools.orders.lookup",
+            description: "Look up an order by ID",
+            signature: "tools.orders.lookup(input: {\n  id: string\n}): Promise<{\n  id: string\n  status: string\n}>",
+          },
+        ],
         total: 1,
       })
     }
@@ -521,31 +565,43 @@ describe("CodeMode public contract", () => {
     })
     const runtime = CodeMode.make({ tools: { context7: { "resolve-library-id": resolveLibrary } } })
 
-    expect(runtime.catalog()).toStrictEqual([{
-      path: "context7.resolve-library-id",
-      description: "Resolve a library ID",
-      signature: 'tools.context7["resolve-library-id"](input: { libraryName: string }): Promise<string>',
-    }])
-    expect(runtime.instructions()).toContain('tools.context7["resolve-library-id"](input: { libraryName: string }): Promise<string>')
+    expect(runtime.catalog()).toStrictEqual([
+      {
+        path: "context7.resolve-library-id",
+        description: "Resolve a library ID",
+        signature: 'tools.context7["resolve-library-id"](input: { libraryName: string }): Promise<string>',
+      },
+    ])
+    expect(runtime.instructions()).toContain(
+      'tools.context7["resolve-library-id"](input: { libraryName: string }): Promise<string>',
+    )
 
-    const search = await Effect.runPromise(runtime.execute(`return await tools.$codemode.search({ query: "resolve library id" })`))
+    const search = await Effect.runPromise(
+      runtime.execute(`return await tools.$codemode.search({ query: "resolve library id" })`),
+    )
     expect(search.ok).toBe(true)
     if (search.ok) {
       expect(search.value).toStrictEqual({
-        items: [{
-          path: 'tools.context7["resolve-library-id"]',
-          description: "Resolve a library ID",
-          signature: 'tools.context7["resolve-library-id"](input: {\n  libraryName: string\n}): Promise<string>',
-        }],
+        items: [
+          {
+            path: 'tools.context7["resolve-library-id"]',
+            description: "Resolve a library ID",
+            signature: 'tools.context7["resolve-library-id"](input: {\n  libraryName: string\n}): Promise<string>',
+          },
+        ],
         total: 1,
       })
     }
 
-    const call = await Effect.runPromise(runtime.execute(`return await tools.context7["resolve-library-id"]({ libraryName: "TypeScript" })`))
+    const call = await Effect.runPromise(
+      runtime.execute(`return await tools.context7["resolve-library-id"]({ libraryName: "TypeScript" })`),
+    )
     expect(call.ok).toBe(true)
     if (call.ok) expect(call.value).toBe("/resolved/TypeScript")
 
-    const exact = await Effect.runPromise(runtime.execute(`return await tools.$codemode.search({ query: 'tools.context7["resolve-library-id"]' })`))
+    const exact = await Effect.runPromise(
+      runtime.execute(`return await tools.$codemode.search({ query: 'tools.context7["resolve-library-id"]' })`),
+    )
     expect(exact.ok).toBe(true)
     if (exact.ok) expect((exact.value as { total: number }).total).toBe(1)
   })
@@ -561,7 +617,9 @@ describe("CodeMode public contract", () => {
     expect(instructions.indexOf("## Rules")).toBeLessThan(instructions.indexOf("## Syntax"))
     expect(instructions.indexOf("## Syntax")).toBeLessThan(instructions.indexOf("\n## Available tools (COMPLETE list"))
     // The workflow carries the result-shape guidance; Rules only add content beyond it.
-    expect(instructions).toContain('`const data = typeof res === "string" ? JSON.parse(res) : res` — most tools return JSON as a string')
+    expect(instructions).toContain(
+      '`const data = typeof res === "string" ? JSON.parse(res) : res` — most tools return JSON as a string',
+    )
     expect(instructions).toContain("Return only the fields you need")
     expect(instructions).toContain("raw payloads get truncated and waste context")
     expect(instructions).toContain("`const res = await tools.<namespace>.<tool>(input)`")
@@ -584,8 +642,12 @@ describe("CodeMode public contract", () => {
     expect(partial).toContain(
       '1. Find a tool (skip when it is already listed below): `const { items } = await tools.$codemode.search({ query: "<intent + key nouns>" })` — short phrases like "list issues" work best.',
     )
-    expect(partial).toContain("Only tools listed here or returned by `tools.$codemode.search` are available inside `tools`")
-    expect(partial).toContain('- Browse one namespace: `await tools.$codemode.search({ query: "", namespace: "<name>" })`.')
+    expect(partial).toContain(
+      "Only tools listed here or returned by `tools.$codemode.search` are available inside `tools`",
+    )
+    expect(partial).toContain(
+      '- Browse one namespace: `await tools.$codemode.search({ query: "", namespace: "<name>" })`.',
+    )
     expect(partial).not.toContain("total_count")
     expect(partial).not.toContain("tools.orders.lookup({")
   })
@@ -604,7 +666,9 @@ describe("CodeMode public contract", () => {
     expect(instructions).not.toContain("instanceof Error")
     expect(instructions).not.toContain("splice")
     // The data-boundary note survives.
-    expect(instructions).toContain("Dates serialize to ISO strings at data boundaries; Map/Set/RegExp serialize to `{}`.")
+    expect(instructions).toContain(
+      "Dates serialize to ISO strings at data boundaries; Map/Set/RegExp serialize to `{}`.",
+    )
   })
 
   test("zero tools keep minimal sections and the no-tools notice", () => {
@@ -635,18 +699,22 @@ describe("CodeMode public contract", () => {
       tools: { thread: { uploadFile: upload, generateImage: generate }, orders: { lookup } },
       discovery: { maxInlineCatalogTokens: 0 },
     })
-    expect(runtime.instructions()).toContain("Available tools (PARTIAL — 0 of 3 shown; find the rest with tools.$codemode.search)")
+    expect(runtime.instructions()).toContain(
+      "Available tools (PARTIAL — 0 of 3 shown; find the rest with tools.$codemode.search)",
+    )
     expect(runtime.instructions()).toContain("- thread (2 tools, none shown)")
     expect(runtime.instructions()).toContain("- orders (1 tool, none shown)")
     expect(runtime.instructions()).toMatch(/\$codemode\.search/)
     expect(runtime.instructions()).not.toMatch(/tools\.thread\.uploadFile\(input/)
 
-    const result = await Effect.runPromise(runtime.execute(`
+    const result = await Effect.runPromise(
+      runtime.execute(`
       return await tools.$codemode.search({
         query: "send message attachment upload file to current Discord thread",
         limit: 2
       })
-    `))
+    `),
+    )
     expect(result.ok).toBe(true)
     if (!result.ok) return
     expect(result.value).toStrictEqual({
@@ -666,19 +734,27 @@ describe("CodeMode public contract", () => {
     })
     expect(result.toolCalls).toStrictEqual([{ name: "$codemode.search" }])
 
-    const variants = await Effect.runPromise(runtime.execute(`
+    const variants = await Effect.runPromise(
+      runtime.execute(`
       return await Promise.all([
         tools.$codemode.search({ query: "file" }),
         tools.$codemode.search({ query: "image" })
       ])
-    `))
+    `),
+    )
     expect(variants.ok).toBe(true)
     if (variants.ok) {
-      expect((variants.value as Array<{ items: Array<{ path: string }> }>)[0]?.items[0]?.path).toBe("tools.thread.uploadFile")
-      expect((variants.value as Array<{ items: Array<{ path: string }> }>)[1]?.items[0]?.path).toBe("tools.thread.generateImage")
+      expect((variants.value as Array<{ items: Array<{ path: string }> }>)[0]?.items[0]?.path).toBe(
+        "tools.thread.uploadFile",
+      )
+      expect((variants.value as Array<{ items: Array<{ path: string }> }>)[1]?.items[0]?.path).toBe(
+        "tools.thread.generateImage",
+      )
     }
 
-    const removed = await Effect.runPromise(runtime.execute(`return await tools.$codemode.describe({ path: "thread.uploadFile" })`))
+    const removed = await Effect.runPromise(
+      runtime.execute(`return await tools.$codemode.describe({ path: "thread.uploadFile" })`),
+    )
     expect(removed.ok).toBe(false)
     if (!removed.ok) expect(removed.error.kind).toBe("UnknownTool")
   })
@@ -706,15 +782,19 @@ describe("CodeMode public contract", () => {
     }
 
     for (const query of ["many.tool13", "tools.many.tool13"]) {
-      const exact = await Effect.runPromise(runtime.execute(`return await tools.$codemode.search({ query: ${JSON.stringify(query)} })`))
+      const exact = await Effect.runPromise(
+        runtime.execute(`return await tools.$codemode.search({ query: ${JSON.stringify(query)} })`),
+      )
       expect(exact.ok).toBe(true)
       if (exact.ok) {
         expect(exact.value).toStrictEqual({
-          items: [{
-            path: "tools.many.tool13",
-            description: "Numbered tool 13",
-            signature: "tools.many.tool13(input: {\n  id: string\n}): Promise<string>",
-          }],
+          items: [
+            {
+              path: "tools.many.tool13",
+              description: "Numbered tool 13",
+              signature: "tools.many.tool13(input: {\n  id: string\n}): Promise<string>",
+            },
+          ],
           total: 1,
         })
       }
@@ -737,20 +817,23 @@ describe("CodeMode public contract", () => {
     })
 
     // Empty query + namespace browses just that namespace, alphabetical by path.
-    const browse = await Effect.runPromise(runtime.execute(
-      `return await tools.$codemode.search({ query: "", namespace: "github" })`,
-    ))
+    const browse = await Effect.runPromise(
+      runtime.execute(`return await tools.$codemode.search({ query: "", namespace: "github" })`),
+    )
     expect(browse.ok).toBe(true)
     if (browse.ok) {
       const value = browse.value as { items: Array<{ path: string }>; total: number }
       expect(value.total).toBe(2)
-      expect(value.items.map((item) => item.path)).toStrictEqual(["tools.github.create_issue", "tools.github.list_issues"])
+      expect(value.items.map((item) => item.path)).toStrictEqual([
+        "tools.github.create_issue",
+        "tools.github.list_issues",
+      ])
     }
 
     // A query + namespace ranks within that namespace only.
-    const scoped = await Effect.runPromise(runtime.execute(
-      `return await tools.$codemode.search({ query: "issues", namespace: "linear" })`,
-    ))
+    const scoped = await Effect.runPromise(
+      runtime.execute(`return await tools.$codemode.search({ query: "issues", namespace: "linear" })`),
+    )
     expect(scoped.ok).toBe(true)
     if (scoped.ok) {
       const value = scoped.value as { items: Array<{ path: string }>; total: number }
@@ -758,9 +841,9 @@ describe("CodeMode public contract", () => {
       expect(value.items[0]?.path).toBe("tools.linear.list_issues")
     }
 
-    const invalid = await Effect.runPromise(runtime.execute(
-      `return await tools.$codemode.search({ query: "issues", namespace: 7 })`,
-    ))
+    const invalid = await Effect.runPromise(
+      runtime.execute(`return await tools.$codemode.search({ query: "issues", namespace: 7 })`),
+    )
     expect(invalid.ok).toBe(false)
     if (!invalid.ok) expect(invalid.error.kind).toBe("InvalidToolInput")
   })
@@ -785,9 +868,9 @@ describe("CodeMode public contract", () => {
 
     // "attachment" appears in neither path nor description — only in the input schema's
     // property names, which the searchable text includes.
-    const byParameter = await Effect.runPromise(runtime.execute(
-      `return await tools.$codemode.search({ query: "attachment" })`,
-    ))
+    const byParameter = await Effect.runPromise(
+      runtime.execute(`return await tools.$codemode.search({ query: "attachment" })`),
+    )
     expect(byParameter.ok).toBe(true)
     if (byParameter.ok) {
       const value = byParameter.value as { items: Array<{ path: string }>; total: number }
@@ -796,9 +879,9 @@ describe("CodeMode public contract", () => {
     }
 
     // Substring matching: a partial word ("docum") still hits the description.
-    const bySubstring = await Effect.runPromise(runtime.execute(
-      `return await tools.$codemode.search({ query: "docum" })`,
-    ))
+    const bySubstring = await Effect.runPromise(
+      runtime.execute(`return await tools.$codemode.search({ query: "docum" })`),
+    )
     expect(bySubstring.ok).toBe(true)
     if (bySubstring.ok) {
       const value = bySubstring.value as { items: Array<{ path: string }>; total: number }
@@ -825,9 +908,9 @@ describe("CodeMode public contract", () => {
     })
 
     // "issues" still finds the singular-only tool (term OR singular(term) per field)...
-    const plural = await Effect.runPromise(runtime.execute(
-      `return await tools.$codemode.search({ query: "issues", namespace: "tracker" })`,
-    ))
+    const plural = await Effect.runPromise(
+      runtime.execute(`return await tools.$codemode.search({ query: "issues", namespace: "tracker" })`),
+    )
     expect(plural.ok).toBe(true)
     if (plural.ok) {
       const value = plural.value as { items: Array<{ path: string }>; total: number }
@@ -836,14 +919,15 @@ describe("CodeMode public contract", () => {
     }
 
     // ...while a true "issues" path match still outranks the singular-only description match.
-    const ranked = await Effect.runPromise(runtime.execute(
-      `return await tools.$codemode.search({ query: "issues" })`,
-    ))
+    const ranked = await Effect.runPromise(runtime.execute(`return await tools.$codemode.search({ query: "issues" })`))
     expect(ranked.ok).toBe(true)
     if (ranked.ok) {
       const value = ranked.value as { items: Array<{ path: string }>; total: number }
       expect(value.total).toBe(2)
-      expect(value.items.map((item) => item.path)).toStrictEqual(["tools.github.list_issues", "tools.tracker.fetch_all"])
+      expect(value.items.map((item) => item.path)).toStrictEqual([
+        "tools.github.list_issues",
+        "tools.tracker.fetch_all",
+      ])
     }
   })
 
@@ -882,8 +966,12 @@ describe("CodeMode public contract", () => {
       run: () => Effect.succeed("ok"),
     })
     const expensive = Tool.make({
-      description: "An expensive tool whose description alone consumes far more than the remaining inline catalog byte budget for this runtime",
-      input: Schema.Struct({ someRatherLongParameterName: Schema.String, anotherEvenLongerParameterName: Schema.Number }),
+      description:
+        "An expensive tool whose description alone consumes far more than the remaining inline catalog byte budget for this runtime",
+      input: Schema.Struct({
+        someRatherLongParameterName: Schema.String,
+        anotherEvenLongerParameterName: Schema.Number,
+      }),
       output: Schema.String,
       run: () => Effect.succeed("ok"),
     })
@@ -896,7 +984,9 @@ describe("CodeMode public contract", () => {
     })
 
     const instructions = runtime.instructions()
-    expect(instructions).toContain("Available tools (PARTIAL — 2 of 3 shown; find the rest with tools.$codemode.search)")
+    expect(instructions).toContain(
+      "Available tools (PARTIAL — 2 of 3 shown; find the rest with tools.$codemode.search)",
+    )
     expect(instructions).toContain("- alpha (2 tools, 1 shown)")
     expect(instructions).toContain("  - tools.alpha.cheap(input: { q: string }): Promise<string> // Cheap")
     expect(instructions).not.toContain("tools.alpha.expensive(")
@@ -912,10 +1002,11 @@ describe("CodeMode public contract", () => {
       description: "Double a number",
       input: Schema.Struct({ value: Schema.NumberFromString }),
       output: Schema.NumberFromString,
-      run: ({ value }) => Effect.sync(() => {
-        observed.push(value)
-        return String(value * 2)
-      }),
+      run: ({ value }) =>
+        Effect.sync(() => {
+          observed.push(value)
+          return String(value * 2)
+        }),
     })
     const runtime = CodeMode.make({
       tools: { math: { double: transformed } },
@@ -934,9 +1025,11 @@ describe("CodeMode public contract", () => {
   })
 
   test("returns JSON-safe data and normalizes undefined to null", async () => {
-    const result = await Effect.runPromise(CodeMode.execute({
-      code: `return { top: undefined, nested: [1, undefined] }`,
-    }))
+    const result = await Effect.runPromise(
+      CodeMode.execute({
+        code: `return { top: undefined, nested: [1, undefined] }`,
+      }),
+    )
     expect(result).toStrictEqual({
       ok: true,
       value: { top: null, nested: [1, null] },
@@ -947,18 +1040,20 @@ describe("CodeMode public contract", () => {
 
   test("rejects invalid configuration and discovery limits", async () => {
     expect(() => CodeMode.execute({ code: "return 1", limits: { timeoutMs: 0 } })).toThrow(RangeError)
-    expect(() => CodeMode.execute({ code: "return 1", limits: { timeoutMs: Number.POSITIVE_INFINITY } })).toThrow(RangeError)
+    expect(() => CodeMode.execute({ code: "return 1", limits: { timeoutMs: Number.POSITIVE_INFINITY } })).toThrow(
+      RangeError,
+    )
     expect(() => CodeMode.execute({ code: "return 1", limits: { maxToolCalls: -1 } })).toThrow(RangeError)
     expect(() => CodeMode.execute({ code: "return 1", limits: { maxOutputBytes: -1 } })).toThrow(RangeError)
 
     expect(() => CodeMode.make({ tools, discovery: { maxInlineCatalogTokens: -1 } })).toThrow(RangeError)
 
-    const result = await Effect.runPromise(CodeMode.make({
-      tools,
-      discovery: { maxInlineCatalogTokens: 0 },
-    }).execute(
-      `return await tools.$codemode.search({ query: "order", limit: 0.5 })`,
-    ))
+    const result = await Effect.runPromise(
+      CodeMode.make({
+        tools,
+        discovery: { maxInlineCatalogTokens: 0 },
+      }).execute(`return await tools.$codemode.search({ query: "order", limit: 0.5 })`),
+    )
     expect(result.ok).toBe(false)
     if (result.ok) return
     expect(result.error.kind).toBe("InvalidToolInput")
@@ -979,14 +1074,16 @@ describe("CodeMode public contract", () => {
       output: Schema.Number,
       run: () => Effect.succeed(1),
     })
-    const result = await Effect.runPromise(CodeMode.execute({
-      tools: { host: { count: counter } },
-      code: `
+    const result = await Effect.runPromise(
+      CodeMode.execute({
+        tools: { host: { count: counter } },
+        code: `
         let total = 0
         for (let i = 0; i < 150; i += 1) total += await tools.host.count({})
         return total
       `,
-    }))
+      }),
+    )
     expect(result).toMatchObject({ ok: true, value: 150 })
     if (result.ok) expect(result.toolCalls.length).toBe(150)
   })
@@ -1008,8 +1105,6 @@ describe("CodeMode public contract", () => {
   })
 
   test("reserves the discovery namespace", () => {
-    expect(() => CodeMode.make({ tools: { $codemode: { lookup } } })).toThrow(
-      /reserved for CodeMode discovery tools/,
-    )
+    expect(() => CodeMode.make({ tools: { $codemode: { lookup } } })).toThrow(/reserved for CodeMode discovery tools/)
   })
 })

+ 24 - 12
packages/codemode/test/enumeration.test.ts

@@ -36,10 +36,12 @@ const error = async (code: string) => {
 
 describe("Object.keys over tool references", () => {
   test("enumerates top-level namespaces (the transcript program)", async () => {
-    expect(await value(`
+    expect(
+      await value(`
       const namespaces = Object.keys(tools)
       return { namespaces, count: namespaces.length }
-    `)).toEqual({ namespaces: ["github", "memory", "playwright"], count: 3 })
+    `),
+    ).toEqual({ namespaces: ["github", "memory", "playwright"], count: 3 })
   })
 
   test("enumerates tool names at a nested namespace", async () => {
@@ -92,7 +94,8 @@ describe("Object.keys over arrays", () => {
 
 describe("for...in", () => {
   test("iterates own enumerable keys of a plain object with break/continue", async () => {
-    expect(await value(`
+    expect(
+      await value(`
       const seen = []
       for (const key in { a: 1, b: 2, c: 3, d: 4 }) {
         if (key === "b") continue
@@ -100,41 +103,50 @@ describe("for...in", () => {
         seen.push(key)
       }
       return seen
-    `)).toEqual(["a", "c"])
+    `),
+    ).toEqual(["a", "c"])
   })
 
   test("iterates index strings over arrays", async () => {
-    expect(await value(`
+    expect(
+      await value(`
       const indexes = []
       for (const i in ["x", "y", "z"]) {
         if (i === "2") break
         indexes.push(i)
       }
       return indexes
-    `)).toEqual(["0", "1"])
+    `),
+    ).toEqual(["0", "1"])
   })
 
   test("supports let declarations and bare identifiers", async () => {
-    expect(await value(`
+    expect(
+      await value(`
       let last = ""
       for (let key in { a: 1, b: 2 }) last = key
       return last
-    `)).toBe("b")
-    expect(await value(`
+    `),
+    ).toBe("b")
+    expect(
+      await value(`
       let key = "before"
       for (key in { only: 1 }) {}
       return key
-    `)).toBe("only")
+    `),
+    ).toBe("only")
   })
 
   test("enumerates namespaces and tools from the host tool tree", async () => {
-    expect(await value(`
+    expect(
+      await value(`
       const names = []
       for (const ns in tools) {
         for (const name in tools[ns]) names.push(ns + "." + name)
       }
       return names
-    `)).toEqual(["github.list_issues", "github.get_issue", "memory.search", "playwright.navigate"])
+    `),
+    ).toEqual(["github.list_issues", "github.get_issue", "memory.search", "playwright.navigate"])
   })
 
   test("unsupported values fail with a hint at for...of and Object.keys", async () => {

+ 97 - 52
packages/codemode/test/parity.test.ts

@@ -143,21 +143,40 @@ describe("H1: NaN/Infinity flow as intermediates and normalize to null at the bo
 
 describe("Error values and instanceof", () => {
   test("new Error carries name/message and is instanceof Error", async () => {
-    expect(await value(`const e = new Error("boom"); return [e instanceof Error, e.name, e.message]`)).toEqual([true, "Error", "boom"])
+    expect(await value(`const e = new Error("boom"); return [e instanceof Error, e.name, e.message]`)).toEqual([
+      true,
+      "Error",
+      "boom",
+    ])
   })
 
   test("Error without new behaves like new Error", async () => {
-    expect(await value(`const e = Error("plain"); return [e instanceof Error, e.name, e.message]`)).toEqual([true, "Error", "plain"])
-    expect(await value(`const e = new Error(); return [e.name, e.message, e instanceof Error]`)).toEqual(["Error", "", true])
+    expect(await value(`const e = Error("plain"); return [e instanceof Error, e.name, e.message]`)).toEqual([
+      true,
+      "Error",
+      "plain",
+    ])
+    expect(await value(`const e = new Error(); return [e.name, e.message, e instanceof Error]`)).toEqual([
+      "Error",
+      "",
+      true,
+    ])
   })
 
   test("specific error types are instanceof themselves and Error, not each other", async () => {
-    expect(await value(`const e = new TypeError("t"); return [e instanceof TypeError, e instanceof Error, e instanceof RangeError]`)).toEqual([true, true, false])
+    expect(
+      await value(
+        `const e = new TypeError("t"); return [e instanceof TypeError, e instanceof Error, e instanceof RangeError]`,
+      ),
+    ).toEqual([true, true, false])
     expect(await value(`return new Error("e") instanceof TypeError`)).toBe(false)
   })
 
   test("thrown errors keep instanceof through try/catch", async () => {
-    expect(await value(`try { throw new Error("x") } catch (e) { return [e instanceof Error, e.message] }`)).toEqual([true, "x"])
+    expect(await value(`try { throw new Error("x") } catch (e) { return [e instanceof Error, e.message] }`)).toEqual([
+      true,
+      "x",
+    ])
   })
 
   test("interpreter runtime failures are caught as Error values", async () => {
@@ -168,33 +187,48 @@ describe("Error values and instanceof", () => {
   test("caught failures carry the constructor name the real-JS failure would have", async () => {
     // JSON.parse throws SyntaxError: name and specific-instanceof both carry through, and the
     // message keeps the engine's position detail.
-    expect(await value(`
+    expect(
+      await value(`
       try { JSON.parse("{oops") } catch (e) {
         return [e.name, e instanceof SyntaxError, e instanceof Error, e instanceof TypeError, e.message.includes("JSON")]
       }
-    `)).toEqual(["SyntaxError", true, true, false, true])
-    expect(await value(`try { undeclared() } catch (e) { return [e.name, e instanceof ReferenceError] }`))
-      .toEqual(["ReferenceError", true])
-    expect(await value(`try { const c = 1; c = 2 } catch (e) { return [e.name, e instanceof TypeError] }`))
-      .toEqual(["TypeError", true])
-    expect(await value(`try { "a".normalize("NOPE") } catch (e) { return [e.name, e instanceof RangeError] }`))
-      .toEqual(["RangeError", true])
-    expect(await value(`try { "a".match("(") } catch (e) { return [e.name, e instanceof SyntaxError] }`))
-      .toEqual(["SyntaxError", true])
-    expect(await value(`try { new RegExp("(") } catch (e) { return [e.name, e instanceof SyntaxError] }`))
-      .toEqual(["SyntaxError", true])
+    `),
+    ).toEqual(["SyntaxError", true, true, false, true])
+    expect(await value(`try { undeclared() } catch (e) { return [e.name, e instanceof ReferenceError] }`)).toEqual([
+      "ReferenceError",
+      true,
+    ])
+    expect(await value(`try { const c = 1; c = 2 } catch (e) { return [e.name, e instanceof TypeError] }`)).toEqual([
+      "TypeError",
+      true,
+    ])
+    expect(await value(`try { "a".normalize("NOPE") } catch (e) { return [e.name, e instanceof RangeError] }`)).toEqual(
+      ["RangeError", true],
+    )
+    expect(await value(`try { "a".match("(") } catch (e) { return [e.name, e instanceof SyntaxError] }`)).toEqual([
+      "SyntaxError",
+      true,
+    ])
+    expect(await value(`try { new RegExp("(") } catch (e) { return [e.name, e instanceof SyntaxError] }`)).toEqual([
+      "SyntaxError",
+      true,
+    ])
   })
 
   test("diagnostics without a specific real-JS analogue are named plain Error", async () => {
-    expect(await value(`try { JSON.parse(5) } catch (e) { return [e.name, e instanceof Error] }`))
-      .toEqual(["Error", true])
+    expect(await value(`try { JSON.parse(5) } catch (e) { return [e.name, e instanceof Error] }`)).toEqual([
+      "Error",
+      true,
+    ])
   })
 
   test("Promise.allSettled rejection reasons are Error values", async () => {
-    expect(await value(`
+    expect(
+      await value(`
       const settled = await Promise.allSettled([Promise.reject(new Error("b"))])
       return [settled[0].reason instanceof Error, settled[0].reason.message]
-    `)).toEqual([true, "b"])
+    `),
+    ).toEqual([true, "b"])
   })
 
   test("non-error thrown values are not instanceof Error", async () => {
@@ -203,7 +237,11 @@ describe("Error values and instanceof", () => {
   })
 
   test("plain data is never instanceof Error", async () => {
-    expect(await value(`return [({}) instanceof Error, "s" instanceof Error, null instanceof Error]`)).toEqual([false, false, false])
+    expect(await value(`return [({}) instanceof Error, "s" instanceof Error, null instanceof Error]`)).toEqual([
+      false,
+      false,
+      false,
+    ])
   })
 
   test("error values still serialize as plain { name, message } data", async () => {
@@ -227,17 +265,29 @@ describe("Error values and instanceof", () => {
 
 describe("array methods: splice, fill, copyWithin, keys/values/entries", () => {
   test("splice removes in place and returns the removed elements", async () => {
-    expect(await value(`const a = [1,2,3,4]; const removed = a.splice(1, 2); return { removed, a }`)).toEqual({ removed: [2, 3], a: [1, 4] })
+    expect(await value(`const a = [1,2,3,4]; const removed = a.splice(1, 2); return { removed, a }`)).toEqual({
+      removed: [2, 3],
+      a: [1, 4],
+    })
   })
 
   test("splice inserts new elements at the cut", async () => {
     expect(await value(`const a = ["a","d"]; a.splice(1, 0, "b", "c"); return a`)).toEqual(["a", "b", "c", "d"])
-    expect(await value(`const a = [1,2,3]; const removed = a.splice(1, 1, "x"); return { removed, a }`)).toEqual({ removed: [2], a: [1, "x", 3] })
+    expect(await value(`const a = [1,2,3]; const removed = a.splice(1, 1, "x"); return { removed, a }`)).toEqual({
+      removed: [2],
+      a: [1, "x", 3],
+    })
   })
 
   test("splice with one argument removes to the end; negative start counts back", async () => {
-    expect(await value(`const a = [1,2,3]; const removed = a.splice(1); return { removed, a }`)).toEqual({ removed: [2, 3], a: [1] })
-    expect(await value(`const a = [1,2,3]; const removed = a.splice(-1); return { removed, a }`)).toEqual({ removed: [3], a: [1, 2] })
+    expect(await value(`const a = [1,2,3]; const removed = a.splice(1); return { removed, a }`)).toEqual({
+      removed: [2, 3],
+      a: [1],
+    })
+    expect(await value(`const a = [1,2,3]; const removed = a.splice(-1); return { removed, a }`)).toEqual({
+      removed: [3],
+      a: [1, 2],
+    })
   })
 
   test("splice rejects inserting a container into itself", async () => {
@@ -258,11 +308,13 @@ describe("array methods: splice, fill, copyWithin, keys/values/entries", () => {
   test("keys/values/entries return arrays usable with for...of and spread", async () => {
     expect(await value(`return [...["x","y","z"].keys()]`)).toEqual([0, 1, 2])
     expect(await value(`return ["x","y"].values()`)).toEqual(["x", "y"])
-    expect(await value(`
+    expect(
+      await value(`
       const out = []
       for (const [index, item] of ["a","b"].entries()) out.push(index + ":" + item)
       return out
-    `)).toEqual(["0:a", "1:b"])
+    `),
+    ).toEqual(["0:a", "1:b"])
     expect(await value(`return [...[7].entries()]`)).toEqual([[0, 7]])
   })
 })
@@ -300,40 +352,33 @@ describe("compound assignment matches its binary operator", () => {
   }
 
   test("sandbox Date += concatenates its string form, like d = d + 1", async () => {
-    const result = await pair(
-      `let d = new Date(1000); d += 1; return d`,
-      `let d = new Date(1000); d = d + 1; return d`,
-    )
+    const result = await pair(`let d = new Date(1000); d += 1; return d`, `let d = new Date(1000); d = d + 1; return d`)
     expect(result).toBe("1970-01-01T00:00:01.000Z1")
   })
 
   test("sandbox Date numeric compound ops use its time value", async () => {
-    expect(await pair(
-      `let d = new Date(1000); d -= 400; return d`,
-      `let d = new Date(1000); d = d - 400; return d`,
-    )).toBe(600)
-    expect(await pair(
-      `let d = new Date(1000); d /= 4; return d`,
-      `let d = new Date(1000); d = d / 4; return d`,
-    )).toBe(250)
+    expect(
+      await pair(`let d = new Date(1000); d -= 400; return d`, `let d = new Date(1000); d = d - 400; return d`),
+    ).toBe(600)
+    expect(await pair(`let d = new Date(1000); d /= 4; return d`, `let d = new Date(1000); d = d / 4; return d`)).toBe(
+      250,
+    )
   })
 
   test("string += object/array matches x = x + obj", async () => {
-    expect(await pair(
-      `let x = "a"; x += { b: 1 }; return x`,
-      `let x = "a"; x = x + { b: 1 }; return x`,
-    )).toBe("a[object Object]")
-    expect(await pair(
-      `let x = "a"; x += [1, 2]; return x`,
-      `let x = "a"; x = x + [1, 2]; return x`,
-    )).toBe("a1,2")
+    expect(await pair(`let x = "a"; x += { b: 1 }; return x`, `let x = "a"; x = x + { b: 1 }; return x`)).toBe(
+      "a[object Object]",
+    )
+    expect(await pair(`let x = "a"; x += [1, 2]; return x`, `let x = "a"; x = x + [1, 2]; return x`)).toBe("a1,2")
   })
 
   test("compound assignment through a member target coerces the same way", async () => {
-    expect(await pair(
-      `const o = { s: "t" }; o.s += new Date(0); return o.s`,
-      `const o = { s: "t" }; o.s = o.s + new Date(0); return o.s`,
-    )).toBe("t1970-01-01T00:00:00.000Z")
+    expect(
+      await pair(
+        `const o = { s: "t" }; o.s += new Date(0); return o.s`,
+        `const o = { s: "t" }; o.s = o.s + new Date(0); return o.s`,
+      ),
+    ).toBe("t1970-01-01T00:00:00.000Z")
   })
 
   test("numeric and string compound operators sweep identically to their expansions", async () => {

+ 54 - 27
packages/codemode/test/promise.test.ts

@@ -23,7 +23,7 @@ const sleepyTool = (trace: Trace) =>
     input: Schema.Struct({ id: Schema.Number, ms: Schema.optionalKey(Schema.Number) }),
     output: Schema.Number,
     run: ({ id, ms }) =>
-      Effect.gen(function*() {
+      Effect.gen(function* () {
         trace.starts.push(id)
         trace.active += 1
         trace.maxActive = Math.max(trace.maxActive, trace.active)
@@ -31,10 +31,14 @@ const sleepyTool = (trace: Trace) =>
         trace.active -= 1
         trace.completed += 1
         return id
-      }).pipe(Effect.onInterrupt(() => Effect.sync(() => {
-        trace.active -= 1
-        trace.interrupted += 1
-      }))),
+      }).pipe(
+        Effect.onInterrupt(() =>
+          Effect.sync(() => {
+            trace.active -= 1
+            trace.interrupted += 1
+          }),
+        ),
+      ),
   })
 
 const failingTool = Tool.make({
@@ -46,11 +50,13 @@ const failingTool = Tool.make({
 
 const run = (code: string, options: { trace?: Trace; limits?: ExecutionLimits } = {}): Promise<ExecuteResult> => {
   const trace = options.trace ?? makeTrace()
-  return Effect.runPromise(CodeMode.execute({
-    tools: { host: { sleepy: sleepyTool(trace), fail: failingTool } },
-    code,
-    ...(options.limits ? { limits: options.limits } : {}),
-  }))
+  return Effect.runPromise(
+    CodeMode.execute({
+      tools: { host: { sleepy: sleepyTool(trace), fail: failingTool } },
+      code,
+      ...(options.limits ? { limits: options.limits } : {}),
+    }),
+  )
 }
 
 const value = async (code: string, options: { trace?: Trace; limits?: ExecutionLimits } = {}) => {
@@ -121,7 +127,8 @@ describe("first-class promise values", () => {
   })
 
   test("an awaited failure is catchable exactly like a synchronous throw", async () => {
-    expect(await value(`
+    expect(
+      await value(`
       const p = tools.host.fail({})
       try {
         await p
@@ -129,7 +136,8 @@ describe("first-class promise values", () => {
       } catch (e) {
         return e.message
       }
-    `)).toBe("Lookup refused")
+    `),
+    ).toBe("Lookup refused")
   })
 
   test("a fire-and-forget call completes before the execution ends", async () => {
@@ -186,20 +194,24 @@ describe("promises at data boundaries", () => {
 
 describe("Promise.all over arbitrary arrays", () => {
   test("mixes promises and plain values, preserving order", async () => {
-    expect(await value(`
+    expect(
+      await value(`
       return await Promise.all([tools.host.sleepy({ id: 1 }), "plain", tools.host.sleepy({ id: 2 }), 42])
-    `)).toEqual([1, "plain", 2, 42])
+    `),
+    ).toEqual([1, "plain", 2, 42])
   })
 
   test("accepts arrays built beforehand, passed as identifiers, and spread elements", async () => {
-    expect(await value(`
+    expect(
+      await value(`
       const calls = []
       calls.push(tools.host.sleepy({ id: 1 }))
       calls.push(7)
       const more = [tools.host.sleepy({ id: 2 })]
       const batch = [...calls, ...more, "x"]
       return await Promise.all(batch)
-    `)).toEqual([1, 7, 2, "x"])
+    `),
+    ).toEqual([1, 7, 2, "x"])
   })
 
   test("runs items.map tool calls in parallel", async () => {
@@ -238,14 +250,16 @@ describe("Promise.all over arbitrary arrays", () => {
   })
 
   test("rejects with the first failure, catchable in-program", async () => {
-    expect(await value(`
+    expect(
+      await value(`
       try {
         await Promise.all([tools.host.sleepy({ id: 1 }), tools.host.fail({})])
         return "no"
       } catch (e) {
         return e.message
       }
-    `)).toBe("Lookup refused")
+    `),
+    ).toBe("Lookup refused")
   })
 
   test("a non-collection argument is a clear error", async () => {
@@ -264,14 +278,16 @@ describe("Promise.all over arbitrary arrays", () => {
 
 describe("Promise.allSettled", () => {
   test("reports fulfilled and rejected outcomes with catch-normalized reasons", async () => {
-    expect(await value(`
+    expect(
+      await value(`
       return await Promise.allSettled([
         tools.host.sleepy({ id: 5 }),
         tools.host.fail({}),
         "plain",
         Promise.reject(new Error("boom")),
       ])
-    `)).toEqual([
+    `),
+    ).toEqual([
       { status: "fulfilled", value: 5 },
       { status: "rejected", reason: { name: "Error", message: "Lookup refused" } },
       { status: "fulfilled", value: "plain" },
@@ -306,7 +322,8 @@ describe("Promise.race", () => {
   })
 
   test("awaiting an interrupted loser afterwards is a catchable program failure", async () => {
-    expect(await value(`
+    expect(
+      await value(`
       const fast = tools.host.sleepy({ id: 1, ms: 10 })
       const slow = tools.host.sleepy({ id: 2, ms: 5000 })
       const winner = await Promise.race([fast, slow])
@@ -316,23 +333,31 @@ describe("Promise.race", () => {
       } catch (e) {
         return { winner, caught: e.message }
       }
-    `)).toEqual({ winner: 1, caught: "This tool call was interrupted because another value settled a Promise.race first." })
+    `),
+    ).toEqual({
+      winner: 1,
+      caught: "This tool call was interrupted because another value settled a Promise.race first.",
+    })
   })
 
   test("a rejection can win the race", async () => {
-    expect(await value(`
+    expect(
+      await value(`
       try {
         await Promise.race([tools.host.fail({}), tools.host.sleepy({ id: 1, ms: 5000 })])
         return "no"
       } catch (e) {
         return e.message
       }
-    `)).toBe("Lookup refused")
+    `),
+    ).toBe("Lookup refused")
   })
 
   test("a plain value wins over pending promises", async () => {
     const trace = makeTrace()
-    expect(await value(`return await Promise.race([tools.host.sleepy({ id: 1, ms: 5000 }), "immediate"])`, { trace })).toBe("immediate")
+    expect(
+      await value(`return await Promise.race([tools.host.sleepy({ id: 1, ms: 5000 }), "immediate"])`, { trace }),
+    ).toBe("immediate")
     expect(trace.interrupted).toBe(1)
   })
 
@@ -350,14 +375,16 @@ describe("Promise.resolve / Promise.reject", () => {
   })
 
   test("reject produces a promise whose await throws the reason", async () => {
-    expect(await value(`
+    expect(
+      await value(`
       try {
         await Promise.reject("nope")
         return "no"
       } catch (e) {
         return e
       }
-    `)).toBe("nope")
+    `),
+    ).toBe("nope")
   })
 })
 

+ 19 - 14
packages/codemode/test/signature.test.ts

@@ -83,15 +83,9 @@ describe("pretty signature rendering", () => {
       true,
     )
     expect(pretty).toBe(
-      [
-        "{",
-        "  /** Search filter */",
-        "  filter?: {",
-        "    /** Issue state */",
-        "    state?: string",
-        "  }",
-        "}",
-      ].join("\n"),
+      ["{", "  /** Search filter */", "  filter?: {", "    /** Issue state */", "    state?: string", "  }", "}"].join(
+        "\n",
+      ),
     )
   })
 
@@ -119,7 +113,14 @@ describe("pretty signature rendering", () => {
     expect(pretty).toContain("  /** @deprecated */\n  legacy?: string")
     expect(pretty).toContain("  /** @format uri */\n  homepage?: string")
     expect(pretty).toContain(
-      ["  /**", '   * @default ["a","b"]', "   * @minItems 2", "   * @maxItems 5", "   */", "  tags?: Array<string>"].join("\n"),
+      [
+        "  /**",
+        '   * @default ["a","b"]',
+        "   * @minItems 2",
+        "   * @maxItems 5",
+        "   */",
+        "  tags?: Array<string>",
+      ].join("\n"),
     )
   })
 
@@ -212,7 +213,11 @@ describe("non-identifier property names render as quoted keys", () => {
     const tool = Tool.make({
       description: "Adapter tool with awkward field names",
       input: rawSchema,
-      output: { type: "object", properties: { "content-type": { type: "string" } }, required: ["content-type"] } as const,
+      output: {
+        type: "object",
+        properties: { "content-type": { type: "string" } },
+        required: ["content-type"],
+      } as const,
       run: () => Effect.succeed({ "content-type": "text/plain" }),
     })
     expect(inputTypeScript(tool)).toContain('"foo-bar"?: string')
@@ -269,9 +274,9 @@ describe("pretty signatures in search results", () => {
   const runtime = CodeMode.make({ tools: { github: { list_issues: listIssues }, orders: { lookup: lookupOrder } } })
 
   const search = async (query: string) => {
-    const result = await Effect.runPromise(runtime.execute(
-      `return await tools.$codemode.search({ query: ${JSON.stringify(query)} })`,
-    ))
+    const result = await Effect.runPromise(
+      runtime.execute(`return await tools.$codemode.search({ query: ${JSON.stringify(query)} })`),
+    )
     expect(result.ok).toBe(true)
     if (!result.ok) throw new Error("search failed")
     return result.value as { items: Array<{ path: string; signature: string }>; total: number }

+ 116 - 48
packages/codemode/test/stdlib.test.ts

@@ -40,7 +40,11 @@ describe("Date", () => {
   })
 
   test("UTC getters read calendar components", async () => {
-    expect(await value(`const d = new Date("2024-03-05T06:07:08.009Z"); return [d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), d.getUTCMilliseconds()]`)).toEqual([2024, 2, 5, 6, 7, 8, 9])
+    expect(
+      await value(
+        `const d = new Date("2024-03-05T06:07:08.009Z"); return [d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), d.getUTCMilliseconds()]`,
+      ),
+    ).toEqual([2024, 2, 5, 6, 7, 8, 9])
   })
 
   test("invalid dates yield NaN times, guardable in-sandbox", async () => {
@@ -49,7 +53,9 @@ describe("Date", () => {
   })
 
   test("toISOString on an invalid date is a catchable error", async () => {
-    expect(await value(`try { new Date("garbage").toISOString(); return "no" } catch { return "caught" }`)).toBe("caught")
+    expect(await value(`try { new Date("garbage").toISOString(); return "no" } catch { return "caught" }`)).toBe(
+      "caught",
+    )
   })
 
   test("template interpolation renders the ISO form", async () => {
@@ -72,14 +78,18 @@ describe("Date", () => {
   })
 
   test("sorting dates with a numeric comparator", async () => {
-    expect(await value(`
+    expect(
+      await value(`
       const dates = [new Date(3000), new Date(1000), new Date(2000)]
       return dates.sort((a, b) => a - b).map((d) => d.getTime())
-    `)).toEqual([1000, 2000, 3000])
+    `),
+    ).toEqual([1000, 2000, 3000])
   })
 
   test("new Date(year, month, day) accepts component form", async () => {
-    expect(await value(`const d = new Date(2024, 0, 2); return [d.getFullYear(), d.getMonth(), d.getDate()]`)).toEqual([2024, 0, 2])
+    expect(await value(`const d = new Date(2024, 0, 2); return [d.getFullYear(), d.getMonth(), d.getDate()]`)).toEqual([
+      2024, 0, 2,
+    ])
   })
 
   test("typeof and unknown properties are forgiving", async () => {
@@ -95,25 +105,31 @@ describe("RegExp", () => {
   })
 
   test("exec exposes captures and index", async () => {
-    expect(await value(`const m = /a(b+)/.exec("xxabbc"); return { full: m[0], group: m[1], index: m.index }`)).toEqual({
-      full: "abb",
-      group: "bb",
-      index: 2,
-    })
+    expect(await value(`const m = /a(b+)/.exec("xxabbc"); return { full: m[0], group: m[1], index: m.index }`)).toEqual(
+      {
+        full: "abb",
+        group: "bb",
+        index: 2,
+      },
+    )
     expect(await value(`return /a/.exec("zzz")`)).toBeNull()
   })
 
   test("named groups read through", async () => {
-    expect(await value(`const m = /(?<word>[a-z]+)-(?<num>\\d+)/.exec("id ab-42"); return m.groups.word + m.groups.num`)).toBe("ab42")
+    expect(
+      await value(`const m = /(?<word>[a-z]+)-(?<num>\\d+)/.exec("id ab-42"); return m.groups.word + m.groups.num`),
+    ).toBe("ab42")
   })
 
   test("global exec advances lastIndex across calls", async () => {
-    expect(await value(`
+    expect(
+      await value(`
       const r = /\\d+/g
       const first = r.exec("a1b22c")
       const second = r.exec("a1b22c")
       return [first[0], second[0]]
-    `)).toEqual(["1", "22"])
+    `),
+    ).toEqual(["1", "22"])
   })
 
   test("string match: non-global carries index, global lists all matches", async () => {
@@ -194,34 +210,51 @@ describe("RegExp", () => {
 
 describe("Map", () => {
   test("get/set/has/size with chaining", async () => {
-    expect(await value(`
+    expect(
+      await value(`
       const m = new Map()
       m.set("a", 1).set("b", 2)
       return { a: m.get("a"), b: m.get("b"), has: m.has("a"), miss: m.get("zz") === undefined, size: m.size }
-    `)).toEqual({ a: 1, b: 2, has: true, miss: true, size: 5 - 3 })
+    `),
+    ).toEqual({ a: 1, b: 2, has: true, miss: true, size: 5 - 3 })
   })
 
   test("object keys use identity", async () => {
-    expect(await value(`
+    expect(
+      await value(`
       const key = { id: 1 }
       const m = new Map()
       m.set(key, "hit")
       return [m.get(key), m.get({ id: 1 }) === undefined]
-    `)).toEqual(["hit", true])
+    `),
+    ).toEqual(["hit", true])
   })
 
   test("construction from entry pairs and another Map", async () => {
     expect(await value(`const m = new Map([["a", 1], ["b", 2]]); return m.get("b")`)).toBe(2)
-    expect(await value(`const m = new Map([["a", 1]]); const n = new Map(m); n.set("b", 2); return [n.get("a"), n.get("b"), m.has("b")]`)).toEqual([1, 2, false])
+    expect(
+      await value(
+        `const m = new Map([["a", 1]]); const n = new Map(m); n.set("b", 2); return [n.get("a"), n.get("b"), m.has("b")]`,
+      ),
+    ).toEqual([1, 2, false])
     expect((await error(`return new Map("nope")`)).message).toMatch(/\[key, value\] pairs/)
     expect((await error(`return new Map(["flat"])`)).message).toMatch(/\[key, value\] pairs/)
   })
 
   test("keys/values/entries return arrays", async () => {
-    expect(await value(`
+    expect(
+      await value(`
       const m = new Map([["a", 1], ["b", 2]])
       return { keys: m.keys(), values: m.values(), entries: m.entries() }
-    `)).toEqual({ keys: ["a", "b"], values: [1, 2], entries: [["a", 1], ["b", 2]] })
+    `),
+    ).toEqual({
+      keys: ["a", "b"],
+      values: [1, 2],
+      entries: [
+        ["a", 1],
+        ["b", 2],
+      ],
+    })
   })
 
   test("Object.fromEntries(map) and Array.from(map)", async () => {
@@ -230,13 +263,15 @@ describe("Map", () => {
   })
 
   test("for...of iterates [key, value] pairs with destructuring", async () => {
-    expect(await value(`
+    expect(
+      await value(`
       const m = new Map([["a", 1], ["b", 2]])
       let total = 0
       let names = ""
       for (const [key, count] of m) { names += key; total += count }
       return names + total
-    `)).toBe("ab3")
+    `),
+    ).toBe("ab3")
   })
 
   test("spread produces entry pairs", async () => {
@@ -244,32 +279,38 @@ describe("Map", () => {
   })
 
   test("forEach passes (value, key)", async () => {
-    expect(await value(`
+    expect(
+      await value(`
       const m = new Map([["a", 1], ["b", 2]])
       const seen = []
       m.forEach((count, key) => seen.push(key + count))
       return seen
-    `)).toEqual(["a1", "b2"])
+    `),
+    ).toEqual(["a1", "b2"])
   })
 
   test("delete and clear", async () => {
-    expect(await value(`
+    expect(
+      await value(`
       const m = new Map([["a", 1], ["b", 2]])
       const removed = m.delete("a")
       const missed = m.delete("zz")
       const sizeAfterDelete = m.size
       m.clear()
       return [removed, missed, sizeAfterDelete, m.size]
-    `)).toEqual([true, false, 1, 0])
+    `),
+    ).toEqual([true, false, 1, 0])
   })
 
   test("counting idiom: grouped tallies", async () => {
-    expect(await value(`
+    expect(
+      await value(`
       const words = ["a", "b", "a", "c", "a"]
       const counts = new Map()
       for (const word of words) counts.set(word, (counts.get(word) ?? 0) + 1)
       return Object.fromEntries(counts)
-    `)).toEqual({ a: 3, b: 1, c: 1 })
+    `),
+    ).toEqual({ a: 3, b: 1, c: 1 })
   })
 
   test("maps serialize to {} at the boundary, like JSON", async () => {
@@ -286,12 +327,14 @@ describe("Map", () => {
 
 describe("Set", () => {
   test("add/has/delete/size with chaining", async () => {
-    expect(await value(`
+    expect(
+      await value(`
       const s = new Set()
       s.add(1).add(2).add(1)
       const removed = s.delete(2)
       return [s.size, s.has(1), s.has(2), removed]
-    `)).toEqual([1, true, false, true])
+    `),
+    ).toEqual([1, true, false, true])
   })
 
   test("dedupe idiom: [...new Set(items)]", async () => {
@@ -308,11 +351,13 @@ describe("Set", () => {
   })
 
   test("for...of iterates values", async () => {
-    expect(await value(`
+    expect(
+      await value(`
       let total = 0
       for (const n of new Set([1, 2, 3])) total += n
       return total
-    `)).toBe(6)
+    `),
+    ).toBe(6)
   })
 
   test("sets serialize to {} at the boundary, like JSON", async () => {
@@ -338,21 +383,32 @@ describe("stdlib integration", () => {
   })
 
   test("dates inside Map values survive in-sandbox reads", async () => {
-    expect(await value(`
+    expect(
+      await value(`
       const m = new Map([["start", new Date(1000)]])
       return m.get("start").getTime()
-    `)).toBe(1000)
+    `),
+    ).toBe(1000)
   })
 
   test("instanceof recognizes the stdlib value types", async () => {
-    expect(await value(`return [new Date(0) instanceof Date, /a/ instanceof RegExp, new Map() instanceof Map, new Set() instanceof Set]`)).toEqual([true, true, true, true])
-    expect(await value(`return [[1] instanceof Array, [1] instanceof Object, ({}) instanceof Object, 5 instanceof Object]`)).toEqual([true, true, true, false])
+    expect(
+      await value(
+        `return [new Date(0) instanceof Date, /a/ instanceof RegExp, new Map() instanceof Map, new Set() instanceof Set]`,
+      ),
+    ).toEqual([true, true, true, true])
+    expect(
+      await value(`return [[1] instanceof Array, [1] instanceof Object, ({}) instanceof Object, 5 instanceof Object]`),
+    ).toEqual([true, true, true, false])
     expect(await value(`return [new Map() instanceof Set, "s" instanceof Date]`)).toEqual([false, false])
-    expect(await value(`const p = Promise.resolve(1); const isPromise = p instanceof Promise; await p; return isPromise`)).toBe(true)
+    expect(
+      await value(`const p = Promise.resolve(1); const isPromise = p instanceof Promise; await p; return isPromise`),
+    ).toBe(true)
   })
 
   test("realistic pipeline: parse, extract with regex, dedupe, count by day", async () => {
-    expect(await value(`
+    expect(
+      await value(`
       const raw = '[{"at":"2024-01-01T05:00:00Z","tag":"a b"},{"at":"2024-01-01T09:00:00Z","tag":"b c"},{"at":"2024-01-02T01:00:00Z","tag":"a"}]'
       const rows = JSON.parse(raw)
       const tags = new Set()
@@ -363,27 +419,34 @@ describe("stdlib integration", () => {
         byDay.set(day, (byDay.get(day) ?? 0) + 1)
       }
       return { tags: [...tags].sort((a, b) => (a < b ? -1 : 1)), byDay: Object.fromEntries(byDay) }
-    `)).toEqual({ tags: ["a", "b", "c"], byDay: { "2024-01-01": 2, "2024-01-02": 1 } })
+    `),
+    ).toEqual({ tags: ["a", "b", "c"], byDay: { "2024-01-01": 2, "2024-01-02": 1 } })
   })
 })
 
 describe("sandbox values at intra-sandbox checkpoints", () => {
   test("Object.values/entries keep Dates usable", async () => {
     expect(await value(`return Object.values({ d: new Date(0) })[0].getTime()`)).toBe(0)
-    expect(await value(`const [key, d] = Object.entries({ d: new Date(0) })[0]; return key + ":" + d.getTime()`)).toBe("d:0")
+    expect(await value(`const [key, d] = Object.entries({ d: new Date(0) })[0]; return key + ":" + d.getTime()`)).toBe(
+      "d:0",
+    )
   })
 
   test("Object.assign keeps Maps usable", async () => {
-    expect(await value(`const merged = Object.assign({}, { m: new Map([["a", 1]]) }); return merged.m.get("a")`)).toBe(1)
+    expect(await value(`const merged = Object.assign({}, { m: new Map([["a", 1]]) }); return merged.m.get("a")`)).toBe(
+      1,
+    )
   })
 
   test("object and array spread keep sandbox values usable", async () => {
-    expect(await value(`
+    expect(
+      await value(`
       const src = { m: new Map([["a", 1]]) }
       const copy = { ...src }
       copy.m.set("b", 2)
       return [copy.m.get("a"), src.m.get("b")]
-    `)).toEqual([1, 2])
+    `),
+    ).toEqual([1, 2])
     expect(await value(`const list = [new Date(1000)]; const copy = [...list]; return copy[0].getTime()`)).toBe(1000)
   })
 
@@ -404,7 +467,10 @@ describe("sandbox values at intra-sandbox checkpoints", () => {
   })
 
   test("the host boundary still serializes JSON forms: results, JSON.stringify, and tool arguments", async () => {
-    expect(await value(`return { d: new Date(0), m: new Map([["a", 1]]) }`)).toEqual({ d: "1970-01-01T00:00:00.000Z", m: {} })
+    expect(await value(`return { d: new Date(0), m: new Map([["a", 1]]) }`)).toEqual({
+      d: "1970-01-01T00:00:00.000Z",
+      m: {},
+    })
     expect(await value(`return JSON.stringify({ d: new Date(0) })`)).toBe('{"d":"1970-01-01T00:00:00.000Z"}')
 
     const observed: Array<unknown> = []
@@ -417,10 +483,12 @@ describe("sandbox values at intra-sandbox checkpoints", () => {
           return "ok"
         }),
     })
-    const result = await Effect.runPromise(CodeMode.execute({
-      tools: { host: { capture } },
-      code: `return await tools.host.capture({ when: new Date(0), tags: new Map([["a", 1]]) })`,
-    }))
+    const result = await Effect.runPromise(
+      CodeMode.execute({
+        tools: { host: { capture } },
+        code: `return await tools.host.capture({ when: new Date(0), tags: new Map([["a", 1]]) })`,
+      }),
+    )
     expect(result.ok).toBe(true)
     expect(observed).toStrictEqual([{ when: "1970-01-01T00:00:00.000Z", tags: {} }])
   })

+ 1 - 4
packages/opencode/src/session/llm/request.ts

@@ -206,10 +206,7 @@ export const prepare = Effect.fn("LLMRequestPrep.prepare")(function* (input: Pre
 })
 
 function resolveTools(input: Pick<PrepareInput, "tools" | "agent" | "permission" | "user">) {
-  const visible = Permission.visibleTools(
-    input.tools,
-    Permission.merge(input.agent.permission, input.permission ?? []),
-  )
+  const visible = Permission.visibleTools(input.tools, Permission.merge(input.agent.permission, input.permission ?? []))
   return Record.filter(visible, (_, k) => input.user.tools?.[k] !== false)
 }
 

+ 7 - 3
packages/opencode/src/tool/code-mode.ts

@@ -104,7 +104,8 @@ export function groupByServer(
   const byLongest = [...servers].sort((a, b) => b.length - a.length)
   const groups = new Map<string, CatalogEntry[]>()
   for (const key of Object.keys(mcpTools).sort((a, b) => a.localeCompare(b))) {
-    const server = byLongest.find((name) => key.startsWith(name + "_")) ?? (key.includes("_") ? key.slice(0, key.indexOf("_")) : key)
+    const server =
+      byLongest.find((name) => key.startsWith(name + "_")) ?? (key.includes("_") ? key.slice(0, key.indexOf("_")) : key)
     const local = server && key.startsWith(server + "_") ? key.slice(server.length + 1) : key
     const def = mcpDefs[key]
     const entry: CatalogEntry = {
@@ -129,7 +130,9 @@ export function buildCatalog(
   mcpDefs: Record<string, MCPToolDef>,
   servers: readonly string[],
 ): CatalogEntry[] {
-  return [...groupByServer(mcpTools, servers, mcpDefs).values()].flat().filter((entry) => entry.tool.execute !== undefined)
+  return [...groupByServer(mcpTools, servers, mcpDefs).values()]
+    .flat()
+    .filter((entry) => entry.tool.execute !== undefined)
 }
 
 /**
@@ -334,7 +337,8 @@ export const CodeModeTool = Tool.define(
         const collect = (attachment: Attachment) => void attachments.push(attachment)
         // Stream the current call list to the UI. Sent on every status change so the
         // tool part shows each child call appearing and resolving while the program runs.
-        const publish = () => ctx.metadata({ title: CODE_MODE_TOOL, metadata: { toolCalls: calls.map((c) => ({ ...c })) } })
+        const publish = () =>
+          ctx.metadata({ title: CODE_MODE_TOOL, metadata: { toolCalls: calls.map((c) => ({ ...c })) } })
 
         // One CodeMode tool per MCP tool, running the same shared middle as legacy
         // per-tool registration (McpInvoke.invoke: plugin before hook → permission

+ 4 - 2
packages/opencode/src/tool/registry.ts

@@ -276,7 +276,10 @@ const layer = Layer.effect(
     // fresh per turn so it tracks live tool-list changes. Hard-denied tools (the shared
     // Permission.visibleTools predicate over the agent's ruleset) never enter the
     // catalog, its inlined signatures, or the in-program search index.
-    const describeCodeMode = Effect.fn("ToolRegistry.describeCodeMode")(function* (agent: Agent.Info, permission?: PermissionV1.Ruleset) {
+    const describeCodeMode = Effect.fn("ToolRegistry.describeCodeMode")(function* (
+      agent: Agent.Info,
+      permission?: PermissionV1.Ruleset,
+    ) {
       const visible = Permission.visibleTools(yield* mcp.tools(), Permission.merge(agent.permission, permission ?? []))
       const servers = Object.keys(yield* mcp.clients()).map(McpCatalog.sanitize)
       return catalogInstructions(visible, yield* mcp.defs(), servers)
@@ -341,7 +344,6 @@ const layer = Layer.effect(
   }),
 )
 
-
 function isZodType(value: unknown): value is z.ZodType {
   return typeof value === "object" && value !== null && "_zod" in value
 }

+ 1 - 2
packages/opencode/test/session/tools.test.ts

@@ -96,8 +96,7 @@ function resolveTools(trigger?: Plugin.Interface["trigger"]) {
       Layer.mergeAll(
         Layer.mock(Permission.Service, { ask: () => Effect.void }),
         Layer.mock(Plugin.Service, {
-          trigger:
-            trigger ?? (((_name, _input, output) => Effect.succeed(output)) as Plugin.Interface["trigger"]),
+          trigger: trigger ?? (((_name, _input, output) => Effect.succeed(output)) as Plugin.Interface["trigger"]),
         }),
         Layer.mock(Truncate.Service, {
           output: (text: string) => Effect.succeed({ content: text, truncated: false as const }),

+ 4 - 7
packages/opencode/test/tool/code-mode-integration.test.ts

@@ -21,8 +21,7 @@ import type { Tool as AITool } from "ai"
 import { Effect, Layer } from "effect"
 
 // A 1x1 transparent PNG, base64-encoded, used to exercise image attachments.
-const PNG =
-  "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
+const PNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
 
 const SERVER = "fixtures"
 
@@ -163,8 +162,8 @@ async function buildTool() {
   // this real in-memory server listed — the same snapshot shape the live service returns.
   const layer = Layer.mergeAll(
     Layer.mock(Plugin.Service, {
-      trigger: (((_name: unknown, _input: unknown, output: unknown) =>
-        Effect.succeed(output)) as Plugin.Interface["trigger"]),
+      trigger: ((_name: unknown, _input: unknown, output: unknown) =>
+        Effect.succeed(output)) as Plugin.Interface["trigger"],
     }),
     Layer.mock(Truncate.Service, {
       output: (text: string) => Effect.succeed({ content: text, truncated: false as const }),
@@ -196,9 +195,7 @@ describe("code mode integration (real MCP server)", () => {
   test("the appended catalog inlines full signatures with real MCP schemas", () => {
     expect(description).toContain("Available tools (COMPLETE list")
     expect(description).toContain("- fixtures (4 tools)")
-    expect(description).toContain(
-      "tools.fixtures.add(input: { a: number; b: number }): Promise<{ sum: number }>",
-    )
+    expect(description).toContain("tools.fixtures.add(input: { a: number; b: number }): Promise<{ sum: number }>")
     expect(description).toContain("tools.fixtures.get_text(input: { name: string }): Promise<unknown>")
     expect(description).toContain("// Add two numbers and return the structured sum")
     // Small catalog: everything is inline, so no discovery tool is advertised.

+ 22 - 16
packages/opencode/test/tool/code-mode.test.ts

@@ -193,7 +193,9 @@ describe("code mode execute", () => {
     // never cherry-picks a catalog tool or fabricates result fields.
     expect(description).toContain("## Workflow")
     expect(description).toContain("1. Pick a tool from the list under `## Available tools`")
-    expect(description).toContain('`const data = typeof res === "string" ? JSON.parse(res) : res` — most tools return JSON as a string')
+    expect(description).toContain(
+      '`const data = typeof res === "string" ? JSON.parse(res) : res` — most tools return JSON as a string',
+    )
     expect(description).toContain("Return only the fields you need")
     expect(description).not.toContain("total_count")
   })
@@ -249,7 +251,9 @@ describe("code mode execute", () => {
     expect(description).toContain("tools.$codemode.search(")
     // PARTIAL catalogs put search first in the workflow and advertise namespace browsing.
     expect(description).toContain("1. Find a tool (skip when it is already listed below)")
-    expect(description).toContain('- Browse one namespace: `await tools.$codemode.search({ query: "", namespace: "<name>" })`.')
+    expect(description).toContain(
+      '- Browse one namespace: `await tools.$codemode.search({ query: "", namespace: "<name>" })`.',
+    )
     expect(description).not.toContain("total_count")
     // All op lines cost the same estimated tokens (chars/4 rounds away the 1- vs 3-digit
     // name difference), so the path tiebreak decides: the lexicographically-first ops made
@@ -290,7 +294,10 @@ describe("code mode execute", () => {
       linear_search: mcpTool("search", () => ""),
     })
     const output = await Effect.runPromise(
-      tool.execute({ code: "const namespaces = Object.keys(tools); return { namespaces, count: namespaces.length }" }, ctx),
+      tool.execute(
+        { code: "const namespaces = Object.keys(tools); return { namespaces, count: namespaces.length }" },
+        ctx,
+      ),
     )
     expect(JSON.parse(output.output)).toEqual({ namespaces: ["github", "linear"], count: 2 })
   })
@@ -565,9 +572,7 @@ describe("code mode execute", () => {
     const tool = await build({
       shot_take: mcpTool("take", () => ({ content: [{ type: "image", data: "PNGDATA", mimeType: "image/png" }] })),
     })
-    const out = await Effect.runPromise(
-      tool.execute({ code: "await tools.shot.take({}); return 'captured'" }, ctx),
-    )
+    const out = await Effect.runPromise(tool.execute({ code: "await tools.shot.take({}); return 'captured'" }, ctx))
     expect(out.output).toBe("captured")
     expect(out.attachments).toHaveLength(1)
   })
@@ -690,9 +695,7 @@ describe("code mode permission visibility", () => {
     expect(called).toEqual([])
 
     // The rest of the namespace still works.
-    const allowed = await Effect.runPromise(
-      tool.execute({ code: "return await tools.github.list_issues({})" }, ctx),
-    )
+    const allowed = await Effect.runPromise(tool.execute({ code: "return await tools.github.list_issues({})" }, ctx))
     expect(allowed.metadata.error).toBeUndefined()
     expect(allowed.output).toBe("ok")
   })
@@ -706,9 +709,7 @@ describe("code mode permission visibility", () => {
       ["github"],
       [askRule("github_list_issues")],
     )
-    const out = await Effect.runPromise(
-      tool.execute({ code: "return await tools.github.list_issues({})" }, askCtx),
-    )
+    const out = await Effect.runPromise(tool.execute({ code: "return await tools.github.list_issues({})" }, askCtx))
     expect(out.output).toBe("ok")
     expect(asked).toEqual(["github_list_issues"])
   })
@@ -733,16 +734,21 @@ describe("toSandboxResult", () => {
 
   test("prefers structuredContent over text", () => {
     const { collect } = collector()
-    expect(toSandboxResult({ structuredContent: { x: 1 }, content: [{ type: "text", text: "hi" }] }, collect)).toEqual(
-      { x: 1 },
-    )
+    expect(toSandboxResult({ structuredContent: { x: 1 }, content: [{ type: "text", text: "hi" }] }, collect)).toEqual({
+      x: 1,
+    })
   })
 
   test("joins text content when no structured content is present", () => {
     const { collect } = collector()
     expect(
       toSandboxResult(
-        { content: [{ type: "text", text: "one" }, { type: "text", text: "two" }] },
+        {
+          content: [
+            { type: "text", text: "one" },
+            { type: "text", text: "two" },
+          ],
+        },
         collect,
       ),
     ).toBe("one\ntwo")

+ 7 - 1
packages/tui/src/routes/session/index.tsx

@@ -2355,7 +2355,13 @@ function Execute(props: ToolProps) {
   return (
     <>
       <InlineTool
-        icon={props.part.state.status === "completed" && !hasRuntimeError() ? "✓" : props.part.state.status === "error" || hasRuntimeError() ? "✗" : "│"}
+        icon={
+          props.part.state.status === "completed" && !hasRuntimeError()
+            ? "✓"
+            : props.part.state.status === "error" || hasRuntimeError()
+              ? "✗"
+              : "│"
+        }
         color={hasRuntimeError() ? theme.error : undefined}
         spinner={isLoading()}
         pending="execute"

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini