llmist 2.6.0 → 3.1.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/dist/cli.cjs CHANGED
@@ -317,7 +317,7 @@ Example fixes:
317
317
  );
318
318
  }
319
319
  }
320
- function findUnknownTypes(schema, path5 = []) {
320
+ function findUnknownTypes(schema, path6 = []) {
321
321
  const issues = [];
322
322
  if (!schema || typeof schema !== "object") {
323
323
  return issues;
@@ -329,7 +329,7 @@ function findUnknownTypes(schema, path5 = []) {
329
329
  }
330
330
  if (schema.properties) {
331
331
  for (const [propName, propSchema] of Object.entries(schema.properties)) {
332
- const propPath = [...path5, propName];
332
+ const propPath = [...path6, propName];
333
333
  if (hasNoType(propSchema)) {
334
334
  issues.push(propPath.join(".") || propName);
335
335
  }
@@ -337,7 +337,7 @@ function findUnknownTypes(schema, path5 = []) {
337
337
  }
338
338
  }
339
339
  if (schema.items) {
340
- const itemPath = [...path5, "[]"];
340
+ const itemPath = [...path6, "[]"];
341
341
  if (hasNoType(schema.items)) {
342
342
  issues.push(itemPath.join("."));
343
343
  }
@@ -345,17 +345,17 @@ function findUnknownTypes(schema, path5 = []) {
345
345
  }
346
346
  if (schema.anyOf) {
347
347
  schema.anyOf.forEach((subSchema, index) => {
348
- issues.push(...findUnknownTypes(subSchema, [...path5, `anyOf[${index}]`]));
348
+ issues.push(...findUnknownTypes(subSchema, [...path6, `anyOf[${index}]`]));
349
349
  });
350
350
  }
351
351
  if (schema.oneOf) {
352
352
  schema.oneOf.forEach((subSchema, index) => {
353
- issues.push(...findUnknownTypes(subSchema, [...path5, `oneOf[${index}]`]));
353
+ issues.push(...findUnknownTypes(subSchema, [...path6, `oneOf[${index}]`]));
354
354
  });
355
355
  }
356
356
  if (schema.allOf) {
357
357
  schema.allOf.forEach((subSchema, index) => {
358
- issues.push(...findUnknownTypes(subSchema, [...path5, `allOf[${index}]`]));
358
+ issues.push(...findUnknownTypes(subSchema, [...path6, `allOf[${index}]`]));
359
359
  });
360
360
  }
361
361
  return issues;
@@ -530,13 +530,13 @@ var init_prompt_config = __esm({
530
530
  });
531
531
 
532
532
  // src/core/messages.ts
533
- function normalizeContent(content) {
533
+ function normalizeMessageContent(content) {
534
534
  if (typeof content === "string") {
535
535
  return [{ type: "text", text: content }];
536
536
  }
537
537
  return content;
538
538
  }
539
- function extractText(content) {
539
+ function extractMessageText(content) {
540
540
  if (typeof content === "string") {
541
541
  return content;
542
542
  }
@@ -896,7 +896,17 @@ Produces: { "items": ["first", "second"] }`);
896
896
  this.messages.push({ role: "user", content: parts });
897
897
  return this;
898
898
  }
899
- addGadgetCall(gadget, parameters, result, media, mediaIds) {
899
+ /**
900
+ * Record a gadget execution result in the message history.
901
+ * Creates an assistant message with the gadget invocation and a user message with the result.
902
+ *
903
+ * @param gadget - Name of the gadget that was executed
904
+ * @param parameters - Parameters that were passed to the gadget
905
+ * @param result - Text result from the gadget execution
906
+ * @param media - Optional media outputs from the gadget
907
+ * @param mediaIds - Optional IDs for the media outputs
908
+ */
909
+ addGadgetCallResult(gadget, parameters, result, media, mediaIds) {
900
910
  const paramStr = this.formatBlockParameters(parameters, "");
901
911
  this.messages.push({
902
912
  role: "assistant",
@@ -1164,21 +1174,21 @@ var init_media_store = __esm({
1164
1174
  });
1165
1175
 
1166
1176
  // src/gadgets/exceptions.ts
1167
- var BreakLoopException, HumanInputException, TimeoutException, AbortError;
1177
+ var TaskCompletionSignal, HumanInputRequiredException, TimeoutException, AbortException;
1168
1178
  var init_exceptions = __esm({
1169
1179
  "src/gadgets/exceptions.ts"() {
1170
1180
  "use strict";
1171
- BreakLoopException = class extends Error {
1181
+ TaskCompletionSignal = class extends Error {
1172
1182
  constructor(message) {
1173
1183
  super(message ?? "Agent loop terminated by gadget");
1174
- this.name = "BreakLoopException";
1184
+ this.name = "TaskCompletionSignal";
1175
1185
  }
1176
1186
  };
1177
- HumanInputException = class extends Error {
1187
+ HumanInputRequiredException = class extends Error {
1178
1188
  question;
1179
1189
  constructor(question) {
1180
1190
  super(`Human input required: ${question}`);
1181
- this.name = "HumanInputException";
1191
+ this.name = "HumanInputRequiredException";
1182
1192
  this.question = question;
1183
1193
  }
1184
1194
  };
@@ -1192,10 +1202,10 @@ var init_exceptions = __esm({
1192
1202
  this.timeoutMs = timeoutMs;
1193
1203
  }
1194
1204
  };
1195
- AbortError = class extends Error {
1205
+ AbortException = class extends Error {
1196
1206
  constructor(message) {
1197
1207
  super(message || "Gadget execution was aborted");
1198
- this.name = "AbortError";
1208
+ this.name = "AbortException";
1199
1209
  }
1200
1210
  };
1201
1211
  }
@@ -1294,29 +1304,29 @@ function schemaToJSONSchema(schema, options) {
1294
1304
  }
1295
1305
  function detectDescriptionMismatch(schema, jsonSchema) {
1296
1306
  const mismatches = [];
1297
- function checkSchema(zodSchema, json, path5) {
1307
+ function checkSchema(zodSchema, json, path6) {
1298
1308
  if (!zodSchema || typeof zodSchema !== "object") return;
1299
1309
  const def = zodSchema._def;
1300
1310
  const jsonObj = json;
1301
1311
  if (def?.description && !jsonObj?.description) {
1302
- mismatches.push(path5 || "root");
1312
+ mismatches.push(path6 || "root");
1303
1313
  }
1304
1314
  if (def?.typeName === "ZodObject" && def?.shape) {
1305
1315
  const shape = typeof def.shape === "function" ? def.shape() : def.shape;
1306
1316
  for (const [key, fieldSchema] of Object.entries(shape)) {
1307
1317
  const properties = jsonObj?.properties;
1308
1318
  const jsonProp = properties?.[key];
1309
- checkSchema(fieldSchema, jsonProp, path5 ? `${path5}.${key}` : key);
1319
+ checkSchema(fieldSchema, jsonProp, path6 ? `${path6}.${key}` : key);
1310
1320
  }
1311
1321
  }
1312
1322
  if (def?.typeName === "ZodArray" && def?.type) {
1313
- checkSchema(def.type, jsonObj?.items, path5 ? `${path5}[]` : "[]");
1323
+ checkSchema(def.type, jsonObj?.items, path6 ? `${path6}[]` : "[]");
1314
1324
  }
1315
1325
  if ((def?.typeName === "ZodOptional" || def?.typeName === "ZodNullable") && def?.innerType) {
1316
- checkSchema(def.innerType, json, path5);
1326
+ checkSchema(def.innerType, json, path6);
1317
1327
  }
1318
1328
  if (def?.typeName === "ZodDefault" && def?.innerType) {
1319
- checkSchema(def.innerType, json, path5);
1329
+ checkSchema(def.innerType, json, path6);
1320
1330
  }
1321
1331
  }
1322
1332
  checkSchema(schema, jsonSchema, "");
@@ -1364,7 +1374,7 @@ var init_schema_to_json = __esm({
1364
1374
  });
1365
1375
 
1366
1376
  // src/gadgets/gadget.ts
1367
- function formatParamsAsBlock(params, prefix = "", argPrefix = GADGET_ARG_PREFIX) {
1377
+ function formatParamsForBlockExample(params, prefix = "", argPrefix = GADGET_ARG_PREFIX) {
1368
1378
  const lines = [];
1369
1379
  for (const [key, value] of Object.entries(params)) {
1370
1380
  const fullPath = prefix ? `${prefix}/${key}` : key;
@@ -1372,14 +1382,14 @@ function formatParamsAsBlock(params, prefix = "", argPrefix = GADGET_ARG_PREFIX)
1372
1382
  value.forEach((item, index) => {
1373
1383
  const itemPath = `${fullPath}/${index}`;
1374
1384
  if (typeof item === "object" && item !== null) {
1375
- lines.push(formatParamsAsBlock(item, itemPath, argPrefix));
1385
+ lines.push(formatParamsForBlockExample(item, itemPath, argPrefix));
1376
1386
  } else {
1377
1387
  lines.push(`${argPrefix}${itemPath}`);
1378
1388
  lines.push(String(item));
1379
1389
  }
1380
1390
  });
1381
1391
  } else if (typeof value === "object" && value !== null) {
1382
- lines.push(formatParamsAsBlock(value, fullPath, argPrefix));
1392
+ lines.push(formatParamsForBlockExample(value, fullPath, argPrefix));
1383
1393
  } else {
1384
1394
  lines.push(`${argPrefix}${fullPath}`);
1385
1395
  lines.push(String(value));
@@ -1468,7 +1478,7 @@ function formatSchemaAsPlainText(schema, indent = "", atRoot = true) {
1468
1478
  }
1469
1479
  return lines.join("\n");
1470
1480
  }
1471
- var BaseGadget;
1481
+ var AbstractGadget;
1472
1482
  var init_gadget = __esm({
1473
1483
  "src/gadgets/gadget.ts"() {
1474
1484
  "use strict";
@@ -1476,7 +1486,7 @@ var init_gadget = __esm({
1476
1486
  init_exceptions();
1477
1487
  init_schema_to_json();
1478
1488
  init_schema_validator();
1479
- BaseGadget = class {
1489
+ AbstractGadget = class {
1480
1490
  /**
1481
1491
  * The name of the gadget. Used for identification when LLM calls it.
1482
1492
  * If not provided, defaults to the class name.
@@ -1504,14 +1514,14 @@ var init_gadget = __esm({
1504
1514
  */
1505
1515
  examples;
1506
1516
  /**
1507
- * Throws an AbortError if the execution has been aborted.
1517
+ * Throws an AbortException if the execution has been aborted.
1508
1518
  *
1509
1519
  * Call this at key checkpoints in long-running gadgets to allow early exit
1510
1520
  * when the gadget has been cancelled (e.g., due to timeout). This enables
1511
1521
  * resource cleanup and prevents unnecessary work after cancellation.
1512
1522
  *
1513
1523
  * @param ctx - The execution context containing the abort signal
1514
- * @throws AbortError if ctx.signal.aborted is true
1524
+ * @throws AbortException if ctx.signal.aborted is true
1515
1525
  *
1516
1526
  * @example
1517
1527
  * ```typescript
@@ -1536,7 +1546,7 @@ var init_gadget = __esm({
1536
1546
  */
1537
1547
  throwIfAborted(ctx) {
1538
1548
  if (ctx?.signal?.aborted) {
1539
- throw new AbortError();
1549
+ throw new AbortException();
1540
1550
  }
1541
1551
  }
1542
1552
  /**
@@ -1678,7 +1688,7 @@ var init_gadget = __esm({
1678
1688
  }
1679
1689
  parts.push(`${effectiveStartPrefix}${gadgetName}`);
1680
1690
  parts.push(
1681
- formatParamsAsBlock(example.params, "", effectiveArgPrefix)
1691
+ formatParamsForBlockExample(example.params, "", effectiveArgPrefix)
1682
1692
  );
1683
1693
  parts.push(effectiveEndPrefix);
1684
1694
  if (example.output !== void 0) {
@@ -1696,7 +1706,7 @@ var init_gadget = __esm({
1696
1706
 
1697
1707
  // src/gadgets/create-gadget.ts
1698
1708
  function createGadget(config) {
1699
- class DynamicGadget extends BaseGadget {
1709
+ class DynamicGadget extends AbstractGadget {
1700
1710
  name = config.name;
1701
1711
  description = config.description;
1702
1712
  parameterSchema = config.schema;
@@ -2334,8 +2344,8 @@ var init_conversation_manager = __esm({
2334
2344
  addAssistantMessage(content) {
2335
2345
  this.historyBuilder.addAssistant(content);
2336
2346
  }
2337
- addGadgetCall(gadgetName, parameters, result, media, mediaIds) {
2338
- this.historyBuilder.addGadgetCall(gadgetName, parameters, result, media, mediaIds);
2347
+ addGadgetCallResult(gadgetName, parameters, result, media, mediaIds) {
2348
+ this.historyBuilder.addGadgetCallResult(gadgetName, parameters, result, media, mediaIds);
2339
2349
  }
2340
2350
  getMessages() {
2341
2351
  return [...this.baseMessages, ...this.initialMessages, ...this.historyBuilder.build()];
@@ -2355,7 +2365,7 @@ var init_conversation_manager = __esm({
2355
2365
  if (msg.role === "user") {
2356
2366
  this.historyBuilder.addUser(msg.content);
2357
2367
  } else if (msg.role === "assistant") {
2358
- this.historyBuilder.addAssistant(extractText(msg.content));
2368
+ this.historyBuilder.addAssistant(extractMessageText(msg.content));
2359
2369
  }
2360
2370
  }
2361
2371
  }
@@ -3205,12 +3215,12 @@ var init_cost_reporting_client = __esm({
3205
3215
  });
3206
3216
 
3207
3217
  // src/gadgets/error-formatter.ts
3208
- var GadgetErrorFormatter;
3218
+ var GadgetExecutionErrorFormatter;
3209
3219
  var init_error_formatter = __esm({
3210
3220
  "src/gadgets/error-formatter.ts"() {
3211
3221
  "use strict";
3212
3222
  init_constants();
3213
- GadgetErrorFormatter = class {
3223
+ GadgetExecutionErrorFormatter = class {
3214
3224
  argPrefix;
3215
3225
  startPrefix;
3216
3226
  endPrefix;
@@ -3231,8 +3241,8 @@ var init_error_formatter = __esm({
3231
3241
  const parts = [];
3232
3242
  parts.push(`Error: Invalid parameters for '${gadgetName}':`);
3233
3243
  for (const issue of zodError.issues) {
3234
- const path5 = issue.path.join(".") || "root";
3235
- parts.push(` - ${path5}: ${issue.message}`);
3244
+ const path6 = issue.path.join(".") || "root";
3245
+ parts.push(` - ${path6}: ${issue.message}`);
3236
3246
  }
3237
3247
  parts.push("");
3238
3248
  parts.push("Gadget Usage:");
@@ -3296,16 +3306,16 @@ function stripMarkdownFences(content) {
3296
3306
  cleaned = cleaned.replace(closingFence, "");
3297
3307
  return cleaned.trim();
3298
3308
  }
3299
- var globalInvocationCounter, StreamParser;
3309
+ var globalInvocationCounter, GadgetCallParser;
3300
3310
  var init_parser = __esm({
3301
3311
  "src/gadgets/parser.ts"() {
3302
3312
  "use strict";
3303
3313
  init_constants();
3304
3314
  init_block_params();
3305
3315
  globalInvocationCounter = 0;
3306
- StreamParser = class {
3316
+ GadgetCallParser = class {
3307
3317
  buffer = "";
3308
- lastReportedTextLength = 0;
3318
+ lastEmittedTextOffset = 0;
3309
3319
  startPrefix;
3310
3320
  endPrefix;
3311
3321
  argPrefix;
@@ -3314,16 +3324,20 @@ var init_parser = __esm({
3314
3324
  this.endPrefix = options.endPrefix ?? GADGET_END_PREFIX;
3315
3325
  this.argPrefix = options.argPrefix ?? GADGET_ARG_PREFIX;
3316
3326
  }
3317
- takeTextUntil(index) {
3318
- if (index <= this.lastReportedTextLength) {
3327
+ /**
3328
+ * Extract and consume text up to the given index.
3329
+ * Returns undefined if no meaningful text to emit.
3330
+ */
3331
+ extractTextSegment(index) {
3332
+ if (index <= this.lastEmittedTextOffset) {
3319
3333
  return void 0;
3320
3334
  }
3321
- const segment = this.buffer.slice(this.lastReportedTextLength, index);
3322
- this.lastReportedTextLength = index;
3335
+ const segment = this.buffer.slice(this.lastEmittedTextOffset, index);
3336
+ this.lastEmittedTextOffset = index;
3323
3337
  return segment.trim().length > 0 ? segment : void 0;
3324
3338
  }
3325
3339
  /**
3326
- * Parse gadget name with optional invocation ID and dependencies.
3340
+ * Parse gadget invocation metadata from the header line.
3327
3341
  *
3328
3342
  * Supported formats:
3329
3343
  * - `GadgetName` - Auto-generate ID, no dependencies
@@ -3332,24 +3346,24 @@ var init_parser = __esm({
3332
3346
  *
3333
3347
  * Dependencies must be comma-separated invocation IDs.
3334
3348
  */
3335
- parseGadgetName(gadgetName) {
3336
- const parts = gadgetName.split(":");
3349
+ parseInvocationMetadata(headerLine) {
3350
+ const parts = headerLine.split(":");
3337
3351
  if (parts.length === 1) {
3338
3352
  return {
3339
- actualName: parts[0],
3353
+ gadgetName: parts[0],
3340
3354
  invocationId: `gadget_${++globalInvocationCounter}`,
3341
3355
  dependencies: []
3342
3356
  };
3343
3357
  } else if (parts.length === 2) {
3344
3358
  return {
3345
- actualName: parts[0],
3359
+ gadgetName: parts[0],
3346
3360
  invocationId: parts[1].trim(),
3347
3361
  dependencies: []
3348
3362
  };
3349
3363
  } else {
3350
3364
  const deps = parts[2].split(",").map((d) => d.trim()).filter((d) => d.length > 0);
3351
3365
  return {
3352
- actualName: parts[0],
3366
+ gadgetName: parts[0],
3353
3367
  invocationId: parts[1].trim(),
3354
3368
  dependencies: deps
3355
3369
  };
@@ -3381,19 +3395,15 @@ var init_parser = __esm({
3381
3395
  while (true) {
3382
3396
  const partStartIndex = this.buffer.indexOf(this.startPrefix, startIndex);
3383
3397
  if (partStartIndex === -1) break;
3384
- const textBefore = this.takeTextUntil(partStartIndex);
3398
+ const textBefore = this.extractTextSegment(partStartIndex);
3385
3399
  if (textBefore !== void 0) {
3386
3400
  yield { type: "text", content: textBefore };
3387
3401
  }
3388
3402
  const metadataStartIndex = partStartIndex + this.startPrefix.length;
3389
3403
  const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
3390
3404
  if (metadataEndIndex === -1) break;
3391
- const gadgetName = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
3392
- const {
3393
- actualName: actualGadgetName,
3394
- invocationId,
3395
- dependencies
3396
- } = this.parseGadgetName(gadgetName);
3405
+ const headerLine = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
3406
+ const { gadgetName, invocationId, dependencies } = this.parseInvocationMetadata(headerLine);
3397
3407
  const contentStartIndex = metadataEndIndex + 1;
3398
3408
  let partEndIndex;
3399
3409
  let endMarkerLength = 0;
@@ -3413,7 +3423,7 @@ var init_parser = __esm({
3413
3423
  yield {
3414
3424
  type: "gadget_call",
3415
3425
  call: {
3416
- gadgetName: actualGadgetName,
3426
+ gadgetName,
3417
3427
  invocationId,
3418
3428
  parametersRaw,
3419
3429
  parameters,
@@ -3422,37 +3432,33 @@ var init_parser = __esm({
3422
3432
  }
3423
3433
  };
3424
3434
  startIndex = partEndIndex + endMarkerLength;
3425
- this.lastReportedTextLength = startIndex;
3435
+ this.lastEmittedTextOffset = startIndex;
3426
3436
  }
3427
3437
  if (startIndex > 0) {
3428
3438
  this.buffer = this.buffer.substring(startIndex);
3429
- this.lastReportedTextLength = 0;
3439
+ this.lastEmittedTextOffset = 0;
3430
3440
  }
3431
3441
  }
3432
3442
  // Finalize parsing and return remaining text or incomplete gadgets
3433
3443
  *finalize() {
3434
- const startIndex = this.buffer.indexOf(this.startPrefix, this.lastReportedTextLength);
3444
+ const startIndex = this.buffer.indexOf(this.startPrefix, this.lastEmittedTextOffset);
3435
3445
  if (startIndex !== -1) {
3436
- const textBefore = this.takeTextUntil(startIndex);
3446
+ const textBefore = this.extractTextSegment(startIndex);
3437
3447
  if (textBefore !== void 0) {
3438
3448
  yield { type: "text", content: textBefore };
3439
3449
  }
3440
3450
  const metadataStartIndex = startIndex + this.startPrefix.length;
3441
3451
  const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
3442
3452
  if (metadataEndIndex !== -1) {
3443
- const gadgetName = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
3444
- const {
3445
- actualName: actualGadgetName,
3446
- invocationId,
3447
- dependencies
3448
- } = this.parseGadgetName(gadgetName);
3453
+ const headerLine = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
3454
+ const { gadgetName, invocationId, dependencies } = this.parseInvocationMetadata(headerLine);
3449
3455
  const contentStartIndex = metadataEndIndex + 1;
3450
3456
  const parametersRaw = this.buffer.substring(contentStartIndex).trim();
3451
3457
  const { parameters, parseError } = this.parseParameters(parametersRaw);
3452
3458
  yield {
3453
3459
  type: "gadget_call",
3454
3460
  call: {
3455
- gadgetName: actualGadgetName,
3461
+ gadgetName,
3456
3462
  invocationId,
3457
3463
  parametersRaw,
3458
3464
  parameters,
@@ -3463,7 +3469,7 @@ var init_parser = __esm({
3463
3469
  return;
3464
3470
  }
3465
3471
  }
3466
- const remainingText = this.takeTextUntil(this.buffer.length);
3472
+ const remainingText = this.extractTextSegment(this.buffer.length);
3467
3473
  if (remainingText !== void 0) {
3468
3474
  yield { type: "text", content: remainingText };
3469
3475
  }
@@ -3471,7 +3477,7 @@ var init_parser = __esm({
3471
3477
  // Reset parser state (note: global invocation counter is NOT reset to ensure unique IDs)
3472
3478
  reset() {
3473
3479
  this.buffer = "";
3474
- this.lastReportedTextLength = 0;
3480
+ this.lastEmittedTextOffset = 0;
3475
3481
  }
3476
3482
  };
3477
3483
  }
@@ -3490,14 +3496,17 @@ var init_executor = __esm({
3490
3496
  init_exceptions();
3491
3497
  init_parser();
3492
3498
  GadgetExecutor = class {
3493
- constructor(registry, onHumanInputRequired, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client, mediaStore) {
3499
+ constructor(registry, requestHumanInput, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client, mediaStore, agentConfig, subagentConfig, onNestedEvent) {
3494
3500
  this.registry = registry;
3495
- this.onHumanInputRequired = onHumanInputRequired;
3501
+ this.requestHumanInput = requestHumanInput;
3496
3502
  this.defaultGadgetTimeoutMs = defaultGadgetTimeoutMs;
3497
3503
  this.client = client;
3498
3504
  this.mediaStore = mediaStore;
3505
+ this.agentConfig = agentConfig;
3506
+ this.subagentConfig = subagentConfig;
3507
+ this.onNestedEvent = onNestedEvent;
3499
3508
  this.logger = logger ?? createLogger({ name: "llmist:executor" });
3500
- this.errorFormatter = new GadgetErrorFormatter(errorFormatterOptions);
3509
+ this.errorFormatter = new GadgetExecutionErrorFormatter(errorFormatterOptions);
3501
3510
  this.argPrefix = errorFormatterOptions?.argPrefix ?? GADGET_ARG_PREFIX;
3502
3511
  }
3503
3512
  logger;
@@ -3517,11 +3526,11 @@ var init_executor = __esm({
3517
3526
  });
3518
3527
  }
3519
3528
  /**
3520
- * Normalizes gadget execute result to consistent format.
3529
+ * Unify gadget execute result to consistent internal format.
3521
3530
  * Handles string returns (backwards compat), object returns with cost,
3522
3531
  * and object returns with media.
3523
3532
  */
3524
- normalizeExecuteResult(raw) {
3533
+ unifyExecuteResult(raw) {
3525
3534
  if (typeof raw === "string") {
3526
3535
  return { result: raw, cost: 0 };
3527
3536
  }
@@ -3639,7 +3648,11 @@ var init_executor = __esm({
3639
3648
  const ctx = {
3640
3649
  reportCost,
3641
3650
  llmist: this.client ? new CostReportingLLMistWrapper(this.client, reportCost) : void 0,
3642
- signal: abortController.signal
3651
+ signal: abortController.signal,
3652
+ agentConfig: this.agentConfig,
3653
+ subagentConfig: this.subagentConfig,
3654
+ invocationId: call.invocationId,
3655
+ onNestedEvent: this.onNestedEvent
3643
3656
  };
3644
3657
  let rawResult;
3645
3658
  if (timeoutMs && timeoutMs > 0) {
@@ -3654,7 +3667,7 @@ var init_executor = __esm({
3654
3667
  } else {
3655
3668
  rawResult = await Promise.resolve(gadget.execute(validatedParameters, ctx));
3656
3669
  }
3657
- const { result, media, cost: returnCost } = this.normalizeExecuteResult(rawResult);
3670
+ const { result, media, cost: returnCost } = this.unifyExecuteResult(rawResult);
3658
3671
  const totalCost = callbackCost + returnCost;
3659
3672
  let mediaIds;
3660
3673
  let storedMedia;
@@ -3700,7 +3713,7 @@ var init_executor = __esm({
3700
3713
  storedMedia
3701
3714
  };
3702
3715
  } catch (error) {
3703
- if (error instanceof BreakLoopException) {
3716
+ if (error instanceof TaskCompletionSignal) {
3704
3717
  this.logger.info("Gadget requested loop termination", {
3705
3718
  gadgetName: call.gadgetName,
3706
3719
  message: error.message
@@ -3728,7 +3741,7 @@ var init_executor = __esm({
3728
3741
  executionTimeMs: Date.now() - startTime
3729
3742
  };
3730
3743
  }
3731
- if (error instanceof AbortError) {
3744
+ if (error instanceof AbortException) {
3732
3745
  this.logger.info("Gadget execution was aborted", {
3733
3746
  gadgetName: call.gadgetName,
3734
3747
  executionTimeMs: Date.now() - startTime
@@ -3741,14 +3754,14 @@ var init_executor = __esm({
3741
3754
  executionTimeMs: Date.now() - startTime
3742
3755
  };
3743
3756
  }
3744
- if (error instanceof HumanInputException) {
3757
+ if (error instanceof HumanInputRequiredException) {
3745
3758
  this.logger.info("Gadget requested human input", {
3746
3759
  gadgetName: call.gadgetName,
3747
3760
  question: error.question
3748
3761
  });
3749
- if (this.onHumanInputRequired) {
3762
+ if (this.requestHumanInput) {
3750
3763
  try {
3751
- const answer = await this.onHumanInputRequired(error.question);
3764
+ const answer = await this.requestHumanInput(error.question);
3752
3765
  this.logger.debug("Human input received", {
3753
3766
  gadgetName: call.gadgetName,
3754
3767
  answerLength: answer.length
@@ -3846,13 +3859,13 @@ var init_stream_processor = __esm({
3846
3859
  parser;
3847
3860
  executor;
3848
3861
  stopOnGadgetError;
3849
- shouldContinueAfterError;
3850
- accumulatedText = "";
3851
- shouldStopExecution = false;
3862
+ canRecoverFromGadgetError;
3863
+ responseText = "";
3864
+ executionHalted = false;
3852
3865
  observerFailureCount = 0;
3853
3866
  // Dependency tracking for gadget execution DAG
3854
3867
  /** Gadgets waiting for their dependencies to complete */
3855
- pendingGadgets = /* @__PURE__ */ new Map();
3868
+ gadgetsAwaitingDependencies = /* @__PURE__ */ new Map();
3856
3869
  /** Completed gadget results, keyed by invocation ID */
3857
3870
  completedResults = /* @__PURE__ */ new Map();
3858
3871
  /** Invocation IDs of gadgets that have failed (error or skipped due to dependency) */
@@ -3863,27 +3876,36 @@ var init_stream_processor = __esm({
3863
3876
  this.hooks = options.hooks ?? {};
3864
3877
  this.logger = options.logger ?? createLogger({ name: "llmist:stream-processor" });
3865
3878
  this.stopOnGadgetError = options.stopOnGadgetError ?? true;
3866
- this.shouldContinueAfterError = options.shouldContinueAfterError;
3867
- this.parser = new StreamParser({
3879
+ this.canRecoverFromGadgetError = options.canRecoverFromGadgetError;
3880
+ this.parser = new GadgetCallParser({
3868
3881
  startPrefix: options.gadgetStartPrefix,
3869
3882
  endPrefix: options.gadgetEndPrefix,
3870
3883
  argPrefix: options.gadgetArgPrefix
3871
3884
  });
3872
3885
  this.executor = new GadgetExecutor(
3873
3886
  options.registry,
3874
- options.onHumanInputRequired,
3887
+ options.requestHumanInput,
3875
3888
  this.logger.getSubLogger({ name: "executor" }),
3876
3889
  options.defaultGadgetTimeoutMs,
3877
3890
  { argPrefix: options.gadgetArgPrefix },
3878
3891
  options.client,
3879
- options.mediaStore
3892
+ options.mediaStore,
3893
+ options.agentConfig,
3894
+ options.subagentConfig,
3895
+ options.onNestedEvent
3880
3896
  );
3881
3897
  }
3882
3898
  /**
3883
- * Process an LLM stream and return structured results.
3899
+ * Process an LLM stream and yield events in real-time.
3900
+ *
3901
+ * This is an async generator that yields events immediately as they occur:
3902
+ * - Text events are yielded as text is streamed from the LLM
3903
+ * - gadget_call events are yielded immediately when a gadget call is parsed
3904
+ * - gadget_result events are yielded when gadget execution completes
3905
+ *
3906
+ * The final event is always a StreamCompletionEvent containing metadata.
3884
3907
  */
3885
- async process(stream2) {
3886
- const outputs = [];
3908
+ async *process(stream2) {
3887
3909
  let finishReason = null;
3888
3910
  let usage;
3889
3911
  let didExecuteGadgets = false;
@@ -3897,7 +3919,7 @@ var init_stream_processor = __esm({
3897
3919
  if (this.hooks.interceptors?.interceptRawChunk) {
3898
3920
  const context = {
3899
3921
  iteration: this.iteration,
3900
- accumulatedText: this.accumulatedText,
3922
+ accumulatedText: this.responseText,
3901
3923
  logger: this.logger
3902
3924
  };
3903
3925
  const intercepted = this.hooks.interceptors.interceptRawChunk(processedChunk, context);
@@ -3908,7 +3930,7 @@ var init_stream_processor = __esm({
3908
3930
  }
3909
3931
  }
3910
3932
  if (processedChunk) {
3911
- this.accumulatedText += processedChunk;
3933
+ this.responseText += processedChunk;
3912
3934
  }
3913
3935
  }
3914
3936
  if (this.hooks.observers?.onStreamChunk && (processedChunk || chunk.usage)) {
@@ -3917,7 +3939,7 @@ var init_stream_processor = __esm({
3917
3939
  const context = {
3918
3940
  iteration: this.iteration,
3919
3941
  rawChunk: processedChunk,
3920
- accumulatedText: this.accumulatedText,
3942
+ accumulatedText: this.responseText,
3921
3943
  usage,
3922
3944
  logger: this.logger
3923
3945
  };
@@ -3929,67 +3951,66 @@ var init_stream_processor = __esm({
3929
3951
  continue;
3930
3952
  }
3931
3953
  for (const event of this.parser.feed(processedChunk)) {
3932
- const processedEvents = await this.processEvent(event);
3933
- outputs.push(...processedEvents);
3934
- if (processedEvents.some((e) => e.type === "gadget_result")) {
3935
- didExecuteGadgets = true;
3936
- }
3937
- for (const evt of processedEvents) {
3938
- if (evt.type === "gadget_result" && evt.result.breaksLoop) {
3939
- shouldBreakLoop = true;
3954
+ for await (const processedEvent of this.processEventGenerator(event)) {
3955
+ yield processedEvent;
3956
+ if (processedEvent.type === "gadget_result") {
3957
+ didExecuteGadgets = true;
3958
+ if (processedEvent.result.breaksLoop) {
3959
+ shouldBreakLoop = true;
3960
+ }
3940
3961
  }
3941
3962
  }
3942
3963
  }
3943
- if (this.shouldStopExecution) {
3964
+ if (this.executionHalted) {
3944
3965
  this.logger.info("Breaking from LLM stream due to gadget error");
3945
3966
  break;
3946
3967
  }
3947
3968
  }
3948
- if (!this.shouldStopExecution) {
3969
+ if (!this.executionHalted) {
3949
3970
  for (const event of this.parser.finalize()) {
3950
- const processedEvents = await this.processEvent(event);
3951
- outputs.push(...processedEvents);
3952
- if (processedEvents.some((e) => e.type === "gadget_result")) {
3953
- didExecuteGadgets = true;
3954
- }
3955
- for (const evt of processedEvents) {
3956
- if (evt.type === "gadget_result" && evt.result.breaksLoop) {
3957
- shouldBreakLoop = true;
3971
+ for await (const processedEvent of this.processEventGenerator(event)) {
3972
+ yield processedEvent;
3973
+ if (processedEvent.type === "gadget_result") {
3974
+ didExecuteGadgets = true;
3975
+ if (processedEvent.result.breaksLoop) {
3976
+ shouldBreakLoop = true;
3977
+ }
3958
3978
  }
3959
3979
  }
3960
3980
  }
3961
- const finalPendingEvents = await this.processPendingGadgets();
3962
- outputs.push(...finalPendingEvents);
3963
- if (finalPendingEvents.some((e) => e.type === "gadget_result")) {
3964
- didExecuteGadgets = true;
3965
- }
3966
- for (const evt of finalPendingEvents) {
3967
- if (evt.type === "gadget_result" && evt.result.breaksLoop) {
3968
- shouldBreakLoop = true;
3981
+ for await (const evt of this.processPendingGadgetsGenerator()) {
3982
+ yield evt;
3983
+ if (evt.type === "gadget_result") {
3984
+ didExecuteGadgets = true;
3985
+ if (evt.result.breaksLoop) {
3986
+ shouldBreakLoop = true;
3987
+ }
3969
3988
  }
3970
3989
  }
3971
3990
  }
3972
- let finalMessage = this.accumulatedText;
3991
+ let finalMessage = this.responseText;
3973
3992
  if (this.hooks.interceptors?.interceptAssistantMessage) {
3974
3993
  const context = {
3975
3994
  iteration: this.iteration,
3976
- rawResponse: this.accumulatedText,
3995
+ rawResponse: this.responseText,
3977
3996
  logger: this.logger
3978
3997
  };
3979
3998
  finalMessage = this.hooks.interceptors.interceptAssistantMessage(finalMessage, context);
3980
3999
  }
3981
- return {
3982
- outputs,
4000
+ const completionEvent = {
4001
+ type: "stream_complete",
3983
4002
  shouldBreakLoop,
3984
4003
  didExecuteGadgets,
3985
4004
  finishReason,
3986
4005
  usage,
3987
- rawResponse: this.accumulatedText,
4006
+ rawResponse: this.responseText,
3988
4007
  finalMessage
3989
4008
  };
4009
+ yield completionEvent;
3990
4010
  }
3991
4011
  /**
3992
4012
  * Process a single parsed event (text or gadget call).
4013
+ * @deprecated Use processEventGenerator for real-time streaming
3993
4014
  */
3994
4015
  async processEvent(event) {
3995
4016
  if (event.type === "text") {
@@ -3999,6 +4020,23 @@ var init_stream_processor = __esm({
3999
4020
  }
4000
4021
  return [event];
4001
4022
  }
4023
+ /**
4024
+ * Process a single parsed event, yielding events in real-time.
4025
+ * Generator version of processEvent for streaming support.
4026
+ */
4027
+ async *processEventGenerator(event) {
4028
+ if (event.type === "text") {
4029
+ for (const e of await this.processTextEvent(event)) {
4030
+ yield e;
4031
+ }
4032
+ } else if (event.type === "gadget_call") {
4033
+ for await (const e of this.processGadgetCallGenerator(event.call)) {
4034
+ yield e;
4035
+ }
4036
+ } else {
4037
+ yield event;
4038
+ }
4039
+ }
4002
4040
  /**
4003
4041
  * Process a text event through interceptors.
4004
4042
  */
@@ -4007,7 +4045,7 @@ var init_stream_processor = __esm({
4007
4045
  if (this.hooks.interceptors?.interceptTextChunk) {
4008
4046
  const context = {
4009
4047
  iteration: this.iteration,
4010
- accumulatedText: this.accumulatedText,
4048
+ accumulatedText: this.responseText,
4011
4049
  logger: this.logger
4012
4050
  };
4013
4051
  const intercepted = this.hooks.interceptors.interceptTextChunk(content, context);
@@ -4026,7 +4064,7 @@ var init_stream_processor = __esm({
4026
4064
  * After each execution, pending gadgets are checked to see if they can now run.
4027
4065
  */
4028
4066
  async processGadgetCall(call) {
4029
- if (this.shouldStopExecution) {
4067
+ if (this.executionHalted) {
4030
4068
  this.logger.debug("Skipping gadget execution due to previous error", {
4031
4069
  gadgetName: call.gadgetName
4032
4070
  });
@@ -4065,7 +4103,7 @@ var init_stream_processor = __esm({
4065
4103
  invocationId: call.invocationId,
4066
4104
  waitingOn: unsatisfied
4067
4105
  });
4068
- this.pendingGadgets.set(call.invocationId, call);
4106
+ this.gadgetsAwaitingDependencies.set(call.invocationId, call);
4069
4107
  return events;
4070
4108
  }
4071
4109
  }
@@ -4075,9 +4113,68 @@ var init_stream_processor = __esm({
4075
4113
  events.push(...triggeredEvents);
4076
4114
  return events;
4077
4115
  }
4116
+ /**
4117
+ * Process a gadget call, yielding events in real-time.
4118
+ *
4119
+ * Key difference from processGadgetCall: yields gadget_call event IMMEDIATELY
4120
+ * when parsed (before execution), enabling real-time UI feedback.
4121
+ */
4122
+ async *processGadgetCallGenerator(call) {
4123
+ if (this.executionHalted) {
4124
+ this.logger.debug("Skipping gadget execution due to previous error", {
4125
+ gadgetName: call.gadgetName
4126
+ });
4127
+ return;
4128
+ }
4129
+ yield { type: "gadget_call", call };
4130
+ if (call.dependencies.length > 0) {
4131
+ if (call.dependencies.includes(call.invocationId)) {
4132
+ this.logger.warn("Gadget has self-referential dependency (depends on itself)", {
4133
+ gadgetName: call.gadgetName,
4134
+ invocationId: call.invocationId
4135
+ });
4136
+ this.failedInvocations.add(call.invocationId);
4137
+ const skipEvent = {
4138
+ type: "gadget_skipped",
4139
+ gadgetName: call.gadgetName,
4140
+ invocationId: call.invocationId,
4141
+ parameters: call.parameters ?? {},
4142
+ failedDependency: call.invocationId,
4143
+ failedDependencyError: `Gadget "${call.invocationId}" cannot depend on itself (self-referential dependency)`
4144
+ };
4145
+ yield skipEvent;
4146
+ return;
4147
+ }
4148
+ const failedDep = call.dependencies.find((dep) => this.failedInvocations.has(dep));
4149
+ if (failedDep) {
4150
+ const skipEvents = await this.handleFailedDependency(call, failedDep);
4151
+ for (const evt of skipEvents) {
4152
+ yield evt;
4153
+ }
4154
+ return;
4155
+ }
4156
+ const unsatisfied = call.dependencies.filter((dep) => !this.completedResults.has(dep));
4157
+ if (unsatisfied.length > 0) {
4158
+ this.logger.debug("Queueing gadget for later - waiting on dependencies", {
4159
+ gadgetName: call.gadgetName,
4160
+ invocationId: call.invocationId,
4161
+ waitingOn: unsatisfied
4162
+ });
4163
+ this.gadgetsAwaitingDependencies.set(call.invocationId, call);
4164
+ return;
4165
+ }
4166
+ }
4167
+ for await (const evt of this.executeGadgetGenerator(call)) {
4168
+ yield evt;
4169
+ }
4170
+ for await (const evt of this.processPendingGadgetsGenerator()) {
4171
+ yield evt;
4172
+ }
4173
+ }
4078
4174
  /**
4079
4175
  * Execute a gadget through the full hook lifecycle.
4080
4176
  * This is the core execution logic, extracted from processGadgetCall.
4177
+ * @deprecated Use executeGadgetGenerator for real-time streaming
4081
4178
  */
4082
4179
  async executeGadgetWithHooks(call) {
4083
4180
  const events = [];
@@ -4087,14 +4184,14 @@ var init_stream_processor = __esm({
4087
4184
  error: call.parseError,
4088
4185
  rawParameters: call.parametersRaw
4089
4186
  });
4090
- const shouldContinue = await this.checkContinueAfterError(
4187
+ const shouldContinue = await this.checkCanRecoverFromError(
4091
4188
  call.parseError,
4092
4189
  call.gadgetName,
4093
4190
  "parse",
4094
4191
  call.parameters
4095
4192
  );
4096
4193
  if (!shouldContinue) {
4097
- this.shouldStopExecution = true;
4194
+ this.executionHalted = true;
4098
4195
  }
4099
4196
  }
4100
4197
  let parameters = call.parameters ?? {};
@@ -4218,18 +4315,171 @@ var init_stream_processor = __esm({
4218
4315
  events.push({ type: "gadget_result", result });
4219
4316
  if (result.error) {
4220
4317
  const errorType = this.determineErrorType(call, result);
4221
- const shouldContinue = await this.checkContinueAfterError(
4318
+ const shouldContinue = await this.checkCanRecoverFromError(
4222
4319
  result.error,
4223
4320
  result.gadgetName,
4224
4321
  errorType,
4225
4322
  result.parameters
4226
4323
  );
4227
4324
  if (!shouldContinue) {
4228
- this.shouldStopExecution = true;
4325
+ this.executionHalted = true;
4229
4326
  }
4230
4327
  }
4231
4328
  return events;
4232
4329
  }
4330
+ /**
4331
+ * Execute a gadget and yield the result event.
4332
+ * Generator version that yields gadget_result immediately when execution completes.
4333
+ */
4334
+ async *executeGadgetGenerator(call) {
4335
+ if (call.parseError) {
4336
+ this.logger.warn("Gadget has parse error", {
4337
+ gadgetName: call.gadgetName,
4338
+ error: call.parseError,
4339
+ rawParameters: call.parametersRaw
4340
+ });
4341
+ const shouldContinue = await this.checkCanRecoverFromError(
4342
+ call.parseError,
4343
+ call.gadgetName,
4344
+ "parse",
4345
+ call.parameters
4346
+ );
4347
+ if (!shouldContinue) {
4348
+ this.executionHalted = true;
4349
+ }
4350
+ }
4351
+ let parameters = call.parameters ?? {};
4352
+ if (this.hooks.interceptors?.interceptGadgetParameters) {
4353
+ const context = {
4354
+ iteration: this.iteration,
4355
+ gadgetName: call.gadgetName,
4356
+ invocationId: call.invocationId,
4357
+ logger: this.logger
4358
+ };
4359
+ parameters = this.hooks.interceptors.interceptGadgetParameters(parameters, context);
4360
+ }
4361
+ call.parameters = parameters;
4362
+ let shouldSkip = false;
4363
+ let syntheticResult;
4364
+ if (this.hooks.controllers?.beforeGadgetExecution) {
4365
+ const context = {
4366
+ iteration: this.iteration,
4367
+ gadgetName: call.gadgetName,
4368
+ invocationId: call.invocationId,
4369
+ parameters,
4370
+ logger: this.logger
4371
+ };
4372
+ const action = await this.hooks.controllers.beforeGadgetExecution(context);
4373
+ validateBeforeGadgetExecutionAction(action);
4374
+ if (action.action === "skip") {
4375
+ shouldSkip = true;
4376
+ syntheticResult = action.syntheticResult;
4377
+ this.logger.info("Controller skipped gadget execution", {
4378
+ gadgetName: call.gadgetName
4379
+ });
4380
+ }
4381
+ }
4382
+ const startObservers = [];
4383
+ if (this.hooks.observers?.onGadgetExecutionStart) {
4384
+ startObservers.push(async () => {
4385
+ const context = {
4386
+ iteration: this.iteration,
4387
+ gadgetName: call.gadgetName,
4388
+ invocationId: call.invocationId,
4389
+ parameters,
4390
+ logger: this.logger
4391
+ };
4392
+ await this.hooks.observers.onGadgetExecutionStart(context);
4393
+ });
4394
+ }
4395
+ await this.runObserversInParallel(startObservers);
4396
+ let result;
4397
+ if (shouldSkip) {
4398
+ result = {
4399
+ gadgetName: call.gadgetName,
4400
+ invocationId: call.invocationId,
4401
+ parameters,
4402
+ result: syntheticResult ?? "Execution skipped",
4403
+ executionTimeMs: 0
4404
+ };
4405
+ } else {
4406
+ result = await this.executor.execute(call);
4407
+ }
4408
+ const originalResult = result.result;
4409
+ if (result.result && this.hooks.interceptors?.interceptGadgetResult) {
4410
+ const context = {
4411
+ iteration: this.iteration,
4412
+ gadgetName: result.gadgetName,
4413
+ invocationId: result.invocationId,
4414
+ parameters,
4415
+ executionTimeMs: result.executionTimeMs,
4416
+ logger: this.logger
4417
+ };
4418
+ result.result = this.hooks.interceptors.interceptGadgetResult(result.result, context);
4419
+ }
4420
+ if (this.hooks.controllers?.afterGadgetExecution) {
4421
+ const context = {
4422
+ iteration: this.iteration,
4423
+ gadgetName: result.gadgetName,
4424
+ invocationId: result.invocationId,
4425
+ parameters,
4426
+ result: result.result,
4427
+ error: result.error,
4428
+ executionTimeMs: result.executionTimeMs,
4429
+ logger: this.logger
4430
+ };
4431
+ const action = await this.hooks.controllers.afterGadgetExecution(context);
4432
+ validateAfterGadgetExecutionAction(action);
4433
+ if (action.action === "recover" && result.error) {
4434
+ this.logger.info("Controller recovered from gadget error", {
4435
+ gadgetName: result.gadgetName,
4436
+ originalError: result.error
4437
+ });
4438
+ result = {
4439
+ ...result,
4440
+ error: void 0,
4441
+ result: action.fallbackResult
4442
+ };
4443
+ }
4444
+ }
4445
+ const completeObservers = [];
4446
+ if (this.hooks.observers?.onGadgetExecutionComplete) {
4447
+ completeObservers.push(async () => {
4448
+ const context = {
4449
+ iteration: this.iteration,
4450
+ gadgetName: result.gadgetName,
4451
+ invocationId: result.invocationId,
4452
+ parameters,
4453
+ originalResult,
4454
+ finalResult: result.result,
4455
+ error: result.error,
4456
+ executionTimeMs: result.executionTimeMs,
4457
+ breaksLoop: result.breaksLoop,
4458
+ cost: result.cost,
4459
+ logger: this.logger
4460
+ };
4461
+ await this.hooks.observers.onGadgetExecutionComplete(context);
4462
+ });
4463
+ }
4464
+ await this.runObserversInParallel(completeObservers);
4465
+ this.completedResults.set(result.invocationId, result);
4466
+ if (result.error) {
4467
+ this.failedInvocations.add(result.invocationId);
4468
+ }
4469
+ yield { type: "gadget_result", result };
4470
+ if (result.error) {
4471
+ const errorType = this.determineErrorType(call, result);
4472
+ const shouldContinue = await this.checkCanRecoverFromError(
4473
+ result.error,
4474
+ result.gadgetName,
4475
+ errorType,
4476
+ result.parameters
4477
+ );
4478
+ if (!shouldContinue) {
4479
+ this.executionHalted = true;
4480
+ }
4481
+ }
4482
+ }
4233
4483
  /**
4234
4484
  * Handle a gadget that cannot execute because a dependency failed.
4235
4485
  * Calls the onDependencySkipped controller to allow customization.
@@ -4312,11 +4562,11 @@ var init_stream_processor = __esm({
4312
4562
  async processPendingGadgets() {
4313
4563
  const events = [];
4314
4564
  let progress = true;
4315
- while (progress && this.pendingGadgets.size > 0) {
4565
+ while (progress && this.gadgetsAwaitingDependencies.size > 0) {
4316
4566
  progress = false;
4317
4567
  const readyToExecute = [];
4318
4568
  const readyToSkip = [];
4319
- for (const [invocationId, call] of this.pendingGadgets) {
4569
+ for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
4320
4570
  const failedDep = call.dependencies.find((dep) => this.failedInvocations.has(dep));
4321
4571
  if (failedDep) {
4322
4572
  readyToSkip.push({ call, failedDep });
@@ -4328,7 +4578,7 @@ var init_stream_processor = __esm({
4328
4578
  }
4329
4579
  }
4330
4580
  for (const { call, failedDep } of readyToSkip) {
4331
- this.pendingGadgets.delete(call.invocationId);
4581
+ this.gadgetsAwaitingDependencies.delete(call.invocationId);
4332
4582
  const skipEvents = await this.handleFailedDependency(call, failedDep);
4333
4583
  events.push(...skipEvents);
4334
4584
  progress = true;
@@ -4339,7 +4589,7 @@ var init_stream_processor = __esm({
4339
4589
  invocationIds: readyToExecute.map((c) => c.invocationId)
4340
4590
  });
4341
4591
  for (const call of readyToExecute) {
4342
- this.pendingGadgets.delete(call.invocationId);
4592
+ this.gadgetsAwaitingDependencies.delete(call.invocationId);
4343
4593
  }
4344
4594
  const executePromises = readyToExecute.map((call) => this.executeGadgetWithHooks(call));
4345
4595
  const results = await Promise.all(executePromises);
@@ -4349,9 +4599,9 @@ var init_stream_processor = __esm({
4349
4599
  progress = true;
4350
4600
  }
4351
4601
  }
4352
- if (this.pendingGadgets.size > 0) {
4353
- const pendingIds = new Set(this.pendingGadgets.keys());
4354
- for (const [invocationId, call] of this.pendingGadgets) {
4602
+ if (this.gadgetsAwaitingDependencies.size > 0) {
4603
+ const pendingIds = new Set(this.gadgetsAwaitingDependencies.keys());
4604
+ for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
4355
4605
  const missingDeps = call.dependencies.filter((dep) => !this.completedResults.has(dep));
4356
4606
  const circularDeps = missingDeps.filter((dep) => pendingIds.has(dep));
4357
4607
  const trulyMissingDeps = missingDeps.filter((dep) => !pendingIds.has(dep));
@@ -4382,10 +4632,103 @@ var init_stream_processor = __esm({
4382
4632
  };
4383
4633
  events.push(skipEvent);
4384
4634
  }
4385
- this.pendingGadgets.clear();
4635
+ this.gadgetsAwaitingDependencies.clear();
4386
4636
  }
4387
4637
  return events;
4388
4638
  }
4639
+ /**
4640
+ * Process pending gadgets, yielding events in real-time.
4641
+ * Generator version that yields events as gadgets complete.
4642
+ *
4643
+ * Note: Gadgets are still executed in parallel for efficiency,
4644
+ * but results are yielded as they become available.
4645
+ */
4646
+ async *processPendingGadgetsGenerator() {
4647
+ let progress = true;
4648
+ while (progress && this.gadgetsAwaitingDependencies.size > 0) {
4649
+ progress = false;
4650
+ const readyToExecute = [];
4651
+ const readyToSkip = [];
4652
+ for (const [_invocationId, call] of this.gadgetsAwaitingDependencies) {
4653
+ const failedDep = call.dependencies.find((dep) => this.failedInvocations.has(dep));
4654
+ if (failedDep) {
4655
+ readyToSkip.push({ call, failedDep });
4656
+ continue;
4657
+ }
4658
+ const allSatisfied = call.dependencies.every((dep) => this.completedResults.has(dep));
4659
+ if (allSatisfied) {
4660
+ readyToExecute.push(call);
4661
+ }
4662
+ }
4663
+ for (const { call, failedDep } of readyToSkip) {
4664
+ this.gadgetsAwaitingDependencies.delete(call.invocationId);
4665
+ const skipEvents = await this.handleFailedDependency(call, failedDep);
4666
+ for (const evt of skipEvents) {
4667
+ yield evt;
4668
+ }
4669
+ progress = true;
4670
+ }
4671
+ if (readyToExecute.length > 0) {
4672
+ this.logger.debug("Executing ready gadgets in parallel", {
4673
+ count: readyToExecute.length,
4674
+ invocationIds: readyToExecute.map((c) => c.invocationId)
4675
+ });
4676
+ for (const call of readyToExecute) {
4677
+ this.gadgetsAwaitingDependencies.delete(call.invocationId);
4678
+ }
4679
+ const eventSets = await Promise.all(
4680
+ readyToExecute.map(async (call) => {
4681
+ const events = [];
4682
+ for await (const evt of this.executeGadgetGenerator(call)) {
4683
+ events.push(evt);
4684
+ }
4685
+ return events;
4686
+ })
4687
+ );
4688
+ for (const events of eventSets) {
4689
+ for (const evt of events) {
4690
+ yield evt;
4691
+ }
4692
+ }
4693
+ progress = true;
4694
+ }
4695
+ }
4696
+ if (this.gadgetsAwaitingDependencies.size > 0) {
4697
+ const pendingIds = new Set(this.gadgetsAwaitingDependencies.keys());
4698
+ for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
4699
+ const missingDeps = call.dependencies.filter((dep) => !this.completedResults.has(dep));
4700
+ const circularDeps = missingDeps.filter((dep) => pendingIds.has(dep));
4701
+ const trulyMissingDeps = missingDeps.filter((dep) => !pendingIds.has(dep));
4702
+ let errorMessage;
4703
+ let logLevel = "warn";
4704
+ if (circularDeps.length > 0 && trulyMissingDeps.length > 0) {
4705
+ errorMessage = `Dependencies unresolvable: circular=[${circularDeps.join(", ")}], missing=[${trulyMissingDeps.join(", ")}]`;
4706
+ logLevel = "error";
4707
+ } else if (circularDeps.length > 0) {
4708
+ errorMessage = `Circular dependency detected: "${invocationId}" depends on "${circularDeps[0]}" which also depends on "${invocationId}" (directly or indirectly)`;
4709
+ } else {
4710
+ errorMessage = `Dependency "${missingDeps[0]}" was never executed - check that the invocation ID exists and is spelled correctly`;
4711
+ }
4712
+ this.logger[logLevel]("Gadget has unresolvable dependencies", {
4713
+ gadgetName: call.gadgetName,
4714
+ invocationId,
4715
+ circularDependencies: circularDeps,
4716
+ missingDependencies: trulyMissingDeps
4717
+ });
4718
+ this.failedInvocations.add(invocationId);
4719
+ const skipEvent = {
4720
+ type: "gadget_skipped",
4721
+ gadgetName: call.gadgetName,
4722
+ invocationId,
4723
+ parameters: call.parameters ?? {},
4724
+ failedDependency: missingDeps[0],
4725
+ failedDependencyError: errorMessage
4726
+ };
4727
+ yield skipEvent;
4728
+ }
4729
+ this.gadgetsAwaitingDependencies.clear();
4730
+ }
4731
+ }
4389
4732
  /**
4390
4733
  * Safely execute an observer, catching and logging any errors.
4391
4734
  * Observers are non-critical, so errors are logged but don't crash the system.
@@ -4412,19 +4755,19 @@ var init_stream_processor = __esm({
4412
4755
  );
4413
4756
  }
4414
4757
  /**
4415
- * Check if execution should continue after an error.
4758
+ * Check if execution can recover from an error.
4416
4759
  *
4417
4760
  * Returns true if we should continue processing subsequent gadgets, false if we should stop.
4418
4761
  *
4419
4762
  * Logic:
4420
- * - If custom shouldContinueAfterError is provided, use it
4763
+ * - If custom canRecoverFromGadgetError is provided, use it
4421
4764
  * - Otherwise, use stopOnGadgetError config:
4422
4765
  * - stopOnGadgetError=true → return false (stop execution)
4423
4766
  * - stopOnGadgetError=false → return true (continue execution)
4424
4767
  */
4425
- async checkContinueAfterError(error, gadgetName, errorType, parameters) {
4426
- if (this.shouldContinueAfterError) {
4427
- return await this.shouldContinueAfterError({
4768
+ async checkCanRecoverFromError(error, gadgetName, errorType, parameters) {
4769
+ if (this.canRecoverFromGadgetError) {
4770
+ return await this.canRecoverFromGadgetError({
4428
4771
  error,
4429
4772
  gadgetName,
4430
4773
  errorType,
@@ -4487,14 +4830,14 @@ var init_agent = __esm({
4487
4830
  gadgetStartPrefix;
4488
4831
  gadgetEndPrefix;
4489
4832
  gadgetArgPrefix;
4490
- onHumanInputRequired;
4833
+ requestHumanInput;
4491
4834
  textOnlyHandler;
4492
4835
  textWithGadgetsHandler;
4493
4836
  stopOnGadgetError;
4494
- shouldContinueAfterError;
4837
+ canRecoverFromGadgetError;
4495
4838
  defaultGadgetTimeoutMs;
4496
4839
  defaultMaxTokens;
4497
- userPromptProvided;
4840
+ hasUserPrompt;
4498
4841
  // Gadget output limiting
4499
4842
  outputStore;
4500
4843
  outputLimitEnabled;
@@ -4505,6 +4848,11 @@ var init_agent = __esm({
4505
4848
  mediaStore;
4506
4849
  // Cancellation
4507
4850
  signal;
4851
+ // Subagent configuration
4852
+ agentContextConfig;
4853
+ subagentConfig;
4854
+ // Nested event callback for subagent gadgets
4855
+ onNestedEvent;
4508
4856
  /**
4509
4857
  * Creates a new Agent instance.
4510
4858
  * @internal This constructor is private. Use LLMist.createAgent() or AgentBuilder instead.
@@ -4524,11 +4872,11 @@ var init_agent = __esm({
4524
4872
  this.gadgetStartPrefix = options.gadgetStartPrefix;
4525
4873
  this.gadgetEndPrefix = options.gadgetEndPrefix;
4526
4874
  this.gadgetArgPrefix = options.gadgetArgPrefix;
4527
- this.onHumanInputRequired = options.onHumanInputRequired;
4875
+ this.requestHumanInput = options.requestHumanInput;
4528
4876
  this.textOnlyHandler = options.textOnlyHandler ?? "terminate";
4529
4877
  this.textWithGadgetsHandler = options.textWithGadgetsHandler;
4530
4878
  this.stopOnGadgetError = options.stopOnGadgetError ?? true;
4531
- this.shouldContinueAfterError = options.shouldContinueAfterError;
4879
+ this.canRecoverFromGadgetError = options.canRecoverFromGadgetError;
4532
4880
  this.defaultGadgetTimeoutMs = options.defaultGadgetTimeoutMs;
4533
4881
  this.defaultMaxTokens = this.resolveMaxTokensFromCatalog(options.model);
4534
4882
  this.outputLimitEnabled = options.gadgetOutputLimit ?? DEFAULT_GADGET_OUTPUT_LIMIT;
@@ -4544,7 +4892,7 @@ var init_agent = __esm({
4544
4892
  createGadgetOutputViewer(this.outputStore, this.outputLimitCharLimit)
4545
4893
  );
4546
4894
  }
4547
- this.hooks = this.mergeOutputLimiterHook(options.hooks);
4895
+ this.hooks = this.chainOutputLimiterWithUserHooks(options.hooks);
4548
4896
  const baseBuilder = new LLMMessageBuilder(options.promptConfig);
4549
4897
  if (options.systemPrompt) {
4550
4898
  baseBuilder.addSystem(options.systemPrompt);
@@ -4564,7 +4912,7 @@ var init_agent = __esm({
4564
4912
  endPrefix: options.gadgetEndPrefix,
4565
4913
  argPrefix: options.gadgetArgPrefix
4566
4914
  });
4567
- this.userPromptProvided = !!options.userPrompt;
4915
+ this.hasUserPrompt = !!options.userPrompt;
4568
4916
  if (options.userPrompt) {
4569
4917
  this.conversation.addUserMessage(options.userPrompt);
4570
4918
  }
@@ -4577,6 +4925,12 @@ var init_agent = __esm({
4577
4925
  );
4578
4926
  }
4579
4927
  this.signal = options.signal;
4928
+ this.agentContextConfig = {
4929
+ model: this.model,
4930
+ temperature: this.temperature
4931
+ };
4932
+ this.subagentConfig = options.subagentConfig;
4933
+ this.onNestedEvent = options.onNestedEvent;
4580
4934
  }
4581
4935
  /**
4582
4936
  * Get the gadget registry for this agent.
@@ -4683,7 +5037,7 @@ var init_agent = __esm({
4683
5037
  * @throws {Error} If no user prompt was provided (when using build() without ask())
4684
5038
  */
4685
5039
  async *run() {
4686
- if (!this.userPromptProvided) {
5040
+ if (!this.hasUserPrompt) {
4687
5041
  throw new Error(
4688
5042
  "No user prompt provided. Use .ask(prompt) instead of .build(), or call agent.run() after providing a prompt."
4689
5043
  );
@@ -4800,17 +5154,37 @@ var init_agent = __esm({
4800
5154
  gadgetArgPrefix: this.gadgetArgPrefix,
4801
5155
  hooks: this.hooks,
4802
5156
  logger: this.logger.getSubLogger({ name: "stream-processor" }),
4803
- onHumanInputRequired: this.onHumanInputRequired,
5157
+ requestHumanInput: this.requestHumanInput,
4804
5158
  stopOnGadgetError: this.stopOnGadgetError,
4805
- shouldContinueAfterError: this.shouldContinueAfterError,
5159
+ canRecoverFromGadgetError: this.canRecoverFromGadgetError,
4806
5160
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
4807
5161
  client: this.client,
4808
- mediaStore: this.mediaStore
5162
+ mediaStore: this.mediaStore,
5163
+ agentConfig: this.agentContextConfig,
5164
+ subagentConfig: this.subagentConfig,
5165
+ onNestedEvent: this.onNestedEvent
4809
5166
  });
4810
- const result = await processor.process(stream2);
4811
- for (const output of result.outputs) {
4812
- yield output;
5167
+ let streamMetadata = null;
5168
+ let gadgetCallCount = 0;
5169
+ const textOutputs = [];
5170
+ const gadgetResults = [];
5171
+ for await (const event of processor.process(stream2)) {
5172
+ if (event.type === "stream_complete") {
5173
+ streamMetadata = event;
5174
+ continue;
5175
+ }
5176
+ if (event.type === "text") {
5177
+ textOutputs.push(event.content);
5178
+ } else if (event.type === "gadget_result") {
5179
+ gadgetCallCount++;
5180
+ gadgetResults.push(event);
5181
+ }
5182
+ yield event;
4813
5183
  }
5184
+ if (!streamMetadata) {
5185
+ throw new Error("Stream processing completed without metadata event");
5186
+ }
5187
+ const result = streamMetadata;
4814
5188
  this.logger.info("LLM response completed", {
4815
5189
  finishReason: result.finishReason,
4816
5190
  usage: result.usage,
@@ -4835,9 +5209,6 @@ var init_agent = __esm({
4835
5209
  });
4836
5210
  let finalMessage = result.finalMessage;
4837
5211
  if (this.hooks.controllers?.afterLLMCall) {
4838
- const gadgetCallCount = result.outputs.filter(
4839
- (output) => output.type === "gadget_result"
4840
- ).length;
4841
5212
  const context = {
4842
5213
  iteration: currentIteration,
4843
5214
  maxIterations: this.maxIterations,
@@ -4858,31 +5229,29 @@ var init_agent = __esm({
4858
5229
  if (msg.role === "user") {
4859
5230
  this.conversation.addUserMessage(msg.content);
4860
5231
  } else if (msg.role === "assistant") {
4861
- this.conversation.addAssistantMessage(extractText(msg.content));
5232
+ this.conversation.addAssistantMessage(extractMessageText(msg.content));
4862
5233
  } else if (msg.role === "system") {
4863
- this.conversation.addUserMessage(`[System] ${extractText(msg.content)}`);
5234
+ this.conversation.addUserMessage(`[System] ${extractMessageText(msg.content)}`);
4864
5235
  }
4865
5236
  }
4866
5237
  }
4867
5238
  }
4868
5239
  if (result.didExecuteGadgets) {
4869
5240
  if (this.textWithGadgetsHandler) {
4870
- const textContent = result.outputs.filter(
4871
- (output) => output.type === "text"
4872
- ).map((output) => output.content).join("");
5241
+ const textContent = textOutputs.join("");
4873
5242
  if (textContent.trim()) {
4874
5243
  const { gadgetName, parameterMapping, resultMapping } = this.textWithGadgetsHandler;
4875
- this.conversation.addGadgetCall(
5244
+ this.conversation.addGadgetCallResult(
4876
5245
  gadgetName,
4877
5246
  parameterMapping(textContent),
4878
5247
  resultMapping ? resultMapping(textContent) : textContent
4879
5248
  );
4880
5249
  }
4881
5250
  }
4882
- for (const output of result.outputs) {
5251
+ for (const output of gadgetResults) {
4883
5252
  if (output.type === "gadget_result") {
4884
5253
  const gadgetResult = output.result;
4885
- this.conversation.addGadgetCall(
5254
+ this.conversation.addGadgetCallResult(
4886
5255
  gadgetResult.gadgetName,
4887
5256
  gadgetResult.parameters,
4888
5257
  gadgetResult.error ?? gadgetResult.result ?? "",
@@ -4893,7 +5262,7 @@ var init_agent = __esm({
4893
5262
  }
4894
5263
  } else {
4895
5264
  if (finalMessage.trim()) {
4896
- this.conversation.addGadgetCall(
5265
+ this.conversation.addGadgetCallResult(
4897
5266
  "TellUser",
4898
5267
  { message: finalMessage, done: false, type: "info" },
4899
5268
  `\u2139\uFE0F ${finalMessage}`
@@ -5019,10 +5388,10 @@ var init_agent = __esm({
5019
5388
  return this.client.modelRegistry.getModelLimits(unprefixedModelId)?.maxOutputTokens;
5020
5389
  }
5021
5390
  /**
5022
- * Merge the output limiter interceptor into user-provided hooks.
5391
+ * Chain the output limiter interceptor with user-provided hooks.
5023
5392
  * The limiter runs first, then chains to any user interceptor.
5024
5393
  */
5025
- mergeOutputLimiterHook(userHooks) {
5394
+ chainOutputLimiterWithUserHooks(userHooks) {
5026
5395
  if (!this.outputLimitEnabled) {
5027
5396
  return userHooks ?? {};
5028
5397
  }
@@ -5376,9 +5745,9 @@ var init_base_provider = __esm({
5376
5745
  */
5377
5746
  async *stream(options, descriptor, spec) {
5378
5747
  const preparedMessages = this.prepareMessages(options.messages);
5379
- const payload = this.buildRequestPayload(options, descriptor, spec, preparedMessages);
5748
+ const payload = this.buildApiRequest(options, descriptor, spec, preparedMessages);
5380
5749
  const rawStream = await this.executeStreamRequest(payload, options.signal);
5381
- yield* this.wrapStream(rawStream);
5750
+ yield* this.normalizeProviderStream(rawStream);
5382
5751
  }
5383
5752
  /**
5384
5753
  * Prepare messages for the request.
@@ -5478,11 +5847,11 @@ var init_anthropic = __esm({
5478
5847
  "Anthropic does not support speech generation. Use OpenAI (TTS) or Google Gemini (TTS) instead."
5479
5848
  );
5480
5849
  }
5481
- buildRequestPayload(options, descriptor, spec, messages) {
5850
+ buildApiRequest(options, descriptor, spec, messages) {
5482
5851
  const systemMessages = messages.filter((message) => message.role === "system");
5483
5852
  const system = systemMessages.length > 0 ? systemMessages.map((m, index) => ({
5484
5853
  type: "text",
5485
- text: extractText(m.content),
5854
+ text: extractMessageText(m.content),
5486
5855
  // Add cache_control to the LAST system message block
5487
5856
  ...index === systemMessages.length - 1 ? { cache_control: { type: "ephemeral" } } : {}
5488
5857
  })) : void 0;
@@ -5519,7 +5888,7 @@ var init_anthropic = __esm({
5519
5888
  * Handles text, images (base64 only), and applies cache_control.
5520
5889
  */
5521
5890
  convertToAnthropicContent(content, addCacheControl) {
5522
- const parts = normalizeContent(content);
5891
+ const parts = normalizeMessageContent(content);
5523
5892
  return parts.map((part, index) => {
5524
5893
  const isLastPart = index === parts.length - 1;
5525
5894
  const cacheControl = addCacheControl && isLastPart ? { cache_control: { type: "ephemeral" } } : {};
@@ -5565,7 +5934,7 @@ var init_anthropic = __esm({
5565
5934
  const stream2 = await client.messages.create(payload, signal ? { signal } : void 0);
5566
5935
  return stream2;
5567
5936
  }
5568
- async *wrapStream(iterable) {
5937
+ async *normalizeProviderStream(iterable) {
5569
5938
  const stream2 = iterable;
5570
5939
  let inputTokens = 0;
5571
5940
  let cachedInputTokens = 0;
@@ -5642,7 +6011,7 @@ var init_anthropic = __esm({
5642
6011
  async countTokens(messages, descriptor, _spec) {
5643
6012
  const client = this.client;
5644
6013
  const systemMessages = messages.filter((message) => message.role === "system");
5645
- const system = systemMessages.length > 0 ? systemMessages.map((m) => extractText(m.content)).join("\n\n") : void 0;
6014
+ const system = systemMessages.length > 0 ? systemMessages.map((m) => extractMessageText(m.content)).join("\n\n") : void 0;
5646
6015
  const conversation = messages.filter(
5647
6016
  (message) => message.role !== "system"
5648
6017
  ).map((message) => ({
@@ -5664,7 +6033,7 @@ var init_anthropic = __esm({
5664
6033
  let totalChars = 0;
5665
6034
  let imageCount = 0;
5666
6035
  for (const msg of messages) {
5667
- const parts = normalizeContent(msg.content);
6036
+ const parts = normalizeMessageContent(msg.content);
5668
6037
  for (const part of parts) {
5669
6038
  if (part.type === "text") {
5670
6039
  totalChars += part.text.length;
@@ -6355,7 +6724,7 @@ var init_gemini = __esm({
6355
6724
  format: spec?.defaultFormat ?? "wav"
6356
6725
  };
6357
6726
  }
6358
- buildRequestPayload(options, descriptor, _spec, messages) {
6727
+ buildApiRequest(options, descriptor, _spec, messages) {
6359
6728
  const contents = this.convertMessagesToContents(messages);
6360
6729
  const generationConfig = this.buildGenerationConfig(options);
6361
6730
  const config = {
@@ -6407,7 +6776,7 @@ var init_gemini = __esm({
6407
6776
  if (message.role === "system") {
6408
6777
  expandedMessages.push({
6409
6778
  role: "user",
6410
- content: extractText(message.content)
6779
+ content: extractMessageText(message.content)
6411
6780
  });
6412
6781
  expandedMessages.push({
6413
6782
  role: "assistant",
@@ -6457,7 +6826,7 @@ var init_gemini = __esm({
6457
6826
  * Handles text, images, and audio (Gemini supports all three).
6458
6827
  */
6459
6828
  convertToGeminiParts(content) {
6460
- const parts = normalizeContent(content);
6829
+ const parts = normalizeMessageContent(content);
6461
6830
  return parts.map((part) => {
6462
6831
  if (part.type === "text") {
6463
6832
  return { text: part.text };
@@ -6502,10 +6871,10 @@ var init_gemini = __esm({
6502
6871
  }
6503
6872
  return Object.keys(config).length > 0 ? config : null;
6504
6873
  }
6505
- async *wrapStream(iterable) {
6874
+ async *normalizeProviderStream(iterable) {
6506
6875
  const stream2 = iterable;
6507
6876
  for await (const chunk of stream2) {
6508
- const text3 = this.extractText(chunk);
6877
+ const text3 = this.extractMessageText(chunk);
6509
6878
  if (text3) {
6510
6879
  yield { text: text3, rawEvent: chunk };
6511
6880
  }
@@ -6516,7 +6885,7 @@ var init_gemini = __esm({
6516
6885
  }
6517
6886
  }
6518
6887
  }
6519
- extractText(chunk) {
6888
+ extractMessageText(chunk) {
6520
6889
  if (!chunk?.candidates) {
6521
6890
  return "";
6522
6891
  }
@@ -6581,7 +6950,7 @@ var init_gemini = __esm({
6581
6950
  let totalChars = 0;
6582
6951
  let mediaCount = 0;
6583
6952
  for (const msg of messages) {
6584
- const parts = normalizeContent(msg.content);
6953
+ const parts = normalizeMessageContent(msg.content);
6585
6954
  for (const part of parts) {
6586
6955
  if (part.type === "text") {
6587
6956
  totalChars += part.text.length;
@@ -7327,7 +7696,7 @@ var init_openai = __esm({
7327
7696
  format
7328
7697
  };
7329
7698
  }
7330
- buildRequestPayload(options, descriptor, spec, messages) {
7699
+ buildApiRequest(options, descriptor, spec, messages) {
7331
7700
  const { maxTokens, temperature, topP, stopSequences, extra } = options;
7332
7701
  const supportsTemperature = spec?.metadata?.supportsTemperature !== false;
7333
7702
  const shouldIncludeTemperature = typeof temperature === "number" && supportsTemperature;
@@ -7362,7 +7731,7 @@ var init_openai = __esm({
7362
7731
  ...message.name ? { name: message.name } : {}
7363
7732
  };
7364
7733
  }
7365
- const textContent = typeof message.content === "string" ? message.content : extractText(message.content);
7734
+ const textContent = typeof message.content === "string" ? message.content : extractMessageText(message.content);
7366
7735
  if (role === "system") {
7367
7736
  return {
7368
7737
  role: "system",
@@ -7422,7 +7791,7 @@ var init_openai = __esm({
7422
7791
  const stream2 = await client.chat.completions.create(payload, signal ? { signal } : void 0);
7423
7792
  return stream2;
7424
7793
  }
7425
- async *wrapStream(iterable) {
7794
+ async *normalizeProviderStream(iterable) {
7426
7795
  const stream2 = iterable;
7427
7796
  for await (const chunk of stream2) {
7428
7797
  const text3 = chunk.choices.map((choice) => choice.delta?.content ?? "").join("");
@@ -7480,9 +7849,9 @@ var init_openai = __esm({
7480
7849
  tokenCount += OPENAI_MESSAGE_OVERHEAD_TOKENS;
7481
7850
  const roleText = ROLE_MAP[message.role];
7482
7851
  tokenCount += encoding.encode(roleText).length;
7483
- const textContent = extractText(message.content);
7852
+ const textContent = extractMessageText(message.content);
7484
7853
  tokenCount += encoding.encode(textContent).length;
7485
- const parts = normalizeContent(message.content);
7854
+ const parts = normalizeMessageContent(message.content);
7486
7855
  for (const part of parts) {
7487
7856
  if (part.type === "image") {
7488
7857
  imageCount++;
@@ -7507,7 +7876,7 @@ var init_openai = __esm({
7507
7876
  let totalChars = 0;
7508
7877
  let imageCount = 0;
7509
7878
  for (const msg of messages) {
7510
- const parts = normalizeContent(msg.content);
7879
+ const parts = normalizeMessageContent(msg.content);
7511
7880
  for (const part of parts) {
7512
7881
  if (part.type === "text") {
7513
7882
  totalChars += part.text.length;
@@ -8372,20 +8741,23 @@ var init_builder = __esm({
8372
8741
  promptConfig;
8373
8742
  gadgets = [];
8374
8743
  initialMessages = [];
8375
- onHumanInputRequired;
8744
+ requestHumanInput;
8376
8745
  gadgetStartPrefix;
8377
8746
  gadgetEndPrefix;
8378
8747
  gadgetArgPrefix;
8379
8748
  textOnlyHandler;
8380
8749
  textWithGadgetsHandler;
8381
8750
  stopOnGadgetError;
8382
- shouldContinueAfterError;
8751
+ canRecoverFromGadgetError;
8383
8752
  defaultGadgetTimeoutMs;
8384
8753
  gadgetOutputLimit;
8385
8754
  gadgetOutputLimitPercent;
8386
8755
  compactionConfig;
8387
8756
  signal;
8388
8757
  trailingMessage;
8758
+ subagentConfig;
8759
+ nestedEventCallback;
8760
+ parentContext;
8389
8761
  constructor(client) {
8390
8762
  this.client = client;
8391
8763
  }
@@ -8476,13 +8848,13 @@ var init_builder = __esm({
8476
8848
  *
8477
8849
  * @example
8478
8850
  * ```typescript
8479
- * .withPromptConfig({
8851
+ * .withPromptTemplateConfig({
8480
8852
  * mainInstruction: "Use the gadget markers below:",
8481
8853
  * rules: ["Always use markers", "Never use function calling"]
8482
8854
  * })
8483
8855
  * ```
8484
8856
  */
8485
- withPromptConfig(config) {
8857
+ withPromptTemplateConfig(config) {
8486
8858
  this.promptConfig = config;
8487
8859
  return this;
8488
8860
  }
@@ -8562,7 +8934,7 @@ var init_builder = __esm({
8562
8934
  * ```
8563
8935
  */
8564
8936
  onHumanInput(handler) {
8565
- this.onHumanInputRequired = handler;
8937
+ this.requestHumanInput = handler;
8566
8938
  return this;
8567
8939
  }
8568
8940
  /**
@@ -8697,9 +9069,9 @@ var init_builder = __esm({
8697
9069
  * Provides fine-grained control over whether to continue after different types of errors.
8698
9070
  * Overrides `stopOnGadgetError` when provided.
8699
9071
  *
8700
- * **Note:** This builder method configures the underlying `shouldContinueAfterError` option
9072
+ * **Note:** This builder method configures the underlying `canRecoverFromGadgetError` option
8701
9073
  * in `AgentOptions`. The method is named `withErrorHandler` for better developer experience,
8702
- * but maps to the `shouldContinueAfterError` property internally.
9074
+ * but maps to the `canRecoverFromGadgetError` property internally.
8703
9075
  *
8704
9076
  * @param handler - Function that decides whether to continue after an error.
8705
9077
  * Return `true` to continue execution, `false` to stop.
@@ -8720,7 +9092,7 @@ var init_builder = __esm({
8720
9092
  * ```
8721
9093
  */
8722
9094
  withErrorHandler(handler) {
8723
- this.shouldContinueAfterError = handler;
9095
+ this.canRecoverFromGadgetError = handler;
8724
9096
  return this;
8725
9097
  }
8726
9098
  /**
@@ -8857,8 +9229,97 @@ var init_builder = __esm({
8857
9229
  * document.getElementById("cancel").onclick = () => controller.abort();
8858
9230
  * ```
8859
9231
  */
8860
- withSignal(signal) {
8861
- this.signal = signal;
9232
+ withSignal(signal) {
9233
+ this.signal = signal;
9234
+ return this;
9235
+ }
9236
+ /**
9237
+ * Set subagent configuration overrides.
9238
+ *
9239
+ * Subagent gadgets (like BrowseWeb) can read these settings from ExecutionContext
9240
+ * to inherit model and other options from the CLI configuration.
9241
+ *
9242
+ * @param config - Subagent configuration map keyed by gadget name
9243
+ * @returns This builder for chaining
9244
+ *
9245
+ * @example
9246
+ * ```typescript
9247
+ * .withSubagentConfig({
9248
+ * BrowseWeb: { model: "inherit", maxIterations: 20, headless: true },
9249
+ * CodeAnalyzer: { model: "sonnet", maxIterations: 10 }
9250
+ * })
9251
+ * ```
9252
+ */
9253
+ withSubagentConfig(config) {
9254
+ this.subagentConfig = config;
9255
+ return this;
9256
+ }
9257
+ /**
9258
+ * Set the callback for nested subagent events.
9259
+ *
9260
+ * Subagent gadgets (like BrowseWeb) can use ExecutionContext.onNestedEvent
9261
+ * to report their internal LLM calls and gadget executions in real-time.
9262
+ * This callback receives those events, enabling hierarchical progress display.
9263
+ *
9264
+ * @param callback - Function to handle nested agent events
9265
+ * @returns This builder for chaining
9266
+ *
9267
+ * @example
9268
+ * ```typescript
9269
+ * .withNestedEventCallback((event) => {
9270
+ * if (event.type === "llm_call_start") {
9271
+ * console.log(` Nested LLM #${event.event.iteration} starting...`);
9272
+ * } else if (event.type === "gadget_call") {
9273
+ * console.log(` ⏵ ${event.event.call.gadgetName}...`);
9274
+ * }
9275
+ * })
9276
+ * ```
9277
+ */
9278
+ withNestedEventCallback(callback) {
9279
+ this.nestedEventCallback = callback;
9280
+ return this;
9281
+ }
9282
+ /**
9283
+ * Enable automatic nested event forwarding to parent agent.
9284
+ *
9285
+ * When building a subagent inside a gadget, call this method to automatically
9286
+ * forward all LLM calls and gadget events to the parent agent. This enables
9287
+ * hierarchical progress display without any manual event handling.
9288
+ *
9289
+ * The method extracts `invocationId` and `onNestedEvent` from the execution
9290
+ * context and sets up automatic forwarding via hooks and event wrapping.
9291
+ *
9292
+ * @param ctx - ExecutionContext passed to the gadget's execute() method
9293
+ * @param depth - Nesting depth (default: 1 for direct child)
9294
+ * @returns This builder for chaining
9295
+ *
9296
+ * @example
9297
+ * ```typescript
9298
+ * // In a subagent gadget like BrowseWeb - ONE LINE enables auto-forwarding:
9299
+ * execute: async (params, ctx) => {
9300
+ * const agent = new AgentBuilder(client)
9301
+ * .withModel(model)
9302
+ * .withGadgets(Navigate, Click, Screenshot)
9303
+ * .withParentContext(ctx) // <-- This is all you need!
9304
+ * .ask(params.task);
9305
+ *
9306
+ * for await (const event of agent.run()) {
9307
+ * // Events automatically forwarded - just process normally
9308
+ * if (event.type === "text") {
9309
+ * result = event.content;
9310
+ * }
9311
+ * }
9312
+ * }
9313
+ * ```
9314
+ */
9315
+ withParentContext(ctx, depth = 1) {
9316
+ if (ctx.onNestedEvent && ctx.invocationId) {
9317
+ this.parentContext = {
9318
+ invocationId: ctx.invocationId,
9319
+ onNestedEvent: ctx.onNestedEvent,
9320
+ depth
9321
+ };
9322
+ }
8862
9323
  return this;
8863
9324
  }
8864
9325
  /**
@@ -8928,14 +9389,58 @@ ${endPrefix}`
8928
9389
  return this;
8929
9390
  }
8930
9391
  /**
8931
- * Compose the final hooks, including trailing message if configured.
9392
+ * Compose the final hooks, including:
9393
+ * - Trailing message injection (if configured)
9394
+ * - Nested event forwarding for LLM calls (if parentContext is set)
8932
9395
  */
8933
9396
  composeHooks() {
9397
+ let hooks = this.hooks;
9398
+ if (this.parentContext) {
9399
+ const { invocationId, onNestedEvent, depth } = this.parentContext;
9400
+ const existingOnLLMCallStart = hooks?.observers?.onLLMCallStart;
9401
+ const existingOnLLMCallComplete = hooks?.observers?.onLLMCallComplete;
9402
+ hooks = {
9403
+ ...hooks,
9404
+ observers: {
9405
+ ...hooks?.observers,
9406
+ onLLMCallStart: async (context) => {
9407
+ onNestedEvent({
9408
+ type: "llm_call_start",
9409
+ gadgetInvocationId: invocationId,
9410
+ depth,
9411
+ event: {
9412
+ iteration: context.iteration,
9413
+ model: context.options.model
9414
+ }
9415
+ });
9416
+ if (existingOnLLMCallStart) {
9417
+ await existingOnLLMCallStart(context);
9418
+ }
9419
+ },
9420
+ onLLMCallComplete: async (context) => {
9421
+ onNestedEvent({
9422
+ type: "llm_call_end",
9423
+ gadgetInvocationId: invocationId,
9424
+ depth,
9425
+ event: {
9426
+ iteration: context.iteration,
9427
+ model: context.options.model,
9428
+ outputTokens: context.usage?.outputTokens,
9429
+ finishReason: context.finishReason
9430
+ }
9431
+ });
9432
+ if (existingOnLLMCallComplete) {
9433
+ await existingOnLLMCallComplete(context);
9434
+ }
9435
+ }
9436
+ }
9437
+ };
9438
+ }
8934
9439
  if (!this.trailingMessage) {
8935
- return this.hooks;
9440
+ return hooks;
8936
9441
  }
8937
9442
  const trailingMsg = this.trailingMessage;
8938
- const existingBeforeLLMCall = this.hooks?.controllers?.beforeLLMCall;
9443
+ const existingBeforeLLMCall = hooks?.controllers?.beforeLLMCall;
8939
9444
  const trailingMessageController = async (ctx) => {
8940
9445
  const result = existingBeforeLLMCall ? await existingBeforeLLMCall(ctx) : { action: "proceed" };
8941
9446
  if (result.action === "skip") {
@@ -8950,9 +9455,9 @@ ${endPrefix}`
8950
9455
  };
8951
9456
  };
8952
9457
  return {
8953
- ...this.hooks,
9458
+ ...hooks,
8954
9459
  controllers: {
8955
- ...this.hooks?.controllers,
9460
+ ...hooks?.controllers,
8956
9461
  beforeLLMCall: trailingMessageController
8957
9462
  }
8958
9463
  };
@@ -9013,6 +9518,19 @@ ${endPrefix}`
9013
9518
  this.client = new LLMistClass();
9014
9519
  }
9015
9520
  const registry = GadgetRegistry.from(this.gadgets);
9521
+ let onNestedEvent = this.nestedEventCallback;
9522
+ if (this.parentContext) {
9523
+ const { invocationId, onNestedEvent: parentCallback, depth } = this.parentContext;
9524
+ const existingCallback = this.nestedEventCallback;
9525
+ onNestedEvent = (event) => {
9526
+ parentCallback({
9527
+ ...event,
9528
+ gadgetInvocationId: invocationId,
9529
+ depth: event.depth + depth
9530
+ });
9531
+ existingCallback?.(event);
9532
+ };
9533
+ }
9016
9534
  return {
9017
9535
  client: this.client,
9018
9536
  model: this.model ?? "openai:gpt-5-nano",
@@ -9025,19 +9543,21 @@ ${endPrefix}`
9025
9543
  hooks: this.composeHooks(),
9026
9544
  promptConfig: this.promptConfig,
9027
9545
  initialMessages: this.initialMessages,
9028
- onHumanInputRequired: this.onHumanInputRequired,
9546
+ requestHumanInput: this.requestHumanInput,
9029
9547
  gadgetStartPrefix: this.gadgetStartPrefix,
9030
9548
  gadgetEndPrefix: this.gadgetEndPrefix,
9031
9549
  gadgetArgPrefix: this.gadgetArgPrefix,
9032
9550
  textOnlyHandler: this.textOnlyHandler,
9033
9551
  textWithGadgetsHandler: this.textWithGadgetsHandler,
9034
9552
  stopOnGadgetError: this.stopOnGadgetError,
9035
- shouldContinueAfterError: this.shouldContinueAfterError,
9553
+ canRecoverFromGadgetError: this.canRecoverFromGadgetError,
9036
9554
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
9037
9555
  gadgetOutputLimit: this.gadgetOutputLimit,
9038
9556
  gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
9039
9557
  compactionConfig: this.compactionConfig,
9040
- signal: this.signal
9558
+ signal: this.signal,
9559
+ subagentConfig: this.subagentConfig,
9560
+ onNestedEvent
9041
9561
  };
9042
9562
  }
9043
9563
  ask(userPrompt) {
@@ -9194,6 +9714,19 @@ ${endPrefix}`
9194
9714
  this.client = new LLMistClass();
9195
9715
  }
9196
9716
  const registry = GadgetRegistry.from(this.gadgets);
9717
+ let onNestedEvent = this.nestedEventCallback;
9718
+ if (this.parentContext) {
9719
+ const { invocationId, onNestedEvent: parentCallback, depth } = this.parentContext;
9720
+ const existingCallback = this.nestedEventCallback;
9721
+ onNestedEvent = (event) => {
9722
+ parentCallback({
9723
+ ...event,
9724
+ gadgetInvocationId: invocationId,
9725
+ depth: event.depth + depth
9726
+ });
9727
+ existingCallback?.(event);
9728
+ };
9729
+ }
9197
9730
  const options = {
9198
9731
  client: this.client,
9199
9732
  model: this.model ?? "openai:gpt-5-nano",
@@ -9206,19 +9739,21 @@ ${endPrefix}`
9206
9739
  hooks: this.composeHooks(),
9207
9740
  promptConfig: this.promptConfig,
9208
9741
  initialMessages: this.initialMessages,
9209
- onHumanInputRequired: this.onHumanInputRequired,
9742
+ requestHumanInput: this.requestHumanInput,
9210
9743
  gadgetStartPrefix: this.gadgetStartPrefix,
9211
9744
  gadgetEndPrefix: this.gadgetEndPrefix,
9212
9745
  gadgetArgPrefix: this.gadgetArgPrefix,
9213
9746
  textOnlyHandler: this.textOnlyHandler,
9214
9747
  textWithGadgetsHandler: this.textWithGadgetsHandler,
9215
9748
  stopOnGadgetError: this.stopOnGadgetError,
9216
- shouldContinueAfterError: this.shouldContinueAfterError,
9749
+ canRecoverFromGadgetError: this.canRecoverFromGadgetError,
9217
9750
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
9218
9751
  gadgetOutputLimit: this.gadgetOutputLimit,
9219
9752
  gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
9220
9753
  compactionConfig: this.compactionConfig,
9221
- signal: this.signal
9754
+ signal: this.signal,
9755
+ subagentConfig: this.subagentConfig,
9756
+ onNestedEvent
9222
9757
  };
9223
9758
  return new Agent(AGENT_INTERNAL_KEY, options);
9224
9759
  }
@@ -9236,7 +9771,8 @@ var COMMANDS = {
9236
9771
  gadget: "gadget",
9237
9772
  image: "image",
9238
9773
  speech: "speech",
9239
- vision: "vision"
9774
+ vision: "vision",
9775
+ init: "init"
9240
9776
  };
9241
9777
  var LOG_LEVELS = ["silly", "trace", "debug", "info", "warn", "error", "fatal"];
9242
9778
  var DEFAULT_MODEL = "openai:gpt-5-nano";
@@ -9312,7 +9848,7 @@ var import_commander2 = require("commander");
9312
9848
  // package.json
9313
9849
  var package_default = {
9314
9850
  name: "llmist",
9315
- version: "2.6.0",
9851
+ version: "3.0.0",
9316
9852
  description: "TypeScript LLM client with streaming tool execution. Tools fire mid-stream. Built-in function calling works with any model\u2014no structured outputs or native tool support required.",
9317
9853
  type: "module",
9318
9854
  main: "dist/index.cjs",
@@ -9720,7 +10256,7 @@ var askUser = createGadget({
9720
10256
  }
9721
10257
  ],
9722
10258
  execute: ({ question }) => {
9723
- throw new HumanInputException(question);
10259
+ throw new HumanInputRequiredException(question);
9724
10260
  }
9725
10261
  });
9726
10262
  var tellUser = createGadget({
@@ -9770,11 +10306,59 @@ var finish = createGadget({
9770
10306
  }
9771
10307
  ],
9772
10308
  execute: () => {
9773
- throw new BreakLoopException("Task completed");
10309
+ throw new TaskCompletionSignal("Task completed");
9774
10310
  }
9775
10311
  });
9776
10312
  var builtinGadgets = [askUser, tellUser, finish];
9777
10313
 
10314
+ // src/cli/subagent-config.ts
10315
+ var INHERIT_MODEL = "inherit";
10316
+ function resolveSubagentConfig(subagentName, parentModel, profileConfig, globalConfig) {
10317
+ const resolved = {};
10318
+ const globalDefaultModel = globalConfig?.["default-model"];
10319
+ const globalSubagent = extractSubagentConfig(globalConfig, subagentName);
10320
+ const profileSubagent = profileConfig?.[subagentName] ?? {};
10321
+ const merged = { ...globalSubagent, ...profileSubagent };
10322
+ const configModel = merged.model ?? globalDefaultModel ?? INHERIT_MODEL;
10323
+ resolved.model = configModel === INHERIT_MODEL ? parentModel : configModel;
10324
+ for (const [key, value] of Object.entries(merged)) {
10325
+ if (key !== "model") {
10326
+ resolved[key] = value;
10327
+ }
10328
+ }
10329
+ return resolved;
10330
+ }
10331
+ function buildSubagentConfigMap(parentModel, profileConfig, globalConfig) {
10332
+ const subagentNames = /* @__PURE__ */ new Set();
10333
+ if (globalConfig) {
10334
+ for (const key of Object.keys(globalConfig)) {
10335
+ if (key !== "default-model" && typeof globalConfig[key] === "object") {
10336
+ subagentNames.add(key);
10337
+ }
10338
+ }
10339
+ }
10340
+ if (profileConfig) {
10341
+ for (const key of Object.keys(profileConfig)) {
10342
+ subagentNames.add(key);
10343
+ }
10344
+ }
10345
+ const result = {};
10346
+ for (const name of subagentNames) {
10347
+ result[name] = resolveSubagentConfig(name, parentModel, profileConfig, globalConfig);
10348
+ }
10349
+ return result;
10350
+ }
10351
+ function extractSubagentConfig(globalConfig, subagentName) {
10352
+ if (!globalConfig) {
10353
+ return {};
10354
+ }
10355
+ const value = globalConfig[subagentName];
10356
+ if (typeof value === "object" && value !== null) {
10357
+ return value;
10358
+ }
10359
+ return {};
10360
+ }
10361
+
9778
10362
  // src/cli/config.ts
9779
10363
  var import_node_fs3 = require("fs");
9780
10364
  var import_node_os2 = require("os");
@@ -9863,7 +10447,7 @@ function hasTemplateSyntax(str) {
9863
10447
  }
9864
10448
 
9865
10449
  // src/cli/config.ts
9866
- var VALID_APPROVAL_MODES = ["allowed", "denied", "approval-required"];
10450
+ var VALID_PERMISSION_LEVELS = ["allowed", "denied", "approval-required"];
9867
10451
  var GLOBAL_CONFIG_KEYS = /* @__PURE__ */ new Set(["log-level", "log-file", "log-reset"]);
9868
10452
  var VALID_LOG_LEVELS = ["silly", "trace", "debug", "info", "warn", "error", "fatal"];
9869
10453
  var COMPLETE_CONFIG_KEYS = /* @__PURE__ */ new Set([
@@ -9903,6 +10487,8 @@ var AGENT_CONFIG_KEYS = /* @__PURE__ */ new Set([
9903
10487
  "gadget-end-prefix",
9904
10488
  "gadget-arg-prefix",
9905
10489
  "gadget-approval",
10490
+ "subagents",
10491
+ // Per-subagent configuration overrides
9906
10492
  "quiet",
9907
10493
  "inherits",
9908
10494
  "log-level",
@@ -9928,9 +10514,9 @@ function getConfigPath() {
9928
10514
  return (0, import_node_path4.join)((0, import_node_os2.homedir)(), ".llmist", "cli.toml");
9929
10515
  }
9930
10516
  var ConfigError = class extends Error {
9931
- constructor(message, path5) {
9932
- super(path5 ? `${path5}: ${message}` : message);
9933
- this.path = path5;
10517
+ constructor(message, path6) {
10518
+ super(path6 ? `${path6}: ${message}` : message);
10519
+ this.path = path6;
9934
10520
  this.name = "ConfigError";
9935
10521
  }
9936
10522
  };
@@ -9986,6 +10572,63 @@ function validateInherits(value, section) {
9986
10572
  }
9987
10573
  throw new ConfigError(`[${section}].inherits must be a string or array of strings`);
9988
10574
  }
10575
+ function validateSingleSubagentConfig(value, subagentName, section) {
10576
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
10577
+ throw new ConfigError(
10578
+ `[${section}].${subagentName} must be a table (e.g., { model = "inherit", maxIterations = 20 })`
10579
+ );
10580
+ }
10581
+ const result = {};
10582
+ const rawObj = value;
10583
+ for (const [key, val] of Object.entries(rawObj)) {
10584
+ if (key === "model") {
10585
+ if (typeof val !== "string") {
10586
+ throw new ConfigError(`[${section}].${subagentName}.model must be a string`);
10587
+ }
10588
+ result.model = val;
10589
+ } else if (key === "maxIterations") {
10590
+ if (typeof val !== "number" || !Number.isInteger(val) || val < 1) {
10591
+ throw new ConfigError(
10592
+ `[${section}].${subagentName}.maxIterations must be a positive integer`
10593
+ );
10594
+ }
10595
+ result.maxIterations = val;
10596
+ } else {
10597
+ result[key] = val;
10598
+ }
10599
+ }
10600
+ return result;
10601
+ }
10602
+ function validateSubagentConfigMap(value, section) {
10603
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
10604
+ throw new ConfigError(
10605
+ `[${section}].subagents must be a table (e.g., { BrowseWeb = { model = "inherit" } })`
10606
+ );
10607
+ }
10608
+ const result = {};
10609
+ for (const [subagentName, config] of Object.entries(value)) {
10610
+ result[subagentName] = validateSingleSubagentConfig(config, subagentName, `${section}.subagents`);
10611
+ }
10612
+ return result;
10613
+ }
10614
+ function validateGlobalSubagentConfig(value, section) {
10615
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
10616
+ throw new ConfigError(`[${section}] must be a table`);
10617
+ }
10618
+ const result = {};
10619
+ const rawObj = value;
10620
+ for (const [key, val] of Object.entries(rawObj)) {
10621
+ if (key === "default-model") {
10622
+ if (typeof val !== "string") {
10623
+ throw new ConfigError(`[${section}].default-model must be a string`);
10624
+ }
10625
+ result["default-model"] = val;
10626
+ } else {
10627
+ result[key] = validateSingleSubagentConfig(val, key, section);
10628
+ }
10629
+ }
10630
+ return result;
10631
+ }
9989
10632
  function validateGadgetApproval(value, section) {
9990
10633
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
9991
10634
  throw new ConfigError(
@@ -9997,9 +10640,9 @@ function validateGadgetApproval(value, section) {
9997
10640
  if (typeof mode !== "string") {
9998
10641
  throw new ConfigError(`[${section}].gadget-approval.${gadgetName} must be a string`);
9999
10642
  }
10000
- if (!VALID_APPROVAL_MODES.includes(mode)) {
10643
+ if (!VALID_PERMISSION_LEVELS.includes(mode)) {
10001
10644
  throw new ConfigError(
10002
- `[${section}].gadget-approval.${gadgetName} must be one of: ${VALID_APPROVAL_MODES.join(", ")}`
10645
+ `[${section}].gadget-approval.${gadgetName} must be one of: ${VALID_PERMISSION_LEVELS.join(", ")}`
10003
10646
  );
10004
10647
  }
10005
10648
  result[gadgetName] = mode;
@@ -10168,6 +10811,9 @@ function validateAgentConfig(raw, section) {
10168
10811
  if ("gadget-approval" in rawObj) {
10169
10812
  result["gadget-approval"] = validateGadgetApproval(rawObj["gadget-approval"], section);
10170
10813
  }
10814
+ if ("subagents" in rawObj) {
10815
+ result.subagents = validateSubagentConfigMap(rawObj.subagents, section);
10816
+ }
10171
10817
  if ("quiet" in rawObj) {
10172
10818
  result.quiet = validateBoolean(rawObj.quiet, "quiet", section);
10173
10819
  }
@@ -10336,6 +10982,9 @@ function validateCustomConfig(raw, section) {
10336
10982
  if ("gadget-approval" in rawObj) {
10337
10983
  result["gadget-approval"] = validateGadgetApproval(rawObj["gadget-approval"], section);
10338
10984
  }
10985
+ if ("subagents" in rawObj) {
10986
+ result.subagents = validateSubagentConfigMap(rawObj.subagents, section);
10987
+ }
10339
10988
  if ("max-tokens" in rawObj) {
10340
10989
  result["max-tokens"] = validateNumber(rawObj["max-tokens"], "max-tokens", section, {
10341
10990
  integer: true,
@@ -10383,6 +11032,8 @@ function validateConfig(raw, configPath) {
10383
11032
  result.prompts = validatePromptsConfig(value, key);
10384
11033
  } else if (key === "docker") {
10385
11034
  result.docker = validateDockerConfig(value, key);
11035
+ } else if (key === "subagents") {
11036
+ result.subagents = validateGlobalSubagentConfig(value, key);
10386
11037
  } else {
10387
11038
  result[key] = validateCustomConfig(value, key);
10388
11039
  }
@@ -10423,7 +11074,16 @@ function loadConfig() {
10423
11074
  return resolveTemplatesInConfig(inherited, configPath);
10424
11075
  }
10425
11076
  function getCustomCommandNames(config) {
10426
- const reserved = /* @__PURE__ */ new Set(["global", "complete", "agent", "image", "speech", "prompts", "docker"]);
11077
+ const reserved = /* @__PURE__ */ new Set([
11078
+ "global",
11079
+ "complete",
11080
+ "agent",
11081
+ "image",
11082
+ "speech",
11083
+ "prompts",
11084
+ "docker",
11085
+ "subagents"
11086
+ ]);
10427
11087
  return Object.keys(config).filter((key) => !reserved.has(key));
10428
11088
  }
10429
11089
  function resolveTemplatesInConfig(config, configPath) {
@@ -10957,11 +11617,11 @@ function resolveDevMode(config, cliDevMode) {
10957
11617
  }
10958
11618
  return { enabled: true, sourcePath };
10959
11619
  }
10960
- function expandHome(path5) {
10961
- if (path5.startsWith("~")) {
10962
- return path5.replace(/^~/, (0, import_node_os4.homedir)());
11620
+ function expandHome(path6) {
11621
+ if (path6.startsWith("~")) {
11622
+ return path6.replace(/^~/, (0, import_node_os4.homedir)());
10963
11623
  }
10964
- return path5;
11624
+ return path6;
10965
11625
  }
10966
11626
  function buildDockerRunArgs(ctx, imageName, devMode) {
10967
11627
  const args = ["run", "--rm"];
@@ -11138,9 +11798,9 @@ async function readFileBuffer(filePath, options = {}) {
11138
11798
  }
11139
11799
 
11140
11800
  // src/cli/gadgets.ts
11141
- var import_node_fs10 = __toESM(require("fs"), 1);
11142
- var import_node_path11 = __toESM(require("path"), 1);
11143
- var import_node_url = require("url");
11801
+ var import_node_fs11 = __toESM(require("fs"), 1);
11802
+ var import_node_path12 = __toESM(require("path"), 1);
11803
+ var import_node_url2 = require("url");
11144
11804
  init_gadget();
11145
11805
 
11146
11806
  // src/cli/builtins/filesystem/edit-file.ts
@@ -11665,6 +12325,208 @@ function isBuiltinGadgetName(name) {
11665
12325
  return name in builtinGadgetRegistry;
11666
12326
  }
11667
12327
 
12328
+ // src/cli/external-gadgets.ts
12329
+ var import_node_child_process = require("child_process");
12330
+ var import_node_fs10 = __toESM(require("fs"), 1);
12331
+ var import_node_path11 = __toESM(require("path"), 1);
12332
+ var import_node_os5 = __toESM(require("os"), 1);
12333
+ var import_node_url = require("url");
12334
+ var CACHE_DIR2 = import_node_path11.default.join(import_node_os5.default.homedir(), ".llmist", "gadget-cache");
12335
+ function isExternalPackageSpecifier(specifier) {
12336
+ if (/^@?[a-z0-9][\w.-]*(?:@[\w.-]+)?(?::[a-z]+)?(?:\/\w+)?$/i.test(specifier)) {
12337
+ return true;
12338
+ }
12339
+ if (specifier.startsWith("git+")) {
12340
+ return true;
12341
+ }
12342
+ return false;
12343
+ }
12344
+ function parseGadgetSpecifier(specifier) {
12345
+ if (specifier.startsWith("git+")) {
12346
+ const url = specifier.slice(4);
12347
+ const [baseUrl, ref] = url.split("#");
12348
+ return {
12349
+ type: "git",
12350
+ package: baseUrl,
12351
+ version: ref
12352
+ };
12353
+ }
12354
+ const npmMatch = specifier.match(
12355
+ /^(@?[a-z0-9][\w.-]*)(?:@([\w.-]+))?(?::([a-z]+))?(?:\/(\w+))?$/i
12356
+ );
12357
+ if (npmMatch) {
12358
+ const [, pkg, version, preset, gadgetName] = npmMatch;
12359
+ return {
12360
+ type: "npm",
12361
+ package: pkg,
12362
+ version,
12363
+ preset,
12364
+ gadgetName
12365
+ };
12366
+ }
12367
+ return null;
12368
+ }
12369
+ function getCacheDir(spec) {
12370
+ const versionSuffix = spec.version ? `@${spec.version}` : "@latest";
12371
+ if (spec.type === "npm") {
12372
+ return import_node_path11.default.join(CACHE_DIR2, "npm", `${spec.package}${versionSuffix}`);
12373
+ }
12374
+ const sanitizedUrl = spec.package.replace(/[/:]/g, "-").replace(/^-+|-+$/g, "");
12375
+ return import_node_path11.default.join(CACHE_DIR2, "git", `${sanitizedUrl}${versionSuffix}`);
12376
+ }
12377
+ function isCached(cacheDir) {
12378
+ const packageJsonPath = import_node_path11.default.join(cacheDir, "package.json");
12379
+ return import_node_fs10.default.existsSync(packageJsonPath);
12380
+ }
12381
+ async function installNpmPackage(spec, cacheDir) {
12382
+ import_node_fs10.default.mkdirSync(cacheDir, { recursive: true });
12383
+ const packageJson = {
12384
+ name: "llmist-gadget-cache",
12385
+ private: true,
12386
+ type: "module"
12387
+ };
12388
+ import_node_fs10.default.writeFileSync(import_node_path11.default.join(cacheDir, "package.json"), JSON.stringify(packageJson, null, 2));
12389
+ const packageSpec = spec.version ? `${spec.package}@${spec.version}` : spec.package;
12390
+ try {
12391
+ (0, import_node_child_process.execSync)(`npm install --prefix "${cacheDir}" "${packageSpec}" --save`, {
12392
+ stdio: "pipe",
12393
+ cwd: cacheDir
12394
+ });
12395
+ } catch (error) {
12396
+ const message = error instanceof Error ? error.message : String(error);
12397
+ throw new Error(`Failed to install npm package '${packageSpec}': ${message}`);
12398
+ }
12399
+ }
12400
+ async function installGitPackage(spec, cacheDir) {
12401
+ import_node_fs10.default.mkdirSync(import_node_path11.default.dirname(cacheDir), { recursive: true });
12402
+ if (import_node_fs10.default.existsSync(cacheDir)) {
12403
+ try {
12404
+ (0, import_node_child_process.execSync)("git fetch", { cwd: cacheDir, stdio: "pipe" });
12405
+ if (spec.version) {
12406
+ (0, import_node_child_process.execSync)(`git checkout ${spec.version}`, { cwd: cacheDir, stdio: "pipe" });
12407
+ }
12408
+ } catch (error) {
12409
+ import_node_fs10.default.rmSync(cacheDir, { recursive: true, force: true });
12410
+ }
12411
+ }
12412
+ if (!import_node_fs10.default.existsSync(cacheDir)) {
12413
+ try {
12414
+ const cloneCmd = spec.version ? `git clone --branch ${spec.version} "${spec.package}" "${cacheDir}"` : `git clone "${spec.package}" "${cacheDir}"`;
12415
+ (0, import_node_child_process.execSync)(cloneCmd, { stdio: "pipe" });
12416
+ } catch (error) {
12417
+ const message = error instanceof Error ? error.message : String(error);
12418
+ throw new Error(`Failed to clone git repository '${spec.package}': ${message}`);
12419
+ }
12420
+ if (import_node_fs10.default.existsSync(import_node_path11.default.join(cacheDir, "package.json"))) {
12421
+ try {
12422
+ (0, import_node_child_process.execSync)("npm install", { cwd: cacheDir, stdio: "pipe" });
12423
+ } catch (error) {
12424
+ const message = error instanceof Error ? error.message : String(error);
12425
+ throw new Error(`Failed to install dependencies for '${spec.package}': ${message}`);
12426
+ }
12427
+ try {
12428
+ const packageJson = JSON.parse(import_node_fs10.default.readFileSync(import_node_path11.default.join(cacheDir, "package.json"), "utf-8"));
12429
+ if (packageJson.scripts?.build) {
12430
+ (0, import_node_child_process.execSync)("npm run build", { cwd: cacheDir, stdio: "pipe" });
12431
+ }
12432
+ } catch (error) {
12433
+ const message = error instanceof Error ? error.message : String(error);
12434
+ throw new Error(`Failed to build package '${spec.package}': ${message}`);
12435
+ }
12436
+ }
12437
+ }
12438
+ }
12439
+ function readManifest(packageDir) {
12440
+ const packageJsonPath = import_node_path11.default.join(packageDir, "package.json");
12441
+ if (!import_node_fs10.default.existsSync(packageJsonPath)) {
12442
+ return null;
12443
+ }
12444
+ try {
12445
+ const packageJson = JSON.parse(import_node_fs10.default.readFileSync(packageJsonPath, "utf-8"));
12446
+ return packageJson.llmist || null;
12447
+ } catch {
12448
+ return null;
12449
+ }
12450
+ }
12451
+ function getPackagePath(cacheDir, packageName) {
12452
+ const nodeModulesPath = import_node_path11.default.join(cacheDir, "node_modules", packageName);
12453
+ if (import_node_fs10.default.existsSync(nodeModulesPath)) {
12454
+ return nodeModulesPath;
12455
+ }
12456
+ return cacheDir;
12457
+ }
12458
+ async function loadExternalGadgets(specifier, forceInstall = false) {
12459
+ const spec = parseGadgetSpecifier(specifier);
12460
+ if (!spec) {
12461
+ throw new Error(`Invalid external package specifier: ${specifier}`);
12462
+ }
12463
+ const cacheDir = getCacheDir(spec);
12464
+ if (!isCached(cacheDir) || forceInstall) {
12465
+ if (spec.type === "npm") {
12466
+ await installNpmPackage(spec, cacheDir);
12467
+ } else {
12468
+ await installGitPackage(spec, cacheDir);
12469
+ }
12470
+ }
12471
+ const packagePath = getPackagePath(cacheDir, spec.package);
12472
+ const manifest = readManifest(packagePath);
12473
+ let entryPoint;
12474
+ let gadgetNames = null;
12475
+ if (spec.gadgetName) {
12476
+ gadgetNames = [spec.gadgetName];
12477
+ if (manifest?.subagents?.[spec.gadgetName]) {
12478
+ entryPoint = manifest.subagents[spec.gadgetName].entryPoint;
12479
+ } else {
12480
+ entryPoint = manifest?.gadgets || "./dist/index.js";
12481
+ }
12482
+ } else if (spec.preset) {
12483
+ if (!manifest?.presets?.[spec.preset]) {
12484
+ throw new Error(`Unknown preset '${spec.preset}' in package '${spec.package}'`);
12485
+ }
12486
+ const preset = manifest.presets[spec.preset];
12487
+ if (preset === "*") {
12488
+ gadgetNames = null;
12489
+ } else {
12490
+ gadgetNames = preset;
12491
+ }
12492
+ entryPoint = manifest?.gadgets || "./dist/index.js";
12493
+ } else {
12494
+ entryPoint = manifest?.gadgets || "./dist/index.js";
12495
+ }
12496
+ const resolvedEntryPoint = import_node_path11.default.resolve(packagePath, entryPoint);
12497
+ if (!import_node_fs10.default.existsSync(resolvedEntryPoint)) {
12498
+ throw new Error(
12499
+ `Entry point not found: ${resolvedEntryPoint}. Make sure the package is built (run 'npm run build' in the package directory).`
12500
+ );
12501
+ }
12502
+ const moduleUrl = (0, import_node_url.pathToFileURL)(resolvedEntryPoint).href;
12503
+ let exports2;
12504
+ try {
12505
+ exports2 = await import(moduleUrl);
12506
+ } catch (error) {
12507
+ const message = error instanceof Error ? error.message : String(error);
12508
+ throw new Error(`Failed to import '${specifier}': ${message}`);
12509
+ }
12510
+ let gadgets = extractGadgetsFromModule(exports2);
12511
+ if (gadgetNames) {
12512
+ const gadgetSet = new Set(gadgetNames.map((n) => n.toLowerCase()));
12513
+ gadgets = gadgets.filter((g) => {
12514
+ const name = g.name?.toLowerCase() || "";
12515
+ return gadgetSet.has(name);
12516
+ });
12517
+ const foundNames = new Set(gadgets.map((g) => g.name?.toLowerCase() || ""));
12518
+ for (const requested of gadgetNames) {
12519
+ if (!foundNames.has(requested.toLowerCase())) {
12520
+ throw new Error(`Gadget '${requested}' not found in package '${spec.package}'`);
12521
+ }
12522
+ }
12523
+ }
12524
+ if (gadgets.length === 0) {
12525
+ throw new Error(`No gadgets found in package '${spec.package}'`);
12526
+ }
12527
+ return gadgets;
12528
+ }
12529
+
11668
12530
  // src/cli/gadgets.ts
11669
12531
  var PATH_PREFIXES = [".", "/", "~"];
11670
12532
  var BUILTIN_PREFIX = "builtin:";
@@ -11680,7 +12542,17 @@ function isGadgetConstructor(value) {
11680
12542
  return false;
11681
12543
  }
11682
12544
  const prototype = value.prototype;
11683
- return Boolean(prototype) && (prototype instanceof BaseGadget || isGadgetLike(prototype));
12545
+ if (!prototype) {
12546
+ return false;
12547
+ }
12548
+ if (prototype instanceof AbstractGadget) {
12549
+ return true;
12550
+ }
12551
+ const proto = prototype;
12552
+ if (typeof proto.execute === "function") {
12553
+ return true;
12554
+ }
12555
+ return isGadgetLike(prototype);
11684
12556
  }
11685
12557
  function expandHomePath(input) {
11686
12558
  if (!input.startsWith("~")) {
@@ -11690,10 +12562,10 @@ function expandHomePath(input) {
11690
12562
  if (!home) {
11691
12563
  return input;
11692
12564
  }
11693
- return import_node_path11.default.join(home, input.slice(1));
12565
+ return import_node_path12.default.join(home, input.slice(1));
11694
12566
  }
11695
12567
  function isFileLikeSpecifier(specifier) {
11696
- return PATH_PREFIXES.some((prefix) => specifier.startsWith(prefix)) || specifier.includes(import_node_path11.default.sep);
12568
+ return PATH_PREFIXES.some((prefix) => specifier.startsWith(prefix)) || specifier.includes(import_node_path12.default.sep);
11697
12569
  }
11698
12570
  function tryResolveBuiltin(specifier) {
11699
12571
  if (specifier.startsWith(BUILTIN_PREFIX)) {
@@ -11716,11 +12588,11 @@ function resolveGadgetSpecifier(specifier, cwd) {
11716
12588
  return specifier;
11717
12589
  }
11718
12590
  const expanded = expandHomePath(specifier);
11719
- const resolvedPath = import_node_path11.default.resolve(cwd, expanded);
11720
- if (!import_node_fs10.default.existsSync(resolvedPath)) {
12591
+ const resolvedPath = import_node_path12.default.resolve(cwd, expanded);
12592
+ if (!import_node_fs11.default.existsSync(resolvedPath)) {
11721
12593
  throw new Error(`Gadget module not found at ${resolvedPath}`);
11722
12594
  }
11723
- return (0, import_node_url.pathToFileURL)(resolvedPath).href;
12595
+ return (0, import_node_url2.pathToFileURL)(resolvedPath).href;
11724
12596
  }
11725
12597
  function extractGadgetsFromModule(moduleExports) {
11726
12598
  const results = [];
@@ -11733,7 +12605,7 @@ function extractGadgetsFromModule(moduleExports) {
11733
12605
  return;
11734
12606
  }
11735
12607
  visited.add(value);
11736
- if (value instanceof BaseGadget || isGadgetLike(value)) {
12608
+ if (value instanceof AbstractGadget || isGadgetLike(value)) {
11737
12609
  results.push(value);
11738
12610
  return;
11739
12611
  }
@@ -11758,12 +12630,23 @@ function extractGadgetsFromModule(moduleExports) {
11758
12630
  }
11759
12631
  async function loadGadgets(specifiers, cwd, importer = (specifier) => import(specifier)) {
11760
12632
  const gadgets = [];
12633
+ const usingDefaultImporter = importer.toString().includes("import(specifier)");
11761
12634
  for (const specifier of specifiers) {
11762
12635
  const builtin = tryResolveBuiltin(specifier);
11763
12636
  if (builtin) {
11764
12637
  gadgets.push(builtin);
11765
12638
  continue;
11766
12639
  }
12640
+ if (usingDefaultImporter && isExternalPackageSpecifier(specifier)) {
12641
+ try {
12642
+ const externalGadgets = await loadExternalGadgets(specifier);
12643
+ gadgets.push(...externalGadgets);
12644
+ continue;
12645
+ } catch (error) {
12646
+ const message = error instanceof Error ? error.message : String(error);
12647
+ throw new Error(`Failed to load external package '${specifier}': ${message}`);
12648
+ }
12649
+ }
11767
12650
  const resolved = resolveGadgetSpecifier(specifier, cwd);
11768
12651
  let exports2;
11769
12652
  try {
@@ -11789,13 +12672,13 @@ async function loadGadgets(specifiers, cwd, importer = (specifier) => import(spe
11789
12672
 
11790
12673
  // src/cli/llm-logging.ts
11791
12674
  var import_promises4 = require("fs/promises");
11792
- var import_node_os5 = require("os");
11793
- var import_node_path12 = require("path");
12675
+ var import_node_os6 = require("os");
12676
+ var import_node_path13 = require("path");
11794
12677
  init_messages();
11795
- var DEFAULT_LLM_LOG_DIR = (0, import_node_path12.join)((0, import_node_os5.homedir)(), ".llmist", "logs");
12678
+ var DEFAULT_LLM_LOG_DIR = (0, import_node_path13.join)((0, import_node_os6.homedir)(), ".llmist", "logs");
11796
12679
  function resolveLogDir(option, subdir) {
11797
12680
  if (option === true) {
11798
- return (0, import_node_path12.join)(DEFAULT_LLM_LOG_DIR, subdir);
12681
+ return (0, import_node_path13.join)(DEFAULT_LLM_LOG_DIR, subdir);
11799
12682
  }
11800
12683
  if (typeof option === "string") {
11801
12684
  return option;
@@ -11806,14 +12689,14 @@ function formatLlmRequest(messages) {
11806
12689
  const lines = [];
11807
12690
  for (const msg of messages) {
11808
12691
  lines.push(`=== ${msg.role.toUpperCase()} ===`);
11809
- lines.push(msg.content ? extractText(msg.content) : "");
12692
+ lines.push(msg.content ? extractMessageText(msg.content) : "");
11810
12693
  lines.push("");
11811
12694
  }
11812
12695
  return lines.join("\n");
11813
12696
  }
11814
12697
  async function writeLogFile(dir, filename, content) {
11815
12698
  await (0, import_promises4.mkdir)(dir, { recursive: true });
11816
- await (0, import_promises4.writeFile)((0, import_node_path12.join)(dir, filename), content, "utf-8");
12699
+ await (0, import_promises4.writeFile)((0, import_node_path13.join)(dir, filename), content, "utf-8");
11817
12700
  }
11818
12701
  function formatSessionTimestamp(date = /* @__PURE__ */ new Date()) {
11819
12702
  const pad = (n) => n.toString().padStart(2, "0");
@@ -11827,7 +12710,7 @@ function formatSessionTimestamp(date = /* @__PURE__ */ new Date()) {
11827
12710
  }
11828
12711
  async function createSessionDir(baseDir) {
11829
12712
  const timestamp = formatSessionTimestamp();
11830
- const sessionDir = (0, import_node_path12.join)(baseDir, timestamp);
12713
+ const sessionDir = (0, import_node_path13.join)(baseDir, timestamp);
11831
12714
  try {
11832
12715
  await (0, import_promises4.mkdir)(sessionDir, { recursive: true });
11833
12716
  return sessionDir;
@@ -12021,12 +12904,14 @@ function formatMediaLine(media) {
12021
12904
  const id = import_chalk3.default.cyan(media.id);
12022
12905
  const mimeType = import_chalk3.default.dim(media.mimeType);
12023
12906
  const size = import_chalk3.default.yellow(formatBytes(media.sizeBytes));
12024
- const path5 = import_chalk3.default.dim(media.path);
12025
- return `${import_chalk3.default.dim("[")}${icon} ${id} ${mimeType} ${size}${import_chalk3.default.dim("]")} ${import_chalk3.default.dim("\u2192")} ${path5}`;
12907
+ const path6 = import_chalk3.default.dim(media.path);
12908
+ return `${import_chalk3.default.dim("[")}${icon} ${id} ${mimeType} ${size}${import_chalk3.default.dim("]")} ${import_chalk3.default.dim("\u2192")} ${path6}`;
12026
12909
  }
12027
12910
  function formatGadgetSummary2(result) {
12028
12911
  const gadgetLabel = import_chalk3.default.magenta.bold(result.gadgetName);
12029
- const timeLabel = import_chalk3.default.dim(`${Math.round(result.executionTimeMs)}ms`);
12912
+ const timeLabel = import_chalk3.default.dim(
12913
+ result.executionTimeMs >= 1e3 ? `${(result.executionTimeMs / 1e3).toFixed(1)}s` : `${Math.round(result.executionTimeMs)}ms`
12914
+ );
12030
12915
  const paramsStr = formatParametersInline(result.parameters);
12031
12916
  const paramsLabel = paramsStr ? `${import_chalk3.default.dim("(")}${paramsStr}${import_chalk3.default.dim(")")}` : "";
12032
12917
  if (result.error) {
@@ -12194,6 +13079,8 @@ var StreamProgress = class {
12194
13079
  delayTimeout = null;
12195
13080
  isRunning = false;
12196
13081
  hasRendered = false;
13082
+ lastRenderLineCount = 0;
13083
+ // Track lines rendered for multi-line clearing
12197
13084
  // Current call stats (streaming mode)
12198
13085
  mode = "cumulative";
12199
13086
  model = "";
@@ -12213,6 +13100,99 @@ var StreamProgress = class {
12213
13100
  totalCost = 0;
12214
13101
  iterations = 0;
12215
13102
  currentIteration = 0;
13103
+ // In-flight gadget tracking for concurrent status display
13104
+ inFlightGadgets = /* @__PURE__ */ new Map();
13105
+ // Nested agent tracking for hierarchical subagent display
13106
+ nestedAgents = /* @__PURE__ */ new Map();
13107
+ // Nested gadget tracking for hierarchical subagent display
13108
+ nestedGadgets = /* @__PURE__ */ new Map();
13109
+ /**
13110
+ * Add a gadget to the in-flight tracking (called when gadget_call event received).
13111
+ * Triggers re-render to show the gadget in the status display.
13112
+ */
13113
+ addGadget(invocationId, name, params) {
13114
+ this.inFlightGadgets.set(invocationId, { name, params, startTime: Date.now() });
13115
+ if (this.isRunning && this.isTTY) {
13116
+ this.render();
13117
+ }
13118
+ }
13119
+ /**
13120
+ * Remove a gadget from in-flight tracking (called when gadget_result event received).
13121
+ * Triggers re-render to update the status display.
13122
+ */
13123
+ removeGadget(invocationId) {
13124
+ this.inFlightGadgets.delete(invocationId);
13125
+ if (this.isRunning && this.isTTY) {
13126
+ this.render();
13127
+ }
13128
+ }
13129
+ /**
13130
+ * Check if there are any gadgets currently in flight.
13131
+ */
13132
+ hasInFlightGadgets() {
13133
+ return this.inFlightGadgets.size > 0;
13134
+ }
13135
+ /**
13136
+ * Add a nested agent LLM call (called when nested llm_call_start event received).
13137
+ * Used to display hierarchical progress for subagent gadgets.
13138
+ */
13139
+ addNestedAgent(id, parentInvocationId, depth, model, iteration, inputTokens) {
13140
+ this.nestedAgents.set(id, {
13141
+ parentInvocationId,
13142
+ depth,
13143
+ model,
13144
+ iteration,
13145
+ startTime: Date.now(),
13146
+ inputTokens
13147
+ });
13148
+ if (this.isRunning && this.isTTY) {
13149
+ this.render();
13150
+ }
13151
+ }
13152
+ /**
13153
+ * Update a nested agent with completion info (called when nested llm_call_end event received).
13154
+ */
13155
+ updateNestedAgent(id, outputTokens) {
13156
+ const agent = this.nestedAgents.get(id);
13157
+ if (agent) {
13158
+ agent.outputTokens = outputTokens;
13159
+ if (this.isRunning && this.isTTY) {
13160
+ this.render();
13161
+ }
13162
+ }
13163
+ }
13164
+ /**
13165
+ * Remove a nested agent (called when the nested LLM call completes).
13166
+ */
13167
+ removeNestedAgent(id) {
13168
+ this.nestedAgents.delete(id);
13169
+ if (this.isRunning && this.isTTY) {
13170
+ this.render();
13171
+ }
13172
+ }
13173
+ /**
13174
+ * Add a nested gadget call (called when nested gadget_call event received).
13175
+ */
13176
+ addNestedGadget(id, depth, parentInvocationId, name) {
13177
+ this.nestedGadgets.set(id, {
13178
+ depth,
13179
+ parentInvocationId,
13180
+ name,
13181
+ startTime: Date.now()
13182
+ });
13183
+ if (this.isRunning && this.isTTY) {
13184
+ this.render();
13185
+ }
13186
+ }
13187
+ /**
13188
+ * Remove a nested gadget (called when nested gadget_result event received).
13189
+ */
13190
+ removeNestedGadget(id) {
13191
+ this.nestedGadgets.delete(id);
13192
+ if (this.isRunning && this.isTTY) {
13193
+ this.render();
13194
+ }
13195
+ }
12216
13196
  /**
12217
13197
  * Starts a new LLM call. Switches to streaming mode.
12218
13198
  * @param model - Model name being used
@@ -12339,15 +13319,57 @@ var StreamProgress = class {
12339
13319
  this.isStreaming = true;
12340
13320
  }
12341
13321
  render() {
13322
+ this.clearRenderedLines();
12342
13323
  const spinner = SPINNER_FRAMES[this.frameIndex++ % SPINNER_FRAMES.length];
13324
+ const lines = [];
12343
13325
  if (this.mode === "streaming") {
12344
- this.renderStreamingMode(spinner);
13326
+ lines.push(this.formatStreamingLine(spinner));
12345
13327
  } else {
12346
- this.renderCumulativeMode(spinner);
12347
- }
13328
+ lines.push(this.formatCumulativeLine(spinner));
13329
+ }
13330
+ if (this.isTTY) {
13331
+ for (const [gadgetId, gadget] of this.inFlightGadgets) {
13332
+ const elapsed = ((Date.now() - gadget.startTime) / 1e3).toFixed(1);
13333
+ const gadgetLine = ` ${import_chalk4.default.blue("\u23F5")} ${import_chalk4.default.magenta.bold(gadget.name)}${import_chalk4.default.dim("(...)")} ${import_chalk4.default.dim(elapsed + "s")}`;
13334
+ lines.push(gadgetLine);
13335
+ for (const [_agentId, nested] of this.nestedAgents) {
13336
+ if (nested.parentInvocationId !== gadgetId) continue;
13337
+ const indent = " ".repeat(nested.depth + 1);
13338
+ const nestedElapsed = ((Date.now() - nested.startTime) / 1e3).toFixed(1);
13339
+ const tokens = nested.inputTokens ? ` ${import_chalk4.default.dim("\u2191")}${import_chalk4.default.yellow(formatTokens(nested.inputTokens))}` : "";
13340
+ const outTokens = nested.outputTokens ? ` ${import_chalk4.default.dim("\u2193")}${import_chalk4.default.green(formatTokens(nested.outputTokens))}` : "";
13341
+ const nestedLine = `${indent}${import_chalk4.default.cyan(`#${nested.iteration}`)} ${import_chalk4.default.dim(nested.model)}${tokens}${outTokens} ${import_chalk4.default.dim(nestedElapsed + "s")} ${import_chalk4.default.cyan(spinner)}`;
13342
+ lines.push(nestedLine);
13343
+ }
13344
+ for (const [_nestedId, nestedGadget] of this.nestedGadgets) {
13345
+ if (nestedGadget.parentInvocationId === gadgetId) {
13346
+ const indent = " ".repeat(nestedGadget.depth + 1);
13347
+ const nestedElapsed = ((Date.now() - nestedGadget.startTime) / 1e3).toFixed(1);
13348
+ const nestedGadgetLine = `${indent}${import_chalk4.default.blue("\u23F5")} ${import_chalk4.default.dim(nestedGadget.name + "(...)")} ${import_chalk4.default.dim(nestedElapsed + "s")}`;
13349
+ lines.push(nestedGadgetLine);
13350
+ }
13351
+ }
13352
+ }
13353
+ }
13354
+ this.lastRenderLineCount = lines.length;
13355
+ this.target.write("\r" + lines.join("\n"));
12348
13356
  this.hasRendered = true;
12349
13357
  }
12350
- renderStreamingMode(spinner) {
13358
+ /**
13359
+ * Clears the previously rendered lines (for multi-line status display).
13360
+ */
13361
+ clearRenderedLines() {
13362
+ if (!this.hasRendered || this.lastRenderLineCount === 0) return;
13363
+ this.target.write("\r\x1B[K");
13364
+ for (let i = 1; i < this.lastRenderLineCount; i++) {
13365
+ this.target.write("\x1B[1A\x1B[K");
13366
+ }
13367
+ this.target.write("\r");
13368
+ }
13369
+ /**
13370
+ * Format the streaming mode progress line (returns string, doesn't write).
13371
+ */
13372
+ formatStreamingLine(spinner) {
12351
13373
  const elapsed = ((Date.now() - this.callStartTime) / 1e3).toFixed(1);
12352
13374
  const outTokens = this.callOutputTokensEstimated ? Math.round(this.callOutputChars / FALLBACK_CHARS_PER_TOKEN) : this.callOutputTokens;
12353
13375
  const parts = [];
@@ -12381,7 +13403,7 @@ var StreamProgress = class {
12381
13403
  if (callCost > 0) {
12382
13404
  parts.push(import_chalk4.default.cyan(`$${formatCost(callCost)}`));
12383
13405
  }
12384
- this.target.write(`\r${parts.join(import_chalk4.default.dim(" | "))} ${import_chalk4.default.cyan(spinner)}`);
13406
+ return `${parts.join(import_chalk4.default.dim(" | "))} ${import_chalk4.default.cyan(spinner)}`;
12385
13407
  }
12386
13408
  /**
12387
13409
  * Calculates live cost estimate for the current streaming call.
@@ -12418,7 +13440,10 @@ var StreamProgress = class {
12418
13440
  }
12419
13441
  return this.callInputTokens / limits.contextWindow * 100;
12420
13442
  }
12421
- renderCumulativeMode(spinner) {
13443
+ /**
13444
+ * Format the cumulative mode progress line (returns string, doesn't write).
13445
+ */
13446
+ formatCumulativeLine(spinner) {
12422
13447
  const elapsed = ((Date.now() - this.totalStartTime) / 1e3).toFixed(1);
12423
13448
  const parts = [];
12424
13449
  if (this.model) {
@@ -12434,10 +13459,10 @@ var StreamProgress = class {
12434
13459
  parts.push(import_chalk4.default.dim("cost:") + import_chalk4.default.cyan(` $${formatCost(this.totalCost)}`));
12435
13460
  }
12436
13461
  parts.push(import_chalk4.default.dim(`${elapsed}s`));
12437
- this.target.write(`\r${parts.join(import_chalk4.default.dim(" | "))} ${import_chalk4.default.cyan(spinner)}`);
13462
+ return `${parts.join(import_chalk4.default.dim(" | "))} ${import_chalk4.default.cyan(spinner)}`;
12438
13463
  }
12439
13464
  /**
12440
- * Pauses the progress indicator and clears the line.
13465
+ * Pauses the progress indicator and clears all rendered lines.
12441
13466
  * Can be resumed with start().
12442
13467
  */
12443
13468
  pause() {
@@ -12451,10 +13476,9 @@ var StreamProgress = class {
12451
13476
  this.interval = null;
12452
13477
  }
12453
13478
  this.isRunning = false;
12454
- if (this.hasRendered) {
12455
- this.target.write("\r\x1B[K\x1B[0G");
12456
- this.hasRendered = false;
12457
- }
13479
+ this.clearRenderedLines();
13480
+ this.hasRendered = false;
13481
+ this.lastRenderLineCount = 0;
12458
13482
  }
12459
13483
  /**
12460
13484
  * Completes the progress indicator and clears the line.
@@ -12642,6 +13666,7 @@ function configToAgentOptions(config) {
12642
13666
  if (config.docker !== void 0) result.docker = config.docker;
12643
13667
  if (config["docker-cwd-permission"] !== void 0)
12644
13668
  result.dockerCwdPermission = config["docker-cwd-permission"];
13669
+ if (config.subagents !== void 0) result.subagents = config.subagents;
12645
13670
  return result;
12646
13671
  }
12647
13672
 
@@ -12829,7 +13854,12 @@ async function executeAgent(promptArg, options, env) {
12829
13854
  return void 0;
12830
13855
  }
12831
13856
  };
12832
- const builder = new AgentBuilder(client).withModel(options.model).withLogger(env.createLogger("llmist:cli:agent")).withHooks({
13857
+ const resolvedSubagentConfig = buildSubagentConfigMap(
13858
+ options.model,
13859
+ options.subagents,
13860
+ options.globalSubagents
13861
+ );
13862
+ const builder = new AgentBuilder(client).withModel(options.model).withSubagentConfig(resolvedSubagentConfig).withLogger(env.createLogger("llmist:cli:agent")).withHooks({
12833
13863
  observers: {
12834
13864
  // onLLMCallStart: Start progress indicator for each LLM call
12835
13865
  // This showcases how to react to agent lifecycle events
@@ -13025,6 +14055,38 @@ Denied: ${result.reason ?? "by user"}`
13025
14055
  "Maximize efficiency by batching independent operations in a single response."
13026
14056
  ].join(" ")
13027
14057
  );
14058
+ if (!options.quiet) {
14059
+ builder.withNestedEventCallback((event) => {
14060
+ if (event.type === "llm_call_start") {
14061
+ const info = event.event;
14062
+ const nestedId = `${event.gadgetInvocationId}:${info.iteration}`;
14063
+ progress.addNestedAgent(
14064
+ nestedId,
14065
+ event.gadgetInvocationId,
14066
+ event.depth,
14067
+ info.model,
14068
+ info.iteration,
14069
+ info.inputTokens
14070
+ );
14071
+ } else if (event.type === "llm_call_end") {
14072
+ const info = event.event;
14073
+ const nestedId = `${event.gadgetInvocationId}:${info.iteration}`;
14074
+ progress.updateNestedAgent(nestedId, info.outputTokens);
14075
+ setTimeout(() => progress.removeNestedAgent(nestedId), 100);
14076
+ } else if (event.type === "gadget_call") {
14077
+ const gadgetEvent = event.event;
14078
+ progress.addNestedGadget(
14079
+ gadgetEvent.call.invocationId,
14080
+ event.depth,
14081
+ event.gadgetInvocationId,
14082
+ gadgetEvent.call.gadgetName
14083
+ );
14084
+ } else if (event.type === "gadget_result") {
14085
+ const resultEvent = event.event;
14086
+ progress.removeNestedGadget(resultEvent.result.invocationId);
14087
+ }
14088
+ });
14089
+ }
13028
14090
  let agent;
13029
14091
  if (options.image || options.audio) {
13030
14092
  const parts = [text(prompt)];
@@ -13049,10 +14111,22 @@ Denied: ${result.reason ?? "by user"}`
13049
14111
  try {
13050
14112
  for await (const event of agent.run()) {
13051
14113
  if (event.type === "text") {
13052
- progress.pause();
13053
14114
  textBuffer += event.content;
14115
+ } else if (event.type === "gadget_call") {
14116
+ flushTextBuffer();
14117
+ if (!options.quiet) {
14118
+ progress.addGadget(
14119
+ event.call.invocationId,
14120
+ event.call.gadgetName,
14121
+ event.call.parameters
14122
+ );
14123
+ progress.start();
14124
+ }
13054
14125
  } else if (event.type === "gadget_result") {
13055
14126
  flushTextBuffer();
14127
+ if (!options.quiet) {
14128
+ progress.removeGadget(event.result.invocationId);
14129
+ }
13056
14130
  progress.pause();
13057
14131
  if (options.quiet) {
13058
14132
  if (event.result.gadgetName === "TellUser" && event.result.parameters?.message) {
@@ -13067,6 +14141,9 @@ Denied: ${result.reason ?? "by user"}`
13067
14141
  `
13068
14142
  );
13069
14143
  }
14144
+ if (progress.hasInFlightGadgets()) {
14145
+ progress.start();
14146
+ }
13070
14147
  }
13071
14148
  }
13072
14149
  } catch (error) {
@@ -13099,14 +14176,16 @@ Denied: ${result.reason ?? "by user"}`
13099
14176
  }
13100
14177
  }
13101
14178
  }
13102
- function registerAgentCommand(program, env, config) {
14179
+ function registerAgentCommand(program, env, config, globalSubagents) {
13103
14180
  const cmd = program.command(COMMANDS.agent).description("Run the llmist agent loop with optional gadgets.").argument("[prompt]", "Prompt for the agent loop. Falls back to stdin when available.");
13104
14181
  addAgentOptions(cmd, config);
13105
14182
  cmd.action(
13106
14183
  (prompt, options) => executeAction(() => {
13107
14184
  const mergedOptions = {
13108
14185
  ...options,
13109
- gadgetApproval: config?.["gadget-approval"]
14186
+ gadgetApproval: config?.["gadget-approval"],
14187
+ subagents: config?.subagents,
14188
+ globalSubagents
13110
14189
  };
13111
14190
  return executeAgent(prompt, mergedOptions, env);
13112
14191
  }, env)
@@ -13206,6 +14285,104 @@ function registerCompleteCommand(program, env, config) {
13206
14285
  );
13207
14286
  }
13208
14287
 
14288
+ // src/cli/init-command.ts
14289
+ var import_node_fs12 = require("fs");
14290
+ var import_node_path14 = require("path");
14291
+ var STARTER_CONFIG = `# ~/.llmist/cli.toml
14292
+ # llmist CLI configuration file
14293
+ #
14294
+ # This is a minimal starter config. For a comprehensive example with all options:
14295
+ # https://github.com/zbigniewsobiecki/llmist/blob/main/examples/cli.example.toml
14296
+ #
14297
+ # Key concepts:
14298
+ # - Any section can inherit from others using: inherits = "section-name"
14299
+ # - Prompts can use templates with Eta syntax: <%~ include("@prompt-name") %>
14300
+ # - Custom sections become CLI commands: [my-command] -> llmist my-command
14301
+
14302
+ #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
14303
+ # GLOBAL OPTIONS
14304
+ # These apply to all commands. CLI flags override these settings.
14305
+ #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
14306
+ [global]
14307
+ # log-level = "info" # silly, trace, debug, info, warn, error, fatal
14308
+ # log-file = "/tmp/llmist.log" # Enable file logging (JSON format)
14309
+
14310
+ #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
14311
+ # COMPLETE COMMAND DEFAULTS
14312
+ # For single LLM responses: llmist complete "prompt"
14313
+ # Model format: provider:model (e.g., openai:gpt-4o, anthropic:claude-sonnet-4-5)
14314
+ #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
14315
+ [complete]
14316
+ # model = "openai:gpt-4o"
14317
+ # temperature = 0.7 # 0-2, higher = more creative
14318
+ # max-tokens = 4096 # Maximum response length
14319
+
14320
+ #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
14321
+ # AGENT COMMAND DEFAULTS
14322
+ # For tool-using agents: llmist agent "prompt"
14323
+ #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
14324
+ [agent]
14325
+ # model = "anthropic:claude-sonnet-4-5"
14326
+ # max-iterations = 15 # Max tool-use loops before stopping
14327
+ # gadgets = [ # Tools the agent can use
14328
+ # "ListDirectory",
14329
+ # "ReadFile",
14330
+ # "WriteFile",
14331
+ # ]
14332
+
14333
+ #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
14334
+ # CUSTOM COMMANDS
14335
+ # Any other section becomes a new CLI command!
14336
+ # Uncomment below to create: llmist summarize "your text"
14337
+ #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
14338
+ # [summarize]
14339
+ # type = "complete" # "complete" or "agent"
14340
+ # description = "Summarize text concisely."
14341
+ # system = "Summarize the following text in 2-3 bullet points."
14342
+ # temperature = 0.3
14343
+ `;
14344
+ async function executeInit(_options, env) {
14345
+ const configPath = getConfigPath();
14346
+ const configDir = (0, import_node_path14.dirname)(configPath);
14347
+ if ((0, import_node_fs12.existsSync)(configPath)) {
14348
+ env.stderr.write(`Configuration already exists at ${configPath}
14349
+ `);
14350
+ env.stderr.write("\n");
14351
+ env.stderr.write(`To view it: cat ${configPath}
14352
+ `);
14353
+ env.stderr.write(`To reset: rm ${configPath} && llmist init
14354
+ `);
14355
+ return;
14356
+ }
14357
+ if (!(0, import_node_fs12.existsSync)(configDir)) {
14358
+ (0, import_node_fs12.mkdirSync)(configDir, { recursive: true });
14359
+ }
14360
+ (0, import_node_fs12.writeFileSync)(configPath, STARTER_CONFIG, "utf-8");
14361
+ env.stderr.write(`Created ${configPath}
14362
+ `);
14363
+ env.stderr.write("\n");
14364
+ env.stderr.write("Next steps:\n");
14365
+ env.stderr.write(" 1. Set your API key:\n");
14366
+ env.stderr.write(" export OPENAI_API_KEY=sk-...\n");
14367
+ env.stderr.write(" export ANTHROPIC_API_KEY=sk-...\n");
14368
+ env.stderr.write(" export GEMINI_API_KEY=...\n");
14369
+ env.stderr.write("\n");
14370
+ env.stderr.write(` 2. Customize your config:
14371
+ `);
14372
+ env.stderr.write(` $EDITOR ${configPath}
14373
+ `);
14374
+ env.stderr.write("\n");
14375
+ env.stderr.write(" 3. See all options:\n");
14376
+ env.stderr.write(
14377
+ " https://github.com/zbigniewsobiecki/llmist/blob/main/examples/cli.example.toml\n"
14378
+ );
14379
+ env.stderr.write("\n");
14380
+ env.stderr.write('Try it: llmist complete "Hello, world!"\n');
14381
+ }
14382
+ function registerInitCommand(program, env) {
14383
+ program.command(COMMANDS.init).description("Initialize llmist configuration at ~/.llmist/cli.toml").action((options) => executeAction(() => executeInit(options, env), env));
14384
+ }
14385
+
13209
14386
  // src/cli/environment.ts
13210
14387
  var import_node_readline = __toESM(require("readline"), 1);
13211
14388
  var import_chalk6 = __toESM(require("chalk"), 1);
@@ -13308,7 +14485,7 @@ function createCommandEnvironment(baseEnv, config) {
13308
14485
  createLogger: createLoggerFactory(loggerConfig)
13309
14486
  };
13310
14487
  }
13311
- function registerCustomCommand(program, name, config, env) {
14488
+ function registerCustomCommand(program, name, config, env, globalSubagents) {
13312
14489
  const type = config.type ?? "agent";
13313
14490
  const description = config.description ?? `Custom ${type} command`;
13314
14491
  const cmd = program.command(name).description(description).argument("[prompt]", "Prompt for the command. Falls back to stdin when available.");
@@ -13333,7 +14510,8 @@ function registerCustomCommand(program, name, config, env) {
13333
14510
  const configDefaults = configToAgentOptions(config);
13334
14511
  const options = {
13335
14512
  ...configDefaults,
13336
- ...cliOptions
14513
+ ...cliOptions,
14514
+ globalSubagents
13337
14515
  };
13338
14516
  await executeAgent(prompt, options, cmdEnv);
13339
14517
  }, cmdEnv);
@@ -13776,7 +14954,7 @@ function registerGadgetCommand(program, env) {
13776
14954
  }
13777
14955
 
13778
14956
  // src/cli/image-command.ts
13779
- var import_node_fs11 = require("fs");
14957
+ var import_node_fs13 = require("fs");
13780
14958
  var DEFAULT_IMAGE_MODEL = "dall-e-3";
13781
14959
  async function executeImage(promptArg, options, env) {
13782
14960
  const prompt = await resolvePrompt(promptArg, env);
@@ -13800,7 +14978,7 @@ async function executeImage(promptArg, options, env) {
13800
14978
  const imageData = result.images[0];
13801
14979
  if (imageData.b64Json) {
13802
14980
  const buffer = Buffer.from(imageData.b64Json, "base64");
13803
- (0, import_node_fs11.writeFileSync)(options.output, buffer);
14981
+ (0, import_node_fs13.writeFileSync)(options.output, buffer);
13804
14982
  if (!options.quiet) {
13805
14983
  env.stderr.write(`${SUMMARY_PREFIX} Image saved to ${options.output}
13806
14984
  `);
@@ -14241,7 +15419,7 @@ function registerModelsCommand(program, env) {
14241
15419
  }
14242
15420
 
14243
15421
  // src/cli/speech-command.ts
14244
- var import_node_fs12 = require("fs");
15422
+ var import_node_fs14 = require("fs");
14245
15423
  var DEFAULT_SPEECH_MODEL = "tts-1";
14246
15424
  var DEFAULT_VOICE = "nova";
14247
15425
  async function executeSpeech(textArg, options, env) {
@@ -14264,7 +15442,7 @@ async function executeSpeech(textArg, options, env) {
14264
15442
  });
14265
15443
  const audioBuffer = Buffer.from(result.audio);
14266
15444
  if (options.output) {
14267
- (0, import_node_fs12.writeFileSync)(options.output, audioBuffer);
15445
+ (0, import_node_fs14.writeFileSync)(options.output, audioBuffer);
14268
15446
  if (!options.quiet) {
14269
15447
  env.stderr.write(`${SUMMARY_PREFIX} Audio saved to ${options.output}
14270
15448
  `);
@@ -14338,17 +15516,18 @@ function createProgram(env, config) {
14338
15516
  writeErr: (str) => env.stderr.write(str)
14339
15517
  });
14340
15518
  registerCompleteCommand(program, env, config?.complete);
14341
- registerAgentCommand(program, env, config?.agent);
15519
+ registerAgentCommand(program, env, config?.agent, config?.subagents);
14342
15520
  registerImageCommand(program, env, config?.image);
14343
15521
  registerSpeechCommand(program, env, config?.speech);
14344
15522
  registerVisionCommand(program, env);
14345
15523
  registerModelsCommand(program, env);
14346
15524
  registerGadgetCommand(program, env);
15525
+ registerInitCommand(program, env);
14347
15526
  if (config) {
14348
15527
  const customNames = getCustomCommandNames(config);
14349
15528
  for (const name of customNames) {
14350
15529
  const cmdConfig = config[name];
14351
- registerCustomCommand(program, name, cmdConfig, env);
15530
+ registerCustomCommand(program, name, cmdConfig, env, config.subagents);
14352
15531
  }
14353
15532
  }
14354
15533
  return program;