@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,159 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import { Effect, Schema } from "effect"
|
|
3
|
+
import { CodeMode, Tool } from "../src/index.js"
|
|
4
|
+
|
|
5
|
+
// Key enumeration: Object.keys and for...in share one surface over plain objects, arrays
|
|
6
|
+
// (index strings), and tool references (namespace/tool names from the host tool tree), so a
|
|
7
|
+
// model can discover what it may call instead of guessing names from the instructions. The
|
|
8
|
+
// motivating transcript: `Object.keys(tools)` failed with the generic plain-objects-only
|
|
9
|
+
// message and `for (const key in tools)` was unsupported syntax, forcing blind guesses.
|
|
10
|
+
|
|
11
|
+
const echo = (description: string) =>
|
|
12
|
+
Tool.make({
|
|
13
|
+
description,
|
|
14
|
+
input: Schema.Struct({ value: Schema.String }),
|
|
15
|
+
output: Schema.String,
|
|
16
|
+
run: ({ value }) => Effect.succeed(value),
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const tools = {
|
|
20
|
+
github: { list_issues: echo("List issues"), get_issue: echo("Get one issue") },
|
|
21
|
+
memory: { search: echo("Search memory") },
|
|
22
|
+
playwright: { navigate: echo("Navigate somewhere") },
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const run = (code: string) => Effect.runPromise(CodeMode.execute({ tools, code }))
|
|
26
|
+
const value = async (code: string) => {
|
|
27
|
+
const result = await run(code)
|
|
28
|
+
if (!result.ok) throw new Error(`expected success, got ${result.error.kind}: ${result.error.message}`)
|
|
29
|
+
return result.value
|
|
30
|
+
}
|
|
31
|
+
const error = async (code: string) => {
|
|
32
|
+
const result = await run(code)
|
|
33
|
+
if (result.ok) throw new Error(`expected failure, got value ${JSON.stringify(result.value)}`)
|
|
34
|
+
return result.error
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
describe("Object.keys over tool references", () => {
|
|
38
|
+
test("enumerates top-level namespaces (the transcript program)", async () => {
|
|
39
|
+
expect(
|
|
40
|
+
await value(`
|
|
41
|
+
const namespaces = Object.keys(tools)
|
|
42
|
+
return { namespaces, count: namespaces.length }
|
|
43
|
+
`),
|
|
44
|
+
).toEqual({ namespaces: ["github", "memory", "playwright", "$codemode"], count: 4 })
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test("enumerates tool names at a nested namespace", async () => {
|
|
48
|
+
expect(await value(`return Object.keys(tools.github)`)).toEqual(["list_issues", "get_issue"])
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test("a callable tool is a leaf and enumerates as []", async () => {
|
|
52
|
+
expect(await value(`return Object.keys(tools.github.list_issues)`)).toEqual([])
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test("the internal discovery namespace enumerates its callable surface", async () => {
|
|
56
|
+
expect(await value(`return Object.keys(tools.$codemode)`)).toEqual(["search"])
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test("an unknown namespace is an UnknownTool error pointing at the discovery idioms", async () => {
|
|
60
|
+
const failure = await error(`return Object.keys(tools.nonexistent)`)
|
|
61
|
+
expect(failure.kind).toBe("UnknownTool")
|
|
62
|
+
expect(failure.message).toContain("Unknown tool namespace 'nonexistent'")
|
|
63
|
+
expect(failure.suggestions?.join(" ")).toContain("Object.keys(tools)")
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test("Object.values/entries on a tool reference explain the working idioms", async () => {
|
|
67
|
+
for (const method of ["values", "entries"] as const) {
|
|
68
|
+
const failure = await error(`return Object.${method}(tools)`)
|
|
69
|
+
expect(failure.kind).toBe("InvalidDataValue")
|
|
70
|
+
expect(failure.message).toContain(
|
|
71
|
+
`Object.${method}(...) cannot read tool references: they are not plain data. Use Object.keys(tools) for names, or tools.$codemode.search({ query }) for signatures.`,
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
const nested = await error(`return Object.entries(tools.github)`)
|
|
75
|
+
expect(nested.message).toContain("Use Object.keys(tools) for names")
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
describe("Object.keys over arrays", () => {
|
|
80
|
+
test("returns index strings, like JS", async () => {
|
|
81
|
+
expect(await value(`return Object.keys(["a", "b", "c"])`)).toEqual(["0", "1", "2"])
|
|
82
|
+
expect(await value(`return Object.keys([])`)).toEqual([])
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test("objects keep their own enumerable keys", async () => {
|
|
86
|
+
expect(await value(`return Object.keys({ a: 1, b: 2 })`)).toEqual(["a", "b"])
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test("non-object inputs still fail clearly", async () => {
|
|
90
|
+
const failure = await error(`return Object.keys("nope")`)
|
|
91
|
+
expect(failure.message).toContain("Object.keys expects a data object or array")
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
describe("for...in", () => {
|
|
96
|
+
test("iterates own enumerable keys of a plain object with break/continue", async () => {
|
|
97
|
+
expect(
|
|
98
|
+
await value(`
|
|
99
|
+
const seen = []
|
|
100
|
+
for (const key in { a: 1, b: 2, c: 3, d: 4 }) {
|
|
101
|
+
if (key === "b") continue
|
|
102
|
+
if (key === "d") break
|
|
103
|
+
seen.push(key)
|
|
104
|
+
}
|
|
105
|
+
return seen
|
|
106
|
+
`),
|
|
107
|
+
).toEqual(["a", "c"])
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test("iterates index strings over arrays", async () => {
|
|
111
|
+
expect(
|
|
112
|
+
await value(`
|
|
113
|
+
const indexes = []
|
|
114
|
+
for (const i in ["x", "y", "z"]) {
|
|
115
|
+
if (i === "2") break
|
|
116
|
+
indexes.push(i)
|
|
117
|
+
}
|
|
118
|
+
return indexes
|
|
119
|
+
`),
|
|
120
|
+
).toEqual(["0", "1"])
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test("supports let declarations and bare identifiers", async () => {
|
|
124
|
+
expect(
|
|
125
|
+
await value(`
|
|
126
|
+
let last = ""
|
|
127
|
+
for (let key in { a: 1, b: 2 }) last = key
|
|
128
|
+
return last
|
|
129
|
+
`),
|
|
130
|
+
).toBe("b")
|
|
131
|
+
expect(
|
|
132
|
+
await value(`
|
|
133
|
+
let key = "before"
|
|
134
|
+
for (key in { only: 1 }) {}
|
|
135
|
+
return key
|
|
136
|
+
`),
|
|
137
|
+
).toBe("only")
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
test("enumerates namespaces and tools from the callable tool tree", async () => {
|
|
141
|
+
expect(
|
|
142
|
+
await value(`
|
|
143
|
+
const names = []
|
|
144
|
+
for (const ns in tools) {
|
|
145
|
+
for (const name in tools[ns]) names.push(ns + "." + name)
|
|
146
|
+
}
|
|
147
|
+
return names
|
|
148
|
+
`),
|
|
149
|
+
).toEqual(["github.list_issues", "github.get_issue", "memory.search", "playwright.navigate", "$codemode.search"])
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
test("unsupported values fail with a hint at for...of and Object.keys", async () => {
|
|
153
|
+
for (const expression of [`"text"`, "new Map([[1, 2]])", "new Set([1])", "42", "null"]) {
|
|
154
|
+
const failure = await error(`for (const key in ${expression}) {}; return "no"`)
|
|
155
|
+
expect(failure.message).toContain("for...in requires a plain object, array, or tools reference")
|
|
156
|
+
expect(failure.message).toContain("Use for...of for arrays/strings/Maps/Sets, or Object.keys(value)")
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
})
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
{
|
|
2
|
+
"openapi": "3.1.0",
|
|
3
|
+
"info": {
|
|
4
|
+
"title": "CodeMode Happy Path",
|
|
5
|
+
"version": "1.0.0"
|
|
6
|
+
},
|
|
7
|
+
"servers": [
|
|
8
|
+
{
|
|
9
|
+
"url": "https://api.example.test/v1"
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"security": [
|
|
13
|
+
{
|
|
14
|
+
"BearerAuth": []
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"paths": {
|
|
18
|
+
"/users/{userId}": {
|
|
19
|
+
"parameters": [
|
|
20
|
+
{
|
|
21
|
+
"$ref": "#/components/parameters/UserId"
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"get": {
|
|
25
|
+
"operationId": "users.get",
|
|
26
|
+
"summary": "Get a user",
|
|
27
|
+
"parameters": [
|
|
28
|
+
{
|
|
29
|
+
"name": "include",
|
|
30
|
+
"in": "query",
|
|
31
|
+
"style": "form",
|
|
32
|
+
"explode": false,
|
|
33
|
+
"schema": {
|
|
34
|
+
"type": "array",
|
|
35
|
+
"items": {
|
|
36
|
+
"type": "string"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": "verbose",
|
|
42
|
+
"in": "query",
|
|
43
|
+
"schema": {
|
|
44
|
+
"type": "boolean"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"name": "X-Trace-ID",
|
|
49
|
+
"in": "header",
|
|
50
|
+
"schema": {
|
|
51
|
+
"type": "string"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
"responses": {
|
|
56
|
+
"200": {
|
|
57
|
+
"$ref": "#/components/responses/UserResponse"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"delete": {
|
|
62
|
+
"operationId": "users.remove",
|
|
63
|
+
"summary": "Remove a user",
|
|
64
|
+
"responses": {
|
|
65
|
+
"204": {
|
|
66
|
+
"description": "Removed"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"/users": {
|
|
72
|
+
"post": {
|
|
73
|
+
"operationId": "users.create",
|
|
74
|
+
"summary": "Create a user",
|
|
75
|
+
"security": [
|
|
76
|
+
{
|
|
77
|
+
"ApiKey": []
|
|
78
|
+
}
|
|
79
|
+
],
|
|
80
|
+
"requestBody": {
|
|
81
|
+
"required": true,
|
|
82
|
+
"content": {
|
|
83
|
+
"application/json": {
|
|
84
|
+
"schema": {
|
|
85
|
+
"type": "object",
|
|
86
|
+
"properties": {
|
|
87
|
+
"name": {
|
|
88
|
+
"type": "string"
|
|
89
|
+
},
|
|
90
|
+
"email": {
|
|
91
|
+
"type": "string",
|
|
92
|
+
"format": "email"
|
|
93
|
+
},
|
|
94
|
+
"role": {
|
|
95
|
+
"type": "string",
|
|
96
|
+
"enum": ["admin", "member"]
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
"required": ["name", "email"],
|
|
100
|
+
"additionalProperties": false
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
"responses": {
|
|
106
|
+
"201": {
|
|
107
|
+
"description": "Created",
|
|
108
|
+
"content": {
|
|
109
|
+
"application/vnd.example+json": {
|
|
110
|
+
"schema": {
|
|
111
|
+
"$ref": "#/components/schemas/User"
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
"/search": {
|
|
120
|
+
"get": {
|
|
121
|
+
"operationId": "search.run",
|
|
122
|
+
"summary": "Search users",
|
|
123
|
+
"security": [],
|
|
124
|
+
"parameters": [
|
|
125
|
+
{
|
|
126
|
+
"name": "filter",
|
|
127
|
+
"in": "query",
|
|
128
|
+
"style": "deepObject",
|
|
129
|
+
"explode": true,
|
|
130
|
+
"schema": {
|
|
131
|
+
"type": "object",
|
|
132
|
+
"properties": {
|
|
133
|
+
"query": {
|
|
134
|
+
"type": "string"
|
|
135
|
+
},
|
|
136
|
+
"page": {
|
|
137
|
+
"type": "integer"
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
"required": ["query"],
|
|
141
|
+
"additionalProperties": false
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"name": "tags",
|
|
146
|
+
"in": "query",
|
|
147
|
+
"style": "form",
|
|
148
|
+
"explode": true,
|
|
149
|
+
"schema": {
|
|
150
|
+
"type": "array",
|
|
151
|
+
"items": {
|
|
152
|
+
"type": "string"
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
],
|
|
157
|
+
"responses": {
|
|
158
|
+
"200": {
|
|
159
|
+
"description": "Summary",
|
|
160
|
+
"content": {
|
|
161
|
+
"text/plain": {
|
|
162
|
+
"schema": {
|
|
163
|
+
"type": "string"
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
"components": {
|
|
173
|
+
"parameters": {
|
|
174
|
+
"UserId": {
|
|
175
|
+
"name": "userId",
|
|
176
|
+
"in": "path",
|
|
177
|
+
"required": true,
|
|
178
|
+
"schema": {
|
|
179
|
+
"type": "string"
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
"responses": {
|
|
184
|
+
"UserResponse": {
|
|
185
|
+
"description": "A user",
|
|
186
|
+
"content": {
|
|
187
|
+
"application/json": {
|
|
188
|
+
"schema": {
|
|
189
|
+
"$ref": "#/components/schemas/User"
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
"schemas": {
|
|
196
|
+
"User": {
|
|
197
|
+
"type": "object",
|
|
198
|
+
"properties": {
|
|
199
|
+
"id": {
|
|
200
|
+
"type": "string"
|
|
201
|
+
},
|
|
202
|
+
"name": {
|
|
203
|
+
"type": "string"
|
|
204
|
+
},
|
|
205
|
+
"email": {
|
|
206
|
+
"type": "string",
|
|
207
|
+
"format": "email"
|
|
208
|
+
},
|
|
209
|
+
"role": {
|
|
210
|
+
"type": "string",
|
|
211
|
+
"enum": ["admin", "member"]
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
"required": ["id", "name", "email"],
|
|
215
|
+
"additionalProperties": false
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
"securitySchemes": {
|
|
219
|
+
"BearerAuth": {
|
|
220
|
+
"type": "http",
|
|
221
|
+
"scheme": "bearer"
|
|
222
|
+
},
|
|
223
|
+
"ApiKey": {
|
|
224
|
+
"type": "apiKey",
|
|
225
|
+
"in": "query",
|
|
226
|
+
"name": "api_key"
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|