@ai-sdk/code-mode 0.0.1 → 1.0.0
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/LICENSE +13 -0
- package/README.md +99 -0
- package/dist/code-mode-tool.js +40 -0
- package/dist/code-mode-tool.js.map +1 -0
- package/dist/errors.js +192 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +179 -2
- package/dist/index.js +32 -1
- package/dist/index.js.map +1 -1
- package/dist/run-code-mode.js +8 -0
- package/dist/run-code-mode.js.map +1 -0
- package/dist/runtime/guest-sources.js +260 -0
- package/dist/runtime/guest-sources.js.map +1 -0
- package/dist/runtime/manager.js +414 -0
- package/dist/runtime/manager.js.map +1 -0
- package/dist/runtime/max-workers.js +40 -0
- package/dist/runtime/max-workers.js.map +1 -0
- package/dist/runtime/protocol.js +1 -0
- package/dist/runtime/protocol.js.map +1 -0
- package/dist/runtime/worker-source.d.ts +11 -0
- package/dist/runtime/worker-source.js +2 -0
- package/dist/tool-invocation.js +129 -0
- package/dist/tool-invocation.js.map +1 -0
- package/dist/tool-prompt.js +440 -0
- package/dist/tool-prompt.js.map +1 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/options.js +76 -0
- package/dist/utils/options.js.map +1 -0
- package/dist/utils/serialization.js +49 -0
- package/dist/utils/serialization.js.map +1 -0
- package/dist/utils/source-cache.js +77 -0
- package/dist/utils/source-cache.js.map +1 -0
- package/package.json +57 -16
- package/src/code-mode-tool.ts +60 -0
- package/src/errors.ts +267 -0
- package/src/index.ts +26 -0
- package/src/run-code-mode.ts +15 -0
- package/src/runtime/guest-sources.ts +260 -0
- package/src/runtime/manager.ts +498 -0
- package/src/runtime/max-workers.ts +65 -0
- package/src/runtime/protocol.ts +52 -0
- package/src/runtime/worker-source.ts +10 -0
- package/src/runtime/worker.ts +497 -0
- package/src/tool-invocation.ts +181 -0
- package/src/tool-prompt.ts +585 -0
- package/src/tsconfig.json +11 -0
- package/src/types.ts +111 -0
- package/src/utils/options.ts +83 -0
- package/src/utils/serialization.ts +66 -0
- package/src/utils/source-cache.ts +91 -0
- package/dist/index.d.ts.map +0 -1
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
import { asSchema } from 'ai';
|
|
2
|
+
import type { CodeModeToolSet } from './types.js';
|
|
3
|
+
|
|
4
|
+
type JsonSchema = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
interface SchemaContext {
|
|
7
|
+
root: JsonSchema;
|
|
8
|
+
seenRefs: Set<string>;
|
|
9
|
+
depth: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const MAX_SCHEMA_DEPTH = 8;
|
|
13
|
+
const MAX_COMPACT_OBJECT_TYPE_LENGTH = 120;
|
|
14
|
+
|
|
15
|
+
export function buildCodeModeToolDescription(tools: CodeModeToolSet): string {
|
|
16
|
+
const toolEntries = Object.entries(tools);
|
|
17
|
+
const typeBlock =
|
|
18
|
+
toolEntries.length === 0
|
|
19
|
+
? 'No host tools. Do not call `tools.*`.'
|
|
20
|
+
: [
|
|
21
|
+
'```ts',
|
|
22
|
+
'declare const tools: {',
|
|
23
|
+
...toolEntries.flatMap(renderToolType),
|
|
24
|
+
'};',
|
|
25
|
+
'```',
|
|
26
|
+
].join('\n');
|
|
27
|
+
|
|
28
|
+
const exampleBlock =
|
|
29
|
+
toolEntries.length === 0
|
|
30
|
+
? ''
|
|
31
|
+
: [
|
|
32
|
+
'',
|
|
33
|
+
'Tool call examples:',
|
|
34
|
+
'```ts',
|
|
35
|
+
...renderToolExamples(toolEntries),
|
|
36
|
+
'```',
|
|
37
|
+
].join('\n');
|
|
38
|
+
|
|
39
|
+
const sections = [
|
|
40
|
+
'Execute code-mode TypeScript in an isolated sandbox.',
|
|
41
|
+
'',
|
|
42
|
+
'Put the full program in `js`; top-level `await`/`return` work. Return a JSON-serializable result.',
|
|
43
|
+
'Call host tools only as async `tools.name(input)`; await each or use `Promise.all` for independent calls.',
|
|
44
|
+
'Use exact names/types below. `JSON.parse`/`JSON.stringify` are available.',
|
|
45
|
+
'Fetch: `fetch` is not available.',
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
sections.push('', 'Tools:', typeBlock);
|
|
49
|
+
|
|
50
|
+
if (exampleBlock.length > 0) {
|
|
51
|
+
sections.push(exampleBlock);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return sections.join('\n');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function renderToolType([toolName, tool]: [
|
|
58
|
+
string,
|
|
59
|
+
CodeModeToolSet[string],
|
|
60
|
+
]): string[] {
|
|
61
|
+
const schema = resolveInputSchema(tool);
|
|
62
|
+
const inputType =
|
|
63
|
+
schema === undefined ? 'unknown' : schemaToType(schema, schema);
|
|
64
|
+
const outputSchema = resolveOutputSchema(tool);
|
|
65
|
+
const outputType =
|
|
66
|
+
outputSchema === undefined
|
|
67
|
+
? 'unknown'
|
|
68
|
+
: schemaToType(outputSchema, outputSchema);
|
|
69
|
+
const description = firstString(tool.description, tool.title);
|
|
70
|
+
const lines: string[] = [];
|
|
71
|
+
|
|
72
|
+
if (description !== undefined) {
|
|
73
|
+
lines.push(` /** ${toComment(description)} */`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
lines.push(
|
|
77
|
+
` ${formatObjectKey(toolName)}: (input: ${indentType(inputType, 2)}) => Promise<${indentType(outputType, 2)}>;`,
|
|
78
|
+
);
|
|
79
|
+
return lines;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function renderToolExamples(
|
|
83
|
+
toolEntries: Array<[string, CodeModeToolSet[string]]>,
|
|
84
|
+
): string[] {
|
|
85
|
+
if (toolEntries.length === 1) {
|
|
86
|
+
const toolEntry = toolEntries[0]!;
|
|
87
|
+
const projection = renderToolProjection('result', toolEntry[1]);
|
|
88
|
+
return [
|
|
89
|
+
`const result = await ${renderToolExampleCall(toolEntry)};`,
|
|
90
|
+
`return ${projection};`,
|
|
91
|
+
];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const variableNames = uniqueToolVariableNames(
|
|
95
|
+
toolEntries.map(([toolName]) => toolName),
|
|
96
|
+
);
|
|
97
|
+
const calls = toolEntries.map(
|
|
98
|
+
toolEntry => ` ${renderToolExampleCall(toolEntry)},`,
|
|
99
|
+
);
|
|
100
|
+
return [
|
|
101
|
+
`const [${variableNames.join(', ')}] = await Promise.all([`,
|
|
102
|
+
...calls,
|
|
103
|
+
']);',
|
|
104
|
+
'return {',
|
|
105
|
+
...toolEntries.map(([toolName, tool], index) => {
|
|
106
|
+
const projection = renderToolProjection(variableNames[index]!, tool);
|
|
107
|
+
return ` ${formatObjectKey(toolName)}: ${projection},`;
|
|
108
|
+
}),
|
|
109
|
+
'};',
|
|
110
|
+
];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function renderToolExampleCall([toolName, tool]: [
|
|
114
|
+
string,
|
|
115
|
+
CodeModeToolSet[string],
|
|
116
|
+
]): string {
|
|
117
|
+
const schema = resolveInputSchema(tool);
|
|
118
|
+
const input =
|
|
119
|
+
firstInputExample(tool) ??
|
|
120
|
+
(schema === undefined ? {} : sampleFromSchema(schema, schema));
|
|
121
|
+
return `tools${formatPropertyAccess(toolName)}(${JSON.stringify(input)})`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function renderToolProjection(
|
|
125
|
+
variableName: string,
|
|
126
|
+
tool: CodeModeToolSet[string],
|
|
127
|
+
): string {
|
|
128
|
+
const field = firstObjectProperty(resolveOutputSchema(tool));
|
|
129
|
+
if (field === undefined) {
|
|
130
|
+
return variableName;
|
|
131
|
+
}
|
|
132
|
+
return `{ ${formatObjectKey(field)}: ${variableName}${formatPropertyAccess(field)} }`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function firstObjectProperty(
|
|
136
|
+
schema: JsonSchema | undefined,
|
|
137
|
+
): string | undefined {
|
|
138
|
+
if (schema === undefined || !isRecord(schema.properties)) {
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
return Object.entries(schema.properties).find(entry =>
|
|
142
|
+
isRecord(entry[1]),
|
|
143
|
+
)?.[0];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function resolveInputSchema(
|
|
147
|
+
tool: CodeModeToolSet[string],
|
|
148
|
+
): JsonSchema | undefined {
|
|
149
|
+
try {
|
|
150
|
+
const jsonSchema = asSchema(tool.inputSchema).jsonSchema;
|
|
151
|
+
if (isPromiseLike(jsonSchema) || !isRecord(jsonSchema)) {
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
return jsonSchema;
|
|
155
|
+
} catch {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function resolveOutputSchema(
|
|
161
|
+
tool: CodeModeToolSet[string],
|
|
162
|
+
): JsonSchema | undefined {
|
|
163
|
+
try {
|
|
164
|
+
const outputSchema = tool.outputSchema;
|
|
165
|
+
if (outputSchema === undefined) {
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
const jsonSchema = asSchema(outputSchema).jsonSchema;
|
|
169
|
+
if (isPromiseLike(jsonSchema) || !isRecord(jsonSchema)) {
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
return jsonSchema;
|
|
173
|
+
} catch {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function schemaToType(schema: JsonSchema, root: JsonSchema): string {
|
|
179
|
+
return schemaToTypeInner(schema, {
|
|
180
|
+
root,
|
|
181
|
+
seenRefs: new Set(),
|
|
182
|
+
depth: 0,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function schemaToTypeInner(schema: JsonSchema, context: SchemaContext): string {
|
|
187
|
+
if (context.depth > MAX_SCHEMA_DEPTH) {
|
|
188
|
+
return 'unknown';
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const ref = firstString(schema.$ref);
|
|
192
|
+
if (ref !== undefined) {
|
|
193
|
+
const resolved = resolveRef(ref, context.root, context.seenRefs);
|
|
194
|
+
return resolved === undefined
|
|
195
|
+
? 'unknown'
|
|
196
|
+
: schemaToTypeInner(resolved, nextContext(context));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const constValue = schema.const;
|
|
200
|
+
if (constValue !== undefined) {
|
|
201
|
+
return literalType(constValue);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const enumValues = asArray(schema.enum);
|
|
205
|
+
if (enumValues !== undefined) {
|
|
206
|
+
return union(enumValues.map(literalType));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const oneOf = schemaArray(schema.oneOf);
|
|
210
|
+
if (oneOf !== undefined) {
|
|
211
|
+
return union(
|
|
212
|
+
oneOf.map(part => schemaToTypeInner(part, nextContext(context))),
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const anyOf = schemaArray(schema.anyOf);
|
|
217
|
+
if (anyOf !== undefined) {
|
|
218
|
+
return union(
|
|
219
|
+
anyOf.map(part => schemaToTypeInner(part, nextContext(context))),
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const allOf = schemaArray(schema.allOf);
|
|
224
|
+
if (allOf !== undefined) {
|
|
225
|
+
return allOf
|
|
226
|
+
.map(part => schemaToTypeInner(part, nextContext(context)))
|
|
227
|
+
.join(' & ');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const type = schema.type;
|
|
231
|
+
if (Array.isArray(type)) {
|
|
232
|
+
return union(
|
|
233
|
+
type.map(part => schemaToTypeInner({ ...schema, type: part }, context)),
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (type === 'object' || isRecord(schema.properties)) {
|
|
238
|
+
return objectType(schema, context);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (type === 'array' || schema.items !== undefined) {
|
|
242
|
+
return arrayType(schema, context);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (type === 'string') {
|
|
246
|
+
return 'string';
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (type === 'number' || type === 'integer') {
|
|
250
|
+
return 'number';
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (type === 'boolean') {
|
|
254
|
+
return 'boolean';
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (type === 'null') {
|
|
258
|
+
return 'null';
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return 'unknown';
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function objectType(schema: JsonSchema, context: SchemaContext): string {
|
|
265
|
+
const properties = isRecord(schema.properties) ? schema.properties : {};
|
|
266
|
+
const required = new Set(asArray(schema.required)?.filter(isString) ?? []);
|
|
267
|
+
const entries = Object.entries(properties).filter(
|
|
268
|
+
(entry): entry is [string, JsonSchema] => isRecord(entry[1]),
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
if (entries.length === 0) {
|
|
272
|
+
const additionalProperties = schema.additionalProperties;
|
|
273
|
+
if (isRecord(additionalProperties)) {
|
|
274
|
+
return `Record<string, ${schemaToTypeInner(additionalProperties, nextContext(context))}>`;
|
|
275
|
+
}
|
|
276
|
+
if (additionalProperties === true) {
|
|
277
|
+
return 'Record<string, unknown>';
|
|
278
|
+
}
|
|
279
|
+
return '{}';
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const lines = entries.flatMap(([name, value]) => {
|
|
283
|
+
const propertyLines: string[] = [];
|
|
284
|
+
const description = firstString(value.description);
|
|
285
|
+
if (description !== undefined) {
|
|
286
|
+
propertyLines.push(` /** ${toComment(description)} */`);
|
|
287
|
+
}
|
|
288
|
+
propertyLines.push(
|
|
289
|
+
` ${formatObjectKey(name)}${required.has(name) ? '' : '?'}: ${indentType(
|
|
290
|
+
schemaToTypeInner(value, nextContext(context)),
|
|
291
|
+
2,
|
|
292
|
+
)};`,
|
|
293
|
+
);
|
|
294
|
+
return propertyLines;
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const additionalProperties = schema.additionalProperties;
|
|
298
|
+
if (isRecord(additionalProperties)) {
|
|
299
|
+
lines.push(
|
|
300
|
+
` [key: string]: ${schemaToTypeInner(additionalProperties, nextContext(context))};`,
|
|
301
|
+
);
|
|
302
|
+
} else if (additionalProperties === true) {
|
|
303
|
+
lines.push(' [key: string]: unknown;');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const compact = compactObjectType(lines);
|
|
307
|
+
if (compact !== undefined) {
|
|
308
|
+
return compact;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return `{\n${lines.join('\n')}\n}`;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function arrayType(schema: JsonSchema, context: SchemaContext): string {
|
|
315
|
+
const items = schema.items;
|
|
316
|
+
if (Array.isArray(items)) {
|
|
317
|
+
return `[${items
|
|
318
|
+
.map(item =>
|
|
319
|
+
isRecord(item)
|
|
320
|
+
? schemaToTypeInner(item, nextContext(context))
|
|
321
|
+
: 'unknown',
|
|
322
|
+
)
|
|
323
|
+
.join(', ')}]`;
|
|
324
|
+
}
|
|
325
|
+
if (isRecord(items)) {
|
|
326
|
+
const itemType = schemaToTypeInner(items, nextContext(context));
|
|
327
|
+
return needsParentheses(itemType) || itemType.includes('\n')
|
|
328
|
+
? `Array<${itemType}>`
|
|
329
|
+
: `${itemType}[]`;
|
|
330
|
+
}
|
|
331
|
+
return 'unknown[]';
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function sampleFromSchema(schema: JsonSchema, root: JsonSchema): unknown {
|
|
335
|
+
return sampleFromSchemaInner(schema, {
|
|
336
|
+
root,
|
|
337
|
+
seenRefs: new Set(),
|
|
338
|
+
depth: 0,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function sampleFromSchemaInner(
|
|
343
|
+
schema: JsonSchema,
|
|
344
|
+
context: SchemaContext,
|
|
345
|
+
): unknown {
|
|
346
|
+
if (context.depth > MAX_SCHEMA_DEPTH) {
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (schema.default !== undefined) {
|
|
351
|
+
return schema.default;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const ref = firstString(schema.$ref);
|
|
355
|
+
if (ref !== undefined) {
|
|
356
|
+
const resolved = resolveRef(ref, context.root, context.seenRefs);
|
|
357
|
+
return resolved === undefined
|
|
358
|
+
? null
|
|
359
|
+
: sampleFromSchemaInner(resolved, nextContext(context));
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (schema.const !== undefined) {
|
|
363
|
+
return schema.const;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const enumValues = asArray(schema.enum);
|
|
367
|
+
if (enumValues !== undefined) {
|
|
368
|
+
return enumValues[0] ?? null;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const oneOf = schemaArray(schema.oneOf);
|
|
372
|
+
if (oneOf !== undefined) {
|
|
373
|
+
return sampleFromSchemaInner(oneOf[0] ?? {}, nextContext(context));
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const anyOf = schemaArray(schema.anyOf);
|
|
377
|
+
if (anyOf !== undefined) {
|
|
378
|
+
return sampleFromSchemaInner(anyOf[0] ?? {}, nextContext(context));
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const type = schema.type;
|
|
382
|
+
if (Array.isArray(type)) {
|
|
383
|
+
return sampleFromSchemaInner(
|
|
384
|
+
{ ...schema, type: type.find(part => part !== 'null') ?? type[0] },
|
|
385
|
+
context,
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (type === 'object' || isRecord(schema.properties)) {
|
|
390
|
+
const properties = isRecord(schema.properties) ? schema.properties : {};
|
|
391
|
+
return Object.fromEntries(
|
|
392
|
+
Object.entries(properties)
|
|
393
|
+
.filter((entry): entry is [string, JsonSchema] => isRecord(entry[1]))
|
|
394
|
+
.map(([name, value]) => [
|
|
395
|
+
name,
|
|
396
|
+
sampleFromSchemaInner(value, nextContext(context)),
|
|
397
|
+
]),
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (type === 'array' || schema.items !== undefined) {
|
|
402
|
+
const items = Array.isArray(schema.items) ? schema.items[0] : schema.items;
|
|
403
|
+
return [
|
|
404
|
+
isRecord(items)
|
|
405
|
+
? sampleFromSchemaInner(items, nextContext(context))
|
|
406
|
+
: null,
|
|
407
|
+
];
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (type === 'number' || type === 'integer') {
|
|
411
|
+
return 1;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (type === 'boolean') {
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (type === 'null') {
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (type === 'string' || type === undefined) {
|
|
423
|
+
const examples = asArray(schema.examples);
|
|
424
|
+
if (examples?.[0] !== undefined) {
|
|
425
|
+
return examples[0];
|
|
426
|
+
}
|
|
427
|
+
const format = firstString(schema.format);
|
|
428
|
+
if (format === 'uri' || format === 'url') {
|
|
429
|
+
return 'https://example.com';
|
|
430
|
+
}
|
|
431
|
+
if (format === 'date-time') {
|
|
432
|
+
return '2026-01-01T00:00:00.000Z';
|
|
433
|
+
}
|
|
434
|
+
if (format === 'date') {
|
|
435
|
+
return '2026-01-01';
|
|
436
|
+
}
|
|
437
|
+
return 'string';
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function firstInputExample(tool: CodeModeToolSet[string]): unknown {
|
|
444
|
+
const examples = asArray(tool.inputExamples);
|
|
445
|
+
const first = examples?.[0];
|
|
446
|
+
return isRecord(first) && 'input' in first ? first.input : undefined;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function resolveRef(
|
|
450
|
+
ref: string,
|
|
451
|
+
root: JsonSchema,
|
|
452
|
+
seenRefs: Set<string>,
|
|
453
|
+
): JsonSchema | undefined {
|
|
454
|
+
if (!ref.startsWith('#/') || seenRefs.has(ref)) {
|
|
455
|
+
return undefined;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
seenRefs.add(ref);
|
|
459
|
+
const value = ref
|
|
460
|
+
.slice(2)
|
|
461
|
+
.split('/')
|
|
462
|
+
.map(part => part.replace(/~1/g, '/').replace(/~0/g, '~'))
|
|
463
|
+
.reduce<unknown>((current, part) => {
|
|
464
|
+
if (!isRecord(current)) {
|
|
465
|
+
return undefined;
|
|
466
|
+
}
|
|
467
|
+
return current[part];
|
|
468
|
+
}, root);
|
|
469
|
+
|
|
470
|
+
return isRecord(value) ? value : undefined;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function nextContext(context: SchemaContext): SchemaContext {
|
|
474
|
+
return {
|
|
475
|
+
...context,
|
|
476
|
+
depth: context.depth + 1,
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function union(types: string[]): string {
|
|
481
|
+
const unique = [...new Set(types)];
|
|
482
|
+
return unique.length === 0 ? 'unknown' : unique.join(' | ');
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function literalType(value: unknown): string {
|
|
486
|
+
if (typeof value === 'string') {
|
|
487
|
+
return JSON.stringify(value);
|
|
488
|
+
}
|
|
489
|
+
if (
|
|
490
|
+
typeof value === 'number' ||
|
|
491
|
+
typeof value === 'boolean' ||
|
|
492
|
+
value === null
|
|
493
|
+
) {
|
|
494
|
+
return String(value);
|
|
495
|
+
}
|
|
496
|
+
return 'unknown';
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function indentType(type: string, spaces: number): string {
|
|
500
|
+
if (!type.includes('\n')) {
|
|
501
|
+
return type;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const padding = ' '.repeat(spaces);
|
|
505
|
+
return type.replaceAll('\n', `\n${padding}`);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function needsParentheses(type: string): boolean {
|
|
509
|
+
return type.includes(' | ') || type.includes(' & ');
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function formatObjectKey(key: string): string {
|
|
513
|
+
return isIdentifier(key) ? key : JSON.stringify(key);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function formatPropertyAccess(key: string): string {
|
|
517
|
+
return isIdentifier(key) ? `.${key}` : `[${JSON.stringify(key)}]`;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function uniqueToolVariableNames(toolNames: string[]): string[] {
|
|
521
|
+
const seen = new Map<string, number>();
|
|
522
|
+
return toolNames.map(toolName => {
|
|
523
|
+
const base = toIdentifier(toolName);
|
|
524
|
+
const count = seen.get(base) ?? 0;
|
|
525
|
+
seen.set(base, count + 1);
|
|
526
|
+
return count === 0 ? base : `${base}${count + 1}`;
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function toIdentifier(value: string): string {
|
|
531
|
+
const identifier = value
|
|
532
|
+
.replace(/[^A-Za-z0-9_$]/g, '_')
|
|
533
|
+
.replace(/^[^A-Za-z_$]+/, '');
|
|
534
|
+
return identifier.length === 0 ? 'tool' : identifier;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function isIdentifier(value: string): boolean {
|
|
538
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function compactObjectType(lines: string[]): string | undefined {
|
|
542
|
+
if (lines.length === 0 || lines.some(line => line.includes('/**'))) {
|
|
543
|
+
return undefined;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const fields = lines.map(line => line.trim());
|
|
547
|
+
if (fields.some(field => field.includes('\n'))) {
|
|
548
|
+
return undefined;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const compact = `{ ${fields.join(' ')} }`;
|
|
552
|
+
return compact.length <= MAX_COMPACT_OBJECT_TYPE_LENGTH ? compact : undefined;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function toComment(value: string): string {
|
|
556
|
+
return value.replaceAll('*/', '* /').replace(/\s+/g, ' ').trim();
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function firstString(...values: unknown[]): string | undefined {
|
|
560
|
+
return values.find(isString);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function schemaArray(value: unknown): JsonSchema[] | undefined {
|
|
564
|
+
const values = asArray(value);
|
|
565
|
+
if (values === undefined || !values.every(isRecord)) {
|
|
566
|
+
return undefined;
|
|
567
|
+
}
|
|
568
|
+
return values;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function asArray(value: unknown): unknown[] | undefined {
|
|
572
|
+
return Array.isArray(value) ? value : undefined;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function isRecord(value: unknown): value is JsonSchema {
|
|
576
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function isString(value: unknown): value is string {
|
|
580
|
+
return typeof value === 'string';
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
|
|
584
|
+
return isRecord(value) && typeof value.then === 'function';
|
|
585
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { ModelMessage, Tool } from 'ai';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Host tools made available to sandboxed code through the global `tools` object.
|
|
5
|
+
*/
|
|
6
|
+
export type CodeModeToolSet = Record<string, Tool<any, any>>;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* AI SDK execution metadata forwarded to nested host tool calls.
|
|
10
|
+
*/
|
|
11
|
+
export interface CodeModeToolExecutionOptions {
|
|
12
|
+
toolCallId: string;
|
|
13
|
+
messages: ModelMessage[];
|
|
14
|
+
abortSignal?: AbortSignal;
|
|
15
|
+
experimental_context?: unknown;
|
|
16
|
+
context?: unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* AI SDK tool returned by `createCodeModeTool`.
|
|
21
|
+
*/
|
|
22
|
+
export type CodeModeTool = Tool<CodeModeToolInput, unknown> & {
|
|
23
|
+
description: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Input accepted by the generated code-mode tool.
|
|
28
|
+
*/
|
|
29
|
+
export interface CodeModeToolInput {
|
|
30
|
+
/**
|
|
31
|
+
* JavaScript or type-stripped TypeScript source to execute.
|
|
32
|
+
*
|
|
33
|
+
* The source is wrapped in an async function, so top-level `await` and
|
|
34
|
+
* `return` are supported.
|
|
35
|
+
*/
|
|
36
|
+
js: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Execution limits applied to each sandbox invocation.
|
|
41
|
+
*/
|
|
42
|
+
export interface CodeModeExecutionPolicy {
|
|
43
|
+
/** @defaultValue `30_000` */
|
|
44
|
+
timeoutMs?: number;
|
|
45
|
+
/** @defaultValue `64 * 1024 * 1024` */
|
|
46
|
+
memoryLimitBytes?: number;
|
|
47
|
+
/** @defaultValue `2 * 1024 * 1024` */
|
|
48
|
+
maxStackSizeBytes?: number;
|
|
49
|
+
/** @defaultValue `1024 * 1024` */
|
|
50
|
+
maxResultBytes?: number;
|
|
51
|
+
/** @defaultValue `64 * 1024` */
|
|
52
|
+
maxConsoleOutputBytes?: number;
|
|
53
|
+
/** @defaultValue `256 * 1024` */
|
|
54
|
+
maxSourceBytes?: number;
|
|
55
|
+
/** @defaultValue `1024 * 1024` */
|
|
56
|
+
maxToolInputBytes?: number;
|
|
57
|
+
/** @defaultValue `4 * 1024 * 1024` */
|
|
58
|
+
maxToolOutputBytes?: number;
|
|
59
|
+
/** @defaultValue `256` */
|
|
60
|
+
maxBridgeRequests?: number;
|
|
61
|
+
/** @defaultValue `32` */
|
|
62
|
+
maxInFlightBridgeRequests?: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Options used by `createCodeModeTool` and `runCodeMode`.
|
|
67
|
+
*/
|
|
68
|
+
export interface CodeModeOptions {
|
|
69
|
+
executionPolicy?: CodeModeExecutionPolicy;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Input for `runCodeMode`.
|
|
74
|
+
*/
|
|
75
|
+
export interface RunCodeModeInput {
|
|
76
|
+
js: string;
|
|
77
|
+
tools: CodeModeToolSet;
|
|
78
|
+
toolExecutionOptions?: Partial<CodeModeToolExecutionOptions>;
|
|
79
|
+
options?: CodeModeOptions;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Fully normalized runtime options.
|
|
84
|
+
*
|
|
85
|
+
* @internal
|
|
86
|
+
*/
|
|
87
|
+
export interface NormalizedCodeModeOptions {
|
|
88
|
+
timeoutMs: number;
|
|
89
|
+
memoryLimitBytes: number;
|
|
90
|
+
maxStackSizeBytes: number;
|
|
91
|
+
maxResultBytes: number;
|
|
92
|
+
maxConsoleOutputBytes: number;
|
|
93
|
+
maxSourceBytes: number;
|
|
94
|
+
maxToolInputBytes: number;
|
|
95
|
+
maxToolOutputBytes: number;
|
|
96
|
+
maxBridgeRequests: number;
|
|
97
|
+
maxInFlightBridgeRequests: number;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Serializable representation of an error crossing the worker boundary.
|
|
102
|
+
*
|
|
103
|
+
* @internal
|
|
104
|
+
*/
|
|
105
|
+
export interface SerializableError {
|
|
106
|
+
name: string;
|
|
107
|
+
message: string;
|
|
108
|
+
stack?: string;
|
|
109
|
+
code?: string;
|
|
110
|
+
details?: unknown;
|
|
111
|
+
}
|