@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
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export const dateMethods = new Set([
|
|
2
|
+
"getTime",
|
|
3
|
+
"valueOf",
|
|
4
|
+
"toISOString",
|
|
5
|
+
"toJSON",
|
|
6
|
+
"toString",
|
|
7
|
+
"getFullYear",
|
|
8
|
+
"getMonth",
|
|
9
|
+
"getDate",
|
|
10
|
+
"getDay",
|
|
11
|
+
"getHours",
|
|
12
|
+
"getMinutes",
|
|
13
|
+
"getSeconds",
|
|
14
|
+
"getMilliseconds",
|
|
15
|
+
"getUTCFullYear",
|
|
16
|
+
"getUTCMonth",
|
|
17
|
+
"getUTCDate",
|
|
18
|
+
"getUTCDay",
|
|
19
|
+
"getUTCHours",
|
|
20
|
+
"getUTCMinutes",
|
|
21
|
+
"getUTCSeconds",
|
|
22
|
+
"getUTCMilliseconds",
|
|
23
|
+
"getTimezoneOffset",
|
|
24
|
+
])
|
|
25
|
+
|
|
26
|
+
export const dateStatics = new Set(["now", "parse", "UTC"])
|
|
27
|
+
|
|
28
|
+
export const invokeDateStatic = (name: string, args: Array<unknown>, node: AstNode): number => {
|
|
29
|
+
switch (name) {
|
|
30
|
+
case "now":
|
|
31
|
+
return Date.now()
|
|
32
|
+
case "parse":
|
|
33
|
+
return Date.parse(coerceToString(args[0]))
|
|
34
|
+
case "UTC":
|
|
35
|
+
return Date.UTC(...(args.map((arg) => coerceToNumber(arg)) as Parameters<typeof Date.UTC>))
|
|
36
|
+
default:
|
|
37
|
+
throw new InterpreterRuntimeError(`Date.${name} is not available in CodeMode.`, node)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const invokeDateMethod = (value: SandboxDate, name: string, node: AstNode): unknown => {
|
|
42
|
+
const hosted = new Date(value.time)
|
|
43
|
+
switch (name) {
|
|
44
|
+
case "getTime":
|
|
45
|
+
case "valueOf":
|
|
46
|
+
return value.time
|
|
47
|
+
case "toISOString":
|
|
48
|
+
if (!Number.isFinite(value.time)) throw new InterpreterRuntimeError("Invalid time value.", node)
|
|
49
|
+
return hosted.toISOString()
|
|
50
|
+
case "toJSON":
|
|
51
|
+
return Number.isFinite(value.time) ? hosted.toISOString() : null
|
|
52
|
+
case "toString":
|
|
53
|
+
return coerceToString(value)
|
|
54
|
+
case "getFullYear":
|
|
55
|
+
return hosted.getFullYear()
|
|
56
|
+
case "getMonth":
|
|
57
|
+
return hosted.getMonth()
|
|
58
|
+
case "getDate":
|
|
59
|
+
return hosted.getDate()
|
|
60
|
+
case "getDay":
|
|
61
|
+
return hosted.getDay()
|
|
62
|
+
case "getHours":
|
|
63
|
+
return hosted.getHours()
|
|
64
|
+
case "getMinutes":
|
|
65
|
+
return hosted.getMinutes()
|
|
66
|
+
case "getSeconds":
|
|
67
|
+
return hosted.getSeconds()
|
|
68
|
+
case "getMilliseconds":
|
|
69
|
+
return hosted.getMilliseconds()
|
|
70
|
+
case "getUTCFullYear":
|
|
71
|
+
return hosted.getUTCFullYear()
|
|
72
|
+
case "getUTCMonth":
|
|
73
|
+
return hosted.getUTCMonth()
|
|
74
|
+
case "getUTCDate":
|
|
75
|
+
return hosted.getUTCDate()
|
|
76
|
+
case "getUTCDay":
|
|
77
|
+
return hosted.getUTCDay()
|
|
78
|
+
case "getUTCHours":
|
|
79
|
+
return hosted.getUTCHours()
|
|
80
|
+
case "getUTCMinutes":
|
|
81
|
+
return hosted.getUTCMinutes()
|
|
82
|
+
case "getUTCSeconds":
|
|
83
|
+
return hosted.getUTCSeconds()
|
|
84
|
+
case "getUTCMilliseconds":
|
|
85
|
+
return hosted.getUTCMilliseconds()
|
|
86
|
+
case "getTimezoneOffset":
|
|
87
|
+
return hosted.getTimezoneOffset()
|
|
88
|
+
default:
|
|
89
|
+
throw new InterpreterRuntimeError(`Date method '${name}' is not available in CodeMode.`, node)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
import { type AstNode, InterpreterRuntimeError } from "../interpreter/model.js"
|
|
93
|
+
import { SandboxDate } from "../values.js"
|
|
94
|
+
import { coerceToNumber, coerceToString } from "./value.js"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AstNode,
|
|
3
|
+
CodeModeFunction,
|
|
4
|
+
InterpreterRuntimeError,
|
|
5
|
+
supportedSyntaxMessage,
|
|
6
|
+
} from "../interpreter/model.js"
|
|
7
|
+
import { copyIn, copyOut } from "../tool-runtime.js"
|
|
8
|
+
|
|
9
|
+
export const jsonStatics = new Set(["stringify", "parse"])
|
|
10
|
+
|
|
11
|
+
export const invokeJsonMethod = (name: string, args: Array<unknown>, node: AstNode): unknown => {
|
|
12
|
+
if (!jsonStatics.has(name)) throw new InterpreterRuntimeError(`JSON.${name} is not available in CodeMode.`, node)
|
|
13
|
+
switch (name) {
|
|
14
|
+
case "stringify": {
|
|
15
|
+
const replacer = args[1]
|
|
16
|
+
if (Array.isArray(replacer) || replacer instanceof CodeModeFunction) {
|
|
17
|
+
throw new InterpreterRuntimeError(
|
|
18
|
+
"JSON.stringify replacers are not supported in CodeMode.",
|
|
19
|
+
node,
|
|
20
|
+
"UnsupportedSyntax",
|
|
21
|
+
[supportedSyntaxMessage],
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
const space = args[2]
|
|
25
|
+
const indent = typeof space === "number" || typeof space === "string" ? space : undefined
|
|
26
|
+
return JSON.stringify(copyOut(copyIn(args[0], "JSON.stringify value")), null, indent)
|
|
27
|
+
}
|
|
28
|
+
case "parse": {
|
|
29
|
+
const text = args[0]
|
|
30
|
+
if (typeof text !== "string") throw new InterpreterRuntimeError("JSON.parse expects a string.", node)
|
|
31
|
+
try {
|
|
32
|
+
return copyIn(JSON.parse(text), "JSON.parse result")
|
|
33
|
+
} catch (error) {
|
|
34
|
+
throw new InterpreterRuntimeError(
|
|
35
|
+
`JSON.parse received invalid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
36
|
+
node,
|
|
37
|
+
).as("SyntaxError")
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
throw new InterpreterRuntimeError(`JSON.${name} is not available in CodeMode.`, node)
|
|
42
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export const mathConstants = new Set(["PI", "E", "LN2", "LN10", "LOG2E", "LOG10E", "SQRT2", "SQRT1_2"])
|
|
2
|
+
|
|
3
|
+
export const mathMethods = new Set([
|
|
4
|
+
"max",
|
|
5
|
+
"min",
|
|
6
|
+
"abs",
|
|
7
|
+
"floor",
|
|
8
|
+
"ceil",
|
|
9
|
+
"round",
|
|
10
|
+
"trunc",
|
|
11
|
+
"sign",
|
|
12
|
+
"sqrt",
|
|
13
|
+
"cbrt",
|
|
14
|
+
"pow",
|
|
15
|
+
"hypot",
|
|
16
|
+
"log",
|
|
17
|
+
"log2",
|
|
18
|
+
"log10",
|
|
19
|
+
"exp",
|
|
20
|
+
])
|
|
21
|
+
|
|
22
|
+
export const invokeMathMethod = (name: string, args: Array<unknown>, node: AstNode): number => {
|
|
23
|
+
if (!mathMethods.has(name)) throw new InterpreterRuntimeError(`Math.${name} is not available in CodeMode.`, node)
|
|
24
|
+
const nums = args.map((arg) => {
|
|
25
|
+
if (typeof arg !== "number") throw new InterpreterRuntimeError(`Math.${name} expects number arguments.`, node)
|
|
26
|
+
return arg
|
|
27
|
+
})
|
|
28
|
+
const [a = Number.NaN, b = Number.NaN] = nums
|
|
29
|
+
switch (name) {
|
|
30
|
+
case "max":
|
|
31
|
+
return Math.max(...nums)
|
|
32
|
+
case "min":
|
|
33
|
+
return Math.min(...nums)
|
|
34
|
+
case "abs":
|
|
35
|
+
return Math.abs(a)
|
|
36
|
+
case "floor":
|
|
37
|
+
return Math.floor(a)
|
|
38
|
+
case "ceil":
|
|
39
|
+
return Math.ceil(a)
|
|
40
|
+
case "round":
|
|
41
|
+
return Math.round(a)
|
|
42
|
+
case "trunc":
|
|
43
|
+
return Math.trunc(a)
|
|
44
|
+
case "sign":
|
|
45
|
+
return Math.sign(a)
|
|
46
|
+
case "sqrt":
|
|
47
|
+
return Math.sqrt(a)
|
|
48
|
+
case "cbrt":
|
|
49
|
+
return Math.cbrt(a)
|
|
50
|
+
case "pow":
|
|
51
|
+
return Math.pow(a, b)
|
|
52
|
+
case "hypot":
|
|
53
|
+
return Math.hypot(...nums)
|
|
54
|
+
case "log":
|
|
55
|
+
return Math.log(a)
|
|
56
|
+
case "log2":
|
|
57
|
+
return Math.log2(a)
|
|
58
|
+
case "log10":
|
|
59
|
+
return Math.log10(a)
|
|
60
|
+
case "exp":
|
|
61
|
+
return Math.exp(a)
|
|
62
|
+
}
|
|
63
|
+
throw new InterpreterRuntimeError(`Math.${name} is not available in CodeMode.`, node)
|
|
64
|
+
}
|
|
65
|
+
import { type AstNode, InterpreterRuntimeError } from "../interpreter/model.js"
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export const numberMethods = new Set(["toFixed", "toPrecision", "toExponential", "toString"])
|
|
2
|
+
|
|
3
|
+
export const numberConstants = new Set(["MAX_SAFE_INTEGER", "MIN_SAFE_INTEGER", "MAX_VALUE", "MIN_VALUE", "EPSILON"])
|
|
4
|
+
|
|
5
|
+
export const numberStatics = new Set(["isInteger", "isFinite", "isNaN", "isSafeInteger", "parseInt", "parseFloat"])
|
|
6
|
+
|
|
7
|
+
export const invokeNumberMethod = (value: number, name: string, args: Array<unknown>, node: AstNode): unknown => {
|
|
8
|
+
const optNum = (index: number): number | undefined => {
|
|
9
|
+
const arg = args[index]
|
|
10
|
+
if (arg === undefined) return undefined
|
|
11
|
+
if (typeof arg !== "number") throw new InterpreterRuntimeError(`Number.${name} expects a number argument.`, node)
|
|
12
|
+
return arg
|
|
13
|
+
}
|
|
14
|
+
let result: unknown
|
|
15
|
+
switch (name) {
|
|
16
|
+
case "toFixed":
|
|
17
|
+
result = value.toFixed(optNum(0))
|
|
18
|
+
break
|
|
19
|
+
case "toExponential":
|
|
20
|
+
result = value.toExponential(optNum(0))
|
|
21
|
+
break
|
|
22
|
+
case "toPrecision": {
|
|
23
|
+
const digits = optNum(0)
|
|
24
|
+
result = digits === undefined ? value.toString() : value.toPrecision(digits)
|
|
25
|
+
break
|
|
26
|
+
}
|
|
27
|
+
case "toString": {
|
|
28
|
+
const radix = optNum(0)
|
|
29
|
+
if (radix !== undefined && (radix < 2 || radix > 36)) {
|
|
30
|
+
throw new InterpreterRuntimeError("Number.toString radix must be between 2 and 36.", node)
|
|
31
|
+
}
|
|
32
|
+
result = value.toString(radix)
|
|
33
|
+
break
|
|
34
|
+
}
|
|
35
|
+
default:
|
|
36
|
+
throw new InterpreterRuntimeError(`Number method '${name}' is not available in CodeMode.`, node)
|
|
37
|
+
}
|
|
38
|
+
return boundedData(result, `Number.${name} result`)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const invokeNumberStatic = (name: string, args: Array<unknown>, node: AstNode): unknown => {
|
|
42
|
+
const value = args[0]
|
|
43
|
+
switch (name) {
|
|
44
|
+
case "isInteger":
|
|
45
|
+
return Number.isInteger(value)
|
|
46
|
+
case "isFinite":
|
|
47
|
+
return Number.isFinite(value)
|
|
48
|
+
case "isNaN":
|
|
49
|
+
return Number.isNaN(value)
|
|
50
|
+
case "isSafeInteger":
|
|
51
|
+
return Number.isSafeInteger(value)
|
|
52
|
+
case "parseInt": {
|
|
53
|
+
const radix = args[1]
|
|
54
|
+
if (radix !== undefined && typeof radix !== "number") {
|
|
55
|
+
throw new InterpreterRuntimeError("Number.parseInt expects a numeric radix.", node)
|
|
56
|
+
}
|
|
57
|
+
return parseInt(coerceToString(value), radix)
|
|
58
|
+
}
|
|
59
|
+
case "parseFloat":
|
|
60
|
+
return parseFloat(coerceToString(value))
|
|
61
|
+
default:
|
|
62
|
+
throw new InterpreterRuntimeError(`Number.${name} is not available in CodeMode.`, node)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
import { type AstNode, InterpreterRuntimeError } from "../interpreter/model.js"
|
|
66
|
+
import { boundedData, coerceToString } from "./value.js"
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { type AstNode, InterpreterRuntimeError } from "../interpreter/model.js"
|
|
2
|
+
import { isBlockedMember } from "../tool-runtime.js"
|
|
3
|
+
import { isSandboxValue, SandboxMap, SandboxURLSearchParams } from "../values.js"
|
|
4
|
+
import { boundedData, coerceToString } from "./value.js"
|
|
5
|
+
|
|
6
|
+
export const objectStatics = new Set(["keys", "values", "entries", "hasOwn", "assign", "fromEntries"])
|
|
7
|
+
|
|
8
|
+
export const invokeObjectMethod = (name: string, args: Array<unknown>, node: AstNode): unknown => {
|
|
9
|
+
if (!objectStatics.has(name)) throw new InterpreterRuntimeError(`Object.${name} is not available in CodeMode.`, node)
|
|
10
|
+
const requireObject = (): Record<string, unknown> => {
|
|
11
|
+
const value = boundedData(args[0], `Object.${name} input`)
|
|
12
|
+
if (isSandboxValue(value)) return {}
|
|
13
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
14
|
+
throw new InterpreterRuntimeError(`Object.${name} expects a data object.`, node)
|
|
15
|
+
}
|
|
16
|
+
return value as Record<string, unknown>
|
|
17
|
+
}
|
|
18
|
+
const guardedSet = (out: Record<string, unknown>, key: string, item: unknown): void => {
|
|
19
|
+
if (isBlockedMember(key)) throw new InterpreterRuntimeError(`Property '${key}' is not available in CodeMode.`, node)
|
|
20
|
+
out[key] = item
|
|
21
|
+
}
|
|
22
|
+
switch (name) {
|
|
23
|
+
case "keys": {
|
|
24
|
+
const value = boundedData(args[0], "Object.keys input")
|
|
25
|
+
if (isSandboxValue(value)) return []
|
|
26
|
+
if (Array.isArray(value)) return Object.keys(value)
|
|
27
|
+
if (value === null || typeof value !== "object") {
|
|
28
|
+
throw new InterpreterRuntimeError("Object.keys expects a data object or array.", node)
|
|
29
|
+
}
|
|
30
|
+
return Object.keys(value)
|
|
31
|
+
}
|
|
32
|
+
case "values":
|
|
33
|
+
return Object.values(requireObject())
|
|
34
|
+
case "entries":
|
|
35
|
+
return Object.entries(requireObject()).map(([key, item]) => [key, item])
|
|
36
|
+
case "hasOwn":
|
|
37
|
+
return Object.hasOwn(requireObject(), String(args[1]))
|
|
38
|
+
case "assign": {
|
|
39
|
+
const out: Record<string, unknown> = Object.create(null)
|
|
40
|
+
for (const source of args) {
|
|
41
|
+
if (source === null || source === undefined) continue
|
|
42
|
+
const value = boundedData(source, "Object.assign input")
|
|
43
|
+
if (isSandboxValue(value)) continue
|
|
44
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
45
|
+
throw new InterpreterRuntimeError("Object.assign expects data objects.", node)
|
|
46
|
+
}
|
|
47
|
+
for (const [key, item] of Object.entries(value)) guardedSet(out, key, item)
|
|
48
|
+
}
|
|
49
|
+
return out
|
|
50
|
+
}
|
|
51
|
+
case "fromEntries": {
|
|
52
|
+
if (args[0] instanceof SandboxMap) {
|
|
53
|
+
const out: Record<string, unknown> = Object.create(null)
|
|
54
|
+
for (const [key, item] of args[0].map.entries()) guardedSet(out, coerceToString(key), item)
|
|
55
|
+
return out
|
|
56
|
+
}
|
|
57
|
+
if (args[0] instanceof SandboxURLSearchParams) {
|
|
58
|
+
const out: Record<string, unknown> = Object.create(null)
|
|
59
|
+
for (const [key, value] of args[0].params.entries()) guardedSet(out, key, value)
|
|
60
|
+
return out
|
|
61
|
+
}
|
|
62
|
+
const pairs = boundedData(args[0], "Object.fromEntries input")
|
|
63
|
+
if (!Array.isArray(pairs)) {
|
|
64
|
+
throw new InterpreterRuntimeError("Object.fromEntries expects an array of [key, value] pairs.", node)
|
|
65
|
+
}
|
|
66
|
+
const out: Record<string, unknown> = Object.create(null)
|
|
67
|
+
for (const pair of pairs) {
|
|
68
|
+
if (!Array.isArray(pair)) {
|
|
69
|
+
throw new InterpreterRuntimeError("Object.fromEntries expects [key, value] pairs.", node)
|
|
70
|
+
}
|
|
71
|
+
guardedSet(out, String(pair[0]), pair[1])
|
|
72
|
+
}
|
|
73
|
+
return out
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
throw new InterpreterRuntimeError(`Object.${name} is not available in CodeMode.`, node)
|
|
77
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { PromiseMethodName } from "../interpreter/model.js"
|
|
2
|
+
|
|
3
|
+
export const promiseStatics = new Set<PromiseMethodName>(["all", "allSettled", "race", "resolve", "reject"])
|
|
4
|
+
|
|
5
|
+
/** Maximum number of eagerly forked tool calls that may run concurrently. */
|
|
6
|
+
export const TOOL_CALL_CONCURRENCY = 8
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export const regexpMethods = new Set(["test", "exec", "toString"])
|
|
2
|
+
|
|
3
|
+
export const regexpProperties = new Set([
|
|
4
|
+
"source",
|
|
5
|
+
"flags",
|
|
6
|
+
"lastIndex",
|
|
7
|
+
"global",
|
|
8
|
+
"ignoreCase",
|
|
9
|
+
"multiline",
|
|
10
|
+
"sticky",
|
|
11
|
+
"unicode",
|
|
12
|
+
"dotAll",
|
|
13
|
+
])
|
|
14
|
+
|
|
15
|
+
export const regexFailureReason = (error: unknown): string =>
|
|
16
|
+
(error instanceof Error ? error.message : String(error)).replace(/^Invalid regular expression:\s*/i, "")
|
|
17
|
+
|
|
18
|
+
export const escapeRegexHint =
|
|
19
|
+
'To match special characters like ( ) [ ] { } + * ? . literally, escape them with a backslash (e.g. "\\\\(") or test for them with String.includes instead.'
|
|
20
|
+
|
|
21
|
+
export const toHostRegex = (arg: unknown, method: string, node: AstNode, extraFlags = ""): RegExp => {
|
|
22
|
+
if (arg instanceof SandboxRegExp) return arg.regex
|
|
23
|
+
if (typeof arg === "string") {
|
|
24
|
+
try {
|
|
25
|
+
return new RegExp(arg, extraFlags)
|
|
26
|
+
} catch (error) {
|
|
27
|
+
throw new InterpreterRuntimeError(
|
|
28
|
+
`String.${method} received the string ${JSON.stringify(arg)}, which is not a valid regular expression pattern (${regexFailureReason(error)}). ${escapeRegexHint}`,
|
|
29
|
+
node,
|
|
30
|
+
).as("SyntaxError")
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
throw new InterpreterRuntimeError(
|
|
34
|
+
`String.${method} expects a regular expression (a /pattern/flags literal or new RegExp(...)) or a string pattern, not ${arg === null ? "null" : typeof arg}.`,
|
|
35
|
+
node,
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const matchToValue = (match: RegExpMatchArray): Array<unknown> => {
|
|
40
|
+
const result: Array<unknown> = Array.from(match, (group) => group)
|
|
41
|
+
if (match.index !== undefined) (result as Record<string, unknown> & Array<unknown>).index = match.index
|
|
42
|
+
if (match.groups) {
|
|
43
|
+
const groups: SafeObject = Object.create(null) as SafeObject
|
|
44
|
+
for (const [key, group] of Object.entries(match.groups)) {
|
|
45
|
+
if (!isBlockedMember(key)) groups[key] = group
|
|
46
|
+
}
|
|
47
|
+
;(result as Record<string, unknown> & Array<unknown>).groups = groups
|
|
48
|
+
}
|
|
49
|
+
return result
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const invokeRegExpMethod = (
|
|
53
|
+
value: SandboxRegExp,
|
|
54
|
+
name: string,
|
|
55
|
+
args: Array<unknown>,
|
|
56
|
+
node: AstNode,
|
|
57
|
+
): unknown => {
|
|
58
|
+
switch (name) {
|
|
59
|
+
case "test":
|
|
60
|
+
return value.regex.test(coerceToString(args[0]))
|
|
61
|
+
case "exec": {
|
|
62
|
+
const matched = value.regex.exec(coerceToString(args[0]))
|
|
63
|
+
return matched === null ? null : matchToValue(matched)
|
|
64
|
+
}
|
|
65
|
+
case "toString":
|
|
66
|
+
return coerceToString(value)
|
|
67
|
+
default:
|
|
68
|
+
throw new InterpreterRuntimeError(`RegExp method '${name}' is not available in CodeMode.`, node)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
import { type AstNode, InterpreterRuntimeError } from "../interpreter/model.js"
|
|
72
|
+
import { isBlockedMember, type SafeObject } from "../tool-runtime.js"
|
|
73
|
+
import { SandboxRegExp } from "../values.js"
|
|
74
|
+
import { coerceToString } from "./value.js"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export const stringMethods = new Set([
|
|
2
|
+
"toLowerCase",
|
|
3
|
+
"toUpperCase",
|
|
4
|
+
"trim",
|
|
5
|
+
"trimStart",
|
|
6
|
+
"trimEnd",
|
|
7
|
+
"trimLeft",
|
|
8
|
+
"trimRight",
|
|
9
|
+
"split",
|
|
10
|
+
"slice",
|
|
11
|
+
"substring",
|
|
12
|
+
"substr",
|
|
13
|
+
"includes",
|
|
14
|
+
"startsWith",
|
|
15
|
+
"endsWith",
|
|
16
|
+
"indexOf",
|
|
17
|
+
"lastIndexOf",
|
|
18
|
+
"replace",
|
|
19
|
+
"replaceAll",
|
|
20
|
+
"repeat",
|
|
21
|
+
"padStart",
|
|
22
|
+
"padEnd",
|
|
23
|
+
"charAt",
|
|
24
|
+
"charCodeAt",
|
|
25
|
+
"codePointAt",
|
|
26
|
+
"at",
|
|
27
|
+
"concat",
|
|
28
|
+
"toString",
|
|
29
|
+
"match",
|
|
30
|
+
"matchAll",
|
|
31
|
+
"search",
|
|
32
|
+
"localeCompare",
|
|
33
|
+
"normalize",
|
|
34
|
+
])
|
|
35
|
+
|
|
36
|
+
export const stringStatics = new Set(["fromCharCode", "fromCodePoint"])
|
|
37
|
+
|
|
38
|
+
export const invokeStringStatic = (name: string, args: Array<unknown>, node: AstNode): unknown => {
|
|
39
|
+
const codes = args.map((arg) => {
|
|
40
|
+
if (typeof arg !== "number") throw new InterpreterRuntimeError(`String.${name} expects number arguments.`, node)
|
|
41
|
+
return arg
|
|
42
|
+
})
|
|
43
|
+
switch (name) {
|
|
44
|
+
case "fromCharCode":
|
|
45
|
+
return String.fromCharCode(...codes)
|
|
46
|
+
case "fromCodePoint":
|
|
47
|
+
return String.fromCodePoint(...codes)
|
|
48
|
+
default:
|
|
49
|
+
throw new InterpreterRuntimeError(`String.${name} is not available in CodeMode.`, node)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
import { type AstNode, InterpreterRuntimeError } from "../interpreter/model.js"
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export const urlProperties = new Set([
|
|
2
|
+
"href",
|
|
3
|
+
"origin",
|
|
4
|
+
"protocol",
|
|
5
|
+
"username",
|
|
6
|
+
"password",
|
|
7
|
+
"host",
|
|
8
|
+
"hostname",
|
|
9
|
+
"port",
|
|
10
|
+
"pathname",
|
|
11
|
+
"search",
|
|
12
|
+
"hash",
|
|
13
|
+
])
|
|
14
|
+
|
|
15
|
+
export const urlWritableProperties = new Set([
|
|
16
|
+
"href",
|
|
17
|
+
"protocol",
|
|
18
|
+
"username",
|
|
19
|
+
"password",
|
|
20
|
+
"host",
|
|
21
|
+
"hostname",
|
|
22
|
+
"port",
|
|
23
|
+
"pathname",
|
|
24
|
+
"search",
|
|
25
|
+
"hash",
|
|
26
|
+
])
|
|
27
|
+
|
|
28
|
+
export const urlMethods = new Set(["toString", "toJSON"])
|
|
29
|
+
export const urlStatics = new Set(["canParse", "parse"])
|
|
30
|
+
export const urlSearchParamsMethods = new Set([
|
|
31
|
+
"append",
|
|
32
|
+
"delete",
|
|
33
|
+
"get",
|
|
34
|
+
"getAll",
|
|
35
|
+
"has",
|
|
36
|
+
"set",
|
|
37
|
+
"sort",
|
|
38
|
+
"forEach",
|
|
39
|
+
"keys",
|
|
40
|
+
"values",
|
|
41
|
+
"entries",
|
|
42
|
+
"toString",
|
|
43
|
+
])
|
|
44
|
+
|
|
45
|
+
export const uriArgument = (value: unknown, label: string): string => coerceToString(boundedData(value, label))
|
|
46
|
+
|
|
47
|
+
export const invokeUriFunction = (ref: UriFunction, args: Array<unknown>, node: AstNode): string => {
|
|
48
|
+
const value = uriArgument(args[0], `${ref.name} input`)
|
|
49
|
+
try {
|
|
50
|
+
switch (ref.name) {
|
|
51
|
+
case "encodeURI":
|
|
52
|
+
return encodeURI(value)
|
|
53
|
+
case "encodeURIComponent":
|
|
54
|
+
return encodeURIComponent(value)
|
|
55
|
+
case "decodeURI":
|
|
56
|
+
return decodeURI(value)
|
|
57
|
+
case "decodeURIComponent":
|
|
58
|
+
return decodeURIComponent(value)
|
|
59
|
+
}
|
|
60
|
+
} catch (error) {
|
|
61
|
+
throw new InterpreterRuntimeError(
|
|
62
|
+
`${ref.name} received malformed URI data: ${error instanceof Error ? error.message : String(error)}`,
|
|
63
|
+
node,
|
|
64
|
+
).as("URIError")
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const urlArgument = (value: unknown, label: string): string =>
|
|
69
|
+
value instanceof SandboxURL ? value.url.href : uriArgument(value, label)
|
|
70
|
+
|
|
71
|
+
export const invokeURLStatic = (name: string, args: Array<unknown>, node: AstNode): unknown => {
|
|
72
|
+
if (!urlStatics.has(name)) throw new InterpreterRuntimeError(`URL.${name} is not available in CodeMode.`, node)
|
|
73
|
+
if (args.length === 0) throw new InterpreterRuntimeError(`URL.${name} requires a URL argument.`, node).as("TypeError")
|
|
74
|
+
const input = urlArgument(args[0], `URL.${name} input`)
|
|
75
|
+
const base = args[1] === undefined ? undefined : urlArgument(args[1], `URL.${name} base`)
|
|
76
|
+
try {
|
|
77
|
+
const url = new URL(input, base)
|
|
78
|
+
return name === "canParse" ? true : new SandboxURL(url)
|
|
79
|
+
} catch {
|
|
80
|
+
return name === "canParse" ? false : null
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const invokeURLMethod = (value: SandboxURL, name: string, node: AstNode): string => {
|
|
85
|
+
if (name === "toString" || name === "toJSON") return value.url.href
|
|
86
|
+
throw new InterpreterRuntimeError(`URL method '${name}' is not available in CodeMode.`, node)
|
|
87
|
+
}
|
|
88
|
+
import { type AstNode, InterpreterRuntimeError, UriFunction } from "../interpreter/model.js"
|
|
89
|
+
import { SandboxURL } from "../values.js"
|
|
90
|
+
import { boundedData, coerceToString } from "./value.js"
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export const errorConstructors = new Set([
|
|
2
|
+
"Error",
|
|
3
|
+
"TypeError",
|
|
4
|
+
"RangeError",
|
|
5
|
+
"SyntaxError",
|
|
6
|
+
"ReferenceError",
|
|
7
|
+
"EvalError",
|
|
8
|
+
"URIError",
|
|
9
|
+
])
|
|
10
|
+
|
|
11
|
+
export const valueConstructors = new Set(["Date", "RegExp", "Map", "Set", "URL", "URLSearchParams"])
|
|
12
|
+
|
|
13
|
+
export const compoundOperators = new Set(["+=", "-=", "*=", "/=", "%=", "**=", "&=", "|=", "^=", "<<=", ">>=", ">>>="])
|
|
14
|
+
|
|
15
|
+
const ErrorBrand: unique symbol = Symbol("codemode.error")
|
|
16
|
+
|
|
17
|
+
export const createErrorValue = (name: string, message: string): SafeObject => {
|
|
18
|
+
const value = Object.assign(Object.create(null) as SafeObject, { name, message })
|
|
19
|
+
Object.defineProperty(value, ErrorBrand, { value: name })
|
|
20
|
+
return value
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const errorBrandName = (value: unknown): string | undefined =>
|
|
24
|
+
value !== null && typeof value === "object"
|
|
25
|
+
? ((value as Record<PropertyKey, unknown>)[ErrorBrand] as string | undefined)
|
|
26
|
+
: undefined
|
|
27
|
+
|
|
28
|
+
export const boundedData = (value: unknown, label: string): unknown => copyIn(value, label, true)
|
|
29
|
+
|
|
30
|
+
export const coerceToString = (value: unknown): string => {
|
|
31
|
+
if (value === null) return "null"
|
|
32
|
+
if (value === undefined) return "undefined"
|
|
33
|
+
if (value instanceof SandboxDate)
|
|
34
|
+
return Number.isFinite(value.time) ? new Date(value.time).toISOString() : "Invalid Date"
|
|
35
|
+
if (value instanceof SandboxRegExp) return `/${value.regex.source}/${value.regex.flags}`
|
|
36
|
+
if (value instanceof SandboxMap) return "[object Map]"
|
|
37
|
+
if (value instanceof SandboxSet) return "[object Set]"
|
|
38
|
+
if (value instanceof SandboxURL) return value.url.href
|
|
39
|
+
if (value instanceof SandboxURLSearchParams) return value.params.toString()
|
|
40
|
+
if (typeof value === "object") {
|
|
41
|
+
return Array.isArray(value)
|
|
42
|
+
? value.map((item) => (item === null || item === undefined ? "" : coerceToString(item))).join(",")
|
|
43
|
+
: "[object Object]"
|
|
44
|
+
}
|
|
45
|
+
return String(value)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const coerceToNumber = (value: unknown): number => {
|
|
49
|
+
if (value instanceof SandboxDate) return value.time
|
|
50
|
+
if (isSandboxValue(value)) return Number.NaN
|
|
51
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? Number.NaN : Number(value)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const invokeCoercion = (ref: CoercionFunction, args: Array<unknown>, node: AstNode): unknown => {
|
|
55
|
+
const raw = args[0]
|
|
56
|
+
if (isSandboxValue(raw)) {
|
|
57
|
+
if (ref.name === "Boolean") return true
|
|
58
|
+
if (ref.name === "Number") return coerceToNumber(raw)
|
|
59
|
+
if (ref.name === "String") return coerceToString(raw)
|
|
60
|
+
if (ref.name === "parseInt") return parseInt(coerceToString(raw))
|
|
61
|
+
return parseFloat(coerceToString(raw))
|
|
62
|
+
}
|
|
63
|
+
const value = boundedData(args[0], `${ref.name} input`)
|
|
64
|
+
if (ref.name === "Number") return coerceToNumber(value)
|
|
65
|
+
if (ref.name === "Boolean") return Boolean(value)
|
|
66
|
+
if (ref.name === "parseInt") {
|
|
67
|
+
const radix = args[1]
|
|
68
|
+
if (radix !== undefined && typeof radix !== "number") {
|
|
69
|
+
throw new InterpreterRuntimeError("parseInt expects a numeric radix.", node)
|
|
70
|
+
}
|
|
71
|
+
return parseInt(coerceToString(value), radix)
|
|
72
|
+
}
|
|
73
|
+
if (ref.name === "parseFloat") return parseFloat(coerceToString(value))
|
|
74
|
+
return coerceToString(value)
|
|
75
|
+
}
|
|
76
|
+
import { type AstNode, CoercionFunction, InterpreterRuntimeError } from "../interpreter/model.js"
|
|
77
|
+
import { copyIn, type SafeObject } from "../tool-runtime.js"
|
|
78
|
+
import {
|
|
79
|
+
isSandboxValue,
|
|
80
|
+
SandboxDate,
|
|
81
|
+
SandboxMap,
|
|
82
|
+
SandboxRegExp,
|
|
83
|
+
SandboxSet,
|
|
84
|
+
SandboxURL,
|
|
85
|
+
SandboxURLSearchParams,
|
|
86
|
+
} from "../values.js"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Schema } from "effect"
|
|
2
|
+
|
|
3
|
+
/** Safe operational refusal from a standard tool pack, reported as `ToolFailure`. */
|
|
4
|
+
export class ToolError extends Schema.TaggedErrorClass<ToolError>()("ToolError", {
|
|
5
|
+
message: Schema.String,
|
|
6
|
+
cause: Schema.optionalKey(Schema.Defect()),
|
|
7
|
+
}) {}
|
|
8
|
+
|
|
9
|
+
/** Creates a tool refusal whose message is safe to include in an execution diagnostic. */
|
|
10
|
+
export const toolError = (message: string, cause?: unknown): ToolError =>
|
|
11
|
+
new ToolError({ message, ...(cause === undefined ? {} : { cause }) })
|