@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.
@@ -0,0 +1,301 @@
1
+ import { JsonPointer, Schema } from "effect"
2
+ import type { Definition, JsonSchema, SchemaType } from "./tool.js"
3
+
4
+ const isEffectSchema = (schema: SchemaType): schema is Schema.Decoder<unknown> & Schema.Top => Schema.isSchema(schema)
5
+
6
+ const renderLiteral = (value: unknown): string => JSON.stringify(value) ?? "unknown"
7
+
8
+ /**
9
+ * Bare TypeScript identifier - usable unquoted as an object key (and, in the tool runtime,
10
+ * with dot access as a tool-path segment). Anything else must be quoted/bracketed.
11
+ */
12
+ export const identifierSegment = /^[A-Za-z_$][A-Za-z0-9_$]*$/
13
+
14
+ /** Renders a property name as a valid TS object key: bare when an identifier, quoted otherwise. */
15
+ const renderKey = (name: string): string => (identifierSegment.test(name) ? name : JSON.stringify(name))
16
+
17
+ const effectNumberSentinel = (schema: JsonSchema) =>
18
+ schema.type === "string" &&
19
+ Array.isArray(schema.enum) &&
20
+ schema.enum.length === 1 &&
21
+ (schema.enum[0] === "NaN" || schema.enum[0] === "Infinity" || schema.enum[0] === "-Infinity")
22
+
23
+ const intersection = (members: ReadonlyArray<string>): string => {
24
+ const concrete = members.filter((member) => member !== "unknown")
25
+ if (concrete.length === 0) return "unknown"
26
+ if (concrete.length === 1) return concrete[0] ?? "unknown"
27
+ return concrete.map((member) => (member.includes(" | ") ? `(${member})` : member)).join(" & ")
28
+ }
29
+
30
+ /**
31
+ * Recursion ceiling for schema rendering. Object, array, and union recursion all increment
32
+ * depth, so this bounds every recursion path - pathological or structurally cyclic schemas
33
+ * degrade to `unknown` instead of overflowing the stack (rendering must never throw).
34
+ */
35
+ const MAX_RENDER_DEPTH = 8
36
+
37
+ type RenderContext = {
38
+ readonly definitions: Readonly<Record<string, JsonSchema>>
39
+ /** Indented, JSDoc-annotated multiline rendering (search results); compact single line otherwise. */
40
+ readonly pretty: boolean
41
+ }
42
+
43
+ const hasUnresolvedRef = (
44
+ schema: JsonSchema,
45
+ definitions: Readonly<Record<string, JsonSchema>>,
46
+ seen: ReadonlySet<string> = new Set(),
47
+ visited: ReadonlySet<JsonSchema> = new Set(),
48
+ ): boolean => {
49
+ if (visited.has(schema)) return false
50
+ const nextVisited = new Set([...visited, schema])
51
+ if (schema.$ref !== undefined) {
52
+ const segment = schema.$ref.match(/^#\/(?:\$defs|definitions)\/([^/]+)$/)?.[1]
53
+ const name = segment === undefined ? undefined : JsonPointer.unescapeToken(segment)
54
+ if (name === undefined || definitions[name] === undefined || seen.has(name)) return true
55
+ if (hasUnresolvedRef(definitions[name], definitions, new Set([...seen, name]), nextVisited)) return true
56
+ }
57
+ return [
58
+ ...(schema.anyOf ?? []),
59
+ ...(schema.oneOf ?? []),
60
+ ...(schema.allOf ?? []),
61
+ ...Object.values(schema.properties ?? {}),
62
+ ...(schema.items === undefined ? [] : [schema.items]),
63
+ ...(typeof schema.additionalProperties === "object" ? [schema.additionalProperties] : []),
64
+ ].some((item) => hasUnresolvedRef(item, definitions, seen, nextVisited))
65
+ }
66
+
67
+ /**
68
+ * Schema constraints a TypeScript type cannot express natively but a model benefits from,
69
+ * surfaced as JSDoc tags (`@deprecated`, `@default`, `@format`, `@minItems`, `@maxItems`).
70
+ */
71
+ const docTags = (schema: JsonSchema): Array<string> => {
72
+ const tags: Array<string> = []
73
+ if (schema.deprecated === true) tags.push("@deprecated")
74
+ if (schema.default !== undefined) {
75
+ try {
76
+ const rendered = JSON.stringify(schema.default)
77
+ if (rendered !== undefined) tags.push(`@default ${rendered}`)
78
+ } catch {
79
+ // unserializable default: skip rather than emit a broken tag
80
+ }
81
+ }
82
+ if (typeof schema.format === "string") tags.push(`@format ${schema.format}`)
83
+ if (typeof schema.minItems === "number") tags.push(`@minItems ${schema.minItems}`)
84
+ if (typeof schema.maxItems === "number") tags.push(`@maxItems ${schema.maxItems}`)
85
+ return tags
86
+ }
87
+
88
+ /**
89
+ * Format a schema `description` plus `tags` as a JSDoc comment at the given indent,
90
+ * preserving multi-line text (a single line stays `/** ... *\/`; multiple lines become a
91
+ * `*`-prefixed block). `*\/` is neutralized so nothing can close the comment early, and
92
+ * blank leading/trailing lines are trimmed. Returns "" (else a trailing newline) so
93
+ * callers can prepend it directly to the field line.
94
+ */
95
+ const jsdoc = (description: string | undefined, tags: ReadonlyArray<string>, pad: string): string => {
96
+ const lines = [...(description === undefined ? [] : description.split("\n")), ...tags].map((line) =>
97
+ line.replaceAll("*/", "* /").replace(/\s+$/, ""),
98
+ )
99
+ while (lines.length > 0 && lines[0]!.trim() === "") lines.shift()
100
+ while (lines.length > 0 && lines[lines.length - 1]!.trim() === "") lines.pop()
101
+ if (lines.length === 0) return ""
102
+ if (lines.length === 1) return `${pad}/** ${lines[0]} */\n`
103
+ const body = lines.map((line) => `${pad} *${line === "" ? "" : ` ${line}`}`).join("\n")
104
+ return `${pad}/**\n${body}\n${pad} */\n`
105
+ }
106
+
107
+ const renderSchema = (
108
+ schema: JsonSchema,
109
+ ctx: RenderContext,
110
+ depth = 0,
111
+ seen: ReadonlySet<string> = new Set(),
112
+ ): string => {
113
+ if (depth > MAX_RENDER_DEPTH) return "unknown"
114
+ const nested =
115
+ schema.definitions === undefined && schema.$defs === undefined
116
+ ? ctx
117
+ : { ...ctx, definitions: { ...ctx.definitions, ...(schema.definitions ?? {}), ...(schema.$defs ?? {}) } }
118
+ if (schema.$ref) {
119
+ const segment = schema.$ref.match(/^#\/(?:\$defs|definitions)\/([^/]+)$/)?.[1]
120
+ const name = segment === undefined ? undefined : JsonPointer.unescapeToken(segment)
121
+ if (!name || !nested.definitions[name] || seen.has(name)) return "unknown"
122
+ return intersection([
123
+ renderSchema(nested.definitions[name], nested, depth, new Set([...seen, name])),
124
+ renderSchema({ ...schema, $ref: undefined }, nested, depth + 1, seen),
125
+ ])
126
+ }
127
+ if (schema.const !== undefined) return renderLiteral(schema.const)
128
+ if (schema.enum) return schema.enum.map(renderLiteral).join(" | ")
129
+ const alternatives = schema.anyOf ?? schema.oneOf
130
+ if (alternatives) {
131
+ // Effect's number schema emits `anyOf: [{ type: "number" }, { const: "NaN" },
132
+ // { const: "Infinity" }, { const: "-Infinity" }]`. Collapse only that artifact;
133
+ // real JSON Schema unions such as `string | number` or `number | null` must keep
134
+ // every branch.
135
+ if (
136
+ alternatives.some((item) => item.type === "number") &&
137
+ alternatives.every((item) => item.type === "number" || effectNumberSentinel(item))
138
+ )
139
+ return "number"
140
+ // An empty Schema.Struct({}) emits `anyOf: [{ type: "object" }, { type: "array" }]`
141
+ // (no properties/items); render the bare shape as {} instead of `{} | Array<unknown>`.
142
+ if (
143
+ alternatives.length === 2 &&
144
+ alternatives[0]?.type === "object" &&
145
+ alternatives[0].properties === undefined &&
146
+ alternatives[1]?.type === "array" &&
147
+ alternatives[1].items === undefined
148
+ ) {
149
+ return "{}"
150
+ }
151
+ const members = alternatives.map((item) => renderSchema(item, nested, depth + 1, seen))
152
+ if (members.some((member) => member === "unknown")) return "unknown"
153
+ return intersection([
154
+ members.join(" | "),
155
+ renderSchema({ ...schema, anyOf: undefined, oneOf: undefined }, nested, depth + 1, seen),
156
+ ])
157
+ }
158
+ if (schema.allOf) {
159
+ const members = schema.allOf.map((item) => renderSchema(item, nested, depth + 1, seen))
160
+ if (schema.allOf.some((item) => hasUnresolvedRef(item, nested.definitions))) return "unknown"
161
+ return intersection([renderSchema({ ...schema, allOf: undefined }, nested, depth + 1, seen), ...members])
162
+ }
163
+ if (Array.isArray(schema.type)) {
164
+ return schema.type.map((item) => renderSchema({ ...schema, type: item }, nested, depth + 1, seen)).join(" | ")
165
+ }
166
+ if (schema.type === "string") return "string"
167
+ if (schema.type === "number" || schema.type === "integer") return "number"
168
+ if (schema.type === "boolean") return "boolean"
169
+ if (schema.type === "null") return "null"
170
+ if (schema.type === "array") return `Array<${renderSchema(schema.items ?? {}, nested, depth + 1, seen)}>`
171
+ if (schema.type === "object" || schema.properties) {
172
+ const required = new Set(schema.required ?? [])
173
+ const properties = Object.entries(schema.properties ?? {})
174
+ const additional = schema.additionalProperties
175
+ const indexType =
176
+ additional && typeof additional === "object" ? renderSchema(additional, nested, depth + 1, seen) : undefined
177
+ const field = ([name, value]: readonly [string, JsonSchema]) =>
178
+ `${renderKey(name)}${required.has(name) ? "" : "?"}: ${renderSchema(value, nested, depth + 1, seen)}`
179
+
180
+ if (!ctx.pretty) {
181
+ const fields = properties.map(field)
182
+ if (indexType !== undefined) fields.push(`[key: string]: ${indexType}`)
183
+ return fields.length === 0 ? "{}" : `{ ${fields.join("; ")} }`
184
+ }
185
+
186
+ // Pretty: an indented block, each described field preceded by its JSDoc comment.
187
+ if (properties.length === 0 && indexType === undefined) return "{}"
188
+ const pad = " ".repeat(depth + 1)
189
+ const lines = properties.map(
190
+ (entry) => `${jsdoc(entry[1].description, docTags(entry[1]), pad)}${pad}${field(entry)},`,
191
+ )
192
+ if (indexType !== undefined) lines.push(`${pad}[key: string]: ${indexType},`)
193
+ return `{\n${lines.join("\n")}\n${" ".repeat(depth)}}`
194
+ }
195
+ return "unknown"
196
+ }
197
+
198
+ export const toTypeScript = (schema: Schema.Top, decoded = false, pretty = false): string => {
199
+ try {
200
+ const visible = decoded ? Schema.toType(schema) : schema
201
+ const document = Schema.toJsonSchemaDocument(visible) as {
202
+ readonly schema: JsonSchema
203
+ readonly definitions?: Readonly<Record<string, JsonSchema>>
204
+ }
205
+ return renderSchema(document.schema, { definitions: document.definitions ?? {}, pretty })
206
+ } catch {
207
+ return "unknown"
208
+ }
209
+ }
210
+
211
+ /** Renders a raw JSON Schema document as a TypeScript type string. */
212
+ export const jsonSchemaToTypeScript = (schema: JsonSchema, pretty = false): string => {
213
+ try {
214
+ return renderSchema(schema, { definitions: { ...(schema.definitions ?? {}), ...(schema.$defs ?? {}) }, pretty })
215
+ } catch {
216
+ return "unknown"
217
+ }
218
+ }
219
+
220
+ /** One input property of a tool, extracted best-effort from its input schema. */
221
+ export type InputProperty = {
222
+ readonly name: string
223
+ readonly description: string | undefined
224
+ readonly required: boolean
225
+ }
226
+
227
+ /**
228
+ * The property names, descriptions, and required flags of a tool's input schema - the raw
229
+ * material for search text. Best-effort: Effect Schemas go through their
230
+ * JSON Schema document (the same emission signature rendering uses); JSON Schemas are read
231
+ * directly, resolving a trivial top-level `$ref` into `$defs`/`definitions` when present.
232
+ * Anything unresolvable yields `[]` (search falls back to path + description).
233
+ */
234
+ export const inputProperties = <R>(definition: Definition<R>): Array<InputProperty> => {
235
+ try {
236
+ const document = isEffectSchema(definition.input)
237
+ ? (Schema.toJsonSchemaDocument(definition.input) as {
238
+ readonly schema: JsonSchema
239
+ readonly definitions?: Readonly<Record<string, JsonSchema>>
240
+ })
241
+ : {
242
+ schema: definition.input,
243
+ definitions: { ...(definition.input.definitions ?? {}), ...(definition.input.$defs ?? {}) },
244
+ }
245
+ const definitions = document.definitions ?? {}
246
+ let schema = document.schema
247
+ if (schema.$ref !== undefined) {
248
+ const segment = schema.$ref.match(/^#\/(?:\$defs|definitions)\/([^/]+)$/)?.[1]
249
+ const name = segment === undefined ? undefined : JsonPointer.unescapeToken(segment)
250
+ const resolved = name === undefined ? undefined : definitions[name]
251
+ if (resolved === undefined) return []
252
+ schema = resolved
253
+ }
254
+ const required = new Set(schema.required ?? [])
255
+ return Object.entries(schema.properties ?? {}).map(([name, value]) => ({
256
+ name,
257
+ description: typeof value.description === "string" ? value.description : undefined,
258
+ required: required.has(name),
259
+ }))
260
+ } catch {
261
+ return []
262
+ }
263
+ }
264
+
265
+ /**
266
+ * The model-visible TypeScript type of a tool's input. `pretty` renders an indented
267
+ * multiline block with schema descriptions and constraints as JSDoc comments on the
268
+ * fields; the default stays the compact single-line form.
269
+ */
270
+ export const inputTypeScript = <R>(definition: Definition<R>, pretty = false): string =>
271
+ isEffectSchema(definition.input)
272
+ ? toTypeScript(definition.input, false, pretty)
273
+ : jsonSchemaToTypeScript(definition.input, pretty)
274
+
275
+ /**
276
+ * The model-visible TypeScript type of a tool's result; tools without an output schema
277
+ * return `unknown`. `pretty` renders the JSDoc-annotated multiline form, as for inputs.
278
+ */
279
+ export const outputTypeScript = <R>(definition: Definition<R>, pretty = false): string =>
280
+ definition.output === undefined
281
+ ? "unknown"
282
+ : isEffectSchema(definition.output)
283
+ ? toTypeScript(definition.output, true, pretty)
284
+ : jsonSchemaToTypeScript(definition.output, pretty)
285
+
286
+ /**
287
+ * Decodes tool input before `run` is invoked. Effect Schemas validate (throwing on failure);
288
+ * JSON-Schema-described inputs pass through unvalidated (render-only).
289
+ */
290
+ export const decodeInput = <R>(definition: Definition<R>, value: unknown): unknown =>
291
+ isEffectSchema(definition.input) ? Schema.decodeUnknownSync(definition.input)(value) : value
292
+
293
+ /**
294
+ * Decodes a tool result before it is exposed to the program. Effect Schemas validate and
295
+ * transform (throwing on failure); JSON Schema outputs and tools without an output schema pass
296
+ * the host value through unchanged.
297
+ */
298
+ export const decodeOutput = <R>(definition: Definition<R>, value: unknown): unknown =>
299
+ definition.output !== undefined && isEffectSchema(definition.output)
300
+ ? Schema.decodeUnknownSync(definition.output)(value)
301
+ : value
package/src/tool.ts ADDED
@@ -0,0 +1,96 @@
1
+ import { Effect, Schema } from "effect"
2
+
3
+ /**
4
+ * JSON Schema subset accepted for render-only tool schemas.
5
+ *
6
+ * A JSON-Schema-described side of a tool is used to generate the model-visible TypeScript
7
+ * signature only - CodeMode performs no validation against it. This is the natural shape for
8
+ * adapter-provided tools (e.g. MCP definitions) whose schemas arrive as JSON Schema documents.
9
+ */
10
+ export type JsonSchema = {
11
+ readonly type?: string | ReadonlyArray<string>
12
+ readonly enum?: ReadonlyArray<unknown>
13
+ readonly const?: unknown
14
+ readonly anyOf?: ReadonlyArray<JsonSchema>
15
+ readonly oneOf?: ReadonlyArray<JsonSchema>
16
+ readonly allOf?: ReadonlyArray<JsonSchema>
17
+ readonly properties?: Readonly<Record<string, JsonSchema>>
18
+ readonly required?: ReadonlyArray<string>
19
+ readonly items?: JsonSchema
20
+ readonly additionalProperties?: boolean | JsonSchema
21
+ readonly description?: string
22
+ readonly default?: unknown
23
+ readonly format?: string
24
+ readonly deprecated?: boolean
25
+ readonly minItems?: number
26
+ readonly maxItems?: number
27
+ readonly $ref?: string
28
+ readonly $defs?: Readonly<Record<string, JsonSchema>>
29
+ readonly definitions?: Readonly<Record<string, JsonSchema>>
30
+ }
31
+
32
+ /** Either a validating Effect Schema or a render-only JSON Schema document. */
33
+ export type SchemaType = Schema.Decoder<unknown> | JsonSchema
34
+
35
+ /** Schema-backed tool definition consumed by a CodeMode tool tree. */
36
+ export type Definition<R = never> = {
37
+ readonly _tag: "CodeModeTool"
38
+ readonly description: string
39
+ readonly input: SchemaType
40
+ readonly output: SchemaType | undefined
41
+ readonly run: (input: unknown) => Effect.Effect<unknown, unknown, R>
42
+ }
43
+
44
+ /** The value `run` receives: the decoded type for Effect Schemas, `unknown` for JSON Schemas. */
45
+ type InputType<S> = S extends Schema.Decoder<unknown> ? S["Type"] : unknown
46
+
47
+ /** The value `run` returns: the encoded type for Effect Schemas, `unknown` otherwise. */
48
+ type ResultType<S> = S extends Schema.Decoder<unknown> ? S["Encoded"] : unknown
49
+
50
+ /** Options for defining one CodeMode tool. */
51
+ export type Options<I extends SchemaType, O extends SchemaType | undefined, R = never> = {
52
+ readonly description: string
53
+ readonly input: I
54
+ readonly output?: O
55
+ readonly run: (input: InputType<I>) => Effect.Effect<ResultType<O>, unknown, R>
56
+ }
57
+
58
+ export const isDefinition = <R = never>(value: unknown): value is Definition<R> =>
59
+ typeof value === "object" && value !== null && "_tag" in value && value._tag === "CodeModeTool"
60
+
61
+ /**
62
+ * Defines one schema-described tool available to a CodeMode program through `tools.*`.
63
+ *
64
+ * `input` and `output` each accept a validating Effect Schema or a render-only JSON Schema
65
+ * document. Effect Schema input is decoded before `run` is invoked, and `run` returns the
66
+ * encoded representation of an Effect Schema `output`, which CodeMode decodes before returning
67
+ * it to the program. JSON Schemas only shape the model-visible signature; values pass through
68
+ * unvalidated. `output` is optional - without it the signature advertises `unknown` and the
69
+ * host result is exposed as-is. The host tool remains responsible for authorization and
70
+ * durable side-effect handling.
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * const lookup = Tool.make({
75
+ * description: "Look up an order",
76
+ * input: Schema.Struct({ id: Schema.String }),
77
+ * output: Schema.Struct({ status: Schema.String }),
78
+ * run: ({ id }) => Effect.succeed({ status: "open" }),
79
+ * })
80
+ *
81
+ * const fromJsonSchema = Tool.make({
82
+ * description: "Call an adapter-described tool",
83
+ * input: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
84
+ * run: (input) => callHost(input),
85
+ * })
86
+ * ```
87
+ */
88
+ export const make = <I extends SchemaType, const O extends SchemaType | undefined = undefined, R = never>(
89
+ options: Options<I, O, R>,
90
+ ): Definition<R> => ({
91
+ _tag: "CodeModeTool",
92
+ description: options.description,
93
+ input: options.input,
94
+ output: options.output,
95
+ run: (input) => options.run(input as InputType<I>),
96
+ })
package/src/values.ts ADDED
@@ -0,0 +1,49 @@
1
+ import type { Effect, Fiber } from "effect"
2
+
3
+ export class SandboxPromise {
4
+ interrupted = false
5
+ constructor(
6
+ readonly fiber: Fiber.Fiber<unknown, unknown> | undefined,
7
+ readonly immediate?: Effect.Effect<unknown, unknown>,
8
+ ) {}
9
+ }
10
+
11
+ export class SandboxDate {
12
+ constructor(readonly time: number) {}
13
+ }
14
+
15
+ export class SandboxRegExp {
16
+ readonly regex: RegExp
17
+ constructor(pattern: string, flags: string) {
18
+ this.regex = new RegExp(pattern, flags)
19
+ }
20
+ }
21
+
22
+ export class SandboxMap {
23
+ readonly map = new Map<unknown, unknown>()
24
+ }
25
+
26
+ export class SandboxSet {
27
+ readonly set = new Set<unknown>()
28
+ }
29
+
30
+ export class SandboxURLSearchParams {
31
+ constructor(readonly params: URLSearchParams) {}
32
+ }
33
+
34
+ export class SandboxURL {
35
+ readonly searchParams: SandboxURLSearchParams
36
+ constructor(readonly url: URL) {
37
+ this.searchParams = new SandboxURLSearchParams(url.searchParams)
38
+ }
39
+ }
40
+
41
+ export const isSandboxValue = (
42
+ value: unknown,
43
+ ): value is SandboxDate | SandboxRegExp | SandboxMap | SandboxSet | SandboxURL | SandboxURLSearchParams =>
44
+ value instanceof SandboxDate ||
45
+ value instanceof SandboxRegExp ||
46
+ value instanceof SandboxMap ||
47
+ value instanceof SandboxSet ||
48
+ value instanceof SandboxURL ||
49
+ value instanceof SandboxURLSearchParams
package/sst-env.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ /* This file is auto-generated by SST. Do not edit. */
2
+ /* tslint:disable */
3
+ /* eslint-disable */
4
+ /* deno-fmt-ignore-file */
5
+ /* biome-ignore-all lint: auto-generated */
6
+
7
+ /// <reference path="../../sst-env.d.ts" />
8
+
9
+ import "sst"
10
+ export {}