2026-07-03 05:19:11 +00:00
import { parse } from "acorn"
import { Cause , Effect , Exit , Fiber , Schema , Semaphore } from "effect"
import { DiagnosticCategory , ModuleKind , ScriptTarget , flattenDiagnosticMessageText , transpileModule } from "typescript"
import {
copyIn ,
copyOut ,
isBlockedMember ,
ToolReference ,
ToolRuntime ,
ToolRuntimeError ,
type HostTools ,
type SafeObject ,
type ToolCall ,
type ToolDescription ,
type Services ,
} from "./tool-runtime.js"
import type { Definition } from "./tool.js"
import { ToolError } from "./tool-error.js"
import { isSandboxValue , SandboxDate , SandboxMap , SandboxPromise , SandboxRegExp , SandboxSet } from "./values.js"
/** A tool call admitted during an execution. */
export type { ToolCall , ToolCallStarted , ToolDescription } from "./tool-runtime.js"
export { ToolError , toolError } from "./tool-error.js"
/** Resource budgets enforced independently during each CodeMode program execution. */
export type ExecutionLimits = {
/** Maximum wall-clock execution time in milliseconds. No default: absent means no timeout. */
readonly timeoutMs? : number
/** Maximum number of tool calls admitted by the runtime. No default: absent means unlimited. */
readonly maxToolCalls? : number
/ * *
* Maximum UTF - 8 bytes of model - facing output : the serialized result value plus captured
* logs . Excess output is truncated with an explanatory marker instead of failing . No
* default : absent means no truncation ( for hosts with their own output bounding ) .
* /
readonly maxOutputBytes? : number
}
/** Controls how much of the tool catalog is inlined in agent instructions. */
export type DiscoveryOptions = {
/ * *
* Estimated - token budget ( chars / 4 , default 2000 ) for inlined full tool signatures in agent
* instructions . Signatures that fit are inlined round - robin across namespaces ; every
* namespace is always listed with its tool count regardless of budget , and
* ` tools. $ codemode.search ` is always registered .
* /
readonly maxInlineCatalogTokens? : number
}
type ToolTree < R = never > = {
readonly [ name : string ] : Definition < R > | ToolTree < R >
}
type ResolvedExecutionLimits = {
/** Undefined means no timeout. */
readonly timeoutMs : number | undefined
/** Undefined means unlimited tool calls. */
readonly maxToolCalls : number | undefined
/** Undefined means no output truncation. */
readonly maxOutputBytes : number | undefined
}
/** Options for one CodeMode execution. */
export type ExecuteOptions < Tools extends Record < string , unknown > = { } > = {
/** Source for one program in the supported JavaScript subset. */
code : string
/** Explicit tool tree exposed to the program as `tools`. */
tools? : Tools & ToolTree < Services < Tools > >
/** Per-execution overrides for the default resource limits. */
limits? : ExecutionLimits
/** Observes decoded tool input immediately before tool execution. */
onToolCallStart ? : ( call : ToolRuntime.ToolCallStarted ) = > Effect . Effect < void , never , Services < Tools > >
/** Observes each admitted tool call as it settles, with outcome and duration. */
onToolCallEnd ? : ( call : ToolRuntime.ToolCallEnded ) = > Effect . Effect < void , never , Services < Tools > >
}
/** A normalized program diagnostic safe to return across an agent tool boundary. */
export type Diagnostic = {
readonly kind : DiagnosticKind
readonly message : string
readonly location ? : { readonly line : number ; readonly column : number }
readonly suggestions? : ReadonlyArray < string >
}
/** A JSON value that can cross the confined interpreter boundary. */
export type DataValue = Schema . Json
/** Successful execution after the result has crossed the plain-data boundary. */
export type ExecuteSuccess = {
readonly ok : true
readonly value : DataValue
readonly logs? : ReadonlyArray < string >
/** Present when the value or logs were truncated to fit `maxOutputBytes`. */
readonly truncated? : boolean
readonly toolCalls : ReadonlyArray < ToolCall >
}
/** Failed execution with calls admitted before the diagnostic was produced. */
export type ExecuteFailure = {
readonly ok : false
readonly error : Diagnostic
readonly logs? : ReadonlyArray < string >
/** Present when the logs were truncated to fit `maxOutputBytes`. */
readonly truncated? : boolean
readonly toolCalls : ReadonlyArray < ToolCall >
}
/** Result of executing a CodeMode program. Program failures are data, not Effect failures. */
export type ExecuteResult = ExecuteSuccess | ExecuteFailure
/** Reusable CodeMode configuration shared by `execute` and `agentTool`. */
export type CodeModeOptions < Tools extends Record < string , unknown > = { } > = Omit < ExecuteOptions < Tools > , "code" > & {
/** Progressive-disclosure configuration for the agent-facing tool catalog. */
readonly discovery? : DiscoveryOptions
}
/** Input schema for the single agent-facing tool produced by `runtime.agentTool()`. */
export const ExecuteInputSchema = Schema . Struct ( { code : Schema.String } )
const DiagnosticKindSchema = Schema . Literals ( [
2026-07-03 05:20:28 +00:00
"ParseError" ,
"UnsupportedSyntax" ,
"UnknownTool" ,
"InvalidToolInput" ,
"InvalidToolOutput" ,
"InvalidDataValue" ,
"ToolCallLimitExceeded" ,
"TimeoutExceeded" ,
"ToolFailure" ,
"ExecutionFailure" ,
2026-07-03 05:19:11 +00:00
] )
/** Structured success or diagnostic result schema returned by CodeMode execution. */
export const ExecuteResultSchema = Schema . Union ( [
Schema . Struct ( {
ok : Schema.Literal ( true ) ,
value : Schema.Json ,
logs : Schema.optionalKey ( Schema . Array ( Schema . String ) ) ,
truncated : Schema.optionalKey ( Schema . Boolean ) ,
toolCalls : Schema.Array ( Schema . Struct ( { name : Schema.String } ) ) ,
} ) ,
Schema . Struct ( {
ok : Schema.Literal ( false ) ,
error : Schema.Struct ( {
kind : DiagnosticKindSchema ,
message : Schema.String ,
location : Schema.optionalKey ( Schema . Struct ( { line : Schema.Number , column : Schema.Number } ) ) ,
suggestions : Schema.optionalKey ( Schema . Array ( Schema . String ) ) ,
} ) ,
logs : Schema.optionalKey ( Schema . Array ( Schema . String ) ) ,
truncated : Schema.optionalKey ( Schema . Boolean ) ,
toolCalls : Schema.Array ( Schema . Struct ( { name : Schema.String } ) ) ,
} ) ,
] )
/** Agent-facing projection of a configured CodeMode runtime. */
export type AgentToolDefinition < R = never > = {
readonly name : "code"
readonly description : string
readonly input : typeof ExecuteInputSchema
readonly output : typeof ExecuteResultSchema
readonly execute : ( input : { readonly code : string } ) = > Effect . Effect < ExecuteResult , never , R >
}
/** Reusable confined runtime over one explicit tool tree. */
export type CodeModeRuntime < R = never > = {
/** Lists schema-described tool paths provided by the host. */
readonly catalog : ( ) = > ReadonlyArray < ToolDescription >
/** Builds model-facing syntax guidance and visible tool signatures. */
readonly instructions : ( ) = > string
/** Projects the configured runtime as one agent-facing `code` tool. */
readonly agentTool : ( ) = > AgentToolDefinition < R >
/** Executes a program using this runtime's configured host tools. */
readonly execute : ( code : string ) = > Effect . Effect < ExecuteResult , never , R >
}
type SourcePosition = {
line : number
column : number
}
type SourceLocation = {
start : SourcePosition
end : SourcePosition
}
type AstNode = {
type : string
loc? : SourceLocation
[ key : string ] : unknown
}
type ProgramNode = AstNode & {
type : "Program"
body : Array < AstNode >
}
type Binding = {
mutable : boolean
value : unknown
// Absent means initialized. `false` marks a parameter binding seeded into its scope but not
// yet bound, so a default that forward-references a later parameter sees a TDZ error (as in JS)
// rather than silently resolving to an outer binding of the same name.
initialized? : boolean
}
type StatementResult =
| { kind : "none" }
| { kind : "value" ; value : unknown }
| { kind : "return" ; value : unknown }
| { kind : "break" }
| { kind : "continue" }
type MemberReference = {
target : SafeObject | Array < unknown >
key : string | number
}
class CodeModeFunction {
constructor (
readonly parameters : ReadonlyArray < AstNode > ,
readonly body : AstNode ,
readonly capturedScopes : ReadonlyArray < Map < string , Binding > > ,
) { }
}
class IntrinsicReference {
constructor (
readonly receiver : unknown ,
readonly name : string ,
) { }
}
class ComputedValue {
constructor ( readonly value : unknown ) { }
}
class PromiseNamespace { }
type PromiseMethodName = "all" | "allSettled" | "race" | "resolve" | "reject"
class PromiseMethodReference {
constructor ( readonly name : PromiseMethodName ) { }
}
// A built-in global namespace (`Object`, `Math`, `JSON`, `Array`, ...); members resolve to a
// GlobalMethodReference, except known constants (e.g. `Math.PI`) which resolve to a value.
type GlobalNamespaceName = "Object" | "Math" | "JSON" | "Array" | "console" | "Date" | "RegExp" | "Map" | "Set"
class GlobalNamespace {
constructor ( readonly name : GlobalNamespaceName ) { }
}
class GlobalMethodReference {
2026-07-03 05:20:28 +00:00
constructor (
readonly namespace : GlobalNamespaceName | "Number" | "String" ,
readonly name : string ,
) { }
2026-07-03 05:19:11 +00:00
}
class CoercionFunction {
constructor ( readonly name : "Number" | "String" | "Boolean" | "parseInt" | "parseFloat" ) { }
}
class ProgramThrow {
constructor ( readonly value : unknown ) { }
}
class ErrorConstructorReference {
constructor ( readonly name : string ) { }
}
// Non-enumerable so spread/copyOut preserve the plain `{ name, message }` data shape.
const ErrorBrand : unique symbol = Symbol ( "codemode.error" )
const brandError = ( errorValue : SafeObject , name : string ) : SafeObject = > {
Object . defineProperty ( errorValue , ErrorBrand , { value : name } )
return errorValue
}
const createErrorValue = ( name : string , message : string ) : SafeObject = >
brandError ( Object . assign ( Object . create ( null ) as SafeObject , { name , message } ) , name )
const errorBrandName = ( value : unknown ) : string | undefined = >
value !== null && typeof value === "object"
? ( ( value as Record < PropertyKey , unknown > ) [ ErrorBrand ] as string | undefined )
: undefined
/** Stable categories produced by program, schema, tool, and limit failures. */
export type DiagnosticKind =
| "ParseError"
| "UnsupportedSyntax"
| "UnknownTool"
| "InvalidToolInput"
| "InvalidToolOutput"
| "InvalidDataValue"
| "ToolCallLimitExceeded"
| "TimeoutExceeded"
| "ToolFailure"
| "ExecutionFailure"
const arrayMethods = new Set ( [
2026-07-03 05:20:28 +00:00
"map" ,
"filter" ,
"find" ,
"findIndex" ,
"findLast" ,
"findLastIndex" ,
"some" ,
"every" ,
"includes" ,
"join" ,
"reduce" ,
"reduceRight" ,
"flatMap" ,
"forEach" ,
"sort" ,
"toSorted" ,
"slice" ,
"concat" ,
"indexOf" ,
"lastIndexOf" ,
"at" ,
"flat" ,
"reverse" ,
"toReversed" ,
"with" ,
"push" ,
"pop" ,
"shift" ,
"unshift" ,
"splice" ,
"fill" ,
"copyWithin" ,
"keys" ,
"values" ,
"entries" ,
2026-07-03 05:19:11 +00:00
] )
const mathConstants = new Set ( [ "PI" , "E" , "LN2" , "LN10" , "LOG2E" , "LOG10E" , "SQRT2" , "SQRT1_2" ] )
const numberMethods = new Set ( [ "toFixed" , "toPrecision" , "toExponential" , "toString" ] )
const stringMethods = new Set ( [
2026-07-03 05:20:28 +00:00
"toLowerCase" ,
"toUpperCase" ,
"trim" ,
"trimStart" ,
"trimEnd" ,
"trimLeft" ,
"trimRight" ,
"split" ,
"slice" ,
"substring" ,
"substr" ,
"includes" ,
"startsWith" ,
"endsWith" ,
"indexOf" ,
"lastIndexOf" ,
"replace" ,
"replaceAll" ,
"repeat" ,
"padStart" ,
"padEnd" ,
"charAt" ,
"charCodeAt" ,
"codePointAt" ,
"at" ,
"concat" ,
"toString" ,
"match" ,
"matchAll" ,
"search" ,
"localeCompare" ,
"normalize" ,
2026-07-03 05:19:11 +00:00
] )
const numberConstants = new Set ( [ "MAX_SAFE_INTEGER" , "MIN_SAFE_INTEGER" , "MAX_VALUE" , "MIN_VALUE" , "EPSILON" ] )
const numberStatics = new Set ( [ "isInteger" , "isFinite" , "isNaN" , "isSafeInteger" , "parseInt" , "parseFloat" ] )
const stringStatics = new Set ( [ "fromCharCode" , "fromCodePoint" ] )
const consoleMethods = new Set ( [ "log" , "info" , "debug" , "warn" , "error" , "dir" , "table" ] )
const promiseStatics = new Set < PromiseMethodName > ( [ "all" , "allSettled" , "race" , "resolve" , "reject" ] )
2026-07-03 05:20:28 +00:00
const errorConstructors = new Set ( [
"Error" ,
"TypeError" ,
"RangeError" ,
"SyntaxError" ,
"ReferenceError" ,
"EvalError" ,
"URIError" ,
] )
2026-07-03 05:19:11 +00:00
const valueConstructors = new Set ( [ "Date" , "RegExp" , "Map" , "Set" ] )
const dateMethods = new Set ( [
2026-07-03 05:20:28 +00:00
"getTime" ,
"valueOf" ,
"toISOString" ,
"toJSON" ,
"toString" ,
"getFullYear" ,
"getMonth" ,
"getDate" ,
"getDay" ,
"getHours" ,
"getMinutes" ,
"getSeconds" ,
"getMilliseconds" ,
"getUTCFullYear" ,
"getUTCMonth" ,
"getUTCDate" ,
"getUTCDay" ,
"getUTCHours" ,
"getUTCMinutes" ,
"getUTCSeconds" ,
"getUTCMilliseconds" ,
2026-07-03 05:19:11 +00:00
"getTimezoneOffset" ,
] )
const dateStatics = new Set ( [ "now" , "parse" , "UTC" ] )
const regexpMethods = new Set ( [ "test" , "exec" , "toString" ] )
// Read-only host regex fields surfaced as plain values.
2026-07-03 05:20:28 +00:00
const regexpProperties = new Set ( [
"source" ,
"flags" ,
"lastIndex" ,
"global" ,
"ignoreCase" ,
"multiline" ,
"sticky" ,
"unicode" ,
"dotAll" ,
] )
2026-07-03 05:19:11 +00:00
const mapMethods = new Set ( [ "get" , "set" , "has" , "delete" , "clear" , "forEach" , "keys" , "values" , "entries" ] )
const setMethods = new Set ( [ "add" , "has" , "delete" , "clear" , "forEach" , "keys" , "values" , "entries" ] )
const OptionalShortCircuit : unique symbol = Symbol ( "codemode.optional-short-circuit" )
const supportedSyntaxMessage =
"Supported orchestration syntax: tools.* calls (they return promises - resolve them with await), data literals, destructuring, optional chaining, template literals, conditionals, switch, loops (incl. for...of and for...in over object/array/tools keys), arrow functions, spread, try/catch, array methods (map/filter/find/findIndex/some/every/reduce/flatMap/forEach/sort/slice/concat/indexOf/lastIndexOf/at/flat/reverse/includes/join), string methods (incl. match/matchAll/replace/split with regular expressions), Date/RegExp/Map/Set, Object/Math/JSON helpers, captured console.log/warn/error/dir/table, and Promise.all/allSettled/race/resolve/reject over arrays mixing promises and plain values for parallel tool calls (promise chaining with .then/.catch is not supported - use await with try/catch)."
const unsupportedSyntax = ( kind : string , node : AstNode ) : InterpreterRuntimeError = >
2026-07-03 05:20:28 +00:00
new InterpreterRuntimeError (
` Syntax ' ${ kind } ' is not supported in CodeMode. ${ supportedSyntaxMessage } ` ,
node ,
"UnsupportedSyntax" ,
[ supportedSyntaxMessage ] ,
)
2026-07-03 05:19:11 +00:00
/** How many eagerly forked tool calls may run at once. Fixed; not a configurable knob. */
const TOOL_CALL_CONCURRENCY = 8
/** Console formatting recursion ceiling; deeper values render as "...". Fixed; not a knob. */
const MAX_CONSOLE_DEPTH = 32
2026-07-03 05:20:28 +00:00
const validateLimit = < Value extends number | undefined > (
name : keyof ExecutionLimits ,
value : Value ,
minimum : number ,
) : Value = > {
2026-07-03 05:19:11 +00:00
if ( value !== undefined && ( ! Number . isSafeInteger ( value ) || value < minimum ) ) {
throw new RangeError ( ` ${ name } must be a safe integer greater than or equal to ${ minimum } . ` )
}
return value
}
// No limit has a default: absent means no timeout / unlimited calls / no output truncation -
// budgets are host policy, not library policy. A host without its own output bounding should
// pass maxOutputBytes explicitly, or oversized results flood model context.
const resolveExecutionLimits = ( limits? : ExecutionLimits ) : ResolvedExecutionLimits = > ( {
timeoutMs : validateLimit ( "timeoutMs" , limits ? . timeoutMs , 1 ) ,
maxToolCalls : validateLimit ( "maxToolCalls" , limits ? . maxToolCalls , 0 ) ,
maxOutputBytes : validateLimit ( "maxOutputBytes" , limits ? . maxOutputBytes , 0 ) ,
} )
class InterpreterRuntimeError extends Error {
readonly node? : AstNode
/ * *
* The constructor name a program observes when it catches this failure ( ` caught.name ` , and
* the brand behind ` caught instanceof SyntaxError ` etc . ) . "Error" unless the failing
* operation names a standard type in real JS - e . g . JSON . parse and invalid regex patterns
* throw SyntaxError , an unknown identifier is a ReferenceError , a bad normalize form is a
* RangeError .
* /
errorName : string = "Error"
2026-07-03 05:20:28 +00:00
constructor (
message : string ,
node? : AstNode ,
readonly kind : DiagnosticKind = "ExecutionFailure" ,
readonly suggestions? : ReadonlyArray < string > ,
) {
2026-07-03 05:19:11 +00:00
super ( message )
this . name = "InterpreterRuntimeError"
if ( node ) {
this . node = node
}
}
as ( errorName : string ) : this {
this . errorName = errorName
return this
}
}
2026-07-03 05:20:28 +00:00
const isRecord = ( value : unknown ) : value is Record < string , unknown > = > typeof value === "object" && value !== null
2026-07-03 05:19:11 +00:00
const asNode = ( value : unknown , context : string ) : AstNode = > {
if ( ! isRecord ( value ) || typeof value . type !== "string" ) {
throw new InterpreterRuntimeError ( ` Invalid AST node while reading ${ context } . ` )
}
return value as AstNode
}
const getArray = ( node : AstNode , key : string ) : Array < unknown > = > {
const value = node [ key ]
if ( ! Array . isArray ( value ) ) {
throw new InterpreterRuntimeError ( ` Expected ' ${ key } ' to be an array. ` , node )
}
return value
}
const getString = ( node : AstNode , key : string ) : string = > {
const value = node [ key ]
if ( typeof value !== "string" ) {
throw new InterpreterRuntimeError ( ` Expected ' ${ key } ' to be a string. ` , node )
}
return value
}
const getBoolean = ( node : AstNode , key : string ) : boolean = > {
const value = node [ key ]
if ( typeof value !== "boolean" ) {
throw new InterpreterRuntimeError ( ` Expected ' ${ key } ' to be a boolean. ` , node )
}
return value
}
const getOptionalNode = ( node : AstNode , key : string ) : AstNode | undefined = > {
const value = node [ key ]
if ( value === undefined || value === null ) {
return undefined
}
return asNode ( value , key )
}
const getNode = ( node : AstNode , key : string ) : AstNode = > {
const value = node [ key ]
return asNode ( value , key )
}
const parseProgram = ( code : string ) : ProgramNode = > {
const transpiled = transpileModule ( ` async function __codemode__() { \ n ${ code } \ n} ` , {
reportDiagnostics : true ,
compilerOptions : {
target : ScriptTarget.ESNext ,
module : ModuleKind.ESNext ,
} ,
} )
const diagnostic = transpiled . diagnostics ? . find ( ( item ) = > item . category === DiagnosticCategory . Error )
if ( diagnostic ) {
throw new InterpreterRuntimeError (
` Failed to parse TypeScript: ${ flattenDiagnosticMessageText ( diagnostic . messageText , "\n" ) } ` ,
undefined ,
"ParseError" ,
)
}
const bodyStart = transpiled . outputText . indexOf ( "{" ) + 1
const bodyEnd = transpiled . outputText . lastIndexOf ( "}" )
const executableCode = transpiled . outputText . slice ( bodyStart , bodyEnd )
const parsed = parse ( executableCode , {
ecmaVersion : "latest" ,
sourceType : "script" ,
allowReturnOutsideFunction : true ,
allowAwaitOutsideFunction : true ,
locations : true ,
} ) as unknown
if ( ! isRecord ( parsed ) || parsed . type !== "Program" || ! Array . isArray ( parsed . body ) ) {
throw new InterpreterRuntimeError ( "Failed to parse script as a Program node." )
}
return parsed as ProgramNode
}
const formatLocation = ( node? : AstNode ) : string = > {
if ( ! node || ! node . loc ) {
return ""
}
const location = sourceLocation ( node )
return ` (line ${ location . line } , col ${ location . column } ) `
}
const sourceLocation = ( node : AstNode ) : { readonly line : number ; readonly column : number } = > ( {
line : Math.max ( 1 , ( node . loc ? . start . line ? ? 2 ) - 1 ) ,
column : Math.max ( 1 , ( node . loc ? . start . column ? ? 4 ) - 3 ) ,
} )
const publicErrorMessage = ( message : string ) : string = >
message . replace ( /\/(?:Users|home|private|tmp|var\/folders)\/[^\s"'`]+/g , "<redacted-path>" )
const normalizeError = ( error : unknown ) : Diagnostic = > {
if ( error instanceof InterpreterRuntimeError ) {
return {
kind : error.kind ,
message : ` ${ error . message } ${ formatLocation ( error . node ) } ` ,
. . . ( error . node ? . loc ? { location : sourceLocation ( error . node ) } : { } ) ,
. . . ( error . suggestions ? { suggestions : error.suggestions } : { } ) ,
}
}
if ( error instanceof ToolRuntimeError ) {
return {
kind : error.kind ,
message : error.message ,
. . . ( error . suggestions . length > 0 ? { suggestions : error.suggestions } : { } ) ,
}
}
if ( error instanceof ToolError ) {
return { kind : "ToolFailure" , message : publicErrorMessage ( error . message ) }
}
if ( error instanceof ProgramThrow ) {
const value = error . value
let message : string
if ( containsRuntimeReference ( value ) ) {
// A thrown tool/function reference must not leak its internal structure.
message = "a non-data value"
} else if ( typeof value === "string" ) {
message = value
2026-07-03 05:20:28 +00:00
} else if (
value !== null &&
typeof value === "object" &&
typeof ( value as { message? : unknown } ) . message === "string"
) {
2026-07-03 05:19:11 +00:00
message = ( value as { message : string } ) . message
} else {
try {
message = JSON . stringify ( copyOut ( value ) ) ? ? String ( value )
} catch {
message = String ( value )
}
}
return { kind : "ExecutionFailure" , message : ` Uncaught: ${ message } ` }
}
if ( error instanceof RangeError && /call stack|recursion/i . test ( error . message ) ) {
return {
kind : "ExecutionFailure" ,
message : "Execution exceeded the maximum nesting depth." ,
}
}
if ( error instanceof Error ) {
return {
kind : error.name === "SyntaxError" ? "ParseError" : "ExecutionFailure" ,
message : publicErrorMessage ( error . message ) ,
}
}
// A non-Error thrown by a host tool (raw string / number / Symbol) still routes through
// path redaction so filesystem paths can never leak through the catch-all branch.
return {
kind : "ExecutionFailure" ,
message : publicErrorMessage ( String ( error ) ) ,
}
}
// Shared by catch bindings, Promise.allSettled rejection reasons, and Promise.race losers.
const caughtErrorValue = ( thrown : unknown ) : unknown = > {
if ( thrown instanceof ProgramThrow ) return thrown . value
if ( thrown instanceof InterpreterRuntimeError ) return createErrorValue ( thrown . errorName , thrown . message )
const name = thrown instanceof Error && errorConstructors . has ( thrown . name ) ? thrown . name : "Error"
return createErrorValue ( name , normalizeError ( thrown ) . message )
}
const boundedData = ( value : unknown , label : string ) : unknown = > copyIn ( value , label , true )
const isRuntimeReference = ( value : unknown ) : boolean = >
2026-07-03 05:20:28 +00:00
value instanceof CodeModeFunction ||
value instanceof ToolReference ||
value instanceof IntrinsicReference ||
value instanceof GlobalNamespace ||
value instanceof GlobalMethodReference ||
value instanceof PromiseNamespace ||
value instanceof PromiseMethodReference ||
value instanceof SandboxPromise ||
value instanceof CoercionFunction ||
value instanceof ErrorConstructorReference ||
isSandboxValue ( value )
2026-07-03 05:19:11 +00:00
const containsRuntimeReference = ( value : unknown , seen = new Set < object > ( ) ) : boolean = > {
if ( isRuntimeReference ( value ) ) return true
if ( value === null || typeof value !== "object" ) return false
if ( seen . has ( value ) ) return false
seen . add ( value )
const contains = Array . isArray ( value )
? value . some ( ( item ) = > containsRuntimeReference ( item , seen ) )
: Object . values ( value ) . some ( ( item ) = > containsRuntimeReference ( item , seen ) )
seen . delete ( value )
return contains
}
// Like containsRuntimeReference, but sandbox value types (Date/RegExp/Map/Set) count as data:
// operators and switch treat them as ordinary object operands (identity equality, ToPrimitive
// coercion) rather than rejecting them as opaque interpreter machinery.
const containsOpaqueReference = ( value : unknown , seen = new Set < object > ( ) ) : boolean = > {
if ( isSandboxValue ( value ) ) return false
if ( isRuntimeReference ( value ) ) return true
if ( value === null || typeof value !== "object" ) return false
if ( seen . has ( value ) ) return false
seen . add ( value )
const contains = Array . isArray ( value )
? value . some ( ( item ) = > containsOpaqueReference ( item , seen ) )
: Object . values ( value ) . some ( ( item ) = > containsOpaqueReference ( item , seen ) )
seen . delete ( value )
return contains
}
// `typeof` never throws in JS; map every interpreter value to its JS-visible category.
// A SandboxPromise falls through to the final `typeof value` and reports "object", exactly
// like a real JS promise.
const typeofValue = ( value : unknown ) : string = > {
if (
2026-07-03 05:20:28 +00:00
value instanceof CodeModeFunction ||
value instanceof CoercionFunction ||
value instanceof IntrinsicReference ||
value instanceof GlobalMethodReference ||
value instanceof PromiseMethodReference ||
value instanceof PromiseNamespace ||
2026-07-03 05:19:11 +00:00
value instanceof ErrorConstructorReference
2026-07-03 05:20:28 +00:00
)
return "function"
2026-07-03 05:19:11 +00:00
if ( value instanceof ToolReference ) return value . path . length > 0 ? "function" : "object"
if ( value instanceof GlobalNamespace ) {
return value . name === "Math" || value . name === "JSON" || value . name === "console" ? "object" : "function"
}
return typeof value
}
// `x instanceof C` against the constructors CodeMode knows. Like `typeof`, it observes any
// left-hand value (opaque references included) without coercing it. Error checks use the
// error brand: `instanceof Error` accepts every branded error; a specific error type matches
// its own brand only (as in JS, where TypeError instances are also Error instances).
const instanceofValue = ( lhs : unknown , rhs : unknown , node : AstNode ) : boolean = > {
if ( rhs instanceof ErrorConstructorReference ) {
const brand = errorBrandName ( lhs )
return brand !== undefined && ( rhs . name === "Error" || brand === rhs . name )
}
if ( rhs instanceof GlobalNamespace ) {
switch ( rhs . name ) {
2026-07-03 05:20:28 +00:00
case "Date" :
return lhs instanceof SandboxDate
case "RegExp" :
return lhs instanceof SandboxRegExp
case "Map" :
return lhs instanceof SandboxMap
case "Set" :
return lhs instanceof SandboxSet
case "Array" :
return Array . isArray ( lhs )
case "Object" :
return lhs !== null && ( typeof lhs === "object" || typeofValue ( lhs ) === "function" )
2026-07-03 05:19:11 +00:00
}
}
if ( rhs instanceof PromiseNamespace ) return lhs instanceof SandboxPromise
// Number/String/Boolean wrap primitives in JS; no boxed values exist in CodeMode, so
// `x instanceof Number` is always false - exactly what it is for primitives in JS.
if ( rhs instanceof CoercionFunction && ( rhs . name === "Number" || rhs . name === "String" || rhs . name === "Boolean" ) ) {
return false
}
throw new InterpreterRuntimeError (
"The right-hand side of 'instanceof' must be a constructor CodeMode knows: Error (or a specific error type like TypeError), Date, RegExp, Map, Set, Array, Object, or Promise." ,
node ,
)
}
// A regex engine failure message without the engine's own "Invalid regular expression:"
// prefix, so composed diagnostics read as one sentence instead of stuttering the phrase.
const regexFailureReason = ( error : unknown ) : string = >
( error instanceof Error ? error.message : String ( error ) ) . replace ( /^Invalid regular expression:\s*/i , "" )
const escapeRegexHint =
'To match special characters like ( ) [ ] { } + * ? . literally, escape them with a backslash (e.g. "\\\\(") or test for them with String.includes instead.'
// A string method's pattern argument as a host regex: a sandbox regex passes its own host
// instance through (so `g` lastIndex semantics follow the spec across calls); a string becomes
// a pattern, exactly as String.prototype.match/matchAll/search do (`extraFlags` adds matchAll's
// implicit `g`). Invalid patterns fail as catchable program errors that say what was wrong
// with the pattern and how to fix it.
const toHostRegex = ( arg : unknown , method : string , node : AstNode , extraFlags = "" ) : RegExp = > {
if ( arg instanceof SandboxRegExp ) return arg . regex
if ( typeof arg === "string" ) {
try {
return new RegExp ( arg , extraFlags )
} catch ( error ) {
throw new InterpreterRuntimeError (
` String. ${ method } received the string ${ JSON . stringify ( arg ) } , which is not a valid regular expression pattern ( ${ regexFailureReason ( error ) } ). ${ escapeRegexHint } ` ,
node ,
) . as ( "SyntaxError" )
}
}
throw new InterpreterRuntimeError (
` String. ${ method } expects a regular expression (a /pattern/flags literal or new RegExp(...)) or a string pattern, not ${ arg === null ? "null" : typeof arg } . ` ,
node ,
)
}
// A host match result as a sandbox value: a plain array of the full match and captures, with
// `index` and named `groups` attached as own array properties (readable, and dropped at data
// boundaries exactly like JSON.stringify drops them in JS). `input` is omitted - it duplicates
// the whole subject string per match.
const matchToValue = ( match : RegExpMatchArray ) : Array < unknown > = > {
const result : Array < unknown > = Array . from ( match , ( group ) = > group )
if ( match . index !== undefined ) ( result as Record < string , unknown > & Array < unknown > ) . index = match . index
if ( match . groups ) {
const groups : SafeObject = Object . create ( null ) as SafeObject
for ( const [ key , group ] of Object . entries ( match . groups ) ) {
if ( ! isBlockedMember ( key ) ) groups [ key ] = group
}
; ( result as Record < string , unknown > & Array < unknown > ) . groups = groups
}
return result
}
const invokeStringMethod = ( value : string , name : string , args : Array < unknown > , node : AstNode ) : unknown = > {
const str = ( index : number ) : string = > {
const arg = args [ index ]
2026-07-03 05:20:28 +00:00
if ( typeof arg !== "string" )
throw new InterpreterRuntimeError ( ` String. ${ name } expects argument ${ index + 1 } to be a string. ` , node )
2026-07-03 05:19:11 +00:00
return arg
}
const num = ( index : number ) : number = > {
const arg = args [ index ]
2026-07-03 05:20:28 +00:00
if ( typeof arg !== "number" )
throw new InterpreterRuntimeError ( ` String. ${ name } expects argument ${ index + 1 } to be a number. ` , node )
2026-07-03 05:19:11 +00:00
return arg
}
const optNum = ( index : number ) : number | undefined = > ( args [ index ] === undefined ? undefined : num ( index ) )
const optStr = ( index : number ) : string | undefined = > ( args [ index ] === undefined ? undefined : str ( index ) )
let result : unknown
switch ( name ) {
2026-07-03 05:20:28 +00:00
case "toLowerCase" :
result = value . toLowerCase ( )
break
case "toUpperCase" :
result = value . toUpperCase ( )
break
case "trim" :
result = value . trim ( )
break
2026-07-03 05:19:11 +00:00
// trimLeft/trimRight are the legacy aliases of trimStart/trimEnd, kept because models write them.
2026-07-03 05:20:28 +00:00
case "trimStart" :
case "trimLeft" :
result = value . trimStart ( )
break
case "trimEnd" :
case "trimRight" :
result = value . trimEnd ( )
break
2026-07-03 05:19:11 +00:00
// Locale/options arguments are ignored: comparison runs with the host default locale, and
// the common use is a sort comparator where any consistent order works.
2026-07-03 05:20:28 +00:00
case "localeCompare" :
result = value . localeCompare ( str ( 0 ) )
break
2026-07-03 05:19:11 +00:00
case "normalize" : {
const form = optStr ( 0 )
try {
result = value . normalize ( form )
} catch {
throw new InterpreterRuntimeError (
` String.normalize expects the form "NFC", "NFD", "NFKC", or "NFKD" (got ${ JSON . stringify ( form ) } ). ` ,
node ,
) . as ( "RangeError" )
}
break
}
case "split" : {
if ( args . length === 0 ) {
result = [ value ]
break
}
if ( args [ 0 ] instanceof SandboxRegExp ) {
result = value . split ( ( args [ 0 ] as SandboxRegExp ) . regex , optNum ( 1 ) )
break
}
const requestedLimit = optNum ( 1 )
result = value . split ( str ( 0 ) , requestedLimit === undefined ? undefined : requestedLimit >>> 0 )
break
}
2026-07-03 05:20:28 +00:00
case "slice" :
result = value . slice ( optNum ( 0 ) , optNum ( 1 ) )
break
case "includes" :
result = value . includes ( str ( 0 ) , optNum ( 1 ) )
break
case "startsWith" :
result = value . startsWith ( str ( 0 ) , optNum ( 1 ) )
break
case "endsWith" :
result = value . endsWith ( str ( 0 ) , optNum ( 1 ) )
break
case "indexOf" :
result = value . indexOf ( str ( 0 ) , optNum ( 1 ) )
break
case "lastIndexOf" :
result = value . lastIndexOf ( str ( 0 ) , optNum ( 1 ) )
break
2026-07-03 05:19:11 +00:00
case "replace" :
case "replaceAll" : {
if ( args [ 0 ] instanceof CodeModeFunction || args [ 1 ] instanceof CodeModeFunction ) {
2026-07-03 05:20:28 +00:00
throw new InterpreterRuntimeError (
` String. ${ name } does not support function replacers in CodeMode; use match/matchAll and rebuild the string instead. ` ,
node ,
"UnsupportedSyntax" ,
[ supportedSyntaxMessage ] ,
)
2026-07-03 05:19:11 +00:00
}
if ( args [ 0 ] instanceof SandboxRegExp ) {
const pattern = ( args [ 0 ] as SandboxRegExp ) . regex
const replacement = str ( 1 )
if ( name === "replaceAll" && ! pattern . global ) {
throw new InterpreterRuntimeError (
` String.replaceAll requires a regular expression with the global (g) flag: write / ${ pattern . source } / ${ pattern . flags } g, or use String.replace to replace only the first match. ` ,
node ,
)
}
result = name === "replace" ? value . replace ( pattern , replacement ) : value . replaceAll ( pattern , replacement )
break
}
if ( name === "replace" ) {
result = value . replace ( str ( 0 ) , str ( 1 ) )
break
}
result = value . replaceAll ( str ( 0 ) , str ( 1 ) )
break
}
case "match" : {
const pattern = toHostRegex ( args [ 0 ] , name , node )
const matched = value . match ( pattern )
if ( matched === null ) return null
// A global match is a plain array of matched strings; a non-global match carries
// index/groups own properties, so bypass the copying data checkpoint to keep them.
if ( pattern . global ) return boundedData ( matched , "String.match result" )
return matchToValue ( matched )
}
case "matchAll" : {
const pattern = toHostRegex ( args [ 0 ] , name , node , "g" )
if ( ! pattern . global ) {
throw new InterpreterRuntimeError (
` String.matchAll requires a regular expression with the global (g) flag: write / ${ pattern . source } / ${ pattern . flags } g, or use String.match for a single match. ` ,
node ,
)
}
// Materialized as an array (not an iterator); each entry is a match array with
// index/groups own properties. Match count is bounded by the subject length.
return Array . from ( value . matchAll ( pattern ) , matchToValue )
}
case "search" : {
result = value . search ( toHostRegex ( args [ 0 ] , name , node ) )
break
}
case "repeat" : {
const count = num ( 0 )
2026-07-03 05:20:28 +00:00
if ( ! Number . isFinite ( count ) || count < 0 )
throw new InterpreterRuntimeError ( "String.repeat expects a finite non-negative count." , node )
2026-07-03 05:19:11 +00:00
result = value . repeat ( count )
break
}
2026-07-03 05:20:28 +00:00
case "padStart" :
result = value . padStart ( num ( 0 ) , optStr ( 1 ) )
break
case "padEnd" :
result = value . padEnd ( num ( 0 ) , optStr ( 1 ) )
break
case "charAt" :
result = value . charAt ( optNum ( 0 ) ? ? 0 )
break
case "at" :
result = value . at ( optNum ( 0 ) ? ? 0 )
break
case "substring" :
result = value . substring ( optNum ( 0 ) ? ? 0 , optNum ( 1 ) )
break
case "substr" :
result = value . substr ( optNum ( 0 ) ? ? 0 , optNum ( 1 ) )
break
2026-07-03 05:19:11 +00:00
// JS charCodeAt returns NaN out of range; NaN flows as an ordinary in-sandbox value
// (normalized to null only at the data boundary - see copyOut), so return it as-is.
2026-07-03 05:20:28 +00:00
case "charCodeAt" :
result = value . charCodeAt ( optNum ( 0 ) ? ? 0 )
break
case "codePointAt" :
result = value . codePointAt ( optNum ( 0 ) ? ? 0 )
break
case "toString" :
result = value
break
2026-07-03 05:19:11 +00:00
case "concat" : {
result = value . concat ( . . . args . map ( ( _ , index ) = > str ( index ) ) )
break
}
2026-07-03 05:20:28 +00:00
default :
throw new InterpreterRuntimeError ( ` String method ' ${ name } ' is not available in CodeMode. ` , node )
2026-07-03 05:19:11 +00:00
}
return boundedData ( result , ` String. ${ name } result ` )
}
const invokeNumberMethod = ( value : number , name : string , args : Array < unknown > , node : AstNode ) : unknown = > {
const optNum = ( index : number ) : number | undefined = > {
const arg = args [ index ]
if ( arg === undefined ) return undefined
if ( typeof arg !== "number" ) throw new InterpreterRuntimeError ( ` Number. ${ name } expects a number argument. ` , node )
return arg
}
let result : unknown
switch ( name ) {
2026-07-03 05:20:28 +00:00
case "toFixed" :
result = value . toFixed ( optNum ( 0 ) )
break
case "toExponential" :
result = value . toExponential ( optNum ( 0 ) )
break
2026-07-03 05:19:11 +00:00
case "toPrecision" : {
const digits = optNum ( 0 )
result = digits === undefined ? value . toString ( ) : value . toPrecision ( digits )
break
}
case "toString" : {
const radix = optNum ( 0 )
if ( radix !== undefined && ( radix < 2 || radix > 36 ) ) {
throw new InterpreterRuntimeError ( "Number.toString radix must be between 2 and 36." , node )
}
result = value . toString ( radix )
break
}
2026-07-03 05:20:28 +00:00
default :
throw new InterpreterRuntimeError ( ` Number method ' ${ name } ' is not available in CodeMode. ` , node )
2026-07-03 05:19:11 +00:00
}
return boundedData ( result , ` Number. ${ name } result ` )
}
// JavaScript's String(...) without tripping over CodeMode's null-prototype data objects.
const coerceToString = ( value : unknown ) : string = > {
if ( value === null ) return "null"
if ( value === undefined ) return "undefined"
// Sandbox values stringify deterministically: Date as ISO (not the host's locale/timezone
// toString), RegExp as its literal form, Map/Set with their JS Object.prototype tags.
2026-07-03 05:20:28 +00:00
if ( value instanceof SandboxDate )
return Number . isFinite ( value . time ) ? new Date ( value . time ) . toISOString ( ) : "Invalid Date"
2026-07-03 05:19:11 +00:00
if ( value instanceof SandboxRegExp ) return ` / ${ value . regex . source } / ${ value . regex . flags } `
if ( value instanceof SandboxMap ) return "[object Map]"
if ( value instanceof SandboxSet ) return "[object Set]"
if ( typeof value === "object" ) {
return Array . isArray ( value )
? value . map ( ( item ) = > ( item === null || item === undefined ? "" : coerceToString ( item ) ) ) . join ( "," )
: "[object Object]"
}
return String ( value )
}
/** Compound assignment operators (`x op= y`), each applying the binary operator `op`. */
const compoundOperators = new Set ( [ "+=" , "-=" , "*=" , "/=" , "%=" , "**=" , "&=" , "|=" , "^=" , "<<=" , ">>=" , ">>>=" ] )
const coerceToNumber = ( value : unknown ) : number = > {
if ( value instanceof SandboxDate ) return value . time
if ( isSandboxValue ( value ) ) return Number . NaN
return value !== null && typeof value === "object" && ! Array . isArray ( value ) ? Number . NaN : Number ( value )
}
const invokeCoercion = ( ref : CoercionFunction , args : Array < unknown > , node : AstNode ) : unknown = > {
// Sandbox values coerce before the data checkpoint (which would JSON-serialize them):
// Number(date) is its time value, String(date) its ISO form, Boolean(x) is true.
const raw = args [ 0 ]
if ( isSandboxValue ( raw ) ) {
if ( ref . name === "Boolean" ) return true
if ( ref . name === "Number" ) return coerceToNumber ( raw )
if ( ref . name === "String" ) return coerceToString ( raw )
if ( ref . name === "parseInt" ) return parseInt ( coerceToString ( raw ) )
return parseFloat ( coerceToString ( raw ) )
}
const value = boundedData ( args [ 0 ] , ` ${ ref . name } input ` )
if ( ref . name === "Number" ) return coerceToNumber ( value )
if ( ref . name === "Boolean" ) return Boolean ( value )
if ( ref . name === "parseInt" ) {
const radix = args [ 1 ]
2026-07-03 05:20:28 +00:00
if ( radix !== undefined && typeof radix !== "number" )
throw new InterpreterRuntimeError ( "parseInt expects a numeric radix." , node )
2026-07-03 05:19:11 +00:00
return parseInt ( coerceToString ( value ) , radix )
}
if ( ref . name === "parseFloat" ) return parseFloat ( coerceToString ( value ) )
return coerceToString ( value )
}
const invokeObjectMethod = ( name : string , args : Array < unknown > , node : AstNode ) : unknown = > {
const requireObject = ( ) : Record < string , unknown > = > {
const value = boundedData ( args [ 0 ] , ` Object. ${ name } input ` )
// Sandbox values (Date/RegExp/Map/Set) have no own enumerable properties in JS, so the
// Object.* helpers see them as empty objects - never their interpreter internals.
if ( isSandboxValue ( value ) ) return { }
if ( value === null || typeof value !== "object" || Array . isArray ( value ) ) {
throw new InterpreterRuntimeError ( ` Object. ${ name } expects a data object. ` , node )
}
return value as Record < string , unknown >
}
const guardedSet = ( out : Record < string , unknown > , key : string , item : unknown ) : void = > {
if ( isBlockedMember ( key ) ) throw new InterpreterRuntimeError ( ` Property ' ${ key } ' is not available in CodeMode. ` , node )
out [ key ] = item
}
switch ( name ) {
case "keys" : {
// Object.keys(array) yields index strings (["0", "1", ...]) exactly as in JS; objects
// yield their own enumerable keys. (Tool references never reach here - the interpreter
// resolves them against the host tool tree first.)
const value = boundedData ( args [ 0 ] , "Object.keys input" )
if ( isSandboxValue ( value ) ) return [ ]
if ( Array . isArray ( value ) ) return Object . keys ( value )
if ( value === null || typeof value !== "object" ) {
throw new InterpreterRuntimeError ( "Object.keys expects a data object or array." , node )
}
return Object . keys ( value )
}
2026-07-03 05:20:28 +00:00
case "values" :
return Object . values ( requireObject ( ) )
case "entries" :
return Object . entries ( requireObject ( ) ) . map ( ( [ key , item ] ) = > [ key , item ] )
case "hasOwn" :
return Object . hasOwn ( requireObject ( ) , String ( args [ 1 ] ) )
2026-07-03 05:19:11 +00:00
case "assign" : {
const out : Record < string , unknown > = Object . create ( null )
for ( const source of args ) {
if ( source === null || source === undefined ) continue
const value = boundedData ( source , "Object.assign input" )
// A sandbox value source contributes nothing (no own enumerable properties in JS).
if ( isSandboxValue ( value ) ) continue
2026-07-03 05:20:28 +00:00
if ( value === null || typeof value !== "object" || Array . isArray ( value ) )
throw new InterpreterRuntimeError ( "Object.assign expects data objects." , node )
2026-07-03 05:19:11 +00:00
for ( const [ key , item ] of Object . entries ( value ) ) guardedSet ( out , key , item )
}
return out
}
case "fromEntries" : {
// A Map is the idiomatic fromEntries source; use its entries directly (the data
// checkpoint would serialize a Map to {}).
if ( args [ 0 ] instanceof SandboxMap ) {
const out : Record < string , unknown > = Object . create ( null )
for ( const [ key , item ] of ( args [ 0 ] as SandboxMap ) . map . entries ( ) ) guardedSet ( out , coerceToString ( key ) , item )
return out
}
const pairs = boundedData ( args [ 0 ] , "Object.fromEntries input" )
2026-07-03 05:20:28 +00:00
if ( ! Array . isArray ( pairs ) )
throw new InterpreterRuntimeError ( "Object.fromEntries expects an array of [key, value] pairs." , node )
2026-07-03 05:19:11 +00:00
const out : Record < string , unknown > = Object . create ( null )
for ( const pair of pairs ) {
2026-07-03 05:20:28 +00:00
if ( ! Array . isArray ( pair ) )
throw new InterpreterRuntimeError ( "Object.fromEntries expects [key, value] pairs." , node )
2026-07-03 05:19:11 +00:00
guardedSet ( out , String ( pair [ 0 ] ) , pair [ 1 ] )
}
return out
}
2026-07-03 05:20:28 +00:00
default :
throw new InterpreterRuntimeError ( ` Object. ${ name } is not available in CodeMode. ` , node )
2026-07-03 05:19:11 +00:00
}
}
const invokeMathMethod = ( name : string , args : Array < unknown > , node : AstNode ) : number = > {
const nums = args . map ( ( arg ) = > {
if ( typeof arg !== "number" ) throw new InterpreterRuntimeError ( ` Math. ${ name } expects number arguments. ` , node )
return arg
} )
const [ a = Number . NaN , b = Number . NaN ] = nums
switch ( name ) {
2026-07-03 05:20:28 +00:00
case "max" :
return Math . max ( . . . nums )
case "min" :
return Math . min ( . . . nums )
case "abs" :
return Math . abs ( a )
case "floor" :
return Math . floor ( a )
case "ceil" :
return Math . ceil ( a )
case "round" :
return Math . round ( a )
case "trunc" :
return Math . trunc ( a )
case "sign" :
return Math . sign ( a )
case "sqrt" :
return Math . sqrt ( a )
case "cbrt" :
return Math . cbrt ( a )
case "pow" :
return Math . pow ( a , b )
case "hypot" :
return Math . hypot ( . . . nums )
case "log" :
return Math . log ( a )
case "log2" :
return Math . log2 ( a )
case "log10" :
return Math . log10 ( a )
case "exp" :
return Math . exp ( a )
default :
throw new InterpreterRuntimeError ( ` Math. ${ name } is not available in CodeMode. ` , node )
2026-07-03 05:19:11 +00:00
}
}
const invokeJsonMethod = ( name : string , args : Array < unknown > , node : AstNode ) : unknown = > {
switch ( name ) {
case "stringify" : {
const replacer = args [ 1 ]
if ( Array . isArray ( replacer ) || replacer instanceof CodeModeFunction ) {
2026-07-03 05:20:28 +00:00
throw new InterpreterRuntimeError (
"JSON.stringify replacers are not supported in CodeMode." ,
node ,
"UnsupportedSyntax" ,
[ supportedSyntaxMessage ] ,
)
2026-07-03 05:19:11 +00:00
}
const space = args [ 2 ]
const indent = typeof space === "number" || typeof space === "string" ? space : undefined
// copyIn first so only Data Values serialize, never a CodeModeFunction/ToolReference.
return JSON . stringify ( copyOut ( copyIn ( args [ 0 ] , "JSON.stringify value" ) ) , null , indent )
}
case "parse" : {
const text = args [ 0 ]
if ( typeof text !== "string" ) throw new InterpreterRuntimeError ( "JSON.parse expects a string." , node )
let parsed : unknown
try {
parsed = JSON . parse ( text )
} catch ( error ) {
// The engine reason is derived from the program-supplied string (token/position), so
// it is safe to surface - and the position is exactly what a model needs to fix it.
throw new InterpreterRuntimeError (
` JSON.parse received invalid JSON: ${ error instanceof Error ? error.message : String ( error ) } ` ,
node ,
) . as ( "SyntaxError" )
}
return copyIn ( parsed , "JSON.parse result" )
}
2026-07-03 05:20:28 +00:00
default :
throw new InterpreterRuntimeError ( ` JSON. ${ name } is not available in CodeMode. ` , node )
2026-07-03 05:19:11 +00:00
}
}
const invokeArrayStatic = ( name : string , args : Array < unknown > , node : AstNode ) : unknown = > {
switch ( name ) {
case "isArray" :
return Array . isArray ( args [ 0 ] )
case "of" :
return [ . . . args ]
case "from" : {
if ( args . length > 1 ) {
throw new InterpreterRuntimeError (
"Array.from(...) does not support a map function in CodeMode; call .map() on the result instead." ,
node ,
"UnsupportedSyntax" ,
[ supportedSyntaxMessage ] ,
)
}
// Map/Set materialize directly (the data checkpoint would serialize them to {}).
2026-07-03 05:20:28 +00:00
if ( args [ 0 ] instanceof SandboxMap )
return Array . from ( ( args [ 0 ] as SandboxMap ) . map . entries ( ) , ( [ key , item ] ) = > [ key , item ] )
2026-07-03 05:19:11 +00:00
if ( args [ 0 ] instanceof SandboxSet ) return Array . from ( ( args [ 0 ] as SandboxSet ) . set . values ( ) )
const source = boundedData ( args [ 0 ] , "Array.from input" )
if ( typeof source === "string" ) return Array . from ( source )
if ( Array . isArray ( source ) ) return [ . . . source ]
2026-07-03 05:20:28 +00:00
if (
source !== null &&
typeof source === "object" &&
typeof ( source as { length? : unknown } ) . length === "number"
) {
2026-07-03 05:19:11 +00:00
return Array . from ( source as ArrayLike < unknown > )
}
throw new InterpreterRuntimeError ( "Array.from expects an array, string, Map, Set, or array-like value." , node )
}
default :
throw new InterpreterRuntimeError ( ` Array. ${ name } is not available in CodeMode. ` , node )
}
}
const invokeNumberStatic = ( name : string , args : Array < unknown > , node : AstNode ) : unknown = > {
const value = args [ 0 ]
switch ( name ) {
2026-07-03 05:20:28 +00:00
case "isInteger" :
return Number . isInteger ( value )
case "isFinite" :
return Number . isFinite ( value )
case "isNaN" :
return Number . isNaN ( value )
case "isSafeInteger" :
return Number . isSafeInteger ( value )
2026-07-03 05:19:11 +00:00
case "parseInt" : {
const radix = args [ 1 ]
2026-07-03 05:20:28 +00:00
if ( radix !== undefined && typeof radix !== "number" )
throw new InterpreterRuntimeError ( "Number.parseInt expects a numeric radix." , node )
2026-07-03 05:19:11 +00:00
return parseInt ( coerceToString ( value ) , radix )
}
2026-07-03 05:20:28 +00:00
case "parseFloat" :
return parseFloat ( coerceToString ( value ) )
default :
throw new InterpreterRuntimeError ( ` Number. ${ name } is not available in CodeMode. ` , node )
2026-07-03 05:19:11 +00:00
}
}
const invokeStringStatic = ( name : string , args : Array < unknown > , node : AstNode ) : unknown = > {
const codes = args . map ( ( arg ) = > {
if ( typeof arg !== "number" ) throw new InterpreterRuntimeError ( ` String. ${ name } expects number arguments. ` , node )
return arg
} )
switch ( name ) {
2026-07-03 05:20:28 +00:00
case "fromCharCode" :
return String . fromCharCode ( . . . codes )
case "fromCodePoint" :
return String . fromCodePoint ( . . . codes )
default :
throw new InterpreterRuntimeError ( ` String. ${ name } is not available in CodeMode. ` , node )
2026-07-03 05:19:11 +00:00
}
}
const invokeDateStatic = ( name : string , args : Array < unknown > , node : AstNode ) : number = > {
switch ( name ) {
2026-07-03 05:20:28 +00:00
case "now" :
return Date . now ( )
case "parse" :
return Date . parse ( coerceToString ( args [ 0 ] ) )
2026-07-03 05:19:11 +00:00
case "UTC" : {
const parts = args . map ( ( arg ) = > coerceToNumber ( arg ) )
return Date . UTC ( . . . ( parts as Parameters < typeof Date.UTC > ) )
}
2026-07-03 05:20:28 +00:00
default :
throw new InterpreterRuntimeError ( ` Date. ${ name } is not available in CodeMode. ` , node )
2026-07-03 05:19:11 +00:00
}
}
const invokeDateMethod = ( value : SandboxDate , name : string , node : AstNode ) : unknown = > {
const hosted = new Date ( value . time )
switch ( name ) {
2026-07-03 05:20:28 +00:00
case "getTime" :
case "valueOf" :
return value . time
2026-07-03 05:19:11 +00:00
case "toISOString" : {
if ( ! Number . isFinite ( value . time ) ) throw new InterpreterRuntimeError ( "Invalid time value." , node )
return hosted . toISOString ( )
}
// toJSON of an invalid date is null in JS (never a throw); toString stays ISO for
// determinism across host timezones/locales.
2026-07-03 05:20:28 +00:00
case "toJSON" :
return Number . isFinite ( value . time ) ? hosted . toISOString ( ) : null
case "toString" :
return coerceToString ( value )
case "getFullYear" :
return hosted . getFullYear ( )
case "getMonth" :
return hosted . getMonth ( )
case "getDate" :
return hosted . getDate ( )
case "getDay" :
return hosted . getDay ( )
case "getHours" :
return hosted . getHours ( )
case "getMinutes" :
return hosted . getMinutes ( )
case "getSeconds" :
return hosted . getSeconds ( )
case "getMilliseconds" :
return hosted . getMilliseconds ( )
case "getUTCFullYear" :
return hosted . getUTCFullYear ( )
case "getUTCMonth" :
return hosted . getUTCMonth ( )
case "getUTCDate" :
return hosted . getUTCDate ( )
case "getUTCDay" :
return hosted . getUTCDay ( )
case "getUTCHours" :
return hosted . getUTCHours ( )
case "getUTCMinutes" :
return hosted . getUTCMinutes ( )
case "getUTCSeconds" :
return hosted . getUTCSeconds ( )
case "getUTCMilliseconds" :
return hosted . getUTCMilliseconds ( )
case "getTimezoneOffset" :
return hosted . getTimezoneOffset ( )
default :
throw new InterpreterRuntimeError ( ` Date method ' ${ name } ' is not available in CodeMode. ` , node )
2026-07-03 05:19:11 +00:00
}
}
const invokeRegExpMethod = ( value : SandboxRegExp , name : string , args : Array < unknown > , node : AstNode ) : unknown = > {
switch ( name ) {
// test/exec run on the sandbox regex's own host instance, so `g`-flag lastIndex advances
// across calls per the spec.
2026-07-03 05:20:28 +00:00
case "test" :
return value . regex . test ( coerceToString ( args [ 0 ] ) )
2026-07-03 05:19:11 +00:00
case "exec" : {
const matched = value . regex . exec ( coerceToString ( args [ 0 ] ) )
if ( matched === null ) return null
return matchToValue ( matched )
}
2026-07-03 05:20:28 +00:00
case "toString" :
return coerceToString ( value )
default :
throw new InterpreterRuntimeError ( ` RegExp method ' ${ name } ' is not available in CodeMode. ` , node )
2026-07-03 05:19:11 +00:00
}
}
const invokeGlobalMethod = ( ref : GlobalMethodReference , args : Array < unknown > , node : AstNode ) : unknown = > {
2026-07-03 05:20:28 +00:00
if ( ref . namespace === "console" )
throw new InterpreterRuntimeError ( ` console. ${ ref . name } is not available in CodeMode. ` , node )
2026-07-03 05:19:11 +00:00
if ( ref . namespace === "Object" ) return invokeObjectMethod ( ref . name , args , node )
if ( ref . namespace === "Math" ) return invokeMathMethod ( ref . name , args , node )
if ( ref . namespace === "Array" ) return invokeArrayStatic ( ref . name , args , node )
if ( ref . namespace === "Number" ) return invokeNumberStatic ( ref . name , args , node )
if ( ref . namespace === "String" ) return invokeStringStatic ( ref . name , args , node )
if ( ref . namespace === "Date" ) {
2026-07-03 05:20:28 +00:00
if ( ! dateStatics . has ( ref . name ) )
throw new InterpreterRuntimeError ( ` Date. ${ ref . name } is not available in CodeMode. ` , node )
2026-07-03 05:19:11 +00:00
return invokeDateStatic ( ref . name , args , node )
}
if ( ref . namespace === "RegExp" || ref . namespace === "Map" || ref . namespace === "Set" ) {
throw new InterpreterRuntimeError ( ` ${ ref . namespace } . ${ ref . name } is not available in CodeMode. ` , node )
}
return invokeJsonMethod ( ref . name , args , node )
}
// Iterable spread sources: arrays, strings (code points), Maps (entry pairs), and Sets (values).
const spreadItems = ( spread : unknown ) : Array < unknown > | undefined = > {
if ( Array . isArray ( spread ) ) return spread
if ( typeof spread === "string" ) return Array . from ( spread )
2026-07-03 05:20:28 +00:00
if ( spread instanceof SandboxMap )
return Array . from ( spread . map . entries ( ) , ( [ key , item ] ) : Array < unknown > = > [ key , item ] )
2026-07-03 05:19:11 +00:00
if ( spread instanceof SandboxSet ) return Array . from ( spread . set . values ( ) )
return undefined
}
// Every identifier a parameter pattern binds, used to seed TDZ slots before defaults run.
const collectPatternNames = ( pattern : AstNode , out : Array < string > = [ ] ) : Array < string > = > {
switch ( pattern . type ) {
case "Identifier" :
out . push ( getString ( pattern , "name" ) )
break
case "AssignmentPattern" :
collectPatternNames ( getNode ( pattern , "left" ) , out )
break
case "RestElement" :
collectPatternNames ( getNode ( pattern , "argument" ) , out )
break
case "ArrayPattern" :
for ( const element of getArray ( pattern , "elements" ) ) {
if ( element !== null ) collectPatternNames ( asNode ( element , "elements" ) , out )
}
break
case "ObjectPattern" :
for ( const property of getArray ( pattern , "properties" ) ) {
const prop = asNode ( property , "properties" )
collectPatternNames ( prop . type === "RestElement" ? getNode ( prop , "argument" ) : getNode ( prop , "value" ) , out )
}
break
}
return out
}
class Interpreter < R > {
private scopes : Array < Map < string , Binding > >
private readonly invokeTool : ( path : ReadonlyArray < string > , args : Array < unknown > ) = > Effect . Effect < unknown , unknown , R >
// Enumerable namespace/tool names at a node of the host tool tree, threaded from
// ToolRuntime.make like invokeTool: the interpreter never holds the tree itself.
private readonly toolKeys : ( path : ReadonlyArray < string > ) = > ReadonlyArray < string >
private readonly logs : Array < string >
private lastValue : unknown
// Caps how many eagerly forked tool calls run at once (the parallel-call concurrency cap).
private readonly callPermits : Semaphore.Semaphore
// Fiber-backed promises whose settlement no program construct has observed yet. Successful
// program completion drains these (like a runtime waiting on in-flight work at exit) and
// surfaces a never-awaited failure as an unhandled-rejection diagnostic.
private readonly pendingSettlements = new Set < SandboxPromise > ( )
constructor (
invokeTool : ( path : ReadonlyArray < string > , args : Array < unknown > ) = > Effect . Effect < unknown , unknown , R > ,
toolKeys : ( path : ReadonlyArray < string > ) = > ReadonlyArray < string > ,
logs : Array < string > = [ ] ,
) {
const globalScope = new Map < string , Binding > ( )
this . scopes = [ globalScope ]
this . invokeTool = invokeTool
this . toolKeys = toolKeys
this . logs = logs
this . lastValue = undefined
this . callPermits = Semaphore . makeUnsafe ( TOOL_CALL_CONCURRENCY )
globalScope . set ( "tools" , { mutable : false , value : new ToolReference ( [ ] ) } )
globalScope . set ( "Promise" , { mutable : false , value : new PromiseNamespace ( ) } )
globalScope . set ( "undefined" , { mutable : false , value : undefined } )
globalScope . set ( "Object" , { mutable : false , value : new GlobalNamespace ( "Object" ) } )
globalScope . set ( "Math" , { mutable : false , value : new GlobalNamespace ( "Math" ) } )
globalScope . set ( "JSON" , { mutable : false , value : new GlobalNamespace ( "JSON" ) } )
globalScope . set ( "Number" , { mutable : false , value : new CoercionFunction ( "Number" ) } )
globalScope . set ( "String" , { mutable : false , value : new CoercionFunction ( "String" ) } )
globalScope . set ( "Boolean" , { mutable : false , value : new CoercionFunction ( "Boolean" ) } )
globalScope . set ( "Array" , { mutable : false , value : new GlobalNamespace ( "Array" ) } )
globalScope . set ( "console" , { mutable : false , value : new GlobalNamespace ( "console" ) } )
globalScope . set ( "parseInt" , { mutable : false , value : new CoercionFunction ( "parseInt" ) } )
globalScope . set ( "parseFloat" , { mutable : false , value : new CoercionFunction ( "parseFloat" ) } )
globalScope . set ( "Date" , { mutable : false , value : new GlobalNamespace ( "Date" ) } )
globalScope . set ( "RegExp" , { mutable : false , value : new GlobalNamespace ( "RegExp" ) } )
globalScope . set ( "Map" , { mutable : false , value : new GlobalNamespace ( "Map" ) } )
globalScope . set ( "Set" , { mutable : false , value : new GlobalNamespace ( "Set" ) } )
// Error constructors are real values, so `x instanceof Error` works and `Error("msg")`
// (with or without `new`) constructs a branded { name, message } error object.
for ( const name of errorConstructors ) {
globalScope . set ( name , { mutable : false , value : new ErrorConstructorReference ( name ) } )
}
// NaN/Infinity flow as ordinary in-sandbox values (normalized to null only at the data
// boundary - see copyOut), so their global bindings must exist too, e.g. `reduce(max, -Infinity)`.
globalScope . set ( "NaN" , { mutable : false , value : NaN } )
globalScope . set ( "Infinity" , { mutable : false , value : Infinity } )
}
run ( program : ProgramNode ) : Effect . Effect < unknown , unknown , R > {
const self = this
// Run the program body in its own module scope on top of the builtin global scope, so
// top-level declarations (`let undefined = 5`, `const Object = ...`) shadow builtins like
// JS module scope, instead of colliding with the seeded globals.
this . pushScope ( )
2026-07-03 05:20:28 +00:00
return Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
self . hoistFunctions ( program . body )
let value : unknown = undefined
let returned = false
for ( const statement of program . body ) {
const result = yield * self . evaluateStatement ( statement )
if ( result . kind === "return" ) {
value = result . value
returned = true
break
}
if ( result . kind === "break" || result . kind === "continue" ) {
throw new InterpreterRuntimeError ( ` Unexpected ' ${ result . kind } ' outside of a loop. ` , statement )
}
if ( result . kind === "value" ) {
self . lastValue = result . value
}
}
if ( ! returned ) value = self . lastValue
// The program body runs inside an implicit async function, so a returned promise
// resolves before crossing the data boundary - `return tools.ns.tool(...)` works
// without an explicit await, exactly as in JS.
if ( value instanceof SandboxPromise ) value = yield * self . settlePromise ( value )
yield * self . drainPendingSettlements ( )
return value
} ) . pipe ( Effect . ensuring ( Effect . sync ( ( ) = > self . popScope ( ) ) ) )
}
// Awaits every fiber-backed promise the program abandoned (fire-and-forget tool calls), so
// their work completes before the execution ends - mirroring a JS runtime waiting on
// in-flight I/O at exit. A failure nobody could have handled becomes an unhandled-rejection
// diagnostic (interrupted calls, e.g. Promise.race losers, are ignored).
private drainPendingSettlements ( ) : Effect . Effect < void , unknown , never > {
const self = this
2026-07-03 05:20:28 +00:00
return Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
for ( const promise of [ . . . self . pendingSettlements ] ) {
const exit = yield * self . observePromise ( promise )
if ( Exit . isSuccess ( exit ) || Cause . hasInterruptsOnly ( exit . cause ) ) continue
const failure = normalizeError ( Cause . squash ( exit . cause ) )
throw new InterpreterRuntimeError (
` Unhandled rejection from an un-awaited tool call: ${ failure . message } ` ,
undefined ,
failure . kind ,
[ "Await tool calls - `const result = await tools.ns.tool(...)` - so failures can be caught and handled." ] ,
)
}
} )
}
// Eagerly starts a tool call on a supervised child fiber (so the execution timeout and
// scope teardown interrupt it) gated by the concurrency semaphore, and wraps the fiber in a
// first-class promise value. `startImmediately` makes the runtime admit the call - charging
// the tool-call budget and firing onToolCallStart - at the call site, before any await.
2026-07-03 05:20:28 +00:00
private createToolCallPromise (
path : ReadonlyArray < string > ,
args : Array < unknown > ,
) : Effect . Effect < SandboxPromise , never , R > {
2026-07-03 05:19:11 +00:00
const self = this
return Effect . map (
2026-07-03 05:20:28 +00:00
Effect . forkChild ( this . callPermits . withPermit ( Effect . suspend ( ( ) = > self . invokeTool ( path , args ) ) ) , {
startImmediately : true ,
} ) ,
2026-07-03 05:19:11 +00:00
( fiber ) = > {
const promise = new SandboxPromise ( fiber )
self . pendingSettlements . add ( promise )
return promise
} ,
)
}
// The promise's settlement as an Exit, marking it observed for unhandled-rejection tracking.
// Fiber settlement is idempotent, so observing the same promise repeatedly (await twice,
// Promise.all([p, p])) never re-runs the underlying call.
private observePromise ( promise : SandboxPromise ) : Effect . Effect < Exit.Exit < unknown , unknown > > {
this . pendingSettlements . delete ( promise )
return promise . fiber !== undefined ? Fiber . await ( promise . fiber ) : Effect . exit ( promise . immediate ? ? Effect . void )
}
// `await promise`: succeed with the fulfilled value or re-raise the failure so try/catch
// observes it exactly like a synchronous throw at the await site.
private settlePromise ( promise : SandboxPromise , node? : AstNode ) : Effect . Effect < unknown , unknown , never > {
const self = this
return Effect . flatMap ( this . observePromise ( promise ) , ( exit ) = > self . unwrapPromiseExit ( promise , exit , node ) )
}
2026-07-03 05:20:28 +00:00
private unwrapPromiseExit (
promise : SandboxPromise | undefined ,
exit : Exit.Exit < unknown , unknown > ,
node? : AstNode ,
) : Effect . Effect < unknown , unknown > {
2026-07-03 05:19:11 +00:00
if ( Exit . isSuccess ( exit ) ) return Effect . succeed ( exit . value )
// A call Promise.race interrupted after losing settles as a catchable program failure;
// any other interruption is execution teardown (timeout/host) and must keep propagating
// as interruption rather than becoming program-visible data.
if ( promise ? . interrupted === true && Cause . hasInterruptsOnly ( exit . cause ) ) {
2026-07-03 05:20:28 +00:00
return Effect . fail (
new InterpreterRuntimeError (
"This tool call was interrupted because another value settled a Promise.race first." ,
node ,
) ,
)
2026-07-03 05:19:11 +00:00
}
return Effect . failCause ( exit . cause )
}
private evaluateStatement ( node : AstNode ) : Effect . Effect < StatementResult , unknown , R > {
switch ( node . type ) {
case "ExpressionStatement" :
return Effect . map ( this . evaluateExpression ( getNode ( node , "expression" ) ) , ( value ) = > ( { kind : "value" , value } ) )
case "VariableDeclaration" :
return Effect . map ( this . evaluateVariableDeclaration ( node ) , ( ) = > ( { kind : "none" } ) )
case "ReturnStatement" : {
const argumentNode = getOptionalNode ( node , "argument" )
return argumentNode
? Effect . map ( this . evaluateExpression ( argumentNode ) , ( value ) = > ( { kind : "return" , value } ) )
: Effect . succeed ( { kind : "return" , value : undefined } )
}
case "BlockStatement" :
return this . evaluateBlock ( node )
case "IfStatement" :
return this . evaluateIfStatement ( node )
case "SwitchStatement" :
return this . evaluateSwitchStatement ( node )
case "WhileStatement" :
return this . evaluateWhileStatement ( node )
case "DoWhileStatement" :
return this . evaluateDoWhileStatement ( node )
case "ForStatement" :
return this . evaluateForStatement ( node )
case "ForOfStatement" :
return this . evaluateForOfStatement ( node )
case "ForInStatement" :
return this . evaluateForInStatement ( node )
case "BreakStatement" :
return Effect . succeed ( this . evaluateBreakStatement ( node ) )
case "ContinueStatement" :
return Effect . succeed ( this . evaluateContinueStatement ( node ) )
case "ThrowStatement" :
return this . evaluateThrowStatement ( node )
case "TryStatement" :
return this . evaluateTryStatement ( node )
case "EmptyStatement" :
return Effect . succeed ( { kind : "none" } )
case "FunctionDeclaration" :
return Effect . succeed ( { kind : "none" } ) // bound ahead of time by hoistFunctions
default :
throw unsupportedSyntax ( node . type , node )
}
}
private evaluateBlock ( node : AstNode ) : Effect . Effect < StatementResult , unknown , R > {
this . pushScope ( )
const self = this
2026-07-03 05:20:28 +00:00
return Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
const body = getArray ( node , "body" )
self . hoistFunctions ( body )
for ( const statementValue of body ) {
const statement = asNode ( statementValue , "body" )
const result = yield * self . evaluateStatement ( statement )
if ( result . kind === "value" ) {
self . lastValue = result . value
continue
}
if ( result . kind !== "none" ) {
return result
}
}
return { kind : "none" } satisfies StatementResult
} ) . pipe ( Effect . ensuring ( Effect . sync ( ( ) = > self . popScope ( ) ) ) )
}
private createFunction ( node : AstNode ) : CodeModeFunction {
if ( node . generator === true ) {
2026-07-03 05:20:28 +00:00
throw new InterpreterRuntimeError (
"Generator functions are not supported in CodeMode." ,
node ,
"UnsupportedSyntax" ,
[ supportedSyntaxMessage ] ,
)
2026-07-03 05:19:11 +00:00
}
return new CodeModeFunction (
getArray ( node , "params" ) . map ( ( parameter , index ) = > asNode ( parameter , ` params[ ${ index } ] ` ) ) ,
getNode ( node , "body" ) ,
this . scopes . slice ( ) ,
)
}
// Function declarations are hoisted: bound in their scope before the body runs, so a
// program can call a helper defined further down (matching JavaScript).
private hoistFunctions ( statements : Array < unknown > ) : void {
for ( const statementValue of statements ) {
if ( ! isRecord ( statementValue ) || statementValue . type !== "FunctionDeclaration" ) continue
const node = statementValue as AstNode
this . declare ( getString ( getNode ( node , "id" ) , "name" ) , this . createFunction ( node ) , true , node )
}
}
private evaluateIfStatement ( node : AstNode ) : Effect . Effect < StatementResult , unknown , R > {
const testNode = getNode ( node , "test" )
const consequentNode = getNode ( node , "consequent" )
const alternateNode = getOptionalNode ( node , "alternate" )
return Effect . flatMap ( this . evaluateExpression ( testNode ) , ( test ) = >
2026-07-03 05:20:28 +00:00
test
? this . evaluateStatement ( consequentNode )
: alternateNode
? this . evaluateStatement ( alternateNode )
: Effect . succeed ( { kind : "none" } ) ,
)
2026-07-03 05:19:11 +00:00
}
private evaluateSwitchStatement ( node : AstNode ) : Effect . Effect < StatementResult , unknown , R > {
const self = this
this . pushScope ( )
2026-07-03 05:20:28 +00:00
return Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
const discriminant = yield * self . evaluateExpression ( getNode ( node , "discriminant" ) )
if ( containsOpaqueReference ( discriminant ) ) {
2026-07-03 05:20:28 +00:00
throw new InterpreterRuntimeError (
"Switch discriminants must be data values in CodeMode." ,
node ,
"InvalidDataValue" ,
)
2026-07-03 05:19:11 +00:00
}
const cases = getArray ( node , "cases" ) . map ( ( value , index ) = > asNode ( value , ` cases[ ${ index } ] ` ) )
let defaultIndex : number | undefined
let selected : number | undefined
for ( const [ index , branch ] of cases . entries ( ) ) {
const test = getOptionalNode ( branch , "test" )
if ( ! test ) {
defaultIndex = index
continue
}
const candidate = yield * self . evaluateExpression ( test )
if ( containsOpaqueReference ( candidate ) ) {
2026-07-03 05:20:28 +00:00
throw new InterpreterRuntimeError (
"Switch case values must be data values in CodeMode." ,
test ,
"InvalidDataValue" ,
)
2026-07-03 05:19:11 +00:00
}
if ( candidate === discriminant ) {
selected = index
break
}
}
const start = selected ? ? defaultIndex
if ( start === undefined ) return { kind : "none" } satisfies StatementResult
for ( let index = start ; index < cases . length ; index += 1 ) {
for ( const statementValue of getArray ( cases [ index ] ! , "consequent" ) ) {
const result = yield * self . evaluateStatement ( asNode ( statementValue , "consequent" ) )
if ( result . kind === "break" ) return { kind : "none" } satisfies StatementResult
if ( result . kind === "return" || result . kind === "continue" ) return result
if ( result . kind === "value" ) self . lastValue = result . value
}
}
return { kind : "none" } satisfies StatementResult
} ) . pipe ( Effect . ensuring ( Effect . sync ( ( ) = > self . popScope ( ) ) ) )
}
private evaluateWhileStatement ( node : AstNode ) : Effect . Effect < StatementResult , unknown , R > {
const testNode = getNode ( node , "test" )
const bodyNode = getNode ( node , "body" )
const self = this
2026-07-03 05:20:28 +00:00
return Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
while ( yield * self . evaluateExpression ( testNode ) ) {
const result = yield * self . evaluateStatement ( bodyNode )
if ( result . kind === "continue" ) {
continue
}
if ( result . kind === "break" ) {
2026-07-03 05:20:28 +00:00
return { kind : "none" } satisfies StatementResult
2026-07-03 05:19:11 +00:00
}
if ( result . kind === "return" ) {
return result
}
if ( result . kind === "value" ) {
self . lastValue = result . value
}
}
return { kind : "none" } satisfies StatementResult
} )
}
private evaluateDoWhileStatement ( node : AstNode ) : Effect . Effect < StatementResult , unknown , R > {
const bodyNode = getNode ( node , "body" )
const testNode = getNode ( node , "test" )
const self = this
2026-07-03 05:20:28 +00:00
return Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
do {
const result = yield * self . evaluateStatement ( bodyNode )
if ( result . kind === "continue" ) {
continue
}
if ( result . kind === "break" ) {
return { kind : "none" } satisfies StatementResult
}
if ( result . kind === "return" ) {
return result
}
if ( result . kind === "value" ) {
self . lastValue = result . value
}
} while ( yield * self . evaluateExpression ( testNode ) )
return { kind : "none" } satisfies StatementResult
} )
}
private evaluateForStatement ( node : AstNode ) : Effect . Effect < StatementResult , unknown , R > {
this . pushScope ( )
const self = this
2026-07-03 05:20:28 +00:00
return Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
const initNode = getOptionalNode ( node , "init" )
const testNode = getOptionalNode ( node , "test" )
const updateNode = getOptionalNode ( node , "update" )
const bodyNode = getNode ( node , "body" )
if ( initNode ) {
if ( initNode . type === "VariableDeclaration" ) {
yield * self . evaluateVariableDeclaration ( initNode )
} else {
yield * self . evaluateExpression ( initNode )
}
}
2026-07-03 05:20:28 +00:00
const perIterationBindings =
initNode ? . type === "VariableDeclaration" && getString ( initNode , "kind" ) !== "var"
? Array . from ( self . currentScope ( ) . keys ( ) )
: [ ]
2026-07-03 05:19:11 +00:00
while ( testNode ? yield * self . evaluateExpression ( testNode ) : true ) {
let iterationScope : Map < string , Binding > | undefined
if ( perIterationBindings . length > 0 ) {
2026-07-03 05:20:28 +00:00
iterationScope = new Map (
perIterationBindings . map ( ( name ) = > {
const binding = self . currentScope ( ) . get ( name ) !
return [ name , { . . . binding } ]
} ) ,
)
2026-07-03 05:19:11 +00:00
self . scopes . push ( iterationScope )
}
const result = yield * self . evaluateStatement ( bodyNode ) . pipe (
2026-07-03 05:20:28 +00:00
Effect . ensuring (
Effect . sync ( ( ) = > {
if ( iterationScope ) self . popScope ( )
} ) ,
) ,
2026-07-03 05:19:11 +00:00
)
if ( result . kind === "return" ) {
return result
}
if ( result . kind === "break" ) {
return { kind : "none" } satisfies StatementResult
}
if ( result . kind === "value" ) {
self . lastValue = result . value
}
if ( iterationScope ) {
const loopScope = self . currentScope ( )
for ( const name of perIterationBindings ) {
loopScope . set ( name , { . . . iterationScope . get ( name ) ! } )
}
}
if ( updateNode ) {
yield * self . evaluateExpression ( updateNode )
}
if ( result . kind === "continue" ) {
continue
}
}
return { kind : "none" } satisfies StatementResult
} ) . pipe ( Effect . ensuring ( Effect . sync ( ( ) = > self . popScope ( ) ) ) )
}
private evaluateForOfStatement ( node : AstNode ) : Effect . Effect < StatementResult , unknown , R > {
if ( getBoolean ( node , "await" ) ) {
throw new InterpreterRuntimeError ( "for await...of is not supported." , node )
}
const self = this
2026-07-03 05:20:28 +00:00
return Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
const left = getNode ( node , "left" )
const right = yield * self . evaluateExpression ( getNode ( node , "right" ) )
const body = getNode ( node , "body" )
// Arrays iterate in place; strings iterate code points; Maps iterate [key, value]
// pairs and Sets iterate values over a snapshot (mutation during iteration is safe).
const iterable = Array . isArray ( right ) ? right : spreadItems ( right )
if ( iterable === undefined ) {
throw new InterpreterRuntimeError ( "for...of requires an array, string, Map, or Set value in CodeMode." , node )
}
let declaration : { readonly pattern : AstNode ; readonly mutable : boolean } | undefined
let assignmentName : string | undefined
if ( left . type === "VariableDeclaration" ) {
const declarations = getArray ( left , "declarations" )
if ( declarations . length !== 1 ) {
throw new InterpreterRuntimeError ( "for...of supports one declared binding." , left )
}
const declarator = asNode ( declarations [ 0 ] , "declarations[0]" )
declaration = { pattern : getNode ( declarator , "id" ) , mutable : getString ( left , "kind" ) !== "const" }
} else if ( left . type === "Identifier" ) {
assignmentName = getString ( left , "name" )
} else {
throw new InterpreterRuntimeError ( "Unsupported for...of binding." , left )
}
for ( const value of iterable ) {
if ( declaration ) {
self . pushScope ( )
yield * self . declarePattern ( declaration . pattern , value , declaration . mutable , left )
} else if ( assignmentName ) {
self . setIdentifierValue ( assignmentName , value , left )
}
const result = yield * self . evaluateStatement ( body ) . pipe (
2026-07-03 05:20:28 +00:00
Effect . ensuring (
Effect . sync ( ( ) = > {
if ( declaration ) self . popScope ( )
} ) ,
) ,
2026-07-03 05:19:11 +00:00
)
if ( result . kind === "return" ) {
return result
}
if ( result . kind === "break" ) {
return { kind : "none" }
}
if ( result . kind === "value" ) {
self . lastValue = result . value
}
if ( result . kind === "continue" ) {
continue
}
}
return { kind : "none" }
} )
}
// Own enumerable string keys of a value, shared by `for...in` and `Object.keys` over tool
// references: plain data objects enumerate their own keys, arrays their index strings (plus
// any own non-index properties, e.g. match results' index/groups - exactly Object.keys in
// JS), and a tool reference the namespace/tool names at its path in the host tool tree.
// Returns undefined for everything else so callers can raise a contextual error.
private enumerableKeys ( value : unknown ) : Array < string > | undefined {
if ( value instanceof ToolReference ) {
return [ . . . this . toolKeys ( value . path ) ]
}
if ( Array . isArray ( value ) ) {
return Object . keys ( value )
}
if ( value !== null && typeof value === "object" && ! isRuntimeReference ( value ) ) {
return Object . keys ( value )
}
return undefined
}
private evaluateForInStatement ( node : AstNode ) : Effect . Effect < StatementResult , unknown , R > {
const self = this
2026-07-03 05:20:28 +00:00
return Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
const left = getNode ( node , "left" )
const right = yield * self . evaluateExpression ( getNode ( node , "right" ) )
const body = getNode ( node , "body" )
// Keys are snapshotted up front (mutation during iteration is safe): plain objects
// enumerate their own keys, arrays their index strings, and tool references the
// namespace/tool names at that node - the same enumeration Object.keys performs.
// Anything else (strings, Maps, Sets, numbers, null, ...) is a deliberate error rather
// than real JS's surprising behavior (indices for strings, zero iterations for
// Maps/Sets/null): the hint points at the constructs that do what the program means.
const keys = self . enumerableKeys ( right )
if ( keys === undefined ) {
throw new InterpreterRuntimeError (
"for...in requires a plain object, array, or tools reference in CodeMode. Use for...of for arrays/strings/Maps/Sets, or Object.keys(value) for a key list." ,
node ,
)
}
let declaration : { readonly pattern : AstNode ; readonly mutable : boolean } | undefined
let assignmentName : string | undefined
if ( left . type === "VariableDeclaration" ) {
const declarations = getArray ( left , "declarations" )
if ( declarations . length !== 1 ) {
throw new InterpreterRuntimeError ( "for...in supports one declared binding." , left )
}
const declarator = asNode ( declarations [ 0 ] , "declarations[0]" )
declaration = { pattern : getNode ( declarator , "id" ) , mutable : getString ( left , "kind" ) !== "const" }
} else if ( left . type === "Identifier" ) {
assignmentName = getString ( left , "name" )
} else {
throw new InterpreterRuntimeError ( "Unsupported for...in binding." , left )
}
for ( const key of keys ) {
if ( declaration ) {
self . pushScope ( )
yield * self . declarePattern ( declaration . pattern , key , declaration . mutable , left )
} else if ( assignmentName ) {
self . setIdentifierValue ( assignmentName , key , left )
}
const result = yield * self . evaluateStatement ( body ) . pipe (
2026-07-03 05:20:28 +00:00
Effect . ensuring (
Effect . sync ( ( ) = > {
if ( declaration ) self . popScope ( )
} ) ,
) ,
2026-07-03 05:19:11 +00:00
)
if ( result . kind === "return" ) {
return result
}
if ( result . kind === "break" ) {
return { kind : "none" }
}
if ( result . kind === "value" ) {
self . lastValue = result . value
}
if ( result . kind === "continue" ) {
continue
}
}
return { kind : "none" }
} )
}
private evaluateBreakStatement ( node : AstNode ) : StatementResult {
const labelNode = getOptionalNode ( node , "label" )
if ( labelNode ) {
throw new InterpreterRuntimeError ( "Labeled break is not supported in v1." , node )
}
return { kind : "break" }
}
private evaluateContinueStatement ( node : AstNode ) : StatementResult {
const labelNode = getOptionalNode ( node , "label" )
if ( labelNode ) {
throw new InterpreterRuntimeError ( "Labeled continue is not supported in v1." , node )
}
return { kind : "continue" }
}
private evaluateThrowStatement ( node : AstNode ) : Effect . Effect < StatementResult , unknown , R > {
const argument = getNode ( node , "argument" )
return Effect . flatMap ( this . evaluateExpression ( argument ) , ( value ) = > Effect . fail ( new ProgramThrow ( value ) ) )
}
private evaluateTryStatement ( node : AstNode ) : Effect . Effect < StatementResult , unknown , R > {
const body = getNode ( node , "block" )
const handler = getOptionalNode ( node , "handler" )
const finalizer = getOptionalNode ( node , "finalizer" )
const self = this
const attempted = Effect . matchCauseEffect ( this . evaluateStatement ( body ) , {
onFailure : ( cause ) = > {
if ( cause . reasons . some ( Cause . isInterruptReason ) || ! handler ) {
return Effect . failCause ( cause )
}
// The program sees a plain { message } error (or the thrown value itself) - see
// caughtErrorValue, shared with Promise.allSettled rejection reasons.
const caught = caughtErrorValue ( Cause . squash ( cause ) )
const parameter = getOptionalNode ( handler , "param" )
self . pushScope ( )
2026-07-03 05:20:28 +00:00
return Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
if ( parameter ) yield * self . declarePattern ( parameter , caught , true , handler )
return yield * self . evaluateStatement ( getNode ( handler , "body" ) )
2026-07-03 05:20:28 +00:00
} ) . pipe ( Effect . ensuring ( Effect . sync ( ( ) = > self . popScope ( ) ) ) )
2026-07-03 05:19:11 +00:00
} ,
onSuccess : Effect.succeed ,
} )
if ( ! finalizer ) return attempted
const isAbrupt = ( result : StatementResult ) : boolean = >
result . kind === "return" || result . kind === "break" || result . kind === "continue"
return Effect . matchCauseEffect ( attempted , {
onFailure : ( cause ) = >
cause . reasons . some ( Cause . isInterruptReason )
? Effect . failCause ( cause )
: Effect . flatMap ( this . evaluateStatement ( finalizer ) , ( final ) = >
2026-07-03 05:20:28 +00:00
isAbrupt ( final ) ? Effect . succeed ( final ) : Effect . failCause ( cause ) ,
) ,
2026-07-03 05:19:11 +00:00
onSuccess : ( result ) = >
Effect . flatMap ( this . evaluateStatement ( finalizer ) , ( final ) = >
2026-07-03 05:20:28 +00:00
isAbrupt ( final ) ? Effect . succeed ( final ) : Effect . succeed ( result ) ,
) ,
2026-07-03 05:19:11 +00:00
} )
}
private evaluateVariableDeclaration ( node : AstNode ) : Effect . Effect < void , unknown , R > {
const kind = getString ( node , "kind" )
const declarations = getArray ( node , "declarations" )
const self = this
2026-07-03 05:20:28 +00:00
return Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
for ( const declarationValue of declarations ) {
const declaration = asNode ( declarationValue , "declarations" )
if ( declaration . type !== "VariableDeclarator" ) {
throw new InterpreterRuntimeError ( "Unsupported variable declaration shape." , declaration )
}
const init = getOptionalNode ( declaration , "init" )
const value = init ? yield * self . evaluateExpression ( init ) : undefined
yield * self . declarePattern ( getNode ( declaration , "id" ) , value , kind !== "const" , declaration )
}
} )
}
2026-07-03 05:20:28 +00:00
private declarePattern (
pattern : AstNode ,
value : unknown ,
mutable : boolean ,
node : AstNode ,
) : Effect . Effect < void , unknown , R > {
2026-07-03 05:19:11 +00:00
const self = this
2026-07-03 05:20:28 +00:00
return Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
if ( pattern . type === "Identifier" ) {
self . declare ( getString ( pattern , "name" ) , value , mutable , node )
return
}
// Default values: `x = expr` / `{ a = 1 }` - the default is evaluated only when the value is undefined.
if ( pattern . type === "AssignmentPattern" ) {
const resolved = value === undefined ? yield * self . evaluateExpression ( getNode ( pattern , "right" ) ) : value
yield * self . declarePattern ( getNode ( pattern , "left" ) , resolved , mutable , node )
return
}
if ( pattern . type === "ObjectPattern" ) {
if ( value === null || typeof value !== "object" || Array . isArray ( value ) || isRuntimeReference ( value ) ) {
2026-07-03 05:20:28 +00:00
throw new InterpreterRuntimeError (
"Object destructuring requires a data object value." ,
pattern ,
"InvalidDataValue" ,
)
2026-07-03 05:19:11 +00:00
}
const consumed = new Set < string > ( )
for ( const propertyValue of getArray ( pattern , "properties" ) ) {
const property = asNode ( propertyValue , "properties" )
// Object rest: `{ a, ...others }` - gather the not-yet-consumed own keys.
if ( property . type === "RestElement" ) {
const rest : SafeObject = Object . create ( null ) as SafeObject
for ( const [ key , item ] of Object . entries ( value as SafeObject ) ) {
if ( ! consumed . has ( key ) && ! isBlockedMember ( key ) ) rest [ key ] = item
}
yield * self . declarePattern ( getNode ( property , "argument" ) , rest , mutable , property )
continue
}
2026-07-03 05:20:28 +00:00
if (
property . type !== "Property" ||
getBoolean ( property , "computed" ) ||
getString ( property , "kind" ) !== "init"
) {
2026-07-03 05:19:11 +00:00
throw new InterpreterRuntimeError ( "Only named object destructuring properties are supported." , property )
}
const keyNode = getNode ( property , "key" )
const key = keyNode . type === "Identifier" ? getString ( keyNode , "name" ) : String ( keyNode . value )
if ( isBlockedMember ( key ) ) {
throw new InterpreterRuntimeError ( ` Property ' ${ key } ' is not available in CodeMode. ` , keyNode )
}
consumed . add ( key )
yield * self . declarePattern ( getNode ( property , "value" ) , ( value as SafeObject ) [ key ] , mutable , property )
}
return
}
if ( pattern . type === "ArrayPattern" ) {
if ( ! Array . isArray ( value ) ) {
throw new InterpreterRuntimeError ( "Array destructuring requires an array value." , pattern )
}
for ( const [ index , item ] of getArray ( pattern , "elements" ) . entries ( ) ) {
if ( item === null ) continue
const element = asNode ( item , ` elements[ ${ index } ] ` )
// Array rest: `[head, ...tail]` - binds the remaining elements (must be last).
if ( element . type === "RestElement" ) {
yield * self . declarePattern ( getNode ( element , "argument" ) , value . slice ( index ) , mutable , element )
break
}
yield * self . declarePattern ( element , value [ index ] , mutable , pattern )
}
return
}
throw new InterpreterRuntimeError ( ` Unsupported binding pattern ' ${ pattern . type } '. ` , pattern )
} )
}
private evaluateExpression ( node : AstNode ) : Effect . Effect < unknown , unknown , R > {
switch ( node . type ) {
case "Literal" : {
// A regex literal parses as a Literal node carrying { pattern, flags }; construct the
// sandbox regex from those (the host `value` instance is never exposed).
const regex = node . regex
if ( isRecord ( regex ) && typeof regex . pattern === "string" ) {
2026-07-03 05:20:28 +00:00
return Effect . sync ( ( ) = >
this . constructRegExp ( [ regex . pattern , typeof regex . flags === "string" ? regex . flags : "" ] , node ) ,
)
2026-07-03 05:19:11 +00:00
}
return Effect . sync ( ( ) = > boundedData ( node . value , "Literal" ) )
}
case "Identifier" :
return Effect . sync ( ( ) = > this . getIdentifierValue ( getString ( node , "name" ) , node ) )
case "BinaryExpression" :
return this . evaluateBinaryExpression ( node )
case "LogicalExpression" :
return this . evaluateLogicalExpression ( node )
case "UnaryExpression" :
return this . evaluateUnaryExpression ( node )
case "AssignmentExpression" :
return this . evaluateAssignmentExpression ( node )
case "CallExpression" :
return this . evaluateCallExpression ( node )
case "ArrowFunctionExpression" :
case "FunctionExpression" :
return Effect . sync ( ( ) = > this . createFunction ( node ) )
case "MemberExpression" :
return this . readMember ( node )
case "ChainExpression" :
return Effect . map ( this . evaluateExpression ( getNode ( node , "expression" ) ) , ( value ) = >
2026-07-03 05:20:28 +00:00
value === OptionalShortCircuit ? undefined : value ,
)
2026-07-03 05:19:11 +00:00
case "ObjectExpression" :
return this . evaluateObjectExpression ( node )
case "ArrayExpression" :
return this . evaluateArrayExpression ( node )
case "TemplateLiteral" :
return this . evaluateTemplateLiteral ( node )
case "ConditionalExpression" :
return this . evaluateConditionalExpression ( node )
case "UpdateExpression" :
return this . evaluateUpdateExpression ( node )
case "AwaitExpression" : {
// `await` resolves a promise value; awaiting anything else is a passthrough no-op,
// matching real JS semantics for non-thenables.
const self = this
return Effect . flatMap ( this . evaluateExpression ( getNode ( node , "argument" ) ) , ( value ) = >
2026-07-03 05:20:28 +00:00
value instanceof SandboxPromise ? self . settlePromise ( value , node ) : Effect . succeed ( value ) ,
)
2026-07-03 05:19:11 +00:00
}
case "NewExpression" :
return this . evaluateNewExpression ( node )
default :
throw unsupportedSyntax ( node . type , node )
}
}
private evaluateNewExpression ( node : AstNode ) : Effect . Effect < unknown , unknown , R > {
const callee = getNode ( node , "callee" )
if ( callee . type !== "Identifier" ) {
throw unsupportedSyntax ( "NewExpression" , node )
}
const name = getString ( callee , "name" )
const argNodes = getArray ( node , "arguments" )
const self = this
if ( name === "Promise" ) {
throw new InterpreterRuntimeError (
"new Promise(...) is not supported in CodeMode; tool calls already return promises - call the tool and await the result." ,
node ,
"UnsupportedSyntax" ,
[ supportedSyntaxMessage ] ,
)
}
if ( errorConstructors . has ( name ) ) {
2026-07-03 05:20:28 +00:00
return Effect . gen ( function * ( ) {
const arg =
argNodes . length > 0 ? yield * self . evaluateExpression ( asNode ( argNodes [ 0 ] , "arguments[0]" ) ) : undefined
2026-07-03 05:19:11 +00:00
return createErrorValue ( name , arg === undefined ? "" : coerceToString ( arg ) )
} )
}
if ( valueConstructors . has ( name ) ) {
2026-07-03 05:20:28 +00:00
return Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
const args = yield * self . evaluateCallArguments ( argNodes )
switch ( name ) {
2026-07-03 05:20:28 +00:00
case "Date" :
return self . constructDate ( args )
case "RegExp" :
return self . constructRegExp ( args , node )
case "Map" :
return self . constructMap ( args [ 0 ] , node )
default :
return self . constructSet ( args [ 0 ] , node )
2026-07-03 05:19:11 +00:00
}
} )
}
throw unsupportedSyntax ( "NewExpression" , node )
}
private constructDate ( args : Array < unknown > ) : SandboxDate {
if ( args . length === 0 ) return new SandboxDate ( Date . now ( ) )
if ( args . length === 1 ) {
const arg = args [ 0 ]
if ( arg instanceof SandboxDate ) return new SandboxDate ( arg . time )
if ( typeof arg === "number" ) return new SandboxDate ( new Date ( arg ) . getTime ( ) )
if ( typeof arg === "string" ) return new SandboxDate ( Date . parse ( arg ) )
return new SandboxDate ( Number . NaN )
}
// new Date(year, month, day?, hours?, ...) - local-time component form.
const parts = args . map ( ( arg ) = > coerceToNumber ( arg ) )
return new SandboxDate ( new Date ( . . . ( parts as [ number , number ] ) ) . getTime ( ) )
}
private constructRegExp ( args : Array < unknown > , node : AstNode ) : SandboxRegExp {
const first = args [ 0 ]
2026-07-03 05:20:28 +00:00
const pattern =
first instanceof SandboxRegExp ? first.regex.source : first === undefined ? "" : coerceToString ( first )
2026-07-03 05:19:11 +00:00
const flagsArg = args [ 1 ]
if ( flagsArg !== undefined && typeof flagsArg !== "string" ) {
throw new InterpreterRuntimeError (
` RegExp flags must be a string of flag characters (e.g. "g", "gi"), not ${ flagsArg === null ? "null" : typeof flagsArg } . ` ,
node ,
)
}
const flags = flagsArg ? ? ( first instanceof SandboxRegExp ? first . regex . flags : "" )
try {
return new SandboxRegExp ( pattern , flags )
} catch ( error ) {
// Say which part was rejected and how to fix it, instead of passing the engine
// message through bare. A flags failure names the flags; a pattern failure gets the
// escaping hint (the usual cause is an unescaped metacharacter in a built-up string).
const reason = regexFailureReason ( error )
throw new InterpreterRuntimeError (
/flag/i . test ( reason )
? ` new RegExp(...) received invalid flags ${ JSON . stringify ( flags ) } ( ${ reason } ). Valid flags are d, g, i, m, s, u, v, and y. `
: ` new RegExp(...) received ${ JSON . stringify ( pattern ) } , which is not a valid regular expression pattern ( ${ reason } ). ${ escapeRegexHint } ` ,
node ,
) . as ( "SyntaxError" )
}
}
private constructMap ( init : unknown , node : AstNode ) : SandboxMap {
const target = new SandboxMap ( )
if ( init === undefined || init === null ) return target
const entries = Array . isArray ( init )
? init
: init instanceof SandboxMap
? Array . from ( init . map . entries ( ) , ( [ key , item ] ) : Array < unknown > = > [ key , item ] )
: undefined
if ( entries === undefined ) {
2026-07-03 05:20:28 +00:00
throw new InterpreterRuntimeError (
"new Map(...) expects an array of [key, value] pairs, a Map, or no argument." ,
node ,
)
2026-07-03 05:19:11 +00:00
}
for ( const pair of entries ) {
if ( ! Array . isArray ( pair ) ) {
throw new InterpreterRuntimeError ( "new Map(...) expects [key, value] pairs." , node )
}
target . map . set ( pair [ 0 ] , pair [ 1 ] )
}
return target
}
private constructSet ( init : unknown , node : AstNode ) : SandboxSet {
const target = new SandboxSet ( )
if ( init === undefined || init === null ) return target
const items = Array . isArray ( init )
? init
: init instanceof SandboxSet
? Array . from ( init . set . values ( ) )
: typeof init === "string"
? Array . from ( init )
: undefined
if ( items === undefined ) {
throw new InterpreterRuntimeError ( "new Set(...) expects an array, Set, string, or no argument." , node )
}
for ( const item of items ) target . set . add ( item )
return target
}
private evaluateBinaryExpression ( node : AstNode ) : Effect . Effect < unknown , unknown , R > {
const operator = getString ( node , "operator" )
const self = this
2026-07-03 05:20:28 +00:00
return Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
const lhs = yield * self . evaluateExpression ( getNode ( node , "left" ) )
const rhs = yield * self . evaluateExpression ( getNode ( node , "right" ) )
// Like `typeof`, `instanceof` observes any value without coercing it (a promise or
// function operand is a legitimate question, not an error), so it is handled before
// the data-only operand check.
if ( operator === "instanceof" ) return instanceofValue ( lhs , rhs , node )
return boundedData ( self . applyBinaryOperator ( operator , lhs , rhs , node ) , "Binary expression result" )
} )
}
/ * *
* Applies a binary operator to two already - evaluated operands with CodeMode ' s coercion
* semantics . Shared by binary expressions and compound assignment ( ` x op= y ` must behave
* exactly like ` x = x op y ` , coercion included ) .
* /
private applyBinaryOperator ( operator : string , lhs : unknown , rhs : unknown , node : AstNode ) : unknown {
if ( containsOpaqueReference ( lhs ) || containsOpaqueReference ( rhs ) ) {
throw new InterpreterRuntimeError ( "Binary operators require data values in CodeMode." , node , "InvalidDataValue" )
}
// Data objects/arrays are null-prototype, so JS's ToPrimitive throws an opaque host
// "No default value" TypeError when an operator coerces them. Coerce to their JS string
// form first (as String(x) / template literals do) so operators behave like JavaScript.
// A Date follows its ToPrimitive hints: string for `+` (concatenation), its time value
// for arithmetic and ordering - so `end - start` and `a < b` work as in JS.
// Identity (=== / !==) and the right operand of `in` keep their raw object value.
const coerceOperand = ( operand : unknown ) : unknown = > {
if ( operand instanceof SandboxDate ) return operator === "+" ? coerceToString ( operand ) : operand . time
return operand !== null && typeof operand === "object" ? coerceToString ( operand ) : operand
}
const bothObjects = lhs !== null && typeof lhs === "object" && rhs !== null && typeof rhs === "object"
const l = coerceOperand ( lhs )
const r = coerceOperand ( rhs )
switch ( operator ) {
2026-07-03 05:20:28 +00:00
case "+" :
return ( l as string ) + ( r as string )
case "-" :
return ( l as number ) - ( r as number )
case "*" :
return ( l as number ) * ( r as number )
case "/" :
return ( l as number ) / ( r as number )
case "%" :
return ( l as number ) % ( r as number )
case "**" :
return ( l as number ) * * ( r as number )
2026-07-03 05:19:11 +00:00
// Two objects compare by identity in JS (no ToPrimitive); only object-vs-primitive coerces.
2026-07-03 05:20:28 +00:00
case "==" :
return bothObjects ? lhs === rhs : l == r
case "===" :
return lhs === rhs
case "!=" :
return bothObjects ? lhs !== rhs : l != r
case "!==" :
return lhs !== rhs
case "<" :
return ( l as string ) < ( r as string )
case "<=" :
return ( l as string ) <= ( r as string )
case ">" :
return ( l as string ) > ( r as string )
case ">=" :
return ( l as string ) >= ( r as string )
case "&" :
return ( l as number ) & ( r as number )
case "|" :
return ( l as number ) | ( r as number )
case "^" :
return ( l as number ) ^ ( r as number )
case "<<" :
return ( l as number ) << ( r as number )
case ">>" :
return ( l as number ) >> ( r as number )
case ">>>" :
return ( l as number ) >>> ( r as number )
2026-07-03 05:19:11 +00:00
case "in" :
if ( rhs === null || typeof rhs !== "object" ) {
throw new InterpreterRuntimeError ( "The 'in' operator requires a data object on the right-hand side." , node )
}
// Own properties only, so arrays don't leak the host Array.prototype (map/constructor/...).
return Object . hasOwn ( rhs as object , coerceOperand ( lhs ) as PropertyKey )
2026-07-03 05:20:28 +00:00
default :
throw new InterpreterRuntimeError ( ` Unsupported binary operator ' ${ operator } '. ` , node )
2026-07-03 05:19:11 +00:00
}
}
private evaluateLogicalExpression ( node : AstNode ) : Effect . Effect < unknown , unknown , R > {
const operator = getString ( node , "operator" )
return Effect . flatMap ( this . evaluateExpression ( getNode ( node , "left" ) ) , ( left ) = > {
if ( operator === "&&" ) return left ? this . evaluateExpression ( getNode ( node , "right" ) ) : Effect . succeed ( left )
if ( operator === "||" ) return left ? Effect . succeed ( left ) : this . evaluateExpression ( getNode ( node , "right" ) )
2026-07-03 05:20:28 +00:00
if ( operator === "??" )
return left !== null && left !== undefined
? Effect . succeed ( left )
: this . evaluateExpression ( getNode ( node , "right" ) )
2026-07-03 05:19:11 +00:00
throw new InterpreterRuntimeError ( ` Unsupported logical operator ' ${ operator } '. ` , node )
} )
}
private evaluateUnaryExpression ( node : AstNode ) : Effect . Effect < unknown , unknown , R > {
const operator = getString ( node , "operator" )
const argument = getNode ( node , "argument" )
// `typeof undeclaredIdentifier` is `"undefined"` in JS (never a ReferenceError), so
// feature-detection guards like `typeof x !== "undefined"` don't crash. Short-circuit before
// evaluating the argument; a declared-but-TDZ binding still falls through to the normal throw.
if ( operator === "typeof" && argument . type === "Identifier" && ! this . resolveBinding ( getString ( argument , "name" ) ) ) {
return Effect . succeed ( "undefined" )
}
return Effect . map ( this . evaluateExpression ( argument ) , ( value ) = > {
// `typeof` and `!` never throw in JS - they observe any value (functions and runtime
// references included) without coercing it, so feature detection and negation work.
if ( operator === "typeof" ) return typeofValue ( value )
if ( operator === "!" ) return ! value
if ( containsOpaqueReference ( value ) ) {
throw new InterpreterRuntimeError ( "Unary operators require data values in CodeMode." , node , "InvalidDataValue" )
}
// Numeric/bitwise unary operators ToPrimitive their operand; a Date yields its time value
// (`+date` is the epoch-ms idiom), other null-prototype data objects/arrays coerce to
// their JS string form first (see evaluateBinaryExpression).
2026-07-03 05:20:28 +00:00
const operand =
value instanceof SandboxDate
? value . time
: value !== null && typeof value === "object"
? coerceToString ( value )
: value
2026-07-03 05:19:11 +00:00
let result : unknown
switch ( operator ) {
2026-07-03 05:20:28 +00:00
case "+" :
result = + ( operand as number )
break
case "-" :
result = - ( operand as number )
break
case "~" :
result = ~ ( operand as number )
break
default :
throw new InterpreterRuntimeError ( ` Unsupported unary operator ' ${ operator } '. ` , node )
2026-07-03 05:19:11 +00:00
}
return boundedData ( result , "Unary expression result" )
} )
}
private evaluateAssignmentExpression ( node : AstNode ) : Effect . Effect < unknown , unknown , R > {
const left = getNode ( node , "left" )
const operator = getString ( node , "operator" )
const self = this
2026-07-03 05:20:28 +00:00
return Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
if ( operator === "??=" || operator === "||=" || operator === "&&=" ) {
return yield * self . evaluateLogicalAssignment ( node , left , operator )
}
const rightValue = yield * self . evaluateExpression ( getNode ( node , "right" ) )
if ( left . type === "Identifier" ) {
const name = getString ( left , "name" )
if ( operator === "=" ) return self . setIdentifierValue ( name , rightValue , left )
2026-07-03 05:20:28 +00:00
const next = boundedData (
self . applyCompoundAssignment ( operator , self . getIdentifierValue ( name , left ) , rightValue , node ) ,
"Assignment result" ,
)
2026-07-03 05:19:11 +00:00
return self . setIdentifierValue ( name , next , left )
}
if ( left . type === "MemberExpression" ) {
if ( operator === "=" ) return yield * self . writeMember ( left , rightValue )
return yield * self . modifyMember ( left , ( current ) = > {
2026-07-03 05:20:28 +00:00
const next = boundedData (
self . applyCompoundAssignment ( operator , current , rightValue , node ) ,
"Assignment result" ,
)
2026-07-03 05:19:11 +00:00
return Effect . succeed ( { write : true , next , result : next } )
} )
}
throw new InterpreterRuntimeError ( "Assignment target must be an Identifier or MemberExpression." , left )
} )
}
2026-07-03 05:20:28 +00:00
private evaluateLogicalAssignment (
node : AstNode ,
left : AstNode ,
operator : string ,
) : Effect . Effect < unknown , unknown , R > {
2026-07-03 05:19:11 +00:00
const self = this
const shouldAssign = ( current : unknown ) : boolean = >
operator === "??=" ? current === null || current === undefined : operator === "||=" ? ! current : Boolean ( current )
if ( left . type === "Identifier" ) {
const name = getString ( left , "name" )
2026-07-03 05:20:28 +00:00
return Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
const current = self . getIdentifierValue ( name , left )
if ( ! shouldAssign ( current ) ) return current
const rightValue = yield * self . evaluateExpression ( getNode ( node , "right" ) )
return self . setIdentifierValue ( name , rightValue , left )
} )
}
if ( left . type === "MemberExpression" ) {
// Resolve the member exactly once; evaluate the RHS only if we actually assign.
return self . modifyMember ( left , ( current ) = >
shouldAssign ( current )
2026-07-03 05:20:28 +00:00
? Effect . map ( self . evaluateExpression ( getNode ( node , "right" ) ) , ( rightValue ) = > ( {
write : true ,
next : rightValue ,
result : rightValue ,
} ) )
: Effect . succeed ( { write : false , next : current , result : current } ) ,
)
2026-07-03 05:19:11 +00:00
}
throw new InterpreterRuntimeError ( "Assignment target must be an Identifier or MemberExpression." , left )
}
private evaluateUpdateExpression ( node : AstNode ) : Effect . Effect < unknown , unknown , R > {
const operator = getString ( node , "operator" )
const argument = getNode ( node , "argument" )
const prefix = getBoolean ( node , "prefix" )
const increment = operator === "++" ? 1 : operator === "--" ? - 1 : undefined
if ( increment === undefined ) {
throw new InterpreterRuntimeError ( ` Unsupported update operator ' ${ operator } '. ` , node )
}
if ( argument . type === "Identifier" ) {
return Effect . sync ( ( ) = > {
const name = getString ( argument , "name" )
const current = Number ( this . getIdentifierValue ( name , argument ) )
const next = current + increment
this . setIdentifierValue ( name , next , argument )
return prefix ? next : current
} )
}
if ( argument . type === "MemberExpression" ) {
return this . modifyMember ( argument , ( current ) = > {
const value = Number ( current )
const next = value + increment
return Effect . succeed ( { write : true , next , result : prefix ? next : value } )
} )
}
throw new InterpreterRuntimeError ( "Update target must be an Identifier or MemberExpression." , argument )
}
private evaluateCallExpression ( node : AstNode ) : Effect . Effect < unknown , unknown , R > {
const callee = getNode ( node , "callee" )
const argNodes = getArray ( node , "arguments" )
const self = this
2026-07-03 05:20:28 +00:00
return Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
const callable = yield * self . evaluateExpression ( callee )
if ( callable === OptionalShortCircuit ) return OptionalShortCircuit
if ( ( callable === null || callable === undefined ) && node . optional === true ) return OptionalShortCircuit
const args = yield * self . evaluateCallArguments ( argNodes )
if ( callable instanceof ToolReference ) {
if ( callable . path . length === 0 ) throw new InterpreterRuntimeError ( "The tools root is not callable." , callee )
// An un-awaited tool call is a first-class promise value; the call itself starts now.
return yield * self . createToolCallPromise ( callable . path , args )
}
if ( callable instanceof PromiseMethodReference ) {
return yield * self . invokePromiseMethod ( callable , args , node )
}
if ( callable instanceof CodeModeFunction ) {
return yield * self . invokeFunction ( callable , args )
}
if ( callable instanceof IntrinsicReference ) {
return yield * self . invokeIntrinsic ( callable , args , node )
}
if ( callable instanceof GlobalMethodReference ) {
if ( callable . namespace === "console" ) return self . invokeConsole ( callable . name , args , node )
if ( callable . namespace === "Object" && args [ 0 ] instanceof ToolReference ) {
return self . invokeObjectMethodOnTools ( callable . name , args [ 0 ] as ToolReference , node )
}
return boundedData ( invokeGlobalMethod ( callable , args , node ) , ` ${ callable . namespace } . ${ callable . name } result ` )
}
if ( callable instanceof CoercionFunction ) {
return boundedData ( invokeCoercion ( callable , args , node ) , ` ${ callable . name } result ` )
}
// `Error("msg")` without `new` constructs an error exactly like `new Error("msg")`, as in JS.
if ( callable instanceof ErrorConstructorReference ) {
return createErrorValue ( callable . name , args [ 0 ] === undefined ? "" : coerceToString ( args [ 0 ] ) )
}
2026-07-03 05:20:28 +00:00
throw new InterpreterRuntimeError ( "Only tools are callable in CodeMode." , callee )
2026-07-03 05:19:11 +00:00
} )
}
// Object.* over a tool reference: `Object.keys(tools)` / `Object.keys(tools.ns)` enumerate
// namespace/tool names from the host tool tree - the discovery idiom a model reaches for
// first. Every other Object helper cannot produce data from a tool reference, so it fails
// with a pointer at the working idioms instead of the generic plain-objects-only message.
private invokeObjectMethodOnTools ( name : string , ref : ToolReference , node : AstNode ) : unknown {
if ( name === "keys" ) {
return boundedData ( this . enumerableKeys ( ref ) ! , "Object.keys result" )
}
throw new InterpreterRuntimeError (
` Object. ${ name } (...) cannot read tool references: they are not plain data. Use Object.keys(tools) for names, or tools. $ codemode.search({ query }) for signatures. ` ,
node ,
"InvalidDataValue" ,
)
}
private invokeConsole ( name : string , args : Array < unknown > , node : AstNode ) : undefined {
2026-07-03 05:20:28 +00:00
if ( ! consoleMethods . has ( name ) )
throw new InterpreterRuntimeError ( ` console. ${ name } is not available in CodeMode. ` , node )
2026-07-03 05:19:11 +00:00
this . logs . push ( publicErrorMessage ( this . formatConsoleMessage ( name , args , node ) ) )
return undefined
}
private formatConsoleMessage ( name : string , args : Array < unknown > , node : AstNode ) : string {
if ( name === "dir" ) return args . length === 0 ? "undefined" : this . formatConsoleArgument ( args [ 0 ] )
if ( name === "table" ) return this . formatConsoleTable ( args [ 0 ] , args [ 1 ] , node )
const prefix = name === "warn" ? "[warn] " : name === "error" ? "[error] " : name === "debug" ? "[debug] " : ""
return ` ${ prefix } ${ args . map ( ( arg ) = > this . formatConsoleArgument ( arg ) ) . join ( " " ) } `
}
// Console arguments format deeply and totally: values render as a debugger would show them
// rather than as boundary JSON - numbers keep NaN/Infinity (JSON would say null), sandbox
// values keep their friendly forms at ANY depth (ISO date, /regex/flags, Map(n) [...],
// Set(n) [...]), opaque runtime references become "[CodeMode reference]" markers in place,
// and plain objects/arrays render JSON-style. Formatting never fails the program: cycles
// render "[Circular]" and extreme depth degrades to "...".
private formatConsoleArgument ( value : unknown ) : string {
if ( value === undefined ) return "undefined"
// A top-level string prints bare; nested strings are JSON-quoted (see formatConsoleValue).
if ( typeof value === "string" ) return value
return this . formatConsoleValue ( value , new Set ( ) , 0 )
}
private formatConsoleValue ( value : unknown , seen : Set < object > , depth : number ) : string {
// Nested undefined renders as null, matching what JSON boundary output would show.
if ( value === null || value === undefined ) return "null"
if ( typeof value === "string" ) return JSON . stringify ( value )
// String(value) keeps NaN/Infinity/-Infinity readable; finite numbers match their JSON form.
if ( typeof value === "number" || typeof value === "boolean" ) return String ( value )
if ( typeof value !== "object" ) return String ( value )
if ( value instanceof SandboxPromise ) return "[Promise (await it to get its value)]"
if ( value instanceof SandboxDate ) return coerceToString ( value )
if ( value instanceof SandboxRegExp ) return coerceToString ( value )
if ( depth > MAX_CONSOLE_DEPTH ) return "..."
if ( seen . has ( value ) ) return "[Circular]"
if ( value instanceof SandboxMap ) {
seen . add ( value )
try {
const entries = Array . from ( value . map . entries ( ) , ( [ key , item ] ) : Array < unknown > = > [ key , item ] )
return ` Map( ${ value . map . size } ) ${ this . formatConsoleValue ( entries , seen , depth + 1 ) } `
} finally {
seen . delete ( value )
}
}
if ( value instanceof SandboxSet ) {
seen . add ( value )
try {
return ` Set( ${ value . set . size } ) ${ this . formatConsoleValue ( Array . from ( value . set . values ( ) ) , seen , depth + 1 ) } `
} finally {
seen . delete ( value )
}
}
if ( isRuntimeReference ( value ) ) return "[CodeMode reference]"
seen . add ( value )
try {
if ( Array . isArray ( value ) ) {
return ` [ ${ value . map ( ( item ) = > this . formatConsoleValue ( item , seen , depth + 1 ) ) . join ( "," ) } ] `
}
2026-07-03 05:20:28 +00:00
return ` { ${ Object . entries ( value )
. map ( ( [ key , item ] ) = > ` ${ JSON . stringify ( key ) } : ${ this . formatConsoleValue ( item , seen , depth + 1 ) } ` )
. join ( "," ) } } `
2026-07-03 05:19:11 +00:00
} finally {
seen . delete ( value )
}
}
private formatConsoleTable ( value : unknown , columnsArgument : unknown , node : AstNode ) : string {
if ( value === undefined ) return "undefined"
// Sandbox values are legitimate table data (cells render their friendly forms); only
// truly opaque references (functions, tools, promises) collapse to the marker.
if ( containsOpaqueReference ( value ) ) return "[CodeMode reference]"
const data = boundedData ( value , "console.table argument" )
const columns = this . consoleTableColumns ( columnsArgument , node )
const rows = this . consoleTableRows ( data , columns )
const keys = columns ? ? Array . from ( new Set ( rows . flatMap ( ( row ) = > Object . keys ( row . values ) ) ) )
const header = [ "(index)" , . . . keys ] . join ( "\t" )
2026-07-03 05:20:28 +00:00
return [
header ,
. . . rows . map ( ( row ) = > [ row . index , . . . keys . map ( ( key ) = > this . formatConsoleTableCell ( row . values [ key ] ) ) ] . join ( "\t" ) ) ,
] . join ( "\n" )
2026-07-03 05:19:11 +00:00
}
private consoleTableColumns ( value : unknown , node : AstNode ) : ReadonlyArray < string > | undefined {
if ( value === undefined ) return undefined
if ( containsRuntimeReference ( value ) ) return undefined
const columns = copyOut ( copyIn ( value , "console.table columns" ) , true )
return Array . isArray ( columns ) ? columns . map ( ( column ) = > String ( column ) ) : undefined
}
2026-07-03 05:20:28 +00:00
private consoleTableRows (
data : unknown ,
columns : ReadonlyArray < string > | undefined ,
) : Array < { readonly index : string ; readonly values : Record < string , unknown > } > {
2026-07-03 05:19:11 +00:00
if ( Array . isArray ( data ) ) {
return data . map ( ( item , index ) = > ( { index : String ( index ) , values : this.consoleTableValues ( item , columns ) } ) )
}
if ( data !== null && typeof data === "object" && ! isSandboxValue ( data ) ) {
return Object . entries ( data ) . map ( ( [ index , item ] ) = > ( { index , values : this.consoleTableValues ( item , columns ) } ) )
}
return [ { index : "0" , values : { Value : data } } ]
}
private consoleTableValues ( value : unknown , columns : ReadonlyArray < string > | undefined ) : Record < string , unknown > {
if ( value !== null && typeof value === "object" && ! Array . isArray ( value ) && ! isSandboxValue ( value ) ) {
const source = value as Record < string , unknown >
if ( columns !== undefined ) return Object . fromEntries ( columns . map ( ( column ) = > [ column , source [ column ] ] ) )
return Object . fromEntries ( Object . entries ( source ) )
}
return { Value : value }
}
private formatConsoleTableCell ( value : unknown ) : string {
if ( value === undefined ) return ""
if ( typeof value === "string" ) return value
return this . formatConsoleValue ( value , new Set ( ) , 0 )
}
private evaluateCallArguments ( argNodes : Array < unknown > ) : Effect . Effect < Array < unknown > , unknown , R > {
const self = this
2026-07-03 05:20:28 +00:00
return Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
const args : Array < unknown > = [ ]
for ( const [ index , arg ] of argNodes . entries ( ) ) {
const argNode = asNode ( arg , ` arguments[ ${ index } ] ` )
if ( argNode . type === "SpreadElement" ) {
const spread = yield * self . evaluateExpression ( getNode ( argNode , "argument" ) )
const items = spreadItems ( spread )
2026-07-03 05:20:28 +00:00
if ( items === undefined )
throw new InterpreterRuntimeError (
"Spread arguments require an array, string, Map, or Set in CodeMode." ,
argNode ,
)
2026-07-03 05:19:11 +00:00
args . push ( . . . items )
} else {
args . push ( yield * self . evaluateExpression ( argNode ) )
}
}
return args
} )
}
// Promise.* over ordinary runtime values. Combinators accept ANY array (or spreadable
// collection) mixing promise values and plain data - built inline, beforehand, via spread,
// whatever - because tool calls already run eagerly on their own fibers; the combinators
// only observe settlements. Joining is therefore sequential (no extra fibers) without
// costing parallelism, and the concurrency cap stays where the work is: the fork semaphore.
2026-07-03 05:20:28 +00:00
private invokePromiseMethod (
ref : PromiseMethodReference ,
args : Array < unknown > ,
node : AstNode ,
) : Effect . Effect < unknown , unknown , R > {
2026-07-03 05:19:11 +00:00
const self = this
if ( ref . name === "resolve" ) {
// Promise.resolve of a promise is that promise (JS flattens); anything else is a
// promise already fulfilled with the value.
const value = args [ 0 ]
2026-07-03 05:20:28 +00:00
return Effect . succeed (
value instanceof SandboxPromise ? value : new SandboxPromise ( undefined , Effect . succeed ( value ) ) ,
)
2026-07-03 05:19:11 +00:00
}
if ( ref . name === "reject" ) {
return Effect . sync ( ( ) = > new SandboxPromise ( undefined , Effect . fail ( new ProgramThrow ( args [ 0 ] ) ) ) )
}
const items = Array . isArray ( args [ 0 ] ) ? args [ 0 ] : spreadItems ( args [ 0 ] )
if ( items === undefined ) {
throw new InterpreterRuntimeError (
` Promise. ${ ref . name } expects an array of promises or plain values (e.g. Promise. ${ ref . name } (items.map((item) => tools.ns.tool(item)))). ` ,
node ,
)
}
switch ( ref . name ) {
case "all" : {
// Mark every promise element observed up-front (Promise.all handles all of its
// members' failures, as in JS), then join in index order; the first failure rejects
// the whole call while unrelated in-flight members keep running.
2026-07-03 05:20:28 +00:00
const settles = items . map ( ( item ) = >
item instanceof SandboxPromise ? this . settlePromise ( item , node ) : Effect . succeed ( item ) ,
)
return Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
const values : Array < unknown > = [ ]
for ( const settle of settles ) values . push ( yield * settle )
return values
} )
}
case "allSettled" : {
const observations = items . map ( ( item ) = >
item instanceof SandboxPromise
? Effect . map ( this . observePromise ( item ) , ( exit ) = > ( { promise : item as SandboxPromise | undefined , exit } ) )
2026-07-03 05:20:28 +00:00
: Effect . succeed ( { promise : undefined as SandboxPromise | undefined , exit : Exit.succeed ( item as unknown ) } ) ,
)
return Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
const outcomes : Array < unknown > = [ ]
for ( const observation of observations ) {
const { exit , promise } = yield * observation
if ( Exit . isSuccess ( exit ) ) {
2026-07-03 05:20:28 +00:00
outcomes . push (
Object . assign ( Object . create ( null ) as SafeObject , { status : "fulfilled" , value : exit.value } ) ,
)
2026-07-03 05:19:11 +00:00
continue
}
const raceInterrupted = promise ? . interrupted === true && Cause . hasInterruptsOnly ( exit . cause )
if ( Cause . hasInterruptsOnly ( exit . cause ) && ! raceInterrupted ) {
// Execution teardown (timeout/host interruption), not a program-level rejection.
return yield * Effect . failCause ( exit . cause )
}
const thrown = raceInterrupted
2026-07-03 05:20:28 +00:00
? new InterpreterRuntimeError (
"This tool call was interrupted because another value settled a Promise.race first." ,
node ,
)
2026-07-03 05:19:11 +00:00
: Cause . squash ( exit . cause )
2026-07-03 05:20:28 +00:00
outcomes . push (
Object . assign ( Object . create ( null ) as SafeObject , {
status : "rejected" ,
reason : caughtErrorValue ( thrown ) ,
} ) ,
)
2026-07-03 05:19:11 +00:00
}
return outcomes
} )
}
case "race" : {
if ( items . length === 0 ) {
2026-07-03 05:20:28 +00:00
throw new InterpreterRuntimeError (
"Promise.race([]) would never settle; provide at least one promise or value." ,
node ,
)
2026-07-03 05:19:11 +00:00
}
const observations = items . map ( ( item , index ) = >
item instanceof SandboxPromise
? Effect . map ( this . observePromise ( item ) , ( exit ) = > ( { index , exit } ) )
2026-07-03 05:20:28 +00:00
: Effect . succeed ( { index , exit : Exit.succeed ( item as unknown ) } ) ,
)
return Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
// First settlement (fulfilled OR rejected) wins; the observations never fail, so
// racing them yields exactly that. Losing in-flight calls are then interrupted.
const winner = yield * Effect . raceAll ( observations )
for ( const [ index , item ] of items . entries ( ) ) {
if ( index === winner . index || ! ( item instanceof SandboxPromise ) || item . fiber === undefined ) continue
item . interrupted = true
yield * Fiber . interrupt ( item . fiber )
}
const winningItem = items [ winner . index ]
2026-07-03 05:20:28 +00:00
return yield * self . unwrapPromiseExit (
winningItem instanceof SandboxPromise ? winningItem : undefined ,
winner . exit ,
node ,
)
2026-07-03 05:19:11 +00:00
} )
}
}
}
private invokeFunction ( fn : CodeModeFunction , args : Array < unknown > ) : Effect . Effect < unknown , unknown , R > {
const self = this
return Effect . suspend ( ( ) = > {
const savedScopes = self . scopes
self . scopes = [ . . . fn . capturedScopes , new Map < string , Binding > ( ) ]
2026-07-03 05:20:28 +00:00
const run = Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
// Seed every parameter name into the scope as a TDZ slot first, so a default that
// references another parameter resolves to that (uninitialized) param rather than
// silently falling through to an outer binding of the same name - matching JS.
const paramScope = self . currentScope ( )
for ( const parameter of fn . parameters ) {
for ( const name of collectPatternNames ( parameter ) ) {
paramScope . set ( name , { mutable : true , value : undefined , initialized : false } )
}
}
for ( const [ index , parameter ] of fn . parameters . entries ( ) ) {
if ( parameter . type === "RestElement" ) {
yield * self . declarePattern ( getNode ( parameter , "argument" ) , args . slice ( index ) , true , parameter )
break
}
yield * self . declarePattern ( parameter , args [ index ] , true , parameter )
}
if ( fn . body . type === "BlockStatement" ) {
const result = yield * self . evaluateStatement ( fn . body )
return result . kind === "return" || result . kind === "value" ? result.value : undefined
}
return yield * self . evaluateExpression ( fn . body )
} )
2026-07-03 05:20:28 +00:00
return run . pipe (
Effect . ensuring (
Effect . sync ( ( ) = > {
self . scopes = savedScopes
} ) ,
) ,
)
2026-07-03 05:19:11 +00:00
} )
}
2026-07-03 05:20:28 +00:00
private invokeIntrinsic (
ref : IntrinsicReference ,
args : Array < unknown > ,
node : AstNode ,
) : Effect . Effect < unknown , unknown , R > {
2026-07-03 05:19:11 +00:00
if ( typeof ref . receiver === "string" ) {
return Effect . succeed ( invokeStringMethod ( ref . receiver , ref . name , args , node ) )
}
if ( typeof ref . receiver === "number" ) {
return Effect . succeed ( invokeNumberMethod ( ref . receiver , ref . name , args , node ) )
}
if ( Array . isArray ( ref . receiver ) ) {
return this . invokeArrayMethod ( ref . receiver , ref . name , args , node )
}
if ( ref . receiver instanceof SandboxDate ) {
return Effect . succeed ( invokeDateMethod ( ref . receiver , ref . name , node ) )
}
if ( ref . receiver instanceof SandboxRegExp ) {
return Effect . succeed ( invokeRegExpMethod ( ref . receiver , ref . name , args , node ) )
}
if ( ref . receiver instanceof SandboxMap ) {
return this . invokeMapMethod ( ref . receiver , ref . name , args , node )
}
if ( ref . receiver instanceof SandboxSet ) {
return this . invokeSetMethod ( ref . receiver , ref . name , args , node )
}
throw new InterpreterRuntimeError ( ` Method ' ${ ref . name } ' is not available in CodeMode. ` , node )
}
// Runs a Map/Set callback (forEach) accepting a user function or a builtin coercion,
// mirroring the array-method callback contract.
2026-07-03 05:20:28 +00:00
private applyCollectionCallback (
callback : unknown ,
name : string ,
node : AstNode ,
) : ( args : Array < unknown > ) = > Effect . Effect < unknown , unknown , R > {
2026-07-03 05:19:11 +00:00
if ( ! ( callback instanceof CodeModeFunction ) && ! ( callback instanceof CoercionFunction ) ) {
throw new InterpreterRuntimeError ( ` ${ name } expects a function callback. ` , node )
}
return ( callbackArgs ) = >
callback instanceof CoercionFunction
? Effect . succeed ( invokeCoercion ( callback , callbackArgs , node ) )
: this . invokeFunction ( callback , callbackArgs )
}
2026-07-03 05:20:28 +00:00
private invokeMapMethod (
target : SandboxMap ,
name : string ,
args : Array < unknown > ,
node : AstNode ,
) : Effect . Effect < unknown , unknown , R > {
2026-07-03 05:19:11 +00:00
switch ( name ) {
2026-07-03 05:20:28 +00:00
case "get" :
return Effect . succeed ( target . map . get ( args [ 0 ] ) )
case "has" :
return Effect . succeed ( target . map . has ( args [ 0 ] ) )
case "set" :
return Effect . sync ( ( ) = > {
target . map . set ( args [ 0 ] , args [ 1 ] )
return target
} )
case "delete" :
return Effect . sync ( ( ) = > target . map . delete ( args [ 0 ] ) )
case "clear" :
return Effect . sync ( ( ) = > {
target . map . clear ( )
return undefined
} )
case "keys" :
return Effect . sync ( ( ) = > Array . from ( target . map . keys ( ) ) )
case "values" :
return Effect . sync ( ( ) = > Array . from ( target . map . values ( ) ) )
case "entries" :
return Effect . sync ( ( ) = > Array . from ( target . map . entries ( ) , ( [ key , item ] ) : Array < unknown > = > [ key , item ] ) )
2026-07-03 05:19:11 +00:00
case "forEach" : {
const apply = this . applyCollectionCallback ( args [ 0 ] , "Map.forEach" , node )
2026-07-03 05:20:28 +00:00
return Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
// Snapshot iteration, matching the array-method callback contract.
for ( const [ key , item ] of Array . from ( target . map . entries ( ) ) ) yield * apply ( [ item , key , target ] )
return undefined
} )
}
2026-07-03 05:20:28 +00:00
default :
throw new InterpreterRuntimeError ( ` Map method ' ${ name } ' is not available in CodeMode. ` , node )
2026-07-03 05:19:11 +00:00
}
}
2026-07-03 05:20:28 +00:00
private invokeSetMethod (
target : SandboxSet ,
name : string ,
args : Array < unknown > ,
node : AstNode ,
) : Effect . Effect < unknown , unknown , R > {
2026-07-03 05:19:11 +00:00
switch ( name ) {
2026-07-03 05:20:28 +00:00
case "has" :
return Effect . succeed ( target . set . has ( args [ 0 ] ) )
case "add" :
return Effect . sync ( ( ) = > {
target . set . add ( args [ 0 ] )
return target
} )
case "delete" :
return Effect . sync ( ( ) = > target . set . delete ( args [ 0 ] ) )
case "clear" :
return Effect . sync ( ( ) = > {
target . set . clear ( )
return undefined
} )
2026-07-03 05:19:11 +00:00
case "keys" :
2026-07-03 05:20:28 +00:00
case "values" :
return Effect . sync ( ( ) = > Array . from ( target . set . values ( ) ) )
case "entries" :
return Effect . sync ( ( ) = > Array . from ( target . set . values ( ) , ( item ) : Array < unknown > = > [ item , item ] ) )
2026-07-03 05:19:11 +00:00
case "forEach" : {
const apply = this . applyCollectionCallback ( args [ 0 ] , "Set.forEach" , node )
2026-07-03 05:20:28 +00:00
return Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
for ( const item of Array . from ( target . set . values ( ) ) ) yield * apply ( [ item , item , target ] )
return undefined
} )
}
2026-07-03 05:20:28 +00:00
default :
throw new InterpreterRuntimeError ( ` Set method ' ${ name } ' is not available in CodeMode. ` , node )
2026-07-03 05:19:11 +00:00
}
}
2026-07-03 05:20:28 +00:00
private invokeArrayMethod (
target : Array < unknown > ,
name : string ,
args : Array < unknown > ,
node : AstNode ,
) : Effect . Effect < unknown , unknown , R > {
2026-07-03 05:19:11 +00:00
const optNumber = ( value : unknown , label : string ) : number | undefined = > {
if ( value === undefined ) return undefined
2026-07-03 05:20:28 +00:00
if ( typeof value !== "number" )
throw new InterpreterRuntimeError ( ` Array. ${ name } expects ${ label } to be a number. ` , node )
2026-07-03 05:19:11 +00:00
return value
}
switch ( name ) {
case "join" : {
if ( args . length > 1 || ( args . length === 1 && typeof args [ 0 ] !== "string" ) ) {
throw new InterpreterRuntimeError ( "Array.join expects zero arguments or one string separator." , node )
}
const input = boundedData ( target , "Array.join input" ) as Array < unknown >
2026-07-03 05:20:28 +00:00
return Effect . succeed (
input . map ( ( item ) = > coerceToString ( item ? ? "" ) ) . join ( args . length === 0 ? "," : ( args [ 0 ] as string ) ) ,
)
2026-07-03 05:19:11 +00:00
}
case "includes" :
2026-07-03 05:20:28 +00:00
if ( args . length === 0 || args . length > 2 )
throw new InterpreterRuntimeError ( "Array.includes expects a value and optional start index." , node )
2026-07-03 05:19:11 +00:00
return Effect . succeed ( target . includes ( args [ 0 ] , optNumber ( args [ 1 ] , "start index" ) ) )
case "indexOf" :
return Effect . succeed ( target . indexOf ( args [ 0 ] , optNumber ( args [ 1 ] , "start index" ) ) )
case "lastIndexOf" :
2026-07-03 05:20:28 +00:00
return Effect . succeed (
args [ 1 ] === undefined
? target . lastIndexOf ( args [ 0 ] )
: target . lastIndexOf ( args [ 0 ] , optNumber ( args [ 1 ] , "start index" ) ) ,
)
2026-07-03 05:19:11 +00:00
case "at" :
return Effect . succeed ( target . at ( optNumber ( args [ 0 ] , "index" ) ? ? 0 ) )
case "slice" :
return Effect . succeed ( target . slice ( optNumber ( args [ 0 ] , "start" ) , optNumber ( args [ 1 ] , "end" ) ) )
case "concat" :
return Effect . succeed ( target . concat ( . . . args ) )
case "flat" :
return Effect . succeed ( target . flat ( optNumber ( args [ 0 ] , "depth" ) ? ? 1 ) )
case "reverse" :
return Effect . succeed ( [ . . . target ] . reverse ( ) )
case "sort" :
case "toSorted" :
return this . sortArray ( target , args [ 0 ] , node )
case "toReversed" :
return Effect . succeed ( [ . . . target ] . reverse ( ) )
case "with" : {
const index = optNumber ( args [ 0 ] , "index" ) ? ? 0
const resolved = index < 0 ? target . length + index : index
if ( resolved < 0 || resolved >= target . length ) {
throw new InterpreterRuntimeError ( "Array.with index is out of range." , node )
}
const copied = [ . . . target ]
copied [ resolved ] = args [ 1 ]
return Effect . succeed ( copied )
}
case "push" : {
// Validate before mutating (so no rollback is needed): inserting a container into
// itself would create a cycle no later walk could survive.
for ( const item of args ) this . rejectCircularInsertion ( target , item , "Array.push result" , node )
target . push ( . . . args )
return Effect . succeed ( target . length )
}
case "unshift" : {
for ( const item of args ) this . rejectCircularInsertion ( target , item , "Array.unshift result" , node )
target . unshift ( . . . args )
return Effect . succeed ( target . length )
}
case "pop" :
return Effect . succeed ( target . pop ( ) )
case "shift" :
return Effect . succeed ( target . shift ( ) )
case "splice" : {
// Mutates in place and returns the removed elements, exactly like JS: one argument
// removes to the end, an undefined delete count removes nothing.
if ( args . length === 0 ) return Effect . succeed ( target . splice ( 0 , 0 ) )
const start = optNumber ( args [ 0 ] , "start" ) ? ? 0
if ( args . length === 1 ) return Effect . succeed ( target . splice ( start ) )
const deleteCount = optNumber ( args [ 1 ] , "delete count" ) ? ? 0
const inserted = args . slice ( 2 )
for ( const item of inserted ) this . rejectCircularInsertion ( target , item , "Array.splice result" , node )
return Effect . succeed ( target . splice ( start , deleteCount , . . . inserted ) )
}
case "fill" : {
this . rejectCircularInsertion ( target , args [ 0 ] , "Array.fill result" , node )
return Effect . succeed ( target . fill ( args [ 0 ] , optNumber ( args [ 1 ] , "start" ) , optNumber ( args [ 2 ] , "end" ) ) )
}
case "copyWithin" :
2026-07-03 05:20:28 +00:00
return Effect . succeed (
target . copyWithin (
optNumber ( args [ 0 ] , "target index" ) ? ? 0 ,
optNumber ( args [ 1 ] , "start" ) ? ? 0 ,
optNumber ( args [ 2 ] , "end" ) ,
) ,
)
2026-07-03 05:19:11 +00:00
// keys/values/entries return arrays (not iterators), matching the Map/Set convention;
// they work with for...of and spread either way.
case "keys" :
return Effect . succeed ( Array . from ( target . keys ( ) ) )
case "values" :
return Effect . succeed ( [ . . . target ] )
case "entries" :
return Effect . succeed ( Array . from ( target . entries ( ) , ( [ index , item ] ) : Array < unknown > = > [ index , item ] ) )
}
const callback = args [ 0 ]
if ( ! ( callback instanceof CodeModeFunction ) && ! ( callback instanceof CoercionFunction ) ) {
throw new InterpreterRuntimeError ( ` Array. ${ name } expects a function callback. ` , node )
}
const self = this
// Accept a user arrow function or a builtin coercion callable (Boolean/String/Number), so the
// idioms `filter(Boolean)` / `map(String)` / `map(Number)` work as in JS. Coercions are
// synchronous; only CodeModeFunctions can await tool calls.
const apply = ( callbackArgs : Array < unknown > ) : Effect . Effect < unknown , unknown , R > = >
callback instanceof CoercionFunction
? Effect . succeed ( invokeCoercion ( callback , callbackArgs , node ) )
: self . invokeFunction ( callback , callbackArgs )
2026-07-03 05:20:28 +00:00
return Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
// Iterate a snapshot taken at call time so a callback that mutates the array can't
// self-extend the loop - matching JS, where elements appended during iteration are not visited.
const items = target . slice ( )
switch ( name ) {
case "map" : {
const values : Array < unknown > = [ ]
for ( const [ index , item ] of items . entries ( ) ) values . push ( yield * apply ( [ item , index , items ] ) )
return values
}
case "flatMap" : {
const values : Array < unknown > = [ ]
for ( const [ index , item ] of items . entries ( ) ) {
const mapped = yield * apply ( [ item , index , items ] )
if ( Array . isArray ( mapped ) ) values . push ( . . . mapped )
else values . push ( mapped )
}
return values
}
case "filter" : {
const values : Array < unknown > = [ ]
for ( const [ index , item ] of items . entries ( ) ) {
if ( yield * apply ( [ item , index , items ] ) ) values . push ( item )
}
return values
}
case "find" :
for ( const [ index , item ] of items . entries ( ) ) {
if ( yield * apply ( [ item , index , items ] ) ) return item
}
return undefined
case "findIndex" :
for ( const [ index , item ] of items . entries ( ) ) {
if ( yield * apply ( [ item , index , items ] ) ) return index
}
return - 1
case "some" :
for ( const [ index , item ] of items . entries ( ) ) {
if ( yield * apply ( [ item , index , items ] ) ) return true
}
return false
case "every" :
for ( const [ index , item ] of items . entries ( ) ) {
if ( ! ( yield * apply ( [ item , index , items ] ) ) ) return false
}
return true
case "forEach" :
for ( const [ index , item ] of items . entries ( ) ) yield * apply ( [ item , index , items ] )
return undefined
case "reduce" : {
let accumulator : unknown
let start : number
if ( args . length >= 2 ) {
accumulator = args [ 1 ]
start = 0
} else {
2026-07-03 05:20:28 +00:00
if ( items . length === 0 )
throw new InterpreterRuntimeError ( "Array.reduce of an empty array with no initial value." , node )
2026-07-03 05:19:11 +00:00
accumulator = items [ 0 ]
start = 1
}
for ( let index = start ; index < items . length ; index += 1 ) {
accumulator = yield * apply ( [ accumulator , items [ index ] , index , items ] )
}
return accumulator
}
case "reduceRight" : {
let accumulator : unknown
let start : number
if ( args . length >= 2 ) {
accumulator = args [ 1 ]
start = items . length - 1
} else {
2026-07-03 05:20:28 +00:00
if ( items . length === 0 )
throw new InterpreterRuntimeError ( "Array.reduceRight of an empty array with no initial value." , node )
2026-07-03 05:19:11 +00:00
accumulator = items [ items . length - 1 ]
start = items . length - 2
}
for ( let index = start ; index >= 0 ; index -= 1 ) {
accumulator = yield * apply ( [ accumulator , items [ index ] , index , items ] )
}
return accumulator
}
case "findLast" :
for ( let index = items . length - 1 ; index >= 0 ; index -= 1 ) {
if ( yield * apply ( [ items [ index ] , index , items ] ) ) return items [ index ]
}
return undefined
case "findLastIndex" :
for ( let index = items . length - 1 ; index >= 0 ; index -= 1 ) {
if ( yield * apply ( [ items [ index ] , index , items ] ) ) return index
}
return - 1
}
throw new InterpreterRuntimeError ( ` Array method ' ${ name } ' is not available in CodeMode. ` , node )
} )
}
2026-07-03 05:20:28 +00:00
private sortArray (
target : Array < unknown > ,
comparator : unknown ,
node : AstNode ,
) : Effect . Effect < Array < unknown > , unknown , R > {
2026-07-03 05:19:11 +00:00
if ( comparator !== undefined && ! ( comparator instanceof CodeModeFunction ) ) {
throw new InterpreterRuntimeError ( "Array.sort expects an arrow function comparator." , node )
}
if ( ! ( comparator instanceof CodeModeFunction ) ) {
return Effect . sync ( ( ) = >
[ . . . target ] . sort ( ( a , b ) = > {
const left = coerceToString ( a )
const right = coerceToString ( b )
return left < right ? - 1 : left > right ? 1 : 0
2026-07-03 05:20:28 +00:00
} ) ,
)
2026-07-03 05:19:11 +00:00
}
const self = this
const mergeSort = ( items : Array < unknown > ) : Effect . Effect < Array < unknown > , unknown , R > = > {
if ( items . length <= 1 ) return Effect . succeed ( items )
const midpoint = Math . floor ( items . length / 2 )
2026-07-03 05:20:28 +00:00
return Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
const left = yield * mergeSort ( items . slice ( 0 , midpoint ) )
const right = yield * mergeSort ( items . slice ( midpoint ) )
const merged : Array < unknown > = [ ]
let leftIndex = 0
let rightIndex = 0
while ( leftIndex < left . length && rightIndex < right . length ) {
// Coerce the comparator's result like JS ToNumber (data objects -> NaN, never a host
// crash) and treat NaN as 0 - the spec's "no consistent order" -> keep the left element.
const order = coerceToNumber ( yield * self . invokeFunction ( comparator , [ left [ leftIndex ] , right [ rightIndex ] ] ) )
if ( Number . isNaN ( order ) || order <= 0 ) merged . push ( left [ leftIndex ++ ] )
else merged . push ( right [ rightIndex ++ ] )
}
return [ . . . merged , . . . left . slice ( leftIndex ) , . . . right . slice ( rightIndex ) ]
} )
}
// Per spec, undefined elements sort to the end and the comparator is never called on them.
const defined = target . filter ( ( item ) = > item !== undefined )
const undefinedCount = target . length - defined . length
return Effect . map ( mergeSort ( defined ) , ( items ) = > [ . . . items , . . . Array ( undefinedCount ) . fill ( undefined ) ] )
}
private evaluateObjectExpression ( node : AstNode ) : Effect . Effect < Record < string , unknown > , unknown , R > {
const objectValue : Record < string , unknown > = Object . create ( null ) as Record < string , unknown >
const properties = getArray ( node , "properties" )
const self = this
2026-07-03 05:20:28 +00:00
return Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
for ( const propertyValue of properties ) {
const property = asNode ( propertyValue , "properties" )
if ( property . type === "SpreadElement" ) {
const spread = yield * self . evaluateExpression ( getNode ( property , "argument" ) )
// JS treats `{ ...null }` / `{ ...undefined }` as a no-op, so the common
// `{ ...maybeOpts, override }` merge works when the operand is absent. Sandbox values
// (Date/RegExp/Map/Set) have no own enumerable properties in JS, so they are no-ops too.
if ( spread === null || spread === undefined || isSandboxValue ( spread ) ) continue
if ( typeof spread !== "object" || Array . isArray ( spread ) || isRuntimeReference ( spread ) ) {
2026-07-03 05:20:28 +00:00
throw new InterpreterRuntimeError (
"Object spread requires a data object in CodeMode." ,
property ,
"InvalidDataValue" ,
)
2026-07-03 05:19:11 +00:00
}
for ( const [ key , value ] of Object . entries ( spread ) ) {
2026-07-03 05:20:28 +00:00
if ( isBlockedMember ( key ) )
throw new InterpreterRuntimeError ( ` Property ' ${ key } ' is not available in CodeMode. ` , property )
2026-07-03 05:19:11 +00:00
objectValue [ key ] = value
}
continue
}
if ( property . type !== "Property" ) {
throw new InterpreterRuntimeError ( "Only standard object properties are supported." , property )
}
if ( getString ( property , "kind" ) !== "init" ) {
throw new InterpreterRuntimeError ( "Only init object properties are supported." , property )
}
const keyNode = getNode ( property , "key" )
const valueNode = getNode ( property , "value" )
const computed = getBoolean ( property , "computed" )
let key : PropertyKey
if ( computed ) {
key = self . toPropertyKey ( yield * self . evaluateExpression ( keyNode ) , keyNode )
} else if ( keyNode . type === "Identifier" ) {
key = getString ( keyNode , "name" )
} else if ( keyNode . type === "Literal" ) {
key = self . toPropertyKey ( keyNode . value , keyNode )
} else {
throw new InterpreterRuntimeError ( "Unsupported object property key shape." , keyNode )
}
if ( isBlockedMember ( String ( key ) ) ) {
throw new InterpreterRuntimeError ( ` Property ' ${ String ( key ) } ' is not available in CodeMode. ` , keyNode )
}
objectValue [ String ( key ) ] = yield * self . evaluateExpression ( valueNode )
}
return objectValue
} )
}
private evaluateArrayExpression ( node : AstNode ) : Effect . Effect < Array < unknown > , unknown , R > {
const elements = getArray ( node , "elements" )
const values : Array < unknown > = [ ]
const self = this
2026-07-03 05:20:28 +00:00
return Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
for ( const elementValue of elements ) {
if ( elementValue === null ) {
values . push ( undefined )
continue
}
const element = asNode ( elementValue , "elements" )
if ( element . type === "SpreadElement" ) {
const spread = yield * self . evaluateExpression ( getNode ( element , "argument" ) )
const items = spreadItems ( spread )
2026-07-03 05:20:28 +00:00
if ( items === undefined )
throw new InterpreterRuntimeError (
"Array spread requires an array, string, Map, or Set in CodeMode." ,
element ,
)
2026-07-03 05:19:11 +00:00
values . push ( . . . items )
} else {
values . push ( yield * self . evaluateExpression ( element ) )
}
}
return values
} )
}
private evaluateTemplateLiteral ( node : AstNode ) : Effect . Effect < string , unknown , R > {
const quasis = getArray ( node , "quasis" )
const expressions = getArray ( node , "expressions" )
let output = ""
const self = this
2026-07-03 05:20:28 +00:00
return Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
for ( let index = 0 ; index < quasis . length ; index += 1 ) {
const quasi = asNode ( quasis [ index ] , "quasis" )
const rawValue = quasi . value
if ( ! isRecord ( rawValue ) || typeof rawValue . cooked !== "string" ) {
throw new InterpreterRuntimeError ( "Invalid template literal quasi." , quasi )
}
output += rawValue . cooked
if ( index < expressions . length ) {
const raw = yield * self . evaluateExpression ( asNode ( expressions [ index ] , "expressions" ) )
// The preserving checkpoint keeps sandbox values intact, so coerceToString renders
// them directly (ISO date, /regex/ literal form) instead of a JSON-serialized husk.
output += coerceToString ( boundedData ( raw , "Template interpolation" ) )
}
}
return output
} )
}
private evaluateConditionalExpression ( node : AstNode ) : Effect . Effect < unknown , unknown , R > {
return Effect . flatMap ( this . evaluateExpression ( getNode ( node , "test" ) ) , ( test ) = >
2026-07-03 05:20:28 +00:00
this . evaluateExpression ( getNode ( node , test ? "consequent" : "alternate" ) ) ,
)
2026-07-03 05:19:11 +00:00
}
2026-07-03 05:20:28 +00:00
private applyCompoundAssignment ( operator : string , current : unknown , incoming : unknown , node : AstNode ) : unknown {
2026-07-03 05:19:11 +00:00
// `x op= y` is `x = x op y`: dispatch through the shared binary operator implementation
// so compound assignment inherits the same coercion semantics (Dates, data objects, ...).
// Only the arithmetic/bitwise operators are compoundable; logical assignments (&&=/||=/??=)
// short-circuit and are handled by evaluateLogicalAssignment before reaching here.
if ( ! compoundOperators . has ( operator ) ) {
throw new InterpreterRuntimeError ( ` Unsupported assignment operator ' ${ operator } '. ` , node )
}
return this . applyBinaryOperator ( operator . slice ( 0 , - 1 ) , current , incoming , node )
}
2026-07-03 05:20:28 +00:00
private getMemberReference (
node : AstNode ,
) : Effect . Effect <
| MemberReference
| ToolReference
| PromiseMethodReference
| IntrinsicReference
| GlobalMethodReference
| ComputedValue
| typeof OptionalShortCircuit
| undefined ,
unknown ,
R
> {
2026-07-03 05:19:11 +00:00
const objectNode = getNode ( node , "object" )
const propertyNode = getNode ( node , "property" )
const computed = getBoolean ( node , "computed" )
const optional = node . optional === true
const self = this
2026-07-03 05:20:28 +00:00
return Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
const objectValue = yield * self . evaluateExpression ( objectNode )
if ( objectValue === OptionalShortCircuit ) return OptionalShortCircuit
if ( ( objectValue === null || objectValue === undefined ) && optional ) return OptionalShortCircuit
const key = computed
? self . toPropertyKey ( yield * self . evaluateExpression ( propertyNode ) , propertyNode )
: propertyNode . type === "Identifier"
? getString ( propertyNode , "name" )
: self . toPropertyKey ( yield * self . evaluateExpression ( propertyNode ) , propertyNode )
if ( objectValue instanceof ToolReference ) {
if ( typeof key !== "string" || isBlockedMember ( key ) ) {
throw new InterpreterRuntimeError ( "Tool paths must use safe string property names." , propertyNode )
}
return new ToolReference ( [ . . . objectValue . path , key ] )
}
if ( objectValue instanceof PromiseNamespace ) {
if ( typeof key === "string" && promiseStatics . has ( key as PromiseMethodName ) ) {
return new PromiseMethodReference ( key as PromiseMethodName )
}
throw new InterpreterRuntimeError (
` Promise. ${ String ( key ) } is not available in CodeMode. Available: Promise.all, Promise.allSettled, Promise.race, Promise.resolve, and Promise.reject; consume promises with await. ` ,
propertyNode ,
)
}
if ( objectValue instanceof GlobalNamespace ) {
if ( typeof key !== "string" || isBlockedMember ( key ) ) {
2026-07-03 05:20:28 +00:00
throw new InterpreterRuntimeError (
` ${ objectValue . name } . ${ String ( key ) } is not available in CodeMode. ` ,
propertyNode ,
)
2026-07-03 05:19:11 +00:00
}
if ( objectValue . name === "Math" && mathConstants . has ( key ) ) {
return new ComputedValue ( ( Math as unknown as Record < string , number > ) [ key ] )
}
return new GlobalMethodReference ( objectValue . name , key )
}
if ( typeof objectValue === "string" ) {
if ( key === "length" ) return new ComputedValue ( objectValue . length )
if ( typeof key === "number" ) return new ComputedValue ( objectValue [ key ] )
if ( typeof key === "string" && /^\d+$/ . test ( key ) ) return new ComputedValue ( objectValue [ Number ( key ) ] )
if ( typeof key === "string" && stringMethods . has ( key ) ) return new IntrinsicReference ( objectValue , key )
// Unknown property on a string reads as `undefined`, matching JS (`"x".foo === undefined`),
// instead of throwing - so defensive access like `result?.login ?? result` on a JSON-string
// tool result doesn't crash. (Optional chaining only guards null/undefined receivers, so a
// real string still reaches here.) Only the method allowlist above yields callables.
return new ComputedValue ( undefined )
}
if ( typeof objectValue === "number" ) {
if ( typeof key === "string" && numberMethods . has ( key ) ) return new IntrinsicReference ( objectValue , key )
// Unknown property on a number reads as `undefined`, matching JS, rather than throwing.
return new ComputedValue ( undefined )
}
// Number / String expose a small allowlist of statics; everything else stays opaque.
if ( objectValue instanceof CoercionFunction && typeof key === "string" && ! isBlockedMember ( key ) ) {
if ( objectValue . name === "Number" && numberConstants . has ( key ) ) {
return new ComputedValue ( ( Number as unknown as Record < string , number > ) [ key ] )
}
if ( objectValue . name === "Number" && numberStatics . has ( key ) ) return new GlobalMethodReference ( "Number" , key )
if ( objectValue . name === "String" && stringStatics . has ( key ) ) return new GlobalMethodReference ( "String" , key )
}
// Sandbox value types expose their method/property allowlists; any other key reads as
// `undefined`, consistent with unknown-property reads on strings/numbers/arrays.
if ( objectValue instanceof SandboxDate ) {
if ( typeof key === "string" && dateMethods . has ( key ) ) return new IntrinsicReference ( objectValue , key )
return new ComputedValue ( undefined )
}
if ( objectValue instanceof SandboxRegExp ) {
if ( typeof key === "string" && regexpProperties . has ( key ) ) {
return new ComputedValue ( ( objectValue . regex as unknown as Record < string , unknown > ) [ key ] )
}
if ( typeof key === "string" && regexpMethods . has ( key ) ) return new IntrinsicReference ( objectValue , key )
return new ComputedValue ( undefined )
}
if ( objectValue instanceof SandboxMap ) {
if ( key === "size" ) return new ComputedValue ( objectValue . map . size )
if ( typeof key === "string" && mapMethods . has ( key ) ) return new IntrinsicReference ( objectValue , key )
return new ComputedValue ( undefined )
}
if ( objectValue instanceof SandboxSet ) {
if ( key === "size" ) return new ComputedValue ( objectValue . set . size )
if ( typeof key === "string" && setMethods . has ( key ) ) return new IntrinsicReference ( objectValue , key )
return new ComputedValue ( undefined )
}
// Any property access on a promise is a confused program (`p.then(...)`, `p.value`);
// reading `undefined` here would hide the missing await, so both paths get an explicit,
// await-hinting error instead of the forgiving unknown-property fallthrough.
if ( objectValue instanceof SandboxPromise ) {
if ( key === "then" || key === "catch" || key === "finally" ) {
throw new InterpreterRuntimeError (
` Promise.prototype. ${ String ( key ) } is not supported in CodeMode; use await instead (with try/catch to handle failures) - e.g. \` const result = await tools.ns.tool(...) \` . ` ,
propertyNode ,
"UnsupportedSyntax" ,
[ supportedSyntaxMessage ] ,
)
}
throw new InterpreterRuntimeError (
"This value is an un-awaited Promise and has no readable properties; await it first - e.g. `const result = await tools.ns.tool(...)`." ,
objectNode ,
"InvalidDataValue" ,
)
}
if ( isRuntimeReference ( objectValue ) ) {
2026-07-03 05:20:28 +00:00
throw new InterpreterRuntimeError (
"CodeMode runtime references are opaque and do not expose properties." ,
objectNode ,
"InvalidDataValue" ,
)
2026-07-03 05:19:11 +00:00
}
if ( typeof objectValue !== "object" || objectValue === null ) {
throw new InterpreterRuntimeError ( "Cannot access a property on a non-object value." , objectNode )
}
if ( typeof key === "string" && isBlockedMember ( key ) ) {
throw new InterpreterRuntimeError ( ` Property ' ${ key } ' is not available in CodeMode. ` , propertyNode )
}
if ( Array . isArray ( objectValue ) ) {
2026-07-03 05:20:28 +00:00
if (
key !== "length" &&
! ( typeof key === "string" && arrayMethods . has ( key ) ) &&
typeof key !== "number" &&
! /^\d+$/ . test ( key )
) {
2026-07-03 05:19:11 +00:00
// Own non-index properties read through (match results carry index/groups); like JS,
// they are readable in place and dropped by JSON at data boundaries.
if ( typeof key === "string" && Object . hasOwn ( objectValue , key ) ) {
return new ComputedValue ( ( objectValue as Record < string , unknown > & Array < unknown > ) [ key ] )
}
// Unknown property on an array reads as `undefined`, matching JS (`[1,2].foo === undefined`),
// instead of throwing - so defensive access under optional chaining behaves as expected.
return new ComputedValue ( undefined )
}
return { target : objectValue , key }
}
return { target : objectValue as SafeObject , key }
} )
}
private readMember ( node : AstNode ) : Effect . Effect < unknown , unknown , R > {
return Effect . map ( this . getMemberReference ( node ) , ( reference ) = > {
if ( reference === OptionalShortCircuit ) return OptionalShortCircuit
if ( reference instanceof ComputedValue ) return reference . value
if (
reference === undefined ||
reference instanceof ToolReference ||
reference instanceof PromiseMethodReference ||
reference instanceof IntrinsicReference ||
reference instanceof GlobalMethodReference
2026-07-03 05:20:28 +00:00
)
return reference
2026-07-03 05:19:11 +00:00
if ( Array . isArray ( reference . target ) ) {
if ( typeof reference . key === "string" && arrayMethods . has ( reference . key ) ) {
return new IntrinsicReference ( reference . target , reference . key )
}
return reference . key === "length" ? reference.target.length : reference.target [ Number ( reference . key ) ]
}
return reference . target [ String ( reference . key ) ]
} )
}
private writeMember ( node : AstNode , value : unknown ) : Effect . Effect < unknown , unknown , R > {
return this . modifyMember ( node , ( ) = > Effect . succeed ( { write : true , next : value , result : value } ) )
}
// Resolves the member reference EXACTLY ONCE (so a side-effecting object/key expression
// runs once), then lets `compute` decide whether to write - enabling compound assignment,
// updates, plain writes, and short-circuiting logical assignment to share one safe path.
private modifyMember (
node : AstNode ,
compute : ( current : unknown ) = > Effect . Effect < { write : boolean ; next : unknown ; result : unknown } , unknown , R > ,
) : Effect . Effect < unknown , unknown , R > {
const self = this
2026-07-03 05:20:28 +00:00
return Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
const reference = yield * self . getMemberReference ( node )
if (
reference === OptionalShortCircuit ||
reference instanceof ComputedValue ||
reference === undefined ||
reference instanceof ToolReference ||
reference instanceof PromiseMethodReference ||
reference instanceof IntrinsicReference ||
reference instanceof GlobalMethodReference
) {
throw new InterpreterRuntimeError ( "Only data fields may be assigned in CodeMode." , node )
}
if ( Array . isArray ( reference . target ) ) {
2026-07-03 05:20:28 +00:00
if ( reference . key === "length" )
throw new InterpreterRuntimeError ( "Array length cannot be assigned in CodeMode." , node )
2026-07-03 05:19:11 +00:00
if ( typeof reference . key === "string" && arrayMethods . has ( reference . key ) ) {
throw new InterpreterRuntimeError ( "Array methods cannot be assigned in CodeMode." , node )
}
}
const key = Array . isArray ( reference . target ) ? Number ( reference . key ) : String ( reference . key )
const current = ( reference . target as Record < PropertyKey , unknown > ) [ key ]
const { write , next , result } = yield * compute ( current )
if ( write ) self . assignToReference ( reference , key , next , node )
return result
} )
}
// Rejects inserting a value that (transitively) contains the container it is being inserted
// into - the mutation that would create a circular structure no later walk could survive.
2026-07-03 05:20:28 +00:00
private rejectCircularInsertion (
container : object ,
value : unknown ,
label : string ,
node : AstNode ,
seen = new Set < object > ( ) ,
) : void {
if ( value === container )
throw new InterpreterRuntimeError ( ` ${ label } contains a circular value. ` , node , "InvalidDataValue" )
2026-07-03 05:19:11 +00:00
if ( value === null || typeof value !== "object" || isRuntimeReference ( value ) || seen . has ( value ) ) return
seen . add ( value )
const items = Array . isArray ( value ) ? value : Object.values ( value )
for ( const item of items ) this . rejectCircularInsertion ( container , item , label , node , seen )
seen . delete ( value )
}
private assignToReference ( reference : MemberReference , key : number | string , next : unknown , node : AstNode ) : void {
if ( Array . isArray ( reference . target ) ) {
const target = reference . target
const index = key as number
if ( ! Number . isInteger ( index ) || index < 0 ) {
2026-07-03 05:20:28 +00:00
throw new InterpreterRuntimeError (
"Array assignment index must be a non-negative integer." ,
node ,
"InvalidDataValue" ,
)
2026-07-03 05:19:11 +00:00
}
this . rejectCircularInsertion ( target , next , "Array assignment result" , node )
target [ index ] = next
return
}
const target = reference . target as SafeObject
const objectKey = key as string
this . rejectCircularInsertion ( target , next , "Object assignment result" , node )
target [ objectKey ] = next
}
private toPropertyKey ( value : unknown , node : AstNode ) : string | number {
if ( typeof value === "string" || typeof value === "number" ) {
return value
}
throw new InterpreterRuntimeError ( "Property key must be a string or number." , node )
}
private declare ( name : string , value : unknown , mutable : boolean , node : AstNode ) : void {
const scope = this . currentScope ( )
// A pre-seeded parameter slot (initialized === false) is being bound for the first time;
// anything else already present is a genuine duplicate declaration.
const existing = scope . get ( name )
if ( existing && existing . initialized !== false ) {
throw new InterpreterRuntimeError ( ` Identifier ' ${ name } ' has already been declared. ` , node )
}
scope . set ( name , { mutable , value , initialized : true } )
}
private getIdentifierValue ( name : string , node : AstNode ) : unknown {
const binding = this . resolveBinding ( name )
if ( ! binding ) {
throw new InterpreterRuntimeError ( ` Unknown identifier ' ${ name } '. ` , node ) . as ( "ReferenceError" )
}
// A parameter default that forward-references a later (not-yet-bound) parameter - JS TDZ.
if ( binding . initialized === false ) {
throw new InterpreterRuntimeError ( ` Cannot access ' ${ name } ' before initialization. ` , node ) . as ( "ReferenceError" )
}
return binding . value
}
private setIdentifierValue ( name : string , value : unknown , node : AstNode ) : unknown {
const binding = this . resolveBinding ( name )
if ( ! binding ) {
throw new InterpreterRuntimeError ( ` Unknown identifier ' ${ name } '. ` , node ) . as ( "ReferenceError" )
}
if ( ! binding . mutable ) {
throw new InterpreterRuntimeError ( ` Cannot assign to constant ' ${ name } '. ` , node ) . as ( "TypeError" )
}
binding . value = value
return value
}
private resolveBinding ( name : string ) : Binding | undefined {
for ( let index = this . scopes . length - 1 ; index >= 0 ; index -= 1 ) {
const scope = this . scopes [ index ]
const binding = scope ? . get ( name )
if ( binding ) {
return binding
}
}
return undefined
}
private currentScope ( ) : Map < string , Binding > {
const scope = this . scopes [ this . scopes . length - 1 ]
if ( ! scope ) {
throw new InterpreterRuntimeError ( "Interpreter scope stack is empty." )
}
return scope
}
private pushScope ( ) : void {
this . scopes . push ( new Map ( ) )
}
private popScope ( ) : void {
this . scopes . pop ( )
}
}
/ * *
* Executes one Effect - native CodeMode program without constructing a reusable runtime .
*
* @example
* ` ` ` ts
* const result = yield * CodeMode . execute ( {
* tools : { lookup } ,
* code : ` return await tools.lookup({ id: "order_42" }) ` ,
* } )
* ` ` `
* /
const executeWithLimits = < const Tools extends Record < string , unknown > > (
options : ExecuteOptions < Tools > ,
limits : ResolvedExecutionLimits ,
searchIndex : ToolRuntime.DiscoveryPlan [ "searchIndex" ] ,
) : Effect . Effect < ExecuteResult , never , Services < Tools > > = > {
const hooks = {
. . . ( options . onToolCallStart === undefined ? { } : { onToolCallStart : options.onToolCallStart } ) ,
. . . ( options . onToolCallEnd === undefined ? { } : { onToolCallEnd : options.onToolCallEnd } ) ,
}
2026-07-03 05:20:28 +00:00
const tools = ToolRuntime . make (
( options . tools ? ? { } ) as HostTools < Services < Tools > > ,
limits . maxToolCalls ,
hooks ,
searchIndex ,
)
2026-07-03 05:19:11 +00:00
const logs : Array < string > = [ ]
2026-07-03 05:20:28 +00:00
const logged = ( ) = > ( logs . length > 0 ? { logs : [ . . . logs ] } : { } )
2026-07-03 05:19:11 +00:00
if ( options . code . trim ( ) . length === 0 ) {
return Effect . succeed ( {
ok : false ,
error : { kind : "ParseError" , message : "Code cannot be empty." } ,
toolCalls : tools.calls ,
} )
}
2026-07-03 05:20:28 +00:00
const operation = Effect . gen ( function * ( ) {
2026-07-03 05:19:11 +00:00
const program = parseProgram ( options . code )
const interpreter = new Interpreter < Services < Tools > > ( tools . invoke , tools . keys , logs )
const value = yield * interpreter . run ( program )
const result = copyOut ( copyIn ( value , "Execution result" ) , true ) as DataValue
return {
ok : true ,
value : result ,
. . . logged ( ) ,
toolCalls : tools.calls ,
} satisfies ExecuteResult
2026-07-03 05:20:28 +00:00
} ) . pipe ( ( program ) = > {
const timeoutMs = limits . timeoutMs
if ( timeoutMs === undefined ) return program
return program . pipe (
Effect . timeoutOrElse ( {
duration : timeoutMs ,
orElse : ( ) = >
Effect . succeed ( {
2026-07-03 05:19:11 +00:00
ok : false ,
error : { kind : "TimeoutExceeded" , message : ` Execution timed out after ${ timeoutMs } ms. ` } ,
. . . logged ( ) ,
toolCalls : tools.calls ,
} satisfies ExecuteResult ) ,
2026-07-03 05:20:28 +00:00
} ) ,
)
} )
2026-07-03 05:19:11 +00:00
return operation . pipe (
Effect . catchCause ( ( cause ) = >
Cause . hasInterruptsOnly ( cause )
? Effect . interrupt
: Effect . succeed ( {
ok : false ,
error : normalizeError ( Cause . squash ( cause ) ) ,
. . . logged ( ) ,
toolCalls : tools.calls ,
} satisfies ExecuteResult ) ,
) ,
2026-07-03 05:20:28 +00:00
Effect . map ( ( result ) = > ( limits . maxOutputBytes === undefined ? result : boundOutput ( result , limits . maxOutputBytes ) ) ) ,
2026-07-03 05:19:11 +00:00
)
}
const utf8ByteLength = ( value : string ) : number = > new TextEncoder ( ) . encode ( value ) . byteLength
// Truncates to a UTF-8 byte budget without splitting a code point (a split multi-byte
// sequence decodes to a replacement character, which is dropped).
const utf8Truncate = ( value : string , maxBytes : number ) : string = > {
const bytes = new TextEncoder ( ) . encode ( value )
if ( bytes . byteLength <= maxBytes ) return value
const text = new TextDecoder ( "utf-8" ) . decode ( bytes . slice ( 0 , Math . max ( 0 , maxBytes ) ) )
return text . endsWith ( "\uFFFD" ) ? text . slice ( 0 , - 1 ) : text
}
/ * *
* Bounds the model - facing output ( serialized result value plus logs ) to ` maxOutputBytes ` .
* Oversized values are replaced by their truncated serialized text with an explanatory marker ,
* and logs are kept from the start until the remaining budget is exhausted . Truncation never
* fails the execution ; ` truncated: true ` marks affected results . Only runs when the host set
* ` maxOutputBytes ` - with the limit absent , output passes through unbounded .
* /
const boundOutput = ( result : ExecuteResult , maxOutputBytes : number ) : ExecuteResult = > {
let truncated = false
let value : DataValue = null
let valueBytes = 0
if ( result . ok ) {
const serialized = JSON . stringify ( result . value ) ? ? "null"
const bytes = utf8ByteLength ( serialized )
if ( bytes > maxOutputBytes ) {
truncated = true
value = ` ${ utf8Truncate ( serialized , maxOutputBytes ) } [result truncated: ${ bytes } bytes exceeds the ${ maxOutputBytes } -byte output limit; return a smaller value] `
valueBytes = maxOutputBytes
} else {
value = result . value
valueBytes = bytes
}
}
const logs = result . logs ? ? [ ]
const kept : Array < string > = [ ]
const logBudget = Math . max ( 0 , maxOutputBytes - valueBytes )
let logBytes = 0
for ( const line of logs ) {
const lineBytes = utf8ByteLength ( line ) + 1
if ( logBytes + lineBytes > logBudget ) break
logBytes += lineBytes
kept . push ( line )
}
if ( kept . length < logs . length ) {
truncated = true
kept . push ( ` [logs truncated: showing ${ kept . length } of ${ logs . length } lines] ` )
}
if ( ! truncated ) return result
const logsPart = kept . length > 0 ? { logs : kept } : { }
return result . ok
? { ok : true , value , . . . logsPart , truncated : true , toolCalls : result.toolCalls }
: { ok : false , error : result.error , . . . logsPart , truncated : true , toolCalls : result.toolCalls }
}
2026-07-03 05:20:28 +00:00
export const execute = < const Tools extends Record < string , unknown > > (
options : ExecuteOptions < Tools > ,
) : Effect . Effect < ExecuteResult , never , Services < Tools > > = > {
2026-07-03 05:19:11 +00:00
const tools = ( options . tools ? ? { } ) as HostTools < Services < Tools > >
ToolRuntime . assertValidTools ( tools )
return executeWithLimits ( options , resolveExecutionLimits ( options . limits ) , ToolRuntime . searchIndex ( tools ) )
}
/ * *
* Creates an Effect - native runtime over explicit , schema - described tools .
*
* Use ` execute ` for host - driven execution or ` agentTool ` to expose one confined code tool to an
* agent framework . Tool requirements remain in the returned Effect environment .
*
* @example
* ` ` ` ts
* const runtime = CodeMode . make ( { tools : { orders : { lookup } } } )
* const code = runtime . agentTool ( )
* ` ` `
* /
2026-07-03 05:20:28 +00:00
export const make = < const Tools extends Record < string , unknown > = { } > (
options : CodeModeOptions < Tools > = { } as CodeModeOptions < Tools > ,
) : CodeModeRuntime < Services < Tools > > = > {
2026-07-03 05:19:11 +00:00
const tools = ( options . tools ? ? { } ) as HostTools < Services < Tools > >
ToolRuntime . assertValidTools ( tools )
const limits = resolveExecutionLimits ( options . limits )
const discovery = ToolRuntime . discoveryPlan ( tools , options . discovery ? . maxInlineCatalogTokens )
const executeProgram = ( code : string ) = > executeWithLimits < Tools > ( { . . . options , code } , limits , discovery . searchIndex )
const catalog = discovery . catalog
const instructions = discovery . instructions
return {
catalog : ( ) = > catalog ,
instructions : ( ) = > instructions ,
agentTool : ( ) = > ( {
name : "code" ,
description : instructions ,
input : ExecuteInputSchema ,
output : ExecuteResultSchema ,
execute : ( { code } ) = > executeProgram ( code ) ,
} ) ,
execute : executeProgram ,
}
}
/** Constructors for one-shot and reusable CodeMode execution. */
export const CodeMode = { make , execute }