import { Effect } from "effect" import type { Diagnostic } from "../codemode.js" import { ToolError } from "../tool-error.js" import { copyOut, ToolRuntimeError, type SafeObject } from "../tool-runtime.js" import { type AstNode, formatLocation, InterpreterRuntimeError, ProgramThrow, sourceLocation } from "./model.js" import { containsRuntimeReference } from "./references.js" import { type SyncIteratorRunner } from "./iterator.js" import { coerceToString, createAggregateErrorValue, createErrorValue, errorConstructors } from "../stdlib/value.js" export 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: error.message } } if (error instanceof ProgramThrow) { const value = error.value let message: string if (containsRuntimeReference(value)) { // Never expose runtime reference internals through thrown values. message = "a non-data value" } else if (typeof value === "string") { message = value } else if ( value !== null && typeof value === "object" && typeof (value as { message?: unknown }).message === "string" ) { message = (value as { message: string }).message } else { try { message = JSON.stringify(copyOut(value, "json")) ?? 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: error.message, } } return { kind: "ExecutionFailure", message: String(error), } } export 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) } export const constructErrorValue = (name: string, args: Array): SafeObject => createErrorValue(name, args[0] === undefined ? "" : coerceToString(args[0])) export const constructAggregateErrorValue = ( runner: SyncIteratorRunner, args: Array, node: AstNode, ): Effect.Effect => Effect.gen(function* () { const cursor = yield* runner.syncIterator(args[0], node) if (cursor === undefined) { throw new InterpreterRuntimeError("new AggregateError(...) expects a synchronous iterable of errors.", node).as( "TypeError", ) } const errors: Array = [] while (true) { const step = yield* cursor.next if (step.done) { return createAggregateErrorValue(errors, args[1] === undefined ? "" : coerceToString(args[1])) } errors.push(step.value) } })