@jaypie/llm 1.3.1 → 1.3.3

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.
@@ -641,7 +641,7 @@ function naturalZodSchema(definition) {
641
641
  //
642
642
  // Helpers
643
643
  //
644
- function isPlainObject(value) {
644
+ function isPlainObject$1(value) {
645
645
  return typeof value === "object" && value !== null && !Array.isArray(value);
646
646
  }
647
647
  /**
@@ -650,11 +650,11 @@ function isPlainObject(value) {
650
650
  * provider adapters perform in `formatOutputSchema`, but without any
651
651
  * provider-specific sanitization.
652
652
  */
653
- function formatToJsonSchema(format) {
653
+ function formatToJsonSchema$1(format) {
654
654
  if (format instanceof v4.z.ZodType) {
655
655
  return v4.z.toJSONSchema(format);
656
656
  }
657
- if (isPlainObject(format) && format.type === "json_schema") {
657
+ if (isPlainObject$1(format) && format.type === "json_schema") {
658
658
  const clone = structuredClone(format);
659
659
  clone.type = "object";
660
660
  return clone;
@@ -672,7 +672,7 @@ function formatToJsonSchema(format) {
672
672
  * properties and array items so nested declared arrays are also backfilled.
673
673
  */
674
674
  function fillFromSchema(schema, value) {
675
- if (!isPlainObject(schema)) {
675
+ if (!isPlainObject$1(schema)) {
676
676
  return value;
677
677
  }
678
678
  const type = schema.type;
@@ -682,18 +682,18 @@ function fillFromSchema(schema, value) {
682
682
  return [];
683
683
  }
684
684
  const items = schema.items;
685
- if (Array.isArray(value) && isPlainObject(items)) {
685
+ if (Array.isArray(value) && isPlainObject$1(items)) {
686
686
  return value.map((entry) => fillFromSchema(items, entry));
687
687
  }
688
688
  return value;
689
689
  }
690
690
  const isObject = type === "object" || (type === undefined && "properties" in schema);
691
691
  if (isObject) {
692
- if (!isPlainObject(value)) {
692
+ if (!isPlainObject$1(value)) {
693
693
  return value;
694
694
  }
695
695
  const properties = schema.properties;
696
- if (isPlainObject(properties)) {
696
+ if (isPlainObject$1(properties)) {
697
697
  for (const [key, propSchema] of Object.entries(properties)) {
698
698
  value[key] = fillFromSchema(propSchema, value[key]);
699
699
  }
@@ -716,7 +716,7 @@ function fillFromSchema(schema, value) {
716
716
  * through untouched.
717
717
  */
718
718
  function fillFormatArrays({ content, format, }) {
719
- const schema = formatToJsonSchema(format);
719
+ const schema = formatToJsonSchema$1(format);
720
720
  if (!schema) {
721
721
  return content;
722
722
  }
@@ -978,6 +978,116 @@ function random$1(defaultSeed) {
978
978
  return rngFn;
979
979
  }
980
980
 
981
+ //
982
+ //
983
+ // Helpers
984
+ //
985
+ function isPlainObject(value) {
986
+ return typeof value === "object" && value !== null && !Array.isArray(value);
987
+ }
988
+ /**
989
+ * Convert a `format` declaration (Zod schema, NaturalSchema, or a JSON Schema
990
+ * JsonObject) into a plain JSON Schema we can walk. Mirrors the conversion the
991
+ * provider adapters perform in `formatOutputSchema`, but without any
992
+ * provider-specific sanitization.
993
+ */
994
+ function formatToJsonSchema(format) {
995
+ if (format instanceof v4.z.ZodType) {
996
+ return v4.z.toJSONSchema(format);
997
+ }
998
+ if (isPlainObject(format) && format.type === "json_schema") {
999
+ const clone = structuredClone(format);
1000
+ clone.type = "object";
1001
+ return clone;
1002
+ }
1003
+ try {
1004
+ return v4.z.toJSONSchema(naturalZodSchema(format));
1005
+ }
1006
+ catch {
1007
+ return undefined;
1008
+ }
1009
+ }
1010
+ /**
1011
+ * Strip a single pair of surrounding double quotes from a key, if present.
1012
+ * `"Merchant Request"` -> `Merchant Request`; `Confidence` -> `Confidence`.
1013
+ */
1014
+ function dequoteKey(key) {
1015
+ if (key.length >= 2 && key.startsWith('"') && key.endsWith('"')) {
1016
+ return key.slice(1, -1);
1017
+ }
1018
+ return key;
1019
+ }
1020
+ /**
1021
+ * Walk a JSON Schema alongside a parsed value, renaming any object key whose
1022
+ * de-quoted form matches a declared property name. Recurses into declared
1023
+ * object properties and array items so nested corrupted keys are also repaired.
1024
+ */
1025
+ function repairFromSchema(schema, value) {
1026
+ if (!isPlainObject(schema)) {
1027
+ return value;
1028
+ }
1029
+ const type = schema.type;
1030
+ const isArray = type === "array" || (type === undefined && "items" in schema);
1031
+ if (isArray) {
1032
+ const items = schema.items;
1033
+ if (Array.isArray(value) && isPlainObject(items)) {
1034
+ return value.map((entry) => repairFromSchema(items, entry));
1035
+ }
1036
+ return value;
1037
+ }
1038
+ const isObject = type === "object" || (type === undefined && "properties" in schema);
1039
+ if (isObject && isPlainObject(value)) {
1040
+ const properties = schema.properties;
1041
+ if (isPlainObject(properties)) {
1042
+ const declared = new Set(Object.keys(properties));
1043
+ // Repair keys: rename a quote-wrapped key to its declared form, unless
1044
+ // the correct key is already present (then drop the corrupted duplicate).
1045
+ for (const key of Object.keys(value)) {
1046
+ if (declared.has(key)) {
1047
+ continue;
1048
+ }
1049
+ const repaired = dequoteKey(key);
1050
+ if (repaired !== key && declared.has(repaired)) {
1051
+ if (!(repaired in value)) {
1052
+ value[repaired] = value[key];
1053
+ }
1054
+ delete value[key];
1055
+ }
1056
+ }
1057
+ // Recurse into declared properties now that keys are aligned.
1058
+ for (const [key, propSchema] of Object.entries(properties)) {
1059
+ if (key in value) {
1060
+ value[key] = repairFromSchema(propSchema, value[key]);
1061
+ }
1062
+ }
1063
+ }
1064
+ return value;
1065
+ }
1066
+ return value;
1067
+ }
1068
+ //
1069
+ //
1070
+ // Main
1071
+ //
1072
+ /**
1073
+ * Repair structured-output keys that a provider/model corrupted by wrapping
1074
+ * them in literal double quotes (observed on OpenAI for multi-word `format`
1075
+ * keys: `Merchant Request` returned as `"Merchant Request"`). A declared
1076
+ * `format` is a schema contract: returned keys should match the declared names
1077
+ * exactly. Any returned key whose de-quoted form matches a declared key is
1078
+ * renamed back to the declared key.
1079
+ *
1080
+ * Only mutates a (cloned) structured object; strings and non-objects pass
1081
+ * through untouched.
1082
+ */
1083
+ function repairFormatKeys({ content, format, }) {
1084
+ const schema = formatToJsonSchema(format);
1085
+ if (!schema) {
1086
+ return content;
1087
+ }
1088
+ return repairFromSchema(schema, structuredClone(content));
1089
+ }
1090
+
981
1091
  /**
982
1092
  * Helper function to safely call a function that might throw
983
1093
  * @param fn Function to call safely
@@ -3547,7 +3657,16 @@ class OpenAiAdapter extends BaseProviderAdapter {
3547
3657
  ? schema
3548
3658
  : naturalZodSchema(schema);
3549
3659
  const responseFormat = zod.zodResponseFormat(zodSchema, "response");
3550
- const jsonSchema = v4.z.toJSONSchema(zodSchema);
3660
+ // Re-spread to drop zod v4's non-enumerable `~standard` Standard-Schema
3661
+ // interop marker, then strip `$schema`: OpenAI's strict json_schema subset
3662
+ // does not recognize the draft-2020-12 `$schema` keyword, and shipping it
3663
+ // can leave strict structured-output enforcement silently disabled (the
3664
+ // model then free-forms, omitting required fields and corrupting keys).
3665
+ // Mirrors the OpenRouter adapter's sanitization.
3666
+ const jsonSchema = {
3667
+ ...v4.z.toJSONSchema(zodSchema),
3668
+ };
3669
+ delete jsonSchema.$schema;
3551
3670
  // OpenAI requires additionalProperties to be false on all objects
3552
3671
  const checks = [jsonSchema];
3553
3672
  while (checks.length > 0) {
@@ -6313,9 +6432,12 @@ class OperateLoop {
6313
6432
  return false; // Stop loop
6314
6433
  }
6315
6434
  /**
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.
6435
+ * Reconcile structured output against the declared `format` contract. A
6436
+ * declared `format` is a schema contract: returned keys should match the
6437
+ * declared names exactly and every declared array field should be present.
6438
+ * First repairs keys a provider/model corrupted by wrapping them in literal
6439
+ * double quotes (observed on OpenAI for multi-word keys), then backfills any
6440
+ * declared array field a provider/model omitted, surfacing it as `[]`.
6319
6441
  */
6320
6442
  applyFormatArrayDefaults(content, options) {
6321
6443
  if (!options.format) {
@@ -6324,7 +6446,8 @@ class OperateLoop {
6324
6446
  if (typeof content !== "object" || content === null) {
6325
6447
  return content;
6326
6448
  }
6327
- return fillFormatArrays({ content, format: options.format });
6449
+ const repaired = repairFormatKeys({ content, format: options.format });
6450
+ return fillFormatArrays({ content: repaired, format: options.format });
6328
6451
  }
6329
6452
  /**
6330
6453
  * Sync the current input state from the updated provider request.