@prestyj/ai 5.16.2 → 5.19.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
@@ -572,6 +572,108 @@ function normalizeRootForAnthropic(schema) {
572
572
  return out;
573
573
  }
574
574
 
575
+ // src/utils/strict-tool-schema.ts
576
+ var UnsupportedStrictSchemaError = class extends Error {
577
+ };
578
+ var UNSUPPORTED_KEYS = [
579
+ "$ref",
580
+ "$defs",
581
+ "definitions",
582
+ "allOf",
583
+ "oneOf",
584
+ "patternProperties",
585
+ "dependentSchemas",
586
+ "dependencies",
587
+ "unevaluatedProperties",
588
+ "propertyNames",
589
+ "contains",
590
+ "prefixItems",
591
+ "not",
592
+ "if",
593
+ "then",
594
+ "else"
595
+ ];
596
+ function isSchemaObject(value) {
597
+ return typeof value === "object" && value !== null && !Array.isArray(value);
598
+ }
599
+ function isStructured(value) {
600
+ if (!isSchemaObject(value)) return false;
601
+ const types = typeof value.type === "string" ? [value.type] : Array.isArray(value.type) ? value.type : [];
602
+ return types.includes("object") || types.includes("array") || value.properties !== void 0 || value.items !== void 0;
603
+ }
604
+ function allowsNull(schema) {
605
+ if (!isSchemaObject(schema)) return false;
606
+ if (schema.type === "null" || Array.isArray(schema.type) && schema.type.includes("null"))
607
+ return true;
608
+ if (schema.const === null || Array.isArray(schema.enum) && schema.enum.includes(null))
609
+ return true;
610
+ return Array.isArray(schema.anyOf) && schema.anyOf.some((variant) => allowsNull(variant));
611
+ }
612
+ function makeNodeStrict(schema) {
613
+ if (!isSchemaObject(schema)) {
614
+ throw new UnsupportedStrictSchemaError("boolean schemas are unsupported");
615
+ }
616
+ for (const key of UNSUPPORTED_KEYS) {
617
+ if (schema[key] !== void 0) {
618
+ throw new UnsupportedStrictSchemaError(`${key} schemas are unsupported`);
619
+ }
620
+ }
621
+ if (schema.anyOf !== void 0) {
622
+ if (!Array.isArray(schema.anyOf) || schema.anyOf.length === 0) {
623
+ throw new UnsupportedStrictSchemaError("anyOf must contain at least one schema");
624
+ }
625
+ for (const variant of schema.anyOf) {
626
+ if (isStructured(variant)) {
627
+ throw new UnsupportedStrictSchemaError("object and array unions are unsupported");
628
+ }
629
+ makeNodeStrict(variant);
630
+ }
631
+ }
632
+ if (schema.items !== void 0) {
633
+ if (Array.isArray(schema.items)) {
634
+ throw new UnsupportedStrictSchemaError("tuple schemas are unsupported");
635
+ }
636
+ makeNodeStrict(schema.items);
637
+ }
638
+ const isObjectSchema = schema.type === "object";
639
+ if (schema.properties !== void 0 && !isObjectSchema) {
640
+ throw new UnsupportedStrictSchemaError("properties require type object");
641
+ }
642
+ if (!isObjectSchema) return;
643
+ if (schema.additionalProperties !== void 0 && schema.additionalProperties !== false) {
644
+ throw new UnsupportedStrictSchemaError(
645
+ "schema-valued or true additionalProperties is unsupported"
646
+ );
647
+ }
648
+ if (schema.properties !== void 0 && !isSchemaObject(schema.properties)) {
649
+ throw new UnsupportedStrictSchemaError("object properties must be a schema map");
650
+ }
651
+ const properties = schema.properties ?? {};
652
+ const required = new Set(Array.isArray(schema.required) ? schema.required : []);
653
+ for (const [key, property] of Object.entries(properties)) {
654
+ makeNodeStrict(property);
655
+ if (!required.has(key) && !allowsNull(property)) {
656
+ properties[key] = { anyOf: [property, { type: "null" }] };
657
+ }
658
+ }
659
+ schema.required = Object.keys(properties);
660
+ schema.additionalProperties = false;
661
+ }
662
+ function supportsStrictToolSampling(provider) {
663
+ return provider === "openai";
664
+ }
665
+ function makeStrictToolSchema(schema) {
666
+ const cloned = structuredClone(schema);
667
+ if (!isSchemaObject(cloned)) {
668
+ throw new UnsupportedStrictSchemaError("root schema must be an object");
669
+ }
670
+ makeNodeStrict(cloned);
671
+ if (cloned.type !== "object") {
672
+ throw new UnsupportedStrictSchemaError("root schema must have type object");
673
+ }
674
+ return cloned;
675
+ }
676
+
575
677
  // src/providers/reasoning-field.ts
576
678
  var REASONING_FIELD_ALIASES = [
577
679
  "reasoning_content",
@@ -1074,13 +1176,11 @@ function toOpenAIMessages(messages, options) {
1074
1176
  const parts = typeof msg.content === "string" ? msg.content : void 0;
1075
1177
  const toolCalls = typeof msg.content !== "string" ? msg.content.filter(
1076
1178
  (p) => p.type === "tool_call"
1077
- ).map(
1078
- (tc) => ({
1079
- id: remapToolCallId(tc.id, idMap),
1080
- type: "function",
1081
- function: { name: tc.name, arguments: JSON.stringify(tc.args) }
1082
- })
1083
- ) : void 0;
1179
+ ).map((tc) => ({
1180
+ id: remapToolCallId(tc.id, idMap),
1181
+ type: "function",
1182
+ function: { name: tc.name, arguments: JSON.stringify(tc.args) }
1183
+ })) : void 0;
1084
1184
  const textParts = typeof msg.content !== "string" ? msg.content.filter((p) => p.type === "text").map((p) => p.text).join("") : void 0;
1085
1185
  const thinkingParts = typeof msg.content !== "string" ? msg.content.filter((p) => p.type === "thinking").map((p) => p.text).join("") : void 0;
1086
1186
  const contentValue = parts || textParts || null;
@@ -1156,15 +1256,28 @@ function toOpenAIMessages(messages, options) {
1156
1256
  }
1157
1257
  return out;
1158
1258
  }
1159
- function toOpenAITools(tools) {
1160
- return tools.map((tool) => ({
1161
- type: "function",
1162
- function: {
1163
- name: tool.name,
1164
- description: tool.description,
1165
- parameters: resolveToolSchema(tool)
1259
+ function toOpenAITools(tools, opts) {
1260
+ return tools.map((tool) => {
1261
+ let parameters = resolveToolSchema(tool);
1262
+ let strict;
1263
+ if (opts?.strict) {
1264
+ try {
1265
+ parameters = makeStrictToolSchema(parameters);
1266
+ strict = true;
1267
+ } catch (error) {
1268
+ if (!(error instanceof UnsupportedStrictSchemaError)) throw error;
1269
+ }
1166
1270
  }
1167
- }));
1271
+ return {
1272
+ type: "function",
1273
+ function: {
1274
+ name: tool.name,
1275
+ description: tool.description,
1276
+ parameters,
1277
+ ...strict ? { strict } : {}
1278
+ }
1279
+ };
1280
+ });
1168
1281
  }
1169
1282
  function toOpenAIToolChoice(choice) {
1170
1283
  if (choice === "auto") return "auto";
@@ -2022,7 +2135,11 @@ async function* runStream2(options) {
2022
2135
  ...options.topP != null && !hasFixedKimiSampling ? { top_p: options.topP } : {},
2023
2136
  ...options.stop ? { stop: options.stop } : {},
2024
2137
  ...options.thinking && !usesThinkingParam && !isKimiK3 && !isKimiK27 && !isLocal ? { reasoning_effort: toOpenAIReasoningEffort(options.thinking, options.model) } : {},
2025
- ...options.tools?.length ? { tools: toOpenAITools(options.tools) } : {},
2138
+ ...options.tools?.length ? {
2139
+ tools: toOpenAITools(options.tools, {
2140
+ strict: supportsStrictToolSampling(options.provider)
2141
+ })
2142
+ } : {},
2026
2143
  ...options.toolChoice && options.tools?.length ? { tool_choice: toOpenAIToolChoice(options.toolChoice) } : {},
2027
2144
  ...useStreaming ? { stream_options: { include_usage: true } } : {}
2028
2145
  };
@@ -2531,7 +2648,7 @@ function toCodexToolChoice(choice, tools) {
2531
2648
  function streamOpenAICodex(options) {
2532
2649
  return new StreamResult(runStream3(options), options.signal);
2533
2650
  }
2534
- async function* runStream3(options) {
2651
+ async function* runStream3(options, retriedWithoutReasoning = false) {
2535
2652
  const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
2536
2653
  const url = `${baseUrl}/codex/responses`;
2537
2654
  const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
@@ -2601,6 +2718,20 @@ async function* runStream3(options) {
2601
2718
  const parsed = parseCodexErrorBody(text, response.status);
2602
2719
  const message = parsed.message ?? `Codex API returned HTTP ${response.status}.`;
2603
2720
  const requestId = parsed.requestId ?? readHeader(response.headers, "x-request-id", "openai-request-id", "x-oai-request-id");
2721
+ if (!retriedWithoutReasoning && !options.signal?.aborted && response.status === 400 && (parsed.errorObj?.code === "invalid_encrypted_content" || /encrypted content.*could not be (?:verified|decrypted|parsed)/i.test(message)) && options.messages.some(
2722
+ (msg) => msg.role === "assistant" && Array.isArray(msg.content) && msg.content.some((part) => part.type === "raw" && isEncryptedReasoning(part.data))
2723
+ )) {
2724
+ providerDiag("codex_retry_without_encrypted_reasoning", { status: response.status });
2725
+ const messages = options.messages.map(
2726
+ (msg) => msg.role === "assistant" && Array.isArray(msg.content) ? {
2727
+ ...msg,
2728
+ content: msg.content.filter(
2729
+ (part) => !(part.type === "raw" && isEncryptedReasoning(part.data))
2730
+ )
2731
+ } : msg
2732
+ );
2733
+ return yield* runStream3({ ...options, messages }, true);
2734
+ }
2604
2735
  const usageLimit = codexUsageLimitError(parsed.errorObj, response.status, requestId);
2605
2736
  if (usageLimit) throw usageLimit;
2606
2737
  let hint;
@@ -2980,13 +3111,23 @@ function toCodexInput(messages, options) {
2980
3111
  return { system, input };
2981
3112
  }
2982
3113
  function toCodexTools(tools) {
2983
- return tools.map((tool) => ({
2984
- type: "function",
2985
- name: tool.name,
2986
- description: tool.description,
2987
- parameters: resolveToolSchema(tool),
2988
- strict: null
2989
- }));
3114
+ return tools.map((tool) => {
3115
+ let parameters = resolveToolSchema(tool);
3116
+ let strict = null;
3117
+ try {
3118
+ parameters = makeStrictToolSchema(parameters);
3119
+ strict = true;
3120
+ } catch (error) {
3121
+ if (!(error instanceof UnsupportedStrictSchemaError)) throw error;
3122
+ }
3123
+ return {
3124
+ type: "function",
3125
+ name: tool.name,
3126
+ description: tool.description,
3127
+ parameters,
3128
+ strict
3129
+ };
3130
+ });
2990
3131
  }
2991
3132
  function parseCodexErrorBody(text, statusCode) {
2992
3133
  if (!text) return {};