Something went wrong. Try again.
opencode v2 mirror
Something went wrong. Try again.
7.0 kB · 193 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194import { parse, type Program } from "acorn"import { Cause, Effect, Scope } from "effect"import type { DataValue, Diagnostic, ResolvedExecutionLimits, Result } from "../codemode.js"import { toBoundary } from "../data.js"import { ToolRuntime } from "../tool-runtime.js"import { normalizeError } from "./errors.js"import type { Value } from "./objects.js"import { createBuiltins } from "./intrinsics.js"import { Pending } from "./promises.js"import { Interpreter } from "./interpreter.js"
export const executeProgram = <R>( code: string, prepared: ToolRuntime.Prepared<R>, limits: ResolvedExecutionLimits, hooks: ToolRuntime.Hooks<R>, globals?: (ctx: Interpreter<R>) => ReadonlyArray<readonly [string, Value]>,): Effect.Effect<Result, never, R> => { if (code.trim().length === 0) { return Effect.succeed({ ok: false, error: { kind: "ParseError", message: "Code cannot be empty." }, toolCalls: [], }) }
// Allocate execution state inside suspension so reused Effects never share it. return Effect.suspend(() => { const builtins = createBuiltins() const tools = ToolRuntime.make(prepared, limits.maxToolCalls, hooks) const logs: Array<string> = [] const logged = () => (logs.length > 0 ? { logs: [...logs] } : {}) // Set only after copy-out so timeouts cannot report invalid values as completed. let returned: { value: DataValue; pending: Pending<R> } | undefined
const base = Effect.acquireUseRelease( Scope.make("parallel"), (scope) => Effect.gen(function* () { const program = parseProgram(code) const pending = new Pending<R>(scope, builtins.Promise) const ctx = new Interpreter<R>({ tools, pending, builtins, logs, globals }) const result = (yield* toBoundary(ctx, yield* ctx.run(program))) ?? null returned = { value: result, pending } const warnings = yield* pending.interrupt() return { ok: true, value: result, ...(warnings.length > 0 ? { warnings } : {}), ...logged(), toolCalls: tools.calls, } satisfies Result }), (scope, exit) => Scope.close(scope, exit), ) const timeoutMs = limits.timeoutMs const operation = timeoutMs === undefined ? base : base.pipe( Effect.timeoutOrElse({ duration: timeoutMs, orElse: () => Effect.sync(() => { if (returned === undefined) { return { ok: false, error: { kind: "TimeoutExceeded", message: `Execution timed out after ${timeoutMs}ms.` }, ...logged(), toolCalls: tools.calls, } satisfies Result } // Keep the timeout warning first so truncation preserves it. return { ok: true, value: returned.value, warnings: [ { kind: "TimeoutExceeded", message: `The program returned, but background work was still running at the ${timeoutMs}ms timeout and was interrupted. Await all started promises.`, }, ...returned.pending.diagnostics(), ], ...logged(), toolCalls: tools.calls, } satisfies Result }), }), )
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 Result), ), Effect.map((result) => limits.maxOutputBytes === undefined ? result : boundOutput(result, limits.maxOutputBytes), ), ) })}
const parseProgram = (code: string): Program => { return parse(code, { ecmaVersion: "latest", sourceType: "script", allowReturnOutsideFunction: true, allowAwaitOutsideFunction: true, locations: true, })}
const utf8ByteLength = (value: string): number => new TextEncoder().encode(value).byteLength
// Drop a replacement character produced by truncating inside a UTF-8 sequence.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}
// Warnings have a separate budget so result data cannot starve diagnostics.const boundOutput = (result: Result, maxOutputBytes: number): Result => { 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 warnings = result.ok ? (result.warnings ?? []) : [] const keptWarnings: Array<Diagnostic> = [] let warningBytes = 0 for (const warning of warnings) { const bytes = utf8ByteLength(JSON.stringify(warning)) + 1 if (warningBytes + bytes > maxOutputBytes) break warningBytes += bytes keptWarnings.push(warning) } if (keptWarnings.length < warnings.length) { truncated = true keptWarnings.push({ kind: "Truncated", message: `${warnings.length - keptWarnings.length} additional warnings omitted by the output limit.`, }) }
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 warningsPart = keptWarnings.length > 0 ? { warnings: keptWarnings } : {} const logsPart = kept.length > 0 ? { logs: kept } : {} return result.ok ? { ok: true, value, ...warningsPart, ...logsPart, truncated: true, toolCalls: result.toolCalls, } : { ok: false, error: result.error, ...logsPart, truncated: true, toolCalls: result.toolCalls }}