@jaypie/llm 1.2.40 → 1.2.41

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.
@@ -575,6 +575,154 @@ function extractReasoning(history) {
575
575
  return reasoningTexts;
576
576
  }
577
577
 
578
+ function naturalZodSchema(definition) {
579
+ if (Array.isArray(definition)) {
580
+ if (definition.length === 0) {
581
+ // Handle empty array - accept any[]
582
+ return v4.z.array(v4.z.any());
583
+ }
584
+ else if (definition.length === 1) {
585
+ // Handle array types
586
+ const itemType = definition[0];
587
+ switch (itemType) {
588
+ case String:
589
+ return v4.z.array(v4.z.string());
590
+ case Number:
591
+ return v4.z.array(v4.z.number());
592
+ case Boolean:
593
+ return v4.z.array(v4.z.boolean());
594
+ default:
595
+ if (typeof itemType === "object") {
596
+ // Handle array of objects
597
+ return v4.z.array(naturalZodSchema(itemType));
598
+ }
599
+ // Handle enum arrays
600
+ return v4.z.enum(definition);
601
+ }
602
+ }
603
+ else {
604
+ // Handle enum arrays
605
+ return v4.z.enum(definition);
606
+ }
607
+ }
608
+ else if (definition && typeof definition === "object") {
609
+ if (Object.keys(definition).length === 0) {
610
+ // Handle empty object - accept any key-value pairs
611
+ return v4.z.record(v4.z.string(), v4.z.any());
612
+ }
613
+ else {
614
+ // Handle object with properties
615
+ const schemaShape = {};
616
+ for (const [key, value] of Object.entries(definition)) {
617
+ schemaShape[key] = naturalZodSchema(value);
618
+ }
619
+ return v4.z.object(schemaShape);
620
+ }
621
+ }
622
+ else {
623
+ switch (definition) {
624
+ case String:
625
+ return v4.z.string();
626
+ case Number:
627
+ return v4.z.number();
628
+ case Boolean:
629
+ return v4.z.boolean();
630
+ case Object:
631
+ return v4.z.record(v4.z.string(), v4.z.any());
632
+ case Array:
633
+ return v4.z.array(v4.z.any());
634
+ default:
635
+ throw new Error(`Unsupported type: ${definition}`);
636
+ }
637
+ }
638
+ }
639
+
640
+ //
641
+ //
642
+ // Helpers
643
+ //
644
+ function isPlainObject(value) {
645
+ return typeof value === "object" && value !== null && !Array.isArray(value);
646
+ }
647
+ /**
648
+ * Convert a `format` declaration (Zod schema, NaturalSchema, or a JSON Schema
649
+ * JsonObject) into a plain JSON Schema we can walk. Mirrors the conversion the
650
+ * provider adapters perform in `formatOutputSchema`, but without any
651
+ * provider-specific sanitization.
652
+ */
653
+ function formatToJsonSchema(format) {
654
+ if (format instanceof v4.z.ZodType) {
655
+ return v4.z.toJSONSchema(format);
656
+ }
657
+ if (isPlainObject(format) && format.type === "json_schema") {
658
+ const clone = structuredClone(format);
659
+ clone.type = "object";
660
+ return clone;
661
+ }
662
+ try {
663
+ return v4.z.toJSONSchema(naturalZodSchema(format));
664
+ }
665
+ catch {
666
+ return undefined;
667
+ }
668
+ }
669
+ /**
670
+ * Walk a JSON Schema alongside a parsed value, filling any declared array field
671
+ * that is absent (`undefined`/`null`) with `[]`. Recurses into object
672
+ * properties and array items so nested declared arrays are also backfilled.
673
+ */
674
+ function fillFromSchema(schema, value) {
675
+ if (!isPlainObject(schema)) {
676
+ return value;
677
+ }
678
+ const type = schema.type;
679
+ const isArray = type === "array" || (type === undefined && "items" in schema);
680
+ if (isArray) {
681
+ if (value === undefined || value === null) {
682
+ return [];
683
+ }
684
+ const items = schema.items;
685
+ if (Array.isArray(value) && isPlainObject(items)) {
686
+ return value.map((entry) => fillFromSchema(items, entry));
687
+ }
688
+ return value;
689
+ }
690
+ const isObject = type === "object" || (type === undefined && "properties" in schema);
691
+ if (isObject) {
692
+ if (!isPlainObject(value)) {
693
+ return value;
694
+ }
695
+ const properties = schema.properties;
696
+ if (isPlainObject(properties)) {
697
+ for (const [key, propSchema] of Object.entries(properties)) {
698
+ value[key] = fillFromSchema(propSchema, value[key]);
699
+ }
700
+ }
701
+ return value;
702
+ }
703
+ return value;
704
+ }
705
+ //
706
+ //
707
+ // Main
708
+ //
709
+ /**
710
+ * Ensure every array field declared in `format` is present in `content` as an
711
+ * array. A declared `format` is a schema contract: an empty list should surface
712
+ * as `[]`, not be dropped from the response. Some providers/models omit empty
713
+ * array fields entirely, leaving consumers to read `.length` on `undefined`.
714
+ *
715
+ * Only mutates a (cloned) structured object; strings and non-objects pass
716
+ * through untouched.
717
+ */
718
+ function fillFormatArrays({ content, format, }) {
719
+ const schema = formatToJsonSchema(format);
720
+ if (!schema) {
721
+ return content;
722
+ }
723
+ return fillFromSchema(schema, structuredClone(content));
724
+ }
725
+
578
726
  /**
579
727
  * Converts a string to a standardized LlmInputMessage
580
728
  * @param input - String to format
@@ -702,68 +850,6 @@ function maxTurnsFromOptions(options) {
702
850
  return 1;
703
851
  }
704
852
 
705
- function naturalZodSchema(definition) {
706
- if (Array.isArray(definition)) {
707
- if (definition.length === 0) {
708
- // Handle empty array - accept any[]
709
- return v4.z.array(v4.z.any());
710
- }
711
- else if (definition.length === 1) {
712
- // Handle array types
713
- const itemType = definition[0];
714
- switch (itemType) {
715
- case String:
716
- return v4.z.array(v4.z.string());
717
- case Number:
718
- return v4.z.array(v4.z.number());
719
- case Boolean:
720
- return v4.z.array(v4.z.boolean());
721
- default:
722
- if (typeof itemType === "object") {
723
- // Handle array of objects
724
- return v4.z.array(naturalZodSchema(itemType));
725
- }
726
- // Handle enum arrays
727
- return v4.z.enum(definition);
728
- }
729
- }
730
- else {
731
- // Handle enum arrays
732
- return v4.z.enum(definition);
733
- }
734
- }
735
- else if (definition && typeof definition === "object") {
736
- if (Object.keys(definition).length === 0) {
737
- // Handle empty object - accept any key-value pairs
738
- return v4.z.record(v4.z.string(), v4.z.any());
739
- }
740
- else {
741
- // Handle object with properties
742
- const schemaShape = {};
743
- for (const [key, value] of Object.entries(definition)) {
744
- schemaShape[key] = naturalZodSchema(value);
745
- }
746
- return v4.z.object(schemaShape);
747
- }
748
- }
749
- else {
750
- switch (definition) {
751
- case String:
752
- return v4.z.string();
753
- case Number:
754
- return v4.z.number();
755
- case Boolean:
756
- return v4.z.boolean();
757
- case Object:
758
- return v4.z.record(v4.z.string(), v4.z.any());
759
- case Array:
760
- return v4.z.array(v4.z.any());
761
- default:
762
- throw new Error(`Unsupported type: ${definition}`);
763
- }
764
- }
765
- }
766
-
767
853
  //
768
854
  // Constants
769
855
  //
@@ -6092,7 +6178,7 @@ class OperateLoop {
6092
6178
  if (this.adapter.hasStructuredOutput(response)) {
6093
6179
  const structuredOutput = this.adapter.extractStructuredOutput(response);
6094
6180
  if (structuredOutput) {
6095
- state.responseBuilder.setContent(structuredOutput);
6181
+ state.responseBuilder.setContent(this.applyFormatArrayDefaults(structuredOutput, options));
6096
6182
  state.responseBuilder.complete();
6097
6183
  return false; // Stop loop
6098
6184
  }
@@ -6217,7 +6303,7 @@ class OperateLoop {
6217
6303
  }
6218
6304
  }
6219
6305
  // No tool calls or no toolkit - we're done
6220
- state.responseBuilder.setContent(parsed.content);
6306
+ state.responseBuilder.setContent(this.applyFormatArrayDefaults(parsed.content, options));
6221
6307
  state.responseBuilder.complete();
6222
6308
  // Add final history items
6223
6309
  const historyItems = this.adapter.responseToHistoryItems(parsed.raw);
@@ -6226,6 +6312,20 @@ class OperateLoop {
6226
6312
  }
6227
6313
  return false; // Stop loop
6228
6314
  }
6315
+ /**
6316
+ * Backfill declared array fields when a `format` is supplied. A declared
6317
+ * `format` is a schema contract: an empty array field should surface as `[]`
6318
+ * rather than be dropped by a provider/model that omits empty lists.
6319
+ */
6320
+ applyFormatArrayDefaults(content, options) {
6321
+ if (!options.format) {
6322
+ return content;
6323
+ }
6324
+ if (typeof content !== "object" || content === null) {
6325
+ return content;
6326
+ }
6327
+ return fillFormatArrays({ content, format: options.format });
6328
+ }
6229
6329
  /**
6230
6330
  * Sync the current input state from the updated provider request.
6231
6331
  * This is necessary because appendToolResult modifies the provider-specific request,