llmist 2.6.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,16 @@ 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) {
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;
3499
3507
  this.logger = logger ?? createLogger({ name: "llmist:executor" });
3500
- this.errorFormatter = new GadgetErrorFormatter(errorFormatterOptions);
3508
+ this.errorFormatter = new GadgetExecutionErrorFormatter(errorFormatterOptions);
3501
3509
  this.argPrefix = errorFormatterOptions?.argPrefix ?? GADGET_ARG_PREFIX;
3502
3510
  }
3503
3511
  logger;
@@ -3517,11 +3525,11 @@ var init_executor = __esm({
3517
3525
  });
3518
3526
  }
3519
3527
  /**
3520
- * Normalizes gadget execute result to consistent format.
3528
+ * Unify gadget execute result to consistent internal format.
3521
3529
  * Handles string returns (backwards compat), object returns with cost,
3522
3530
  * and object returns with media.
3523
3531
  */
3524
- normalizeExecuteResult(raw) {
3532
+ unifyExecuteResult(raw) {
3525
3533
  if (typeof raw === "string") {
3526
3534
  return { result: raw, cost: 0 };
3527
3535
  }
@@ -3639,7 +3647,9 @@ var init_executor = __esm({
3639
3647
  const ctx = {
3640
3648
  reportCost,
3641
3649
  llmist: this.client ? new CostReportingLLMistWrapper(this.client, reportCost) : void 0,
3642
- signal: abortController.signal
3650
+ signal: abortController.signal,
3651
+ agentConfig: this.agentConfig,
3652
+ subagentConfig: this.subagentConfig
3643
3653
  };
3644
3654
  let rawResult;
3645
3655
  if (timeoutMs && timeoutMs > 0) {
@@ -3654,7 +3664,7 @@ var init_executor = __esm({
3654
3664
  } else {
3655
3665
  rawResult = await Promise.resolve(gadget.execute(validatedParameters, ctx));
3656
3666
  }
3657
- const { result, media, cost: returnCost } = this.normalizeExecuteResult(rawResult);
3667
+ const { result, media, cost: returnCost } = this.unifyExecuteResult(rawResult);
3658
3668
  const totalCost = callbackCost + returnCost;
3659
3669
  let mediaIds;
3660
3670
  let storedMedia;
@@ -3700,7 +3710,7 @@ var init_executor = __esm({
3700
3710
  storedMedia
3701
3711
  };
3702
3712
  } catch (error) {
3703
- if (error instanceof BreakLoopException) {
3713
+ if (error instanceof TaskCompletionSignal) {
3704
3714
  this.logger.info("Gadget requested loop termination", {
3705
3715
  gadgetName: call.gadgetName,
3706
3716
  message: error.message
@@ -3728,7 +3738,7 @@ var init_executor = __esm({
3728
3738
  executionTimeMs: Date.now() - startTime
3729
3739
  };
3730
3740
  }
3731
- if (error instanceof AbortError) {
3741
+ if (error instanceof AbortException) {
3732
3742
  this.logger.info("Gadget execution was aborted", {
3733
3743
  gadgetName: call.gadgetName,
3734
3744
  executionTimeMs: Date.now() - startTime
@@ -3741,14 +3751,14 @@ var init_executor = __esm({
3741
3751
  executionTimeMs: Date.now() - startTime
3742
3752
  };
3743
3753
  }
3744
- if (error instanceof HumanInputException) {
3754
+ if (error instanceof HumanInputRequiredException) {
3745
3755
  this.logger.info("Gadget requested human input", {
3746
3756
  gadgetName: call.gadgetName,
3747
3757
  question: error.question
3748
3758
  });
3749
- if (this.onHumanInputRequired) {
3759
+ if (this.requestHumanInput) {
3750
3760
  try {
3751
- const answer = await this.onHumanInputRequired(error.question);
3761
+ const answer = await this.requestHumanInput(error.question);
3752
3762
  this.logger.debug("Human input received", {
3753
3763
  gadgetName: call.gadgetName,
3754
3764
  answerLength: answer.length
@@ -3846,13 +3856,13 @@ var init_stream_processor = __esm({
3846
3856
  parser;
3847
3857
  executor;
3848
3858
  stopOnGadgetError;
3849
- shouldContinueAfterError;
3850
- accumulatedText = "";
3851
- shouldStopExecution = false;
3859
+ canRecoverFromGadgetError;
3860
+ responseText = "";
3861
+ executionHalted = false;
3852
3862
  observerFailureCount = 0;
3853
3863
  // Dependency tracking for gadget execution DAG
3854
3864
  /** Gadgets waiting for their dependencies to complete */
3855
- pendingGadgets = /* @__PURE__ */ new Map();
3865
+ gadgetsAwaitingDependencies = /* @__PURE__ */ new Map();
3856
3866
  /** Completed gadget results, keyed by invocation ID */
3857
3867
  completedResults = /* @__PURE__ */ new Map();
3858
3868
  /** Invocation IDs of gadgets that have failed (error or skipped due to dependency) */
@@ -3863,20 +3873,22 @@ var init_stream_processor = __esm({
3863
3873
  this.hooks = options.hooks ?? {};
3864
3874
  this.logger = options.logger ?? createLogger({ name: "llmist:stream-processor" });
3865
3875
  this.stopOnGadgetError = options.stopOnGadgetError ?? true;
3866
- this.shouldContinueAfterError = options.shouldContinueAfterError;
3867
- this.parser = new StreamParser({
3876
+ this.canRecoverFromGadgetError = options.canRecoverFromGadgetError;
3877
+ this.parser = new GadgetCallParser({
3868
3878
  startPrefix: options.gadgetStartPrefix,
3869
3879
  endPrefix: options.gadgetEndPrefix,
3870
3880
  argPrefix: options.gadgetArgPrefix
3871
3881
  });
3872
3882
  this.executor = new GadgetExecutor(
3873
3883
  options.registry,
3874
- options.onHumanInputRequired,
3884
+ options.requestHumanInput,
3875
3885
  this.logger.getSubLogger({ name: "executor" }),
3876
3886
  options.defaultGadgetTimeoutMs,
3877
3887
  { argPrefix: options.gadgetArgPrefix },
3878
3888
  options.client,
3879
- options.mediaStore
3889
+ options.mediaStore,
3890
+ options.agentConfig,
3891
+ options.subagentConfig
3880
3892
  );
3881
3893
  }
3882
3894
  /**
@@ -3897,7 +3909,7 @@ var init_stream_processor = __esm({
3897
3909
  if (this.hooks.interceptors?.interceptRawChunk) {
3898
3910
  const context = {
3899
3911
  iteration: this.iteration,
3900
- accumulatedText: this.accumulatedText,
3912
+ accumulatedText: this.responseText,
3901
3913
  logger: this.logger
3902
3914
  };
3903
3915
  const intercepted = this.hooks.interceptors.interceptRawChunk(processedChunk, context);
@@ -3908,7 +3920,7 @@ var init_stream_processor = __esm({
3908
3920
  }
3909
3921
  }
3910
3922
  if (processedChunk) {
3911
- this.accumulatedText += processedChunk;
3923
+ this.responseText += processedChunk;
3912
3924
  }
3913
3925
  }
3914
3926
  if (this.hooks.observers?.onStreamChunk && (processedChunk || chunk.usage)) {
@@ -3917,7 +3929,7 @@ var init_stream_processor = __esm({
3917
3929
  const context = {
3918
3930
  iteration: this.iteration,
3919
3931
  rawChunk: processedChunk,
3920
- accumulatedText: this.accumulatedText,
3932
+ accumulatedText: this.responseText,
3921
3933
  usage,
3922
3934
  logger: this.logger
3923
3935
  };
@@ -3940,12 +3952,12 @@ var init_stream_processor = __esm({
3940
3952
  }
3941
3953
  }
3942
3954
  }
3943
- if (this.shouldStopExecution) {
3955
+ if (this.executionHalted) {
3944
3956
  this.logger.info("Breaking from LLM stream due to gadget error");
3945
3957
  break;
3946
3958
  }
3947
3959
  }
3948
- if (!this.shouldStopExecution) {
3960
+ if (!this.executionHalted) {
3949
3961
  for (const event of this.parser.finalize()) {
3950
3962
  const processedEvents = await this.processEvent(event);
3951
3963
  outputs.push(...processedEvents);
@@ -3969,11 +3981,11 @@ var init_stream_processor = __esm({
3969
3981
  }
3970
3982
  }
3971
3983
  }
3972
- let finalMessage = this.accumulatedText;
3984
+ let finalMessage = this.responseText;
3973
3985
  if (this.hooks.interceptors?.interceptAssistantMessage) {
3974
3986
  const context = {
3975
3987
  iteration: this.iteration,
3976
- rawResponse: this.accumulatedText,
3988
+ rawResponse: this.responseText,
3977
3989
  logger: this.logger
3978
3990
  };
3979
3991
  finalMessage = this.hooks.interceptors.interceptAssistantMessage(finalMessage, context);
@@ -3984,7 +3996,7 @@ var init_stream_processor = __esm({
3984
3996
  didExecuteGadgets,
3985
3997
  finishReason,
3986
3998
  usage,
3987
- rawResponse: this.accumulatedText,
3999
+ rawResponse: this.responseText,
3988
4000
  finalMessage
3989
4001
  };
3990
4002
  }
@@ -4007,7 +4019,7 @@ var init_stream_processor = __esm({
4007
4019
  if (this.hooks.interceptors?.interceptTextChunk) {
4008
4020
  const context = {
4009
4021
  iteration: this.iteration,
4010
- accumulatedText: this.accumulatedText,
4022
+ accumulatedText: this.responseText,
4011
4023
  logger: this.logger
4012
4024
  };
4013
4025
  const intercepted = this.hooks.interceptors.interceptTextChunk(content, context);
@@ -4026,7 +4038,7 @@ var init_stream_processor = __esm({
4026
4038
  * After each execution, pending gadgets are checked to see if they can now run.
4027
4039
  */
4028
4040
  async processGadgetCall(call) {
4029
- if (this.shouldStopExecution) {
4041
+ if (this.executionHalted) {
4030
4042
  this.logger.debug("Skipping gadget execution due to previous error", {
4031
4043
  gadgetName: call.gadgetName
4032
4044
  });
@@ -4065,7 +4077,7 @@ var init_stream_processor = __esm({
4065
4077
  invocationId: call.invocationId,
4066
4078
  waitingOn: unsatisfied
4067
4079
  });
4068
- this.pendingGadgets.set(call.invocationId, call);
4080
+ this.gadgetsAwaitingDependencies.set(call.invocationId, call);
4069
4081
  return events;
4070
4082
  }
4071
4083
  }
@@ -4087,14 +4099,14 @@ var init_stream_processor = __esm({
4087
4099
  error: call.parseError,
4088
4100
  rawParameters: call.parametersRaw
4089
4101
  });
4090
- const shouldContinue = await this.checkContinueAfterError(
4102
+ const shouldContinue = await this.checkCanRecoverFromError(
4091
4103
  call.parseError,
4092
4104
  call.gadgetName,
4093
4105
  "parse",
4094
4106
  call.parameters
4095
4107
  );
4096
4108
  if (!shouldContinue) {
4097
- this.shouldStopExecution = true;
4109
+ this.executionHalted = true;
4098
4110
  }
4099
4111
  }
4100
4112
  let parameters = call.parameters ?? {};
@@ -4218,14 +4230,14 @@ var init_stream_processor = __esm({
4218
4230
  events.push({ type: "gadget_result", result });
4219
4231
  if (result.error) {
4220
4232
  const errorType = this.determineErrorType(call, result);
4221
- const shouldContinue = await this.checkContinueAfterError(
4233
+ const shouldContinue = await this.checkCanRecoverFromError(
4222
4234
  result.error,
4223
4235
  result.gadgetName,
4224
4236
  errorType,
4225
4237
  result.parameters
4226
4238
  );
4227
4239
  if (!shouldContinue) {
4228
- this.shouldStopExecution = true;
4240
+ this.executionHalted = true;
4229
4241
  }
4230
4242
  }
4231
4243
  return events;
@@ -4312,11 +4324,11 @@ var init_stream_processor = __esm({
4312
4324
  async processPendingGadgets() {
4313
4325
  const events = [];
4314
4326
  let progress = true;
4315
- while (progress && this.pendingGadgets.size > 0) {
4327
+ while (progress && this.gadgetsAwaitingDependencies.size > 0) {
4316
4328
  progress = false;
4317
4329
  const readyToExecute = [];
4318
4330
  const readyToSkip = [];
4319
- for (const [invocationId, call] of this.pendingGadgets) {
4331
+ for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
4320
4332
  const failedDep = call.dependencies.find((dep) => this.failedInvocations.has(dep));
4321
4333
  if (failedDep) {
4322
4334
  readyToSkip.push({ call, failedDep });
@@ -4328,7 +4340,7 @@ var init_stream_processor = __esm({
4328
4340
  }
4329
4341
  }
4330
4342
  for (const { call, failedDep } of readyToSkip) {
4331
- this.pendingGadgets.delete(call.invocationId);
4343
+ this.gadgetsAwaitingDependencies.delete(call.invocationId);
4332
4344
  const skipEvents = await this.handleFailedDependency(call, failedDep);
4333
4345
  events.push(...skipEvents);
4334
4346
  progress = true;
@@ -4339,7 +4351,7 @@ var init_stream_processor = __esm({
4339
4351
  invocationIds: readyToExecute.map((c) => c.invocationId)
4340
4352
  });
4341
4353
  for (const call of readyToExecute) {
4342
- this.pendingGadgets.delete(call.invocationId);
4354
+ this.gadgetsAwaitingDependencies.delete(call.invocationId);
4343
4355
  }
4344
4356
  const executePromises = readyToExecute.map((call) => this.executeGadgetWithHooks(call));
4345
4357
  const results = await Promise.all(executePromises);
@@ -4349,9 +4361,9 @@ var init_stream_processor = __esm({
4349
4361
  progress = true;
4350
4362
  }
4351
4363
  }
4352
- if (this.pendingGadgets.size > 0) {
4353
- const pendingIds = new Set(this.pendingGadgets.keys());
4354
- for (const [invocationId, call] of this.pendingGadgets) {
4364
+ if (this.gadgetsAwaitingDependencies.size > 0) {
4365
+ const pendingIds = new Set(this.gadgetsAwaitingDependencies.keys());
4366
+ for (const [invocationId, call] of this.gadgetsAwaitingDependencies) {
4355
4367
  const missingDeps = call.dependencies.filter((dep) => !this.completedResults.has(dep));
4356
4368
  const circularDeps = missingDeps.filter((dep) => pendingIds.has(dep));
4357
4369
  const trulyMissingDeps = missingDeps.filter((dep) => !pendingIds.has(dep));
@@ -4382,7 +4394,7 @@ var init_stream_processor = __esm({
4382
4394
  };
4383
4395
  events.push(skipEvent);
4384
4396
  }
4385
- this.pendingGadgets.clear();
4397
+ this.gadgetsAwaitingDependencies.clear();
4386
4398
  }
4387
4399
  return events;
4388
4400
  }
@@ -4412,19 +4424,19 @@ var init_stream_processor = __esm({
4412
4424
  );
4413
4425
  }
4414
4426
  /**
4415
- * Check if execution should continue after an error.
4427
+ * Check if execution can recover from an error.
4416
4428
  *
4417
4429
  * Returns true if we should continue processing subsequent gadgets, false if we should stop.
4418
4430
  *
4419
4431
  * Logic:
4420
- * - If custom shouldContinueAfterError is provided, use it
4432
+ * - If custom canRecoverFromGadgetError is provided, use it
4421
4433
  * - Otherwise, use stopOnGadgetError config:
4422
4434
  * - stopOnGadgetError=true → return false (stop execution)
4423
4435
  * - stopOnGadgetError=false → return true (continue execution)
4424
4436
  */
4425
- async checkContinueAfterError(error, gadgetName, errorType, parameters) {
4426
- if (this.shouldContinueAfterError) {
4427
- return await this.shouldContinueAfterError({
4437
+ async checkCanRecoverFromError(error, gadgetName, errorType, parameters) {
4438
+ if (this.canRecoverFromGadgetError) {
4439
+ return await this.canRecoverFromGadgetError({
4428
4440
  error,
4429
4441
  gadgetName,
4430
4442
  errorType,
@@ -4487,14 +4499,14 @@ var init_agent = __esm({
4487
4499
  gadgetStartPrefix;
4488
4500
  gadgetEndPrefix;
4489
4501
  gadgetArgPrefix;
4490
- onHumanInputRequired;
4502
+ requestHumanInput;
4491
4503
  textOnlyHandler;
4492
4504
  textWithGadgetsHandler;
4493
4505
  stopOnGadgetError;
4494
- shouldContinueAfterError;
4506
+ canRecoverFromGadgetError;
4495
4507
  defaultGadgetTimeoutMs;
4496
4508
  defaultMaxTokens;
4497
- userPromptProvided;
4509
+ hasUserPrompt;
4498
4510
  // Gadget output limiting
4499
4511
  outputStore;
4500
4512
  outputLimitEnabled;
@@ -4505,6 +4517,9 @@ var init_agent = __esm({
4505
4517
  mediaStore;
4506
4518
  // Cancellation
4507
4519
  signal;
4520
+ // Subagent configuration
4521
+ agentContextConfig;
4522
+ subagentConfig;
4508
4523
  /**
4509
4524
  * Creates a new Agent instance.
4510
4525
  * @internal This constructor is private. Use LLMist.createAgent() or AgentBuilder instead.
@@ -4524,11 +4539,11 @@ var init_agent = __esm({
4524
4539
  this.gadgetStartPrefix = options.gadgetStartPrefix;
4525
4540
  this.gadgetEndPrefix = options.gadgetEndPrefix;
4526
4541
  this.gadgetArgPrefix = options.gadgetArgPrefix;
4527
- this.onHumanInputRequired = options.onHumanInputRequired;
4542
+ this.requestHumanInput = options.requestHumanInput;
4528
4543
  this.textOnlyHandler = options.textOnlyHandler ?? "terminate";
4529
4544
  this.textWithGadgetsHandler = options.textWithGadgetsHandler;
4530
4545
  this.stopOnGadgetError = options.stopOnGadgetError ?? true;
4531
- this.shouldContinueAfterError = options.shouldContinueAfterError;
4546
+ this.canRecoverFromGadgetError = options.canRecoverFromGadgetError;
4532
4547
  this.defaultGadgetTimeoutMs = options.defaultGadgetTimeoutMs;
4533
4548
  this.defaultMaxTokens = this.resolveMaxTokensFromCatalog(options.model);
4534
4549
  this.outputLimitEnabled = options.gadgetOutputLimit ?? DEFAULT_GADGET_OUTPUT_LIMIT;
@@ -4544,7 +4559,7 @@ var init_agent = __esm({
4544
4559
  createGadgetOutputViewer(this.outputStore, this.outputLimitCharLimit)
4545
4560
  );
4546
4561
  }
4547
- this.hooks = this.mergeOutputLimiterHook(options.hooks);
4562
+ this.hooks = this.chainOutputLimiterWithUserHooks(options.hooks);
4548
4563
  const baseBuilder = new LLMMessageBuilder(options.promptConfig);
4549
4564
  if (options.systemPrompt) {
4550
4565
  baseBuilder.addSystem(options.systemPrompt);
@@ -4564,7 +4579,7 @@ var init_agent = __esm({
4564
4579
  endPrefix: options.gadgetEndPrefix,
4565
4580
  argPrefix: options.gadgetArgPrefix
4566
4581
  });
4567
- this.userPromptProvided = !!options.userPrompt;
4582
+ this.hasUserPrompt = !!options.userPrompt;
4568
4583
  if (options.userPrompt) {
4569
4584
  this.conversation.addUserMessage(options.userPrompt);
4570
4585
  }
@@ -4577,6 +4592,11 @@ var init_agent = __esm({
4577
4592
  );
4578
4593
  }
4579
4594
  this.signal = options.signal;
4595
+ this.agentContextConfig = {
4596
+ model: this.model,
4597
+ temperature: this.temperature
4598
+ };
4599
+ this.subagentConfig = options.subagentConfig;
4580
4600
  }
4581
4601
  /**
4582
4602
  * Get the gadget registry for this agent.
@@ -4683,7 +4703,7 @@ var init_agent = __esm({
4683
4703
  * @throws {Error} If no user prompt was provided (when using build() without ask())
4684
4704
  */
4685
4705
  async *run() {
4686
- if (!this.userPromptProvided) {
4706
+ if (!this.hasUserPrompt) {
4687
4707
  throw new Error(
4688
4708
  "No user prompt provided. Use .ask(prompt) instead of .build(), or call agent.run() after providing a prompt."
4689
4709
  );
@@ -4800,12 +4820,14 @@ var init_agent = __esm({
4800
4820
  gadgetArgPrefix: this.gadgetArgPrefix,
4801
4821
  hooks: this.hooks,
4802
4822
  logger: this.logger.getSubLogger({ name: "stream-processor" }),
4803
- onHumanInputRequired: this.onHumanInputRequired,
4823
+ requestHumanInput: this.requestHumanInput,
4804
4824
  stopOnGadgetError: this.stopOnGadgetError,
4805
- shouldContinueAfterError: this.shouldContinueAfterError,
4825
+ canRecoverFromGadgetError: this.canRecoverFromGadgetError,
4806
4826
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
4807
4827
  client: this.client,
4808
- mediaStore: this.mediaStore
4828
+ mediaStore: this.mediaStore,
4829
+ agentConfig: this.agentContextConfig,
4830
+ subagentConfig: this.subagentConfig
4809
4831
  });
4810
4832
  const result = await processor.process(stream2);
4811
4833
  for (const output of result.outputs) {
@@ -4858,9 +4880,9 @@ var init_agent = __esm({
4858
4880
  if (msg.role === "user") {
4859
4881
  this.conversation.addUserMessage(msg.content);
4860
4882
  } else if (msg.role === "assistant") {
4861
- this.conversation.addAssistantMessage(extractText(msg.content));
4883
+ this.conversation.addAssistantMessage(extractMessageText(msg.content));
4862
4884
  } else if (msg.role === "system") {
4863
- this.conversation.addUserMessage(`[System] ${extractText(msg.content)}`);
4885
+ this.conversation.addUserMessage(`[System] ${extractMessageText(msg.content)}`);
4864
4886
  }
4865
4887
  }
4866
4888
  }
@@ -4872,7 +4894,7 @@ var init_agent = __esm({
4872
4894
  ).map((output) => output.content).join("");
4873
4895
  if (textContent.trim()) {
4874
4896
  const { gadgetName, parameterMapping, resultMapping } = this.textWithGadgetsHandler;
4875
- this.conversation.addGadgetCall(
4897
+ this.conversation.addGadgetCallResult(
4876
4898
  gadgetName,
4877
4899
  parameterMapping(textContent),
4878
4900
  resultMapping ? resultMapping(textContent) : textContent
@@ -4882,7 +4904,7 @@ var init_agent = __esm({
4882
4904
  for (const output of result.outputs) {
4883
4905
  if (output.type === "gadget_result") {
4884
4906
  const gadgetResult = output.result;
4885
- this.conversation.addGadgetCall(
4907
+ this.conversation.addGadgetCallResult(
4886
4908
  gadgetResult.gadgetName,
4887
4909
  gadgetResult.parameters,
4888
4910
  gadgetResult.error ?? gadgetResult.result ?? "",
@@ -4893,7 +4915,7 @@ var init_agent = __esm({
4893
4915
  }
4894
4916
  } else {
4895
4917
  if (finalMessage.trim()) {
4896
- this.conversation.addGadgetCall(
4918
+ this.conversation.addGadgetCallResult(
4897
4919
  "TellUser",
4898
4920
  { message: finalMessage, done: false, type: "info" },
4899
4921
  `\u2139\uFE0F ${finalMessage}`
@@ -5019,10 +5041,10 @@ var init_agent = __esm({
5019
5041
  return this.client.modelRegistry.getModelLimits(unprefixedModelId)?.maxOutputTokens;
5020
5042
  }
5021
5043
  /**
5022
- * Merge the output limiter interceptor into user-provided hooks.
5044
+ * Chain the output limiter interceptor with user-provided hooks.
5023
5045
  * The limiter runs first, then chains to any user interceptor.
5024
5046
  */
5025
- mergeOutputLimiterHook(userHooks) {
5047
+ chainOutputLimiterWithUserHooks(userHooks) {
5026
5048
  if (!this.outputLimitEnabled) {
5027
5049
  return userHooks ?? {};
5028
5050
  }
@@ -5376,9 +5398,9 @@ var init_base_provider = __esm({
5376
5398
  */
5377
5399
  async *stream(options, descriptor, spec) {
5378
5400
  const preparedMessages = this.prepareMessages(options.messages);
5379
- const payload = this.buildRequestPayload(options, descriptor, spec, preparedMessages);
5401
+ const payload = this.buildApiRequest(options, descriptor, spec, preparedMessages);
5380
5402
  const rawStream = await this.executeStreamRequest(payload, options.signal);
5381
- yield* this.wrapStream(rawStream);
5403
+ yield* this.normalizeProviderStream(rawStream);
5382
5404
  }
5383
5405
  /**
5384
5406
  * Prepare messages for the request.
@@ -5478,11 +5500,11 @@ var init_anthropic = __esm({
5478
5500
  "Anthropic does not support speech generation. Use OpenAI (TTS) or Google Gemini (TTS) instead."
5479
5501
  );
5480
5502
  }
5481
- buildRequestPayload(options, descriptor, spec, messages) {
5503
+ buildApiRequest(options, descriptor, spec, messages) {
5482
5504
  const systemMessages = messages.filter((message) => message.role === "system");
5483
5505
  const system = systemMessages.length > 0 ? systemMessages.map((m, index) => ({
5484
5506
  type: "text",
5485
- text: extractText(m.content),
5507
+ text: extractMessageText(m.content),
5486
5508
  // Add cache_control to the LAST system message block
5487
5509
  ...index === systemMessages.length - 1 ? { cache_control: { type: "ephemeral" } } : {}
5488
5510
  })) : void 0;
@@ -5519,7 +5541,7 @@ var init_anthropic = __esm({
5519
5541
  * Handles text, images (base64 only), and applies cache_control.
5520
5542
  */
5521
5543
  convertToAnthropicContent(content, addCacheControl) {
5522
- const parts = normalizeContent(content);
5544
+ const parts = normalizeMessageContent(content);
5523
5545
  return parts.map((part, index) => {
5524
5546
  const isLastPart = index === parts.length - 1;
5525
5547
  const cacheControl = addCacheControl && isLastPart ? { cache_control: { type: "ephemeral" } } : {};
@@ -5565,7 +5587,7 @@ var init_anthropic = __esm({
5565
5587
  const stream2 = await client.messages.create(payload, signal ? { signal } : void 0);
5566
5588
  return stream2;
5567
5589
  }
5568
- async *wrapStream(iterable) {
5590
+ async *normalizeProviderStream(iterable) {
5569
5591
  const stream2 = iterable;
5570
5592
  let inputTokens = 0;
5571
5593
  let cachedInputTokens = 0;
@@ -5642,7 +5664,7 @@ var init_anthropic = __esm({
5642
5664
  async countTokens(messages, descriptor, _spec) {
5643
5665
  const client = this.client;
5644
5666
  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;
5667
+ const system = systemMessages.length > 0 ? systemMessages.map((m) => extractMessageText(m.content)).join("\n\n") : void 0;
5646
5668
  const conversation = messages.filter(
5647
5669
  (message) => message.role !== "system"
5648
5670
  ).map((message) => ({
@@ -5664,7 +5686,7 @@ var init_anthropic = __esm({
5664
5686
  let totalChars = 0;
5665
5687
  let imageCount = 0;
5666
5688
  for (const msg of messages) {
5667
- const parts = normalizeContent(msg.content);
5689
+ const parts = normalizeMessageContent(msg.content);
5668
5690
  for (const part of parts) {
5669
5691
  if (part.type === "text") {
5670
5692
  totalChars += part.text.length;
@@ -6355,7 +6377,7 @@ var init_gemini = __esm({
6355
6377
  format: spec?.defaultFormat ?? "wav"
6356
6378
  };
6357
6379
  }
6358
- buildRequestPayload(options, descriptor, _spec, messages) {
6380
+ buildApiRequest(options, descriptor, _spec, messages) {
6359
6381
  const contents = this.convertMessagesToContents(messages);
6360
6382
  const generationConfig = this.buildGenerationConfig(options);
6361
6383
  const config = {
@@ -6407,7 +6429,7 @@ var init_gemini = __esm({
6407
6429
  if (message.role === "system") {
6408
6430
  expandedMessages.push({
6409
6431
  role: "user",
6410
- content: extractText(message.content)
6432
+ content: extractMessageText(message.content)
6411
6433
  });
6412
6434
  expandedMessages.push({
6413
6435
  role: "assistant",
@@ -6457,7 +6479,7 @@ var init_gemini = __esm({
6457
6479
  * Handles text, images, and audio (Gemini supports all three).
6458
6480
  */
6459
6481
  convertToGeminiParts(content) {
6460
- const parts = normalizeContent(content);
6482
+ const parts = normalizeMessageContent(content);
6461
6483
  return parts.map((part) => {
6462
6484
  if (part.type === "text") {
6463
6485
  return { text: part.text };
@@ -6502,10 +6524,10 @@ var init_gemini = __esm({
6502
6524
  }
6503
6525
  return Object.keys(config).length > 0 ? config : null;
6504
6526
  }
6505
- async *wrapStream(iterable) {
6527
+ async *normalizeProviderStream(iterable) {
6506
6528
  const stream2 = iterable;
6507
6529
  for await (const chunk of stream2) {
6508
- const text3 = this.extractText(chunk);
6530
+ const text3 = this.extractMessageText(chunk);
6509
6531
  if (text3) {
6510
6532
  yield { text: text3, rawEvent: chunk };
6511
6533
  }
@@ -6516,7 +6538,7 @@ var init_gemini = __esm({
6516
6538
  }
6517
6539
  }
6518
6540
  }
6519
- extractText(chunk) {
6541
+ extractMessageText(chunk) {
6520
6542
  if (!chunk?.candidates) {
6521
6543
  return "";
6522
6544
  }
@@ -6581,7 +6603,7 @@ var init_gemini = __esm({
6581
6603
  let totalChars = 0;
6582
6604
  let mediaCount = 0;
6583
6605
  for (const msg of messages) {
6584
- const parts = normalizeContent(msg.content);
6606
+ const parts = normalizeMessageContent(msg.content);
6585
6607
  for (const part of parts) {
6586
6608
  if (part.type === "text") {
6587
6609
  totalChars += part.text.length;
@@ -7327,7 +7349,7 @@ var init_openai = __esm({
7327
7349
  format
7328
7350
  };
7329
7351
  }
7330
- buildRequestPayload(options, descriptor, spec, messages) {
7352
+ buildApiRequest(options, descriptor, spec, messages) {
7331
7353
  const { maxTokens, temperature, topP, stopSequences, extra } = options;
7332
7354
  const supportsTemperature = spec?.metadata?.supportsTemperature !== false;
7333
7355
  const shouldIncludeTemperature = typeof temperature === "number" && supportsTemperature;
@@ -7362,7 +7384,7 @@ var init_openai = __esm({
7362
7384
  ...message.name ? { name: message.name } : {}
7363
7385
  };
7364
7386
  }
7365
- const textContent = typeof message.content === "string" ? message.content : extractText(message.content);
7387
+ const textContent = typeof message.content === "string" ? message.content : extractMessageText(message.content);
7366
7388
  if (role === "system") {
7367
7389
  return {
7368
7390
  role: "system",
@@ -7422,7 +7444,7 @@ var init_openai = __esm({
7422
7444
  const stream2 = await client.chat.completions.create(payload, signal ? { signal } : void 0);
7423
7445
  return stream2;
7424
7446
  }
7425
- async *wrapStream(iterable) {
7447
+ async *normalizeProviderStream(iterable) {
7426
7448
  const stream2 = iterable;
7427
7449
  for await (const chunk of stream2) {
7428
7450
  const text3 = chunk.choices.map((choice) => choice.delta?.content ?? "").join("");
@@ -7480,9 +7502,9 @@ var init_openai = __esm({
7480
7502
  tokenCount += OPENAI_MESSAGE_OVERHEAD_TOKENS;
7481
7503
  const roleText = ROLE_MAP[message.role];
7482
7504
  tokenCount += encoding.encode(roleText).length;
7483
- const textContent = extractText(message.content);
7505
+ const textContent = extractMessageText(message.content);
7484
7506
  tokenCount += encoding.encode(textContent).length;
7485
- const parts = normalizeContent(message.content);
7507
+ const parts = normalizeMessageContent(message.content);
7486
7508
  for (const part of parts) {
7487
7509
  if (part.type === "image") {
7488
7510
  imageCount++;
@@ -7507,7 +7529,7 @@ var init_openai = __esm({
7507
7529
  let totalChars = 0;
7508
7530
  let imageCount = 0;
7509
7531
  for (const msg of messages) {
7510
- const parts = normalizeContent(msg.content);
7532
+ const parts = normalizeMessageContent(msg.content);
7511
7533
  for (const part of parts) {
7512
7534
  if (part.type === "text") {
7513
7535
  totalChars += part.text.length;
@@ -8372,20 +8394,21 @@ var init_builder = __esm({
8372
8394
  promptConfig;
8373
8395
  gadgets = [];
8374
8396
  initialMessages = [];
8375
- onHumanInputRequired;
8397
+ requestHumanInput;
8376
8398
  gadgetStartPrefix;
8377
8399
  gadgetEndPrefix;
8378
8400
  gadgetArgPrefix;
8379
8401
  textOnlyHandler;
8380
8402
  textWithGadgetsHandler;
8381
8403
  stopOnGadgetError;
8382
- shouldContinueAfterError;
8404
+ canRecoverFromGadgetError;
8383
8405
  defaultGadgetTimeoutMs;
8384
8406
  gadgetOutputLimit;
8385
8407
  gadgetOutputLimitPercent;
8386
8408
  compactionConfig;
8387
8409
  signal;
8388
8410
  trailingMessage;
8411
+ subagentConfig;
8389
8412
  constructor(client) {
8390
8413
  this.client = client;
8391
8414
  }
@@ -8476,13 +8499,13 @@ var init_builder = __esm({
8476
8499
  *
8477
8500
  * @example
8478
8501
  * ```typescript
8479
- * .withPromptConfig({
8502
+ * .withPromptTemplateConfig({
8480
8503
  * mainInstruction: "Use the gadget markers below:",
8481
8504
  * rules: ["Always use markers", "Never use function calling"]
8482
8505
  * })
8483
8506
  * ```
8484
8507
  */
8485
- withPromptConfig(config) {
8508
+ withPromptTemplateConfig(config) {
8486
8509
  this.promptConfig = config;
8487
8510
  return this;
8488
8511
  }
@@ -8562,7 +8585,7 @@ var init_builder = __esm({
8562
8585
  * ```
8563
8586
  */
8564
8587
  onHumanInput(handler) {
8565
- this.onHumanInputRequired = handler;
8588
+ this.requestHumanInput = handler;
8566
8589
  return this;
8567
8590
  }
8568
8591
  /**
@@ -8697,9 +8720,9 @@ var init_builder = __esm({
8697
8720
  * Provides fine-grained control over whether to continue after different types of errors.
8698
8721
  * Overrides `stopOnGadgetError` when provided.
8699
8722
  *
8700
- * **Note:** This builder method configures the underlying `shouldContinueAfterError` option
8723
+ * **Note:** This builder method configures the underlying `canRecoverFromGadgetError` option
8701
8724
  * in `AgentOptions`. The method is named `withErrorHandler` for better developer experience,
8702
- * but maps to the `shouldContinueAfterError` property internally.
8725
+ * but maps to the `canRecoverFromGadgetError` property internally.
8703
8726
  *
8704
8727
  * @param handler - Function that decides whether to continue after an error.
8705
8728
  * Return `true` to continue execution, `false` to stop.
@@ -8720,7 +8743,7 @@ var init_builder = __esm({
8720
8743
  * ```
8721
8744
  */
8722
8745
  withErrorHandler(handler) {
8723
- this.shouldContinueAfterError = handler;
8746
+ this.canRecoverFromGadgetError = handler;
8724
8747
  return this;
8725
8748
  }
8726
8749
  /**
@@ -8861,6 +8884,27 @@ var init_builder = __esm({
8861
8884
  this.signal = signal;
8862
8885
  return this;
8863
8886
  }
8887
+ /**
8888
+ * Set subagent configuration overrides.
8889
+ *
8890
+ * Subagent gadgets (like BrowseWeb) can read these settings from ExecutionContext
8891
+ * to inherit model and other options from the CLI configuration.
8892
+ *
8893
+ * @param config - Subagent configuration map keyed by gadget name
8894
+ * @returns This builder for chaining
8895
+ *
8896
+ * @example
8897
+ * ```typescript
8898
+ * .withSubagentConfig({
8899
+ * BrowseWeb: { model: "inherit", maxIterations: 20, headless: true },
8900
+ * CodeAnalyzer: { model: "sonnet", maxIterations: 10 }
8901
+ * })
8902
+ * ```
8903
+ */
8904
+ withSubagentConfig(config) {
8905
+ this.subagentConfig = config;
8906
+ return this;
8907
+ }
8864
8908
  /**
8865
8909
  * Add an ephemeral trailing message that appears at the end of each LLM request.
8866
8910
  *
@@ -9025,19 +9069,20 @@ ${endPrefix}`
9025
9069
  hooks: this.composeHooks(),
9026
9070
  promptConfig: this.promptConfig,
9027
9071
  initialMessages: this.initialMessages,
9028
- onHumanInputRequired: this.onHumanInputRequired,
9072
+ requestHumanInput: this.requestHumanInput,
9029
9073
  gadgetStartPrefix: this.gadgetStartPrefix,
9030
9074
  gadgetEndPrefix: this.gadgetEndPrefix,
9031
9075
  gadgetArgPrefix: this.gadgetArgPrefix,
9032
9076
  textOnlyHandler: this.textOnlyHandler,
9033
9077
  textWithGadgetsHandler: this.textWithGadgetsHandler,
9034
9078
  stopOnGadgetError: this.stopOnGadgetError,
9035
- shouldContinueAfterError: this.shouldContinueAfterError,
9079
+ canRecoverFromGadgetError: this.canRecoverFromGadgetError,
9036
9080
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
9037
9081
  gadgetOutputLimit: this.gadgetOutputLimit,
9038
9082
  gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
9039
9083
  compactionConfig: this.compactionConfig,
9040
- signal: this.signal
9084
+ signal: this.signal,
9085
+ subagentConfig: this.subagentConfig
9041
9086
  };
9042
9087
  }
9043
9088
  ask(userPrompt) {
@@ -9206,19 +9251,20 @@ ${endPrefix}`
9206
9251
  hooks: this.composeHooks(),
9207
9252
  promptConfig: this.promptConfig,
9208
9253
  initialMessages: this.initialMessages,
9209
- onHumanInputRequired: this.onHumanInputRequired,
9254
+ requestHumanInput: this.requestHumanInput,
9210
9255
  gadgetStartPrefix: this.gadgetStartPrefix,
9211
9256
  gadgetEndPrefix: this.gadgetEndPrefix,
9212
9257
  gadgetArgPrefix: this.gadgetArgPrefix,
9213
9258
  textOnlyHandler: this.textOnlyHandler,
9214
9259
  textWithGadgetsHandler: this.textWithGadgetsHandler,
9215
9260
  stopOnGadgetError: this.stopOnGadgetError,
9216
- shouldContinueAfterError: this.shouldContinueAfterError,
9261
+ canRecoverFromGadgetError: this.canRecoverFromGadgetError,
9217
9262
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
9218
9263
  gadgetOutputLimit: this.gadgetOutputLimit,
9219
9264
  gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
9220
9265
  compactionConfig: this.compactionConfig,
9221
- signal: this.signal
9266
+ signal: this.signal,
9267
+ subagentConfig: this.subagentConfig
9222
9268
  };
9223
9269
  return new Agent(AGENT_INTERNAL_KEY, options);
9224
9270
  }
@@ -9236,7 +9282,8 @@ var COMMANDS = {
9236
9282
  gadget: "gadget",
9237
9283
  image: "image",
9238
9284
  speech: "speech",
9239
- vision: "vision"
9285
+ vision: "vision",
9286
+ init: "init"
9240
9287
  };
9241
9288
  var LOG_LEVELS = ["silly", "trace", "debug", "info", "warn", "error", "fatal"];
9242
9289
  var DEFAULT_MODEL = "openai:gpt-5-nano";
@@ -9312,7 +9359,7 @@ var import_commander2 = require("commander");
9312
9359
  // package.json
9313
9360
  var package_default = {
9314
9361
  name: "llmist",
9315
- version: "2.6.0",
9362
+ version: "3.0.0",
9316
9363
  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
9364
  type: "module",
9318
9365
  main: "dist/index.cjs",
@@ -9720,7 +9767,7 @@ var askUser = createGadget({
9720
9767
  }
9721
9768
  ],
9722
9769
  execute: ({ question }) => {
9723
- throw new HumanInputException(question);
9770
+ throw new HumanInputRequiredException(question);
9724
9771
  }
9725
9772
  });
9726
9773
  var tellUser = createGadget({
@@ -9770,11 +9817,59 @@ var finish = createGadget({
9770
9817
  }
9771
9818
  ],
9772
9819
  execute: () => {
9773
- throw new BreakLoopException("Task completed");
9820
+ throw new TaskCompletionSignal("Task completed");
9774
9821
  }
9775
9822
  });
9776
9823
  var builtinGadgets = [askUser, tellUser, finish];
9777
9824
 
9825
+ // src/cli/subagent-config.ts
9826
+ var INHERIT_MODEL = "inherit";
9827
+ function resolveSubagentConfig(subagentName, parentModel, profileConfig, globalConfig) {
9828
+ const resolved = {};
9829
+ const globalDefaultModel = globalConfig?.["default-model"];
9830
+ const globalSubagent = extractSubagentConfig(globalConfig, subagentName);
9831
+ const profileSubagent = profileConfig?.[subagentName] ?? {};
9832
+ const merged = { ...globalSubagent, ...profileSubagent };
9833
+ const configModel = merged.model ?? globalDefaultModel ?? INHERIT_MODEL;
9834
+ resolved.model = configModel === INHERIT_MODEL ? parentModel : configModel;
9835
+ for (const [key, value] of Object.entries(merged)) {
9836
+ if (key !== "model") {
9837
+ resolved[key] = value;
9838
+ }
9839
+ }
9840
+ return resolved;
9841
+ }
9842
+ function buildSubagentConfigMap(parentModel, profileConfig, globalConfig) {
9843
+ const subagentNames = /* @__PURE__ */ new Set();
9844
+ if (globalConfig) {
9845
+ for (const key of Object.keys(globalConfig)) {
9846
+ if (key !== "default-model" && typeof globalConfig[key] === "object") {
9847
+ subagentNames.add(key);
9848
+ }
9849
+ }
9850
+ }
9851
+ if (profileConfig) {
9852
+ for (const key of Object.keys(profileConfig)) {
9853
+ subagentNames.add(key);
9854
+ }
9855
+ }
9856
+ const result = {};
9857
+ for (const name of subagentNames) {
9858
+ result[name] = resolveSubagentConfig(name, parentModel, profileConfig, globalConfig);
9859
+ }
9860
+ return result;
9861
+ }
9862
+ function extractSubagentConfig(globalConfig, subagentName) {
9863
+ if (!globalConfig) {
9864
+ return {};
9865
+ }
9866
+ const value = globalConfig[subagentName];
9867
+ if (typeof value === "object" && value !== null) {
9868
+ return value;
9869
+ }
9870
+ return {};
9871
+ }
9872
+
9778
9873
  // src/cli/config.ts
9779
9874
  var import_node_fs3 = require("fs");
9780
9875
  var import_node_os2 = require("os");
@@ -9863,7 +9958,7 @@ function hasTemplateSyntax(str) {
9863
9958
  }
9864
9959
 
9865
9960
  // src/cli/config.ts
9866
- var VALID_APPROVAL_MODES = ["allowed", "denied", "approval-required"];
9961
+ var VALID_PERMISSION_LEVELS = ["allowed", "denied", "approval-required"];
9867
9962
  var GLOBAL_CONFIG_KEYS = /* @__PURE__ */ new Set(["log-level", "log-file", "log-reset"]);
9868
9963
  var VALID_LOG_LEVELS = ["silly", "trace", "debug", "info", "warn", "error", "fatal"];
9869
9964
  var COMPLETE_CONFIG_KEYS = /* @__PURE__ */ new Set([
@@ -9903,6 +9998,8 @@ var AGENT_CONFIG_KEYS = /* @__PURE__ */ new Set([
9903
9998
  "gadget-end-prefix",
9904
9999
  "gadget-arg-prefix",
9905
10000
  "gadget-approval",
10001
+ "subagents",
10002
+ // Per-subagent configuration overrides
9906
10003
  "quiet",
9907
10004
  "inherits",
9908
10005
  "log-level",
@@ -9928,9 +10025,9 @@ function getConfigPath() {
9928
10025
  return (0, import_node_path4.join)((0, import_node_os2.homedir)(), ".llmist", "cli.toml");
9929
10026
  }
9930
10027
  var ConfigError = class extends Error {
9931
- constructor(message, path5) {
9932
- super(path5 ? `${path5}: ${message}` : message);
9933
- this.path = path5;
10028
+ constructor(message, path6) {
10029
+ super(path6 ? `${path6}: ${message}` : message);
10030
+ this.path = path6;
9934
10031
  this.name = "ConfigError";
9935
10032
  }
9936
10033
  };
@@ -9986,6 +10083,63 @@ function validateInherits(value, section) {
9986
10083
  }
9987
10084
  throw new ConfigError(`[${section}].inherits must be a string or array of strings`);
9988
10085
  }
10086
+ function validateSingleSubagentConfig(value, subagentName, section) {
10087
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
10088
+ throw new ConfigError(
10089
+ `[${section}].${subagentName} must be a table (e.g., { model = "inherit", maxIterations = 20 })`
10090
+ );
10091
+ }
10092
+ const result = {};
10093
+ const rawObj = value;
10094
+ for (const [key, val] of Object.entries(rawObj)) {
10095
+ if (key === "model") {
10096
+ if (typeof val !== "string") {
10097
+ throw new ConfigError(`[${section}].${subagentName}.model must be a string`);
10098
+ }
10099
+ result.model = val;
10100
+ } else if (key === "maxIterations") {
10101
+ if (typeof val !== "number" || !Number.isInteger(val) || val < 1) {
10102
+ throw new ConfigError(
10103
+ `[${section}].${subagentName}.maxIterations must be a positive integer`
10104
+ );
10105
+ }
10106
+ result.maxIterations = val;
10107
+ } else {
10108
+ result[key] = val;
10109
+ }
10110
+ }
10111
+ return result;
10112
+ }
10113
+ function validateSubagentConfigMap(value, section) {
10114
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
10115
+ throw new ConfigError(
10116
+ `[${section}].subagents must be a table (e.g., { BrowseWeb = { model = "inherit" } })`
10117
+ );
10118
+ }
10119
+ const result = {};
10120
+ for (const [subagentName, config] of Object.entries(value)) {
10121
+ result[subagentName] = validateSingleSubagentConfig(config, subagentName, `${section}.subagents`);
10122
+ }
10123
+ return result;
10124
+ }
10125
+ function validateGlobalSubagentConfig(value, section) {
10126
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
10127
+ throw new ConfigError(`[${section}] must be a table`);
10128
+ }
10129
+ const result = {};
10130
+ const rawObj = value;
10131
+ for (const [key, val] of Object.entries(rawObj)) {
10132
+ if (key === "default-model") {
10133
+ if (typeof val !== "string") {
10134
+ throw new ConfigError(`[${section}].default-model must be a string`);
10135
+ }
10136
+ result["default-model"] = val;
10137
+ } else {
10138
+ result[key] = validateSingleSubagentConfig(val, key, section);
10139
+ }
10140
+ }
10141
+ return result;
10142
+ }
9989
10143
  function validateGadgetApproval(value, section) {
9990
10144
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
9991
10145
  throw new ConfigError(
@@ -9997,9 +10151,9 @@ function validateGadgetApproval(value, section) {
9997
10151
  if (typeof mode !== "string") {
9998
10152
  throw new ConfigError(`[${section}].gadget-approval.${gadgetName} must be a string`);
9999
10153
  }
10000
- if (!VALID_APPROVAL_MODES.includes(mode)) {
10154
+ if (!VALID_PERMISSION_LEVELS.includes(mode)) {
10001
10155
  throw new ConfigError(
10002
- `[${section}].gadget-approval.${gadgetName} must be one of: ${VALID_APPROVAL_MODES.join(", ")}`
10156
+ `[${section}].gadget-approval.${gadgetName} must be one of: ${VALID_PERMISSION_LEVELS.join(", ")}`
10003
10157
  );
10004
10158
  }
10005
10159
  result[gadgetName] = mode;
@@ -10168,6 +10322,9 @@ function validateAgentConfig(raw, section) {
10168
10322
  if ("gadget-approval" in rawObj) {
10169
10323
  result["gadget-approval"] = validateGadgetApproval(rawObj["gadget-approval"], section);
10170
10324
  }
10325
+ if ("subagents" in rawObj) {
10326
+ result.subagents = validateSubagentConfigMap(rawObj.subagents, section);
10327
+ }
10171
10328
  if ("quiet" in rawObj) {
10172
10329
  result.quiet = validateBoolean(rawObj.quiet, "quiet", section);
10173
10330
  }
@@ -10336,6 +10493,9 @@ function validateCustomConfig(raw, section) {
10336
10493
  if ("gadget-approval" in rawObj) {
10337
10494
  result["gadget-approval"] = validateGadgetApproval(rawObj["gadget-approval"], section);
10338
10495
  }
10496
+ if ("subagents" in rawObj) {
10497
+ result.subagents = validateSubagentConfigMap(rawObj.subagents, section);
10498
+ }
10339
10499
  if ("max-tokens" in rawObj) {
10340
10500
  result["max-tokens"] = validateNumber(rawObj["max-tokens"], "max-tokens", section, {
10341
10501
  integer: true,
@@ -10383,6 +10543,8 @@ function validateConfig(raw, configPath) {
10383
10543
  result.prompts = validatePromptsConfig(value, key);
10384
10544
  } else if (key === "docker") {
10385
10545
  result.docker = validateDockerConfig(value, key);
10546
+ } else if (key === "subagents") {
10547
+ result.subagents = validateGlobalSubagentConfig(value, key);
10386
10548
  } else {
10387
10549
  result[key] = validateCustomConfig(value, key);
10388
10550
  }
@@ -10423,7 +10585,16 @@ function loadConfig() {
10423
10585
  return resolveTemplatesInConfig(inherited, configPath);
10424
10586
  }
10425
10587
  function getCustomCommandNames(config) {
10426
- const reserved = /* @__PURE__ */ new Set(["global", "complete", "agent", "image", "speech", "prompts", "docker"]);
10588
+ const reserved = /* @__PURE__ */ new Set([
10589
+ "global",
10590
+ "complete",
10591
+ "agent",
10592
+ "image",
10593
+ "speech",
10594
+ "prompts",
10595
+ "docker",
10596
+ "subagents"
10597
+ ]);
10427
10598
  return Object.keys(config).filter((key) => !reserved.has(key));
10428
10599
  }
10429
10600
  function resolveTemplatesInConfig(config, configPath) {
@@ -10957,11 +11128,11 @@ function resolveDevMode(config, cliDevMode) {
10957
11128
  }
10958
11129
  return { enabled: true, sourcePath };
10959
11130
  }
10960
- function expandHome(path5) {
10961
- if (path5.startsWith("~")) {
10962
- return path5.replace(/^~/, (0, import_node_os4.homedir)());
11131
+ function expandHome(path6) {
11132
+ if (path6.startsWith("~")) {
11133
+ return path6.replace(/^~/, (0, import_node_os4.homedir)());
10963
11134
  }
10964
- return path5;
11135
+ return path6;
10965
11136
  }
10966
11137
  function buildDockerRunArgs(ctx, imageName, devMode) {
10967
11138
  const args = ["run", "--rm"];
@@ -11138,9 +11309,9 @@ async function readFileBuffer(filePath, options = {}) {
11138
11309
  }
11139
11310
 
11140
11311
  // 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");
11312
+ var import_node_fs11 = __toESM(require("fs"), 1);
11313
+ var import_node_path12 = __toESM(require("path"), 1);
11314
+ var import_node_url2 = require("url");
11144
11315
  init_gadget();
11145
11316
 
11146
11317
  // src/cli/builtins/filesystem/edit-file.ts
@@ -11665,6 +11836,208 @@ function isBuiltinGadgetName(name) {
11665
11836
  return name in builtinGadgetRegistry;
11666
11837
  }
11667
11838
 
11839
+ // src/cli/external-gadgets.ts
11840
+ var import_node_child_process = require("child_process");
11841
+ var import_node_fs10 = __toESM(require("fs"), 1);
11842
+ var import_node_path11 = __toESM(require("path"), 1);
11843
+ var import_node_os5 = __toESM(require("os"), 1);
11844
+ var import_node_url = require("url");
11845
+ var CACHE_DIR2 = import_node_path11.default.join(import_node_os5.default.homedir(), ".llmist", "gadget-cache");
11846
+ function isExternalPackageSpecifier(specifier) {
11847
+ if (/^@?[a-z0-9][\w.-]*(?:@[\w.-]+)?(?::[a-z]+)?(?:\/\w+)?$/i.test(specifier)) {
11848
+ return true;
11849
+ }
11850
+ if (specifier.startsWith("git+")) {
11851
+ return true;
11852
+ }
11853
+ return false;
11854
+ }
11855
+ function parseGadgetSpecifier(specifier) {
11856
+ if (specifier.startsWith("git+")) {
11857
+ const url = specifier.slice(4);
11858
+ const [baseUrl, ref] = url.split("#");
11859
+ return {
11860
+ type: "git",
11861
+ package: baseUrl,
11862
+ version: ref
11863
+ };
11864
+ }
11865
+ const npmMatch = specifier.match(
11866
+ /^(@?[a-z0-9][\w.-]*)(?:@([\w.-]+))?(?::([a-z]+))?(?:\/(\w+))?$/i
11867
+ );
11868
+ if (npmMatch) {
11869
+ const [, pkg, version, preset, gadgetName] = npmMatch;
11870
+ return {
11871
+ type: "npm",
11872
+ package: pkg,
11873
+ version,
11874
+ preset,
11875
+ gadgetName
11876
+ };
11877
+ }
11878
+ return null;
11879
+ }
11880
+ function getCacheDir(spec) {
11881
+ const versionSuffix = spec.version ? `@${spec.version}` : "@latest";
11882
+ if (spec.type === "npm") {
11883
+ return import_node_path11.default.join(CACHE_DIR2, "npm", `${spec.package}${versionSuffix}`);
11884
+ }
11885
+ const sanitizedUrl = spec.package.replace(/[/:]/g, "-").replace(/^-+|-+$/g, "");
11886
+ return import_node_path11.default.join(CACHE_DIR2, "git", `${sanitizedUrl}${versionSuffix}`);
11887
+ }
11888
+ function isCached(cacheDir) {
11889
+ const packageJsonPath = import_node_path11.default.join(cacheDir, "package.json");
11890
+ return import_node_fs10.default.existsSync(packageJsonPath);
11891
+ }
11892
+ async function installNpmPackage(spec, cacheDir) {
11893
+ import_node_fs10.default.mkdirSync(cacheDir, { recursive: true });
11894
+ const packageJson = {
11895
+ name: "llmist-gadget-cache",
11896
+ private: true,
11897
+ type: "module"
11898
+ };
11899
+ import_node_fs10.default.writeFileSync(import_node_path11.default.join(cacheDir, "package.json"), JSON.stringify(packageJson, null, 2));
11900
+ const packageSpec = spec.version ? `${spec.package}@${spec.version}` : spec.package;
11901
+ try {
11902
+ (0, import_node_child_process.execSync)(`npm install --prefix "${cacheDir}" "${packageSpec}" --save`, {
11903
+ stdio: "pipe",
11904
+ cwd: cacheDir
11905
+ });
11906
+ } catch (error) {
11907
+ const message = error instanceof Error ? error.message : String(error);
11908
+ throw new Error(`Failed to install npm package '${packageSpec}': ${message}`);
11909
+ }
11910
+ }
11911
+ async function installGitPackage(spec, cacheDir) {
11912
+ import_node_fs10.default.mkdirSync(import_node_path11.default.dirname(cacheDir), { recursive: true });
11913
+ if (import_node_fs10.default.existsSync(cacheDir)) {
11914
+ try {
11915
+ (0, import_node_child_process.execSync)("git fetch", { cwd: cacheDir, stdio: "pipe" });
11916
+ if (spec.version) {
11917
+ (0, import_node_child_process.execSync)(`git checkout ${spec.version}`, { cwd: cacheDir, stdio: "pipe" });
11918
+ }
11919
+ } catch (error) {
11920
+ import_node_fs10.default.rmSync(cacheDir, { recursive: true, force: true });
11921
+ }
11922
+ }
11923
+ if (!import_node_fs10.default.existsSync(cacheDir)) {
11924
+ try {
11925
+ const cloneCmd = spec.version ? `git clone --branch ${spec.version} "${spec.package}" "${cacheDir}"` : `git clone "${spec.package}" "${cacheDir}"`;
11926
+ (0, import_node_child_process.execSync)(cloneCmd, { stdio: "pipe" });
11927
+ } catch (error) {
11928
+ const message = error instanceof Error ? error.message : String(error);
11929
+ throw new Error(`Failed to clone git repository '${spec.package}': ${message}`);
11930
+ }
11931
+ if (import_node_fs10.default.existsSync(import_node_path11.default.join(cacheDir, "package.json"))) {
11932
+ try {
11933
+ (0, import_node_child_process.execSync)("npm install", { cwd: cacheDir, stdio: "pipe" });
11934
+ } catch (error) {
11935
+ const message = error instanceof Error ? error.message : String(error);
11936
+ throw new Error(`Failed to install dependencies for '${spec.package}': ${message}`);
11937
+ }
11938
+ try {
11939
+ const packageJson = JSON.parse(import_node_fs10.default.readFileSync(import_node_path11.default.join(cacheDir, "package.json"), "utf-8"));
11940
+ if (packageJson.scripts?.build) {
11941
+ (0, import_node_child_process.execSync)("npm run build", { cwd: cacheDir, stdio: "pipe" });
11942
+ }
11943
+ } catch (error) {
11944
+ const message = error instanceof Error ? error.message : String(error);
11945
+ throw new Error(`Failed to build package '${spec.package}': ${message}`);
11946
+ }
11947
+ }
11948
+ }
11949
+ }
11950
+ function readManifest(packageDir) {
11951
+ const packageJsonPath = import_node_path11.default.join(packageDir, "package.json");
11952
+ if (!import_node_fs10.default.existsSync(packageJsonPath)) {
11953
+ return null;
11954
+ }
11955
+ try {
11956
+ const packageJson = JSON.parse(import_node_fs10.default.readFileSync(packageJsonPath, "utf-8"));
11957
+ return packageJson.llmist || null;
11958
+ } catch {
11959
+ return null;
11960
+ }
11961
+ }
11962
+ function getPackagePath(cacheDir, packageName) {
11963
+ const nodeModulesPath = import_node_path11.default.join(cacheDir, "node_modules", packageName);
11964
+ if (import_node_fs10.default.existsSync(nodeModulesPath)) {
11965
+ return nodeModulesPath;
11966
+ }
11967
+ return cacheDir;
11968
+ }
11969
+ async function loadExternalGadgets(specifier, forceInstall = false) {
11970
+ const spec = parseGadgetSpecifier(specifier);
11971
+ if (!spec) {
11972
+ throw new Error(`Invalid external package specifier: ${specifier}`);
11973
+ }
11974
+ const cacheDir = getCacheDir(spec);
11975
+ if (!isCached(cacheDir) || forceInstall) {
11976
+ if (spec.type === "npm") {
11977
+ await installNpmPackage(spec, cacheDir);
11978
+ } else {
11979
+ await installGitPackage(spec, cacheDir);
11980
+ }
11981
+ }
11982
+ const packagePath = getPackagePath(cacheDir, spec.package);
11983
+ const manifest = readManifest(packagePath);
11984
+ let entryPoint;
11985
+ let gadgetNames = null;
11986
+ if (spec.gadgetName) {
11987
+ gadgetNames = [spec.gadgetName];
11988
+ if (manifest?.subagents?.[spec.gadgetName]) {
11989
+ entryPoint = manifest.subagents[spec.gadgetName].entryPoint;
11990
+ } else {
11991
+ entryPoint = manifest?.gadgets || "./dist/index.js";
11992
+ }
11993
+ } else if (spec.preset) {
11994
+ if (!manifest?.presets?.[spec.preset]) {
11995
+ throw new Error(`Unknown preset '${spec.preset}' in package '${spec.package}'`);
11996
+ }
11997
+ const preset = manifest.presets[spec.preset];
11998
+ if (preset === "*") {
11999
+ gadgetNames = null;
12000
+ } else {
12001
+ gadgetNames = preset;
12002
+ }
12003
+ entryPoint = manifest?.gadgets || "./dist/index.js";
12004
+ } else {
12005
+ entryPoint = manifest?.gadgets || "./dist/index.js";
12006
+ }
12007
+ const resolvedEntryPoint = import_node_path11.default.resolve(packagePath, entryPoint);
12008
+ if (!import_node_fs10.default.existsSync(resolvedEntryPoint)) {
12009
+ throw new Error(
12010
+ `Entry point not found: ${resolvedEntryPoint}. Make sure the package is built (run 'npm run build' in the package directory).`
12011
+ );
12012
+ }
12013
+ const moduleUrl = (0, import_node_url.pathToFileURL)(resolvedEntryPoint).href;
12014
+ let exports2;
12015
+ try {
12016
+ exports2 = await import(moduleUrl);
12017
+ } catch (error) {
12018
+ const message = error instanceof Error ? error.message : String(error);
12019
+ throw new Error(`Failed to import '${specifier}': ${message}`);
12020
+ }
12021
+ let gadgets = extractGadgetsFromModule(exports2);
12022
+ if (gadgetNames) {
12023
+ const gadgetSet = new Set(gadgetNames.map((n) => n.toLowerCase()));
12024
+ gadgets = gadgets.filter((g) => {
12025
+ const name = g.name?.toLowerCase() || "";
12026
+ return gadgetSet.has(name);
12027
+ });
12028
+ const foundNames = new Set(gadgets.map((g) => g.name?.toLowerCase() || ""));
12029
+ for (const requested of gadgetNames) {
12030
+ if (!foundNames.has(requested.toLowerCase())) {
12031
+ throw new Error(`Gadget '${requested}' not found in package '${spec.package}'`);
12032
+ }
12033
+ }
12034
+ }
12035
+ if (gadgets.length === 0) {
12036
+ throw new Error(`No gadgets found in package '${spec.package}'`);
12037
+ }
12038
+ return gadgets;
12039
+ }
12040
+
11668
12041
  // src/cli/gadgets.ts
11669
12042
  var PATH_PREFIXES = [".", "/", "~"];
11670
12043
  var BUILTIN_PREFIX = "builtin:";
@@ -11680,7 +12053,17 @@ function isGadgetConstructor(value) {
11680
12053
  return false;
11681
12054
  }
11682
12055
  const prototype = value.prototype;
11683
- return Boolean(prototype) && (prototype instanceof BaseGadget || isGadgetLike(prototype));
12056
+ if (!prototype) {
12057
+ return false;
12058
+ }
12059
+ if (prototype instanceof AbstractGadget) {
12060
+ return true;
12061
+ }
12062
+ const proto = prototype;
12063
+ if (typeof proto.execute === "function") {
12064
+ return true;
12065
+ }
12066
+ return isGadgetLike(prototype);
11684
12067
  }
11685
12068
  function expandHomePath(input) {
11686
12069
  if (!input.startsWith("~")) {
@@ -11690,10 +12073,10 @@ function expandHomePath(input) {
11690
12073
  if (!home) {
11691
12074
  return input;
11692
12075
  }
11693
- return import_node_path11.default.join(home, input.slice(1));
12076
+ return import_node_path12.default.join(home, input.slice(1));
11694
12077
  }
11695
12078
  function isFileLikeSpecifier(specifier) {
11696
- return PATH_PREFIXES.some((prefix) => specifier.startsWith(prefix)) || specifier.includes(import_node_path11.default.sep);
12079
+ return PATH_PREFIXES.some((prefix) => specifier.startsWith(prefix)) || specifier.includes(import_node_path12.default.sep);
11697
12080
  }
11698
12081
  function tryResolveBuiltin(specifier) {
11699
12082
  if (specifier.startsWith(BUILTIN_PREFIX)) {
@@ -11716,11 +12099,11 @@ function resolveGadgetSpecifier(specifier, cwd) {
11716
12099
  return specifier;
11717
12100
  }
11718
12101
  const expanded = expandHomePath(specifier);
11719
- const resolvedPath = import_node_path11.default.resolve(cwd, expanded);
11720
- if (!import_node_fs10.default.existsSync(resolvedPath)) {
12102
+ const resolvedPath = import_node_path12.default.resolve(cwd, expanded);
12103
+ if (!import_node_fs11.default.existsSync(resolvedPath)) {
11721
12104
  throw new Error(`Gadget module not found at ${resolvedPath}`);
11722
12105
  }
11723
- return (0, import_node_url.pathToFileURL)(resolvedPath).href;
12106
+ return (0, import_node_url2.pathToFileURL)(resolvedPath).href;
11724
12107
  }
11725
12108
  function extractGadgetsFromModule(moduleExports) {
11726
12109
  const results = [];
@@ -11733,7 +12116,7 @@ function extractGadgetsFromModule(moduleExports) {
11733
12116
  return;
11734
12117
  }
11735
12118
  visited.add(value);
11736
- if (value instanceof BaseGadget || isGadgetLike(value)) {
12119
+ if (value instanceof AbstractGadget || isGadgetLike(value)) {
11737
12120
  results.push(value);
11738
12121
  return;
11739
12122
  }
@@ -11758,12 +12141,23 @@ function extractGadgetsFromModule(moduleExports) {
11758
12141
  }
11759
12142
  async function loadGadgets(specifiers, cwd, importer = (specifier) => import(specifier)) {
11760
12143
  const gadgets = [];
12144
+ const usingDefaultImporter = importer.toString().includes("import(specifier)");
11761
12145
  for (const specifier of specifiers) {
11762
12146
  const builtin = tryResolveBuiltin(specifier);
11763
12147
  if (builtin) {
11764
12148
  gadgets.push(builtin);
11765
12149
  continue;
11766
12150
  }
12151
+ if (usingDefaultImporter && isExternalPackageSpecifier(specifier)) {
12152
+ try {
12153
+ const externalGadgets = await loadExternalGadgets(specifier);
12154
+ gadgets.push(...externalGadgets);
12155
+ continue;
12156
+ } catch (error) {
12157
+ const message = error instanceof Error ? error.message : String(error);
12158
+ throw new Error(`Failed to load external package '${specifier}': ${message}`);
12159
+ }
12160
+ }
11767
12161
  const resolved = resolveGadgetSpecifier(specifier, cwd);
11768
12162
  let exports2;
11769
12163
  try {
@@ -11789,13 +12183,13 @@ async function loadGadgets(specifiers, cwd, importer = (specifier) => import(spe
11789
12183
 
11790
12184
  // src/cli/llm-logging.ts
11791
12185
  var import_promises4 = require("fs/promises");
11792
- var import_node_os5 = require("os");
11793
- var import_node_path12 = require("path");
12186
+ var import_node_os6 = require("os");
12187
+ var import_node_path13 = require("path");
11794
12188
  init_messages();
11795
- var DEFAULT_LLM_LOG_DIR = (0, import_node_path12.join)((0, import_node_os5.homedir)(), ".llmist", "logs");
12189
+ var DEFAULT_LLM_LOG_DIR = (0, import_node_path13.join)((0, import_node_os6.homedir)(), ".llmist", "logs");
11796
12190
  function resolveLogDir(option, subdir) {
11797
12191
  if (option === true) {
11798
- return (0, import_node_path12.join)(DEFAULT_LLM_LOG_DIR, subdir);
12192
+ return (0, import_node_path13.join)(DEFAULT_LLM_LOG_DIR, subdir);
11799
12193
  }
11800
12194
  if (typeof option === "string") {
11801
12195
  return option;
@@ -11806,14 +12200,14 @@ function formatLlmRequest(messages) {
11806
12200
  const lines = [];
11807
12201
  for (const msg of messages) {
11808
12202
  lines.push(`=== ${msg.role.toUpperCase()} ===`);
11809
- lines.push(msg.content ? extractText(msg.content) : "");
12203
+ lines.push(msg.content ? extractMessageText(msg.content) : "");
11810
12204
  lines.push("");
11811
12205
  }
11812
12206
  return lines.join("\n");
11813
12207
  }
11814
12208
  async function writeLogFile(dir, filename, content) {
11815
12209
  await (0, import_promises4.mkdir)(dir, { recursive: true });
11816
- await (0, import_promises4.writeFile)((0, import_node_path12.join)(dir, filename), content, "utf-8");
12210
+ await (0, import_promises4.writeFile)((0, import_node_path13.join)(dir, filename), content, "utf-8");
11817
12211
  }
11818
12212
  function formatSessionTimestamp(date = /* @__PURE__ */ new Date()) {
11819
12213
  const pad = (n) => n.toString().padStart(2, "0");
@@ -11827,7 +12221,7 @@ function formatSessionTimestamp(date = /* @__PURE__ */ new Date()) {
11827
12221
  }
11828
12222
  async function createSessionDir(baseDir) {
11829
12223
  const timestamp = formatSessionTimestamp();
11830
- const sessionDir = (0, import_node_path12.join)(baseDir, timestamp);
12224
+ const sessionDir = (0, import_node_path13.join)(baseDir, timestamp);
11831
12225
  try {
11832
12226
  await (0, import_promises4.mkdir)(sessionDir, { recursive: true });
11833
12227
  return sessionDir;
@@ -12021,8 +12415,8 @@ function formatMediaLine(media) {
12021
12415
  const id = import_chalk3.default.cyan(media.id);
12022
12416
  const mimeType = import_chalk3.default.dim(media.mimeType);
12023
12417
  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}`;
12418
+ const path6 = import_chalk3.default.dim(media.path);
12419
+ return `${import_chalk3.default.dim("[")}${icon} ${id} ${mimeType} ${size}${import_chalk3.default.dim("]")} ${import_chalk3.default.dim("\u2192")} ${path6}`;
12026
12420
  }
12027
12421
  function formatGadgetSummary2(result) {
12028
12422
  const gadgetLabel = import_chalk3.default.magenta.bold(result.gadgetName);
@@ -12642,6 +13036,7 @@ function configToAgentOptions(config) {
12642
13036
  if (config.docker !== void 0) result.docker = config.docker;
12643
13037
  if (config["docker-cwd-permission"] !== void 0)
12644
13038
  result.dockerCwdPermission = config["docker-cwd-permission"];
13039
+ if (config.subagents !== void 0) result.subagents = config.subagents;
12645
13040
  return result;
12646
13041
  }
12647
13042
 
@@ -12829,7 +13224,12 @@ async function executeAgent(promptArg, options, env) {
12829
13224
  return void 0;
12830
13225
  }
12831
13226
  };
12832
- const builder = new AgentBuilder(client).withModel(options.model).withLogger(env.createLogger("llmist:cli:agent")).withHooks({
13227
+ const resolvedSubagentConfig = buildSubagentConfigMap(
13228
+ options.model,
13229
+ options.subagents,
13230
+ options.globalSubagents
13231
+ );
13232
+ const builder = new AgentBuilder(client).withModel(options.model).withSubagentConfig(resolvedSubagentConfig).withLogger(env.createLogger("llmist:cli:agent")).withHooks({
12833
13233
  observers: {
12834
13234
  // onLLMCallStart: Start progress indicator for each LLM call
12835
13235
  // This showcases how to react to agent lifecycle events
@@ -13099,14 +13499,16 @@ Denied: ${result.reason ?? "by user"}`
13099
13499
  }
13100
13500
  }
13101
13501
  }
13102
- function registerAgentCommand(program, env, config) {
13502
+ function registerAgentCommand(program, env, config, globalSubagents) {
13103
13503
  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
13504
  addAgentOptions(cmd, config);
13105
13505
  cmd.action(
13106
13506
  (prompt, options) => executeAction(() => {
13107
13507
  const mergedOptions = {
13108
13508
  ...options,
13109
- gadgetApproval: config?.["gadget-approval"]
13509
+ gadgetApproval: config?.["gadget-approval"],
13510
+ subagents: config?.subagents,
13511
+ globalSubagents
13110
13512
  };
13111
13513
  return executeAgent(prompt, mergedOptions, env);
13112
13514
  }, env)
@@ -13206,6 +13608,104 @@ function registerCompleteCommand(program, env, config) {
13206
13608
  );
13207
13609
  }
13208
13610
 
13611
+ // src/cli/init-command.ts
13612
+ var import_node_fs12 = require("fs");
13613
+ var import_node_path14 = require("path");
13614
+ var STARTER_CONFIG = `# ~/.llmist/cli.toml
13615
+ # llmist CLI configuration file
13616
+ #
13617
+ # This is a minimal starter config. For a comprehensive example with all options:
13618
+ # https://github.com/zbigniewsobiecki/llmist/blob/main/examples/cli.example.toml
13619
+ #
13620
+ # Key concepts:
13621
+ # - Any section can inherit from others using: inherits = "section-name"
13622
+ # - Prompts can use templates with Eta syntax: <%~ include("@prompt-name") %>
13623
+ # - Custom sections become CLI commands: [my-command] -> llmist my-command
13624
+
13625
+ #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
13626
+ # GLOBAL OPTIONS
13627
+ # These apply to all commands. CLI flags override these settings.
13628
+ #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
13629
+ [global]
13630
+ # log-level = "info" # silly, trace, debug, info, warn, error, fatal
13631
+ # log-file = "/tmp/llmist.log" # Enable file logging (JSON format)
13632
+
13633
+ #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
13634
+ # COMPLETE COMMAND DEFAULTS
13635
+ # For single LLM responses: llmist complete "prompt"
13636
+ # Model format: provider:model (e.g., openai:gpt-4o, anthropic:claude-sonnet-4-5)
13637
+ #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
13638
+ [complete]
13639
+ # model = "openai:gpt-4o"
13640
+ # temperature = 0.7 # 0-2, higher = more creative
13641
+ # max-tokens = 4096 # Maximum response length
13642
+
13643
+ #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
13644
+ # AGENT COMMAND DEFAULTS
13645
+ # For tool-using agents: llmist agent "prompt"
13646
+ #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
13647
+ [agent]
13648
+ # model = "anthropic:claude-sonnet-4-5"
13649
+ # max-iterations = 15 # Max tool-use loops before stopping
13650
+ # gadgets = [ # Tools the agent can use
13651
+ # "ListDirectory",
13652
+ # "ReadFile",
13653
+ # "WriteFile",
13654
+ # ]
13655
+
13656
+ #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
13657
+ # CUSTOM COMMANDS
13658
+ # Any other section becomes a new CLI command!
13659
+ # Uncomment below to create: llmist summarize "your text"
13660
+ #\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
13661
+ # [summarize]
13662
+ # type = "complete" # "complete" or "agent"
13663
+ # description = "Summarize text concisely."
13664
+ # system = "Summarize the following text in 2-3 bullet points."
13665
+ # temperature = 0.3
13666
+ `;
13667
+ async function executeInit(_options, env) {
13668
+ const configPath = getConfigPath();
13669
+ const configDir = (0, import_node_path14.dirname)(configPath);
13670
+ if ((0, import_node_fs12.existsSync)(configPath)) {
13671
+ env.stderr.write(`Configuration already exists at ${configPath}
13672
+ `);
13673
+ env.stderr.write("\n");
13674
+ env.stderr.write(`To view it: cat ${configPath}
13675
+ `);
13676
+ env.stderr.write(`To reset: rm ${configPath} && llmist init
13677
+ `);
13678
+ return;
13679
+ }
13680
+ if (!(0, import_node_fs12.existsSync)(configDir)) {
13681
+ (0, import_node_fs12.mkdirSync)(configDir, { recursive: true });
13682
+ }
13683
+ (0, import_node_fs12.writeFileSync)(configPath, STARTER_CONFIG, "utf-8");
13684
+ env.stderr.write(`Created ${configPath}
13685
+ `);
13686
+ env.stderr.write("\n");
13687
+ env.stderr.write("Next steps:\n");
13688
+ env.stderr.write(" 1. Set your API key:\n");
13689
+ env.stderr.write(" export OPENAI_API_KEY=sk-...\n");
13690
+ env.stderr.write(" export ANTHROPIC_API_KEY=sk-...\n");
13691
+ env.stderr.write(" export GEMINI_API_KEY=...\n");
13692
+ env.stderr.write("\n");
13693
+ env.stderr.write(` 2. Customize your config:
13694
+ `);
13695
+ env.stderr.write(` $EDITOR ${configPath}
13696
+ `);
13697
+ env.stderr.write("\n");
13698
+ env.stderr.write(" 3. See all options:\n");
13699
+ env.stderr.write(
13700
+ " https://github.com/zbigniewsobiecki/llmist/blob/main/examples/cli.example.toml\n"
13701
+ );
13702
+ env.stderr.write("\n");
13703
+ env.stderr.write('Try it: llmist complete "Hello, world!"\n');
13704
+ }
13705
+ function registerInitCommand(program, env) {
13706
+ program.command(COMMANDS.init).description("Initialize llmist configuration at ~/.llmist/cli.toml").action((options) => executeAction(() => executeInit(options, env), env));
13707
+ }
13708
+
13209
13709
  // src/cli/environment.ts
13210
13710
  var import_node_readline = __toESM(require("readline"), 1);
13211
13711
  var import_chalk6 = __toESM(require("chalk"), 1);
@@ -13308,7 +13808,7 @@ function createCommandEnvironment(baseEnv, config) {
13308
13808
  createLogger: createLoggerFactory(loggerConfig)
13309
13809
  };
13310
13810
  }
13311
- function registerCustomCommand(program, name, config, env) {
13811
+ function registerCustomCommand(program, name, config, env, globalSubagents) {
13312
13812
  const type = config.type ?? "agent";
13313
13813
  const description = config.description ?? `Custom ${type} command`;
13314
13814
  const cmd = program.command(name).description(description).argument("[prompt]", "Prompt for the command. Falls back to stdin when available.");
@@ -13333,7 +13833,8 @@ function registerCustomCommand(program, name, config, env) {
13333
13833
  const configDefaults = configToAgentOptions(config);
13334
13834
  const options = {
13335
13835
  ...configDefaults,
13336
- ...cliOptions
13836
+ ...cliOptions,
13837
+ globalSubagents
13337
13838
  };
13338
13839
  await executeAgent(prompt, options, cmdEnv);
13339
13840
  }, cmdEnv);
@@ -13776,7 +14277,7 @@ function registerGadgetCommand(program, env) {
13776
14277
  }
13777
14278
 
13778
14279
  // src/cli/image-command.ts
13779
- var import_node_fs11 = require("fs");
14280
+ var import_node_fs13 = require("fs");
13780
14281
  var DEFAULT_IMAGE_MODEL = "dall-e-3";
13781
14282
  async function executeImage(promptArg, options, env) {
13782
14283
  const prompt = await resolvePrompt(promptArg, env);
@@ -13800,7 +14301,7 @@ async function executeImage(promptArg, options, env) {
13800
14301
  const imageData = result.images[0];
13801
14302
  if (imageData.b64Json) {
13802
14303
  const buffer = Buffer.from(imageData.b64Json, "base64");
13803
- (0, import_node_fs11.writeFileSync)(options.output, buffer);
14304
+ (0, import_node_fs13.writeFileSync)(options.output, buffer);
13804
14305
  if (!options.quiet) {
13805
14306
  env.stderr.write(`${SUMMARY_PREFIX} Image saved to ${options.output}
13806
14307
  `);
@@ -14241,7 +14742,7 @@ function registerModelsCommand(program, env) {
14241
14742
  }
14242
14743
 
14243
14744
  // src/cli/speech-command.ts
14244
- var import_node_fs12 = require("fs");
14745
+ var import_node_fs14 = require("fs");
14245
14746
  var DEFAULT_SPEECH_MODEL = "tts-1";
14246
14747
  var DEFAULT_VOICE = "nova";
14247
14748
  async function executeSpeech(textArg, options, env) {
@@ -14264,7 +14765,7 @@ async function executeSpeech(textArg, options, env) {
14264
14765
  });
14265
14766
  const audioBuffer = Buffer.from(result.audio);
14266
14767
  if (options.output) {
14267
- (0, import_node_fs12.writeFileSync)(options.output, audioBuffer);
14768
+ (0, import_node_fs14.writeFileSync)(options.output, audioBuffer);
14268
14769
  if (!options.quiet) {
14269
14770
  env.stderr.write(`${SUMMARY_PREFIX} Audio saved to ${options.output}
14270
14771
  `);
@@ -14338,17 +14839,18 @@ function createProgram(env, config) {
14338
14839
  writeErr: (str) => env.stderr.write(str)
14339
14840
  });
14340
14841
  registerCompleteCommand(program, env, config?.complete);
14341
- registerAgentCommand(program, env, config?.agent);
14842
+ registerAgentCommand(program, env, config?.agent, config?.subagents);
14342
14843
  registerImageCommand(program, env, config?.image);
14343
14844
  registerSpeechCommand(program, env, config?.speech);
14344
14845
  registerVisionCommand(program, env);
14345
14846
  registerModelsCommand(program, env);
14346
14847
  registerGadgetCommand(program, env);
14848
+ registerInitCommand(program, env);
14347
14849
  if (config) {
14348
14850
  const customNames = getCustomCommandNames(config);
14349
14851
  for (const name of customNames) {
14350
14852
  const cmdConfig = config[name];
14351
- registerCustomCommand(program, name, cmdConfig, env);
14853
+ registerCustomCommand(program, name, cmdConfig, env, config.subagents);
14352
14854
  }
14353
14855
  }
14354
14856
  return program;