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