mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-10 08:38:30 +00:00
201 lines
5.8 KiB
TypeScript
201 lines
5.8 KiB
TypeScript
import type { SafeObject } from "../tool-runtime.js"
|
|
import type { SandboxURL } from "../values.js"
|
|
|
|
export type SourcePosition = {
|
|
line: number
|
|
column: number
|
|
}
|
|
|
|
export type SourceLocation = {
|
|
start: SourcePosition
|
|
end: SourcePosition
|
|
}
|
|
|
|
export type AstNode = {
|
|
type: string
|
|
loc?: SourceLocation
|
|
[key: string]: unknown
|
|
}
|
|
|
|
export type ProgramNode = AstNode & {
|
|
type: "Program"
|
|
body: Array<AstNode>
|
|
}
|
|
|
|
export type Binding = {
|
|
mutable: boolean
|
|
value: unknown
|
|
initialized?: boolean
|
|
}
|
|
|
|
export type StatementResult =
|
|
| { kind: "none" }
|
|
| { kind: "return"; value: unknown }
|
|
| { kind: "break" }
|
|
| { kind: "continue" }
|
|
|
|
export type MemberReference = {
|
|
target: SafeObject | Array<unknown> | SandboxURL
|
|
key: string | number
|
|
}
|
|
|
|
export class CodeModeFunction {
|
|
constructor(
|
|
readonly parameters: ReadonlyArray<AstNode>,
|
|
readonly body: AstNode,
|
|
readonly capturedScopes: ReadonlyArray<Map<string, Binding>>,
|
|
readonly async: boolean,
|
|
) {}
|
|
}
|
|
|
|
export class IntrinsicReference {
|
|
constructor(
|
|
readonly receiver: unknown,
|
|
readonly name: string,
|
|
) {}
|
|
}
|
|
|
|
export class ComputedValue {
|
|
constructor(readonly value: unknown) {}
|
|
}
|
|
|
|
export class PromiseNamespace {}
|
|
|
|
export type PromiseMethodName = "all" | "allSettled" | "race" | "resolve" | "reject"
|
|
|
|
export class PromiseMethodReference {
|
|
constructor(readonly name: PromiseMethodName) {}
|
|
}
|
|
|
|
export type GlobalNamespaceName =
|
|
| "Object"
|
|
| "Math"
|
|
| "JSON"
|
|
| "Array"
|
|
| "console"
|
|
| "Date"
|
|
| "RegExp"
|
|
| "Map"
|
|
| "Set"
|
|
| "URL"
|
|
| "URLSearchParams"
|
|
|
|
export class GlobalNamespace {
|
|
constructor(readonly name: GlobalNamespaceName) {}
|
|
}
|
|
|
|
export class GlobalMethodReference {
|
|
constructor(
|
|
readonly namespace: GlobalNamespaceName | "Number" | "String",
|
|
readonly name: string,
|
|
) {}
|
|
}
|
|
|
|
export class CoercionFunction {
|
|
constructor(readonly name: "Number" | "String" | "Boolean" | "parseInt" | "parseFloat") {}
|
|
}
|
|
|
|
export class UriFunction {
|
|
constructor(readonly name: "encodeURI" | "encodeURIComponent" | "decodeURI" | "decodeURIComponent") {}
|
|
}
|
|
|
|
export class ProgramThrow {
|
|
constructor(readonly value: unknown) {}
|
|
}
|
|
|
|
export class ErrorConstructorReference {
|
|
constructor(readonly name: string) {}
|
|
}
|
|
|
|
export type DiagnosticKind =
|
|
| "ParseError"
|
|
| "UnsupportedSyntax"
|
|
| "UnknownTool"
|
|
| "InvalidToolInput"
|
|
| "InvalidToolOutput"
|
|
| "InvalidDataValue"
|
|
| "ToolCallLimitExceeded"
|
|
| "TimeoutExceeded"
|
|
| "ToolFailure"
|
|
| "ExecutionFailure"
|
|
|
|
export const OptionalShortCircuit: unique symbol = Symbol("codemode.optional-short-circuit")
|
|
|
|
export 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/URL/URLSearchParams, URI encoding helpers, 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)."
|
|
|
|
export class InterpreterRuntimeError extends Error {
|
|
readonly node?: AstNode
|
|
errorName: string = "Error"
|
|
|
|
constructor(
|
|
message: string,
|
|
node?: AstNode,
|
|
readonly kind: DiagnosticKind = "ExecutionFailure",
|
|
readonly suggestions?: ReadonlyArray<string>,
|
|
) {
|
|
super(message)
|
|
this.name = "InterpreterRuntimeError"
|
|
if (node) this.node = node
|
|
}
|
|
|
|
as(errorName: string): this {
|
|
this.errorName = errorName
|
|
return this
|
|
}
|
|
}
|
|
|
|
export const unsupportedSyntax = (kind: string, node: AstNode): InterpreterRuntimeError =>
|
|
new InterpreterRuntimeError(
|
|
`Syntax '${kind}' is not supported in CodeMode. ${supportedSyntaxMessage}`,
|
|
node,
|
|
"UnsupportedSyntax",
|
|
[supportedSyntaxMessage],
|
|
)
|
|
|
|
export const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
typeof value === "object" && value !== null
|
|
|
|
export 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
|
|
}
|
|
|
|
export 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
|
|
}
|
|
|
|
export 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
|
|
}
|
|
|
|
export 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
|
|
}
|
|
|
|
export const getOptionalNode = (node: AstNode, key: string): AstNode | undefined => {
|
|
const value = node[key]
|
|
if (value === undefined || value === null) return undefined
|
|
return asNode(value, key)
|
|
}
|
|
|
|
export const getNode = (node: AstNode, key: string): AstNode => asNode(node[key], key)
|
|
|
|
export 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),
|
|
})
|
|
|
|
export const formatLocation = (node?: AstNode): string => {
|
|
if (!node?.loc) return ""
|
|
const location = sourceLocation(node)
|
|
return ` (line ${location.line}, col ${location.column})`
|
|
}
|