@cloudflare/codemode 0.1.1 → 0.1.2
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/package.json +7 -3
- package/CHANGELOG.md +0 -109
- package/e2e/codemode.spec.ts +0 -124
- package/e2e/playwright.config.ts +0 -24
- package/e2e/worker.ts +0 -144
- package/e2e/wrangler.jsonc +0 -14
- package/scripts/build.ts +0 -25
- package/src/ai.ts +0 -1
- package/src/executor.ts +0 -170
- package/src/index.ts +0 -13
- package/src/tests/cloudflare-test.d.ts +0 -5
- package/src/tests/executor.test.ts +0 -224
- package/src/tests/schema-conversion.test.ts +0 -1068
- package/src/tests/tool.test.ts +0 -454
- package/src/tests/tsconfig.json +0 -10
- package/src/tests/types.test.ts +0 -446
- package/src/tool.ts +0 -152
- package/src/types.ts +0 -677
- package/tsconfig.json +0 -4
- package/vitest.config.ts +0 -17
- package/wrangler.jsonc +0 -16
package/src/types.ts
DELETED
|
@@ -1,677 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
zodToTs,
|
|
3
|
-
printNode as printNodeZodToTs,
|
|
4
|
-
createTypeAlias,
|
|
5
|
-
createAuxiliaryTypeStore
|
|
6
|
-
} from "zod-to-ts";
|
|
7
|
-
import type { ZodType } from "zod";
|
|
8
|
-
import type { ToolSet } from "ai";
|
|
9
|
-
import type { JSONSchema7, JSONSchema7Definition } from "json-schema";
|
|
10
|
-
|
|
11
|
-
interface ConversionContext {
|
|
12
|
-
root: JSONSchema7;
|
|
13
|
-
depth: number;
|
|
14
|
-
seen: Set<unknown>;
|
|
15
|
-
maxDepth: number;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const JS_RESERVED = new Set([
|
|
19
|
-
"abstract",
|
|
20
|
-
"arguments",
|
|
21
|
-
"await",
|
|
22
|
-
"boolean",
|
|
23
|
-
"break",
|
|
24
|
-
"byte",
|
|
25
|
-
"case",
|
|
26
|
-
"catch",
|
|
27
|
-
"char",
|
|
28
|
-
"class",
|
|
29
|
-
"const",
|
|
30
|
-
"continue",
|
|
31
|
-
"debugger",
|
|
32
|
-
"default",
|
|
33
|
-
"delete",
|
|
34
|
-
"do",
|
|
35
|
-
"double",
|
|
36
|
-
"else",
|
|
37
|
-
"enum",
|
|
38
|
-
"eval",
|
|
39
|
-
"export",
|
|
40
|
-
"extends",
|
|
41
|
-
"false",
|
|
42
|
-
"final",
|
|
43
|
-
"finally",
|
|
44
|
-
"float",
|
|
45
|
-
"for",
|
|
46
|
-
"function",
|
|
47
|
-
"goto",
|
|
48
|
-
"if",
|
|
49
|
-
"implements",
|
|
50
|
-
"import",
|
|
51
|
-
"in",
|
|
52
|
-
"instanceof",
|
|
53
|
-
"int",
|
|
54
|
-
"interface",
|
|
55
|
-
"let",
|
|
56
|
-
"long",
|
|
57
|
-
"native",
|
|
58
|
-
"new",
|
|
59
|
-
"null",
|
|
60
|
-
"package",
|
|
61
|
-
"private",
|
|
62
|
-
"protected",
|
|
63
|
-
"public",
|
|
64
|
-
"return",
|
|
65
|
-
"short",
|
|
66
|
-
"static",
|
|
67
|
-
"super",
|
|
68
|
-
"switch",
|
|
69
|
-
"synchronized",
|
|
70
|
-
"this",
|
|
71
|
-
"throw",
|
|
72
|
-
"throws",
|
|
73
|
-
"transient",
|
|
74
|
-
"true",
|
|
75
|
-
"try",
|
|
76
|
-
"typeof",
|
|
77
|
-
"undefined",
|
|
78
|
-
"var",
|
|
79
|
-
"void",
|
|
80
|
-
"volatile",
|
|
81
|
-
"while",
|
|
82
|
-
"with",
|
|
83
|
-
"yield"
|
|
84
|
-
]);
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Sanitize a tool name into a valid JavaScript identifier.
|
|
88
|
-
* Replaces hyphens, dots, and spaces with `_`, strips other invalid chars,
|
|
89
|
-
* prefixes digit-leading names with `_`, and appends `_` to JS reserved words.
|
|
90
|
-
*/
|
|
91
|
-
export function sanitizeToolName(name: string): string {
|
|
92
|
-
if (!name) return "_";
|
|
93
|
-
|
|
94
|
-
// Replace common separators with underscores
|
|
95
|
-
let sanitized = name.replace(/[-.\s]/g, "_");
|
|
96
|
-
|
|
97
|
-
// Strip any remaining non-identifier characters
|
|
98
|
-
sanitized = sanitized.replace(/[^a-zA-Z0-9_$]/g, "");
|
|
99
|
-
|
|
100
|
-
if (!sanitized) return "_";
|
|
101
|
-
|
|
102
|
-
// Prefix with _ if starts with a digit
|
|
103
|
-
if (/^[0-9]/.test(sanitized)) {
|
|
104
|
-
sanitized = "_" + sanitized;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Append _ to reserved words
|
|
108
|
-
if (JS_RESERVED.has(sanitized)) {
|
|
109
|
-
sanitized = sanitized + "_";
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return sanitized;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function toCamelCase(str: string) {
|
|
116
|
-
return str
|
|
117
|
-
.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase())
|
|
118
|
-
.replace(/^[a-z]/, (letter) => letter.toUpperCase());
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Extract field descriptions from a schema and format as @param lines.
|
|
123
|
-
* Returns an array of `@param input.fieldName - description` lines.
|
|
124
|
-
*/
|
|
125
|
-
function extractParamDescriptions(schema: unknown): string[] {
|
|
126
|
-
const descriptions = extractDescriptions(schema);
|
|
127
|
-
return Object.entries(descriptions).map(
|
|
128
|
-
([fieldName, desc]) => `@param input.${fieldName} - ${desc}`
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export interface ToolDescriptor {
|
|
133
|
-
description?: string;
|
|
134
|
-
inputSchema: ZodType;
|
|
135
|
-
outputSchema?: ZodType;
|
|
136
|
-
execute?: (args: unknown) => Promise<unknown>;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export type ToolDescriptors = Record<string, ToolDescriptor>;
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Check if a value is a Zod schema (has _zod property).
|
|
143
|
-
*/
|
|
144
|
-
function isZodSchema(value: unknown): value is ZodType {
|
|
145
|
-
return (
|
|
146
|
-
value !== null &&
|
|
147
|
-
typeof value === "object" &&
|
|
148
|
-
"_zod" in value &&
|
|
149
|
-
(value as { _zod?: unknown })._zod !== undefined
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Check if a value is an AI SDK jsonSchema wrapper.
|
|
155
|
-
* The jsonSchema wrapper has a [Symbol] with jsonSchema property.
|
|
156
|
-
*/
|
|
157
|
-
function isJsonSchemaWrapper(
|
|
158
|
-
value: unknown
|
|
159
|
-
): value is { jsonSchema: JSONSchema7 } {
|
|
160
|
-
if (value === null || typeof value !== "object") return false;
|
|
161
|
-
|
|
162
|
-
// AI SDK jsonSchema wrapper stores data in a symbol property
|
|
163
|
-
// but also exposes jsonSchema directly in some versions
|
|
164
|
-
if ("jsonSchema" in value) {
|
|
165
|
-
return true;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Check for symbol-based storage (AI SDK internal)
|
|
169
|
-
const symbols = Object.getOwnPropertySymbols(value);
|
|
170
|
-
for (const sym of symbols) {
|
|
171
|
-
const symValue = (value as Record<symbol, unknown>)[sym];
|
|
172
|
-
if (symValue && typeof symValue === "object" && "jsonSchema" in symValue) {
|
|
173
|
-
return true;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return false;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Extract JSON schema from an AI SDK jsonSchema wrapper.
|
|
182
|
-
*/
|
|
183
|
-
function extractJsonSchema(wrapper: unknown): JSONSchema7 | null {
|
|
184
|
-
if (wrapper === null || typeof wrapper !== "object") return null;
|
|
185
|
-
|
|
186
|
-
// Direct property access
|
|
187
|
-
if ("jsonSchema" in wrapper) {
|
|
188
|
-
return (wrapper as { jsonSchema: JSONSchema7 }).jsonSchema;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Symbol-based storage
|
|
192
|
-
const symbols = Object.getOwnPropertySymbols(wrapper);
|
|
193
|
-
for (const sym of symbols) {
|
|
194
|
-
const symValue = (wrapper as Record<symbol, unknown>)[sym];
|
|
195
|
-
if (symValue && typeof symValue === "object" && "jsonSchema" in symValue) {
|
|
196
|
-
return (symValue as { jsonSchema: JSONSchema7 }).jsonSchema;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return null;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Check if a property name needs quoting in TypeScript.
|
|
205
|
-
*/
|
|
206
|
-
function needsQuotes(name: string): boolean {
|
|
207
|
-
// Valid JS identifier: starts with letter, $, or _, followed by letters, digits, $, _
|
|
208
|
-
return !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Escape a character as a unicode escape sequence if it is a control character.
|
|
213
|
-
*/
|
|
214
|
-
function escapeControlChar(ch: string): string {
|
|
215
|
-
const code = ch.charCodeAt(0);
|
|
216
|
-
if (code <= 0x1f || code === 0x7f) {
|
|
217
|
-
return "\\u" + code.toString(16).padStart(4, "0");
|
|
218
|
-
}
|
|
219
|
-
return ch;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Quote a property name if needed.
|
|
224
|
-
* Escapes backslashes, quotes, and control characters.
|
|
225
|
-
*/
|
|
226
|
-
function quoteProp(name: string): string {
|
|
227
|
-
if (needsQuotes(name)) {
|
|
228
|
-
let escaped = "";
|
|
229
|
-
for (const ch of name) {
|
|
230
|
-
if (ch === "\\") escaped += "\\\\";
|
|
231
|
-
else if (ch === '"') escaped += '\\"';
|
|
232
|
-
else if (ch === "\n") escaped += "\\n";
|
|
233
|
-
else if (ch === "\r") escaped += "\\r";
|
|
234
|
-
else if (ch === "\t") escaped += "\\t";
|
|
235
|
-
else if (ch === "\u2028") escaped += "\\u2028";
|
|
236
|
-
else if (ch === "\u2029") escaped += "\\u2029";
|
|
237
|
-
else escaped += escapeControlChar(ch);
|
|
238
|
-
}
|
|
239
|
-
return `"${escaped}"`;
|
|
240
|
-
}
|
|
241
|
-
return name;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Escape a string for use inside a double-quoted TypeScript string literal.
|
|
246
|
-
* Handles backslashes, quotes, newlines, control characters, and line/paragraph separators.
|
|
247
|
-
*/
|
|
248
|
-
function escapeStringLiteral(s: string): string {
|
|
249
|
-
let out = "";
|
|
250
|
-
for (const ch of s) {
|
|
251
|
-
if (ch === "\\") out += "\\\\";
|
|
252
|
-
else if (ch === '"') out += '\\"';
|
|
253
|
-
else if (ch === "\n") out += "\\n";
|
|
254
|
-
else if (ch === "\r") out += "\\r";
|
|
255
|
-
else if (ch === "\t") out += "\\t";
|
|
256
|
-
else if (ch === "\u2028") out += "\\u2028";
|
|
257
|
-
else if (ch === "\u2029") out += "\\u2029";
|
|
258
|
-
else out += escapeControlChar(ch);
|
|
259
|
-
}
|
|
260
|
-
return out;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Escape a string for use inside a JSDoc comment.
|
|
265
|
-
* Prevents premature comment closure from star-slash sequences.
|
|
266
|
-
*/
|
|
267
|
-
function escapeJsDoc(text: string): string {
|
|
268
|
-
return text.replace(/\*\//g, "*\\/");
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* Resolve an internal JSON Pointer $ref (e.g. #/definitions/Foo) against the root schema.
|
|
273
|
-
* Returns null for external URLs or unresolvable paths.
|
|
274
|
-
*/
|
|
275
|
-
function resolveRef(
|
|
276
|
-
ref: string,
|
|
277
|
-
root: JSONSchema7
|
|
278
|
-
): JSONSchema7Definition | null {
|
|
279
|
-
// "#" is a valid self-reference to the root schema
|
|
280
|
-
if (ref === "#") return root;
|
|
281
|
-
|
|
282
|
-
if (!ref.startsWith("#/")) return null;
|
|
283
|
-
|
|
284
|
-
const segments = ref
|
|
285
|
-
.slice(2)
|
|
286
|
-
.split("/")
|
|
287
|
-
.map((s) => s.replace(/~1/g, "/").replace(/~0/g, "~"));
|
|
288
|
-
|
|
289
|
-
let current: unknown = root;
|
|
290
|
-
for (const seg of segments) {
|
|
291
|
-
if (current === null || typeof current !== "object") return null;
|
|
292
|
-
current = (current as Record<string, unknown>)[seg];
|
|
293
|
-
if (current === undefined) return null;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Allow both object schemas and boolean schemas (true = any, false = never)
|
|
297
|
-
if (typeof current === "boolean") return current;
|
|
298
|
-
if (current === null || typeof current !== "object") return null;
|
|
299
|
-
return current as JSONSchema7;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Convert a JSON Schema to a TypeScript type string.
|
|
304
|
-
* This is a direct conversion without going through Zod.
|
|
305
|
-
*/
|
|
306
|
-
function jsonSchemaToTypeString(
|
|
307
|
-
schema: JSONSchema7Definition,
|
|
308
|
-
indent: string,
|
|
309
|
-
ctx: ConversionContext
|
|
310
|
-
): string {
|
|
311
|
-
// Handle boolean schemas
|
|
312
|
-
if (typeof schema === "boolean") {
|
|
313
|
-
return schema ? "unknown" : "never";
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// Depth guard
|
|
317
|
-
if (ctx.depth >= ctx.maxDepth) return "unknown";
|
|
318
|
-
|
|
319
|
-
// Circular reference guard
|
|
320
|
-
if (ctx.seen.has(schema)) return "unknown";
|
|
321
|
-
|
|
322
|
-
const nextCtx: ConversionContext = {
|
|
323
|
-
...ctx,
|
|
324
|
-
depth: ctx.depth + 1,
|
|
325
|
-
seen: new Set([...ctx.seen, schema])
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
// Handle $ref
|
|
329
|
-
if (schema.$ref) {
|
|
330
|
-
const resolved = resolveRef(schema.$ref, ctx.root);
|
|
331
|
-
if (!resolved) return "unknown";
|
|
332
|
-
return applyNullable(
|
|
333
|
-
jsonSchemaToTypeString(resolved, indent, nextCtx),
|
|
334
|
-
schema
|
|
335
|
-
);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// Handle anyOf/oneOf (union types)
|
|
339
|
-
if (schema.anyOf) {
|
|
340
|
-
const types = schema.anyOf.map((s) =>
|
|
341
|
-
jsonSchemaToTypeString(s, indent, nextCtx)
|
|
342
|
-
);
|
|
343
|
-
return applyNullable(types.join(" | "), schema);
|
|
344
|
-
}
|
|
345
|
-
if (schema.oneOf) {
|
|
346
|
-
const types = schema.oneOf.map((s) =>
|
|
347
|
-
jsonSchemaToTypeString(s, indent, nextCtx)
|
|
348
|
-
);
|
|
349
|
-
return applyNullable(types.join(" | "), schema);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// Handle allOf (intersection types)
|
|
353
|
-
if (schema.allOf) {
|
|
354
|
-
const types = schema.allOf.map((s) =>
|
|
355
|
-
jsonSchemaToTypeString(s, indent, nextCtx)
|
|
356
|
-
);
|
|
357
|
-
return applyNullable(types.join(" & "), schema);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// Handle enum
|
|
361
|
-
if (schema.enum) {
|
|
362
|
-
if (schema.enum.length === 0) return "never";
|
|
363
|
-
const result = schema.enum
|
|
364
|
-
.map((v) => {
|
|
365
|
-
if (v === null) return "null";
|
|
366
|
-
if (typeof v === "string") return '"' + escapeStringLiteral(v) + '"';
|
|
367
|
-
if (typeof v === "object") return JSON.stringify(v) ?? "unknown";
|
|
368
|
-
return String(v);
|
|
369
|
-
})
|
|
370
|
-
.join(" | ");
|
|
371
|
-
return applyNullable(result, schema);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// Handle const
|
|
375
|
-
if (schema.const !== undefined) {
|
|
376
|
-
const result =
|
|
377
|
-
schema.const === null
|
|
378
|
-
? "null"
|
|
379
|
-
: typeof schema.const === "string"
|
|
380
|
-
? '"' + escapeStringLiteral(schema.const) + '"'
|
|
381
|
-
: typeof schema.const === "object"
|
|
382
|
-
? (JSON.stringify(schema.const) ?? "unknown")
|
|
383
|
-
: String(schema.const);
|
|
384
|
-
return applyNullable(result, schema);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// Handle type
|
|
388
|
-
const type = schema.type;
|
|
389
|
-
|
|
390
|
-
if (type === "string") return applyNullable("string", schema);
|
|
391
|
-
if (type === "number" || type === "integer")
|
|
392
|
-
return applyNullable("number", schema);
|
|
393
|
-
if (type === "boolean") return applyNullable("boolean", schema);
|
|
394
|
-
if (type === "null") return "null";
|
|
395
|
-
|
|
396
|
-
if (type === "array") {
|
|
397
|
-
// Tuple support: prefixItems (JSON Schema 2020-12)
|
|
398
|
-
const prefixItems = (schema as Record<string, unknown>)
|
|
399
|
-
.prefixItems as JSONSchema7Definition[];
|
|
400
|
-
if (Array.isArray(prefixItems)) {
|
|
401
|
-
const types = prefixItems.map((s) =>
|
|
402
|
-
jsonSchemaToTypeString(s, indent, nextCtx)
|
|
403
|
-
);
|
|
404
|
-
return applyNullable(`[${types.join(", ")}]`, schema);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// Tuple support: items as array (draft-07)
|
|
408
|
-
if (Array.isArray(schema.items)) {
|
|
409
|
-
const types = schema.items.map((s) =>
|
|
410
|
-
jsonSchemaToTypeString(s, indent, nextCtx)
|
|
411
|
-
);
|
|
412
|
-
return applyNullable(`[${types.join(", ")}]`, schema);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
if (schema.items) {
|
|
416
|
-
const itemType = jsonSchemaToTypeString(schema.items, indent, nextCtx);
|
|
417
|
-
return applyNullable(`${itemType}[]`, schema);
|
|
418
|
-
}
|
|
419
|
-
return applyNullable("unknown[]", schema);
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
if (type === "object" || schema.properties) {
|
|
423
|
-
const props = schema.properties || {};
|
|
424
|
-
const required = new Set(schema.required || []);
|
|
425
|
-
const lines: string[] = [];
|
|
426
|
-
|
|
427
|
-
for (const [propName, propSchema] of Object.entries(props)) {
|
|
428
|
-
if (typeof propSchema === "boolean") {
|
|
429
|
-
const boolType = propSchema ? "unknown" : "never";
|
|
430
|
-
const optionalMark = required.has(propName) ? "" : "?";
|
|
431
|
-
lines.push(
|
|
432
|
-
`${indent} ${quoteProp(propName)}${optionalMark}: ${boolType};`
|
|
433
|
-
);
|
|
434
|
-
continue;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
const isRequired = required.has(propName);
|
|
438
|
-
const propType = jsonSchemaToTypeString(
|
|
439
|
-
propSchema,
|
|
440
|
-
indent + " ",
|
|
441
|
-
nextCtx
|
|
442
|
-
);
|
|
443
|
-
const desc = propSchema.description;
|
|
444
|
-
const format = propSchema.format;
|
|
445
|
-
|
|
446
|
-
if (desc || format) {
|
|
447
|
-
const descText = desc
|
|
448
|
-
? escapeJsDoc(desc.replace(/\r?\n/g, " "))
|
|
449
|
-
: undefined;
|
|
450
|
-
const formatTag = format ? `@format ${escapeJsDoc(format)}` : undefined;
|
|
451
|
-
|
|
452
|
-
if (descText && formatTag) {
|
|
453
|
-
// Multi-line JSDoc when both description and format are present
|
|
454
|
-
lines.push(`${indent} /**`);
|
|
455
|
-
lines.push(`${indent} * ${descText}`);
|
|
456
|
-
lines.push(`${indent} * ${formatTag}`);
|
|
457
|
-
lines.push(`${indent} */`);
|
|
458
|
-
} else {
|
|
459
|
-
lines.push(`${indent} /** ${descText ?? formatTag} */`);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
const quotedName = quoteProp(propName);
|
|
464
|
-
const optionalMark = isRequired ? "" : "?";
|
|
465
|
-
lines.push(`${indent} ${quotedName}${optionalMark}: ${propType};`);
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// Handle additionalProperties
|
|
469
|
-
// NOTE: In TypeScript, an index signature [key: string]: T requires all
|
|
470
|
-
// named properties to be assignable to T. If any named property has an
|
|
471
|
-
// incompatible type, the generated type is invalid. We emit it anyway
|
|
472
|
-
// since it's more informative for LLMs consuming these types.
|
|
473
|
-
if (schema.additionalProperties) {
|
|
474
|
-
const valueType =
|
|
475
|
-
schema.additionalProperties === true
|
|
476
|
-
? "unknown"
|
|
477
|
-
: jsonSchemaToTypeString(
|
|
478
|
-
schema.additionalProperties,
|
|
479
|
-
indent + " ",
|
|
480
|
-
nextCtx
|
|
481
|
-
);
|
|
482
|
-
lines.push(`${indent} [key: string]: ${valueType};`);
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
if (lines.length === 0) {
|
|
486
|
-
// additionalProperties: false means no keys allowed → empty object
|
|
487
|
-
if (schema.additionalProperties === false) {
|
|
488
|
-
return applyNullable("{}", schema);
|
|
489
|
-
}
|
|
490
|
-
return applyNullable("Record<string, unknown>", schema);
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
const result = `{\n${lines.join("\n")}\n${indent}}`;
|
|
494
|
-
return applyNullable(result, schema);
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
// Handle array of types (e.g., ["string", "null"])
|
|
498
|
-
if (Array.isArray(type)) {
|
|
499
|
-
const types = type.map((t) => {
|
|
500
|
-
if (t === "string") return "string";
|
|
501
|
-
if (t === "number" || t === "integer") return "number";
|
|
502
|
-
if (t === "boolean") return "boolean";
|
|
503
|
-
if (t === "null") return "null";
|
|
504
|
-
if (t === "array") return "unknown[]";
|
|
505
|
-
if (t === "object") return "Record<string, unknown>";
|
|
506
|
-
return "unknown";
|
|
507
|
-
});
|
|
508
|
-
return applyNullable(types.join(" | "), schema);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
return "unknown";
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
/**
|
|
515
|
-
* Apply OpenAPI 3.0 `nullable: true` to a type result.
|
|
516
|
-
*/
|
|
517
|
-
function applyNullable(result: string, schema: unknown): string {
|
|
518
|
-
if (
|
|
519
|
-
result !== "unknown" &&
|
|
520
|
-
result !== "never" &&
|
|
521
|
-
(schema as Record<string, unknown>)?.nullable === true
|
|
522
|
-
) {
|
|
523
|
-
return `${result} | null`;
|
|
524
|
-
}
|
|
525
|
-
return result;
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
/**
|
|
529
|
-
* Extract field descriptions from a schema.
|
|
530
|
-
* Works with Zod schemas (via .shape) and jsonSchema wrappers (via .properties).
|
|
531
|
-
*/
|
|
532
|
-
function extractDescriptions(schema: unknown): Record<string, string> {
|
|
533
|
-
const descriptions: Record<string, string> = {};
|
|
534
|
-
|
|
535
|
-
// Try Zod schema shape
|
|
536
|
-
const shape = (schema as { shape?: Record<string, ZodType> }).shape;
|
|
537
|
-
if (shape && typeof shape === "object") {
|
|
538
|
-
for (const [fieldName, fieldSchema] of Object.entries(shape)) {
|
|
539
|
-
const desc = (fieldSchema as { description?: string }).description;
|
|
540
|
-
if (desc) {
|
|
541
|
-
descriptions[fieldName] = desc;
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
return descriptions;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
// Try JSON Schema properties (for jsonSchema wrapper)
|
|
548
|
-
if (isJsonSchemaWrapper(schema)) {
|
|
549
|
-
const jsonSchema = extractJsonSchema(schema);
|
|
550
|
-
if (jsonSchema?.properties) {
|
|
551
|
-
for (const [fieldName, propSchema] of Object.entries(
|
|
552
|
-
jsonSchema.properties
|
|
553
|
-
)) {
|
|
554
|
-
if (
|
|
555
|
-
propSchema &&
|
|
556
|
-
typeof propSchema === "object" &&
|
|
557
|
-
propSchema.description
|
|
558
|
-
) {
|
|
559
|
-
descriptions[fieldName] = propSchema.description;
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
return descriptions;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
/**
|
|
569
|
-
* Safely convert a schema to TypeScript type string.
|
|
570
|
-
* Handles Zod schemas and AI SDK jsonSchema wrappers.
|
|
571
|
-
* Returns "unknown" if the schema cannot be represented in TypeScript.
|
|
572
|
-
*/
|
|
573
|
-
function safeSchemaToTs(
|
|
574
|
-
schema: unknown,
|
|
575
|
-
typeName: string,
|
|
576
|
-
auxiliaryTypeStore: ReturnType<typeof createAuxiliaryTypeStore>
|
|
577
|
-
): string {
|
|
578
|
-
try {
|
|
579
|
-
// For Zod schemas, use zod-to-ts
|
|
580
|
-
if (isZodSchema(schema)) {
|
|
581
|
-
const result = zodToTs(schema, { auxiliaryTypeStore });
|
|
582
|
-
return printNodeZodToTs(createTypeAlias(result.node, typeName));
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
// For JSON Schema wrapper, convert directly to TypeScript
|
|
586
|
-
if (isJsonSchemaWrapper(schema)) {
|
|
587
|
-
const jsonSchema = extractJsonSchema(schema);
|
|
588
|
-
if (jsonSchema) {
|
|
589
|
-
const ctx: ConversionContext = {
|
|
590
|
-
root: jsonSchema,
|
|
591
|
-
depth: 0,
|
|
592
|
-
seen: new Set(),
|
|
593
|
-
maxDepth: 20
|
|
594
|
-
};
|
|
595
|
-
const typeBody = jsonSchemaToTypeString(jsonSchema, "", ctx);
|
|
596
|
-
return `type ${typeName} = ${typeBody}`;
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
return `type ${typeName} = unknown`;
|
|
601
|
-
} catch {
|
|
602
|
-
// If the schema cannot be represented, fall back to unknown
|
|
603
|
-
return `type ${typeName} = unknown`;
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
/**
|
|
608
|
-
* Generate TypeScript type definitions from tool descriptors or an AI SDK ToolSet.
|
|
609
|
-
* These types can be included in tool descriptions to help LLMs write correct code.
|
|
610
|
-
*/
|
|
611
|
-
export function generateTypes(tools: ToolDescriptors | ToolSet): string {
|
|
612
|
-
let availableTools = "";
|
|
613
|
-
let availableTypes = "";
|
|
614
|
-
|
|
615
|
-
const auxiliaryTypeStore = createAuxiliaryTypeStore();
|
|
616
|
-
|
|
617
|
-
for (const [toolName, tool] of Object.entries(tools)) {
|
|
618
|
-
const safeName = sanitizeToolName(toolName);
|
|
619
|
-
const camelName = toCamelCase(safeName);
|
|
620
|
-
|
|
621
|
-
try {
|
|
622
|
-
// Handle both our ToolDescriptor and AI SDK Tool types
|
|
623
|
-
const inputSchema =
|
|
624
|
-
"inputSchema" in tool ? tool.inputSchema : tool.parameters;
|
|
625
|
-
const outputSchema =
|
|
626
|
-
"outputSchema" in tool ? tool.outputSchema : undefined;
|
|
627
|
-
const description = tool.description;
|
|
628
|
-
|
|
629
|
-
const inputType = safeSchemaToTs(
|
|
630
|
-
inputSchema,
|
|
631
|
-
`${camelName}Input`,
|
|
632
|
-
auxiliaryTypeStore
|
|
633
|
-
);
|
|
634
|
-
|
|
635
|
-
const outputType = outputSchema
|
|
636
|
-
? safeSchemaToTs(outputSchema, `${camelName}Output`, auxiliaryTypeStore)
|
|
637
|
-
: `type ${camelName}Output = unknown`;
|
|
638
|
-
|
|
639
|
-
availableTypes += `\n${inputType.trim()}`;
|
|
640
|
-
availableTypes += `\n${outputType.trim()}`;
|
|
641
|
-
|
|
642
|
-
// Build JSDoc comment with description and param descriptions
|
|
643
|
-
const paramDescs = inputSchema
|
|
644
|
-
? extractParamDescriptions(inputSchema)
|
|
645
|
-
: [];
|
|
646
|
-
const jsdocLines: string[] = [];
|
|
647
|
-
if (description?.trim()) {
|
|
648
|
-
jsdocLines.push(escapeJsDoc(description.trim().replace(/\r?\n/g, " ")));
|
|
649
|
-
} else {
|
|
650
|
-
jsdocLines.push(escapeJsDoc(toolName));
|
|
651
|
-
}
|
|
652
|
-
for (const pd of paramDescs) {
|
|
653
|
-
jsdocLines.push(escapeJsDoc(pd.replace(/\r?\n/g, " ")));
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
const jsdocBody = jsdocLines.map((l) => `\t * ${l}`).join("\n");
|
|
657
|
-
availableTools += `\n\t/**\n${jsdocBody}\n\t */`;
|
|
658
|
-
availableTools += `\n\t${safeName}: (input: ${camelName}Input) => Promise<${camelName}Output>;`;
|
|
659
|
-
availableTools += "\n";
|
|
660
|
-
} catch {
|
|
661
|
-
// One bad tool should not break the others — emit unknown types
|
|
662
|
-
availableTypes += `\ntype ${camelName}Input = unknown`;
|
|
663
|
-
availableTypes += `\ntype ${camelName}Output = unknown`;
|
|
664
|
-
|
|
665
|
-
availableTools += `\n\t/**\n\t * ${escapeJsDoc(toolName)}\n\t */`;
|
|
666
|
-
availableTools += `\n\t${safeName}: (input: ${camelName}Input) => Promise<${camelName}Output>;`;
|
|
667
|
-
availableTools += "\n";
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
availableTools = `\ndeclare const codemode: {${availableTools}}`;
|
|
672
|
-
|
|
673
|
-
return `
|
|
674
|
-
${availableTypes}
|
|
675
|
-
${availableTools}
|
|
676
|
-
`.trim();
|
|
677
|
-
}
|
package/tsconfig.json
DELETED
package/vitest.config.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config";
|
|
2
|
-
|
|
3
|
-
export default defineWorkersConfig({
|
|
4
|
-
test: {
|
|
5
|
-
name: "workers",
|
|
6
|
-
include: ["src/tests/**/*.test.ts"],
|
|
7
|
-
poolOptions: {
|
|
8
|
-
workers: {
|
|
9
|
-
isolatedStorage: false,
|
|
10
|
-
singleWorker: true,
|
|
11
|
-
wrangler: {
|
|
12
|
-
configPath: "./wrangler.jsonc"
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
});
|
package/wrangler.jsonc
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compatibility_date": "2026-01-28",
|
|
3
|
-
"compatibility_flags": [
|
|
4
|
-
"nodejs_compat",
|
|
5
|
-
"enable_nodejs_tty_module",
|
|
6
|
-
"enable_nodejs_fs_module",
|
|
7
|
-
"enable_nodejs_http_modules",
|
|
8
|
-
"enable_nodejs_perf_hooks_module"
|
|
9
|
-
],
|
|
10
|
-
"worker_loaders": [
|
|
11
|
-
{
|
|
12
|
-
"binding": "LOADER"
|
|
13
|
-
}
|
|
14
|
-
],
|
|
15
|
-
"main": "src/index.ts"
|
|
16
|
-
}
|