llmist 2.5.0 → 3.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/README.md +10 -1
- package/dist/{chunk-YHS2DYXP.js → chunk-67MMSOAT.js} +1144 -799
- package/dist/chunk-67MMSOAT.js.map +1 -0
- package/dist/{chunk-IHSZUAYN.js → chunk-NBPKLSXJ.js} +136 -20
- package/dist/chunk-NBPKLSXJ.js.map +1 -0
- package/dist/cli.cjs +3994 -3128
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +3254 -2748
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +792 -323
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +734 -481
- package/dist/index.d.ts +734 -481
- package/dist/index.js +32 -16
- package/dist/{mock-stream-ga4KIiwX.d.cts → mock-stream-COHw8h9b.d.cts} +441 -58
- package/dist/{mock-stream-ga4KIiwX.d.ts → mock-stream-COHw8h9b.d.ts} +441 -58
- package/dist/testing/index.cjs +1115 -761
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +346 -346
- package/dist/testing/index.d.ts +346 -346
- package/dist/testing/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-IHSZUAYN.js.map +0 -1
- package/dist/chunk-YHS2DYXP.js.map +0 -1
package/dist/testing/index.cjs
CHANGED
|
@@ -134,6 +134,12 @@ function isAudioPart(part) {
|
|
|
134
134
|
function text(content) {
|
|
135
135
|
return { type: "text", text: content };
|
|
136
136
|
}
|
|
137
|
+
function imageFromBase64(data, mediaType) {
|
|
138
|
+
return {
|
|
139
|
+
type: "image",
|
|
140
|
+
source: { type: "base64", mediaType, data }
|
|
141
|
+
};
|
|
142
|
+
}
|
|
137
143
|
function imageFromUrl(url) {
|
|
138
144
|
return {
|
|
139
145
|
type: "image",
|
|
@@ -194,6 +200,12 @@ function toBase64(data) {
|
|
|
194
200
|
}
|
|
195
201
|
return Buffer.from(data).toString("base64");
|
|
196
202
|
}
|
|
203
|
+
function audioFromBase64(data, mediaType) {
|
|
204
|
+
return {
|
|
205
|
+
type: "audio",
|
|
206
|
+
source: { type: "base64", mediaType, data }
|
|
207
|
+
};
|
|
208
|
+
}
|
|
197
209
|
function audioFromBuffer(buffer, mediaType) {
|
|
198
210
|
const detectedType = mediaType ?? detectAudioMimeType(buffer);
|
|
199
211
|
if (!detectedType) {
|
|
@@ -240,7 +252,9 @@ var init_input_content = __esm({
|
|
|
240
252
|
// WAV (RIFF)
|
|
241
253
|
{ bytes: [82, 73, 70, 70], mimeType: "audio/wav" },
|
|
242
254
|
// WebM
|
|
243
|
-
{ bytes: [26, 69, 223, 163], mimeType: "audio/webm" }
|
|
255
|
+
{ bytes: [26, 69, 223, 163], mimeType: "audio/webm" },
|
|
256
|
+
// FLAC (fLaC)
|
|
257
|
+
{ bytes: [102, 76, 97, 67], mimeType: "audio/flac" }
|
|
244
258
|
];
|
|
245
259
|
}
|
|
246
260
|
});
|
|
@@ -287,13 +301,13 @@ var init_prompt_config = __esm({
|
|
|
287
301
|
});
|
|
288
302
|
|
|
289
303
|
// src/core/messages.ts
|
|
290
|
-
function
|
|
304
|
+
function normalizeMessageContent(content) {
|
|
291
305
|
if (typeof content === "string") {
|
|
292
306
|
return [{ type: "text", text: content }];
|
|
293
307
|
}
|
|
294
308
|
return content;
|
|
295
309
|
}
|
|
296
|
-
function
|
|
310
|
+
function extractMessageText(content) {
|
|
297
311
|
if (typeof content === "string") {
|
|
298
312
|
return content;
|
|
299
313
|
}
|
|
@@ -653,7 +667,17 @@ Produces: { "items": ["first", "second"] }`);
|
|
|
653
667
|
this.messages.push({ role: "user", content: parts });
|
|
654
668
|
return this;
|
|
655
669
|
}
|
|
656
|
-
|
|
670
|
+
/**
|
|
671
|
+
* Record a gadget execution result in the message history.
|
|
672
|
+
* Creates an assistant message with the gadget invocation and a user message with the result.
|
|
673
|
+
*
|
|
674
|
+
* @param gadget - Name of the gadget that was executed
|
|
675
|
+
* @param parameters - Parameters that were passed to the gadget
|
|
676
|
+
* @param result - Text result from the gadget execution
|
|
677
|
+
* @param media - Optional media outputs from the gadget
|
|
678
|
+
* @param mediaIds - Optional IDs for the media outputs
|
|
679
|
+
*/
|
|
680
|
+
addGadgetCallResult(gadget, parameters, result, media, mediaIds) {
|
|
657
681
|
const paramStr = this.formatBlockParameters(parameters, "");
|
|
658
682
|
this.messages.push({
|
|
659
683
|
role: "assistant",
|
|
@@ -661,10 +685,25 @@ Produces: { "items": ["first", "second"] }`);
|
|
|
661
685
|
${paramStr}
|
|
662
686
|
${this.endPrefix}`
|
|
663
687
|
});
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
688
|
+
if (media && media.length > 0 && mediaIds && mediaIds.length > 0) {
|
|
689
|
+
const idRefs = media.map((m, i) => `[Media: ${mediaIds[i]} (${m.kind})]`).join("\n");
|
|
690
|
+
const textWithIds = `Result: ${result}
|
|
691
|
+
${idRefs}`;
|
|
692
|
+
const parts = [text(textWithIds)];
|
|
693
|
+
for (const item of media) {
|
|
694
|
+
if (item.kind === "image") {
|
|
695
|
+
parts.push(imageFromBase64(item.data, item.mimeType));
|
|
696
|
+
} else if (item.kind === "audio") {
|
|
697
|
+
parts.push(audioFromBase64(item.data, item.mimeType));
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
this.messages.push({ role: "user", content: parts });
|
|
701
|
+
} else {
|
|
702
|
+
this.messages.push({
|
|
703
|
+
role: "user",
|
|
704
|
+
content: `Result: ${result}`
|
|
705
|
+
});
|
|
706
|
+
}
|
|
668
707
|
return this;
|
|
669
708
|
}
|
|
670
709
|
/**
|
|
@@ -998,22 +1037,226 @@ var init_registry = __esm({
|
|
|
998
1037
|
}
|
|
999
1038
|
});
|
|
1000
1039
|
|
|
1040
|
+
// src/gadgets/media-store.ts
|
|
1041
|
+
function getLlmistTmpDir() {
|
|
1042
|
+
return (0, import_node_path2.join)((0, import_node_os.homedir)(), ".llmist", "tmp");
|
|
1043
|
+
}
|
|
1044
|
+
var import_node_crypto, import_promises, import_node_os, import_node_path2, MIME_TO_EXTENSION, MediaStore;
|
|
1045
|
+
var init_media_store = __esm({
|
|
1046
|
+
"src/gadgets/media-store.ts"() {
|
|
1047
|
+
"use strict";
|
|
1048
|
+
import_node_crypto = require("crypto");
|
|
1049
|
+
import_promises = require("fs/promises");
|
|
1050
|
+
import_node_os = require("os");
|
|
1051
|
+
import_node_path2 = require("path");
|
|
1052
|
+
MIME_TO_EXTENSION = {
|
|
1053
|
+
// Images
|
|
1054
|
+
"image/png": ".png",
|
|
1055
|
+
"image/jpeg": ".jpg",
|
|
1056
|
+
"image/gif": ".gif",
|
|
1057
|
+
"image/webp": ".webp",
|
|
1058
|
+
"image/svg+xml": ".svg",
|
|
1059
|
+
"image/bmp": ".bmp",
|
|
1060
|
+
"image/tiff": ".tiff",
|
|
1061
|
+
// Audio
|
|
1062
|
+
"audio/mp3": ".mp3",
|
|
1063
|
+
"audio/mpeg": ".mp3",
|
|
1064
|
+
"audio/wav": ".wav",
|
|
1065
|
+
"audio/webm": ".webm",
|
|
1066
|
+
"audio/ogg": ".ogg",
|
|
1067
|
+
"audio/flac": ".flac",
|
|
1068
|
+
"audio/aac": ".aac",
|
|
1069
|
+
// Video
|
|
1070
|
+
"video/mp4": ".mp4",
|
|
1071
|
+
"video/webm": ".webm",
|
|
1072
|
+
"video/ogg": ".ogv",
|
|
1073
|
+
"video/quicktime": ".mov",
|
|
1074
|
+
"video/x-msvideo": ".avi",
|
|
1075
|
+
// Documents
|
|
1076
|
+
"application/pdf": ".pdf",
|
|
1077
|
+
"application/json": ".json",
|
|
1078
|
+
"text/plain": ".txt",
|
|
1079
|
+
"text/html": ".html",
|
|
1080
|
+
"text/css": ".css",
|
|
1081
|
+
"text/javascript": ".js"
|
|
1082
|
+
};
|
|
1083
|
+
MediaStore = class {
|
|
1084
|
+
items = /* @__PURE__ */ new Map();
|
|
1085
|
+
outputDir;
|
|
1086
|
+
counter = 0;
|
|
1087
|
+
initialized = false;
|
|
1088
|
+
/**
|
|
1089
|
+
* Create a new MediaStore.
|
|
1090
|
+
*
|
|
1091
|
+
* @param sessionId - Optional session ID for the output directory.
|
|
1092
|
+
* If not provided, a random ID is generated.
|
|
1093
|
+
*/
|
|
1094
|
+
constructor(sessionId) {
|
|
1095
|
+
const id = sessionId ?? (0, import_node_crypto.randomBytes)(8).toString("hex");
|
|
1096
|
+
this.outputDir = (0, import_node_path2.join)(getLlmistTmpDir(), `media-${id}`);
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Get the output directory path.
|
|
1100
|
+
*/
|
|
1101
|
+
getOutputDir() {
|
|
1102
|
+
return this.outputDir;
|
|
1103
|
+
}
|
|
1104
|
+
/**
|
|
1105
|
+
* Ensure the output directory exists.
|
|
1106
|
+
* @throws Error if directory creation fails
|
|
1107
|
+
*/
|
|
1108
|
+
async ensureDir() {
|
|
1109
|
+
if (this.initialized) return;
|
|
1110
|
+
try {
|
|
1111
|
+
await (0, import_promises.mkdir)(this.outputDir, { recursive: true });
|
|
1112
|
+
this.initialized = true;
|
|
1113
|
+
} catch (error) {
|
|
1114
|
+
throw new Error(
|
|
1115
|
+
`MediaStore: Failed to create directory ${this.outputDir}: ${error instanceof Error ? error.message : String(error)}`
|
|
1116
|
+
);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* Generate a unique media ID.
|
|
1121
|
+
* Format: "media_" + 6 random alphanumeric characters
|
|
1122
|
+
*/
|
|
1123
|
+
generateId() {
|
|
1124
|
+
const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
1125
|
+
let id = "media_";
|
|
1126
|
+
const bytes = (0, import_node_crypto.randomBytes)(6);
|
|
1127
|
+
for (let i = 0; i < 6; i++) {
|
|
1128
|
+
id += chars[bytes[i] % chars.length];
|
|
1129
|
+
}
|
|
1130
|
+
return id;
|
|
1131
|
+
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Get file extension from MIME type.
|
|
1134
|
+
*/
|
|
1135
|
+
getExtension(mimeType) {
|
|
1136
|
+
return MIME_TO_EXTENSION[mimeType] ?? ".bin";
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Store media and return stored metadata with ID.
|
|
1140
|
+
*
|
|
1141
|
+
* @param media - The media output from a gadget
|
|
1142
|
+
* @param gadgetName - Name of the gadget that created this media
|
|
1143
|
+
* @returns Stored media information including generated ID
|
|
1144
|
+
* @throws Error if file write fails
|
|
1145
|
+
*/
|
|
1146
|
+
async store(media, gadgetName) {
|
|
1147
|
+
await this.ensureDir();
|
|
1148
|
+
const id = this.generateId();
|
|
1149
|
+
const ext = this.getExtension(media.mimeType);
|
|
1150
|
+
const filename = media.fileName ?? `${gadgetName}_${String(++this.counter).padStart(3, "0")}${ext}`;
|
|
1151
|
+
const filePath = (0, import_node_path2.join)(this.outputDir, filename);
|
|
1152
|
+
const buffer = Buffer.from(media.data, "base64");
|
|
1153
|
+
try {
|
|
1154
|
+
await (0, import_promises.writeFile)(filePath, buffer);
|
|
1155
|
+
} catch (error) {
|
|
1156
|
+
throw new Error(
|
|
1157
|
+
`MediaStore: Failed to write media file ${filePath}: ${error instanceof Error ? error.message : String(error)}`
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
1160
|
+
const stored = {
|
|
1161
|
+
id,
|
|
1162
|
+
kind: media.kind,
|
|
1163
|
+
path: filePath,
|
|
1164
|
+
mimeType: media.mimeType,
|
|
1165
|
+
sizeBytes: buffer.length,
|
|
1166
|
+
description: media.description,
|
|
1167
|
+
metadata: media.metadata,
|
|
1168
|
+
gadgetName,
|
|
1169
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
1170
|
+
};
|
|
1171
|
+
this.items.set(id, stored);
|
|
1172
|
+
return stored;
|
|
1173
|
+
}
|
|
1174
|
+
/**
|
|
1175
|
+
* Get stored media by ID.
|
|
1176
|
+
*
|
|
1177
|
+
* @param id - The media ID (e.g., "media_a1b2c3")
|
|
1178
|
+
* @returns The stored media or undefined if not found
|
|
1179
|
+
*/
|
|
1180
|
+
get(id) {
|
|
1181
|
+
return this.items.get(id);
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Get the actual file path for a media ID.
|
|
1185
|
+
* Convenience method for gadgets that need the raw path.
|
|
1186
|
+
*
|
|
1187
|
+
* @param id - The media ID
|
|
1188
|
+
* @returns The file path or undefined if not found
|
|
1189
|
+
*/
|
|
1190
|
+
getPath(id) {
|
|
1191
|
+
return this.items.get(id)?.path;
|
|
1192
|
+
}
|
|
1193
|
+
/**
|
|
1194
|
+
* List all stored media, optionally filtered by kind.
|
|
1195
|
+
*
|
|
1196
|
+
* @param kind - Optional media kind to filter by
|
|
1197
|
+
* @returns Array of stored media items
|
|
1198
|
+
*/
|
|
1199
|
+
list(kind) {
|
|
1200
|
+
const all = Array.from(this.items.values());
|
|
1201
|
+
if (kind) {
|
|
1202
|
+
return all.filter((item) => item.kind === kind);
|
|
1203
|
+
}
|
|
1204
|
+
return all;
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Get the count of stored media items.
|
|
1208
|
+
*/
|
|
1209
|
+
get size() {
|
|
1210
|
+
return this.items.size;
|
|
1211
|
+
}
|
|
1212
|
+
/**
|
|
1213
|
+
* Check if a media ID exists.
|
|
1214
|
+
*/
|
|
1215
|
+
has(id) {
|
|
1216
|
+
return this.items.has(id);
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* Clear in-memory store without deleting files.
|
|
1220
|
+
* Resets the counter but leaves files on disk.
|
|
1221
|
+
*/
|
|
1222
|
+
clear() {
|
|
1223
|
+
this.items.clear();
|
|
1224
|
+
this.counter = 0;
|
|
1225
|
+
}
|
|
1226
|
+
/**
|
|
1227
|
+
* Delete all stored files and clear memory.
|
|
1228
|
+
* Removes the entire session directory.
|
|
1229
|
+
*/
|
|
1230
|
+
async cleanup() {
|
|
1231
|
+
if (this.initialized) {
|
|
1232
|
+
try {
|
|
1233
|
+
await (0, import_promises.rm)(this.outputDir, { recursive: true, force: true });
|
|
1234
|
+
} catch {
|
|
1235
|
+
}
|
|
1236
|
+
this.initialized = false;
|
|
1237
|
+
}
|
|
1238
|
+
this.clear();
|
|
1239
|
+
}
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
});
|
|
1243
|
+
|
|
1001
1244
|
// src/gadgets/exceptions.ts
|
|
1002
|
-
var
|
|
1245
|
+
var TaskCompletionSignal, HumanInputRequiredException, TimeoutException, AbortException;
|
|
1003
1246
|
var init_exceptions = __esm({
|
|
1004
1247
|
"src/gadgets/exceptions.ts"() {
|
|
1005
1248
|
"use strict";
|
|
1006
|
-
|
|
1249
|
+
TaskCompletionSignal = class extends Error {
|
|
1007
1250
|
constructor(message) {
|
|
1008
1251
|
super(message ?? "Agent loop terminated by gadget");
|
|
1009
|
-
this.name = "
|
|
1252
|
+
this.name = "TaskCompletionSignal";
|
|
1010
1253
|
}
|
|
1011
1254
|
};
|
|
1012
|
-
|
|
1255
|
+
HumanInputRequiredException = class extends Error {
|
|
1013
1256
|
question;
|
|
1014
1257
|
constructor(question) {
|
|
1015
1258
|
super(`Human input required: ${question}`);
|
|
1016
|
-
this.name = "
|
|
1259
|
+
this.name = "HumanInputRequiredException";
|
|
1017
1260
|
this.question = question;
|
|
1018
1261
|
}
|
|
1019
1262
|
};
|
|
@@ -1027,10 +1270,10 @@ var init_exceptions = __esm({
|
|
|
1027
1270
|
this.timeoutMs = timeoutMs;
|
|
1028
1271
|
}
|
|
1029
1272
|
};
|
|
1030
|
-
|
|
1273
|
+
AbortException = class extends Error {
|
|
1031
1274
|
constructor(message) {
|
|
1032
1275
|
super(message || "Gadget execution was aborted");
|
|
1033
|
-
this.name = "
|
|
1276
|
+
this.name = "AbortException";
|
|
1034
1277
|
}
|
|
1035
1278
|
};
|
|
1036
1279
|
}
|
|
@@ -1120,7 +1363,7 @@ var init_schema_to_json = __esm({
|
|
|
1120
1363
|
});
|
|
1121
1364
|
|
|
1122
1365
|
// src/gadgets/gadget.ts
|
|
1123
|
-
function
|
|
1366
|
+
function formatParamsForBlockExample(params, prefix = "", argPrefix = GADGET_ARG_PREFIX) {
|
|
1124
1367
|
const lines = [];
|
|
1125
1368
|
for (const [key, value] of Object.entries(params)) {
|
|
1126
1369
|
const fullPath = prefix ? `${prefix}/${key}` : key;
|
|
@@ -1128,14 +1371,14 @@ function formatParamsAsBlock(params, prefix = "", argPrefix = GADGET_ARG_PREFIX)
|
|
|
1128
1371
|
value.forEach((item, index) => {
|
|
1129
1372
|
const itemPath = `${fullPath}/${index}`;
|
|
1130
1373
|
if (typeof item === "object" && item !== null) {
|
|
1131
|
-
lines.push(
|
|
1374
|
+
lines.push(formatParamsForBlockExample(item, itemPath, argPrefix));
|
|
1132
1375
|
} else {
|
|
1133
1376
|
lines.push(`${argPrefix}${itemPath}`);
|
|
1134
1377
|
lines.push(String(item));
|
|
1135
1378
|
}
|
|
1136
1379
|
});
|
|
1137
1380
|
} else if (typeof value === "object" && value !== null) {
|
|
1138
|
-
lines.push(
|
|
1381
|
+
lines.push(formatParamsForBlockExample(value, fullPath, argPrefix));
|
|
1139
1382
|
} else {
|
|
1140
1383
|
lines.push(`${argPrefix}${fullPath}`);
|
|
1141
1384
|
lines.push(String(value));
|
|
@@ -1224,7 +1467,7 @@ function formatSchemaAsPlainText(schema, indent = "", atRoot = true) {
|
|
|
1224
1467
|
}
|
|
1225
1468
|
return lines.join("\n");
|
|
1226
1469
|
}
|
|
1227
|
-
var
|
|
1470
|
+
var AbstractGadget;
|
|
1228
1471
|
var init_gadget = __esm({
|
|
1229
1472
|
"src/gadgets/gadget.ts"() {
|
|
1230
1473
|
"use strict";
|
|
@@ -1232,7 +1475,7 @@ var init_gadget = __esm({
|
|
|
1232
1475
|
init_exceptions();
|
|
1233
1476
|
init_schema_to_json();
|
|
1234
1477
|
init_schema_validator();
|
|
1235
|
-
|
|
1478
|
+
AbstractGadget = class {
|
|
1236
1479
|
/**
|
|
1237
1480
|
* The name of the gadget. Used for identification when LLM calls it.
|
|
1238
1481
|
* If not provided, defaults to the class name.
|
|
@@ -1260,14 +1503,14 @@ var init_gadget = __esm({
|
|
|
1260
1503
|
*/
|
|
1261
1504
|
examples;
|
|
1262
1505
|
/**
|
|
1263
|
-
* Throws an
|
|
1506
|
+
* Throws an AbortException if the execution has been aborted.
|
|
1264
1507
|
*
|
|
1265
1508
|
* Call this at key checkpoints in long-running gadgets to allow early exit
|
|
1266
1509
|
* when the gadget has been cancelled (e.g., due to timeout). This enables
|
|
1267
1510
|
* resource cleanup and prevents unnecessary work after cancellation.
|
|
1268
1511
|
*
|
|
1269
1512
|
* @param ctx - The execution context containing the abort signal
|
|
1270
|
-
* @throws
|
|
1513
|
+
* @throws AbortException if ctx.signal.aborted is true
|
|
1271
1514
|
*
|
|
1272
1515
|
* @example
|
|
1273
1516
|
* ```typescript
|
|
@@ -1292,7 +1535,7 @@ var init_gadget = __esm({
|
|
|
1292
1535
|
*/
|
|
1293
1536
|
throwIfAborted(ctx) {
|
|
1294
1537
|
if (ctx?.signal?.aborted) {
|
|
1295
|
-
throw new
|
|
1538
|
+
throw new AbortException();
|
|
1296
1539
|
}
|
|
1297
1540
|
}
|
|
1298
1541
|
/**
|
|
@@ -1433,7 +1676,9 @@ var init_gadget = __esm({
|
|
|
1433
1676
|
parts.push(`# ${example.comment}`);
|
|
1434
1677
|
}
|
|
1435
1678
|
parts.push(`${effectiveStartPrefix}${gadgetName}`);
|
|
1436
|
-
parts.push(
|
|
1679
|
+
parts.push(
|
|
1680
|
+
formatParamsForBlockExample(example.params, "", effectiveArgPrefix)
|
|
1681
|
+
);
|
|
1437
1682
|
parts.push(effectiveEndPrefix);
|
|
1438
1683
|
if (example.output !== void 0) {
|
|
1439
1684
|
parts.push("");
|
|
@@ -1450,7 +1695,7 @@ var init_gadget = __esm({
|
|
|
1450
1695
|
|
|
1451
1696
|
// src/gadgets/create-gadget.ts
|
|
1452
1697
|
function createGadget(config) {
|
|
1453
|
-
class DynamicGadget extends
|
|
1698
|
+
class DynamicGadget extends AbstractGadget {
|
|
1454
1699
|
name = config.name;
|
|
1455
1700
|
description = config.description;
|
|
1456
1701
|
parameterSchema = config.schema;
|
|
@@ -1635,6 +1880,18 @@ var init_output_viewer = __esm({
|
|
|
1635
1880
|
}
|
|
1636
1881
|
});
|
|
1637
1882
|
|
|
1883
|
+
// src/agent/agent-internal-key.ts
|
|
1884
|
+
function isValidAgentKey(key) {
|
|
1885
|
+
return key === AGENT_INTERNAL_KEY;
|
|
1886
|
+
}
|
|
1887
|
+
var AGENT_INTERNAL_KEY;
|
|
1888
|
+
var init_agent_internal_key = __esm({
|
|
1889
|
+
"src/agent/agent-internal-key.ts"() {
|
|
1890
|
+
"use strict";
|
|
1891
|
+
AGENT_INTERNAL_KEY = Symbol("AGENT_INTERNAL_KEY");
|
|
1892
|
+
}
|
|
1893
|
+
});
|
|
1894
|
+
|
|
1638
1895
|
// src/agent/compaction/config.ts
|
|
1639
1896
|
function resolveCompactionConfig(config = {}) {
|
|
1640
1897
|
const trigger = config.triggerThresholdPercent ?? DEFAULT_COMPACTION_CONFIG.triggerThresholdPercent;
|
|
@@ -1882,9 +2139,9 @@ var init_hybrid = __esm({
|
|
|
1882
2139
|
var init_strategies = __esm({
|
|
1883
2140
|
"src/agent/compaction/strategies/index.ts"() {
|
|
1884
2141
|
"use strict";
|
|
2142
|
+
init_hybrid();
|
|
1885
2143
|
init_sliding_window();
|
|
1886
2144
|
init_summarization();
|
|
1887
|
-
init_hybrid();
|
|
1888
2145
|
}
|
|
1889
2146
|
});
|
|
1890
2147
|
|
|
@@ -2046,98 +2303,6 @@ var init_manager = __esm({
|
|
|
2046
2303
|
}
|
|
2047
2304
|
});
|
|
2048
2305
|
|
|
2049
|
-
// src/agent/gadget-output-store.ts
|
|
2050
|
-
var import_node_crypto, GadgetOutputStore;
|
|
2051
|
-
var init_gadget_output_store = __esm({
|
|
2052
|
-
"src/agent/gadget-output-store.ts"() {
|
|
2053
|
-
"use strict";
|
|
2054
|
-
import_node_crypto = require("crypto");
|
|
2055
|
-
GadgetOutputStore = class {
|
|
2056
|
-
outputs = /* @__PURE__ */ new Map();
|
|
2057
|
-
/**
|
|
2058
|
-
* Store a gadget output and return its ID.
|
|
2059
|
-
*
|
|
2060
|
-
* @param gadgetName - Name of the gadget that produced the output
|
|
2061
|
-
* @param content - Full output content to store
|
|
2062
|
-
* @returns Generated ID for retrieving the output later
|
|
2063
|
-
*/
|
|
2064
|
-
store(gadgetName, content) {
|
|
2065
|
-
const id = this.generateId(gadgetName);
|
|
2066
|
-
const encoder = new TextEncoder();
|
|
2067
|
-
const stored = {
|
|
2068
|
-
id,
|
|
2069
|
-
gadgetName,
|
|
2070
|
-
content,
|
|
2071
|
-
byteSize: encoder.encode(content).length,
|
|
2072
|
-
lineCount: content.split("\n").length,
|
|
2073
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
2074
|
-
};
|
|
2075
|
-
this.outputs.set(id, stored);
|
|
2076
|
-
return id;
|
|
2077
|
-
}
|
|
2078
|
-
/**
|
|
2079
|
-
* Retrieve a stored output by ID.
|
|
2080
|
-
*
|
|
2081
|
-
* @param id - The output ID (e.g., "Search_d34db33f")
|
|
2082
|
-
* @returns The stored output or undefined if not found
|
|
2083
|
-
*/
|
|
2084
|
-
get(id) {
|
|
2085
|
-
return this.outputs.get(id);
|
|
2086
|
-
}
|
|
2087
|
-
/**
|
|
2088
|
-
* Check if an output exists.
|
|
2089
|
-
*
|
|
2090
|
-
* @param id - The output ID to check
|
|
2091
|
-
* @returns True if the output exists
|
|
2092
|
-
*/
|
|
2093
|
-
has(id) {
|
|
2094
|
-
return this.outputs.has(id);
|
|
2095
|
-
}
|
|
2096
|
-
/**
|
|
2097
|
-
* Get all stored output IDs.
|
|
2098
|
-
*
|
|
2099
|
-
* @returns Array of output IDs
|
|
2100
|
-
*/
|
|
2101
|
-
getIds() {
|
|
2102
|
-
return Array.from(this.outputs.keys());
|
|
2103
|
-
}
|
|
2104
|
-
/**
|
|
2105
|
-
* Get the number of stored outputs.
|
|
2106
|
-
*/
|
|
2107
|
-
get size() {
|
|
2108
|
-
return this.outputs.size;
|
|
2109
|
-
}
|
|
2110
|
-
/**
|
|
2111
|
-
* Clear all stored outputs.
|
|
2112
|
-
* Called when the agent run completes.
|
|
2113
|
-
*/
|
|
2114
|
-
clear() {
|
|
2115
|
-
this.outputs.clear();
|
|
2116
|
-
}
|
|
2117
|
-
/**
|
|
2118
|
-
* Generate a unique ID for a stored output.
|
|
2119
|
-
* Format: {GadgetName}_{8 hex chars}
|
|
2120
|
-
*/
|
|
2121
|
-
generateId(gadgetName) {
|
|
2122
|
-
const hex = (0, import_node_crypto.randomBytes)(4).toString("hex");
|
|
2123
|
-
return `${gadgetName}_${hex}`;
|
|
2124
|
-
}
|
|
2125
|
-
};
|
|
2126
|
-
}
|
|
2127
|
-
});
|
|
2128
|
-
|
|
2129
|
-
// src/agent/agent-internal-key.ts
|
|
2130
|
-
function isValidAgentKey(key) {
|
|
2131
|
-
return key === AGENT_INTERNAL_KEY;
|
|
2132
|
-
}
|
|
2133
|
-
var AGENT_INTERNAL_KEY;
|
|
2134
|
-
var init_agent_internal_key = __esm({
|
|
2135
|
-
"src/agent/agent-internal-key.ts"() {
|
|
2136
|
-
"use strict";
|
|
2137
|
-
AGENT_INTERNAL_KEY = Symbol("AGENT_INTERNAL_KEY");
|
|
2138
|
-
}
|
|
2139
|
-
});
|
|
2140
|
-
|
|
2141
2306
|
// src/agent/conversation-manager.ts
|
|
2142
2307
|
var ConversationManager;
|
|
2143
2308
|
var init_conversation_manager = __esm({
|
|
@@ -2168,8 +2333,8 @@ var init_conversation_manager = __esm({
|
|
|
2168
2333
|
addAssistantMessage(content) {
|
|
2169
2334
|
this.historyBuilder.addAssistant(content);
|
|
2170
2335
|
}
|
|
2171
|
-
|
|
2172
|
-
this.historyBuilder.
|
|
2336
|
+
addGadgetCallResult(gadgetName, parameters, result, media, mediaIds) {
|
|
2337
|
+
this.historyBuilder.addGadgetCallResult(gadgetName, parameters, result, media, mediaIds);
|
|
2173
2338
|
}
|
|
2174
2339
|
getMessages() {
|
|
2175
2340
|
return [...this.baseMessages, ...this.initialMessages, ...this.historyBuilder.build()];
|
|
@@ -2189,7 +2354,7 @@ var init_conversation_manager = __esm({
|
|
|
2189
2354
|
if (msg.role === "user") {
|
|
2190
2355
|
this.historyBuilder.addUser(msg.content);
|
|
2191
2356
|
} else if (msg.role === "assistant") {
|
|
2192
|
-
this.historyBuilder.addAssistant(
|
|
2357
|
+
this.historyBuilder.addAssistant(extractMessageText(msg.content));
|
|
2193
2358
|
}
|
|
2194
2359
|
}
|
|
2195
2360
|
}
|
|
@@ -2253,59 +2418,139 @@ var init_event_handlers = __esm({
|
|
|
2253
2418
|
}
|
|
2254
2419
|
});
|
|
2255
2420
|
|
|
2256
|
-
// src/agent/
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2421
|
+
// src/agent/gadget-output-store.ts
|
|
2422
|
+
var import_node_crypto2, GadgetOutputStore;
|
|
2423
|
+
var init_gadget_output_store = __esm({
|
|
2424
|
+
"src/agent/gadget-output-store.ts"() {
|
|
2425
|
+
"use strict";
|
|
2426
|
+
import_node_crypto2 = require("crypto");
|
|
2427
|
+
GadgetOutputStore = class {
|
|
2428
|
+
outputs = /* @__PURE__ */ new Map();
|
|
2429
|
+
/**
|
|
2430
|
+
* Store a gadget output and return its ID.
|
|
2431
|
+
*
|
|
2432
|
+
* @param gadgetName - Name of the gadget that produced the output
|
|
2433
|
+
* @param content - Full output content to store
|
|
2434
|
+
* @returns Generated ID for retrieving the output later
|
|
2435
|
+
*/
|
|
2436
|
+
store(gadgetName, content) {
|
|
2437
|
+
const id = this.generateId(gadgetName);
|
|
2438
|
+
const encoder = new TextEncoder();
|
|
2439
|
+
const stored = {
|
|
2440
|
+
id,
|
|
2441
|
+
gadgetName,
|
|
2442
|
+
content,
|
|
2443
|
+
byteSize: encoder.encode(content).length,
|
|
2444
|
+
lineCount: content.split("\n").length,
|
|
2445
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
2446
|
+
};
|
|
2447
|
+
this.outputs.set(id, stored);
|
|
2448
|
+
return id;
|
|
2449
|
+
}
|
|
2450
|
+
/**
|
|
2451
|
+
* Retrieve a stored output by ID.
|
|
2452
|
+
*
|
|
2453
|
+
* @param id - The output ID (e.g., "Search_d34db33f")
|
|
2454
|
+
* @returns The stored output or undefined if not found
|
|
2455
|
+
*/
|
|
2456
|
+
get(id) {
|
|
2457
|
+
return this.outputs.get(id);
|
|
2458
|
+
}
|
|
2459
|
+
/**
|
|
2460
|
+
* Check if an output exists.
|
|
2461
|
+
*
|
|
2462
|
+
* @param id - The output ID to check
|
|
2463
|
+
* @returns True if the output exists
|
|
2464
|
+
*/
|
|
2465
|
+
has(id) {
|
|
2466
|
+
return this.outputs.has(id);
|
|
2467
|
+
}
|
|
2468
|
+
/**
|
|
2469
|
+
* Get all stored output IDs.
|
|
2470
|
+
*
|
|
2471
|
+
* @returns Array of output IDs
|
|
2472
|
+
*/
|
|
2473
|
+
getIds() {
|
|
2474
|
+
return Array.from(this.outputs.keys());
|
|
2475
|
+
}
|
|
2476
|
+
/**
|
|
2477
|
+
* Get the number of stored outputs.
|
|
2478
|
+
*/
|
|
2479
|
+
get size() {
|
|
2480
|
+
return this.outputs.size;
|
|
2481
|
+
}
|
|
2482
|
+
/**
|
|
2483
|
+
* Clear all stored outputs.
|
|
2484
|
+
* Called when the agent run completes.
|
|
2485
|
+
*/
|
|
2486
|
+
clear() {
|
|
2487
|
+
this.outputs.clear();
|
|
2488
|
+
}
|
|
2489
|
+
/**
|
|
2490
|
+
* Generate a unique ID for a stored output.
|
|
2491
|
+
* Format: {GadgetName}_{8 hex chars}
|
|
2492
|
+
*/
|
|
2493
|
+
generateId(gadgetName) {
|
|
2494
|
+
const hex = (0, import_node_crypto2.randomBytes)(4).toString("hex");
|
|
2495
|
+
return `${gadgetName}_${hex}`;
|
|
2496
|
+
}
|
|
2497
|
+
};
|
|
2498
|
+
}
|
|
2499
|
+
});
|
|
2500
|
+
|
|
2501
|
+
// src/agent/hook-validators.ts
|
|
2502
|
+
function validateBeforeLLMCallAction(action) {
|
|
2503
|
+
if (!action || typeof action !== "object" || !("action" in action)) {
|
|
2504
|
+
throw new HookValidationError(
|
|
2505
|
+
"beforeLLMCall",
|
|
2506
|
+
"Must return an action object with an 'action' field"
|
|
2507
|
+
);
|
|
2508
|
+
}
|
|
2509
|
+
const actionType = action.action;
|
|
2510
|
+
if (actionType !== "proceed" && actionType !== "skip") {
|
|
2511
|
+
throw new HookValidationError(
|
|
2512
|
+
"beforeLLMCall",
|
|
2513
|
+
`Invalid action type: ${actionType}. Must be 'proceed' or 'skip'`
|
|
2514
|
+
);
|
|
2515
|
+
}
|
|
2516
|
+
if (actionType === "skip" && !action.syntheticResponse) {
|
|
2517
|
+
throw new HookValidationError(
|
|
2518
|
+
"beforeLLMCall",
|
|
2519
|
+
"When action is 'skip', syntheticResponse is required"
|
|
2520
|
+
);
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
function validateAfterLLMCallAction(action) {
|
|
2524
|
+
if (!action || typeof action !== "object" || !("action" in action)) {
|
|
2525
|
+
throw new HookValidationError(
|
|
2526
|
+
"afterLLMCall",
|
|
2527
|
+
"Must return an action object with an 'action' field"
|
|
2528
|
+
);
|
|
2529
|
+
}
|
|
2530
|
+
const actionType = action.action;
|
|
2531
|
+
const validActions = ["continue", "append_messages", "modify_and_continue", "append_and_modify"];
|
|
2532
|
+
if (!validActions.includes(actionType)) {
|
|
2533
|
+
throw new HookValidationError(
|
|
2534
|
+
"afterLLMCall",
|
|
2535
|
+
`Invalid action type: ${actionType}. Must be one of: ${validActions.join(", ")}`
|
|
2536
|
+
);
|
|
2537
|
+
}
|
|
2538
|
+
if (actionType === "append_messages" || actionType === "append_and_modify") {
|
|
2539
|
+
if (!("messages" in action) || !action.messages || !Array.isArray(action.messages)) {
|
|
2540
|
+
throw new HookValidationError(
|
|
2541
|
+
"afterLLMCall",
|
|
2542
|
+
`When action is '${actionType}', messages array is required`
|
|
2543
|
+
);
|
|
2544
|
+
}
|
|
2545
|
+
if (action.messages.length === 0) {
|
|
2546
|
+
throw new HookValidationError(
|
|
2547
|
+
"afterLLMCall",
|
|
2548
|
+
`When action is '${actionType}', messages array must not be empty`
|
|
2549
|
+
);
|
|
2550
|
+
}
|
|
2551
|
+
for (let i = 0; i < action.messages.length; i++) {
|
|
2552
|
+
const msg = action.messages[i];
|
|
2553
|
+
if (!msg || typeof msg !== "object") {
|
|
2309
2554
|
throw new HookValidationError("afterLLMCall", `Message at index ${i} must be an object`);
|
|
2310
2555
|
}
|
|
2311
2556
|
if (!msg.role || !msg.content) {
|
|
@@ -2563,8 +2808,7 @@ var init_schema_introspector = __esm({
|
|
|
2563
2808
|
const values = def?.values;
|
|
2564
2809
|
const value = values?.[0] ?? def?.value;
|
|
2565
2810
|
if (typeof value === "string") return "string";
|
|
2566
|
-
if (typeof value === "number" || typeof value === "bigint")
|
|
2567
|
-
return "number";
|
|
2811
|
+
if (typeof value === "number" || typeof value === "bigint") return "number";
|
|
2568
2812
|
if (typeof value === "boolean") return "boolean";
|
|
2569
2813
|
return "unknown";
|
|
2570
2814
|
}
|
|
@@ -2836,7 +3080,13 @@ var init_cost_reporting_client = __esm({
|
|
|
2836
3080
|
cacheCreationInputTokens = chunk.usage.cacheCreationInputTokens ?? 0;
|
|
2837
3081
|
}
|
|
2838
3082
|
}
|
|
2839
|
-
this.reportCostFromUsage(
|
|
3083
|
+
this.reportCostFromUsage(
|
|
3084
|
+
model,
|
|
3085
|
+
inputTokens,
|
|
3086
|
+
outputTokens,
|
|
3087
|
+
cachedInputTokens,
|
|
3088
|
+
cacheCreationInputTokens
|
|
3089
|
+
);
|
|
2840
3090
|
return result;
|
|
2841
3091
|
}
|
|
2842
3092
|
/**
|
|
@@ -2876,7 +3126,13 @@ var init_cost_reporting_client = __esm({
|
|
|
2876
3126
|
}
|
|
2877
3127
|
}
|
|
2878
3128
|
} finally {
|
|
2879
|
-
this.reportCostFromUsage(
|
|
3129
|
+
this.reportCostFromUsage(
|
|
3130
|
+
model,
|
|
3131
|
+
inputTokens,
|
|
3132
|
+
outputTokens,
|
|
3133
|
+
cachedInputTokens,
|
|
3134
|
+
cacheCreationInputTokens
|
|
3135
|
+
);
|
|
2880
3136
|
}
|
|
2881
3137
|
}
|
|
2882
3138
|
/**
|
|
@@ -2914,7 +3170,13 @@ var init_cost_reporting_client = __esm({
|
|
|
2914
3170
|
}
|
|
2915
3171
|
} finally {
|
|
2916
3172
|
if (inputTokens > 0 || outputTokens > 0) {
|
|
2917
|
-
reportCostFromUsage(
|
|
3173
|
+
reportCostFromUsage(
|
|
3174
|
+
model,
|
|
3175
|
+
inputTokens,
|
|
3176
|
+
outputTokens,
|
|
3177
|
+
cachedInputTokens,
|
|
3178
|
+
cacheCreationInputTokens
|
|
3179
|
+
);
|
|
2918
3180
|
}
|
|
2919
3181
|
}
|
|
2920
3182
|
}
|
|
@@ -2942,12 +3204,12 @@ var init_cost_reporting_client = __esm({
|
|
|
2942
3204
|
});
|
|
2943
3205
|
|
|
2944
3206
|
// src/gadgets/error-formatter.ts
|
|
2945
|
-
var
|
|
3207
|
+
var GadgetExecutionErrorFormatter;
|
|
2946
3208
|
var init_error_formatter = __esm({
|
|
2947
3209
|
"src/gadgets/error-formatter.ts"() {
|
|
2948
3210
|
"use strict";
|
|
2949
3211
|
init_constants();
|
|
2950
|
-
|
|
3212
|
+
GadgetExecutionErrorFormatter = class {
|
|
2951
3213
|
argPrefix;
|
|
2952
3214
|
startPrefix;
|
|
2953
3215
|
endPrefix;
|
|
@@ -3033,16 +3295,16 @@ function stripMarkdownFences(content) {
|
|
|
3033
3295
|
cleaned = cleaned.replace(closingFence, "");
|
|
3034
3296
|
return cleaned.trim();
|
|
3035
3297
|
}
|
|
3036
|
-
var globalInvocationCounter,
|
|
3298
|
+
var globalInvocationCounter, GadgetCallParser;
|
|
3037
3299
|
var init_parser = __esm({
|
|
3038
3300
|
"src/gadgets/parser.ts"() {
|
|
3039
3301
|
"use strict";
|
|
3040
3302
|
init_constants();
|
|
3041
3303
|
init_block_params();
|
|
3042
3304
|
globalInvocationCounter = 0;
|
|
3043
|
-
|
|
3305
|
+
GadgetCallParser = class {
|
|
3044
3306
|
buffer = "";
|
|
3045
|
-
|
|
3307
|
+
lastEmittedTextOffset = 0;
|
|
3046
3308
|
startPrefix;
|
|
3047
3309
|
endPrefix;
|
|
3048
3310
|
argPrefix;
|
|
@@ -3051,16 +3313,20 @@ var init_parser = __esm({
|
|
|
3051
3313
|
this.endPrefix = options.endPrefix ?? GADGET_END_PREFIX;
|
|
3052
3314
|
this.argPrefix = options.argPrefix ?? GADGET_ARG_PREFIX;
|
|
3053
3315
|
}
|
|
3054
|
-
|
|
3055
|
-
|
|
3316
|
+
/**
|
|
3317
|
+
* Extract and consume text up to the given index.
|
|
3318
|
+
* Returns undefined if no meaningful text to emit.
|
|
3319
|
+
*/
|
|
3320
|
+
extractTextSegment(index) {
|
|
3321
|
+
if (index <= this.lastEmittedTextOffset) {
|
|
3056
3322
|
return void 0;
|
|
3057
3323
|
}
|
|
3058
|
-
const segment = this.buffer.slice(this.
|
|
3059
|
-
this.
|
|
3324
|
+
const segment = this.buffer.slice(this.lastEmittedTextOffset, index);
|
|
3325
|
+
this.lastEmittedTextOffset = index;
|
|
3060
3326
|
return segment.trim().length > 0 ? segment : void 0;
|
|
3061
3327
|
}
|
|
3062
3328
|
/**
|
|
3063
|
-
* Parse gadget
|
|
3329
|
+
* Parse gadget invocation metadata from the header line.
|
|
3064
3330
|
*
|
|
3065
3331
|
* Supported formats:
|
|
3066
3332
|
* - `GadgetName` - Auto-generate ID, no dependencies
|
|
@@ -3069,24 +3335,24 @@ var init_parser = __esm({
|
|
|
3069
3335
|
*
|
|
3070
3336
|
* Dependencies must be comma-separated invocation IDs.
|
|
3071
3337
|
*/
|
|
3072
|
-
|
|
3073
|
-
const parts =
|
|
3338
|
+
parseInvocationMetadata(headerLine) {
|
|
3339
|
+
const parts = headerLine.split(":");
|
|
3074
3340
|
if (parts.length === 1) {
|
|
3075
3341
|
return {
|
|
3076
|
-
|
|
3342
|
+
gadgetName: parts[0],
|
|
3077
3343
|
invocationId: `gadget_${++globalInvocationCounter}`,
|
|
3078
3344
|
dependencies: []
|
|
3079
3345
|
};
|
|
3080
3346
|
} else if (parts.length === 2) {
|
|
3081
3347
|
return {
|
|
3082
|
-
|
|
3348
|
+
gadgetName: parts[0],
|
|
3083
3349
|
invocationId: parts[1].trim(),
|
|
3084
3350
|
dependencies: []
|
|
3085
3351
|
};
|
|
3086
3352
|
} else {
|
|
3087
3353
|
const deps = parts[2].split(",").map((d) => d.trim()).filter((d) => d.length > 0);
|
|
3088
3354
|
return {
|
|
3089
|
-
|
|
3355
|
+
gadgetName: parts[0],
|
|
3090
3356
|
invocationId: parts[1].trim(),
|
|
3091
3357
|
dependencies: deps
|
|
3092
3358
|
};
|
|
@@ -3118,15 +3384,15 @@ var init_parser = __esm({
|
|
|
3118
3384
|
while (true) {
|
|
3119
3385
|
const partStartIndex = this.buffer.indexOf(this.startPrefix, startIndex);
|
|
3120
3386
|
if (partStartIndex === -1) break;
|
|
3121
|
-
const textBefore = this.
|
|
3387
|
+
const textBefore = this.extractTextSegment(partStartIndex);
|
|
3122
3388
|
if (textBefore !== void 0) {
|
|
3123
3389
|
yield { type: "text", content: textBefore };
|
|
3124
3390
|
}
|
|
3125
3391
|
const metadataStartIndex = partStartIndex + this.startPrefix.length;
|
|
3126
3392
|
const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
|
|
3127
3393
|
if (metadataEndIndex === -1) break;
|
|
3128
|
-
const
|
|
3129
|
-
const {
|
|
3394
|
+
const headerLine = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
|
|
3395
|
+
const { gadgetName, invocationId, dependencies } = this.parseInvocationMetadata(headerLine);
|
|
3130
3396
|
const contentStartIndex = metadataEndIndex + 1;
|
|
3131
3397
|
let partEndIndex;
|
|
3132
3398
|
let endMarkerLength = 0;
|
|
@@ -3146,7 +3412,7 @@ var init_parser = __esm({
|
|
|
3146
3412
|
yield {
|
|
3147
3413
|
type: "gadget_call",
|
|
3148
3414
|
call: {
|
|
3149
|
-
gadgetName
|
|
3415
|
+
gadgetName,
|
|
3150
3416
|
invocationId,
|
|
3151
3417
|
parametersRaw,
|
|
3152
3418
|
parameters,
|
|
@@ -3155,33 +3421,33 @@ var init_parser = __esm({
|
|
|
3155
3421
|
}
|
|
3156
3422
|
};
|
|
3157
3423
|
startIndex = partEndIndex + endMarkerLength;
|
|
3158
|
-
this.
|
|
3424
|
+
this.lastEmittedTextOffset = startIndex;
|
|
3159
3425
|
}
|
|
3160
3426
|
if (startIndex > 0) {
|
|
3161
3427
|
this.buffer = this.buffer.substring(startIndex);
|
|
3162
|
-
this.
|
|
3428
|
+
this.lastEmittedTextOffset = 0;
|
|
3163
3429
|
}
|
|
3164
3430
|
}
|
|
3165
3431
|
// Finalize parsing and return remaining text or incomplete gadgets
|
|
3166
3432
|
*finalize() {
|
|
3167
|
-
const startIndex = this.buffer.indexOf(this.startPrefix, this.
|
|
3433
|
+
const startIndex = this.buffer.indexOf(this.startPrefix, this.lastEmittedTextOffset);
|
|
3168
3434
|
if (startIndex !== -1) {
|
|
3169
|
-
const textBefore = this.
|
|
3435
|
+
const textBefore = this.extractTextSegment(startIndex);
|
|
3170
3436
|
if (textBefore !== void 0) {
|
|
3171
3437
|
yield { type: "text", content: textBefore };
|
|
3172
3438
|
}
|
|
3173
3439
|
const metadataStartIndex = startIndex + this.startPrefix.length;
|
|
3174
3440
|
const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
|
|
3175
3441
|
if (metadataEndIndex !== -1) {
|
|
3176
|
-
const
|
|
3177
|
-
const {
|
|
3442
|
+
const headerLine = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
|
|
3443
|
+
const { gadgetName, invocationId, dependencies } = this.parseInvocationMetadata(headerLine);
|
|
3178
3444
|
const contentStartIndex = metadataEndIndex + 1;
|
|
3179
3445
|
const parametersRaw = this.buffer.substring(contentStartIndex).trim();
|
|
3180
3446
|
const { parameters, parseError } = this.parseParameters(parametersRaw);
|
|
3181
3447
|
yield {
|
|
3182
3448
|
type: "gadget_call",
|
|
3183
3449
|
call: {
|
|
3184
|
-
gadgetName
|
|
3450
|
+
gadgetName,
|
|
3185
3451
|
invocationId,
|
|
3186
3452
|
parametersRaw,
|
|
3187
3453
|
parameters,
|
|
@@ -3192,7 +3458,7 @@ var init_parser = __esm({
|
|
|
3192
3458
|
return;
|
|
3193
3459
|
}
|
|
3194
3460
|
}
|
|
3195
|
-
const remainingText = this.
|
|
3461
|
+
const remainingText = this.extractTextSegment(this.buffer.length);
|
|
3196
3462
|
if (remainingText !== void 0) {
|
|
3197
3463
|
yield { type: "text", content: remainingText };
|
|
3198
3464
|
}
|
|
@@ -3200,7 +3466,7 @@ var init_parser = __esm({
|
|
|
3200
3466
|
// Reset parser state (note: global invocation counter is NOT reset to ensure unique IDs)
|
|
3201
3467
|
reset() {
|
|
3202
3468
|
this.buffer = "";
|
|
3203
|
-
this.
|
|
3469
|
+
this.lastEmittedTextOffset = 0;
|
|
3204
3470
|
}
|
|
3205
3471
|
};
|
|
3206
3472
|
}
|
|
@@ -3219,13 +3485,16 @@ var init_executor = __esm({
|
|
|
3219
3485
|
init_exceptions();
|
|
3220
3486
|
init_parser();
|
|
3221
3487
|
GadgetExecutor = class {
|
|
3222
|
-
constructor(registry,
|
|
3488
|
+
constructor(registry, requestHumanInput, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client, mediaStore, agentConfig, subagentConfig) {
|
|
3223
3489
|
this.registry = registry;
|
|
3224
|
-
this.
|
|
3490
|
+
this.requestHumanInput = requestHumanInput;
|
|
3225
3491
|
this.defaultGadgetTimeoutMs = defaultGadgetTimeoutMs;
|
|
3226
3492
|
this.client = client;
|
|
3493
|
+
this.mediaStore = mediaStore;
|
|
3494
|
+
this.agentConfig = agentConfig;
|
|
3495
|
+
this.subagentConfig = subagentConfig;
|
|
3227
3496
|
this.logger = logger ?? createLogger({ name: "llmist:executor" });
|
|
3228
|
-
this.errorFormatter = new
|
|
3497
|
+
this.errorFormatter = new GadgetExecutionErrorFormatter(errorFormatterOptions);
|
|
3229
3498
|
this.argPrefix = errorFormatterOptions?.argPrefix ?? GADGET_ARG_PREFIX;
|
|
3230
3499
|
}
|
|
3231
3500
|
logger;
|
|
@@ -3245,13 +3514,17 @@ var init_executor = __esm({
|
|
|
3245
3514
|
});
|
|
3246
3515
|
}
|
|
3247
3516
|
/**
|
|
3248
|
-
*
|
|
3249
|
-
* Handles
|
|
3517
|
+
* Unify gadget execute result to consistent internal format.
|
|
3518
|
+
* Handles string returns (backwards compat), object returns with cost,
|
|
3519
|
+
* and object returns with media.
|
|
3250
3520
|
*/
|
|
3251
|
-
|
|
3521
|
+
unifyExecuteResult(raw) {
|
|
3252
3522
|
if (typeof raw === "string") {
|
|
3253
3523
|
return { result: raw, cost: 0 };
|
|
3254
3524
|
}
|
|
3525
|
+
if ("media" in raw && raw.media) {
|
|
3526
|
+
return { result: raw.result, media: raw.media, cost: raw.cost ?? 0 };
|
|
3527
|
+
}
|
|
3255
3528
|
return { result: raw.result, cost: raw.cost ?? 0 };
|
|
3256
3529
|
}
|
|
3257
3530
|
// Execute a gadget call asynchronously
|
|
@@ -3363,7 +3636,9 @@ var init_executor = __esm({
|
|
|
3363
3636
|
const ctx = {
|
|
3364
3637
|
reportCost,
|
|
3365
3638
|
llmist: this.client ? new CostReportingLLMistWrapper(this.client, reportCost) : void 0,
|
|
3366
|
-
signal: abortController.signal
|
|
3639
|
+
signal: abortController.signal,
|
|
3640
|
+
agentConfig: this.agentConfig,
|
|
3641
|
+
subagentConfig: this.subagentConfig
|
|
3367
3642
|
};
|
|
3368
3643
|
let rawResult;
|
|
3369
3644
|
if (timeoutMs && timeoutMs > 0) {
|
|
@@ -3378,8 +3653,21 @@ var init_executor = __esm({
|
|
|
3378
3653
|
} else {
|
|
3379
3654
|
rawResult = await Promise.resolve(gadget.execute(validatedParameters, ctx));
|
|
3380
3655
|
}
|
|
3381
|
-
const { result, cost: returnCost } = this.
|
|
3656
|
+
const { result, media, cost: returnCost } = this.unifyExecuteResult(rawResult);
|
|
3382
3657
|
const totalCost = callbackCost + returnCost;
|
|
3658
|
+
let mediaIds;
|
|
3659
|
+
let storedMedia;
|
|
3660
|
+
if (media && media.length > 0 && this.mediaStore) {
|
|
3661
|
+
storedMedia = await Promise.all(
|
|
3662
|
+
media.map((item) => this.mediaStore.store(item, call.gadgetName))
|
|
3663
|
+
);
|
|
3664
|
+
mediaIds = storedMedia.map((m) => m.id);
|
|
3665
|
+
this.logger.debug("Stored media outputs", {
|
|
3666
|
+
gadgetName: call.gadgetName,
|
|
3667
|
+
mediaIds,
|
|
3668
|
+
count: media.length
|
|
3669
|
+
});
|
|
3670
|
+
}
|
|
3383
3671
|
const executionTimeMs = Date.now() - startTime;
|
|
3384
3672
|
this.logger.info("Gadget executed successfully", {
|
|
3385
3673
|
gadgetName: call.gadgetName,
|
|
@@ -3387,7 +3675,8 @@ var init_executor = __esm({
|
|
|
3387
3675
|
executionTimeMs,
|
|
3388
3676
|
cost: totalCost > 0 ? totalCost : void 0,
|
|
3389
3677
|
callbackCost: callbackCost > 0 ? callbackCost : void 0,
|
|
3390
|
-
returnCost: returnCost > 0 ? returnCost : void 0
|
|
3678
|
+
returnCost: returnCost > 0 ? returnCost : void 0,
|
|
3679
|
+
mediaCount: media?.length
|
|
3391
3680
|
});
|
|
3392
3681
|
this.logger.debug("Gadget result", {
|
|
3393
3682
|
gadgetName: call.gadgetName,
|
|
@@ -3395,7 +3684,8 @@ var init_executor = __esm({
|
|
|
3395
3684
|
parameters: validatedParameters,
|
|
3396
3685
|
result,
|
|
3397
3686
|
cost: totalCost,
|
|
3398
|
-
executionTimeMs
|
|
3687
|
+
executionTimeMs,
|
|
3688
|
+
mediaIds
|
|
3399
3689
|
});
|
|
3400
3690
|
return {
|
|
3401
3691
|
gadgetName: call.gadgetName,
|
|
@@ -3403,10 +3693,13 @@ var init_executor = __esm({
|
|
|
3403
3693
|
parameters: validatedParameters,
|
|
3404
3694
|
result,
|
|
3405
3695
|
executionTimeMs,
|
|
3406
|
-
cost: totalCost
|
|
3696
|
+
cost: totalCost,
|
|
3697
|
+
media,
|
|
3698
|
+
mediaIds,
|
|
3699
|
+
storedMedia
|
|
3407
3700
|
};
|
|
3408
3701
|
} catch (error) {
|
|
3409
|
-
if (error instanceof
|
|
3702
|
+
if (error instanceof TaskCompletionSignal) {
|
|
3410
3703
|
this.logger.info("Gadget requested loop termination", {
|
|
3411
3704
|
gadgetName: call.gadgetName,
|
|
3412
3705
|
message: error.message
|
|
@@ -3434,7 +3727,7 @@ var init_executor = __esm({
|
|
|
3434
3727
|
executionTimeMs: Date.now() - startTime
|
|
3435
3728
|
};
|
|
3436
3729
|
}
|
|
3437
|
-
if (error instanceof
|
|
3730
|
+
if (error instanceof AbortException) {
|
|
3438
3731
|
this.logger.info("Gadget execution was aborted", {
|
|
3439
3732
|
gadgetName: call.gadgetName,
|
|
3440
3733
|
executionTimeMs: Date.now() - startTime
|
|
@@ -3447,14 +3740,14 @@ var init_executor = __esm({
|
|
|
3447
3740
|
executionTimeMs: Date.now() - startTime
|
|
3448
3741
|
};
|
|
3449
3742
|
}
|
|
3450
|
-
if (error instanceof
|
|
3743
|
+
if (error instanceof HumanInputRequiredException) {
|
|
3451
3744
|
this.logger.info("Gadget requested human input", {
|
|
3452
3745
|
gadgetName: call.gadgetName,
|
|
3453
3746
|
question: error.question
|
|
3454
3747
|
});
|
|
3455
|
-
if (this.
|
|
3748
|
+
if (this.requestHumanInput) {
|
|
3456
3749
|
try {
|
|
3457
|
-
const answer = await this.
|
|
3750
|
+
const answer = await this.requestHumanInput(error.question);
|
|
3458
3751
|
this.logger.debug("Human input received", {
|
|
3459
3752
|
gadgetName: call.gadgetName,
|
|
3460
3753
|
answerLength: answer.length
|
|
@@ -3552,13 +3845,13 @@ var init_stream_processor = __esm({
|
|
|
3552
3845
|
parser;
|
|
3553
3846
|
executor;
|
|
3554
3847
|
stopOnGadgetError;
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3848
|
+
canRecoverFromGadgetError;
|
|
3849
|
+
responseText = "";
|
|
3850
|
+
executionHalted = false;
|
|
3558
3851
|
observerFailureCount = 0;
|
|
3559
3852
|
// Dependency tracking for gadget execution DAG
|
|
3560
3853
|
/** Gadgets waiting for their dependencies to complete */
|
|
3561
|
-
|
|
3854
|
+
gadgetsAwaitingDependencies = /* @__PURE__ */ new Map();
|
|
3562
3855
|
/** Completed gadget results, keyed by invocation ID */
|
|
3563
3856
|
completedResults = /* @__PURE__ */ new Map();
|
|
3564
3857
|
/** Invocation IDs of gadgets that have failed (error or skipped due to dependency) */
|
|
@@ -3569,19 +3862,22 @@ var init_stream_processor = __esm({
|
|
|
3569
3862
|
this.hooks = options.hooks ?? {};
|
|
3570
3863
|
this.logger = options.logger ?? createLogger({ name: "llmist:stream-processor" });
|
|
3571
3864
|
this.stopOnGadgetError = options.stopOnGadgetError ?? true;
|
|
3572
|
-
this.
|
|
3573
|
-
this.parser = new
|
|
3865
|
+
this.canRecoverFromGadgetError = options.canRecoverFromGadgetError;
|
|
3866
|
+
this.parser = new GadgetCallParser({
|
|
3574
3867
|
startPrefix: options.gadgetStartPrefix,
|
|
3575
3868
|
endPrefix: options.gadgetEndPrefix,
|
|
3576
3869
|
argPrefix: options.gadgetArgPrefix
|
|
3577
3870
|
});
|
|
3578
3871
|
this.executor = new GadgetExecutor(
|
|
3579
3872
|
options.registry,
|
|
3580
|
-
options.
|
|
3873
|
+
options.requestHumanInput,
|
|
3581
3874
|
this.logger.getSubLogger({ name: "executor" }),
|
|
3582
3875
|
options.defaultGadgetTimeoutMs,
|
|
3583
3876
|
{ argPrefix: options.gadgetArgPrefix },
|
|
3584
|
-
options.client
|
|
3877
|
+
options.client,
|
|
3878
|
+
options.mediaStore,
|
|
3879
|
+
options.agentConfig,
|
|
3880
|
+
options.subagentConfig
|
|
3585
3881
|
);
|
|
3586
3882
|
}
|
|
3587
3883
|
/**
|
|
@@ -3602,7 +3898,7 @@ var init_stream_processor = __esm({
|
|
|
3602
3898
|
if (this.hooks.interceptors?.interceptRawChunk) {
|
|
3603
3899
|
const context = {
|
|
3604
3900
|
iteration: this.iteration,
|
|
3605
|
-
accumulatedText: this.
|
|
3901
|
+
accumulatedText: this.responseText,
|
|
3606
3902
|
logger: this.logger
|
|
3607
3903
|
};
|
|
3608
3904
|
const intercepted = this.hooks.interceptors.interceptRawChunk(processedChunk, context);
|
|
@@ -3613,7 +3909,7 @@ var init_stream_processor = __esm({
|
|
|
3613
3909
|
}
|
|
3614
3910
|
}
|
|
3615
3911
|
if (processedChunk) {
|
|
3616
|
-
this.
|
|
3912
|
+
this.responseText += processedChunk;
|
|
3617
3913
|
}
|
|
3618
3914
|
}
|
|
3619
3915
|
if (this.hooks.observers?.onStreamChunk && (processedChunk || chunk.usage)) {
|
|
@@ -3622,7 +3918,7 @@ var init_stream_processor = __esm({
|
|
|
3622
3918
|
const context = {
|
|
3623
3919
|
iteration: this.iteration,
|
|
3624
3920
|
rawChunk: processedChunk,
|
|
3625
|
-
accumulatedText: this.
|
|
3921
|
+
accumulatedText: this.responseText,
|
|
3626
3922
|
usage,
|
|
3627
3923
|
logger: this.logger
|
|
3628
3924
|
};
|
|
@@ -3645,12 +3941,12 @@ var init_stream_processor = __esm({
|
|
|
3645
3941
|
}
|
|
3646
3942
|
}
|
|
3647
3943
|
}
|
|
3648
|
-
if (this.
|
|
3944
|
+
if (this.executionHalted) {
|
|
3649
3945
|
this.logger.info("Breaking from LLM stream due to gadget error");
|
|
3650
3946
|
break;
|
|
3651
3947
|
}
|
|
3652
3948
|
}
|
|
3653
|
-
if (!this.
|
|
3949
|
+
if (!this.executionHalted) {
|
|
3654
3950
|
for (const event of this.parser.finalize()) {
|
|
3655
3951
|
const processedEvents = await this.processEvent(event);
|
|
3656
3952
|
outputs.push(...processedEvents);
|
|
@@ -3674,11 +3970,11 @@ var init_stream_processor = __esm({
|
|
|
3674
3970
|
}
|
|
3675
3971
|
}
|
|
3676
3972
|
}
|
|
3677
|
-
let finalMessage = this.
|
|
3973
|
+
let finalMessage = this.responseText;
|
|
3678
3974
|
if (this.hooks.interceptors?.interceptAssistantMessage) {
|
|
3679
3975
|
const context = {
|
|
3680
3976
|
iteration: this.iteration,
|
|
3681
|
-
rawResponse: this.
|
|
3977
|
+
rawResponse: this.responseText,
|
|
3682
3978
|
logger: this.logger
|
|
3683
3979
|
};
|
|
3684
3980
|
finalMessage = this.hooks.interceptors.interceptAssistantMessage(finalMessage, context);
|
|
@@ -3689,7 +3985,7 @@ var init_stream_processor = __esm({
|
|
|
3689
3985
|
didExecuteGadgets,
|
|
3690
3986
|
finishReason,
|
|
3691
3987
|
usage,
|
|
3692
|
-
rawResponse: this.
|
|
3988
|
+
rawResponse: this.responseText,
|
|
3693
3989
|
finalMessage
|
|
3694
3990
|
};
|
|
3695
3991
|
}
|
|
@@ -3712,7 +4008,7 @@ var init_stream_processor = __esm({
|
|
|
3712
4008
|
if (this.hooks.interceptors?.interceptTextChunk) {
|
|
3713
4009
|
const context = {
|
|
3714
4010
|
iteration: this.iteration,
|
|
3715
|
-
accumulatedText: this.
|
|
4011
|
+
accumulatedText: this.responseText,
|
|
3716
4012
|
logger: this.logger
|
|
3717
4013
|
};
|
|
3718
4014
|
const intercepted = this.hooks.interceptors.interceptTextChunk(content, context);
|
|
@@ -3731,7 +4027,7 @@ var init_stream_processor = __esm({
|
|
|
3731
4027
|
* After each execution, pending gadgets are checked to see if they can now run.
|
|
3732
4028
|
*/
|
|
3733
4029
|
async processGadgetCall(call) {
|
|
3734
|
-
if (this.
|
|
4030
|
+
if (this.executionHalted) {
|
|
3735
4031
|
this.logger.debug("Skipping gadget execution due to previous error", {
|
|
3736
4032
|
gadgetName: call.gadgetName
|
|
3737
4033
|
});
|
|
@@ -3770,7 +4066,7 @@ var init_stream_processor = __esm({
|
|
|
3770
4066
|
invocationId: call.invocationId,
|
|
3771
4067
|
waitingOn: unsatisfied
|
|
3772
4068
|
});
|
|
3773
|
-
this.
|
|
4069
|
+
this.gadgetsAwaitingDependencies.set(call.invocationId, call);
|
|
3774
4070
|
return events;
|
|
3775
4071
|
}
|
|
3776
4072
|
}
|
|
@@ -3792,14 +4088,14 @@ var init_stream_processor = __esm({
|
|
|
3792
4088
|
error: call.parseError,
|
|
3793
4089
|
rawParameters: call.parametersRaw
|
|
3794
4090
|
});
|
|
3795
|
-
const shouldContinue = await this.
|
|
4091
|
+
const shouldContinue = await this.checkCanRecoverFromError(
|
|
3796
4092
|
call.parseError,
|
|
3797
4093
|
call.gadgetName,
|
|
3798
4094
|
"parse",
|
|
3799
4095
|
call.parameters
|
|
3800
4096
|
);
|
|
3801
4097
|
if (!shouldContinue) {
|
|
3802
|
-
this.
|
|
4098
|
+
this.executionHalted = true;
|
|
3803
4099
|
}
|
|
3804
4100
|
}
|
|
3805
4101
|
let parameters = call.parameters ?? {};
|
|
@@ -3923,14 +4219,14 @@ var init_stream_processor = __esm({
|
|
|
3923
4219
|
events.push({ type: "gadget_result", result });
|
|
3924
4220
|
if (result.error) {
|
|
3925
4221
|
const errorType = this.determineErrorType(call, result);
|
|
3926
|
-
const shouldContinue = await this.
|
|
4222
|
+
const shouldContinue = await this.checkCanRecoverFromError(
|
|
3927
4223
|
result.error,
|
|
3928
4224
|
result.gadgetName,
|
|
3929
4225
|
errorType,
|
|
3930
4226
|
result.parameters
|
|
3931
4227
|
);
|
|
3932
4228
|
if (!shouldContinue) {
|
|
3933
|
-
this.
|
|
4229
|
+
this.executionHalted = true;
|
|
3934
4230
|
}
|
|
3935
4231
|
}
|
|
3936
4232
|
return events;
|
|
@@ -4017,11 +4313,11 @@ var init_stream_processor = __esm({
|
|
|
4017
4313
|
async processPendingGadgets() {
|
|
4018
4314
|
const events = [];
|
|
4019
4315
|
let progress = true;
|
|
4020
|
-
while (progress && this.
|
|
4316
|
+
while (progress && this.gadgetsAwaitingDependencies.size > 0) {
|
|
4021
4317
|
progress = false;
|
|
4022
4318
|
const readyToExecute = [];
|
|
4023
4319
|
const readyToSkip = [];
|
|
4024
|
-
for (const [invocationId, call] of this.
|
|
4320
|
+
for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
|
|
4025
4321
|
const failedDep = call.dependencies.find((dep) => this.failedInvocations.has(dep));
|
|
4026
4322
|
if (failedDep) {
|
|
4027
4323
|
readyToSkip.push({ call, failedDep });
|
|
@@ -4033,7 +4329,7 @@ var init_stream_processor = __esm({
|
|
|
4033
4329
|
}
|
|
4034
4330
|
}
|
|
4035
4331
|
for (const { call, failedDep } of readyToSkip) {
|
|
4036
|
-
this.
|
|
4332
|
+
this.gadgetsAwaitingDependencies.delete(call.invocationId);
|
|
4037
4333
|
const skipEvents = await this.handleFailedDependency(call, failedDep);
|
|
4038
4334
|
events.push(...skipEvents);
|
|
4039
4335
|
progress = true;
|
|
@@ -4044,7 +4340,7 @@ var init_stream_processor = __esm({
|
|
|
4044
4340
|
invocationIds: readyToExecute.map((c) => c.invocationId)
|
|
4045
4341
|
});
|
|
4046
4342
|
for (const call of readyToExecute) {
|
|
4047
|
-
this.
|
|
4343
|
+
this.gadgetsAwaitingDependencies.delete(call.invocationId);
|
|
4048
4344
|
}
|
|
4049
4345
|
const executePromises = readyToExecute.map((call) => this.executeGadgetWithHooks(call));
|
|
4050
4346
|
const results = await Promise.all(executePromises);
|
|
@@ -4054,9 +4350,9 @@ var init_stream_processor = __esm({
|
|
|
4054
4350
|
progress = true;
|
|
4055
4351
|
}
|
|
4056
4352
|
}
|
|
4057
|
-
if (this.
|
|
4058
|
-
const pendingIds = new Set(this.
|
|
4059
|
-
for (const [invocationId, call] of this.
|
|
4353
|
+
if (this.gadgetsAwaitingDependencies.size > 0) {
|
|
4354
|
+
const pendingIds = new Set(this.gadgetsAwaitingDependencies.keys());
|
|
4355
|
+
for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
|
|
4060
4356
|
const missingDeps = call.dependencies.filter((dep) => !this.completedResults.has(dep));
|
|
4061
4357
|
const circularDeps = missingDeps.filter((dep) => pendingIds.has(dep));
|
|
4062
4358
|
const trulyMissingDeps = missingDeps.filter((dep) => !pendingIds.has(dep));
|
|
@@ -4087,7 +4383,7 @@ var init_stream_processor = __esm({
|
|
|
4087
4383
|
};
|
|
4088
4384
|
events.push(skipEvent);
|
|
4089
4385
|
}
|
|
4090
|
-
this.
|
|
4386
|
+
this.gadgetsAwaitingDependencies.clear();
|
|
4091
4387
|
}
|
|
4092
4388
|
return events;
|
|
4093
4389
|
}
|
|
@@ -4117,19 +4413,19 @@ var init_stream_processor = __esm({
|
|
|
4117
4413
|
);
|
|
4118
4414
|
}
|
|
4119
4415
|
/**
|
|
4120
|
-
* Check if execution
|
|
4416
|
+
* Check if execution can recover from an error.
|
|
4121
4417
|
*
|
|
4122
4418
|
* Returns true if we should continue processing subsequent gadgets, false if we should stop.
|
|
4123
4419
|
*
|
|
4124
4420
|
* Logic:
|
|
4125
|
-
* - If custom
|
|
4421
|
+
* - If custom canRecoverFromGadgetError is provided, use it
|
|
4126
4422
|
* - Otherwise, use stopOnGadgetError config:
|
|
4127
4423
|
* - stopOnGadgetError=true → return false (stop execution)
|
|
4128
4424
|
* - stopOnGadgetError=false → return true (continue execution)
|
|
4129
4425
|
*/
|
|
4130
|
-
async
|
|
4131
|
-
if (this.
|
|
4132
|
-
return await this.
|
|
4426
|
+
async checkCanRecoverFromError(error, gadgetName, errorType, parameters) {
|
|
4427
|
+
if (this.canRecoverFromGadgetError) {
|
|
4428
|
+
return await this.canRecoverFromGadgetError({
|
|
4133
4429
|
error,
|
|
4134
4430
|
gadgetName,
|
|
4135
4431
|
errorType,
|
|
@@ -4170,13 +4466,14 @@ var init_agent = __esm({
|
|
|
4170
4466
|
init_constants();
|
|
4171
4467
|
init_messages();
|
|
4172
4468
|
init_model_shortcuts();
|
|
4469
|
+
init_media_store();
|
|
4173
4470
|
init_output_viewer();
|
|
4174
4471
|
init_logger();
|
|
4175
|
-
init_manager();
|
|
4176
|
-
init_gadget_output_store();
|
|
4177
4472
|
init_agent_internal_key();
|
|
4473
|
+
init_manager();
|
|
4178
4474
|
init_conversation_manager();
|
|
4179
4475
|
init_event_handlers();
|
|
4476
|
+
init_gadget_output_store();
|
|
4180
4477
|
init_hook_validators();
|
|
4181
4478
|
init_stream_processor();
|
|
4182
4479
|
Agent = class {
|
|
@@ -4191,22 +4488,27 @@ var init_agent = __esm({
|
|
|
4191
4488
|
gadgetStartPrefix;
|
|
4192
4489
|
gadgetEndPrefix;
|
|
4193
4490
|
gadgetArgPrefix;
|
|
4194
|
-
|
|
4491
|
+
requestHumanInput;
|
|
4195
4492
|
textOnlyHandler;
|
|
4196
4493
|
textWithGadgetsHandler;
|
|
4197
4494
|
stopOnGadgetError;
|
|
4198
|
-
|
|
4495
|
+
canRecoverFromGadgetError;
|
|
4199
4496
|
defaultGadgetTimeoutMs;
|
|
4200
4497
|
defaultMaxTokens;
|
|
4201
|
-
|
|
4498
|
+
hasUserPrompt;
|
|
4202
4499
|
// Gadget output limiting
|
|
4203
4500
|
outputStore;
|
|
4204
4501
|
outputLimitEnabled;
|
|
4205
4502
|
outputLimitCharLimit;
|
|
4206
4503
|
// Context compaction
|
|
4207
4504
|
compactionManager;
|
|
4505
|
+
// Media storage (for gadgets returning images, audio, etc.)
|
|
4506
|
+
mediaStore;
|
|
4208
4507
|
// Cancellation
|
|
4209
4508
|
signal;
|
|
4509
|
+
// Subagent configuration
|
|
4510
|
+
agentContextConfig;
|
|
4511
|
+
subagentConfig;
|
|
4210
4512
|
/**
|
|
4211
4513
|
* Creates a new Agent instance.
|
|
4212
4514
|
* @internal This constructor is private. Use LLMist.createAgent() or AgentBuilder instead.
|
|
@@ -4226,15 +4528,16 @@ var init_agent = __esm({
|
|
|
4226
4528
|
this.gadgetStartPrefix = options.gadgetStartPrefix;
|
|
4227
4529
|
this.gadgetEndPrefix = options.gadgetEndPrefix;
|
|
4228
4530
|
this.gadgetArgPrefix = options.gadgetArgPrefix;
|
|
4229
|
-
this.
|
|
4531
|
+
this.requestHumanInput = options.requestHumanInput;
|
|
4230
4532
|
this.textOnlyHandler = options.textOnlyHandler ?? "terminate";
|
|
4231
4533
|
this.textWithGadgetsHandler = options.textWithGadgetsHandler;
|
|
4232
4534
|
this.stopOnGadgetError = options.stopOnGadgetError ?? true;
|
|
4233
|
-
this.
|
|
4535
|
+
this.canRecoverFromGadgetError = options.canRecoverFromGadgetError;
|
|
4234
4536
|
this.defaultGadgetTimeoutMs = options.defaultGadgetTimeoutMs;
|
|
4235
4537
|
this.defaultMaxTokens = this.resolveMaxTokensFromCatalog(options.model);
|
|
4236
4538
|
this.outputLimitEnabled = options.gadgetOutputLimit ?? DEFAULT_GADGET_OUTPUT_LIMIT;
|
|
4237
4539
|
this.outputStore = new GadgetOutputStore();
|
|
4540
|
+
this.mediaStore = new MediaStore();
|
|
4238
4541
|
const limitPercent = options.gadgetOutputLimitPercent ?? DEFAULT_GADGET_OUTPUT_LIMIT_PERCENT;
|
|
4239
4542
|
const limits = this.client.modelRegistry.getModelLimits(this.model);
|
|
4240
4543
|
const contextWindow = limits?.contextWindow ?? FALLBACK_CONTEXT_WINDOW;
|
|
@@ -4245,7 +4548,7 @@ var init_agent = __esm({
|
|
|
4245
4548
|
createGadgetOutputViewer(this.outputStore, this.outputLimitCharLimit)
|
|
4246
4549
|
);
|
|
4247
4550
|
}
|
|
4248
|
-
this.hooks = this.
|
|
4551
|
+
this.hooks = this.chainOutputLimiterWithUserHooks(options.hooks);
|
|
4249
4552
|
const baseBuilder = new LLMMessageBuilder(options.promptConfig);
|
|
4250
4553
|
if (options.systemPrompt) {
|
|
4251
4554
|
baseBuilder.addSystem(options.systemPrompt);
|
|
@@ -4265,7 +4568,7 @@ var init_agent = __esm({
|
|
|
4265
4568
|
endPrefix: options.gadgetEndPrefix,
|
|
4266
4569
|
argPrefix: options.gadgetArgPrefix
|
|
4267
4570
|
});
|
|
4268
|
-
this.
|
|
4571
|
+
this.hasUserPrompt = !!options.userPrompt;
|
|
4269
4572
|
if (options.userPrompt) {
|
|
4270
4573
|
this.conversation.addUserMessage(options.userPrompt);
|
|
4271
4574
|
}
|
|
@@ -4278,6 +4581,11 @@ var init_agent = __esm({
|
|
|
4278
4581
|
);
|
|
4279
4582
|
}
|
|
4280
4583
|
this.signal = options.signal;
|
|
4584
|
+
this.agentContextConfig = {
|
|
4585
|
+
model: this.model,
|
|
4586
|
+
temperature: this.temperature
|
|
4587
|
+
};
|
|
4588
|
+
this.subagentConfig = options.subagentConfig;
|
|
4281
4589
|
}
|
|
4282
4590
|
/**
|
|
4283
4591
|
* Get the gadget registry for this agent.
|
|
@@ -4300,6 +4608,36 @@ var init_agent = __esm({
|
|
|
4300
4608
|
getRegistry() {
|
|
4301
4609
|
return this.registry;
|
|
4302
4610
|
}
|
|
4611
|
+
/**
|
|
4612
|
+
* Get the media store for this agent session.
|
|
4613
|
+
*
|
|
4614
|
+
* The media store holds all media outputs (images, audio, etc.) produced by gadgets
|
|
4615
|
+
* during this agent's execution. Use this to:
|
|
4616
|
+
* - Access stored media files by ID
|
|
4617
|
+
* - List all stored media
|
|
4618
|
+
* - Clean up temporary files after execution
|
|
4619
|
+
*
|
|
4620
|
+
* @returns The MediaStore instance for this agent
|
|
4621
|
+
*
|
|
4622
|
+
* @example
|
|
4623
|
+
* ```typescript
|
|
4624
|
+
* const agent = new AgentBuilder()
|
|
4625
|
+
* .withModel("sonnet")
|
|
4626
|
+
* .build();
|
|
4627
|
+
*
|
|
4628
|
+
* // After execution, access stored media
|
|
4629
|
+
* const store = agent.getMediaStore();
|
|
4630
|
+
* for (const media of store.list()) {
|
|
4631
|
+
* console.log(`${media.id}: ${media.path}`);
|
|
4632
|
+
* }
|
|
4633
|
+
*
|
|
4634
|
+
* // Clean up when done
|
|
4635
|
+
* await store.cleanup();
|
|
4636
|
+
* ```
|
|
4637
|
+
*/
|
|
4638
|
+
getMediaStore() {
|
|
4639
|
+
return this.mediaStore;
|
|
4640
|
+
}
|
|
4303
4641
|
/**
|
|
4304
4642
|
* Manually trigger context compaction.
|
|
4305
4643
|
*
|
|
@@ -4354,7 +4692,7 @@ var init_agent = __esm({
|
|
|
4354
4692
|
* @throws {Error} If no user prompt was provided (when using build() without ask())
|
|
4355
4693
|
*/
|
|
4356
4694
|
async *run() {
|
|
4357
|
-
if (!this.
|
|
4695
|
+
if (!this.hasUserPrompt) {
|
|
4358
4696
|
throw new Error(
|
|
4359
4697
|
"No user prompt provided. Use .ask(prompt) instead of .build(), or call agent.run() after providing a prompt."
|
|
4360
4698
|
);
|
|
@@ -4471,11 +4809,14 @@ var init_agent = __esm({
|
|
|
4471
4809
|
gadgetArgPrefix: this.gadgetArgPrefix,
|
|
4472
4810
|
hooks: this.hooks,
|
|
4473
4811
|
logger: this.logger.getSubLogger({ name: "stream-processor" }),
|
|
4474
|
-
|
|
4812
|
+
requestHumanInput: this.requestHumanInput,
|
|
4475
4813
|
stopOnGadgetError: this.stopOnGadgetError,
|
|
4476
|
-
|
|
4814
|
+
canRecoverFromGadgetError: this.canRecoverFromGadgetError,
|
|
4477
4815
|
defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
|
|
4478
|
-
client: this.client
|
|
4816
|
+
client: this.client,
|
|
4817
|
+
mediaStore: this.mediaStore,
|
|
4818
|
+
agentConfig: this.agentContextConfig,
|
|
4819
|
+
subagentConfig: this.subagentConfig
|
|
4479
4820
|
});
|
|
4480
4821
|
const result = await processor.process(stream2);
|
|
4481
4822
|
for (const output of result.outputs) {
|
|
@@ -4528,19 +4869,21 @@ var init_agent = __esm({
|
|
|
4528
4869
|
if (msg.role === "user") {
|
|
4529
4870
|
this.conversation.addUserMessage(msg.content);
|
|
4530
4871
|
} else if (msg.role === "assistant") {
|
|
4531
|
-
this.conversation.addAssistantMessage(
|
|
4872
|
+
this.conversation.addAssistantMessage(extractMessageText(msg.content));
|
|
4532
4873
|
} else if (msg.role === "system") {
|
|
4533
|
-
this.conversation.addUserMessage(`[System] ${
|
|
4874
|
+
this.conversation.addUserMessage(`[System] ${extractMessageText(msg.content)}`);
|
|
4534
4875
|
}
|
|
4535
4876
|
}
|
|
4536
4877
|
}
|
|
4537
4878
|
}
|
|
4538
4879
|
if (result.didExecuteGadgets) {
|
|
4539
4880
|
if (this.textWithGadgetsHandler) {
|
|
4540
|
-
const textContent = result.outputs.filter(
|
|
4881
|
+
const textContent = result.outputs.filter(
|
|
4882
|
+
(output) => output.type === "text"
|
|
4883
|
+
).map((output) => output.content).join("");
|
|
4541
4884
|
if (textContent.trim()) {
|
|
4542
4885
|
const { gadgetName, parameterMapping, resultMapping } = this.textWithGadgetsHandler;
|
|
4543
|
-
this.conversation.
|
|
4886
|
+
this.conversation.addGadgetCallResult(
|
|
4544
4887
|
gadgetName,
|
|
4545
4888
|
parameterMapping(textContent),
|
|
4546
4889
|
resultMapping ? resultMapping(textContent) : textContent
|
|
@@ -4550,16 +4893,18 @@ var init_agent = __esm({
|
|
|
4550
4893
|
for (const output of result.outputs) {
|
|
4551
4894
|
if (output.type === "gadget_result") {
|
|
4552
4895
|
const gadgetResult = output.result;
|
|
4553
|
-
this.conversation.
|
|
4896
|
+
this.conversation.addGadgetCallResult(
|
|
4554
4897
|
gadgetResult.gadgetName,
|
|
4555
4898
|
gadgetResult.parameters,
|
|
4556
|
-
gadgetResult.error ?? gadgetResult.result ?? ""
|
|
4899
|
+
gadgetResult.error ?? gadgetResult.result ?? "",
|
|
4900
|
+
gadgetResult.media,
|
|
4901
|
+
gadgetResult.mediaIds
|
|
4557
4902
|
);
|
|
4558
4903
|
}
|
|
4559
4904
|
}
|
|
4560
4905
|
} else {
|
|
4561
4906
|
if (finalMessage.trim()) {
|
|
4562
|
-
this.conversation.
|
|
4907
|
+
this.conversation.addGadgetCallResult(
|
|
4563
4908
|
"TellUser",
|
|
4564
4909
|
{ message: finalMessage, done: false, type: "info" },
|
|
4565
4910
|
`\u2139\uFE0F ${finalMessage}`
|
|
@@ -4685,10 +5030,10 @@ var init_agent = __esm({
|
|
|
4685
5030
|
return this.client.modelRegistry.getModelLimits(unprefixedModelId)?.maxOutputTokens;
|
|
4686
5031
|
}
|
|
4687
5032
|
/**
|
|
4688
|
-
*
|
|
5033
|
+
* Chain the output limiter interceptor with user-provided hooks.
|
|
4689
5034
|
* The limiter runs first, then chains to any user interceptor.
|
|
4690
5035
|
*/
|
|
4691
|
-
|
|
5036
|
+
chainOutputLimiterWithUserHooks(userHooks) {
|
|
4692
5037
|
if (!this.outputLimitEnabled) {
|
|
4693
5038
|
return userHooks ?? {};
|
|
4694
5039
|
}
|
|
@@ -4767,20 +5112,21 @@ var init_builder = __esm({
|
|
|
4767
5112
|
promptConfig;
|
|
4768
5113
|
gadgets = [];
|
|
4769
5114
|
initialMessages = [];
|
|
4770
|
-
|
|
5115
|
+
requestHumanInput;
|
|
4771
5116
|
gadgetStartPrefix;
|
|
4772
5117
|
gadgetEndPrefix;
|
|
4773
5118
|
gadgetArgPrefix;
|
|
4774
5119
|
textOnlyHandler;
|
|
4775
5120
|
textWithGadgetsHandler;
|
|
4776
5121
|
stopOnGadgetError;
|
|
4777
|
-
|
|
5122
|
+
canRecoverFromGadgetError;
|
|
4778
5123
|
defaultGadgetTimeoutMs;
|
|
4779
5124
|
gadgetOutputLimit;
|
|
4780
5125
|
gadgetOutputLimitPercent;
|
|
4781
5126
|
compactionConfig;
|
|
4782
5127
|
signal;
|
|
4783
5128
|
trailingMessage;
|
|
5129
|
+
subagentConfig;
|
|
4784
5130
|
constructor(client) {
|
|
4785
5131
|
this.client = client;
|
|
4786
5132
|
}
|
|
@@ -4871,13 +5217,13 @@ var init_builder = __esm({
|
|
|
4871
5217
|
*
|
|
4872
5218
|
* @example
|
|
4873
5219
|
* ```typescript
|
|
4874
|
-
* .
|
|
5220
|
+
* .withPromptTemplateConfig({
|
|
4875
5221
|
* mainInstruction: "Use the gadget markers below:",
|
|
4876
5222
|
* rules: ["Always use markers", "Never use function calling"]
|
|
4877
5223
|
* })
|
|
4878
5224
|
* ```
|
|
4879
5225
|
*/
|
|
4880
|
-
|
|
5226
|
+
withPromptTemplateConfig(config) {
|
|
4881
5227
|
this.promptConfig = config;
|
|
4882
5228
|
return this;
|
|
4883
5229
|
}
|
|
@@ -4957,7 +5303,7 @@ var init_builder = __esm({
|
|
|
4957
5303
|
* ```
|
|
4958
5304
|
*/
|
|
4959
5305
|
onHumanInput(handler) {
|
|
4960
|
-
this.
|
|
5306
|
+
this.requestHumanInput = handler;
|
|
4961
5307
|
return this;
|
|
4962
5308
|
}
|
|
4963
5309
|
/**
|
|
@@ -5092,9 +5438,9 @@ var init_builder = __esm({
|
|
|
5092
5438
|
* Provides fine-grained control over whether to continue after different types of errors.
|
|
5093
5439
|
* Overrides `stopOnGadgetError` when provided.
|
|
5094
5440
|
*
|
|
5095
|
-
* **Note:** This builder method configures the underlying `
|
|
5441
|
+
* **Note:** This builder method configures the underlying `canRecoverFromGadgetError` option
|
|
5096
5442
|
* in `AgentOptions`. The method is named `withErrorHandler` for better developer experience,
|
|
5097
|
-
* but maps to the `
|
|
5443
|
+
* but maps to the `canRecoverFromGadgetError` property internally.
|
|
5098
5444
|
*
|
|
5099
5445
|
* @param handler - Function that decides whether to continue after an error.
|
|
5100
5446
|
* Return `true` to continue execution, `false` to stop.
|
|
@@ -5115,7 +5461,7 @@ var init_builder = __esm({
|
|
|
5115
5461
|
* ```
|
|
5116
5462
|
*/
|
|
5117
5463
|
withErrorHandler(handler) {
|
|
5118
|
-
this.
|
|
5464
|
+
this.canRecoverFromGadgetError = handler;
|
|
5119
5465
|
return this;
|
|
5120
5466
|
}
|
|
5121
5467
|
/**
|
|
@@ -5256,6 +5602,27 @@ var init_builder = __esm({
|
|
|
5256
5602
|
this.signal = signal;
|
|
5257
5603
|
return this;
|
|
5258
5604
|
}
|
|
5605
|
+
/**
|
|
5606
|
+
* Set subagent configuration overrides.
|
|
5607
|
+
*
|
|
5608
|
+
* Subagent gadgets (like BrowseWeb) can read these settings from ExecutionContext
|
|
5609
|
+
* to inherit model and other options from the CLI configuration.
|
|
5610
|
+
*
|
|
5611
|
+
* @param config - Subagent configuration map keyed by gadget name
|
|
5612
|
+
* @returns This builder for chaining
|
|
5613
|
+
*
|
|
5614
|
+
* @example
|
|
5615
|
+
* ```typescript
|
|
5616
|
+
* .withSubagentConfig({
|
|
5617
|
+
* BrowseWeb: { model: "inherit", maxIterations: 20, headless: true },
|
|
5618
|
+
* CodeAnalyzer: { model: "sonnet", maxIterations: 10 }
|
|
5619
|
+
* })
|
|
5620
|
+
* ```
|
|
5621
|
+
*/
|
|
5622
|
+
withSubagentConfig(config) {
|
|
5623
|
+
this.subagentConfig = config;
|
|
5624
|
+
return this;
|
|
5625
|
+
}
|
|
5259
5626
|
/**
|
|
5260
5627
|
* Add an ephemeral trailing message that appears at the end of each LLM request.
|
|
5261
5628
|
*
|
|
@@ -5420,19 +5787,20 @@ ${endPrefix}`
|
|
|
5420
5787
|
hooks: this.composeHooks(),
|
|
5421
5788
|
promptConfig: this.promptConfig,
|
|
5422
5789
|
initialMessages: this.initialMessages,
|
|
5423
|
-
|
|
5790
|
+
requestHumanInput: this.requestHumanInput,
|
|
5424
5791
|
gadgetStartPrefix: this.gadgetStartPrefix,
|
|
5425
5792
|
gadgetEndPrefix: this.gadgetEndPrefix,
|
|
5426
5793
|
gadgetArgPrefix: this.gadgetArgPrefix,
|
|
5427
5794
|
textOnlyHandler: this.textOnlyHandler,
|
|
5428
5795
|
textWithGadgetsHandler: this.textWithGadgetsHandler,
|
|
5429
5796
|
stopOnGadgetError: this.stopOnGadgetError,
|
|
5430
|
-
|
|
5797
|
+
canRecoverFromGadgetError: this.canRecoverFromGadgetError,
|
|
5431
5798
|
defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
|
|
5432
5799
|
gadgetOutputLimit: this.gadgetOutputLimit,
|
|
5433
5800
|
gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
|
|
5434
5801
|
compactionConfig: this.compactionConfig,
|
|
5435
|
-
signal: this.signal
|
|
5802
|
+
signal: this.signal,
|
|
5803
|
+
subagentConfig: this.subagentConfig
|
|
5436
5804
|
};
|
|
5437
5805
|
}
|
|
5438
5806
|
ask(userPrompt) {
|
|
@@ -5601,19 +5969,20 @@ ${endPrefix}`
|
|
|
5601
5969
|
hooks: this.composeHooks(),
|
|
5602
5970
|
promptConfig: this.promptConfig,
|
|
5603
5971
|
initialMessages: this.initialMessages,
|
|
5604
|
-
|
|
5972
|
+
requestHumanInput: this.requestHumanInput,
|
|
5605
5973
|
gadgetStartPrefix: this.gadgetStartPrefix,
|
|
5606
5974
|
gadgetEndPrefix: this.gadgetEndPrefix,
|
|
5607
5975
|
gadgetArgPrefix: this.gadgetArgPrefix,
|
|
5608
5976
|
textOnlyHandler: this.textOnlyHandler,
|
|
5609
5977
|
textWithGadgetsHandler: this.textWithGadgetsHandler,
|
|
5610
5978
|
stopOnGadgetError: this.stopOnGadgetError,
|
|
5611
|
-
|
|
5979
|
+
canRecoverFromGadgetError: this.canRecoverFromGadgetError,
|
|
5612
5980
|
defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
|
|
5613
5981
|
gadgetOutputLimit: this.gadgetOutputLimit,
|
|
5614
5982
|
gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
|
|
5615
5983
|
compactionConfig: this.compactionConfig,
|
|
5616
|
-
signal: this.signal
|
|
5984
|
+
signal: this.signal,
|
|
5985
|
+
subagentConfig: this.subagentConfig
|
|
5617
5986
|
};
|
|
5618
5987
|
return new Agent(AGENT_INTERNAL_KEY, options);
|
|
5619
5988
|
}
|
|
@@ -5919,9 +6288,9 @@ var init_base_provider = __esm({
|
|
|
5919
6288
|
*/
|
|
5920
6289
|
async *stream(options, descriptor, spec) {
|
|
5921
6290
|
const preparedMessages = this.prepareMessages(options.messages);
|
|
5922
|
-
const payload = this.
|
|
6291
|
+
const payload = this.buildApiRequest(options, descriptor, spec, preparedMessages);
|
|
5923
6292
|
const rawStream = await this.executeStreamRequest(payload, options.signal);
|
|
5924
|
-
yield* this.
|
|
6293
|
+
yield* this.normalizeProviderStream(rawStream);
|
|
5925
6294
|
}
|
|
5926
6295
|
/**
|
|
5927
6296
|
* Prepare messages for the request.
|
|
@@ -6021,11 +6390,11 @@ var init_anthropic = __esm({
|
|
|
6021
6390
|
"Anthropic does not support speech generation. Use OpenAI (TTS) or Google Gemini (TTS) instead."
|
|
6022
6391
|
);
|
|
6023
6392
|
}
|
|
6024
|
-
|
|
6393
|
+
buildApiRequest(options, descriptor, spec, messages) {
|
|
6025
6394
|
const systemMessages = messages.filter((message) => message.role === "system");
|
|
6026
6395
|
const system = systemMessages.length > 0 ? systemMessages.map((m, index) => ({
|
|
6027
6396
|
type: "text",
|
|
6028
|
-
text:
|
|
6397
|
+
text: extractMessageText(m.content),
|
|
6029
6398
|
// Add cache_control to the LAST system message block
|
|
6030
6399
|
...index === systemMessages.length - 1 ? { cache_control: { type: "ephemeral" } } : {}
|
|
6031
6400
|
})) : void 0;
|
|
@@ -6062,7 +6431,7 @@ var init_anthropic = __esm({
|
|
|
6062
6431
|
* Handles text, images (base64 only), and applies cache_control.
|
|
6063
6432
|
*/
|
|
6064
6433
|
convertToAnthropicContent(content, addCacheControl) {
|
|
6065
|
-
const parts =
|
|
6434
|
+
const parts = normalizeMessageContent(content);
|
|
6066
6435
|
return parts.map((part, index) => {
|
|
6067
6436
|
const isLastPart = index === parts.length - 1;
|
|
6068
6437
|
const cacheControl = addCacheControl && isLastPart ? { cache_control: { type: "ephemeral" } } : {};
|
|
@@ -6108,7 +6477,7 @@ var init_anthropic = __esm({
|
|
|
6108
6477
|
const stream2 = await client.messages.create(payload, signal ? { signal } : void 0);
|
|
6109
6478
|
return stream2;
|
|
6110
6479
|
}
|
|
6111
|
-
async *
|
|
6480
|
+
async *normalizeProviderStream(iterable) {
|
|
6112
6481
|
const stream2 = iterable;
|
|
6113
6482
|
let inputTokens = 0;
|
|
6114
6483
|
let cachedInputTokens = 0;
|
|
@@ -6185,7 +6554,7 @@ var init_anthropic = __esm({
|
|
|
6185
6554
|
async countTokens(messages, descriptor, _spec) {
|
|
6186
6555
|
const client = this.client;
|
|
6187
6556
|
const systemMessages = messages.filter((message) => message.role === "system");
|
|
6188
|
-
const system = systemMessages.length > 0 ? systemMessages.map((m) =>
|
|
6557
|
+
const system = systemMessages.length > 0 ? systemMessages.map((m) => extractMessageText(m.content)).join("\n\n") : void 0;
|
|
6189
6558
|
const conversation = messages.filter(
|
|
6190
6559
|
(message) => message.role !== "system"
|
|
6191
6560
|
).map((message) => ({
|
|
@@ -6207,7 +6576,7 @@ var init_anthropic = __esm({
|
|
|
6207
6576
|
let totalChars = 0;
|
|
6208
6577
|
let imageCount = 0;
|
|
6209
6578
|
for (const msg of messages) {
|
|
6210
|
-
const parts =
|
|
6579
|
+
const parts = normalizeMessageContent(msg.content);
|
|
6211
6580
|
for (const part of parts) {
|
|
6212
6581
|
if (part.type === "text") {
|
|
6213
6582
|
totalChars += part.text.length;
|
|
@@ -6898,7 +7267,7 @@ var init_gemini = __esm({
|
|
|
6898
7267
|
format: spec?.defaultFormat ?? "wav"
|
|
6899
7268
|
};
|
|
6900
7269
|
}
|
|
6901
|
-
|
|
7270
|
+
buildApiRequest(options, descriptor, _spec, messages) {
|
|
6902
7271
|
const contents = this.convertMessagesToContents(messages);
|
|
6903
7272
|
const generationConfig = this.buildGenerationConfig(options);
|
|
6904
7273
|
const config = {
|
|
@@ -6950,7 +7319,7 @@ var init_gemini = __esm({
|
|
|
6950
7319
|
if (message.role === "system") {
|
|
6951
7320
|
expandedMessages.push({
|
|
6952
7321
|
role: "user",
|
|
6953
|
-
content:
|
|
7322
|
+
content: extractMessageText(message.content)
|
|
6954
7323
|
});
|
|
6955
7324
|
expandedMessages.push({
|
|
6956
7325
|
role: "assistant",
|
|
@@ -7000,7 +7369,7 @@ var init_gemini = __esm({
|
|
|
7000
7369
|
* Handles text, images, and audio (Gemini supports all three).
|
|
7001
7370
|
*/
|
|
7002
7371
|
convertToGeminiParts(content) {
|
|
7003
|
-
const parts =
|
|
7372
|
+
const parts = normalizeMessageContent(content);
|
|
7004
7373
|
return parts.map((part) => {
|
|
7005
7374
|
if (part.type === "text") {
|
|
7006
7375
|
return { text: part.text };
|
|
@@ -7045,10 +7414,10 @@ var init_gemini = __esm({
|
|
|
7045
7414
|
}
|
|
7046
7415
|
return Object.keys(config).length > 0 ? config : null;
|
|
7047
7416
|
}
|
|
7048
|
-
async *
|
|
7417
|
+
async *normalizeProviderStream(iterable) {
|
|
7049
7418
|
const stream2 = iterable;
|
|
7050
7419
|
for await (const chunk of stream2) {
|
|
7051
|
-
const text3 = this.
|
|
7420
|
+
const text3 = this.extractMessageText(chunk);
|
|
7052
7421
|
if (text3) {
|
|
7053
7422
|
yield { text: text3, rawEvent: chunk };
|
|
7054
7423
|
}
|
|
@@ -7059,7 +7428,7 @@ var init_gemini = __esm({
|
|
|
7059
7428
|
}
|
|
7060
7429
|
}
|
|
7061
7430
|
}
|
|
7062
|
-
|
|
7431
|
+
extractMessageText(chunk) {
|
|
7063
7432
|
if (!chunk?.candidates) {
|
|
7064
7433
|
return "";
|
|
7065
7434
|
}
|
|
@@ -7124,7 +7493,7 @@ var init_gemini = __esm({
|
|
|
7124
7493
|
let totalChars = 0;
|
|
7125
7494
|
let mediaCount = 0;
|
|
7126
7495
|
for (const msg of messages) {
|
|
7127
|
-
const parts =
|
|
7496
|
+
const parts = normalizeMessageContent(msg.content);
|
|
7128
7497
|
for (const part of parts) {
|
|
7129
7498
|
if (part.type === "text") {
|
|
7130
7499
|
totalChars += part.text.length;
|
|
@@ -7645,14 +8014,7 @@ var OPENAI_TTS_VOICES, OPENAI_TTS_EXTENDED_VOICES, OPENAI_TTS_FORMATS, openaiSpe
|
|
|
7645
8014
|
var init_openai_speech_models = __esm({
|
|
7646
8015
|
"src/providers/openai-speech-models.ts"() {
|
|
7647
8016
|
"use strict";
|
|
7648
|
-
OPENAI_TTS_VOICES = [
|
|
7649
|
-
"alloy",
|
|
7650
|
-
"echo",
|
|
7651
|
-
"fable",
|
|
7652
|
-
"onyx",
|
|
7653
|
-
"nova",
|
|
7654
|
-
"shimmer"
|
|
7655
|
-
];
|
|
8017
|
+
OPENAI_TTS_VOICES = ["alloy", "echo", "fable", "onyx", "nova", "shimmer"];
|
|
7656
8018
|
OPENAI_TTS_EXTENDED_VOICES = [
|
|
7657
8019
|
...OPENAI_TTS_VOICES,
|
|
7658
8020
|
"ash",
|
|
@@ -7877,7 +8239,7 @@ var init_openai = __esm({
|
|
|
7877
8239
|
format
|
|
7878
8240
|
};
|
|
7879
8241
|
}
|
|
7880
|
-
|
|
8242
|
+
buildApiRequest(options, descriptor, spec, messages) {
|
|
7881
8243
|
const { maxTokens, temperature, topP, stopSequences, extra } = options;
|
|
7882
8244
|
const supportsTemperature = spec?.metadata?.supportsTemperature !== false;
|
|
7883
8245
|
const shouldIncludeTemperature = typeof temperature === "number" && supportsTemperature;
|
|
@@ -7912,7 +8274,7 @@ var init_openai = __esm({
|
|
|
7912
8274
|
...message.name ? { name: message.name } : {}
|
|
7913
8275
|
};
|
|
7914
8276
|
}
|
|
7915
|
-
const textContent = typeof message.content === "string" ? message.content :
|
|
8277
|
+
const textContent = typeof message.content === "string" ? message.content : extractMessageText(message.content);
|
|
7916
8278
|
if (role === "system") {
|
|
7917
8279
|
return {
|
|
7918
8280
|
role: "system",
|
|
@@ -7972,7 +8334,7 @@ var init_openai = __esm({
|
|
|
7972
8334
|
const stream2 = await client.chat.completions.create(payload, signal ? { signal } : void 0);
|
|
7973
8335
|
return stream2;
|
|
7974
8336
|
}
|
|
7975
|
-
async *
|
|
8337
|
+
async *normalizeProviderStream(iterable) {
|
|
7976
8338
|
const stream2 = iterable;
|
|
7977
8339
|
for await (const chunk of stream2) {
|
|
7978
8340
|
const text3 = chunk.choices.map((choice) => choice.delta?.content ?? "").join("");
|
|
@@ -8030,9 +8392,9 @@ var init_openai = __esm({
|
|
|
8030
8392
|
tokenCount += OPENAI_MESSAGE_OVERHEAD_TOKENS;
|
|
8031
8393
|
const roleText = ROLE_MAP[message.role];
|
|
8032
8394
|
tokenCount += encoding.encode(roleText).length;
|
|
8033
|
-
const textContent =
|
|
8395
|
+
const textContent = extractMessageText(message.content);
|
|
8034
8396
|
tokenCount += encoding.encode(textContent).length;
|
|
8035
|
-
const parts =
|
|
8397
|
+
const parts = normalizeMessageContent(message.content);
|
|
8036
8398
|
for (const part of parts) {
|
|
8037
8399
|
if (part.type === "image") {
|
|
8038
8400
|
imageCount++;
|
|
@@ -8057,7 +8419,7 @@ var init_openai = __esm({
|
|
|
8057
8419
|
let totalChars = 0;
|
|
8058
8420
|
let imageCount = 0;
|
|
8059
8421
|
for (const msg of messages) {
|
|
8060
|
-
const parts =
|
|
8422
|
+
const parts = normalizeMessageContent(msg.content);
|
|
8061
8423
|
for (const part of parts) {
|
|
8062
8424
|
if (part.type === "text") {
|
|
8063
8425
|
totalChars += part.text.length;
|
|
@@ -8350,9 +8712,7 @@ var init_image = __esm({
|
|
|
8350
8712
|
return this.findImageAdapter(modelId) !== void 0;
|
|
8351
8713
|
}
|
|
8352
8714
|
findImageAdapter(modelId) {
|
|
8353
|
-
return this.adapters.find(
|
|
8354
|
-
(adapter) => adapter.supportsImageGeneration?.(modelId) ?? false
|
|
8355
|
-
);
|
|
8715
|
+
return this.adapters.find((adapter) => adapter.supportsImageGeneration?.(modelId) ?? false);
|
|
8356
8716
|
}
|
|
8357
8717
|
};
|
|
8358
8718
|
}
|
|
@@ -8404,9 +8764,7 @@ var init_speech = __esm({
|
|
|
8404
8764
|
return this.findSpeechAdapter(modelId) !== void 0;
|
|
8405
8765
|
}
|
|
8406
8766
|
findSpeechAdapter(modelId) {
|
|
8407
|
-
return this.adapters.find(
|
|
8408
|
-
(adapter) => adapter.supportsSpeechGeneration?.(modelId) ?? false
|
|
8409
|
-
);
|
|
8767
|
+
return this.adapters.find((adapter) => adapter.supportsSpeechGeneration?.(modelId) ?? false);
|
|
8410
8768
|
}
|
|
8411
8769
|
};
|
|
8412
8770
|
}
|
|
@@ -8517,11 +8875,7 @@ var init_vision = __esm({
|
|
|
8517
8875
|
if (!parsed) {
|
|
8518
8876
|
throw new Error("Invalid data URL format");
|
|
8519
8877
|
}
|
|
8520
|
-
builder.addUserWithImage(
|
|
8521
|
-
options.prompt,
|
|
8522
|
-
parsed.data,
|
|
8523
|
-
parsed.mimeType
|
|
8524
|
-
);
|
|
8878
|
+
builder.addUserWithImage(options.prompt, parsed.data, parsed.mimeType);
|
|
8525
8879
|
} else {
|
|
8526
8880
|
const buffer = Buffer.from(options.image, "base64");
|
|
8527
8881
|
builder.addUserWithImage(options.prompt, buffer, options.mimeType);
|
|
@@ -8952,46 +9306,271 @@ __export(testing_exports, {
|
|
|
8952
9306
|
});
|
|
8953
9307
|
module.exports = __toCommonJS(testing_exports);
|
|
8954
9308
|
|
|
8955
|
-
// src/
|
|
8956
|
-
|
|
8957
|
-
|
|
8958
|
-
|
|
8959
|
-
|
|
8960
|
-
|
|
8961
|
-
|
|
8962
|
-
};
|
|
8963
|
-
}
|
|
8964
|
-
const issues = result.error.issues.map((issue) => ({
|
|
8965
|
-
path: issue.path.join(".") || "root",
|
|
8966
|
-
message: issue.message
|
|
8967
|
-
}));
|
|
8968
|
-
const formattedError = `Invalid parameters: ${issues.map((i) => `${i.path}: ${i.message}`).join("; ")}`;
|
|
9309
|
+
// src/testing/cli-helpers.ts
|
|
9310
|
+
var import_node_stream = require("stream");
|
|
9311
|
+
function createTestEnvironment(options = {}) {
|
|
9312
|
+
const stdin = createMockReadable(options.stdin);
|
|
9313
|
+
const stdout = new import_node_stream.PassThrough();
|
|
9314
|
+
const stderr = new import_node_stream.PassThrough();
|
|
9315
|
+
let exitCode;
|
|
8969
9316
|
return {
|
|
8970
|
-
|
|
8971
|
-
|
|
8972
|
-
|
|
9317
|
+
stdin,
|
|
9318
|
+
stdout,
|
|
9319
|
+
stderr,
|
|
9320
|
+
isTTY: options.isTTY ?? false,
|
|
9321
|
+
argv: options.argv ?? ["node", "llmist"],
|
|
9322
|
+
env: { ...filterDefinedEnv(process.env), ...options.env },
|
|
9323
|
+
get exitCode() {
|
|
9324
|
+
return exitCode;
|
|
9325
|
+
},
|
|
9326
|
+
setExitCode: (code) => {
|
|
9327
|
+
exitCode = code;
|
|
9328
|
+
}
|
|
8973
9329
|
};
|
|
8974
9330
|
}
|
|
8975
|
-
function
|
|
8976
|
-
if (!
|
|
8977
|
-
|
|
8978
|
-
|
|
8979
|
-
|
|
8980
|
-
|
|
9331
|
+
function createMockReadable(input) {
|
|
9332
|
+
if (!input) {
|
|
9333
|
+
const stream3 = new import_node_stream.Readable({ read() {
|
|
9334
|
+
} });
|
|
9335
|
+
stream3.push(null);
|
|
9336
|
+
return stream3;
|
|
8981
9337
|
}
|
|
8982
|
-
|
|
9338
|
+
const content = Array.isArray(input) ? `${input.join("\n")}
|
|
9339
|
+
` : input;
|
|
9340
|
+
const stream2 = new import_node_stream.Readable({ read() {
|
|
9341
|
+
} });
|
|
9342
|
+
stream2.push(content);
|
|
9343
|
+
stream2.push(null);
|
|
9344
|
+
return stream2;
|
|
8983
9345
|
}
|
|
8984
|
-
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
|
|
8988
|
-
|
|
8989
|
-
|
|
8990
|
-
|
|
8991
|
-
|
|
8992
|
-
|
|
8993
|
-
|
|
8994
|
-
|
|
9346
|
+
function createMockWritable() {
|
|
9347
|
+
const chunks = [];
|
|
9348
|
+
const stream2 = new import_node_stream.Writable({
|
|
9349
|
+
write(chunk, _encoding, callback) {
|
|
9350
|
+
chunks.push(Buffer.from(chunk));
|
|
9351
|
+
callback();
|
|
9352
|
+
}
|
|
9353
|
+
});
|
|
9354
|
+
stream2.getData = () => Buffer.concat(chunks).toString("utf8");
|
|
9355
|
+
return stream2;
|
|
9356
|
+
}
|
|
9357
|
+
async function collectOutput(stream2, timeout = 5e3) {
|
|
9358
|
+
return new Promise((resolve, reject) => {
|
|
9359
|
+
const chunks = [];
|
|
9360
|
+
const timeoutId = setTimeout(() => {
|
|
9361
|
+
resolve(Buffer.concat(chunks).toString("utf8"));
|
|
9362
|
+
}, timeout);
|
|
9363
|
+
stream2.on("data", (chunk) => {
|
|
9364
|
+
chunks.push(Buffer.from(chunk));
|
|
9365
|
+
});
|
|
9366
|
+
stream2.on("end", () => {
|
|
9367
|
+
clearTimeout(timeoutId);
|
|
9368
|
+
resolve(Buffer.concat(chunks).toString("utf8"));
|
|
9369
|
+
});
|
|
9370
|
+
stream2.on("error", (err) => {
|
|
9371
|
+
clearTimeout(timeoutId);
|
|
9372
|
+
reject(err);
|
|
9373
|
+
});
|
|
9374
|
+
});
|
|
9375
|
+
}
|
|
9376
|
+
function getBufferedOutput(stream2) {
|
|
9377
|
+
const chunks = [];
|
|
9378
|
+
for (; ; ) {
|
|
9379
|
+
const chunk = stream2.read();
|
|
9380
|
+
if (chunk === null) break;
|
|
9381
|
+
chunks.push(chunk);
|
|
9382
|
+
}
|
|
9383
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
9384
|
+
}
|
|
9385
|
+
function createMockPrompt(responses) {
|
|
9386
|
+
let index = 0;
|
|
9387
|
+
return async (_question) => {
|
|
9388
|
+
if (index >= responses.length) {
|
|
9389
|
+
throw new Error(`Mock prompt exhausted: no response for question ${index + 1}`);
|
|
9390
|
+
}
|
|
9391
|
+
return responses[index++];
|
|
9392
|
+
};
|
|
9393
|
+
}
|
|
9394
|
+
var MockPromptRecorder = class {
|
|
9395
|
+
responses;
|
|
9396
|
+
index = 0;
|
|
9397
|
+
questions = [];
|
|
9398
|
+
constructor(responses) {
|
|
9399
|
+
this.responses = responses;
|
|
9400
|
+
}
|
|
9401
|
+
/**
|
|
9402
|
+
* The prompt function to use in tests.
|
|
9403
|
+
*/
|
|
9404
|
+
prompt = async (question) => {
|
|
9405
|
+
this.questions.push(question);
|
|
9406
|
+
if (this.index >= this.responses.length) {
|
|
9407
|
+
throw new Error(`Mock prompt exhausted after ${this.index} questions`);
|
|
9408
|
+
}
|
|
9409
|
+
return this.responses[this.index++];
|
|
9410
|
+
};
|
|
9411
|
+
/**
|
|
9412
|
+
* Get all questions that were asked.
|
|
9413
|
+
*/
|
|
9414
|
+
getQuestions() {
|
|
9415
|
+
return [...this.questions];
|
|
9416
|
+
}
|
|
9417
|
+
/**
|
|
9418
|
+
* Get the number of questions asked.
|
|
9419
|
+
*/
|
|
9420
|
+
getQuestionCount() {
|
|
9421
|
+
return this.questions.length;
|
|
9422
|
+
}
|
|
9423
|
+
/**
|
|
9424
|
+
* Reset the recorder state.
|
|
9425
|
+
*/
|
|
9426
|
+
reset(newResponses) {
|
|
9427
|
+
this.index = 0;
|
|
9428
|
+
this.questions = [];
|
|
9429
|
+
if (newResponses) {
|
|
9430
|
+
this.responses = newResponses;
|
|
9431
|
+
}
|
|
9432
|
+
}
|
|
9433
|
+
};
|
|
9434
|
+
async function waitFor(condition, timeout = 5e3, interval = 50) {
|
|
9435
|
+
const startTime = Date.now();
|
|
9436
|
+
while (!condition()) {
|
|
9437
|
+
if (Date.now() - startTime > timeout) {
|
|
9438
|
+
throw new Error(`waitFor timed out after ${timeout}ms`);
|
|
9439
|
+
}
|
|
9440
|
+
await sleep(interval);
|
|
9441
|
+
}
|
|
9442
|
+
}
|
|
9443
|
+
function sleep(ms) {
|
|
9444
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9445
|
+
}
|
|
9446
|
+
function filterDefinedEnv(env) {
|
|
9447
|
+
const result = {};
|
|
9448
|
+
for (const [key, value] of Object.entries(env)) {
|
|
9449
|
+
if (value !== void 0) {
|
|
9450
|
+
result[key] = value;
|
|
9451
|
+
}
|
|
9452
|
+
}
|
|
9453
|
+
return result;
|
|
9454
|
+
}
|
|
9455
|
+
|
|
9456
|
+
// src/testing/conversation-fixtures.ts
|
|
9457
|
+
function createConversation(turnCount, options) {
|
|
9458
|
+
const messages = [];
|
|
9459
|
+
const userPrefix = options?.userPrefix ?? "User message";
|
|
9460
|
+
const assistantPrefix = options?.assistantPrefix ?? "Assistant response";
|
|
9461
|
+
const contentLength = options?.contentLength ?? 100;
|
|
9462
|
+
for (let i = 0; i < turnCount; i++) {
|
|
9463
|
+
const padding = " ".repeat(Math.max(0, contentLength - 30));
|
|
9464
|
+
messages.push({
|
|
9465
|
+
role: "user",
|
|
9466
|
+
content: `${userPrefix} ${i + 1}: This is turn ${i + 1} of the conversation.${padding}`
|
|
9467
|
+
});
|
|
9468
|
+
messages.push({
|
|
9469
|
+
role: "assistant",
|
|
9470
|
+
content: `${assistantPrefix} ${i + 1}: I acknowledge turn ${i + 1}.${padding}`
|
|
9471
|
+
});
|
|
9472
|
+
}
|
|
9473
|
+
return messages;
|
|
9474
|
+
}
|
|
9475
|
+
function createConversationWithGadgets(turnCount, gadgetCallsPerTurn = 1, options) {
|
|
9476
|
+
const messages = [];
|
|
9477
|
+
const gadgetNames = options?.gadgetNames ?? ["search", "calculate", "read"];
|
|
9478
|
+
const contentLength = options?.contentLength ?? 50;
|
|
9479
|
+
let gadgetIndex = 0;
|
|
9480
|
+
for (let turn = 0; turn < turnCount; turn++) {
|
|
9481
|
+
messages.push({
|
|
9482
|
+
role: "user",
|
|
9483
|
+
content: `User request ${turn + 1}${"x".repeat(contentLength)}`
|
|
9484
|
+
});
|
|
9485
|
+
for (let g = 0; g < gadgetCallsPerTurn; g++) {
|
|
9486
|
+
const gadgetName = gadgetNames[gadgetIndex % gadgetNames.length];
|
|
9487
|
+
gadgetIndex++;
|
|
9488
|
+
messages.push({
|
|
9489
|
+
role: "assistant",
|
|
9490
|
+
content: `!!!GADGET_START:${gadgetName}
|
|
9491
|
+
!!!ARG:query
|
|
9492
|
+
test query ${turn}-${g}
|
|
9493
|
+
!!!GADGET_END`
|
|
9494
|
+
});
|
|
9495
|
+
messages.push({
|
|
9496
|
+
role: "user",
|
|
9497
|
+
content: `Result: Gadget ${gadgetName} returned result for query ${turn}-${g}`
|
|
9498
|
+
});
|
|
9499
|
+
}
|
|
9500
|
+
messages.push({
|
|
9501
|
+
role: "assistant",
|
|
9502
|
+
content: `Final response for turn ${turn + 1}${"y".repeat(contentLength)}`
|
|
9503
|
+
});
|
|
9504
|
+
}
|
|
9505
|
+
return messages;
|
|
9506
|
+
}
|
|
9507
|
+
function estimateTokens(messages) {
|
|
9508
|
+
return Math.ceil(messages.reduce((sum, msg) => sum + (msg.content?.length ?? 0), 0) / 4);
|
|
9509
|
+
}
|
|
9510
|
+
function createUserMessage(content) {
|
|
9511
|
+
return { role: "user", content };
|
|
9512
|
+
}
|
|
9513
|
+
function createAssistantMessage(content) {
|
|
9514
|
+
return { role: "assistant", content };
|
|
9515
|
+
}
|
|
9516
|
+
function createSystemMessage(content) {
|
|
9517
|
+
return { role: "system", content };
|
|
9518
|
+
}
|
|
9519
|
+
function createMinimalConversation() {
|
|
9520
|
+
return [
|
|
9521
|
+
{ role: "user", content: "Hello" },
|
|
9522
|
+
{ role: "assistant", content: "Hi there!" }
|
|
9523
|
+
];
|
|
9524
|
+
}
|
|
9525
|
+
function createLargeConversation(targetTokens, options) {
|
|
9526
|
+
const tokensPerTurn = options?.tokensPerTurn ?? 200;
|
|
9527
|
+
const turnsNeeded = Math.ceil(targetTokens / tokensPerTurn);
|
|
9528
|
+
const charsPerMessage = Math.floor(tokensPerTurn * 4 / 2);
|
|
9529
|
+
return createConversation(turnsNeeded, {
|
|
9530
|
+
contentLength: charsPerMessage
|
|
9531
|
+
});
|
|
9532
|
+
}
|
|
9533
|
+
|
|
9534
|
+
// src/gadgets/validation.ts
|
|
9535
|
+
function validateAndApplyDefaults(schema, params) {
|
|
9536
|
+
const result = schema.safeParse(params);
|
|
9537
|
+
if (result.success) {
|
|
9538
|
+
return {
|
|
9539
|
+
success: true,
|
|
9540
|
+
data: result.data
|
|
9541
|
+
};
|
|
9542
|
+
}
|
|
9543
|
+
const issues = result.error.issues.map((issue) => ({
|
|
9544
|
+
path: issue.path.join(".") || "root",
|
|
9545
|
+
message: issue.message
|
|
9546
|
+
}));
|
|
9547
|
+
const formattedError = `Invalid parameters: ${issues.map((i) => `${i.path}: ${i.message}`).join("; ")}`;
|
|
9548
|
+
return {
|
|
9549
|
+
success: false,
|
|
9550
|
+
error: formattedError,
|
|
9551
|
+
issues
|
|
9552
|
+
};
|
|
9553
|
+
}
|
|
9554
|
+
function validateGadgetParams(gadget, params) {
|
|
9555
|
+
if (!gadget.parameterSchema) {
|
|
9556
|
+
return {
|
|
9557
|
+
success: true,
|
|
9558
|
+
data: params
|
|
9559
|
+
};
|
|
9560
|
+
}
|
|
9561
|
+
return validateAndApplyDefaults(gadget.parameterSchema, params);
|
|
9562
|
+
}
|
|
9563
|
+
|
|
9564
|
+
// src/testing/gadget-testing.ts
|
|
9565
|
+
async function testGadget(gadget, params, options) {
|
|
9566
|
+
let validatedParams = params;
|
|
9567
|
+
if (!options?.skipValidation) {
|
|
9568
|
+
const validationResult = validateGadgetParams(gadget, params);
|
|
9569
|
+
if (!validationResult.success) {
|
|
9570
|
+
return {
|
|
9571
|
+
error: validationResult.error,
|
|
9572
|
+
validatedParams: params
|
|
9573
|
+
};
|
|
8995
9574
|
}
|
|
8996
9575
|
validatedParams = validationResult.data;
|
|
8997
9576
|
}
|
|
@@ -9189,7 +9768,7 @@ function getMockManager(options) {
|
|
|
9189
9768
|
|
|
9190
9769
|
// src/testing/mock-stream.ts
|
|
9191
9770
|
init_constants();
|
|
9192
|
-
function
|
|
9771
|
+
function sleep2(ms) {
|
|
9193
9772
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9194
9773
|
}
|
|
9195
9774
|
function generateInvocationId() {
|
|
@@ -9270,7 +9849,7 @@ ${blockParams}${GADGET_END_PREFIX}`;
|
|
|
9270
9849
|
}
|
|
9271
9850
|
async function* createMockStream(response) {
|
|
9272
9851
|
if (response.delayMs) {
|
|
9273
|
-
await
|
|
9852
|
+
await sleep2(response.delayMs);
|
|
9274
9853
|
}
|
|
9275
9854
|
const streamDelay = response.streamDelayMs ?? 0;
|
|
9276
9855
|
let fullText = response.text ?? "";
|
|
@@ -9295,7 +9874,7 @@ async function* createMockStream(response) {
|
|
|
9295
9874
|
}
|
|
9296
9875
|
yield chunk;
|
|
9297
9876
|
if (streamDelay > 0 && !isLast) {
|
|
9298
|
-
await
|
|
9877
|
+
await sleep2(streamDelay);
|
|
9299
9878
|
}
|
|
9300
9879
|
}
|
|
9301
9880
|
} else {
|
|
@@ -9572,7 +10151,9 @@ var MockBuilder = class {
|
|
|
9572
10151
|
*/
|
|
9573
10152
|
whenMessageContains(text3) {
|
|
9574
10153
|
this.matchers.push(
|
|
9575
|
-
(ctx) => ctx.messages.some(
|
|
10154
|
+
(ctx) => ctx.messages.some(
|
|
10155
|
+
(msg) => extractMessageText(msg.content).toLowerCase().includes(text3.toLowerCase())
|
|
10156
|
+
)
|
|
9576
10157
|
);
|
|
9577
10158
|
return this;
|
|
9578
10159
|
}
|
|
@@ -9586,7 +10167,7 @@ var MockBuilder = class {
|
|
|
9586
10167
|
this.matchers.push((ctx) => {
|
|
9587
10168
|
const lastMsg = ctx.messages[ctx.messages.length - 1];
|
|
9588
10169
|
if (!lastMsg) return false;
|
|
9589
|
-
return
|
|
10170
|
+
return extractMessageText(lastMsg.content).toLowerCase().includes(text3.toLowerCase());
|
|
9590
10171
|
});
|
|
9591
10172
|
return this;
|
|
9592
10173
|
}
|
|
@@ -9597,7 +10178,7 @@ var MockBuilder = class {
|
|
|
9597
10178
|
* mockLLM().whenMessageMatches(/calculate \d+/)
|
|
9598
10179
|
*/
|
|
9599
10180
|
whenMessageMatches(regex) {
|
|
9600
|
-
this.matchers.push((ctx) => ctx.messages.some((msg) => regex.test(
|
|
10181
|
+
this.matchers.push((ctx) => ctx.messages.some((msg) => regex.test(extractMessageText(msg.content))));
|
|
9601
10182
|
return this;
|
|
9602
10183
|
}
|
|
9603
10184
|
/**
|
|
@@ -9609,7 +10190,7 @@ var MockBuilder = class {
|
|
|
9609
10190
|
whenRoleContains(role, text3) {
|
|
9610
10191
|
this.matchers.push(
|
|
9611
10192
|
(ctx) => ctx.messages.some(
|
|
9612
|
-
(msg) => msg.role === role &&
|
|
10193
|
+
(msg) => msg.role === role && extractMessageText(msg.content).toLowerCase().includes(text3.toLowerCase())
|
|
9613
10194
|
)
|
|
9614
10195
|
);
|
|
9615
10196
|
return this;
|
|
@@ -10010,32 +10591,161 @@ function createMockClient(options) {
|
|
|
10010
10591
|
});
|
|
10011
10592
|
}
|
|
10012
10593
|
|
|
10013
|
-
// src/testing/mock-
|
|
10014
|
-
|
|
10015
|
-
|
|
10016
|
-
|
|
10017
|
-
|
|
10018
|
-
|
|
10019
|
-
|
|
10020
|
-
|
|
10021
|
-
|
|
10022
|
-
|
|
10023
|
-
|
|
10024
|
-
|
|
10025
|
-
|
|
10026
|
-
|
|
10027
|
-
|
|
10028
|
-
|
|
10029
|
-
|
|
10030
|
-
|
|
10031
|
-
this.
|
|
10032
|
-
this.
|
|
10033
|
-
|
|
10034
|
-
|
|
10035
|
-
|
|
10036
|
-
|
|
10037
|
-
|
|
10038
|
-
|
|
10594
|
+
// src/testing/mock-conversation.ts
|
|
10595
|
+
var MockConversationManager = class {
|
|
10596
|
+
history;
|
|
10597
|
+
baseMessages;
|
|
10598
|
+
replacementHistory;
|
|
10599
|
+
replaceHistoryCallCount = 0;
|
|
10600
|
+
addedMessages = [];
|
|
10601
|
+
constructor(history = [], baseMessages = []) {
|
|
10602
|
+
this.history = [...history];
|
|
10603
|
+
this.baseMessages = [...baseMessages];
|
|
10604
|
+
}
|
|
10605
|
+
addUserMessage(content) {
|
|
10606
|
+
const msg = { role: "user", content };
|
|
10607
|
+
this.history.push(msg);
|
|
10608
|
+
this.addedMessages.push(msg);
|
|
10609
|
+
}
|
|
10610
|
+
addAssistantMessage(content) {
|
|
10611
|
+
const msg = { role: "assistant", content };
|
|
10612
|
+
this.history.push(msg);
|
|
10613
|
+
this.addedMessages.push(msg);
|
|
10614
|
+
}
|
|
10615
|
+
addGadgetCallResult(gadgetName, parameters, result) {
|
|
10616
|
+
const assistantMsg = {
|
|
10617
|
+
role: "assistant",
|
|
10618
|
+
content: `!!!GADGET_START:${gadgetName}
|
|
10619
|
+
${JSON.stringify(parameters)}
|
|
10620
|
+
!!!GADGET_END`
|
|
10621
|
+
};
|
|
10622
|
+
const resultMsg = {
|
|
10623
|
+
role: "user",
|
|
10624
|
+
content: `Result: ${result}`
|
|
10625
|
+
};
|
|
10626
|
+
this.history.push(assistantMsg);
|
|
10627
|
+
this.history.push(resultMsg);
|
|
10628
|
+
this.addedMessages.push(assistantMsg);
|
|
10629
|
+
this.addedMessages.push(resultMsg);
|
|
10630
|
+
}
|
|
10631
|
+
getMessages() {
|
|
10632
|
+
return [...this.baseMessages, ...this.history];
|
|
10633
|
+
}
|
|
10634
|
+
getHistoryMessages() {
|
|
10635
|
+
return [...this.history];
|
|
10636
|
+
}
|
|
10637
|
+
getBaseMessages() {
|
|
10638
|
+
return [...this.baseMessages];
|
|
10639
|
+
}
|
|
10640
|
+
replaceHistory(newHistory) {
|
|
10641
|
+
this.replacementHistory = [...newHistory];
|
|
10642
|
+
this.history = [...newHistory];
|
|
10643
|
+
this.replaceHistoryCallCount++;
|
|
10644
|
+
}
|
|
10645
|
+
// ============================================
|
|
10646
|
+
// Test Helper Methods
|
|
10647
|
+
// ============================================
|
|
10648
|
+
/**
|
|
10649
|
+
* Check if replaceHistory was called.
|
|
10650
|
+
*/
|
|
10651
|
+
wasReplaceHistoryCalled() {
|
|
10652
|
+
return this.replaceHistoryCallCount > 0;
|
|
10653
|
+
}
|
|
10654
|
+
/**
|
|
10655
|
+
* Get the number of times replaceHistory was called.
|
|
10656
|
+
*/
|
|
10657
|
+
getReplaceHistoryCallCount() {
|
|
10658
|
+
return this.replaceHistoryCallCount;
|
|
10659
|
+
}
|
|
10660
|
+
/**
|
|
10661
|
+
* Get the most recent history passed to replaceHistory.
|
|
10662
|
+
* Returns undefined if replaceHistory was never called.
|
|
10663
|
+
*/
|
|
10664
|
+
getReplacementHistory() {
|
|
10665
|
+
return this.replacementHistory;
|
|
10666
|
+
}
|
|
10667
|
+
/**
|
|
10668
|
+
* Get all messages that were added via add* methods.
|
|
10669
|
+
*/
|
|
10670
|
+
getAddedMessages() {
|
|
10671
|
+
return [...this.addedMessages];
|
|
10672
|
+
}
|
|
10673
|
+
/**
|
|
10674
|
+
* Reset all tracking state while preserving the conversation.
|
|
10675
|
+
*/
|
|
10676
|
+
resetTracking() {
|
|
10677
|
+
this.replacementHistory = void 0;
|
|
10678
|
+
this.replaceHistoryCallCount = 0;
|
|
10679
|
+
this.addedMessages = [];
|
|
10680
|
+
}
|
|
10681
|
+
/**
|
|
10682
|
+
* Completely reset the mock to initial state.
|
|
10683
|
+
* Note: baseMessages cannot be changed after construction.
|
|
10684
|
+
*/
|
|
10685
|
+
reset(history = []) {
|
|
10686
|
+
this.history = [...history];
|
|
10687
|
+
this.resetTracking();
|
|
10688
|
+
}
|
|
10689
|
+
/**
|
|
10690
|
+
* Set the history directly (for test setup).
|
|
10691
|
+
*/
|
|
10692
|
+
setHistory(messages) {
|
|
10693
|
+
this.history = [...messages];
|
|
10694
|
+
}
|
|
10695
|
+
/**
|
|
10696
|
+
* Get the current history length.
|
|
10697
|
+
*/
|
|
10698
|
+
getHistoryLength() {
|
|
10699
|
+
return this.history.length;
|
|
10700
|
+
}
|
|
10701
|
+
/**
|
|
10702
|
+
* Get total message count (base + history).
|
|
10703
|
+
*/
|
|
10704
|
+
getTotalMessageCount() {
|
|
10705
|
+
return this.baseMessages.length + this.history.length;
|
|
10706
|
+
}
|
|
10707
|
+
};
|
|
10708
|
+
function createMockConversationManager(turnCount, baseMessages = []) {
|
|
10709
|
+
const history = [];
|
|
10710
|
+
for (let i = 0; i < turnCount; i++) {
|
|
10711
|
+
history.push({
|
|
10712
|
+
role: "user",
|
|
10713
|
+
content: `User message ${i + 1}: This is turn ${i + 1} of the conversation.`
|
|
10714
|
+
});
|
|
10715
|
+
history.push({
|
|
10716
|
+
role: "assistant",
|
|
10717
|
+
content: `Assistant response ${i + 1}: I acknowledge turn ${i + 1}.`
|
|
10718
|
+
});
|
|
10719
|
+
}
|
|
10720
|
+
return new MockConversationManager(history, baseMessages);
|
|
10721
|
+
}
|
|
10722
|
+
|
|
10723
|
+
// src/testing/mock-gadget.ts
|
|
10724
|
+
init_gadget();
|
|
10725
|
+
var MockGadgetImpl = class extends AbstractGadget {
|
|
10726
|
+
name;
|
|
10727
|
+
description;
|
|
10728
|
+
parameterSchema;
|
|
10729
|
+
timeoutMs;
|
|
10730
|
+
calls = [];
|
|
10731
|
+
resultValue;
|
|
10732
|
+
resultFn;
|
|
10733
|
+
errorToThrow;
|
|
10734
|
+
delayMs;
|
|
10735
|
+
shouldTrackCalls;
|
|
10736
|
+
constructor(config) {
|
|
10737
|
+
super();
|
|
10738
|
+
this.name = config.name;
|
|
10739
|
+
this.description = config.description ?? `Mock gadget: ${config.name}`;
|
|
10740
|
+
this.parameterSchema = config.schema;
|
|
10741
|
+
this.resultValue = config.result;
|
|
10742
|
+
this.resultFn = config.resultFn;
|
|
10743
|
+
this.delayMs = config.delayMs ?? 0;
|
|
10744
|
+
this.shouldTrackCalls = config.trackCalls ?? true;
|
|
10745
|
+
this.timeoutMs = config.timeoutMs;
|
|
10746
|
+
if (config.error) {
|
|
10747
|
+
this.errorToThrow = typeof config.error === "string" ? new Error(config.error) : config.error;
|
|
10748
|
+
}
|
|
10039
10749
|
}
|
|
10040
10750
|
async execute(params) {
|
|
10041
10751
|
if (this.shouldTrackCalls) {
|
|
@@ -10169,7 +10879,7 @@ function createTestStream(chunks) {
|
|
|
10169
10879
|
function createTextStream(text3, options) {
|
|
10170
10880
|
return async function* () {
|
|
10171
10881
|
if (options?.delayMs) {
|
|
10172
|
-
await
|
|
10882
|
+
await sleep3(options.delayMs);
|
|
10173
10883
|
}
|
|
10174
10884
|
const chunkSize = options?.chunkSize ?? text3.length;
|
|
10175
10885
|
const chunks = [];
|
|
@@ -10191,7 +10901,7 @@ function createTextStream(text3, options) {
|
|
|
10191
10901
|
}
|
|
10192
10902
|
yield chunk;
|
|
10193
10903
|
if (options?.chunkDelayMs && !isLast) {
|
|
10194
|
-
await
|
|
10904
|
+
await sleep3(options.chunkDelayMs);
|
|
10195
10905
|
}
|
|
10196
10906
|
}
|
|
10197
10907
|
}();
|
|
@@ -10229,365 +10939,9 @@ function createErrorStream(chunksBeforeError, error) {
|
|
|
10229
10939
|
throw error;
|
|
10230
10940
|
}();
|
|
10231
10941
|
}
|
|
10232
|
-
function sleep2(ms) {
|
|
10233
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
10234
|
-
}
|
|
10235
|
-
|
|
10236
|
-
// src/testing/conversation-fixtures.ts
|
|
10237
|
-
function createConversation(turnCount, options) {
|
|
10238
|
-
const messages = [];
|
|
10239
|
-
const userPrefix = options?.userPrefix ?? "User message";
|
|
10240
|
-
const assistantPrefix = options?.assistantPrefix ?? "Assistant response";
|
|
10241
|
-
const contentLength = options?.contentLength ?? 100;
|
|
10242
|
-
for (let i = 0; i < turnCount; i++) {
|
|
10243
|
-
const padding = " ".repeat(Math.max(0, contentLength - 30));
|
|
10244
|
-
messages.push({
|
|
10245
|
-
role: "user",
|
|
10246
|
-
content: `${userPrefix} ${i + 1}: This is turn ${i + 1} of the conversation.${padding}`
|
|
10247
|
-
});
|
|
10248
|
-
messages.push({
|
|
10249
|
-
role: "assistant",
|
|
10250
|
-
content: `${assistantPrefix} ${i + 1}: I acknowledge turn ${i + 1}.${padding}`
|
|
10251
|
-
});
|
|
10252
|
-
}
|
|
10253
|
-
return messages;
|
|
10254
|
-
}
|
|
10255
|
-
function createConversationWithGadgets(turnCount, gadgetCallsPerTurn = 1, options) {
|
|
10256
|
-
const messages = [];
|
|
10257
|
-
const gadgetNames = options?.gadgetNames ?? ["search", "calculate", "read"];
|
|
10258
|
-
const contentLength = options?.contentLength ?? 50;
|
|
10259
|
-
let gadgetIndex = 0;
|
|
10260
|
-
for (let turn = 0; turn < turnCount; turn++) {
|
|
10261
|
-
messages.push({
|
|
10262
|
-
role: "user",
|
|
10263
|
-
content: `User request ${turn + 1}${"x".repeat(contentLength)}`
|
|
10264
|
-
});
|
|
10265
|
-
for (let g = 0; g < gadgetCallsPerTurn; g++) {
|
|
10266
|
-
const gadgetName = gadgetNames[gadgetIndex % gadgetNames.length];
|
|
10267
|
-
gadgetIndex++;
|
|
10268
|
-
messages.push({
|
|
10269
|
-
role: "assistant",
|
|
10270
|
-
content: `!!!GADGET_START:${gadgetName}
|
|
10271
|
-
!!!ARG:query
|
|
10272
|
-
test query ${turn}-${g}
|
|
10273
|
-
!!!GADGET_END`
|
|
10274
|
-
});
|
|
10275
|
-
messages.push({
|
|
10276
|
-
role: "user",
|
|
10277
|
-
content: `Result: Gadget ${gadgetName} returned result for query ${turn}-${g}`
|
|
10278
|
-
});
|
|
10279
|
-
}
|
|
10280
|
-
messages.push({
|
|
10281
|
-
role: "assistant",
|
|
10282
|
-
content: `Final response for turn ${turn + 1}${"y".repeat(contentLength)}`
|
|
10283
|
-
});
|
|
10284
|
-
}
|
|
10285
|
-
return messages;
|
|
10286
|
-
}
|
|
10287
|
-
function estimateTokens(messages) {
|
|
10288
|
-
return Math.ceil(
|
|
10289
|
-
messages.reduce((sum, msg) => sum + (msg.content?.length ?? 0), 0) / 4
|
|
10290
|
-
);
|
|
10291
|
-
}
|
|
10292
|
-
function createUserMessage(content) {
|
|
10293
|
-
return { role: "user", content };
|
|
10294
|
-
}
|
|
10295
|
-
function createAssistantMessage(content) {
|
|
10296
|
-
return { role: "assistant", content };
|
|
10297
|
-
}
|
|
10298
|
-
function createSystemMessage(content) {
|
|
10299
|
-
return { role: "system", content };
|
|
10300
|
-
}
|
|
10301
|
-
function createMinimalConversation() {
|
|
10302
|
-
return [
|
|
10303
|
-
{ role: "user", content: "Hello" },
|
|
10304
|
-
{ role: "assistant", content: "Hi there!" }
|
|
10305
|
-
];
|
|
10306
|
-
}
|
|
10307
|
-
function createLargeConversation(targetTokens, options) {
|
|
10308
|
-
const tokensPerTurn = options?.tokensPerTurn ?? 200;
|
|
10309
|
-
const turnsNeeded = Math.ceil(targetTokens / tokensPerTurn);
|
|
10310
|
-
const charsPerMessage = Math.floor(tokensPerTurn * 4 / 2);
|
|
10311
|
-
return createConversation(turnsNeeded, {
|
|
10312
|
-
contentLength: charsPerMessage
|
|
10313
|
-
});
|
|
10314
|
-
}
|
|
10315
|
-
|
|
10316
|
-
// src/testing/mock-conversation.ts
|
|
10317
|
-
var MockConversationManager = class {
|
|
10318
|
-
history;
|
|
10319
|
-
baseMessages;
|
|
10320
|
-
replacementHistory;
|
|
10321
|
-
replaceHistoryCallCount = 0;
|
|
10322
|
-
addedMessages = [];
|
|
10323
|
-
constructor(history = [], baseMessages = []) {
|
|
10324
|
-
this.history = [...history];
|
|
10325
|
-
this.baseMessages = [...baseMessages];
|
|
10326
|
-
}
|
|
10327
|
-
addUserMessage(content) {
|
|
10328
|
-
const msg = { role: "user", content };
|
|
10329
|
-
this.history.push(msg);
|
|
10330
|
-
this.addedMessages.push(msg);
|
|
10331
|
-
}
|
|
10332
|
-
addAssistantMessage(content) {
|
|
10333
|
-
const msg = { role: "assistant", content };
|
|
10334
|
-
this.history.push(msg);
|
|
10335
|
-
this.addedMessages.push(msg);
|
|
10336
|
-
}
|
|
10337
|
-
addGadgetCall(gadgetName, parameters, result) {
|
|
10338
|
-
const assistantMsg = {
|
|
10339
|
-
role: "assistant",
|
|
10340
|
-
content: `!!!GADGET_START:${gadgetName}
|
|
10341
|
-
${JSON.stringify(parameters)}
|
|
10342
|
-
!!!GADGET_END`
|
|
10343
|
-
};
|
|
10344
|
-
const resultMsg = {
|
|
10345
|
-
role: "user",
|
|
10346
|
-
content: `Result: ${result}`
|
|
10347
|
-
};
|
|
10348
|
-
this.history.push(assistantMsg);
|
|
10349
|
-
this.history.push(resultMsg);
|
|
10350
|
-
this.addedMessages.push(assistantMsg);
|
|
10351
|
-
this.addedMessages.push(resultMsg);
|
|
10352
|
-
}
|
|
10353
|
-
getMessages() {
|
|
10354
|
-
return [...this.baseMessages, ...this.history];
|
|
10355
|
-
}
|
|
10356
|
-
getHistoryMessages() {
|
|
10357
|
-
return [...this.history];
|
|
10358
|
-
}
|
|
10359
|
-
getBaseMessages() {
|
|
10360
|
-
return [...this.baseMessages];
|
|
10361
|
-
}
|
|
10362
|
-
replaceHistory(newHistory) {
|
|
10363
|
-
this.replacementHistory = [...newHistory];
|
|
10364
|
-
this.history = [...newHistory];
|
|
10365
|
-
this.replaceHistoryCallCount++;
|
|
10366
|
-
}
|
|
10367
|
-
// ============================================
|
|
10368
|
-
// Test Helper Methods
|
|
10369
|
-
// ============================================
|
|
10370
|
-
/**
|
|
10371
|
-
* Check if replaceHistory was called.
|
|
10372
|
-
*/
|
|
10373
|
-
wasReplaceHistoryCalled() {
|
|
10374
|
-
return this.replaceHistoryCallCount > 0;
|
|
10375
|
-
}
|
|
10376
|
-
/**
|
|
10377
|
-
* Get the number of times replaceHistory was called.
|
|
10378
|
-
*/
|
|
10379
|
-
getReplaceHistoryCallCount() {
|
|
10380
|
-
return this.replaceHistoryCallCount;
|
|
10381
|
-
}
|
|
10382
|
-
/**
|
|
10383
|
-
* Get the most recent history passed to replaceHistory.
|
|
10384
|
-
* Returns undefined if replaceHistory was never called.
|
|
10385
|
-
*/
|
|
10386
|
-
getReplacementHistory() {
|
|
10387
|
-
return this.replacementHistory;
|
|
10388
|
-
}
|
|
10389
|
-
/**
|
|
10390
|
-
* Get all messages that were added via add* methods.
|
|
10391
|
-
*/
|
|
10392
|
-
getAddedMessages() {
|
|
10393
|
-
return [...this.addedMessages];
|
|
10394
|
-
}
|
|
10395
|
-
/**
|
|
10396
|
-
* Reset all tracking state while preserving the conversation.
|
|
10397
|
-
*/
|
|
10398
|
-
resetTracking() {
|
|
10399
|
-
this.replacementHistory = void 0;
|
|
10400
|
-
this.replaceHistoryCallCount = 0;
|
|
10401
|
-
this.addedMessages = [];
|
|
10402
|
-
}
|
|
10403
|
-
/**
|
|
10404
|
-
* Completely reset the mock to initial state.
|
|
10405
|
-
* Note: baseMessages cannot be changed after construction.
|
|
10406
|
-
*/
|
|
10407
|
-
reset(history = []) {
|
|
10408
|
-
this.history = [...history];
|
|
10409
|
-
this.resetTracking();
|
|
10410
|
-
}
|
|
10411
|
-
/**
|
|
10412
|
-
* Set the history directly (for test setup).
|
|
10413
|
-
*/
|
|
10414
|
-
setHistory(messages) {
|
|
10415
|
-
this.history = [...messages];
|
|
10416
|
-
}
|
|
10417
|
-
/**
|
|
10418
|
-
* Get the current history length.
|
|
10419
|
-
*/
|
|
10420
|
-
getHistoryLength() {
|
|
10421
|
-
return this.history.length;
|
|
10422
|
-
}
|
|
10423
|
-
/**
|
|
10424
|
-
* Get total message count (base + history).
|
|
10425
|
-
*/
|
|
10426
|
-
getTotalMessageCount() {
|
|
10427
|
-
return this.baseMessages.length + this.history.length;
|
|
10428
|
-
}
|
|
10429
|
-
};
|
|
10430
|
-
function createMockConversationManager(turnCount, baseMessages = []) {
|
|
10431
|
-
const history = [];
|
|
10432
|
-
for (let i = 0; i < turnCount; i++) {
|
|
10433
|
-
history.push({
|
|
10434
|
-
role: "user",
|
|
10435
|
-
content: `User message ${i + 1}: This is turn ${i + 1} of the conversation.`
|
|
10436
|
-
});
|
|
10437
|
-
history.push({
|
|
10438
|
-
role: "assistant",
|
|
10439
|
-
content: `Assistant response ${i + 1}: I acknowledge turn ${i + 1}.`
|
|
10440
|
-
});
|
|
10441
|
-
}
|
|
10442
|
-
return new MockConversationManager(history, baseMessages);
|
|
10443
|
-
}
|
|
10444
|
-
|
|
10445
|
-
// src/testing/cli-helpers.ts
|
|
10446
|
-
var import_node_stream = require("stream");
|
|
10447
|
-
function createTestEnvironment(options = {}) {
|
|
10448
|
-
const stdin = createMockReadable(options.stdin);
|
|
10449
|
-
const stdout = new import_node_stream.PassThrough();
|
|
10450
|
-
const stderr = new import_node_stream.PassThrough();
|
|
10451
|
-
let exitCode;
|
|
10452
|
-
return {
|
|
10453
|
-
stdin,
|
|
10454
|
-
stdout,
|
|
10455
|
-
stderr,
|
|
10456
|
-
isTTY: options.isTTY ?? false,
|
|
10457
|
-
argv: options.argv ?? ["node", "llmist"],
|
|
10458
|
-
env: { ...filterDefinedEnv(process.env), ...options.env },
|
|
10459
|
-
get exitCode() {
|
|
10460
|
-
return exitCode;
|
|
10461
|
-
},
|
|
10462
|
-
setExitCode: (code) => {
|
|
10463
|
-
exitCode = code;
|
|
10464
|
-
}
|
|
10465
|
-
};
|
|
10466
|
-
}
|
|
10467
|
-
function createMockReadable(input) {
|
|
10468
|
-
if (!input) {
|
|
10469
|
-
const stream3 = new import_node_stream.Readable({ read() {
|
|
10470
|
-
} });
|
|
10471
|
-
stream3.push(null);
|
|
10472
|
-
return stream3;
|
|
10473
|
-
}
|
|
10474
|
-
const content = Array.isArray(input) ? `${input.join("\n")}
|
|
10475
|
-
` : input;
|
|
10476
|
-
const stream2 = new import_node_stream.Readable({ read() {
|
|
10477
|
-
} });
|
|
10478
|
-
stream2.push(content);
|
|
10479
|
-
stream2.push(null);
|
|
10480
|
-
return stream2;
|
|
10481
|
-
}
|
|
10482
|
-
function createMockWritable() {
|
|
10483
|
-
const chunks = [];
|
|
10484
|
-
const stream2 = new import_node_stream.Writable({
|
|
10485
|
-
write(chunk, _encoding, callback) {
|
|
10486
|
-
chunks.push(Buffer.from(chunk));
|
|
10487
|
-
callback();
|
|
10488
|
-
}
|
|
10489
|
-
});
|
|
10490
|
-
stream2.getData = () => Buffer.concat(chunks).toString("utf8");
|
|
10491
|
-
return stream2;
|
|
10492
|
-
}
|
|
10493
|
-
async function collectOutput(stream2, timeout = 5e3) {
|
|
10494
|
-
return new Promise((resolve, reject) => {
|
|
10495
|
-
const chunks = [];
|
|
10496
|
-
const timeoutId = setTimeout(() => {
|
|
10497
|
-
resolve(Buffer.concat(chunks).toString("utf8"));
|
|
10498
|
-
}, timeout);
|
|
10499
|
-
stream2.on("data", (chunk) => {
|
|
10500
|
-
chunks.push(Buffer.from(chunk));
|
|
10501
|
-
});
|
|
10502
|
-
stream2.on("end", () => {
|
|
10503
|
-
clearTimeout(timeoutId);
|
|
10504
|
-
resolve(Buffer.concat(chunks).toString("utf8"));
|
|
10505
|
-
});
|
|
10506
|
-
stream2.on("error", (err) => {
|
|
10507
|
-
clearTimeout(timeoutId);
|
|
10508
|
-
reject(err);
|
|
10509
|
-
});
|
|
10510
|
-
});
|
|
10511
|
-
}
|
|
10512
|
-
function getBufferedOutput(stream2) {
|
|
10513
|
-
const chunks = [];
|
|
10514
|
-
for (; ; ) {
|
|
10515
|
-
const chunk = stream2.read();
|
|
10516
|
-
if (chunk === null) break;
|
|
10517
|
-
chunks.push(chunk);
|
|
10518
|
-
}
|
|
10519
|
-
return Buffer.concat(chunks).toString("utf8");
|
|
10520
|
-
}
|
|
10521
|
-
function createMockPrompt(responses) {
|
|
10522
|
-
let index = 0;
|
|
10523
|
-
return async (_question) => {
|
|
10524
|
-
if (index >= responses.length) {
|
|
10525
|
-
throw new Error(`Mock prompt exhausted: no response for question ${index + 1}`);
|
|
10526
|
-
}
|
|
10527
|
-
return responses[index++];
|
|
10528
|
-
};
|
|
10529
|
-
}
|
|
10530
|
-
var MockPromptRecorder = class {
|
|
10531
|
-
responses;
|
|
10532
|
-
index = 0;
|
|
10533
|
-
questions = [];
|
|
10534
|
-
constructor(responses) {
|
|
10535
|
-
this.responses = responses;
|
|
10536
|
-
}
|
|
10537
|
-
/**
|
|
10538
|
-
* The prompt function to use in tests.
|
|
10539
|
-
*/
|
|
10540
|
-
prompt = async (question) => {
|
|
10541
|
-
this.questions.push(question);
|
|
10542
|
-
if (this.index >= this.responses.length) {
|
|
10543
|
-
throw new Error(`Mock prompt exhausted after ${this.index} questions`);
|
|
10544
|
-
}
|
|
10545
|
-
return this.responses[this.index++];
|
|
10546
|
-
};
|
|
10547
|
-
/**
|
|
10548
|
-
* Get all questions that were asked.
|
|
10549
|
-
*/
|
|
10550
|
-
getQuestions() {
|
|
10551
|
-
return [...this.questions];
|
|
10552
|
-
}
|
|
10553
|
-
/**
|
|
10554
|
-
* Get the number of questions asked.
|
|
10555
|
-
*/
|
|
10556
|
-
getQuestionCount() {
|
|
10557
|
-
return this.questions.length;
|
|
10558
|
-
}
|
|
10559
|
-
/**
|
|
10560
|
-
* Reset the recorder state.
|
|
10561
|
-
*/
|
|
10562
|
-
reset(newResponses) {
|
|
10563
|
-
this.index = 0;
|
|
10564
|
-
this.questions = [];
|
|
10565
|
-
if (newResponses) {
|
|
10566
|
-
this.responses = newResponses;
|
|
10567
|
-
}
|
|
10568
|
-
}
|
|
10569
|
-
};
|
|
10570
|
-
async function waitFor(condition, timeout = 5e3, interval = 50) {
|
|
10571
|
-
const startTime = Date.now();
|
|
10572
|
-
while (!condition()) {
|
|
10573
|
-
if (Date.now() - startTime > timeout) {
|
|
10574
|
-
throw new Error(`waitFor timed out after ${timeout}ms`);
|
|
10575
|
-
}
|
|
10576
|
-
await sleep3(interval);
|
|
10577
|
-
}
|
|
10578
|
-
}
|
|
10579
10942
|
function sleep3(ms) {
|
|
10580
10943
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
10581
10944
|
}
|
|
10582
|
-
function filterDefinedEnv(env) {
|
|
10583
|
-
const result = {};
|
|
10584
|
-
for (const [key, value] of Object.entries(env)) {
|
|
10585
|
-
if (value !== void 0) {
|
|
10586
|
-
result[key] = value;
|
|
10587
|
-
}
|
|
10588
|
-
}
|
|
10589
|
-
return result;
|
|
10590
|
-
}
|
|
10591
10945
|
// Annotate the CommonJS export names for ESM import in node:
|
|
10592
10946
|
0 && (module.exports = {
|
|
10593
10947
|
MockBuilder,
|