evalution 1.0.1
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 +661 -0
- package/LICENSE.addendum +79 -0
- package/LICENSING.md +69 -0
- package/README.md +50 -0
- package/bin/evalution.js +5 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +871 -0
- package/dist/client/assets/index-CORbBplP.js +144 -0
- package/dist/client/assets/index-CgcFVsRZ.css +32 -0
- package/dist/client/favicon.svg +19 -0
- package/dist/client/index.html +17 -0
- package/dist/index.d.ts +1440 -0
- package/dist/index.js +1325 -0
- package/dist/vercel-ai-sdk-CareWPDM.js +759 -0
- package/package.json +102 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1325 @@
|
|
|
1
|
+
import { a as MemoryTraceProvider, c as PROMPT_PROVIDER_ID_ATTRIBUTE, d as isEditable, i as setupStepCommand, l as SPAN_KIND_ATTRIBUTE, n as findPackageDts, o as PROMPT_ID_ATTRIBUTE, s as PROMPT_NAME_ATTRIBUTE, t as VercelAISDK, u as createTracerForPrompt } from "./vercel-ai-sdk-CareWPDM.js";
|
|
2
|
+
import fs, { glob } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import fs$1 from "node:fs";
|
|
6
|
+
import { addProperty, extractPropertiesFromDeclaration, extractPropertiesFromObjectLiteral, extractPropertiesFromParameters, findTypeDeclaration, removeProperty, updateProperty, valueToSourceText } from "ts-proppy";
|
|
7
|
+
import ts from "typescript";
|
|
8
|
+
import chokidar from "chokidar";
|
|
9
|
+
import { makeRe, minimatch } from "minimatch";
|
|
10
|
+
import { GoogleGenAI } from "@google/genai";
|
|
11
|
+
//#region src/file-provider.ts
|
|
12
|
+
/**
|
|
13
|
+
* An in-memory {@link FileProvider} backed by a `Map<string, string>`.
|
|
14
|
+
*
|
|
15
|
+
* Intended for unit tests — all file I/O stays in-process with no disk access.
|
|
16
|
+
* Calling {@link writeFile} triggers any active {@link watch} callbacks
|
|
17
|
+
* synchronously, making it easy to test reactive code paths.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* const provider = new MemoryFileProvider({
|
|
22
|
+
* '/virtual/prompt.ts': 'export function myPrompt() { ... }',
|
|
23
|
+
* });
|
|
24
|
+
* const content = await provider.readFile('/virtual/prompt.ts');
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
var MemoryFileProvider = class {
|
|
28
|
+
files;
|
|
29
|
+
watchers = /* @__PURE__ */ new Set();
|
|
30
|
+
/**
|
|
31
|
+
* @param files - Initial file contents keyed by absolute path.
|
|
32
|
+
*/
|
|
33
|
+
constructor(files = {}) {
|
|
34
|
+
this.files = new Map(Object.entries(files));
|
|
35
|
+
}
|
|
36
|
+
async readFile(filePath) {
|
|
37
|
+
const content = this.files.get(filePath);
|
|
38
|
+
if (content === void 0) throw new Error(`File not found: ${filePath}`);
|
|
39
|
+
return content;
|
|
40
|
+
}
|
|
41
|
+
async writeFile(filePath, content) {
|
|
42
|
+
const isNew = !this.files.has(filePath);
|
|
43
|
+
this.files.set(filePath, content);
|
|
44
|
+
this.notifyWatchers(isNew ? "add" : "change", filePath);
|
|
45
|
+
}
|
|
46
|
+
async deleteFile(filePath) {
|
|
47
|
+
this.files.delete(filePath);
|
|
48
|
+
this.notifyWatchers("remove", filePath);
|
|
49
|
+
}
|
|
50
|
+
async import(filePath) {
|
|
51
|
+
const content = this.files.get(filePath);
|
|
52
|
+
if (content === void 0) throw new Error(`File not found: ${filePath}`);
|
|
53
|
+
return import(`data:text/javascript;charset=utf-8,${encodeURIComponent(content)}`);
|
|
54
|
+
}
|
|
55
|
+
async *glob(pattern, options = {}) {
|
|
56
|
+
const { cwd = process.cwd(), ignore = [], absolute = false } = options;
|
|
57
|
+
for (const filePath of this.files.keys()) {
|
|
58
|
+
if (!filePath.startsWith(cwd + path.sep)) continue;
|
|
59
|
+
const relativePath = path.relative(cwd, filePath).replace(/\\/g, "/");
|
|
60
|
+
if (!minimatch(relativePath, pattern)) continue;
|
|
61
|
+
if (ignore.some((p) => minimatch(relativePath, p))) continue;
|
|
62
|
+
yield absolute ? filePath : relativePath;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
watch(patterns, options, callback) {
|
|
66
|
+
const watcher = {
|
|
67
|
+
patterns,
|
|
68
|
+
cwd: options.cwd ?? process.cwd(),
|
|
69
|
+
ignored: options.ignored ?? [],
|
|
70
|
+
callback
|
|
71
|
+
};
|
|
72
|
+
this.watchers.add(watcher);
|
|
73
|
+
return () => {
|
|
74
|
+
this.watchers.delete(watcher);
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
notifyWatchers(eventType, filePath) {
|
|
78
|
+
for (const watcher of this.watchers) {
|
|
79
|
+
const { cwd, patterns, ignored, callback } = watcher;
|
|
80
|
+
if (!filePath.startsWith(cwd + path.sep)) continue;
|
|
81
|
+
const relativePath = path.relative(cwd, filePath).replace(/\\/g, "/");
|
|
82
|
+
if (!patterns.some((p) => minimatch(relativePath, p))) continue;
|
|
83
|
+
if (ignored.some((p) => minimatch(relativePath, p))) continue;
|
|
84
|
+
callback(eventType, relativePath);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* A {@link FileProvider} backed by the local file system.
|
|
90
|
+
*
|
|
91
|
+
* Uses `fs/promises` for I/O, `fs/promises.glob` (Node.js ≥ 22) for pattern
|
|
92
|
+
* matching, and [chokidar](https://github.com/paulmillr/chokidar) for file
|
|
93
|
+
* watching.
|
|
94
|
+
*
|
|
95
|
+
* This is the default implementation used by {@link FilePromptProvider} and
|
|
96
|
+
* {@link TSPromptFileType} when no custom provider is supplied.
|
|
97
|
+
*/
|
|
98
|
+
var LocalFileProvider = class {
|
|
99
|
+
async readFile(filePath) {
|
|
100
|
+
return fs.readFile(filePath, "utf-8");
|
|
101
|
+
}
|
|
102
|
+
async writeFile(filePath, content) {
|
|
103
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
104
|
+
await fs.writeFile(filePath, content, "utf-8");
|
|
105
|
+
}
|
|
106
|
+
async deleteFile(filePath) {
|
|
107
|
+
await fs.unlink(filePath);
|
|
108
|
+
}
|
|
109
|
+
async import(filePath) {
|
|
110
|
+
return import(pathToFileURL(filePath).href);
|
|
111
|
+
}
|
|
112
|
+
async *glob(pattern, options = {}) {
|
|
113
|
+
const { cwd, ignore = [], absolute = false } = options;
|
|
114
|
+
const baseCwd = cwd ?? process.cwd();
|
|
115
|
+
for await (const file of glob(pattern, { cwd: baseCwd })) {
|
|
116
|
+
const relativePath = file.replace(/\\/g, "/");
|
|
117
|
+
if (ignore.some((p) => minimatch(relativePath, p))) continue;
|
|
118
|
+
yield absolute ? path.resolve(baseCwd, file) : file;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
watch(patterns, options, callback) {
|
|
122
|
+
const cwd = options.cwd ?? process.cwd();
|
|
123
|
+
const ignored = options.ignored ?? [];
|
|
124
|
+
const includeMatchers = patterns.map((p) => makeRe(p)).filter((re) => re !== false);
|
|
125
|
+
const matches = (fp) => includeMatchers.some((re) => re.test(fp));
|
|
126
|
+
const watcher = chokidar.watch(".", {
|
|
127
|
+
cwd,
|
|
128
|
+
ignored: (absPath) => {
|
|
129
|
+
const rel = path.relative(cwd, absPath).replace(/\\/g, "/");
|
|
130
|
+
return ignored.some((p) => minimatch(rel, p, { dot: true }));
|
|
131
|
+
},
|
|
132
|
+
persistent: true,
|
|
133
|
+
ignoreInitial: options.ignoreInitial ?? true
|
|
134
|
+
});
|
|
135
|
+
watcher.on("change", (fp) => {
|
|
136
|
+
if (matches(fp)) callback("change", fp);
|
|
137
|
+
});
|
|
138
|
+
watcher.on("add", (fp) => {
|
|
139
|
+
if (matches(fp)) callback("add", fp);
|
|
140
|
+
});
|
|
141
|
+
watcher.on("unlink", (fp) => {
|
|
142
|
+
if (matches(fp)) callback("remove", fp);
|
|
143
|
+
});
|
|
144
|
+
return () => {
|
|
145
|
+
watcher.close();
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region src/prompt/file/ts/ts-prompt-file-type.ts
|
|
151
|
+
/**
|
|
152
|
+
* {@link PromptFileType} implementation for TypeScript `.prompt.ts` files.
|
|
153
|
+
*
|
|
154
|
+
* Each prompt is an exported function that returns an SDK-specific config
|
|
155
|
+
* object. For example, for the Vercel AI SDK, a prompt file might look like this:
|
|
156
|
+
*
|
|
157
|
+
* ```ts
|
|
158
|
+
* import { openai } from '@ai-sdk/openai';
|
|
159
|
+
*
|
|
160
|
+
* export function myPrompt() {
|
|
161
|
+
* return {
|
|
162
|
+
* model: openai('gpt-4o'),
|
|
163
|
+
* system: 'You are a helpful assistant.',
|
|
164
|
+
* messages: [{ role: 'user', content: 'Hello!' }],
|
|
165
|
+
* };
|
|
166
|
+
* }
|
|
167
|
+
* ```
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```ts
|
|
171
|
+
* const fileType = new TSPromptFileType();
|
|
172
|
+
* const prompts = await fileType.parsePrompts(['/path/to/my.prompt.ts'], '/path/to');
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
function isValidIdentifier(name) {
|
|
176
|
+
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
177
|
+
}
|
|
178
|
+
var TSPromptFileType = class {
|
|
179
|
+
defaultIncludePatterns = ["**/*.prompt.ts", "**/*.promp.ts"];
|
|
180
|
+
defaultFileExtension = ".prompt.ts";
|
|
181
|
+
newPromptSkeleton(promptsId, name, importPath) {
|
|
182
|
+
const key = isValidIdentifier(name) ? name : `[${JSON.stringify(name)}]`;
|
|
183
|
+
return `import { prompts } from ${JSON.stringify(importPath)};
|
|
184
|
+
|
|
185
|
+
export default prompts(
|
|
186
|
+
${JSON.stringify(promptsId)},
|
|
187
|
+
() => ({
|
|
188
|
+
${key}: () => ({
|
|
189
|
+
})
|
|
190
|
+
}));`;
|
|
191
|
+
}
|
|
192
|
+
fileProvider;
|
|
193
|
+
constructor(fileProvider = new LocalFileProvider()) {
|
|
194
|
+
this.fileProvider = fileProvider;
|
|
195
|
+
}
|
|
196
|
+
async parsePrompts(files, rootDir = "") {
|
|
197
|
+
return (await Promise.all(files.map(async (filePath) => {
|
|
198
|
+
const sourceCode = await this.fileProvider.readFile(filePath);
|
|
199
|
+
return this.parseFileContent(filePath, sourceCode, rootDir);
|
|
200
|
+
}))).flat();
|
|
201
|
+
}
|
|
202
|
+
async updateProperty(filePath, propDef, value, promptId) {
|
|
203
|
+
if (!propDef.valueSpan) throw new Error(`Property '${propDef.name}' is missing valueSpan`);
|
|
204
|
+
let sourceCode = await this.fileProvider.readFile(filePath);
|
|
205
|
+
const adjusted = resolveBindingsAndAugment(sourceCode, value);
|
|
206
|
+
sourceCode = adjusted.sourceCode;
|
|
207
|
+
const resolvedValue = adjusted.value;
|
|
208
|
+
const functionName = promptId?.slice(promptId.lastIndexOf("#") + 1);
|
|
209
|
+
if (functionName) {
|
|
210
|
+
const freshDef = this.findFreshDefinition(sourceCode, filePath, functionName, propDef.name);
|
|
211
|
+
if (freshDef) propDef = {
|
|
212
|
+
...propDef,
|
|
213
|
+
valueSpan: freshDef.valueSpan,
|
|
214
|
+
fullSpan: freshDef.fullSpan
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
sourceCode = updateProperty(sourceCode, propDef, resolvedValue);
|
|
218
|
+
await this.fileProvider.writeFile(filePath, sourceCode);
|
|
219
|
+
}
|
|
220
|
+
async removeProperty(filePath, propDef) {
|
|
221
|
+
if (!propDef.fullSpan) throw new Error(`Property '${propDef.name}' is missing fullSpan`);
|
|
222
|
+
const newSourceCode = removeProperty(await this.fileProvider.readFile(filePath), propDef);
|
|
223
|
+
await this.fileProvider.writeFile(filePath, newSourceCode);
|
|
224
|
+
}
|
|
225
|
+
async addProperty(filePath, promptName, propertyName, value) {
|
|
226
|
+
let sourceCode = await this.fileProvider.readFile(filePath);
|
|
227
|
+
const adjusted = resolveBindingsAndAugment(sourceCode, value);
|
|
228
|
+
sourceCode = adjusted.sourceCode;
|
|
229
|
+
const resolvedValue = adjusted.value;
|
|
230
|
+
const sourceFile = ts.createSourceFile(filePath, sourceCode, ts.ScriptTarget.Latest, true);
|
|
231
|
+
const returnObj = this.findReturnObjectInSource(sourceFile, promptName);
|
|
232
|
+
if (!returnObj) throw new Error(`Return object not found in function '${promptName}'`);
|
|
233
|
+
const extracted = extractPropertiesFromObjectLiteral(returnObj, void 0, sourceFile);
|
|
234
|
+
sourceCode = addProperty(sourceCode, extracted, propertyName, resolvedValue);
|
|
235
|
+
await this.fileProvider.writeFile(filePath, sourceCode);
|
|
236
|
+
}
|
|
237
|
+
async renamePrompt(filePath, oldName, newName) {
|
|
238
|
+
const sourceCode = await this.fileProvider.readFile(filePath);
|
|
239
|
+
const sourceFile = ts.createSourceFile(filePath, sourceCode, ts.ScriptTarget.Latest, true);
|
|
240
|
+
let nameStart = -1;
|
|
241
|
+
let nameEnd = -1;
|
|
242
|
+
let isFunctionDeclaration = false;
|
|
243
|
+
const visit = (node) => {
|
|
244
|
+
if (nameStart >= 0) return;
|
|
245
|
+
if (ts.isFunctionDeclaration(node) && node.name?.text === oldName) {
|
|
246
|
+
nameStart = node.name.getStart(sourceFile);
|
|
247
|
+
nameEnd = node.name.getEnd();
|
|
248
|
+
isFunctionDeclaration = true;
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (ts.isExportAssignment(node)) {
|
|
252
|
+
const helper = findPromptsHelperCall(node.expression);
|
|
253
|
+
if (helper) {
|
|
254
|
+
for (const prop of helper.object.properties) if (getPropertyName(prop) === oldName) {
|
|
255
|
+
const nameNode = prop.name;
|
|
256
|
+
nameStart = nameNode.getStart(sourceFile);
|
|
257
|
+
nameEnd = nameNode.getEnd();
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
ts.forEachChild(node, visit);
|
|
263
|
+
};
|
|
264
|
+
visit(sourceFile);
|
|
265
|
+
if (nameStart < 0) throw new Error(`Function '${oldName}' not found in ${filePath}`);
|
|
266
|
+
if (!isValidIdentifier(newName) && isFunctionDeclaration) throw new Error(`'${newName}' is not a valid function name`);
|
|
267
|
+
const replacement = isValidIdentifier(newName) ? newName : `[${JSON.stringify(newName)}]`;
|
|
268
|
+
const newSource = sourceCode.slice(0, nameStart) + replacement + sourceCode.slice(nameEnd);
|
|
269
|
+
await this.fileProvider.writeFile(filePath, newSource);
|
|
270
|
+
}
|
|
271
|
+
async loadConfig(filePath, promptName, params) {
|
|
272
|
+
const module = await this.fileProvider.import(filePath);
|
|
273
|
+
let fn = module[promptName];
|
|
274
|
+
if (typeof fn !== "function" && typeof module.default === "function") {
|
|
275
|
+
const obj = module.default();
|
|
276
|
+
if (obj && typeof obj[promptName] === "function") fn = obj[promptName].bind(obj);
|
|
277
|
+
}
|
|
278
|
+
if (typeof fn !== "function") throw new Error(`Function '${promptName}' not found in ${filePath}`);
|
|
279
|
+
const config = fn(...params);
|
|
280
|
+
if (!config || typeof config !== "object") throw new Error(`'${promptName}' did not return a valid config object`);
|
|
281
|
+
return config;
|
|
282
|
+
}
|
|
283
|
+
parseFileContent(filePath, sourceCode, rootDir) {
|
|
284
|
+
const sourceFile = ts.createSourceFile(filePath, sourceCode, ts.ScriptTarget.ESNext, true);
|
|
285
|
+
const prompts = [];
|
|
286
|
+
const visitNode = (node) => {
|
|
287
|
+
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
288
|
+
if (node.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.ExportKeyword)) {
|
|
289
|
+
const prompt = this.parseFunctionDeclaration(node, sourceFile, filePath, rootDir);
|
|
290
|
+
if (prompt) prompts.push(prompt);
|
|
291
|
+
}
|
|
292
|
+
} else if (ts.isExportAssignment(node)) {
|
|
293
|
+
const helper = findPromptsHelperCall(node.expression);
|
|
294
|
+
if (helper) for (const prop of helper.object.properties) {
|
|
295
|
+
const parsed = this.parseHelperProperty(prop, sourceFile, filePath, rootDir, helper.moduleId);
|
|
296
|
+
if (parsed) prompts.push(parsed);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
ts.forEachChild(node, visitNode);
|
|
300
|
+
};
|
|
301
|
+
visitNode(sourceFile);
|
|
302
|
+
return prompts;
|
|
303
|
+
}
|
|
304
|
+
parseHelperProperty(prop, sourceFile, filePath, rootDir, moduleId) {
|
|
305
|
+
const name = getPropertyName(prop);
|
|
306
|
+
if (!name) return null;
|
|
307
|
+
const fn = getPropertyFunction(prop);
|
|
308
|
+
if (!fn) return null;
|
|
309
|
+
const returnObject = findReturnObjectInFunctionLike(fn);
|
|
310
|
+
if (!returnObject) return null;
|
|
311
|
+
const functionParameters = extractPropertiesFromParameters(fn.parameters, sourceFile).definitions;
|
|
312
|
+
const relativeFilePath = rootDir ? path.relative(rootDir, filePath) : filePath;
|
|
313
|
+
const extractedProps = extractPropertiesFromObjectLiteral(returnObject, void 0, sourceFile);
|
|
314
|
+
const treePath = relativeFilePath.split("/").filter(Boolean);
|
|
315
|
+
return {
|
|
316
|
+
id: `${relativeFilePath}#${name}`,
|
|
317
|
+
globalId: moduleId ? `${moduleId}#${name}` : void 0,
|
|
318
|
+
name,
|
|
319
|
+
functionParameters,
|
|
320
|
+
extractedProps,
|
|
321
|
+
metadata: { relativeFilePath },
|
|
322
|
+
treePath
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
parseFunctionDeclaration(node, sourceFile, filePath, rootDir) {
|
|
326
|
+
if (!node.name) return null;
|
|
327
|
+
const functionName = node.name.text;
|
|
328
|
+
const functionParameters = extractPropertiesFromParameters(node.parameters, sourceFile).definitions;
|
|
329
|
+
const returnObject = this.findReturnObjectInFunction(node);
|
|
330
|
+
if (!returnObject) return null;
|
|
331
|
+
const relativeFilePath = rootDir ? path.relative(rootDir, filePath) : filePath;
|
|
332
|
+
const promptId = `${relativeFilePath}#${functionName}`;
|
|
333
|
+
const extractedProps = extractPropertiesFromObjectLiteral(returnObject, void 0, sourceFile);
|
|
334
|
+
const treePath = relativeFilePath.split("/").filter(Boolean);
|
|
335
|
+
return {
|
|
336
|
+
id: promptId,
|
|
337
|
+
name: functionName,
|
|
338
|
+
functionParameters,
|
|
339
|
+
extractedProps,
|
|
340
|
+
metadata: { relativeFilePath },
|
|
341
|
+
treePath
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
findFreshDefinition(sourceCode, filePath, functionName, propertyName) {
|
|
345
|
+
const sourceFile = ts.createSourceFile(filePath, sourceCode, ts.ScriptTarget.Latest, true);
|
|
346
|
+
const returnObj = this.findReturnObjectInSource(sourceFile, functionName);
|
|
347
|
+
if (!returnObj) return null;
|
|
348
|
+
return extractPropertiesFromObjectLiteral(returnObj, void 0, sourceFile).definitions.find((d) => d.name === propertyName) ?? null;
|
|
349
|
+
}
|
|
350
|
+
findReturnObjectInFunction(node) {
|
|
351
|
+
let returnObject = null;
|
|
352
|
+
const visitNode = (n) => {
|
|
353
|
+
if (ts.isReturnStatement(n) && n.expression) {
|
|
354
|
+
if (ts.isObjectLiteralExpression(n.expression)) returnObject = n.expression;
|
|
355
|
+
} else if (ts.isArrowFunction(n) && ts.isObjectLiteralExpression(n.body)) returnObject = n.body;
|
|
356
|
+
if (!returnObject) ts.forEachChild(n, visitNode);
|
|
357
|
+
};
|
|
358
|
+
if (node.body) visitNode(node.body);
|
|
359
|
+
return returnObject;
|
|
360
|
+
}
|
|
361
|
+
findReturnObjectInSource(sourceFile, functionName) {
|
|
362
|
+
let returnObj = null;
|
|
363
|
+
const visitFunc = (node) => {
|
|
364
|
+
if (ts.isFunctionDeclaration(node) && node.name?.text === functionName) {
|
|
365
|
+
returnObj = this.findReturnObjectInFunction(node);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
if (ts.isExportAssignment(node)) {
|
|
369
|
+
const helper = findPromptsHelperCall(node.expression);
|
|
370
|
+
if (helper) {
|
|
371
|
+
for (const prop of helper.object.properties) if (getPropertyName(prop) === functionName) {
|
|
372
|
+
const fn = getPropertyFunction(prop);
|
|
373
|
+
if (fn) returnObj = findReturnObjectInFunctionLike(fn);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (!returnObj) ts.forEachChild(node, visitFunc);
|
|
379
|
+
};
|
|
380
|
+
visitFunc(sourceFile);
|
|
381
|
+
return returnObj;
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
/**
|
|
385
|
+
* If `expr` is a call like `prompts(id, factory)` (or the legacy `prompts(factory)`)
|
|
386
|
+
* whose factory immediately returns an object literal, return that object literal
|
|
387
|
+
* together with the module ID — the first string-literal argument, when present.
|
|
388
|
+
* Otherwise null.
|
|
389
|
+
*/
|
|
390
|
+
function findPromptsHelperCall(expr) {
|
|
391
|
+
if (!ts.isCallExpression(expr)) return null;
|
|
392
|
+
if (!ts.isIdentifier(expr.expression) || expr.expression.text !== "prompts") return null;
|
|
393
|
+
const factory = expr.arguments.find((arg) => ts.isArrowFunction(arg) || ts.isFunctionExpression(arg));
|
|
394
|
+
if (!factory) return null;
|
|
395
|
+
const object = findReturnObjectInFunctionLike(factory);
|
|
396
|
+
if (!object) return null;
|
|
397
|
+
const first = expr.arguments[0];
|
|
398
|
+
return {
|
|
399
|
+
object,
|
|
400
|
+
moduleId: first && ts.isStringLiteralLike(first) ? first.text : void 0
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
function getPropertyName(prop) {
|
|
404
|
+
if (ts.isMethodDeclaration(prop) || ts.isPropertyAssignment(prop)) {
|
|
405
|
+
const name = prop.name;
|
|
406
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name)) return name.text;
|
|
407
|
+
if (ts.isComputedPropertyName(name) && ts.isStringLiteralLike(name.expression)) return name.expression.text;
|
|
408
|
+
} else if (ts.isShorthandPropertyAssignment(prop)) return prop.name.text;
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
function getPropertyFunction(prop) {
|
|
412
|
+
if (ts.isMethodDeclaration(prop)) return prop;
|
|
413
|
+
if (ts.isPropertyAssignment(prop)) {
|
|
414
|
+
const init = prop.initializer;
|
|
415
|
+
if (ts.isArrowFunction(init) || ts.isFunctionExpression(init)) return init;
|
|
416
|
+
}
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Resolve binding-array candidates against the file's structure.
|
|
421
|
+
*
|
|
422
|
+
* For each `functionCall` in `value`, walk its `binding` candidates in order
|
|
423
|
+
* and pick the first one that matches the source:
|
|
424
|
+
* - `parameter` candidates match when the file contains the named
|
|
425
|
+
* `enclosingCall` (e.g. `prompts(({...}) => ...)`) whose first parameter is
|
|
426
|
+
* a destructured object. The callee is added to that destructure (if not
|
|
427
|
+
* already present) and the value's `binding` is stripped — the new
|
|
428
|
+
* functionCall reads its callee from the closure parameter, not a top-level
|
|
429
|
+
* import.
|
|
430
|
+
* - `import` candidates always match. They collapse the binding to that
|
|
431
|
+
* single import so ts-proppy's emitter adds the corresponding top-level
|
|
432
|
+
* import.
|
|
433
|
+
*
|
|
434
|
+
* Returns the (possibly) adjusted source code along with a plain
|
|
435
|
+
* {@link PropValue} ready for ts-proppy.
|
|
436
|
+
*/
|
|
437
|
+
function resolveBindingsAndAugment(sourceCode, value) {
|
|
438
|
+
const sourceFile = ts.createSourceFile("helper-adjust.ts", sourceCode, ts.ScriptTarget.Latest, true);
|
|
439
|
+
const destructureAdditions = /* @__PURE__ */ new Map();
|
|
440
|
+
const resolveCandidate = (_fc, candidates) => {
|
|
441
|
+
for (const c of candidates) if (c.kind === "parameter") {
|
|
442
|
+
const dest = findEnclosingCallDestructure(sourceFile, c.enclosingCall);
|
|
443
|
+
if (dest) return { viaDestructure: dest };
|
|
444
|
+
} else if (c.kind === "import") return { binding: c };
|
|
445
|
+
return {};
|
|
446
|
+
};
|
|
447
|
+
const adjusted = mapFunctionCalls(value, (fc) => {
|
|
448
|
+
if (!fc.binding) return fc;
|
|
449
|
+
const result = resolveCandidate(fc, Array.isArray(fc.binding) ? fc.binding : [fc.binding]);
|
|
450
|
+
if (result.viaDestructure) {
|
|
451
|
+
const set = destructureAdditions.get(result.viaDestructure) ?? /* @__PURE__ */ new Set();
|
|
452
|
+
set.add(fc.callee);
|
|
453
|
+
destructureAdditions.set(result.viaDestructure, set);
|
|
454
|
+
const { binding: _drop, ...rest } = fc;
|
|
455
|
+
return rest;
|
|
456
|
+
}
|
|
457
|
+
if (result.binding) return {
|
|
458
|
+
...fc,
|
|
459
|
+
binding: result.binding
|
|
460
|
+
};
|
|
461
|
+
const { binding: _none, ...rest } = fc;
|
|
462
|
+
return rest;
|
|
463
|
+
});
|
|
464
|
+
const augmentations = [...destructureAdditions.entries()].map(([dest, names]) => {
|
|
465
|
+
const existing = /* @__PURE__ */ new Set();
|
|
466
|
+
for (const el of dest.elements) if (ts.isIdentifier(el.name)) existing.add(el.name.text);
|
|
467
|
+
return {
|
|
468
|
+
dest,
|
|
469
|
+
toAdd: [...names].filter((n) => !existing.has(n))
|
|
470
|
+
};
|
|
471
|
+
}).filter((a) => a.toAdd.length > 0).sort((a, b) => b.dest.getEnd() - a.dest.getEnd());
|
|
472
|
+
let nextSource = sourceCode;
|
|
473
|
+
for (const { dest, toAdd } of augmentations) {
|
|
474
|
+
let closeOffset = dest.getEnd() - 1;
|
|
475
|
+
while (nextSource[closeOffset - 1] === " ") closeOffset--;
|
|
476
|
+
const isEmpty = dest.elements.length === 0;
|
|
477
|
+
const insertion = (isEmpty ? " " : ", ") + toAdd.join(", ") + (isEmpty ? " " : "");
|
|
478
|
+
nextSource = nextSource.slice(0, closeOffset) + insertion + nextSource.slice(closeOffset);
|
|
479
|
+
}
|
|
480
|
+
return {
|
|
481
|
+
sourceCode: nextSource,
|
|
482
|
+
value: adjusted
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Find the destructured first parameter of a call matching `enclosingCall` at
|
|
487
|
+
* the top level of `sourceFile`. Returns null when no such call exists or when
|
|
488
|
+
* its first parameter is not an object binding pattern.
|
|
489
|
+
*
|
|
490
|
+
* When `enclosingCall.import` is provided, the callee identifier must resolve
|
|
491
|
+
* to a named import matching that spec.
|
|
492
|
+
*/
|
|
493
|
+
function findEnclosingCallDestructure(sourceFile, enclosingCall) {
|
|
494
|
+
if (!enclosingCall) return null;
|
|
495
|
+
if (!(enclosingCall.import ? sourceFileHasNamedImport(sourceFile, enclosingCall.import.name, enclosingCall.import.from) : true)) return null;
|
|
496
|
+
let found = null;
|
|
497
|
+
const visit = (node) => {
|
|
498
|
+
if (found) return;
|
|
499
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === enclosingCall.callee) {
|
|
500
|
+
const factory = node.arguments.find((arg) => ts.isArrowFunction(arg) || ts.isFunctionExpression(arg));
|
|
501
|
+
if (factory) {
|
|
502
|
+
const param = factory.parameters[0];
|
|
503
|
+
if (param?.name && ts.isObjectBindingPattern(param.name)) {
|
|
504
|
+
found = param.name;
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
ts.forEachChild(node, visit);
|
|
510
|
+
};
|
|
511
|
+
visit(sourceFile);
|
|
512
|
+
return found;
|
|
513
|
+
}
|
|
514
|
+
function sourceFileHasNamedImport(sourceFile, name, from) {
|
|
515
|
+
for (const stmt of sourceFile.statements) {
|
|
516
|
+
if (!ts.isImportDeclaration(stmt)) continue;
|
|
517
|
+
if (!ts.isStringLiteral(stmt.moduleSpecifier)) continue;
|
|
518
|
+
if (stmt.moduleSpecifier.text !== from) continue;
|
|
519
|
+
const clause = stmt.importClause;
|
|
520
|
+
if (!clause?.namedBindings || !ts.isNamedImports(clause.namedBindings)) continue;
|
|
521
|
+
for (const el of clause.namedBindings.elements) if (el.name.text === name) return true;
|
|
522
|
+
}
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
/** Walk a ModelPropValue tree, transforming each functionCall via `fn`. */
|
|
526
|
+
function mapFunctionCalls(value, fn) {
|
|
527
|
+
switch (value.kind) {
|
|
528
|
+
case "functionCall": {
|
|
529
|
+
const mappedArgs = value.args.map((a) => mapFunctionCalls(a, fn));
|
|
530
|
+
return fn({
|
|
531
|
+
...value,
|
|
532
|
+
args: mappedArgs
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
case "object": {
|
|
536
|
+
const properties = {};
|
|
537
|
+
for (const [k, v] of Object.entries(value.properties)) properties[k] = mapFunctionCalls(v, fn);
|
|
538
|
+
return {
|
|
539
|
+
...value,
|
|
540
|
+
properties
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
case "array":
|
|
544
|
+
case "tuple": return {
|
|
545
|
+
...value,
|
|
546
|
+
elements: value.elements.map((el) => mapFunctionCalls(el, fn))
|
|
547
|
+
};
|
|
548
|
+
default: return value;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
function findReturnObjectInFunctionLike(fn) {
|
|
552
|
+
if (ts.isArrowFunction(fn) && !ts.isBlock(fn.body)) {
|
|
553
|
+
const body = ts.isParenthesizedExpression(fn.body) ? fn.body.expression : fn.body;
|
|
554
|
+
return ts.isObjectLiteralExpression(body) ? body : null;
|
|
555
|
+
}
|
|
556
|
+
let result = null;
|
|
557
|
+
const visit = (n) => {
|
|
558
|
+
if (result) return;
|
|
559
|
+
if (ts.isReturnStatement(n) && n.expression && ts.isObjectLiteralExpression(n.expression)) {
|
|
560
|
+
result = n.expression;
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
ts.forEachChild(n, visit);
|
|
564
|
+
};
|
|
565
|
+
if (fn.body) visit(fn.body);
|
|
566
|
+
return result;
|
|
567
|
+
}
|
|
568
|
+
//#endregion
|
|
569
|
+
//#region src/prompt/file/file-prompt-provider.ts
|
|
570
|
+
const DEFAULT_IGNORE_PATTERNS = [
|
|
571
|
+
"**/node_modules/**",
|
|
572
|
+
"**/dist/**",
|
|
573
|
+
"**/.git/**"
|
|
574
|
+
];
|
|
575
|
+
let defaultIDCounter = 0;
|
|
576
|
+
/**
|
|
577
|
+
* A {@link PromptProvider} that discovers and serves prompts from
|
|
578
|
+
* files on the local file system (or any {@link FileProvider}).
|
|
579
|
+
*
|
|
580
|
+
* Out of the box it scans for `**\/*.prompt.ts` files and parses them with
|
|
581
|
+
* {@link TSPromptFileType}. Pass a {@link FilePromptProviderOptions} to the
|
|
582
|
+
* constructor to customize this behavior. You must specify at least
|
|
583
|
+
* {@link FilePromptProviderOptions.sdk}.
|
|
584
|
+
*
|
|
585
|
+
* @example
|
|
586
|
+
* ```ts
|
|
587
|
+
* const provider = new FilePromptProvider({ rootDir: '/my/project', sdk: new VercelAISDK() });
|
|
588
|
+
* const prompts = await provider.getAllPrompts();
|
|
589
|
+
* ```
|
|
590
|
+
*/
|
|
591
|
+
var FilePromptProvider = class {
|
|
592
|
+
id;
|
|
593
|
+
displayName = "File System";
|
|
594
|
+
description = "Create a .prompt.ts file";
|
|
595
|
+
icon = "<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentColor\"><path d=\"M1.5 3A1.5 1.5 0 000 4.5v8A1.5 1.5 0 001.5 14h13a1.5 1.5 0 001.5-1.5v-7A1.5 1.5 0 0014.5 4H8L6.5 2.5h-5z\"/></svg>";
|
|
596
|
+
files = null;
|
|
597
|
+
rootDir;
|
|
598
|
+
fileType;
|
|
599
|
+
fileProvider;
|
|
600
|
+
includePatterns;
|
|
601
|
+
ignorePatterns;
|
|
602
|
+
sdkAdapter;
|
|
603
|
+
suppressedWatchEvents = /* @__PURE__ */ new Map();
|
|
604
|
+
constructor({ id = "fs" + (defaultIDCounter++ ? defaultIDCounter : ""), rootDir = process.cwd(), fileProvider = new LocalFileProvider(), fileType, includePatterns, ignorePatterns = DEFAULT_IGNORE_PATTERNS, sdk }) {
|
|
605
|
+
fileType ??= new TSPromptFileType(fileProvider);
|
|
606
|
+
this.id = id;
|
|
607
|
+
this.rootDir = rootDir;
|
|
608
|
+
this.fileProvider = fileProvider;
|
|
609
|
+
this.fileType = fileType;
|
|
610
|
+
this.includePatterns = includePatterns ?? fileType.defaultIncludePatterns;
|
|
611
|
+
this.ignorePatterns = ignorePatterns;
|
|
612
|
+
this.sdkAdapter = sdk;
|
|
613
|
+
}
|
|
614
|
+
async getAllPrompts() {
|
|
615
|
+
await this.ensureFiles();
|
|
616
|
+
return (await this.fileType.parsePrompts(this.files, this.rootDir)).map((p) => this.normalizeFilePrompt(p));
|
|
617
|
+
}
|
|
618
|
+
async getPrompt(id) {
|
|
619
|
+
const parsed = await this.getParsedPrompt(id);
|
|
620
|
+
return parsed ? this.normalizeFilePrompt(parsed) : null;
|
|
621
|
+
}
|
|
622
|
+
async updatePromptProperties(promptId, updates) {
|
|
623
|
+
const parsed = await this.getParsedPrompt(promptId);
|
|
624
|
+
if (!parsed) throw new Error("Prompt not found");
|
|
625
|
+
const [filePath, promptName] = this.parsePromptId(promptId);
|
|
626
|
+
const { definitions, values } = parsed.extractedProps;
|
|
627
|
+
const rawUpdates = this.sdkAdapter.denormalizeUpdates(updates, values);
|
|
628
|
+
for (const [propertyName, value] of Object.entries(rawUpdates)) {
|
|
629
|
+
this.suppressNextWatchEvent(filePath, "change");
|
|
630
|
+
const propDef = definitions.find((d) => d.name === propertyName);
|
|
631
|
+
const currentValue = values?.[propertyName];
|
|
632
|
+
if (value === null) {
|
|
633
|
+
if (!propDef) throw new Error(`Property '${propertyName}' not found`);
|
|
634
|
+
await this.fileType.removeProperty(filePath, propDef);
|
|
635
|
+
} else if (!propDef) await this.fileType.addProperty(filePath, promptName, propertyName, value);
|
|
636
|
+
else {
|
|
637
|
+
if (currentValue && !isEditable(currentValue)) throw new Error(`Property '${propertyName}' is not editable`);
|
|
638
|
+
if (!propDef.valueSpan) throw new Error(`Property '${propertyName}' is missing source metadata`);
|
|
639
|
+
await this.fileType.updateProperty(filePath, propDef, value, promptId);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return await this.getPrompt(promptId);
|
|
643
|
+
}
|
|
644
|
+
async getParsedPrompt(id) {
|
|
645
|
+
const [filePath, name] = this.parsePromptId(id);
|
|
646
|
+
let prompts;
|
|
647
|
+
try {
|
|
648
|
+
prompts = await this.fileType.parsePrompts([filePath], this.rootDir);
|
|
649
|
+
} catch {
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
652
|
+
return prompts.find((p) => p.name === name) || null;
|
|
653
|
+
}
|
|
654
|
+
normalizeFilePrompt(parsed) {
|
|
655
|
+
return {
|
|
656
|
+
...this.sdkAdapter.normalizePrompt(parsed),
|
|
657
|
+
metadata: parsed.metadata
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
getModelCatalog() {
|
|
661
|
+
return this.sdkAdapter.getModelCatalog();
|
|
662
|
+
}
|
|
663
|
+
getModelParameters() {
|
|
664
|
+
return this.sdkAdapter.getModelParameters(this.rootDir);
|
|
665
|
+
}
|
|
666
|
+
async execute(promptId, params, stream) {
|
|
667
|
+
const [filePath, promptName] = this.parsePromptId(promptId);
|
|
668
|
+
const config = await this.fileType.loadConfig(filePath, promptName, params);
|
|
669
|
+
return this.sdkAdapter.executeConfig(config, stream);
|
|
670
|
+
}
|
|
671
|
+
async renamePrompt(promptId, newName) {
|
|
672
|
+
const [filePath, oldName] = this.parsePromptId(promptId);
|
|
673
|
+
this.suppressNextWatchEvent(filePath, "change");
|
|
674
|
+
await this.fileType.renamePrompt(filePath, oldName, newName);
|
|
675
|
+
const relFilePath = path.relative(this.rootDir, filePath);
|
|
676
|
+
const prompt = await this.getPrompt(`${relFilePath}#${newName}`);
|
|
677
|
+
if (!prompt) throw new Error("Failed to find renamed prompt");
|
|
678
|
+
return prompt;
|
|
679
|
+
}
|
|
680
|
+
async addPrompt(partial) {
|
|
681
|
+
const relFilePath = partial.metadata?.relativeFilePath;
|
|
682
|
+
if (relFilePath) {
|
|
683
|
+
const normalizedRelFilePath = path.extname(relFilePath) ? relFilePath : relFilePath + this.fileType.defaultFileExtension;
|
|
684
|
+
const absPath = path.join(this.rootDir, normalizedRelFilePath);
|
|
685
|
+
const baseName = path.basename(normalizedRelFilePath);
|
|
686
|
+
const firstDot = baseName.indexOf(".");
|
|
687
|
+
const promptsId = firstDot >= 0 ? baseName.slice(0, firstDot) : baseName;
|
|
688
|
+
const name = partial.name ?? promptsId;
|
|
689
|
+
const content = this.fileType.newPromptSkeleton(promptsId, name, this.sdkAdapter.promptsHelperImport);
|
|
690
|
+
this.suppressNextWatchEvent(absPath, "add");
|
|
691
|
+
await this.fileProvider.writeFile(absPath, content);
|
|
692
|
+
if (this.files && !this.files.includes(absPath)) this.files.push(absPath);
|
|
693
|
+
const prompt = await this.getPrompt(`${normalizedRelFilePath}#${name}`);
|
|
694
|
+
if (!prompt) throw new Error("Failed to create prompt");
|
|
695
|
+
return prompt;
|
|
696
|
+
}
|
|
697
|
+
const directories = await this.listDirectories();
|
|
698
|
+
const prompts = await this.getAllPrompts();
|
|
699
|
+
const dirCounts = /* @__PURE__ */ new Map();
|
|
700
|
+
for (const p of prompts) {
|
|
701
|
+
const dir = path.dirname(p.metadata.relativeFilePath);
|
|
702
|
+
dirCounts.set(dir, (dirCounts.get(dir) ?? 0) + 1);
|
|
703
|
+
}
|
|
704
|
+
let defaultDir = ".";
|
|
705
|
+
let maxCount = 0;
|
|
706
|
+
for (const [dir, count] of dirCounts) if (count > maxCount && directories.includes(dir)) {
|
|
707
|
+
defaultDir = dir;
|
|
708
|
+
maxCount = count;
|
|
709
|
+
}
|
|
710
|
+
return { fields: [
|
|
711
|
+
{
|
|
712
|
+
name: "directory",
|
|
713
|
+
label: "Directory",
|
|
714
|
+
type: "select",
|
|
715
|
+
required: true,
|
|
716
|
+
defaultValue: defaultDir,
|
|
717
|
+
options: directories.map((d) => ({
|
|
718
|
+
label: d === "." ? "(root)" : d,
|
|
719
|
+
value: d
|
|
720
|
+
}))
|
|
721
|
+
},
|
|
722
|
+
{
|
|
723
|
+
name: "fileName",
|
|
724
|
+
label: "File name",
|
|
725
|
+
type: "text",
|
|
726
|
+
required: true,
|
|
727
|
+
placeholder: `my-prompt (or my-prompt${this.fileType.defaultFileExtension})`
|
|
728
|
+
},
|
|
729
|
+
{
|
|
730
|
+
name: "name",
|
|
731
|
+
label: "Prompt name",
|
|
732
|
+
type: "text",
|
|
733
|
+
required: false,
|
|
734
|
+
placeholder: "Default: file name without extension"
|
|
735
|
+
}
|
|
736
|
+
] };
|
|
737
|
+
}
|
|
738
|
+
watch(callback) {
|
|
739
|
+
return this.fileProvider.watch(this.includePatterns, {
|
|
740
|
+
cwd: this.rootDir,
|
|
741
|
+
ignored: this.ignorePatterns
|
|
742
|
+
}, async (eventType, filePath) => {
|
|
743
|
+
const absolutePath = this.resolveFilePath(filePath);
|
|
744
|
+
if (this.consumeSuppressedWatchEvent(absolutePath, eventType)) return;
|
|
745
|
+
if (eventType === "change" || eventType === "add") {
|
|
746
|
+
if (this.files && !this.files.includes(absolutePath)) this.files.push(absolutePath);
|
|
747
|
+
(await this.fileType.parsePrompts([absolutePath], this.rootDir)).forEach((prompt) => {
|
|
748
|
+
callback({
|
|
749
|
+
type: eventType === "change" ? "change" : "add",
|
|
750
|
+
promptId: prompt.id
|
|
751
|
+
});
|
|
752
|
+
});
|
|
753
|
+
} else {
|
|
754
|
+
if (this.files) this.files = this.files.filter((f) => f !== absolutePath);
|
|
755
|
+
callback({
|
|
756
|
+
type: "remove",
|
|
757
|
+
promptId: filePath
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
async ensureFiles() {
|
|
763
|
+
if (!this.files) this.files = await Array.fromAsync(this.findPromptFiles());
|
|
764
|
+
}
|
|
765
|
+
suppressNextWatchEvent(filePath, eventType) {
|
|
766
|
+
if (eventType !== "change" && eventType !== "add") return;
|
|
767
|
+
const key = `${eventType}:${filePath}`;
|
|
768
|
+
const entry = this.suppressedWatchEvents.get(key);
|
|
769
|
+
this.suppressedWatchEvents.set(key, {
|
|
770
|
+
remaining: (entry?.remaining ?? 0) + 1,
|
|
771
|
+
expiresAt: Date.now() + 2e3
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
consumeSuppressedWatchEvent(filePath, eventType) {
|
|
775
|
+
const key = `${eventType}:${filePath}`;
|
|
776
|
+
const entry = this.suppressedWatchEvents.get(key);
|
|
777
|
+
if (!entry) return false;
|
|
778
|
+
if (entry.expiresAt < Date.now()) {
|
|
779
|
+
this.suppressedWatchEvents.delete(key);
|
|
780
|
+
return false;
|
|
781
|
+
}
|
|
782
|
+
if (entry.remaining <= 1) this.suppressedWatchEvents.delete(key);
|
|
783
|
+
else this.suppressedWatchEvents.set(key, {
|
|
784
|
+
remaining: entry.remaining - 1,
|
|
785
|
+
expiresAt: entry.expiresAt
|
|
786
|
+
});
|
|
787
|
+
return true;
|
|
788
|
+
}
|
|
789
|
+
async *findPromptFiles() {
|
|
790
|
+
const uniqueFiles = /* @__PURE__ */ new Set();
|
|
791
|
+
for (const pattern of this.includePatterns) {
|
|
792
|
+
const iter = this.fileProvider.glob(pattern, {
|
|
793
|
+
cwd: this.rootDir,
|
|
794
|
+
absolute: true,
|
|
795
|
+
ignore: this.ignorePatterns
|
|
796
|
+
});
|
|
797
|
+
for await (const file of iter) if (!uniqueFiles.has(file)) {
|
|
798
|
+
uniqueFiles.add(file);
|
|
799
|
+
yield file;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
parsePromptId(id) {
|
|
804
|
+
const hashIdx = id.lastIndexOf("#");
|
|
805
|
+
if (hashIdx < 0) throw new Error(`Invalid prompt ID format: ${id}`);
|
|
806
|
+
const relativePath = id.slice(0, hashIdx);
|
|
807
|
+
const functionName = id.slice(hashIdx + 1);
|
|
808
|
+
return [path.isAbsolute(relativePath) ? relativePath : path.join(this.rootDir, relativePath), functionName];
|
|
809
|
+
}
|
|
810
|
+
async listDirectories() {
|
|
811
|
+
const dirs = new Set(["."]);
|
|
812
|
+
const iter = this.fileProvider.glob("**/", {
|
|
813
|
+
cwd: this.rootDir,
|
|
814
|
+
ignore: this.ignorePatterns
|
|
815
|
+
});
|
|
816
|
+
for await (const dir of iter) {
|
|
817
|
+
const clean = dir.replace(/\/$/, "");
|
|
818
|
+
if (clean) dirs.add(clean);
|
|
819
|
+
}
|
|
820
|
+
return Array.from(dirs).sort();
|
|
821
|
+
}
|
|
822
|
+
resolveFilePath(relativePath) {
|
|
823
|
+
if (relativePath.startsWith("/")) return relativePath;
|
|
824
|
+
return `${this.rootDir}/${relativePath}`;
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
//#endregion
|
|
828
|
+
//#region src/sdk/gemini-interactions-sdk.ts
|
|
829
|
+
const MODEL_KEY = "model";
|
|
830
|
+
const AGENT_KEY = "agent";
|
|
831
|
+
const SYSTEM_KEY = "system_instruction";
|
|
832
|
+
const INPUT_KEY = "input";
|
|
833
|
+
const GENERATION_CONFIG_KEY = "generation_config";
|
|
834
|
+
const FALLBACK_GENERATION_CONFIG_PARAMS = [
|
|
835
|
+
{
|
|
836
|
+
name: "temperature",
|
|
837
|
+
description: "Controls the randomness of the output.",
|
|
838
|
+
type: {
|
|
839
|
+
kind: "primitive",
|
|
840
|
+
syntax: "number"
|
|
841
|
+
},
|
|
842
|
+
optional: true
|
|
843
|
+
},
|
|
844
|
+
{
|
|
845
|
+
name: "top_p",
|
|
846
|
+
description: "The maximum cumulative probability of tokens to consider when sampling.",
|
|
847
|
+
type: {
|
|
848
|
+
kind: "primitive",
|
|
849
|
+
syntax: "number"
|
|
850
|
+
},
|
|
851
|
+
optional: true
|
|
852
|
+
},
|
|
853
|
+
{
|
|
854
|
+
name: "max_output_tokens",
|
|
855
|
+
description: "The maximum number of tokens to include in the response.",
|
|
856
|
+
type: {
|
|
857
|
+
kind: "primitive",
|
|
858
|
+
syntax: "number"
|
|
859
|
+
},
|
|
860
|
+
optional: true
|
|
861
|
+
},
|
|
862
|
+
{
|
|
863
|
+
name: "seed",
|
|
864
|
+
description: "Seed used in decoding for reproducibility.",
|
|
865
|
+
type: {
|
|
866
|
+
kind: "primitive",
|
|
867
|
+
syntax: "number"
|
|
868
|
+
},
|
|
869
|
+
optional: true
|
|
870
|
+
},
|
|
871
|
+
{
|
|
872
|
+
name: "stop_sequences",
|
|
873
|
+
description: "A list of character sequences that will stop output interaction.",
|
|
874
|
+
type: {
|
|
875
|
+
kind: "array",
|
|
876
|
+
syntax: "string[]",
|
|
877
|
+
elementType: {
|
|
878
|
+
kind: "primitive",
|
|
879
|
+
syntax: "string"
|
|
880
|
+
}
|
|
881
|
+
},
|
|
882
|
+
optional: true
|
|
883
|
+
},
|
|
884
|
+
{
|
|
885
|
+
name: "thinking_level",
|
|
886
|
+
description: "The level of thought tokens that the model should generate.",
|
|
887
|
+
type: {
|
|
888
|
+
kind: "union",
|
|
889
|
+
syntax: "'minimal' | 'low' | 'medium' | 'high'",
|
|
890
|
+
types: [
|
|
891
|
+
{
|
|
892
|
+
kind: "constant",
|
|
893
|
+
syntax: "'minimal'",
|
|
894
|
+
value: "minimal"
|
|
895
|
+
},
|
|
896
|
+
{
|
|
897
|
+
kind: "constant",
|
|
898
|
+
syntax: "'low'",
|
|
899
|
+
value: "low"
|
|
900
|
+
},
|
|
901
|
+
{
|
|
902
|
+
kind: "constant",
|
|
903
|
+
syntax: "'medium'",
|
|
904
|
+
value: "medium"
|
|
905
|
+
},
|
|
906
|
+
{
|
|
907
|
+
kind: "constant",
|
|
908
|
+
syntax: "'high'",
|
|
909
|
+
value: "high"
|
|
910
|
+
}
|
|
911
|
+
]
|
|
912
|
+
},
|
|
913
|
+
optional: true
|
|
914
|
+
},
|
|
915
|
+
{
|
|
916
|
+
name: "thinking_summaries",
|
|
917
|
+
description: "Whether to include thought summaries in the response.",
|
|
918
|
+
type: {
|
|
919
|
+
kind: "union",
|
|
920
|
+
syntax: "'auto' | 'none'",
|
|
921
|
+
types: [{
|
|
922
|
+
kind: "constant",
|
|
923
|
+
syntax: "'auto'",
|
|
924
|
+
value: "auto"
|
|
925
|
+
}, {
|
|
926
|
+
kind: "constant",
|
|
927
|
+
syntax: "'none'",
|
|
928
|
+
value: "none"
|
|
929
|
+
}]
|
|
930
|
+
},
|
|
931
|
+
optional: true
|
|
932
|
+
}
|
|
933
|
+
];
|
|
934
|
+
/**
|
|
935
|
+
* {@link SDKAdapter} implementation for the Google GenAI
|
|
936
|
+
* [Interactions API](https://ai.google.dev/gemini-api/docs/interactions)
|
|
937
|
+
* (`@google/genai` package). Currently experimental and untested.
|
|
938
|
+
*/
|
|
939
|
+
var GeminiInteractionsSDK = class {
|
|
940
|
+
promptsHelperImport = "FIXME";
|
|
941
|
+
getModelCatalog() {
|
|
942
|
+
return Promise.resolve({
|
|
943
|
+
modelValueTypes: {
|
|
944
|
+
model: {
|
|
945
|
+
label: "Models",
|
|
946
|
+
description: "Models"
|
|
947
|
+
},
|
|
948
|
+
agent: {
|
|
949
|
+
label: "Agents",
|
|
950
|
+
description: "Agents"
|
|
951
|
+
}
|
|
952
|
+
},
|
|
953
|
+
groups: { Google: { customValueTemplates: {
|
|
954
|
+
model: {
|
|
955
|
+
kind: "object",
|
|
956
|
+
properties: {
|
|
957
|
+
key: {
|
|
958
|
+
kind: "primitive",
|
|
959
|
+
value: "model"
|
|
960
|
+
},
|
|
961
|
+
value: {
|
|
962
|
+
kind: "primitive",
|
|
963
|
+
value: "$input"
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
},
|
|
967
|
+
agent: {
|
|
968
|
+
kind: "object",
|
|
969
|
+
properties: {
|
|
970
|
+
key: {
|
|
971
|
+
kind: "primitive",
|
|
972
|
+
value: "agent"
|
|
973
|
+
},
|
|
974
|
+
value: {
|
|
975
|
+
kind: "primitive",
|
|
976
|
+
value: "$input"
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
} } },
|
|
981
|
+
models: [
|
|
982
|
+
{
|
|
983
|
+
id: "gemini-3.1-pro-preview",
|
|
984
|
+
label: "Gemini 3.1 Pro Preview",
|
|
985
|
+
values: { model: {
|
|
986
|
+
kind: "object",
|
|
987
|
+
properties: {
|
|
988
|
+
key: {
|
|
989
|
+
kind: "primitive",
|
|
990
|
+
value: "model"
|
|
991
|
+
},
|
|
992
|
+
value: {
|
|
993
|
+
kind: "primitive",
|
|
994
|
+
value: "gemini-3.1-pro-preview"
|
|
995
|
+
}
|
|
996
|
+
},
|
|
997
|
+
displayValue: "gemini-3.1-pro-preview"
|
|
998
|
+
} },
|
|
999
|
+
group: "Google"
|
|
1000
|
+
},
|
|
1001
|
+
{
|
|
1002
|
+
id: "gemini-3-flash-preview",
|
|
1003
|
+
label: "Gemini 3 Flash Preview",
|
|
1004
|
+
values: { model: {
|
|
1005
|
+
kind: "object",
|
|
1006
|
+
properties: {
|
|
1007
|
+
key: {
|
|
1008
|
+
kind: "primitive",
|
|
1009
|
+
value: "model"
|
|
1010
|
+
},
|
|
1011
|
+
value: {
|
|
1012
|
+
kind: "primitive",
|
|
1013
|
+
value: "gemini-3-flash-preview"
|
|
1014
|
+
}
|
|
1015
|
+
},
|
|
1016
|
+
displayValue: "gemini-3-flash-preview"
|
|
1017
|
+
} },
|
|
1018
|
+
group: "Google"
|
|
1019
|
+
},
|
|
1020
|
+
{
|
|
1021
|
+
id: "gemini-3.1-flash-lite-preview",
|
|
1022
|
+
label: "Gemini 3.1 Flash-Lite Preview",
|
|
1023
|
+
values: { model: {
|
|
1024
|
+
kind: "object",
|
|
1025
|
+
properties: {
|
|
1026
|
+
key: {
|
|
1027
|
+
kind: "primitive",
|
|
1028
|
+
value: "model"
|
|
1029
|
+
},
|
|
1030
|
+
value: {
|
|
1031
|
+
kind: "primitive",
|
|
1032
|
+
value: "gemini-3.1-flash-lite-preview"
|
|
1033
|
+
}
|
|
1034
|
+
},
|
|
1035
|
+
displayValue: "gemini-3.1-flash-lite-preview"
|
|
1036
|
+
} },
|
|
1037
|
+
group: "Google"
|
|
1038
|
+
},
|
|
1039
|
+
{
|
|
1040
|
+
id: "gemini-2.5-pro",
|
|
1041
|
+
label: "Gemini 2.5 Pro",
|
|
1042
|
+
values: { model: {
|
|
1043
|
+
kind: "object",
|
|
1044
|
+
properties: {
|
|
1045
|
+
key: {
|
|
1046
|
+
kind: "primitive",
|
|
1047
|
+
value: "model"
|
|
1048
|
+
},
|
|
1049
|
+
value: {
|
|
1050
|
+
kind: "primitive",
|
|
1051
|
+
value: "gemini-2.5-pro"
|
|
1052
|
+
}
|
|
1053
|
+
},
|
|
1054
|
+
displayValue: "gemini-2.5-pro"
|
|
1055
|
+
} },
|
|
1056
|
+
group: "Google"
|
|
1057
|
+
},
|
|
1058
|
+
{
|
|
1059
|
+
id: "gemini-2.5-flash",
|
|
1060
|
+
label: "Gemini 2.5 Flash",
|
|
1061
|
+
values: { model: {
|
|
1062
|
+
kind: "object",
|
|
1063
|
+
properties: {
|
|
1064
|
+
key: {
|
|
1065
|
+
kind: "primitive",
|
|
1066
|
+
value: "model"
|
|
1067
|
+
},
|
|
1068
|
+
value: {
|
|
1069
|
+
kind: "primitive",
|
|
1070
|
+
value: "gemini-2.5-flash"
|
|
1071
|
+
}
|
|
1072
|
+
},
|
|
1073
|
+
displayValue: "gemini-2.5-flash"
|
|
1074
|
+
} },
|
|
1075
|
+
group: "Google"
|
|
1076
|
+
},
|
|
1077
|
+
{
|
|
1078
|
+
id: "gemini-2.5-flash-lite",
|
|
1079
|
+
label: "Gemini 2.5 Flash-lite",
|
|
1080
|
+
values: { model: {
|
|
1081
|
+
kind: "object",
|
|
1082
|
+
properties: {
|
|
1083
|
+
key: {
|
|
1084
|
+
kind: "primitive",
|
|
1085
|
+
value: "model"
|
|
1086
|
+
},
|
|
1087
|
+
value: {
|
|
1088
|
+
kind: "primitive",
|
|
1089
|
+
value: "gemini-2.5-flash-lite"
|
|
1090
|
+
}
|
|
1091
|
+
},
|
|
1092
|
+
displayValue: "gemini-2.5-flash-lite"
|
|
1093
|
+
} },
|
|
1094
|
+
group: "Google"
|
|
1095
|
+
},
|
|
1096
|
+
{
|
|
1097
|
+
id: "deep-research-pro-preview-12-2025",
|
|
1098
|
+
label: "Deep Research Preview",
|
|
1099
|
+
values: { agent: {
|
|
1100
|
+
kind: "object",
|
|
1101
|
+
properties: {
|
|
1102
|
+
key: {
|
|
1103
|
+
kind: "primitive",
|
|
1104
|
+
value: "agent"
|
|
1105
|
+
},
|
|
1106
|
+
value: {
|
|
1107
|
+
kind: "primitive",
|
|
1108
|
+
value: "deep-research-pro-preview-12-2025"
|
|
1109
|
+
}
|
|
1110
|
+
},
|
|
1111
|
+
displayValue: "deep-research-pro-preview-12-2025"
|
|
1112
|
+
} },
|
|
1113
|
+
group: "Google"
|
|
1114
|
+
}
|
|
1115
|
+
]
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
getModelParameters(rootDir) {
|
|
1119
|
+
const dtsPath = findPackageDts("@google/genai", "dist/genai.d.ts", rootDir);
|
|
1120
|
+
if (dtsPath) try {
|
|
1121
|
+
const sourceText = fs$1.readFileSync(dtsPath, "utf-8");
|
|
1122
|
+
const sourceFile = ts.createSourceFile(dtsPath, sourceText, ts.ScriptTarget.Latest, true);
|
|
1123
|
+
const decl = findTypeDeclaration(sourceFile, "GenerationConfig_2");
|
|
1124
|
+
if (decl) return extractPropertiesFromDeclaration(decl, sourceFile).definitions;
|
|
1125
|
+
} catch {}
|
|
1126
|
+
return FALLBACK_GENERATION_CONFIG_PARAMS;
|
|
1127
|
+
}
|
|
1128
|
+
async executeConfig(config, stream) {
|
|
1129
|
+
const result = await new GoogleGenAI({}).interactions.create({
|
|
1130
|
+
...config,
|
|
1131
|
+
stream,
|
|
1132
|
+
store: false
|
|
1133
|
+
});
|
|
1134
|
+
if ("id" in result) return {
|
|
1135
|
+
text: (result.outputs ?? []).find((o) => o.type === "text")?.text ?? "",
|
|
1136
|
+
usage: result.usage
|
|
1137
|
+
};
|
|
1138
|
+
else return streamTextFromSSE(result);
|
|
1139
|
+
}
|
|
1140
|
+
normalizePrompt(prompt) {
|
|
1141
|
+
const { definitions, values } = prompt.extractedProps;
|
|
1142
|
+
const systemValue = values?.[SYSTEM_KEY];
|
|
1143
|
+
const inputValue = values?.[INPUT_KEY];
|
|
1144
|
+
const modelValue = values?.[MODEL_KEY];
|
|
1145
|
+
const agentValue = values?.[AGENT_KEY];
|
|
1146
|
+
let model;
|
|
1147
|
+
let modelEditable = false;
|
|
1148
|
+
if (modelValue) {
|
|
1149
|
+
model = {
|
|
1150
|
+
kind: "object",
|
|
1151
|
+
properties: {
|
|
1152
|
+
key: {
|
|
1153
|
+
kind: "primitive",
|
|
1154
|
+
value: "model"
|
|
1155
|
+
},
|
|
1156
|
+
value: modelValue
|
|
1157
|
+
},
|
|
1158
|
+
displayValue: valueToSourceText(modelValue)
|
|
1159
|
+
};
|
|
1160
|
+
modelEditable = isEditable(modelValue);
|
|
1161
|
+
} else if (agentValue) {
|
|
1162
|
+
model = {
|
|
1163
|
+
kind: "object",
|
|
1164
|
+
properties: {
|
|
1165
|
+
key: {
|
|
1166
|
+
kind: "primitive",
|
|
1167
|
+
value: "agent"
|
|
1168
|
+
},
|
|
1169
|
+
value: agentValue
|
|
1170
|
+
},
|
|
1171
|
+
displayValue: valueToSourceText(agentValue)
|
|
1172
|
+
};
|
|
1173
|
+
modelEditable = isEditable(agentValue);
|
|
1174
|
+
}
|
|
1175
|
+
const genConfigDef = definitions.find((d) => d.name === GENERATION_CONFIG_KEY);
|
|
1176
|
+
const genConfigValue = values?.[GENERATION_CONFIG_KEY];
|
|
1177
|
+
const genConfigProps = genConfigValue?.kind === "object" ? genConfigValue.properties : {};
|
|
1178
|
+
const modelParameters = (genConfigDef?.type.kind === "object" ? genConfigDef.type.properties : []).map((def) => {
|
|
1179
|
+
const value = genConfigProps[def.name];
|
|
1180
|
+
return {
|
|
1181
|
+
def,
|
|
1182
|
+
value,
|
|
1183
|
+
editable: value ? isEditable(value) : true
|
|
1184
|
+
};
|
|
1185
|
+
});
|
|
1186
|
+
return {
|
|
1187
|
+
id: prompt.id,
|
|
1188
|
+
providerId: prompt.providerId,
|
|
1189
|
+
name: prompt.name,
|
|
1190
|
+
functionParameters: prompt.functionParameters,
|
|
1191
|
+
metadata: prompt.metadata,
|
|
1192
|
+
treePath: prompt.treePath,
|
|
1193
|
+
model,
|
|
1194
|
+
modelEditable,
|
|
1195
|
+
system: systemValue,
|
|
1196
|
+
systemEditable: systemValue ? isEditable(systemValue) : true,
|
|
1197
|
+
messages: extractMessages(inputValue),
|
|
1198
|
+
messagesEditable: inputValue ? isEditable(inputValue) : true,
|
|
1199
|
+
modelParameters
|
|
1200
|
+
};
|
|
1201
|
+
}
|
|
1202
|
+
denormalizeUpdates(updates, currentValues) {
|
|
1203
|
+
const out = {};
|
|
1204
|
+
if ("model" in updates) {
|
|
1205
|
+
if (currentValues && MODEL_KEY in currentValues) out[MODEL_KEY] = null;
|
|
1206
|
+
if (currentValues && AGENT_KEY in currentValues) out[AGENT_KEY] = null;
|
|
1207
|
+
const modelUpdate = updates.model;
|
|
1208
|
+
if (modelUpdate?.kind === "object" && modelUpdate.properties.key?.kind === "primitive") {
|
|
1209
|
+
const key = String(modelUpdate.properties.key.value);
|
|
1210
|
+
out[key] = modelUpdate.properties.value;
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
if ("system" in updates) out[SYSTEM_KEY] = updates.system ?? null;
|
|
1214
|
+
if ("messages" in updates) out[INPUT_KEY] = updates.messages == null ? null : messagesToValue(updates.messages);
|
|
1215
|
+
if (updates.modelParameters) {
|
|
1216
|
+
const current = currentValues?.[GENERATION_CONFIG_KEY];
|
|
1217
|
+
const merged = current?.kind === "object" ? { ...current.properties } : {};
|
|
1218
|
+
for (const [name, value] of Object.entries(updates.modelParameters)) if (value === null) delete merged[name];
|
|
1219
|
+
else merged[name] = value;
|
|
1220
|
+
out[GENERATION_CONFIG_KEY] = Object.keys(merged).length > 0 ? {
|
|
1221
|
+
kind: "object",
|
|
1222
|
+
properties: merged
|
|
1223
|
+
} : null;
|
|
1224
|
+
}
|
|
1225
|
+
return out;
|
|
1226
|
+
}
|
|
1227
|
+
};
|
|
1228
|
+
/**
|
|
1229
|
+
* Convert a {@link NormalizedMessage} array into a PropValue representing the
|
|
1230
|
+
* `input` property: an array of [Step](https://ai.google.dev/api/interactions-api#Resource:Step)
|
|
1231
|
+
* objects (`{type: 'user_input' | 'model_output', content: [...]}`), each
|
|
1232
|
+
* carrying a single text [Content](https://ai.google.dev/api/interactions-api#schema-example-Content-text).
|
|
1233
|
+
*/
|
|
1234
|
+
function messagesToValue(msgs) {
|
|
1235
|
+
return {
|
|
1236
|
+
kind: "array",
|
|
1237
|
+
elements: msgs.map((msg) => ({
|
|
1238
|
+
kind: "object",
|
|
1239
|
+
properties: {
|
|
1240
|
+
type: {
|
|
1241
|
+
kind: "primitive",
|
|
1242
|
+
value: msg.role === "assistant" ? "model_output" : "user_input"
|
|
1243
|
+
},
|
|
1244
|
+
content: {
|
|
1245
|
+
kind: "array",
|
|
1246
|
+
elements: [{
|
|
1247
|
+
kind: "object",
|
|
1248
|
+
properties: {
|
|
1249
|
+
type: {
|
|
1250
|
+
kind: "primitive",
|
|
1251
|
+
value: "text"
|
|
1252
|
+
},
|
|
1253
|
+
text: msg.content
|
|
1254
|
+
}
|
|
1255
|
+
}]
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}))
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
const EMPTY_CONTENT = {
|
|
1262
|
+
kind: "primitive",
|
|
1263
|
+
value: ""
|
|
1264
|
+
};
|
|
1265
|
+
function extractContent(el) {
|
|
1266
|
+
if (el?.kind !== "object" || el.properties.type?.kind !== "primitive") return;
|
|
1267
|
+
switch (el.properties.type.value) {
|
|
1268
|
+
case "text": return {
|
|
1269
|
+
role: "user",
|
|
1270
|
+
content: el.properties.text ?? EMPTY_CONTENT
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
/**
|
|
1275
|
+
* Extract a {@link NormalizedMessage} array from the `input` PropValue.
|
|
1276
|
+
*
|
|
1277
|
+
* The `input` field can be:
|
|
1278
|
+
* - A primitive string (single user message)
|
|
1279
|
+
* - A single content object (e.g. `{type: 'text', text: '...'}`)
|
|
1280
|
+
* - An array of Step objects (https://ai.google.dev/api/interactions-api#Resource:Step)
|
|
1281
|
+
* - An array of Content objects (https://ai.google.dev/api/interactions-api#schema-example-Content-text)
|
|
1282
|
+
*
|
|
1283
|
+
* The Interactions API uses `"model"` for assistant messages; we translate
|
|
1284
|
+
* `"model"` → `"assistant"` on the way in.
|
|
1285
|
+
*/
|
|
1286
|
+
function extractMessages(value) {
|
|
1287
|
+
if (!value) return [];
|
|
1288
|
+
const content = extractContent(value);
|
|
1289
|
+
if (content) return [content];
|
|
1290
|
+
if (value.kind !== "array") return [{
|
|
1291
|
+
role: "user",
|
|
1292
|
+
content: value
|
|
1293
|
+
}];
|
|
1294
|
+
const results = [];
|
|
1295
|
+
for (const el of value.elements) {
|
|
1296
|
+
const content = extractContent(el);
|
|
1297
|
+
if (content) {
|
|
1298
|
+
results.push(content);
|
|
1299
|
+
continue;
|
|
1300
|
+
}
|
|
1301
|
+
if (el.kind !== "object" || el.properties.type?.kind !== "primitive") continue;
|
|
1302
|
+
switch (el.properties.type.value) {
|
|
1303
|
+
case "user_input":
|
|
1304
|
+
results.push(...extractMessages(el.properties.content));
|
|
1305
|
+
break;
|
|
1306
|
+
case "model_output":
|
|
1307
|
+
for (const msg of extractMessages(el.properties.content)) results.push({
|
|
1308
|
+
...msg,
|
|
1309
|
+
role: "assistant"
|
|
1310
|
+
});
|
|
1311
|
+
break;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
return results;
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Yields text chunks from the Interactions API SSE stream.
|
|
1318
|
+
*/
|
|
1319
|
+
async function* streamTextFromSSE(stream) {
|
|
1320
|
+
for await (const chunk of stream) if (chunk.event_type === "content.delta") {
|
|
1321
|
+
if (chunk.delta?.type === "text" && chunk.delta.text) yield chunk.delta.text;
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
//#endregion
|
|
1325
|
+
export { FilePromptProvider, GeminiInteractionsSDK, LocalFileProvider, MemoryFileProvider, MemoryTraceProvider, PROMPT_ID_ATTRIBUTE, PROMPT_NAME_ATTRIBUTE, PROMPT_PROVIDER_ID_ATTRIBUTE, SPAN_KIND_ATTRIBUTE, TSPromptFileType, VercelAISDK, createTracerForPrompt, setupStepCommand };
|