@neurocode-ai/codemode 1.18.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +23 -0
- package/README.md +369 -0
- package/codemode.md +176 -0
- package/package.json +25 -0
- package/src/codemode.ts +159 -0
- package/src/index.ts +4 -0
- package/src/interpreter/model.ts +201 -0
- package/src/interpreter/runtime.ts +3465 -0
- package/src/openapi/TODO.md +19 -0
- package/src/openapi/index.ts +130 -0
- package/src/openapi/runtime.ts +326 -0
- package/src/openapi/spec.ts +511 -0
- package/src/openapi/types.ts +112 -0
- package/src/stdlib/collections.ts +51 -0
- package/src/stdlib/console.ts +4 -0
- package/src/stdlib/date.ts +94 -0
- package/src/stdlib/json.ts +42 -0
- package/src/stdlib/math.ts +65 -0
- package/src/stdlib/number.ts +66 -0
- package/src/stdlib/object.ts +77 -0
- package/src/stdlib/promise.ts +6 -0
- package/src/stdlib/regexp.ts +74 -0
- package/src/stdlib/string.ts +52 -0
- package/src/stdlib/url.ts +90 -0
- package/src/stdlib/value.ts +86 -0
- package/src/tool-error.ts +11 -0
- package/src/tool-runtime.ts +806 -0
- package/src/tool-schema.ts +301 -0
- package/src/tool.ts +96 -0
- package/src/values.ts +49 -0
- package/sst-env.d.ts +10 -0
- package/test/codemode.test.ts +1163 -0
- package/test/enumeration.test.ts +159 -0
- package/test/fixtures/openapi-happy-path.json +230 -0
- package/test/fixtures/opencode-v2-openapi.json +23730 -0
- package/test/openapi.test.ts +964 -0
- package/test/parity.test.ts +425 -0
- package/test/promise.test.ts +456 -0
- package/test/signature.test.ts +449 -0
- package/test/stdlib.test.ts +715 -0
- package/tsconfig.json +7 -0
package/src/codemode.ts
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect"
|
|
2
|
+
import { executeWithLimits } from "./interpreter/runtime.js"
|
|
3
|
+
import { type HostTools, type Services, type ToolDescription, ToolRuntime } from "./tool-runtime.js"
|
|
4
|
+
import type { Definition } from "./tool.js"
|
|
5
|
+
|
|
6
|
+
/** A tool call admitted during an execution. */
|
|
7
|
+
export type { ToolCall, ToolCallEnded, ToolCallHooks, ToolCallStarted, ToolDescription } from "./tool-runtime.js"
|
|
8
|
+
|
|
9
|
+
/** Resource budgets enforced independently during each CodeMode program execution. */
|
|
10
|
+
export type ExecutionLimits = {
|
|
11
|
+
/** Maximum wall-clock execution time in milliseconds. No default: absent means no timeout. */
|
|
12
|
+
readonly timeoutMs?: number
|
|
13
|
+
/** Maximum number of tool calls admitted by the runtime. No default: absent means unlimited. */
|
|
14
|
+
readonly maxToolCalls?: number
|
|
15
|
+
/** Maximum UTF-8 bytes of model-facing output. No default: absent means no truncation. */
|
|
16
|
+
readonly maxOutputBytes?: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Controls how much of the tool catalog is inlined in agent instructions. */
|
|
20
|
+
export type DiscoveryOptions = {
|
|
21
|
+
/** Approximate token budget (chars/4, default 2000) for full catalog entries. */
|
|
22
|
+
readonly catalogBudget?: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type ToolTree<R = never> = {
|
|
26
|
+
readonly [name: string]: Definition<R> | ToolTree<R>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type ResolvedExecutionLimits = {
|
|
30
|
+
readonly timeoutMs: number | undefined
|
|
31
|
+
readonly maxToolCalls: number | undefined
|
|
32
|
+
readonly maxOutputBytes: number | undefined
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Options for one CodeMode execution. */
|
|
36
|
+
export type ExecuteOptions<Tools extends Record<string, unknown> = {}> = {
|
|
37
|
+
/** Source for one program in the supported JavaScript subset. */
|
|
38
|
+
code: string
|
|
39
|
+
/** Explicit tool tree exposed to the program as `tools`. */
|
|
40
|
+
tools?: Tools & ToolTree<Services<Tools>>
|
|
41
|
+
/** Per-execution overrides for the default resource limits. */
|
|
42
|
+
limits?: ExecutionLimits
|
|
43
|
+
/** Observes decoded tool input immediately before tool execution. */
|
|
44
|
+
onToolCallStart?: (call: ToolRuntime.ToolCallStarted) => Effect.Effect<void, never, Services<Tools>>
|
|
45
|
+
/** Observes each admitted tool call as it settles, with outcome and duration. */
|
|
46
|
+
onToolCallEnd?: (call: ToolRuntime.ToolCallEnded) => Effect.Effect<void, never, Services<Tools>>
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** A JSON value that can cross the confined interpreter boundary. */
|
|
50
|
+
export type DataValue = Schema.Json
|
|
51
|
+
|
|
52
|
+
/** Configuration shared by `CodeMode.make` and `CodeMode.execute`. */
|
|
53
|
+
export type Options<Tools extends Record<string, unknown> = {}> = Omit<ExecuteOptions<Tools>, "code"> & {
|
|
54
|
+
/** Progressive-disclosure configuration for the agent-facing tool catalog. */
|
|
55
|
+
readonly discovery?: DiscoveryOptions
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Schema for a host tool input containing CodeMode source. */
|
|
59
|
+
export const Input = Schema.Struct({ code: Schema.String })
|
|
60
|
+
export type Input = typeof Input.Type
|
|
61
|
+
|
|
62
|
+
export const DiagnosticKind = Schema.Literals([
|
|
63
|
+
"ParseError",
|
|
64
|
+
"UnsupportedSyntax",
|
|
65
|
+
"UnknownTool",
|
|
66
|
+
"InvalidToolInput",
|
|
67
|
+
"InvalidToolOutput",
|
|
68
|
+
"InvalidDataValue",
|
|
69
|
+
"ToolCallLimitExceeded",
|
|
70
|
+
"TimeoutExceeded",
|
|
71
|
+
"ToolFailure",
|
|
72
|
+
"ExecutionFailure",
|
|
73
|
+
])
|
|
74
|
+
/** Stable categories produced by program, schema, tool, and limit failures. */
|
|
75
|
+
export type DiagnosticKind = typeof DiagnosticKind.Type
|
|
76
|
+
|
|
77
|
+
export const Diagnostic = Schema.Struct({
|
|
78
|
+
kind: DiagnosticKind,
|
|
79
|
+
message: Schema.String,
|
|
80
|
+
location: Schema.optionalKey(Schema.Struct({ line: Schema.Number, column: Schema.Number })),
|
|
81
|
+
suggestions: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
82
|
+
})
|
|
83
|
+
/** A normalized program diagnostic safe to return across an agent tool boundary. */
|
|
84
|
+
export type Diagnostic = typeof Diagnostic.Type
|
|
85
|
+
|
|
86
|
+
const ToolCallSchema = Schema.Struct({ name: Schema.String })
|
|
87
|
+
export const Success = Schema.Struct({
|
|
88
|
+
ok: Schema.Literal(true),
|
|
89
|
+
value: Schema.Json,
|
|
90
|
+
logs: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
91
|
+
truncated: Schema.optionalKey(Schema.Boolean),
|
|
92
|
+
toolCalls: Schema.Array(ToolCallSchema),
|
|
93
|
+
})
|
|
94
|
+
/** Successful execution after the result has crossed the plain-data boundary. */
|
|
95
|
+
export type Success = typeof Success.Type
|
|
96
|
+
|
|
97
|
+
export const Failure = Schema.Struct({
|
|
98
|
+
ok: Schema.Literal(false),
|
|
99
|
+
error: Diagnostic,
|
|
100
|
+
logs: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
101
|
+
truncated: Schema.optionalKey(Schema.Boolean),
|
|
102
|
+
toolCalls: Schema.Array(ToolCallSchema),
|
|
103
|
+
})
|
|
104
|
+
/** Failed execution with calls admitted before the diagnostic was produced. */
|
|
105
|
+
export type Failure = typeof Failure.Type
|
|
106
|
+
|
|
107
|
+
/** Schema for the structured success or diagnostic returned by CodeMode execution. */
|
|
108
|
+
export const Result = Schema.Union([Success, Failure])
|
|
109
|
+
/** Result of executing a CodeMode program. Program failures are data, not Effect failures. */
|
|
110
|
+
export type Result = typeof Result.Type
|
|
111
|
+
|
|
112
|
+
/** Reusable confined runtime over one explicit tool tree. */
|
|
113
|
+
export type Runtime<R = never> = {
|
|
114
|
+
readonly catalog: () => ReadonlyArray<ToolDescription>
|
|
115
|
+
readonly instructions: () => string
|
|
116
|
+
readonly execute: (code: string) => Effect.Effect<Result, never, R>
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const validateLimit = <Value extends number | undefined>(
|
|
120
|
+
name: keyof ExecutionLimits,
|
|
121
|
+
value: Value,
|
|
122
|
+
minimum: number,
|
|
123
|
+
): Value => {
|
|
124
|
+
if (value !== undefined && (!Number.isSafeInteger(value) || value < minimum)) {
|
|
125
|
+
throw new RangeError(`${name} must be a safe integer greater than or equal to ${minimum}.`)
|
|
126
|
+
}
|
|
127
|
+
return value
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const resolveExecutionLimits = (limits?: ExecutionLimits): ResolvedExecutionLimits => ({
|
|
131
|
+
timeoutMs: validateLimit("timeoutMs", limits?.timeoutMs, 1),
|
|
132
|
+
maxToolCalls: validateLimit("maxToolCalls", limits?.maxToolCalls, 0),
|
|
133
|
+
maxOutputBytes: validateLimit("maxOutputBytes", limits?.maxOutputBytes, 0),
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
/** Executes one Effect-native CodeMode program without constructing a reusable runtime. */
|
|
137
|
+
export const execute = <const Tools extends Record<string, unknown>>(
|
|
138
|
+
options: ExecuteOptions<Tools>,
|
|
139
|
+
): Effect.Effect<Result, never, Services<Tools>> => {
|
|
140
|
+
const tools = (options.tools ?? {}) as HostTools<Services<Tools>>
|
|
141
|
+
ToolRuntime.assertValidTools(tools)
|
|
142
|
+
return executeWithLimits(options, resolveExecutionLimits(options.limits), ToolRuntime.searchIndex(tools))
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Creates an Effect-native runtime over explicit, schema-described tools. */
|
|
146
|
+
export const make = <const Tools extends Record<string, unknown> = {}>(
|
|
147
|
+
options: Options<Tools> = {} as Options<Tools>,
|
|
148
|
+
): Runtime<Services<Tools>> => {
|
|
149
|
+
const tools = (options.tools ?? {}) as HostTools<Services<Tools>>
|
|
150
|
+
ToolRuntime.assertValidTools(tools)
|
|
151
|
+
const limits = resolveExecutionLimits(options.limits)
|
|
152
|
+
const prepared = ToolRuntime.prepare(tools, options.discovery?.catalogBudget)
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
catalog: () => prepared.catalog,
|
|
156
|
+
instructions: () => prepared.instructions,
|
|
157
|
+
execute: (code) => executeWithLimits<Tools>({ ...options, code }, limits, prepared.searchIndex),
|
|
158
|
+
}
|
|
159
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import type { SafeObject } from "../tool-runtime.js"
|
|
2
|
+
import type { SandboxURL } from "../values.js"
|
|
3
|
+
|
|
4
|
+
export type SourcePosition = {
|
|
5
|
+
line: number
|
|
6
|
+
column: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type SourceLocation = {
|
|
10
|
+
start: SourcePosition
|
|
11
|
+
end: SourcePosition
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type AstNode = {
|
|
15
|
+
type: string
|
|
16
|
+
loc?: SourceLocation
|
|
17
|
+
[key: string]: unknown
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type ProgramNode = AstNode & {
|
|
21
|
+
type: "Program"
|
|
22
|
+
body: Array<AstNode>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type Binding = {
|
|
26
|
+
mutable: boolean
|
|
27
|
+
value: unknown
|
|
28
|
+
initialized?: boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type StatementResult =
|
|
32
|
+
| { kind: "none" }
|
|
33
|
+
| { kind: "value"; value: unknown }
|
|
34
|
+
| { kind: "return"; value: unknown }
|
|
35
|
+
| { kind: "break" }
|
|
36
|
+
| { kind: "continue" }
|
|
37
|
+
|
|
38
|
+
export type MemberReference = {
|
|
39
|
+
target: SafeObject | Array<unknown> | SandboxURL
|
|
40
|
+
key: string | number
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class CodeModeFunction {
|
|
44
|
+
constructor(
|
|
45
|
+
readonly parameters: ReadonlyArray<AstNode>,
|
|
46
|
+
readonly body: AstNode,
|
|
47
|
+
readonly capturedScopes: ReadonlyArray<Map<string, Binding>>,
|
|
48
|
+
) {}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export class IntrinsicReference {
|
|
52
|
+
constructor(
|
|
53
|
+
readonly receiver: unknown,
|
|
54
|
+
readonly name: string,
|
|
55
|
+
) {}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export class ComputedValue {
|
|
59
|
+
constructor(readonly value: unknown) {}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export class PromiseNamespace {}
|
|
63
|
+
|
|
64
|
+
export type PromiseMethodName = "all" | "allSettled" | "race" | "resolve" | "reject"
|
|
65
|
+
|
|
66
|
+
export class PromiseMethodReference {
|
|
67
|
+
constructor(readonly name: PromiseMethodName) {}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export type GlobalNamespaceName =
|
|
71
|
+
| "Object"
|
|
72
|
+
| "Math"
|
|
73
|
+
| "JSON"
|
|
74
|
+
| "Array"
|
|
75
|
+
| "console"
|
|
76
|
+
| "Date"
|
|
77
|
+
| "RegExp"
|
|
78
|
+
| "Map"
|
|
79
|
+
| "Set"
|
|
80
|
+
| "URL"
|
|
81
|
+
| "URLSearchParams"
|
|
82
|
+
|
|
83
|
+
export class GlobalNamespace {
|
|
84
|
+
constructor(readonly name: GlobalNamespaceName) {}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export class GlobalMethodReference {
|
|
88
|
+
constructor(
|
|
89
|
+
readonly namespace: GlobalNamespaceName | "Number" | "String",
|
|
90
|
+
readonly name: string,
|
|
91
|
+
) {}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export class CoercionFunction {
|
|
95
|
+
constructor(readonly name: "Number" | "String" | "Boolean" | "parseInt" | "parseFloat") {}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export class UriFunction {
|
|
99
|
+
constructor(readonly name: "encodeURI" | "encodeURIComponent" | "decodeURI" | "decodeURIComponent") {}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export class ProgramThrow {
|
|
103
|
+
constructor(readonly value: unknown) {}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export class ErrorConstructorReference {
|
|
107
|
+
constructor(readonly name: string) {}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export type DiagnosticKind =
|
|
111
|
+
| "ParseError"
|
|
112
|
+
| "UnsupportedSyntax"
|
|
113
|
+
| "UnknownTool"
|
|
114
|
+
| "InvalidToolInput"
|
|
115
|
+
| "InvalidToolOutput"
|
|
116
|
+
| "InvalidDataValue"
|
|
117
|
+
| "ToolCallLimitExceeded"
|
|
118
|
+
| "TimeoutExceeded"
|
|
119
|
+
| "ToolFailure"
|
|
120
|
+
| "ExecutionFailure"
|
|
121
|
+
|
|
122
|
+
export const OptionalShortCircuit: unique symbol = Symbol("codemode.optional-short-circuit")
|
|
123
|
+
|
|
124
|
+
export const supportedSyntaxMessage =
|
|
125
|
+
"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)."
|
|
126
|
+
|
|
127
|
+
export class InterpreterRuntimeError extends Error {
|
|
128
|
+
readonly node?: AstNode
|
|
129
|
+
errorName: string = "Error"
|
|
130
|
+
|
|
131
|
+
constructor(
|
|
132
|
+
message: string,
|
|
133
|
+
node?: AstNode,
|
|
134
|
+
readonly kind: DiagnosticKind = "ExecutionFailure",
|
|
135
|
+
readonly suggestions?: ReadonlyArray<string>,
|
|
136
|
+
) {
|
|
137
|
+
super(message)
|
|
138
|
+
this.name = "InterpreterRuntimeError"
|
|
139
|
+
if (node) this.node = node
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
as(errorName: string): this {
|
|
143
|
+
this.errorName = errorName
|
|
144
|
+
return this
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export const unsupportedSyntax = (kind: string, node: AstNode): InterpreterRuntimeError =>
|
|
149
|
+
new InterpreterRuntimeError(
|
|
150
|
+
`Syntax '${kind}' is not supported in CodeMode. ${supportedSyntaxMessage}`,
|
|
151
|
+
node,
|
|
152
|
+
"UnsupportedSyntax",
|
|
153
|
+
[supportedSyntaxMessage],
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
export const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
157
|
+
typeof value === "object" && value !== null
|
|
158
|
+
|
|
159
|
+
export const asNode = (value: unknown, context: string): AstNode => {
|
|
160
|
+
if (!isRecord(value) || typeof value.type !== "string") {
|
|
161
|
+
throw new InterpreterRuntimeError(`Invalid AST node while reading ${context}.`)
|
|
162
|
+
}
|
|
163
|
+
return value as AstNode
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export const getArray = (node: AstNode, key: string): Array<unknown> => {
|
|
167
|
+
const value = node[key]
|
|
168
|
+
if (!Array.isArray(value)) throw new InterpreterRuntimeError(`Expected '${key}' to be an array.`, node)
|
|
169
|
+
return value
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export const getString = (node: AstNode, key: string): string => {
|
|
173
|
+
const value = node[key]
|
|
174
|
+
if (typeof value !== "string") throw new InterpreterRuntimeError(`Expected '${key}' to be a string.`, node)
|
|
175
|
+
return value
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export const getBoolean = (node: AstNode, key: string): boolean => {
|
|
179
|
+
const value = node[key]
|
|
180
|
+
if (typeof value !== "boolean") throw new InterpreterRuntimeError(`Expected '${key}' to be a boolean.`, node)
|
|
181
|
+
return value
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export const getOptionalNode = (node: AstNode, key: string): AstNode | undefined => {
|
|
185
|
+
const value = node[key]
|
|
186
|
+
if (value === undefined || value === null) return undefined
|
|
187
|
+
return asNode(value, key)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export const getNode = (node: AstNode, key: string): AstNode => asNode(node[key], key)
|
|
191
|
+
|
|
192
|
+
export const sourceLocation = (node: AstNode): { readonly line: number; readonly column: number } => ({
|
|
193
|
+
line: Math.max(1, (node.loc?.start.line ?? 2) - 1),
|
|
194
|
+
column: Math.max(1, (node.loc?.start.column ?? 4) - 3),
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
export const formatLocation = (node?: AstNode): string => {
|
|
198
|
+
if (!node?.loc) return ""
|
|
199
|
+
const location = sourceLocation(node)
|
|
200
|
+
return ` (line ${location.line}, col ${location.column})`
|
|
201
|
+
}
|