llmist 0.4.0 → 0.5.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/index.cjs CHANGED
@@ -384,6 +384,7 @@ var init_prompt_config = __esm({
384
384
  criticalUsage: "INVOKE gadgets using the markers - do not describe what you want to do.",
385
385
  formatDescriptionYaml: "Parameters in YAML format (one per line)",
386
386
  formatDescriptionJson: "Parameters in JSON format (valid JSON object)",
387
+ formatDescriptionToml: "Parameters in TOML format (key = value pairs, use triple-quotes for multiline)",
387
388
  rules: () => [
388
389
  "Output ONLY plain text with the exact markers - never use function/tool calling",
389
390
  "You can invoke multiple gadgets in a single response",
@@ -391,6 +392,7 @@ var init_prompt_config = __esm({
391
392
  ],
392
393
  schemaLabelJson: "\n\nInput Schema (JSON):",
393
394
  schemaLabelYaml: "\n\nInput Schema (YAML):",
395
+ schemaLabelToml: "\n\nInput Schema (TOML):",
394
396
  customExamples: null
395
397
  };
396
398
  }
@@ -411,6 +413,15 @@ var init_messages = __esm({
411
413
  constructor(promptConfig) {
412
414
  this.promptConfig = promptConfig ?? {};
413
415
  }
416
+ /**
417
+ * Set custom prefixes for gadget markers.
418
+ * Used to configure history builder to match system prompt markers.
419
+ */
420
+ withPrefixes(startPrefix, endPrefix) {
421
+ this.startPrefix = startPrefix;
422
+ this.endPrefix = endPrefix;
423
+ return this;
424
+ }
414
425
  addSystem(content, metadata) {
415
426
  this.messages.push({ role: "system", content, metadata });
416
427
  return this;
@@ -448,7 +459,14 @@ var init_messages = __esm({
448
459
  for (const gadget of gadgets) {
449
460
  const gadgetName = gadget.name ?? gadget.constructor.name;
450
461
  const instruction = gadget.getInstruction(parameterFormat);
451
- const schemaMarker = parameterFormat === "yaml" ? "\n\nInput Schema (YAML):" : "\n\nInput Schema (JSON):";
462
+ const schemaMarkers = {
463
+ yaml: "\n\nInput Schema (YAML):",
464
+ json: "\n\nInput Schema (JSON):",
465
+ toml: "\n\nInput Schema (TOML):",
466
+ auto: "\n\nInput Schema (JSON):"
467
+ // auto defaults to JSON schema display
468
+ };
469
+ const schemaMarker = schemaMarkers[parameterFormat];
452
470
  const schemaIndex = instruction.indexOf(schemaMarker);
453
471
  const description = (schemaIndex !== -1 ? instruction.substring(0, schemaIndex) : instruction).trim();
454
472
  const schema = schemaIndex !== -1 ? instruction.substring(schemaIndex + schemaMarker.length).trim() : "";
@@ -468,15 +486,26 @@ ${schema}`);
468
486
  }
469
487
  buildUsageSection(parameterFormat, context) {
470
488
  const parts = [];
471
- const formatDescription = parameterFormat === "yaml" ? resolvePromptTemplate(
472
- this.promptConfig.formatDescriptionYaml,
473
- DEFAULT_PROMPTS.formatDescriptionYaml,
474
- context
475
- ) : resolvePromptTemplate(
476
- this.promptConfig.formatDescriptionJson,
477
- DEFAULT_PROMPTS.formatDescriptionJson,
478
- context
479
- );
489
+ const formatDescriptionMap = {
490
+ yaml: {
491
+ config: this.promptConfig.formatDescriptionYaml,
492
+ defaultValue: DEFAULT_PROMPTS.formatDescriptionYaml
493
+ },
494
+ json: {
495
+ config: this.promptConfig.formatDescriptionJson,
496
+ defaultValue: DEFAULT_PROMPTS.formatDescriptionJson
497
+ },
498
+ toml: {
499
+ config: this.promptConfig.formatDescriptionToml,
500
+ defaultValue: DEFAULT_PROMPTS.formatDescriptionToml
501
+ },
502
+ auto: {
503
+ config: this.promptConfig.formatDescriptionJson,
504
+ defaultValue: DEFAULT_PROMPTS.formatDescriptionJson
505
+ }
506
+ };
507
+ const { config, defaultValue } = formatDescriptionMap[parameterFormat];
508
+ const formatDescription = resolvePromptTemplate(config, defaultValue, context);
480
509
  parts.push("\n\nHOW TO INVOKE GADGETS");
481
510
  parts.push("\n=====================\n");
482
511
  const criticalUsage = resolvePromptTemplate(
@@ -504,38 +533,110 @@ CRITICAL: ${criticalUsage}
504
533
  return this.promptConfig.customExamples(context);
505
534
  }
506
535
  const parts = [];
507
- const singleExample = parameterFormat === "yaml" ? `${this.startPrefix}translate
536
+ const singleExamples = {
537
+ yaml: `${this.startPrefix}translate
508
538
  from: English
509
539
  to: Polish
510
- content: Paris is the capital of France.
511
- ${this.endPrefix}` : `${this.startPrefix}translate
512
- {"from": "English", "to": "Polish", "content": "Paris is the capital of France."}
513
- ${this.endPrefix}`;
540
+ content: "Paris is the capital of France: a beautiful city."
541
+ ${this.endPrefix}`,
542
+ json: `${this.startPrefix}translate
543
+ {"from": "English", "to": "Polish", "content": "Paris is the capital of France: a beautiful city."}
544
+ ${this.endPrefix}`,
545
+ toml: `${this.startPrefix}translate
546
+ from = "English"
547
+ to = "Polish"
548
+ content = "Paris is the capital of France: a beautiful city."
549
+ ${this.endPrefix}`,
550
+ auto: `${this.startPrefix}translate
551
+ {"from": "English", "to": "Polish", "content": "Paris is the capital of France: a beautiful city."}
552
+ ${this.endPrefix}`
553
+ };
514
554
  parts.push(`
515
555
 
516
556
  EXAMPLE (Single Gadget):
517
557
 
518
- ${singleExample}`);
519
- const multipleExample = parameterFormat === "yaml" ? `${this.startPrefix}translate
558
+ ${singleExamples[parameterFormat]}`);
559
+ const multipleExamples = {
560
+ yaml: `${this.startPrefix}translate
520
561
  from: English
521
562
  to: Polish
522
- content: Paris is the capital of France.
563
+ content: "Paris is the capital of France: a beautiful city."
523
564
  ${this.endPrefix}
524
565
  ${this.startPrefix}analyze
525
566
  type: economic_analysis
526
567
  matter: "Polish Economy"
527
- question: Polish arms exports 2025.
528
- ${this.endPrefix}` : `${this.startPrefix}translate
529
- {"from": "English", "to": "Polish", "content": "Paris is the capital of France."}
568
+ question: |
569
+ Analyze the following:
570
+ - Polish arms exports 2025
571
+ - Economic implications
572
+ ${this.endPrefix}`,
573
+ json: `${this.startPrefix}translate
574
+ {"from": "English", "to": "Polish", "content": "Paris is the capital of France: a beautiful city."}
575
+ ${this.endPrefix}
576
+ ${this.startPrefix}analyze
577
+ {"type": "economic_analysis", "matter": "Polish Economy", "question": "Analyze the following: Polish arms exports 2025, economic implications"}
578
+ ${this.endPrefix}`,
579
+ toml: `${this.startPrefix}translate
580
+ from = "English"
581
+ to = "Polish"
582
+ content = "Paris is the capital of France: a beautiful city."
583
+ ${this.endPrefix}
584
+ ${this.startPrefix}analyze
585
+ type = "economic_analysis"
586
+ matter = "Polish Economy"
587
+ question = """
588
+ Analyze the following:
589
+ - Polish arms exports 2025
590
+ - Economic implications
591
+ """
592
+ ${this.endPrefix}`,
593
+ auto: `${this.startPrefix}translate
594
+ {"from": "English", "to": "Polish", "content": "Paris is the capital of France: a beautiful city."}
530
595
  ${this.endPrefix}
531
596
  ${this.startPrefix}analyze
532
- {"type": "economic_analysis", "matter": "Polish Economy", "question": "Polish arms exports 2025."}
533
- ${this.endPrefix}`;
597
+ {"type": "economic_analysis", "matter": "Polish Economy", "question": "Analyze the following: Polish arms exports 2025, economic implications"}
598
+ ${this.endPrefix}`
599
+ };
534
600
  parts.push(`
535
601
 
536
602
  EXAMPLE (Multiple Gadgets):
537
603
 
538
- ${multipleExample}`);
604
+ ${multipleExamples[parameterFormat]}`);
605
+ if (parameterFormat === "yaml") {
606
+ parts.push(`
607
+
608
+ YAML MULTILINE SYNTAX:
609
+ For string values with special characters (colons, dashes, quotes) or multiple lines,
610
+ use the pipe (|) syntax. ALL content lines MUST be indented with 2 spaces:
611
+
612
+ CORRECT - all lines indented:
613
+ question: |
614
+ Which option do you prefer?
615
+ - Option A: fast processing
616
+ - Option B: thorough analysis
617
+ Please choose one.
618
+
619
+ WRONG - inconsistent indentation breaks YAML:
620
+ question: |
621
+ Which option do you prefer?
622
+ - Option A: fast
623
+ Please choose one. <-- ERROR: not indented, breaks out of the block`);
624
+ } else if (parameterFormat === "toml") {
625
+ parts.push(`
626
+
627
+ TOML MULTILINE SYNTAX:
628
+ For string values with multiple lines or special characters, use triple-quotes ("""):
629
+
630
+ filePath = "README.md"
631
+ content = """
632
+ # Project Title
633
+
634
+ This content can contain:
635
+ - Markdown lists
636
+ - Special characters: # : -
637
+ - Multiple paragraphs
638
+ """`);
639
+ }
539
640
  return parts.join("");
540
641
  }
541
642
  buildRulesSection(context) {
@@ -579,6 +680,16 @@ ${this.endPrefix}`
579
680
  return `${key}: ${JSON.stringify(value)}`;
580
681
  }).join("\n");
581
682
  }
683
+ if (format === "toml") {
684
+ return Object.entries(parameters).map(([key, value]) => {
685
+ if (typeof value === "string" && value.includes("\n")) {
686
+ return `${key} = """
687
+ ${value}
688
+ """`;
689
+ }
690
+ return `${key} = ${JSON.stringify(value)}`;
691
+ }).join("\n");
692
+ }
582
693
  return JSON.stringify(parameters);
583
694
  }
584
695
  build() {
@@ -680,11 +791,14 @@ var init_conversation_manager = __esm({
680
791
  initialMessages;
681
792
  historyBuilder;
682
793
  parameterFormat;
683
- constructor(baseMessages, initialMessages, parameterFormat = "json") {
794
+ constructor(baseMessages, initialMessages, options = {}) {
684
795
  this.baseMessages = baseMessages;
685
796
  this.initialMessages = initialMessages;
686
- this.parameterFormat = parameterFormat;
797
+ this.parameterFormat = options.parameterFormat ?? "json";
687
798
  this.historyBuilder = new LLMMessageBuilder();
799
+ if (options.startPrefix && options.endPrefix) {
800
+ this.historyBuilder.withPrefixes(options.startPrefix, options.endPrefix);
801
+ }
688
802
  }
689
803
  addUserMessage(content) {
690
804
  this.historyBuilder.addUser(content);
@@ -1185,11 +1299,109 @@ var init_executor = __esm({
1185
1299
  });
1186
1300
 
1187
1301
  // src/gadgets/parser.ts
1188
- var yaml, globalInvocationCounter, StreamParser;
1302
+ function preprocessYaml(yamlStr) {
1303
+ const lines = yamlStr.split("\n");
1304
+ const result = [];
1305
+ let i = 0;
1306
+ while (i < lines.length) {
1307
+ const line = lines[i];
1308
+ const match = line.match(/^(\s*)([\w-]+):\s+(.+)$/);
1309
+ if (match) {
1310
+ const [, indent, key, value] = match;
1311
+ if (value === "|" || value === ">" || value === "|-" || value === ">-") {
1312
+ result.push(line);
1313
+ i++;
1314
+ const keyIndentLen2 = indent.length;
1315
+ const blockLines = [];
1316
+ let minContentIndent = Infinity;
1317
+ while (i < lines.length) {
1318
+ const blockLine = lines[i];
1319
+ const blockIndentMatch = blockLine.match(/^(\s*)/);
1320
+ const blockIndentLen = blockIndentMatch ? blockIndentMatch[1].length : 0;
1321
+ if (blockLine.trim() === "") {
1322
+ blockLines.push({ content: "", originalIndent: 0 });
1323
+ i++;
1324
+ continue;
1325
+ }
1326
+ if (blockIndentLen > keyIndentLen2) {
1327
+ const content = blockLine.substring(blockIndentLen);
1328
+ blockLines.push({ content, originalIndent: blockIndentLen });
1329
+ if (content.trim().length > 0) {
1330
+ minContentIndent = Math.min(minContentIndent, blockIndentLen);
1331
+ }
1332
+ i++;
1333
+ } else {
1334
+ break;
1335
+ }
1336
+ }
1337
+ const targetIndent = keyIndentLen2 + 2;
1338
+ for (const blockLine of blockLines) {
1339
+ if (blockLine.content === "") {
1340
+ result.push("");
1341
+ } else {
1342
+ result.push(" ".repeat(targetIndent) + blockLine.content);
1343
+ }
1344
+ }
1345
+ continue;
1346
+ }
1347
+ if (value.startsWith('"') || value.startsWith("'") || value === "true" || value === "false" || /^-?\d+(\.\d+)?$/.test(value)) {
1348
+ result.push(line);
1349
+ i++;
1350
+ continue;
1351
+ }
1352
+ const keyIndentLen = indent.length;
1353
+ const continuationLines = [];
1354
+ let j = i + 1;
1355
+ while (j < lines.length) {
1356
+ const nextLine = lines[j];
1357
+ if (nextLine.trim() === "") {
1358
+ continuationLines.push(nextLine);
1359
+ j++;
1360
+ continue;
1361
+ }
1362
+ const nextIndentMatch = nextLine.match(/^(\s*)/);
1363
+ const nextIndentLen = nextIndentMatch ? nextIndentMatch[1].length : 0;
1364
+ if (nextIndentLen > keyIndentLen) {
1365
+ continuationLines.push(nextLine);
1366
+ j++;
1367
+ } else {
1368
+ break;
1369
+ }
1370
+ }
1371
+ if (continuationLines.length > 0 && continuationLines.some((l) => l.trim().length > 0)) {
1372
+ result.push(`${indent}${key}: |`);
1373
+ result.push(`${indent} ${value}`);
1374
+ for (const contLine of continuationLines) {
1375
+ if (contLine.trim() === "") {
1376
+ result.push("");
1377
+ } else {
1378
+ const contIndentMatch = contLine.match(/^(\s*)/);
1379
+ const contIndent = contIndentMatch ? contIndentMatch[1] : "";
1380
+ const contContent = contLine.substring(contIndent.length);
1381
+ result.push(`${indent} ${contContent}`);
1382
+ }
1383
+ }
1384
+ i = j;
1385
+ continue;
1386
+ }
1387
+ if (value.includes(": ") || value.endsWith(":")) {
1388
+ const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
1389
+ result.push(`${indent}${key}: "${escaped}"`);
1390
+ i++;
1391
+ continue;
1392
+ }
1393
+ }
1394
+ result.push(line);
1395
+ i++;
1396
+ }
1397
+ return result.join("\n");
1398
+ }
1399
+ var yaml, import_js_toml, globalInvocationCounter, StreamParser;
1189
1400
  var init_parser = __esm({
1190
1401
  "src/gadgets/parser.ts"() {
1191
1402
  "use strict";
1192
1403
  yaml = __toESM(require("js-yaml"), 1);
1404
+ import_js_toml = require("js-toml");
1193
1405
  init_constants();
1194
1406
  globalInvocationCounter = 0;
1195
1407
  StreamParser = class {
@@ -1211,6 +1423,17 @@ var init_parser = __esm({
1211
1423
  this.lastReportedTextLength = index;
1212
1424
  return segment.trim().length > 0 ? segment : void 0;
1213
1425
  }
1426
+ /**
1427
+ * Parse gadget name, handling both old format (name:invocationId) and new format (just name).
1428
+ * For new format, generates a unique invocation ID.
1429
+ */
1430
+ parseGadgetName(gadgetName) {
1431
+ if (gadgetName.includes(":")) {
1432
+ const parts = gadgetName.split(":");
1433
+ return { actualName: parts[0], invocationId: parts[1] };
1434
+ }
1435
+ return { actualName: gadgetName, invocationId: `gadget_${++globalInvocationCounter}` };
1436
+ }
1214
1437
  /**
1215
1438
  * Parse parameter string according to configured format
1216
1439
  */
@@ -1224,20 +1447,31 @@ var init_parser = __esm({
1224
1447
  }
1225
1448
  if (this.parameterFormat === "yaml") {
1226
1449
  try {
1227
- return { parameters: yaml.load(raw) };
1450
+ return { parameters: yaml.load(preprocessYaml(raw)) };
1228
1451
  } catch (error) {
1229
1452
  return { parseError: error instanceof Error ? error.message : "Failed to parse YAML" };
1230
1453
  }
1231
1454
  }
1455
+ if (this.parameterFormat === "toml") {
1456
+ try {
1457
+ return { parameters: (0, import_js_toml.load)(raw) };
1458
+ } catch (error) {
1459
+ return { parseError: error instanceof Error ? error.message : "Failed to parse TOML" };
1460
+ }
1461
+ }
1232
1462
  try {
1233
1463
  return { parameters: JSON.parse(raw) };
1234
1464
  } catch {
1235
1465
  try {
1236
- return { parameters: yaml.load(raw) };
1237
- } catch (error) {
1238
- return {
1239
- parseError: error instanceof Error ? error.message : "Failed to parse as JSON or YAML"
1240
- };
1466
+ return { parameters: (0, import_js_toml.load)(raw) };
1467
+ } catch {
1468
+ try {
1469
+ return { parameters: yaml.load(preprocessYaml(raw)) };
1470
+ } catch (error) {
1471
+ return {
1472
+ parseError: error instanceof Error ? error.message : "Failed to parse as JSON, TOML, or YAML"
1473
+ };
1474
+ }
1241
1475
  }
1242
1476
  }
1243
1477
  }
@@ -1256,16 +1490,7 @@ var init_parser = __esm({
1256
1490
  const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
1257
1491
  if (metadataEndIndex === -1) break;
1258
1492
  const gadgetName = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
1259
- let invocationId;
1260
- let actualGadgetName;
1261
- if (gadgetName.includes(":")) {
1262
- const parts = gadgetName.split(":");
1263
- actualGadgetName = parts[0];
1264
- invocationId = parts[1];
1265
- } else {
1266
- actualGadgetName = gadgetName;
1267
- invocationId = `gadget_${++globalInvocationCounter}`;
1268
- }
1493
+ const { actualName: actualGadgetName, invocationId } = this.parseGadgetName(gadgetName);
1269
1494
  const contentStartIndex = metadataEndIndex + 1;
1270
1495
  let partEndIndex;
1271
1496
  let endMarkerLength = 0;
@@ -1275,23 +1500,29 @@ var init_parser = __esm({
1275
1500
  if (partEndIndex === -1) break;
1276
1501
  endMarkerLength = oldEndMarker.length;
1277
1502
  } else {
1278
- partEndIndex = contentStartIndex;
1503
+ const nextStartPos = this.buffer.indexOf(this.startPrefix, contentStartIndex);
1504
+ let validEndPos = -1;
1505
+ let searchPos = contentStartIndex;
1279
1506
  while (true) {
1280
- const endPos = this.buffer.indexOf(this.endPrefix, partEndIndex);
1281
- if (endPos === -1) {
1282
- partEndIndex = -1;
1283
- break;
1284
- }
1507
+ const endPos = this.buffer.indexOf(this.endPrefix, searchPos);
1508
+ if (endPos === -1) break;
1285
1509
  const afterEnd = this.buffer.substring(endPos + this.endPrefix.length);
1286
1510
  if (afterEnd.startsWith("\n") || afterEnd.startsWith("\r") || afterEnd.startsWith(this.startPrefix) || afterEnd.length === 0) {
1287
- partEndIndex = endPos;
1288
- endMarkerLength = this.endPrefix.length;
1511
+ validEndPos = endPos;
1289
1512
  break;
1290
1513
  } else {
1291
- partEndIndex = endPos + this.endPrefix.length;
1514
+ searchPos = endPos + this.endPrefix.length;
1292
1515
  }
1293
1516
  }
1294
- if (partEndIndex === -1) break;
1517
+ if (nextStartPos !== -1 && (validEndPos === -1 || nextStartPos < validEndPos)) {
1518
+ partEndIndex = nextStartPos;
1519
+ endMarkerLength = 0;
1520
+ } else if (validEndPos !== -1) {
1521
+ partEndIndex = validEndPos;
1522
+ endMarkerLength = this.endPrefix.length;
1523
+ } else {
1524
+ break;
1525
+ }
1295
1526
  }
1296
1527
  const parametersRaw = this.buffer.substring(contentStartIndex, partEndIndex).trim();
1297
1528
  const { parameters, parseError } = this.parseParameters(parametersRaw);
@@ -1314,8 +1545,35 @@ var init_parser = __esm({
1314
1545
  this.lastReportedTextLength = 0;
1315
1546
  }
1316
1547
  }
1317
- // Finalize parsing and return remaining text
1548
+ // Finalize parsing and return remaining text or incomplete gadgets
1318
1549
  *finalize() {
1550
+ const startIndex = this.buffer.indexOf(this.startPrefix, this.lastReportedTextLength);
1551
+ if (startIndex !== -1) {
1552
+ const textBefore = this.takeTextUntil(startIndex);
1553
+ if (textBefore !== void 0) {
1554
+ yield { type: "text", content: textBefore };
1555
+ }
1556
+ const metadataStartIndex = startIndex + this.startPrefix.length;
1557
+ const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
1558
+ if (metadataEndIndex !== -1) {
1559
+ const gadgetName = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
1560
+ const { actualName: actualGadgetName, invocationId } = this.parseGadgetName(gadgetName);
1561
+ const contentStartIndex = metadataEndIndex + 1;
1562
+ const parametersRaw = this.buffer.substring(contentStartIndex).trim();
1563
+ const { parameters, parseError } = this.parseParameters(parametersRaw);
1564
+ yield {
1565
+ type: "gadget_call",
1566
+ call: {
1567
+ gadgetName: actualGadgetName,
1568
+ invocationId,
1569
+ parametersYaml: parametersRaw,
1570
+ parameters,
1571
+ parseError
1572
+ }
1573
+ };
1574
+ return;
1575
+ }
1576
+ }
1319
1577
  const remainingText = this.takeTextUntil(this.buffer.length);
1320
1578
  if (remainingText !== void 0) {
1321
1579
  yield { type: "text", content: remainingText };
@@ -1796,11 +2054,11 @@ var init_agent = __esm({
1796
2054
  role: message.role,
1797
2055
  content: message.content
1798
2056
  }));
1799
- this.conversation = new ConversationManager(
1800
- baseMessages,
1801
- initialMessages,
1802
- this.parameterFormat
1803
- );
2057
+ this.conversation = new ConversationManager(baseMessages, initialMessages, {
2058
+ parameterFormat: this.parameterFormat,
2059
+ startPrefix: options.gadgetStartPrefix,
2060
+ endPrefix: options.gadgetEndPrefix
2061
+ });
1804
2062
  this.userPromptProvided = !!options.userPrompt;
1805
2063
  if (options.userPrompt) {
1806
2064
  this.conversation.addUserMessage(options.userPrompt);
@@ -2791,10 +3049,11 @@ var init_gemini = __esm({
2791
3049
  return GEMINI_MODELS;
2792
3050
  }
2793
3051
  buildRequestPayload(options, descriptor, _spec, messages) {
2794
- const { systemInstruction, contents } = this.extractSystemAndContents(messages);
3052
+ const contents = this.convertMessagesToContents(messages);
2795
3053
  const generationConfig = this.buildGenerationConfig(options);
2796
3054
  const config = {
2797
- ...systemInstruction ? { systemInstruction: systemInstruction.parts.map((p) => p.text).join("\n") } : {},
3055
+ // Note: systemInstruction removed - it doesn't work with countTokens()
3056
+ // System messages are now included in contents as user+model exchanges
2798
3057
  ...generationConfig ? { ...generationConfig } : {},
2799
3058
  // Explicitly disable function calling to prevent UNEXPECTED_TOOL_CALL errors
2800
3059
  toolConfig: {
@@ -2815,31 +3074,37 @@ var init_gemini = __esm({
2815
3074
  const streamResponse = await client.models.generateContentStream(payload);
2816
3075
  return streamResponse;
2817
3076
  }
2818
- extractSystemAndContents(messages) {
2819
- const firstSystemIndex = messages.findIndex((message) => message.role === "system");
2820
- if (firstSystemIndex === -1) {
2821
- return {
2822
- systemInstruction: null,
2823
- contents: this.mergeConsecutiveMessages(messages)
2824
- };
3077
+ /**
3078
+ * Convert LLM messages to Gemini contents format.
3079
+ *
3080
+ * For Gemini, we convert system messages to user+model exchanges instead of
3081
+ * using systemInstruction, because:
3082
+ * 1. systemInstruction doesn't work with countTokens() API
3083
+ * 2. This approach gives perfect token counting accuracy (0% error)
3084
+ * 3. The model receives and follows system instructions identically
3085
+ *
3086
+ * System message: "You are a helpful assistant"
3087
+ * Becomes:
3088
+ * - User: "You are a helpful assistant"
3089
+ * - Model: "Understood."
3090
+ */
3091
+ convertMessagesToContents(messages) {
3092
+ const expandedMessages = [];
3093
+ for (const message of messages) {
3094
+ if (message.role === "system") {
3095
+ expandedMessages.push({
3096
+ role: "user",
3097
+ content: message.content
3098
+ });
3099
+ expandedMessages.push({
3100
+ role: "assistant",
3101
+ content: "Understood."
3102
+ });
3103
+ } else {
3104
+ expandedMessages.push(message);
3105
+ }
2825
3106
  }
2826
- let systemBlockEnd = firstSystemIndex;
2827
- while (systemBlockEnd < messages.length && messages[systemBlockEnd].role === "system") {
2828
- systemBlockEnd++;
2829
- }
2830
- const systemMessages = messages.slice(firstSystemIndex, systemBlockEnd);
2831
- const nonSystemMessages = [
2832
- ...messages.slice(0, firstSystemIndex),
2833
- ...messages.slice(systemBlockEnd)
2834
- ];
2835
- const systemInstruction = {
2836
- role: "system",
2837
- parts: systemMessages.map((message) => ({ text: message.content }))
2838
- };
2839
- return {
2840
- systemInstruction,
2841
- contents: this.mergeConsecutiveMessages(nonSystemMessages)
2842
- };
3107
+ return this.mergeConsecutiveMessages(expandedMessages);
2843
3108
  }
2844
3109
  mergeConsecutiveMessages(messages) {
2845
3110
  if (messages.length === 0) {
@@ -2928,8 +3193,8 @@ var init_gemini = __esm({
2928
3193
  *
2929
3194
  * This method provides accurate token estimation for Gemini models by:
2930
3195
  * - Using the SDK's countTokens() method
2931
- * - Properly extracting and handling system instructions
2932
- * - Transforming messages to Gemini's expected format
3196
+ * - Converting system messages to user+model exchanges (same as in generation)
3197
+ * - This gives perfect token counting accuracy (0% error vs actual usage)
2933
3198
  *
2934
3199
  * @param messages - The messages to count tokens for
2935
3200
  * @param descriptor - Model descriptor containing the model name
@@ -2948,16 +3213,14 @@ var init_gemini = __esm({
2948
3213
  */
2949
3214
  async countTokens(messages, descriptor, _spec) {
2950
3215
  const client = this.client;
2951
- const { systemInstruction, contents } = this.extractSystemAndContents(messages);
2952
- const request = {
2953
- model: descriptor.name,
2954
- contents: this.convertContentsForNewSDK(contents)
2955
- };
2956
- if (systemInstruction) {
2957
- request.systemInstruction = systemInstruction.parts.map((p) => p.text).join("\n");
2958
- }
3216
+ const contents = this.convertMessagesToContents(messages);
2959
3217
  try {
2960
- const response = await client.models.countTokens(request);
3218
+ const response = await client.models.countTokens({
3219
+ model: descriptor.name,
3220
+ contents: this.convertContentsForNewSDK(contents)
3221
+ // Note: systemInstruction not used - it's not supported by countTokens()
3222
+ // and would cause a 2100% token counting error
3223
+ });
2961
3224
  return response.totalTokens ?? 0;
2962
3225
  } catch (error) {
2963
3226
  console.warn(
@@ -5185,6 +5448,83 @@ function mergeDescriptions(schema, jsonSchema) {
5185
5448
 
5186
5449
  // src/gadgets/gadget.ts
5187
5450
  init_schema_validator();
5451
+ function formatYamlValue(value, indent = "") {
5452
+ if (typeof value === "string") {
5453
+ const lines = value.split("\n");
5454
+ if (lines.length === 1 && !value.includes(":") && !value.startsWith("-")) {
5455
+ return value;
5456
+ }
5457
+ const indentedLines = lines.map((line) => `${indent} ${line}`).join("\n");
5458
+ return `|
5459
+ ${indentedLines}`;
5460
+ }
5461
+ if (typeof value === "number" || typeof value === "boolean") {
5462
+ return String(value);
5463
+ }
5464
+ if (value === null || value === void 0) {
5465
+ return "null";
5466
+ }
5467
+ if (Array.isArray(value)) {
5468
+ if (value.length === 0) return "[]";
5469
+ const items = value.map((item) => `${indent}- ${formatYamlValue(item, indent + " ")}`);
5470
+ return "\n" + items.join("\n");
5471
+ }
5472
+ if (typeof value === "object") {
5473
+ const entries = Object.entries(value);
5474
+ if (entries.length === 0) return "{}";
5475
+ const lines = entries.map(([k, v]) => {
5476
+ const formattedValue = formatYamlValue(v, indent + " ");
5477
+ if (formattedValue.startsWith("\n") || formattedValue.startsWith("|")) {
5478
+ return `${indent}${k}: ${formattedValue}`;
5479
+ }
5480
+ return `${indent}${k}: ${formattedValue}`;
5481
+ });
5482
+ return "\n" + lines.join("\n");
5483
+ }
5484
+ return yaml2.dump(value).trimEnd();
5485
+ }
5486
+ function formatParamsAsYaml(params) {
5487
+ const lines = [];
5488
+ for (const [key, value] of Object.entries(params)) {
5489
+ const formattedValue = formatYamlValue(value, "");
5490
+ if (formattedValue.startsWith("\n")) {
5491
+ lines.push(`${key}:${formattedValue}`);
5492
+ } else {
5493
+ lines.push(`${key}: ${formattedValue}`);
5494
+ }
5495
+ }
5496
+ return lines.join("\n");
5497
+ }
5498
+ function formatTomlValue(value) {
5499
+ if (typeof value === "string") {
5500
+ if (value.includes("\n")) {
5501
+ return `"""
5502
+ ${value}
5503
+ """`;
5504
+ }
5505
+ return JSON.stringify(value);
5506
+ }
5507
+ if (typeof value === "number" || typeof value === "boolean") {
5508
+ return String(value);
5509
+ }
5510
+ if (value === null || value === void 0) {
5511
+ return '""';
5512
+ }
5513
+ if (Array.isArray(value)) {
5514
+ return JSON.stringify(value);
5515
+ }
5516
+ if (typeof value === "object") {
5517
+ return JSON.stringify(value);
5518
+ }
5519
+ return JSON.stringify(value);
5520
+ }
5521
+ function formatParamsAsToml(params) {
5522
+ const lines = [];
5523
+ for (const [key, value] of Object.entries(params)) {
5524
+ lines.push(`${key} = ${formatTomlValue(value)}`);
5525
+ }
5526
+ return lines.join("\n");
5527
+ }
5188
5528
  var BaseGadget = class {
5189
5529
  /**
5190
5530
  * The name of the gadget. Used for identification when LLM calls it.
@@ -5204,6 +5544,14 @@ var BaseGadget = class {
5204
5544
  * Set to 0 or undefined to disable timeout for this gadget.
5205
5545
  */
5206
5546
  timeoutMs;
5547
+ /**
5548
+ * Optional usage examples to help LLMs understand proper invocation.
5549
+ * Examples are rendered in getInstruction() alongside the schema.
5550
+ *
5551
+ * Note: Uses broader `unknown` type to allow typed examples from subclasses
5552
+ * while maintaining runtime compatibility.
5553
+ */
5554
+ examples;
5207
5555
  /**
5208
5556
  * Auto-generated instruction text for the LLM.
5209
5557
  * Combines name, description, and parameter schema into a formatted instruction.
@@ -5216,7 +5564,7 @@ var BaseGadget = class {
5216
5564
  * Generate instruction text for the LLM with format-specific schema.
5217
5565
  * Combines name, description, and parameter schema into a formatted instruction.
5218
5566
  *
5219
- * @param format - Format for the schema representation ('json' | 'yaml' | 'auto')
5567
+ * @param format - Format for the schema representation ('json' | 'yaml' | 'toml' | 'auto')
5220
5568
  * @returns Formatted instruction string
5221
5569
  */
5222
5570
  getInstruction(format = "json") {
@@ -5231,12 +5579,38 @@ var BaseGadget = class {
5231
5579
  if (format === "json" || format === "auto") {
5232
5580
  parts.push("\n\nInput Schema (JSON):");
5233
5581
  parts.push(JSON.stringify(jsonSchema, null, 2));
5582
+ } else if (format === "toml") {
5583
+ parts.push("\n\nInput Schema (TOML):");
5584
+ parts.push(JSON.stringify(jsonSchema, null, 2));
5234
5585
  } else {
5235
5586
  const yamlSchema = yaml2.dump(jsonSchema).trimEnd();
5236
5587
  parts.push("\n\nInput Schema (YAML):");
5237
5588
  parts.push(yamlSchema);
5238
5589
  }
5239
5590
  }
5591
+ if (this.examples && this.examples.length > 0) {
5592
+ parts.push("\n\nExamples:");
5593
+ this.examples.forEach((example, index) => {
5594
+ if (index > 0) {
5595
+ parts.push("");
5596
+ }
5597
+ if (example.comment) {
5598
+ parts.push(`# ${example.comment}`);
5599
+ }
5600
+ parts.push("Input:");
5601
+ if (format === "json" || format === "auto") {
5602
+ parts.push(JSON.stringify(example.params, null, 2));
5603
+ } else if (format === "toml") {
5604
+ parts.push(formatParamsAsToml(example.params));
5605
+ } else {
5606
+ parts.push(formatParamsAsYaml(example.params));
5607
+ }
5608
+ if (example.output !== void 0) {
5609
+ parts.push("Output:");
5610
+ parts.push(example.output);
5611
+ }
5612
+ });
5613
+ }
5240
5614
  return parts.join("\n");
5241
5615
  }
5242
5616
  };
@@ -5248,6 +5622,7 @@ function createGadget(config) {
5248
5622
  description = config.description;
5249
5623
  parameterSchema = config.schema;
5250
5624
  timeoutMs = config.timeoutMs;
5625
+ examples = config.examples;
5251
5626
  execute(params) {
5252
5627
  return config.execute(params);
5253
5628
  }
@@ -5268,6 +5643,7 @@ function Gadget(config) {
5268
5643
  parameterSchema = config.schema;
5269
5644
  name = config.name;
5270
5645
  timeoutMs = config.timeoutMs;
5646
+ examples = config.examples;
5271
5647
  /**
5272
5648
  * Type helper property for accessing inferred parameter type.
5273
5649
  * This is used in the execute method signature: `execute(params: this['params'])`