refactor(codemode): simplify model-facing wording (#38042)

This commit is contained in:
Aiden Cline 2026-07-21 10:19:53 -05:00 committed by GitHub
commit 8d80365ef4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 70 additions and 98 deletions

View file

@ -4,6 +4,7 @@
- Do not add a speculative generic permission or approval policy. A host omits tools it does not expose and enforces domain authorization inside each provided tool. - Do not add a speculative generic permission or approval policy. A host omits tools it does not expose and enforces domain authorization inside each provided tool.
- Keep Code Mode unaware of host session, channel, and conversation models. The hosting application supplies trusted execution scope around it. - Keep Code Mode unaware of host session, channel, and conversation models. The hosting application supplies trusted execution scope around it.
- Tool schemas are the model-facing Interface. Keep arguments minimal and natural to the operation; never add unrelated IDs as ambient capability tokens. - Tool schemas are the model-facing Interface. Keep arguments minimal and natural to the operation; never add unrelated IDs as ambient capability tokens.
- State model-visible diagnostics, logs, tool descriptions, and instructions directly. The execution context is already clear; do not repeat `Code Mode` or `CodeMode` unless the distinction is necessary.
- When interpreter behavior or support changes, update `interpreter-support.md` and direct tests in the same PR. - When interpreter behavior or support changes, update `interpreter-support.md` and direct tests in the same PR.
## OpenAPI ## OpenAPI

View file

@ -126,7 +126,7 @@ export const invokeIntrinsic = <R>(
if (ref.receiver instanceof CodeModeURLSearchParams) { if (ref.receiver instanceof CodeModeURLSearchParams) {
return invokeURLSearchParamsMethod(runner, ref.receiver, ref.name, args, node) return invokeURLSearchParamsMethod(runner, ref.receiver, ref.name, args, node)
} }
throw new InterpreterRuntimeError(`Method '${ref.name}' is not available in CodeMode.`, node) throw new InterpreterRuntimeError(`Method '${ref.name}' is not available.`, node)
} }
const coerceNumericArgument = <R>( const coerceNumericArgument = <R>(
@ -157,8 +157,7 @@ const coerceNumericArgument = <R>(
} }
export const invokeGlobalMethod = (ref: GlobalMethodReference, args: Array<unknown>, node: AstNode): unknown => { export const invokeGlobalMethod = (ref: GlobalMethodReference, args: Array<unknown>, node: AstNode): unknown => {
if (ref.namespace === "console") if (ref.namespace === "console") throw new InterpreterRuntimeError(`console.${ref.name} is not available.`, node)
throw new InterpreterRuntimeError(`console.${ref.name} is not available in CodeMode.`, node)
if (ref.namespace === "Object") return invokeObjectMethod(ref.name, args, node) if (ref.namespace === "Object") return invokeObjectMethod(ref.name, args, node)
if (ref.namespace === "Math") return invokeMathMethod(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 === "Array") return invokeArrayStatic(ref.name, args, node)
@ -168,9 +167,9 @@ export const invokeGlobalMethod = (ref: GlobalMethodReference, args: Array<unkno
if (ref.namespace === "Date") return invokeDateStatic(ref.name, args, node) if (ref.namespace === "Date") return invokeDateStatic(ref.name, args, node)
if (ref.namespace === "RegExp") return invokeRegExpStatic(ref.name, args, node) if (ref.namespace === "RegExp") return invokeRegExpStatic(ref.name, args, node)
if (ref.namespace === "Map" || ref.namespace === "Set" || ref.namespace === "URLSearchParams") { if (ref.namespace === "Map" || ref.namespace === "Set" || ref.namespace === "URLSearchParams") {
throw new InterpreterRuntimeError(`${ref.namespace}.${ref.name} is not available in CodeMode.`, node) throw new InterpreterRuntimeError(`${ref.namespace}.${ref.name} is not available.`, node)
} }
throw new InterpreterRuntimeError(`${ref.namespace}.${ref.name} is not available in CodeMode.`, node) throw new InterpreterRuntimeError(`${ref.namespace}.${ref.name} is not available.`, node)
} }
const requireDataArgument = (name: string, index: number, arg: unknown, node: AstNode): unknown => { const requireDataArgument = (name: string, index: number, arg: unknown, node: AstNode): unknown => {
@ -348,7 +347,7 @@ const invokeStringMethod = (value: string, name: string, args: Array<unknown>, n
break break
} }
default: default:
throw new InterpreterRuntimeError(`String method '${name}' is not available in CodeMode.`, node) throw new InterpreterRuntimeError(`String method '${name}' is not available.`, node)
} }
return boundedData(result, `String.${name} result`) return boundedData(result, `String.${name} result`)
} }
@ -364,7 +363,7 @@ const invokeArrayStatic = (name: string, args: Array<unknown>, node: AstNode): u
case "from": case "from":
return arrayFromItems(args[0], node) return arrayFromItems(args[0], node)
default: default:
throw new InterpreterRuntimeError(`Array.${name} is not available in CodeMode.`, node) throw new InterpreterRuntimeError(`Array.${name} is not available.`, node)
} }
} }
@ -449,7 +448,7 @@ export const invokeGroupBy = <R>(
for (const item of items) { for (const item of items) {
const key = yield* coerceGroupByPropertyKey(runner, yield* apply([item, index]), node) const key = yield* coerceGroupByPropertyKey(runner, yield* apply([item, index]), node)
if (isBlockedMember(key)) { if (isBlockedMember(key)) {
throw new InterpreterRuntimeError(`Property '${key}' is not available in CodeMode.`, node) throw new InterpreterRuntimeError(`Property '${key}' is not available.`, node)
} }
const group = result[key] const group = result[key]
if (group === undefined) result[key] = [item] if (group === undefined) result[key] = [item]
@ -619,7 +618,7 @@ const invokeMapMethod = <R>(
}) })
} }
default: default:
throw new InterpreterRuntimeError(`Map method '${name}' is not available in CodeMode.`, node) throw new InterpreterRuntimeError(`Map method '${name}' is not available.`, node)
} }
} }
@ -666,7 +665,7 @@ const invokeSetMethod = <R>(
case "isDisjointFrom": case "isDisjointFrom":
return invokeSetOperation(runner, target, name, args[0], node) return invokeSetOperation(runner, target, name, args[0], node)
default: default:
throw new InterpreterRuntimeError(`Set method '${name}' is not available in CodeMode.`, node) throw new InterpreterRuntimeError(`Set method '${name}' is not available.`, node)
} }
} }
@ -861,7 +860,7 @@ const invokeURLSearchParamsMethod = <R>(
}) })
} }
default: default:
throw new InterpreterRuntimeError(`URLSearchParams method '${name}' is not available in CodeMode.`, node) throw new InterpreterRuntimeError(`URLSearchParams method '${name}' is not available.`, node)
} }
} }
@ -1104,7 +1103,7 @@ const invokeArrayMethod = <R>(
} }
return -1 return -1
} }
throw new InterpreterRuntimeError(`Array method '${name}' is not available in CodeMode.`, node) throw new InterpreterRuntimeError(`Array method '${name}' is not available.`, node)
}) })
} }

View file

@ -166,7 +166,7 @@ export class InterpreterRuntimeError extends Error {
export const unsupportedSyntax = (kind: string, node: AstNode): InterpreterRuntimeError => export const unsupportedSyntax = (kind: string, node: AstNode): InterpreterRuntimeError =>
new InterpreterRuntimeError( new InterpreterRuntimeError(
`Syntax '${kind}' is not supported in CodeMode. ${supportedSyntaxMessage}`, `Syntax '${kind}' is not supported. ${supportedSyntaxMessage}`,
node, node,
"UnsupportedSyntax", "UnsupportedSyntax",
[supportedSyntaxMessage], [supportedSyntaxMessage],

View file

@ -166,7 +166,7 @@ const instanceofValue = (lhs: unknown, rhs: unknown, node: AstNode): boolean =>
return false return false
} }
throw new InterpreterRuntimeError( 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, URL, URLSearchParams, Array, Object, or Promise.", "The right-hand side of 'instanceof' must be a supported constructor: Error (or a specific error type like TypeError), Date, RegExp, Map, Set, URL, URLSearchParams, Array, Object, or Promise.",
node, node,
) )
} }
@ -393,12 +393,9 @@ export class Interpreter<R> {
private createFunction(node: AstNode): CodeModeFunction { private createFunction(node: AstNode): CodeModeFunction {
if (node.generator === true) { if (node.generator === true) {
throw new InterpreterRuntimeError( throw new InterpreterRuntimeError("Generator functions are not supported.", node, "UnsupportedSyntax", [
"Generator functions are not supported in CodeMode.", supportedSyntaxMessage,
node, ])
"UnsupportedSyntax",
[supportedSyntaxMessage],
)
} }
return new CodeModeFunction( return new CodeModeFunction(
getArray(node, "params").map((parameter, index) => asNode(parameter, `params[${index}]`)), getArray(node, "params").map((parameter, index) => asNode(parameter, `params[${index}]`)),
@ -454,11 +451,7 @@ export class Interpreter<R> {
return Effect.gen(function* () { return Effect.gen(function* () {
const discriminant = yield* self.evaluateExpression(getNode(node, "discriminant")) const discriminant = yield* self.evaluateExpression(getNode(node, "discriminant"))
if (containsOpaqueReference(discriminant)) { if (containsOpaqueReference(discriminant)) {
throw new InterpreterRuntimeError( throw new InterpreterRuntimeError("Switch discriminants must be data values.", node, "InvalidDataValue")
"Switch discriminants must be data values in CodeMode.",
node,
"InvalidDataValue",
)
} }
self.scopes.push() self.scopes.push()
return yield* Effect.gen(function* () { return yield* Effect.gen(function* () {
@ -474,11 +467,7 @@ export class Interpreter<R> {
} }
const candidate = yield* self.evaluateExpression(test) const candidate = yield* self.evaluateExpression(test)
if (containsOpaqueReference(candidate)) { if (containsOpaqueReference(candidate)) {
throw new InterpreterRuntimeError( throw new InterpreterRuntimeError("Switch case values must be data values.", test, "InvalidDataValue")
"Switch case values must be data values in CodeMode.",
test,
"InvalidDataValue",
)
} }
if (candidate === discriminant) { if (candidate === discriminant) {
selected = index selected = index
@ -649,7 +638,7 @@ export class Interpreter<R> {
const iterable = spreadItems(right) const iterable = spreadItems(right)
if (iterable === undefined) { if (iterable === undefined) {
throw new InterpreterRuntimeError( throw new InterpreterRuntimeError(
`${awaiting ? "for await...of" : "for...of"} requires an array, string, Map, Set, or URLSearchParams value in CodeMode.`, `${awaiting ? "for await...of" : "for...of"} requires an array, string, Map, Set, or URLSearchParams value.`,
node, node,
) )
} }
@ -746,7 +735,7 @@ export class Interpreter<R> {
const keys = self.enumerableKeys(right) const keys = self.enumerableKeys(right)
if (keys === undefined) { if (keys === undefined) {
throw new InterpreterRuntimeError( 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.", "for...in requires a plain object, array, or tools reference. Use for...of for arrays/strings/Maps/Sets, or Object.keys(value) for a key list.",
node, node,
) )
} }
@ -948,7 +937,7 @@ export class Interpreter<R> {
const key = yield* self.destructuringPropertyKey(property) const key = yield* self.destructuringPropertyKey(property)
if (isBlockedMember(String(key))) { if (isBlockedMember(String(key))) {
throw new InterpreterRuntimeError(`Property '${String(key)}' is not available in CodeMode.`, property) throw new InterpreterRuntimeError(`Property '${String(key)}' is not available.`, property)
} }
consumed.add(String(key)) consumed.add(String(key))
yield* self.declarePattern( yield* self.declarePattern(
@ -1026,7 +1015,7 @@ export class Interpreter<R> {
} }
const key = yield* self.destructuringPropertyKey(property) const key = yield* self.destructuringPropertyKey(property)
if (isBlockedMember(String(key))) { if (isBlockedMember(String(key))) {
throw new InterpreterRuntimeError(`Property '${String(key)}' is not available in CodeMode.`, property) throw new InterpreterRuntimeError(`Property '${String(key)}' is not available.`, property)
} }
consumed.add(String(key)) consumed.add(String(key))
yield* self.assignPattern(getNode(property, "value"), self.destructuringPropertyValue(source, key), property) yield* self.assignPattern(getNode(property, "value"), self.destructuringPropertyValue(source, key), property)
@ -1202,7 +1191,7 @@ export class Interpreter<R> {
if (first === null || first === undefined) return {} if (first === null || first === undefined) return {}
if (typeof first === "object") return first if (typeof first === "object") return first
throw new InterpreterRuntimeError( throw new InterpreterRuntimeError(
`Object(${typeof first}) wrapper objects are not supported in CodeMode; use the primitive value directly.`, `Object(${typeof first}) wrapper objects are not supported; use the primitive value directly.`,
node, node,
) )
} }
@ -1379,7 +1368,7 @@ export class Interpreter<R> {
private applyBinaryOperator(operator: string, lhs: unknown, rhs: unknown, node: AstNode): unknown { private applyBinaryOperator(operator: string, lhs: unknown, rhs: unknown, node: AstNode): unknown {
if (containsOpaqueReference(lhs) || containsOpaqueReference(rhs)) { if (containsOpaqueReference(lhs) || containsOpaqueReference(rhs)) {
throw new InterpreterRuntimeError("Binary operators require data values in CodeMode.", node, "InvalidDataValue") throw new InterpreterRuntimeError("Binary operators require data values.", node, "InvalidDataValue")
} }
// Null-prototype data needs explicit primitive coercion; identity and `in` retain raw objects. // Null-prototype data needs explicit primitive coercion; identity and `in` retain raw objects.
// Dates use their default string hint for addition and loose equality, and epoch time elsewhere. // Dates use their default string hint for addition and loose equality, and epoch time elsewhere.
@ -1470,7 +1459,7 @@ export class Interpreter<R> {
if (operator === "!") return !value if (operator === "!") return !value
if (operator === "void") return undefined if (operator === "void") return undefined
if (containsOpaqueReference(value)) { if (containsOpaqueReference(value)) {
throw new InterpreterRuntimeError("Unary operators require data values in CodeMode.", node, "InvalidDataValue") throw new InterpreterRuntimeError("Unary operators require data values.", node, "InvalidDataValue")
} }
const operand = const operand =
value instanceof CodeModeDate value instanceof CodeModeDate
@ -1585,11 +1574,7 @@ export class Interpreter<R> {
// the host throw during ToPrimitive, and opaque runtime references must reject clearly. // the host throw during ToPrimitive, and opaque runtime references must reject clearly.
const operand = (current: unknown): number => { const operand = (current: unknown): number => {
if (containsOpaqueReference(current)) { if (containsOpaqueReference(current)) {
throw new InterpreterRuntimeError( throw new InterpreterRuntimeError(`'${operator}' requires a data value.`, argument, "InvalidDataValue")
`'${operator}' requires a data value in CodeMode.`,
argument,
"InvalidDataValue",
)
} }
return coerceToNumber(current) return coerceToNumber(current)
} }
@ -1712,7 +1697,7 @@ export class Interpreter<R> {
if (callable === undefined || callable === null) { if (callable === undefined || callable === null) {
throw new InterpreterRuntimeError(`${calleeDescription(callee)} is not a function.`, callee).as("TypeError") throw new InterpreterRuntimeError(`${calleeDescription(callee)} is not a function.`, callee).as("TypeError")
} }
throw new InterpreterRuntimeError("Only tools are callable in CodeMode.", callee) throw new InterpreterRuntimeError("Only tools are callable here.", callee)
}) })
} }
@ -1728,8 +1713,7 @@ export class Interpreter<R> {
} }
private invokeConsole(name: string, args: Array<unknown>, node: AstNode): undefined { private invokeConsole(name: string, args: Array<unknown>, node: AstNode): undefined {
if (!consoleMethods.has(name)) if (!consoleMethods.has(name)) throw new InterpreterRuntimeError(`console.${name} is not available.`, node)
throw new InterpreterRuntimeError(`console.${name} is not available in CodeMode.`, node)
this.logs.push(formatConsoleMessage(name, args)) this.logs.push(formatConsoleMessage(name, args))
return undefined return undefined
} }
@ -1744,10 +1728,7 @@ export class Interpreter<R> {
const spread = yield* self.evaluateExpression(getNode(argNode, "argument")) const spread = yield* self.evaluateExpression(getNode(argNode, "argument"))
const items = spreadItems(spread) const items = spreadItems(spread)
if (items === undefined) if (items === undefined)
throw new InterpreterRuntimeError( throw new InterpreterRuntimeError("Spread arguments require an array, string, Map, or Set.", argNode)
"Spread arguments require an array, string, Map, or Set in CodeMode.",
argNode,
)
args.push(...items) args.push(...items)
} else { } else {
args.push(yield* self.evaluateExpression(argNode)) args.push(yield* self.evaluateExpression(argNode))
@ -1813,15 +1794,10 @@ export class Interpreter<R> {
const spread = yield* self.evaluateExpression(getNode(property, "argument")) const spread = yield* self.evaluateExpression(getNode(property, "argument"))
if (spread === null || spread === undefined || isCodeModeValue(spread)) continue if (spread === null || spread === undefined || isCodeModeValue(spread)) continue
if (typeof spread !== "object" || Array.isArray(spread) || isRuntimeReference(spread)) { if (typeof spread !== "object" || Array.isArray(spread) || isRuntimeReference(spread)) {
throw new InterpreterRuntimeError( throw new InterpreterRuntimeError("Object spread requires a data object.", property, "InvalidDataValue")
"Object spread requires a data object in CodeMode.",
property,
"InvalidDataValue",
)
} }
for (const [key, value] of Object.entries(spread)) { for (const [key, value] of Object.entries(spread)) {
if (isBlockedMember(key)) if (isBlockedMember(key)) throw new InterpreterRuntimeError(`Property '${key}' is not available.`, property)
throw new InterpreterRuntimeError(`Property '${key}' is not available in CodeMode.`, property)
objectValue[key] = value objectValue[key] = value
} }
continue continue
@ -1852,7 +1828,7 @@ export class Interpreter<R> {
} }
if (isBlockedMember(String(key))) { if (isBlockedMember(String(key))) {
throw new InterpreterRuntimeError(`Property '${String(key)}' is not available in CodeMode.`, keyNode) throw new InterpreterRuntimeError(`Property '${String(key)}' is not available.`, keyNode)
} }
objectValue[String(key)] = yield* self.evaluateExpression(valueNode) objectValue[String(key)] = yield* self.evaluateExpression(valueNode)
} }
@ -1878,10 +1854,7 @@ export class Interpreter<R> {
const spread = yield* self.evaluateExpression(getNode(element, "argument")) const spread = yield* self.evaluateExpression(getNode(element, "argument"))
const items = spreadItems(spread) const items = spreadItems(spread)
if (items === undefined) if (items === undefined)
throw new InterpreterRuntimeError( throw new InterpreterRuntimeError("Array spread requires an array, string, Map, or Set.", element)
"Array spread requires an array, string, Map, or Set in CodeMode.",
element,
)
values.push(...items) values.push(...items)
} else { } else {
values.push(yield* self.evaluateExpression(element)) values.push(yield* self.evaluateExpression(element))
@ -1977,14 +1950,14 @@ export class Interpreter<R> {
return new PromiseMethodReference(key as PromiseMethodName) return new PromiseMethodReference(key as PromiseMethodName)
} }
throw new InterpreterRuntimeError( throw new InterpreterRuntimeError(
`Promise.${String(key)} is not available in CodeMode. Available: Promise.all, Promise.allSettled, Promise.race, Promise.any, Promise.resolve, and Promise.reject; consume promises with await.`, `Promise.${String(key)} is not available. Available: Promise.all, Promise.allSettled, Promise.race, Promise.any, Promise.resolve, and Promise.reject; consume promises with await.`,
propertyNode, propertyNode,
) )
} }
if (objectValue instanceof GlobalNamespace) { if (objectValue instanceof GlobalNamespace) {
if (typeof key === "string" && isBlockedMember(key)) { if (typeof key === "string" && isBlockedMember(key)) {
throw new InterpreterRuntimeError(`${objectValue.name}.${key} is not available in CodeMode.`, propertyNode) throw new InterpreterRuntimeError(`${objectValue.name}.${key} is not available.`, propertyNode)
} }
if (typeof key !== "string") return new ComputedValue(undefined) if (typeof key !== "string") return new ComputedValue(undefined)
if (objectValue.name === "Math" && mathConstants.has(key)) { if (objectValue.name === "Math" && mathConstants.has(key)) {
@ -2016,7 +1989,7 @@ export class Interpreter<R> {
if (objectValue instanceof CoercionFunction) { if (objectValue instanceof CoercionFunction) {
if (typeof key === "string" && isBlockedMember(key)) { if (typeof key === "string" && isBlockedMember(key)) {
throw new InterpreterRuntimeError(`${objectValue.name}.${key} is not available in CodeMode.`, propertyNode) throw new InterpreterRuntimeError(`${objectValue.name}.${key} is not available.`, propertyNode)
} }
if (typeof key !== "string") return new ComputedValue(undefined) if (typeof key !== "string") return new ComputedValue(undefined)
if (objectValue.name === "Number" && numberConstants.has(key)) { if (objectValue.name === "Number" && numberConstants.has(key)) {
@ -2083,7 +2056,7 @@ export class Interpreter<R> {
if (isRuntimeReference(objectValue)) { if (isRuntimeReference(objectValue)) {
throw new InterpreterRuntimeError( throw new InterpreterRuntimeError(
"CodeMode runtime references are opaque and do not expose properties.", "Runtime references are opaque and do not expose properties.",
objectNode, objectNode,
"InvalidDataValue", "InvalidDataValue",
) )
@ -2094,7 +2067,7 @@ export class Interpreter<R> {
} }
if (typeof key === "string" && isBlockedMember(key)) { if (typeof key === "string" && isBlockedMember(key)) {
throw new InterpreterRuntimeError(`Property '${key}' is not available in CodeMode.`, propertyNode) throw new InterpreterRuntimeError(`Property '${key}' is not available.`, propertyNode)
} }
if (Array.isArray(objectValue)) { if (Array.isArray(objectValue)) {
@ -2147,7 +2120,7 @@ export class Interpreter<R> {
private evaluateDeleteExpression(argument: AstNode): Effect.Effect<boolean, unknown, R> { private evaluateDeleteExpression(argument: AstNode): Effect.Effect<boolean, unknown, R> {
const target = argument.type === "ChainExpression" ? getNode(argument, "expression") : argument const target = argument.type === "ChainExpression" ? getNode(argument, "expression") : argument
if (target.type !== "MemberExpression") { if (target.type !== "MemberExpression") {
throw new InterpreterRuntimeError("Only data fields may be deleted in CodeMode.", argument) throw new InterpreterRuntimeError("Only data fields may be deleted.", argument)
} }
return Effect.map(this.getMemberReference(target, "delete"), (reference) => { return Effect.map(this.getMemberReference(target, "delete"), (reference) => {
if (reference === OptionalShortCircuit) return true if (reference === OptionalShortCircuit) return true
@ -2162,7 +2135,7 @@ export class Interpreter<R> {
reference instanceof JsonMethodReference || reference instanceof JsonMethodReference ||
reference.target instanceof CodeModeURL reference.target instanceof CodeModeURL
) { ) {
throw new InterpreterRuntimeError("Only data fields may be deleted in CodeMode.", target, "InvalidDataValue") throw new InterpreterRuntimeError("Only data fields may be deleted.", target, "InvalidDataValue")
} }
if (reference.target instanceof CodeModeRegExp) { if (reference.target instanceof CodeModeRegExp) {
return Reflect.deleteProperty(reference.target.regex, reference.key) return Reflect.deleteProperty(reference.target.regex, reference.key)
@ -2190,13 +2163,12 @@ export class Interpreter<R> {
reference instanceof GlobalMethodReference || reference instanceof GlobalMethodReference ||
reference instanceof JsonMethodReference reference instanceof JsonMethodReference
) { ) {
throw new InterpreterRuntimeError("Only data fields may be assigned in CodeMode.", node) throw new InterpreterRuntimeError("Only data fields may be assigned.", node)
} }
if (Array.isArray(reference.target)) { if (Array.isArray(reference.target)) {
if (reference.key === "length") if (reference.key === "length") throw new InterpreterRuntimeError("Array length cannot be assigned.", node)
throw new InterpreterRuntimeError("Array length cannot be assigned in CodeMode.", node)
if (typeof reference.key === "string" && arrayMethods.has(reference.key)) { if (typeof reference.key === "string" && arrayMethods.has(reference.key)) {
throw new InterpreterRuntimeError("Array methods cannot be assigned in CodeMode.", node) throw new InterpreterRuntimeError("Array methods cannot be assigned.", node)
} }
} }
const key = Array.isArray(reference.target) ? reference.key : String(reference.key) const key = Array.isArray(reference.target) ? reference.key : String(reference.key)

View file

@ -58,7 +58,7 @@ const formatConsoleValue = (value: unknown, seen: Set<object>, depth: number): s
seen.delete(value) seen.delete(value)
} }
} }
if (isRuntimeReference(value)) return "[CodeMode reference]" if (isRuntimeReference(value)) return "[opaque reference]"
seen.add(value) seen.add(value)
try { try {
if (Array.isArray(value)) { if (Array.isArray(value)) {
@ -74,7 +74,7 @@ const formatConsoleValue = (value: unknown, seen: Set<object>, depth: number): s
const formatConsoleTable = (value: unknown, columnsArgument: unknown): string => { const formatConsoleTable = (value: unknown, columnsArgument: unknown): string => {
if (value === undefined) return "undefined" if (value === undefined) return "undefined"
if (containsOpaqueReference(value)) return "[CodeMode reference]" if (containsOpaqueReference(value)) return "[opaque reference]"
const data = boundedData(value, "console.table argument") const data = boundedData(value, "console.table argument")
const columns = consoleTableColumns(columnsArgument) const columns = consoleTableColumns(columnsArgument)
const rows = consoleTableRows(data, columns) const rows = consoleTableRows(data, columns)

View file

@ -59,7 +59,7 @@ export const invokeDateStatic = (name: string, args: Array<unknown>, node: AstNo
case "UTC": case "UTC":
return Date.UTC(...(args.map((arg) => coerceToNumber(arg)) as Parameters<typeof Date.UTC>)) return Date.UTC(...(args.map((arg) => coerceToNumber(arg)) as Parameters<typeof Date.UTC>))
default: default:
throw new InterpreterRuntimeError(`Date.${name} is not available in CodeMode.`, node) throw new InterpreterRuntimeError(`Date.${name} is not available.`, node)
} }
} }
@ -170,7 +170,7 @@ export const invokeDateMethod = (
if (args.length < 3) return updateDate(value, hosted.setUTCFullYear(args[0], args[1])) if (args.length < 3) return updateDate(value, hosted.setUTCFullYear(args[0], args[1]))
return updateDate(value, hosted.setUTCFullYear(args[0], args[1], args[2])) return updateDate(value, hosted.setUTCFullYear(args[0], args[1], args[2]))
default: default:
throw new InterpreterRuntimeError(`Date method '${name}' is not available in CodeMode.`, node) throw new InterpreterRuntimeError(`Date method '${name}' is not available.`, node)
} }
} }

View file

@ -51,7 +51,7 @@ export const mathMethods = new Set([
]) ])
export const invokeMathMethod = (name: string, args: Array<unknown>, node: AstNode): number => { export const invokeMathMethod = (name: string, args: Array<unknown>, node: AstNode): number => {
if (!mathMethods.has(name)) throw new InterpreterRuntimeError(`Math.${name} is not available in CodeMode.`, node) if (!mathMethods.has(name)) throw new InterpreterRuntimeError(`Math.${name} is not available.`, node)
if (name === "random") return Math.random() if (name === "random") return Math.random()
if (name === "sumPrecise") { if (name === "sumPrecise") {
const items = spreadItems(args[0]) const items = spreadItems(args[0])
@ -151,5 +151,5 @@ export const invokeMathMethod = (name: string, args: Array<unknown>, node: AstNo
case "imul": case "imul":
return Math.imul(a, b()) return Math.imul(a, b())
} }
throw new InterpreterRuntimeError(`Math.${name} is not available in CodeMode.`, node) throw new InterpreterRuntimeError(`Math.${name} is not available.`, node)
} }

View file

@ -45,7 +45,7 @@ export const invokeNumberMethod = (value: number, name: string, args: Array<unkn
result = value result = value
break break
default: default:
throw new InterpreterRuntimeError(`Number method '${name}' is not available in CodeMode.`, node) throw new InterpreterRuntimeError(`Number method '${name}' is not available.`, node)
} }
return boundedData(result, `Number.${name} result`) return boundedData(result, `Number.${name} result`)
} }
@ -71,7 +71,7 @@ export const invokeNumberStatic = (name: string, args: Array<unknown>, node: Ast
case "parseFloat": case "parseFloat":
return parseFloat(coerceToString(value)) return parseFloat(coerceToString(value))
default: default:
throw new InterpreterRuntimeError(`Number.${name} is not available in CodeMode.`, node) throw new InterpreterRuntimeError(`Number.${name} is not available.`, node)
} }
} }
import { type AstNode, InterpreterRuntimeError } from "../interpreter/model.js" import { type AstNode, InterpreterRuntimeError } from "../interpreter/model.js"

View file

@ -30,7 +30,7 @@ export const invokeObjectMethod = (name: string, args: Array<unknown>, node: Ast
return input as Record<string, unknown> return input as Record<string, unknown>
} }
const guardedSet = (out: Record<string, unknown>, key: string, item: unknown): void => { 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) if (isBlockedMember(key)) throw new InterpreterRuntimeError(`Property '${key}' is not available.`, node)
out[key] = item out[key] = item
} }
const addEntry = (out: Record<string, unknown>, key: unknown, item: unknown): void => { const addEntry = (out: Record<string, unknown>, key: unknown, item: unknown): void => {
@ -49,7 +49,7 @@ export const invokeObjectMethod = (name: string, args: Array<unknown>, node: Ast
return Object.hasOwn(requireObject(), String(args[1])) return Object.hasOwn(requireObject(), String(args[1]))
case "is": case "is":
if (containsOpaqueReference(args[0]) || containsOpaqueReference(args[1])) { if (containsOpaqueReference(args[0]) || containsOpaqueReference(args[1])) {
throw new InterpreterRuntimeError("Object.is requires data values in CodeMode.", node, "InvalidDataValue") throw new InterpreterRuntimeError("Object.is requires data values.", node, "InvalidDataValue")
} }
return Object.is(args[0], args[1]) return Object.is(args[0], args[1])
case "assign": { case "assign": {
@ -94,5 +94,5 @@ export const invokeObjectMethod = (name: string, args: Array<unknown>, node: Ast
return out return out
} }
} }
throw new InterpreterRuntimeError(`Object.${name} is not available in CodeMode.`, node) throw new InterpreterRuntimeError(`Object.${name} is not available.`, node)
} }

View file

@ -72,7 +72,7 @@ export const matchToValue = (match: RegExpMatchArray): Array<unknown> => {
} }
export const invokeRegExpStatic = (name: string, args: Array<unknown>, node: AstNode): string => { export const invokeRegExpStatic = (name: string, args: Array<unknown>, node: AstNode): string => {
if (name !== "escape") throw new InterpreterRuntimeError(`RegExp.${name} is not available in CodeMode.`, node) if (name !== "escape") throw new InterpreterRuntimeError(`RegExp.${name} is not available.`, node)
if (typeof args[0] !== "string") { if (typeof args[0] !== "string") {
throw new InterpreterRuntimeError("RegExp.escape expects a string.", node).as("TypeError") throw new InterpreterRuntimeError("RegExp.escape expects a string.", node).as("TypeError")
} }
@ -104,7 +104,7 @@ export const invokeRegExpMethod = (
case "toString": case "toString":
return coerceToString(value) return coerceToString(value)
default: default:
throw new InterpreterRuntimeError(`RegExp method '${name}' is not available in CodeMode.`, node) throw new InterpreterRuntimeError(`RegExp method '${name}' is not available.`, node)
} }
} }

View file

@ -43,7 +43,7 @@ export const invokeStringStatic = (name: string, args: Array<unknown>, node: Ast
case "fromCodePoint": case "fromCodePoint":
return String.fromCodePoint(...codes) return String.fromCodePoint(...codes)
default: default:
throw new InterpreterRuntimeError(`String.${name} is not available in CodeMode.`, node) throw new InterpreterRuntimeError(`String.${name} is not available.`, node)
} }
} }
import { type AstNode, InterpreterRuntimeError } from "../interpreter/model.js" import { type AstNode, InterpreterRuntimeError } from "../interpreter/model.js"

View file

@ -69,7 +69,7 @@ export const urlArgument = (value: unknown, label: string): string =>
value instanceof CodeModeURL ? value.url.href : uriArgument(value, label) value instanceof CodeModeURL ? value.url.href : uriArgument(value, label)
export const invokeURLStatic = (name: string, args: Array<unknown>, node: AstNode): unknown => { export const invokeURLStatic = (name: string, args: Array<unknown>, node: AstNode): unknown => {
if (!urlStatics.has(name)) throw new InterpreterRuntimeError(`URL.${name} is not available in CodeMode.`, node) if (!urlStatics.has(name)) throw new InterpreterRuntimeError(`URL.${name} is not available.`, node)
if (args.length === 0) throw new InterpreterRuntimeError(`URL.${name} requires a URL argument.`, node).as("TypeError") if (args.length === 0) throw new InterpreterRuntimeError(`URL.${name} requires a URL argument.`, node).as("TypeError")
const input = urlArgument(args[0], `URL.${name} input`) const input = urlArgument(args[0], `URL.${name} input`)
const base = args[1] === undefined ? undefined : urlArgument(args[1], `URL.${name} base`) const base = args[1] === undefined ? undefined : urlArgument(args[1], `URL.${name} base`)
@ -83,7 +83,7 @@ export const invokeURLStatic = (name: string, args: Array<unknown>, node: AstNod
export const invokeURLMethod = (value: CodeModeURL, name: string, node: AstNode): string => { export const invokeURLMethod = (value: CodeModeURL, name: string, node: AstNode): string => {
if (name === "toString" || name === "toJSON") return value.url.href if (name === "toString" || name === "toJSON") return value.url.href
throw new InterpreterRuntimeError(`URL method '${name}' is not available in CodeMode.`, node) throw new InterpreterRuntimeError(`URL method '${name}' is not available.`, node)
} }
import { type AstNode, InterpreterRuntimeError, UriFunction } from "../interpreter/model.js" import { type AstNode, InterpreterRuntimeError, UriFunction } from "../interpreter/model.js"
import { CodeModeURL } from "../values.js" import { CodeModeURL } from "../values.js"

View file

@ -363,7 +363,7 @@ const termForms = (term: string): Array<string> => {
const makeSearchTool = (searchIndex: ReadonlyArray<SearchEntry>): Definition => ({ const makeSearchTool = (searchIndex: ReadonlyArray<SearchEntry>): Definition => ({
_tag: "CodeModeTool", _tag: "CodeModeTool",
description: "Search available Code Mode tools", description: "Search available tools",
input: SearchInput, input: SearchInput,
output: SearchOutput, output: SearchOutput,
run: (input) => run: (input) =>
@ -501,8 +501,8 @@ export const prepare = <R>(tools: Tools<R>, catalogBudget = defaultCatalogBudget
empty empty
? "This is a restricted JavaScript language for calling tools, not a general-purpose runtime." ? "This is a restricted JavaScript language for calling tools, not a general-purpose runtime."
: complete : complete
? "This is a restricted JavaScript language for calling tools, not a general-purpose runtime. Inside the confined interpreter, `tools` contains the Code Mode tools listed below; surrounding agent tools are not available." ? "This is a restricted JavaScript language for calling tools, not a general-purpose runtime. Inside the confined interpreter, `tools` contains the tools listed below; surrounding agent tools are not available."
: "This is a restricted JavaScript language for calling tools, not a general-purpose runtime. Inside the confined interpreter, `tools` contains the Code Mode tools listed or searchable below; surrounding agent tools are not available.", : "This is a restricted JavaScript language for calling tools, not a general-purpose runtime. Inside the confined interpreter, `tools` contains the tools listed or searchable below; surrounding agent tools are not available.",
...(empty ...(empty
? [] ? []
: ["Do not infer or normalize tool names; use only exact signatures shown below or returned by search."]), : ["Do not infer or normalize tool names; use only exact signatures shown below or returned by search."]),
@ -533,8 +533,8 @@ export const prepare = <R>(tools: Tools<R>, catalogBudget = defaultCatalogBudget
"## Rules", "## Rules",
"", "",
complete complete
? "- Only Code Mode tools listed here are available; surrounding agent tools are not implicitly exposed." ? "- Only tools listed here are available; surrounding agent tools are not implicitly exposed."
: "- Only Code Mode tools listed here or returned by the built-in `search` function are available; surrounding agent tools are not implicitly exposed.", : "- Only tools listed here or returned by the built-in `search` function are available; surrounding agent tools are not implicitly exposed.",
"- Filter, aggregate, and transform collections in code - never return them raw or call a tool per item across messages.", "- Filter, aggregate, and transform collections in code - never return them raw or call a tool per item across messages.",
"- A result typed `Promise<unknown>` may be structured data or text. Before reading fields, check that it is a non-null object and not an array; otherwise handle the returned text or primitive directly.", "- A result typed `Promise<unknown>` may be structured data or text. Before reading fields, check that it is a non-null object and not an array; otherwise handle the returned text or primitive directly.",
'- Run independent calls in parallel: `await Promise.all(items.map((item) => tools.<namespace>.<tool>(item)))`, or use `tools.<namespace>["tool-name"](item)` when the listed signature uses bracket notation.', '- Run independent calls in parallel: `await Promise.all(items.map((item) => tools.<namespace>.<tool>(item)))`, or use `tools.<namespace>["tool-name"](item)` when the listed signature uses bracket notation.',
@ -553,7 +553,7 @@ export const prepare = <R>(tools: Tools<R>, catalogBudget = defaultCatalogBudget
"## Language", "## Language",
"", "",
"Use common JavaScript data operations, functions, control flow, selected standard-library methods, and awaited tool calls. Built-ins include Date, RegExp, Map, Set, URL, URLSearchParams, and URI encoding helpers.", "Use common JavaScript data operations, functions, control flow, selected standard-library methods, and awaited tool calls. Built-ins include Date, RegExp, Map, Set, URL, URLSearchParams, and URI encoding helpers.",
"Modules/imports, classes, generators, timers, fetch, eval, prototype access, and unlisted methods are unavailable. Use Code Mode tools for external operations. Use await with try/catch.", "Modules/imports, classes, generators, timers, fetch, eval, prototype access, and unlisted methods are unavailable. Use tools for external operations. Use await with try/catch.",
"Prefer explicit `return`; otherwise only the final top-level expression becomes the result.", "Prefer explicit `return`; otherwise only the final top-level expression becomes the result.",
"Dates and URLs serialize to strings at data boundaries; Map/Set/RegExp/URLSearchParams serialize to `{}`.", "Dates and URLs serialize to strings at data boundaries; Map/Set/RegExp/URLSearchParams serialize to `{}`.",
] ]

View file

@ -308,7 +308,7 @@ describe("CodeMode console capture", () => {
) )
expect(result.ok).toBe(true) expect(result.ok).toBe(true)
expect(result.logs).toStrictEqual(['{"box":Map(1) [["self",[Circular]]]}', '{"fn":[CodeMode reference],"ok":1}']) expect(result.logs).toStrictEqual(['{"box":Map(1) [["self",[Circular]]]}', '{"fn":[opaque reference],"ok":1}'])
}) })
test("console.table renders CodeMode value cells", async () => { test("console.table renders CodeMode value cells", async () => {
@ -696,7 +696,7 @@ describe("CodeMode public contract", () => {
expect(instructions).toContain("Do not infer or normalize tool names") expect(instructions).toContain("Do not infer or normalize tool names")
expect(instructions).toContain("bracket notation and quotes are part of the path") expect(instructions).toContain("bracket notation and quotes are part of the path")
expect(instructions).toContain("surrounding agent tools are not available") expect(instructions).toContain("surrounding agent tools are not available")
expect(instructions).toContain("Only Code Mode tools listed here are available") expect(instructions).toContain("Only tools listed here are available")
// Placeholders use generic namespace/tool/field names only - no fabricated real tools // Placeholders use generic namespace/tool/field names only - no fabricated real tools
// and no real catalog tools cherry-picked into example lines. // and no real catalog tools cherry-picked into example lines.
expect(instructions).toContain("`const result = await tools.<namespace>.<tool>(input)`") expect(instructions).toContain("`const result = await tools.<namespace>.<tool>(input)`")
@ -718,7 +718,7 @@ describe("CodeMode public contract", () => {
'1. If needed, discover tools with the built-in search function: `return search({ query: "<intent + key nouns>" })`.', '1. If needed, discover tools with the built-in search function: `return search({ query: "<intent + key nouns>" })`.',
) )
expect(partial).toContain("In the next execution, copy a returned path exactly") expect(partial).toContain("In the next execution, copy a returned path exactly")
expect(partial).toContain("Only Code Mode tools listed here or returned by the built-in `search` function") expect(partial).toContain("Only tools listed here or returned by the built-in `search` function")
expect(partial).toContain('- Browse one namespace: `search({ query: "", namespace: "<name>" })`.') expect(partial).toContain('- Browse one namespace: `search({ query: "", namespace: "<name>" })`.')
expect(partial).toContain("repeat the same search with `offset: next.offset`") expect(partial).toContain("repeat the same search with `offset: next.offset`")
expect(partial).toContain(" limit?: number,\n offset?: number,") expect(partial).toContain(" limit?: number,\n offset?: number,")
@ -739,7 +739,7 @@ describe("CodeMode public contract", () => {
expect(instructions).not.toContain("promise chaining") expect(instructions).not.toContain("promise chaining")
expect(instructions).toContain("URL, URLSearchParams, and URI encoding helpers") expect(instructions).toContain("URL, URLSearchParams, and URI encoding helpers")
expect(instructions).not.toContain("host globals") expect(instructions).not.toContain("host globals")
expect(instructions).toContain("Use Code Mode tools for external operations") expect(instructions).toContain("Use tools for external operations")
expect(instructions).toContain( expect(instructions).toContain(
"Prefer explicit `return`; otherwise only the final top-level expression becomes the result.", "Prefer explicit `return`; otherwise only the final top-level expression becomes the result.",
) )