@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.js CHANGED
@@ -505,6 +505,108 @@ function normalizeRootForAnthropic(schema) {
505
505
  return out;
506
506
  }
507
507
 
508
+ // src/utils/strict-tool-schema.ts
509
+ var UnsupportedStrictSchemaError = class extends Error {
510
+ };
511
+ var UNSUPPORTED_KEYS = [
512
+ "$ref",
513
+ "$defs",
514
+ "definitions",
515
+ "allOf",
516
+ "oneOf",
517
+ "patternProperties",
518
+ "dependentSchemas",
519
+ "dependencies",
520
+ "unevaluatedProperties",
521
+ "propertyNames",
522
+ "contains",
523
+ "prefixItems",
524
+ "not",
525
+ "if",
526
+ "then",
527
+ "else"
528
+ ];
529
+ function isSchemaObject(value) {
530
+ return typeof value === "object" && value !== null && !Array.isArray(value);
531
+ }
532
+ function isStructured(value) {
533
+ if (!isSchemaObject(value)) return false;
534
+ const types = typeof value.type === "string" ? [value.type] : Array.isArray(value.type) ? value.type : [];
535
+ return types.includes("object") || types.includes("array") || value.properties !== void 0 || value.items !== void 0;
536
+ }
537
+ function allowsNull(schema) {
538
+ if (!isSchemaObject(schema)) return false;
539
+ if (schema.type === "null" || Array.isArray(schema.type) && schema.type.includes("null"))
540
+ return true;
541
+ if (schema.const === null || Array.isArray(schema.enum) && schema.enum.includes(null))
542
+ return true;
543
+ return Array.isArray(schema.anyOf) && schema.anyOf.some((variant) => allowsNull(variant));
544
+ }
545
+ function makeNodeStrict(schema) {
546
+ if (!isSchemaObject(schema)) {
547
+ throw new UnsupportedStrictSchemaError("boolean schemas are unsupported");
548
+ }
549
+ for (const key of UNSUPPORTED_KEYS) {
550
+ if (schema[key] !== void 0) {
551
+ throw new UnsupportedStrictSchemaError(`${key} schemas are unsupported`);
552
+ }
553
+ }
554
+ if (schema.anyOf !== void 0) {
555
+ if (!Array.isArray(schema.anyOf) || schema.anyOf.length === 0) {
556
+ throw new UnsupportedStrictSchemaError("anyOf must contain at least one schema");
557
+ }
558
+ for (const variant of schema.anyOf) {
559
+ if (isStructured(variant)) {
560
+ throw new UnsupportedStrictSchemaError("object and array unions are unsupported");
561
+ }
562
+ makeNodeStrict(variant);
563
+ }
564
+ }
565
+ if (schema.items !== void 0) {
566
+ if (Array.isArray(schema.items)) {
567
+ throw new UnsupportedStrictSchemaError("tuple schemas are unsupported");
568
+ }
569
+ makeNodeStrict(schema.items);
570
+ }
571
+ const isObjectSchema = schema.type === "object";
572
+ if (schema.properties !== void 0 && !isObjectSchema) {
573
+ throw new UnsupportedStrictSchemaError("properties require type object");
574
+ }
575
+ if (!isObjectSchema) return;
576
+ if (schema.additionalProperties !== void 0 && schema.additionalProperties !== false) {
577
+ throw new UnsupportedStrictSchemaError(
578
+ "schema-valued or true additionalProperties is unsupported"
579
+ );
580
+ }
581
+ if (schema.properties !== void 0 && !isSchemaObject(schema.properties)) {
582
+ throw new UnsupportedStrictSchemaError("object properties must be a schema map");
583
+ }
584
+ const properties = schema.properties ?? {};
585
+ const required = new Set(Array.isArray(schema.required) ? schema.required : []);
586
+ for (const [key, property] of Object.entries(properties)) {
587
+ makeNodeStrict(property);
588
+ if (!required.has(key) && !allowsNull(property)) {
589
+ properties[key] = { anyOf: [property, { type: "null" }] };
590
+ }
591
+ }
592
+ schema.required = Object.keys(properties);
593
+ schema.additionalProperties = false;
594
+ }
595
+ function supportsStrictToolSampling(provider) {
596
+ return provider === "openai";
597
+ }
598
+ function makeStrictToolSchema(schema) {
599
+ const cloned = structuredClone(schema);
600
+ if (!isSchemaObject(cloned)) {
601
+ throw new UnsupportedStrictSchemaError("root schema must be an object");
602
+ }
603
+ makeNodeStrict(cloned);
604
+ if (cloned.type !== "object") {
605
+ throw new UnsupportedStrictSchemaError("root schema must have type object");
606
+ }
607
+ return cloned;
608
+ }
609
+
508
610
  // src/providers/reasoning-field.ts
509
611
  var REASONING_FIELD_ALIASES = [
510
612
  "reasoning_content",
@@ -1007,13 +1109,11 @@ function toOpenAIMessages(messages, options) {
1007
1109
  const parts = typeof msg.content === "string" ? msg.content : void 0;
1008
1110
  const toolCalls = typeof msg.content !== "string" ? msg.content.filter(
1009
1111
  (p) => p.type === "tool_call"
1010
- ).map(
1011
- (tc) => ({
1012
- id: remapToolCallId(tc.id, idMap),
1013
- type: "function",
1014
- function: { name: tc.name, arguments: JSON.stringify(tc.args) }
1015
- })
1016
- ) : void 0;
1112
+ ).map((tc) => ({
1113
+ id: remapToolCallId(tc.id, idMap),
1114
+ type: "function",
1115
+ function: { name: tc.name, arguments: JSON.stringify(tc.args) }
1116
+ })) : void 0;
1017
1117
  const textParts = typeof msg.content !== "string" ? msg.content.filter((p) => p.type === "text").map((p) => p.text).join("") : void 0;
1018
1118
  const thinkingParts = typeof msg.content !== "string" ? msg.content.filter((p) => p.type === "thinking").map((p) => p.text).join("") : void 0;
1019
1119
  const contentValue = parts || textParts || null;
@@ -1089,15 +1189,28 @@ function toOpenAIMessages(messages, options) {
1089
1189
  }
1090
1190
  return out;
1091
1191
  }
1092
- function toOpenAITools(tools) {
1093
- return tools.map((tool) => ({
1094
- type: "function",
1095
- function: {
1096
- name: tool.name,
1097
- description: tool.description,
1098
- parameters: resolveToolSchema(tool)
1192
+ function toOpenAITools(tools, opts) {
1193
+ return tools.map((tool) => {
1194
+ let parameters = resolveToolSchema(tool);
1195
+ let strict;
1196
+ if (opts?.strict) {
1197
+ try {
1198
+ parameters = makeStrictToolSchema(parameters);
1199
+ strict = true;
1200
+ } catch (error) {
1201
+ if (!(error instanceof UnsupportedStrictSchemaError)) throw error;
1202
+ }
1099
1203
  }
1100
- }));
1204
+ return {
1205
+ type: "function",
1206
+ function: {
1207
+ name: tool.name,
1208
+ description: tool.description,
1209
+ parameters,
1210
+ ...strict ? { strict } : {}
1211
+ }
1212
+ };
1213
+ });
1101
1214
  }
1102
1215
  function toOpenAIToolChoice(choice) {
1103
1216
  if (choice === "auto") return "auto";
@@ -1955,7 +2068,11 @@ async function* runStream2(options) {
1955
2068
  ...options.topP != null && !hasFixedKimiSampling ? { top_p: options.topP } : {},
1956
2069
  ...options.stop ? { stop: options.stop } : {},
1957
2070
  ...options.thinking && !usesThinkingParam && !isKimiK3 && !isKimiK27 && !isLocal ? { reasoning_effort: toOpenAIReasoningEffort(options.thinking, options.model) } : {},
1958
- ...options.tools?.length ? { tools: toOpenAITools(options.tools) } : {},
2071
+ ...options.tools?.length ? {
2072
+ tools: toOpenAITools(options.tools, {
2073
+ strict: supportsStrictToolSampling(options.provider)
2074
+ })
2075
+ } : {},
1959
2076
  ...options.toolChoice && options.tools?.length ? { tool_choice: toOpenAIToolChoice(options.toolChoice) } : {},
1960
2077
  ...useStreaming ? { stream_options: { include_usage: true } } : {}
1961
2078
  };
@@ -2464,7 +2581,7 @@ function toCodexToolChoice(choice, tools) {
2464
2581
  function streamOpenAICodex(options) {
2465
2582
  return new StreamResult(runStream3(options), options.signal);
2466
2583
  }
2467
- async function* runStream3(options) {
2584
+ async function* runStream3(options, retriedWithoutReasoning = false) {
2468
2585
  const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
2469
2586
  const url = `${baseUrl}/codex/responses`;
2470
2587
  const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
@@ -2534,6 +2651,20 @@ async function* runStream3(options) {
2534
2651
  const parsed = parseCodexErrorBody(text, response.status);
2535
2652
  const message = parsed.message ?? `Codex API returned HTTP ${response.status}.`;
2536
2653
  const requestId = parsed.requestId ?? readHeader(response.headers, "x-request-id", "openai-request-id", "x-oai-request-id");
2654
+ 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(
2655
+ (msg) => msg.role === "assistant" && Array.isArray(msg.content) && msg.content.some((part) => part.type === "raw" && isEncryptedReasoning(part.data))
2656
+ )) {
2657
+ providerDiag("codex_retry_without_encrypted_reasoning", { status: response.status });
2658
+ const messages = options.messages.map(
2659
+ (msg) => msg.role === "assistant" && Array.isArray(msg.content) ? {
2660
+ ...msg,
2661
+ content: msg.content.filter(
2662
+ (part) => !(part.type === "raw" && isEncryptedReasoning(part.data))
2663
+ )
2664
+ } : msg
2665
+ );
2666
+ return yield* runStream3({ ...options, messages }, true);
2667
+ }
2537
2668
  const usageLimit = codexUsageLimitError(parsed.errorObj, response.status, requestId);
2538
2669
  if (usageLimit) throw usageLimit;
2539
2670
  let hint;
@@ -2913,13 +3044,23 @@ function toCodexInput(messages, options) {
2913
3044
  return { system, input };
2914
3045
  }
2915
3046
  function toCodexTools(tools) {
2916
- return tools.map((tool) => ({
2917
- type: "function",
2918
- name: tool.name,
2919
- description: tool.description,
2920
- parameters: resolveToolSchema(tool),
2921
- strict: null
2922
- }));
3047
+ return tools.map((tool) => {
3048
+ let parameters = resolveToolSchema(tool);
3049
+ let strict = null;
3050
+ try {
3051
+ parameters = makeStrictToolSchema(parameters);
3052
+ strict = true;
3053
+ } catch (error) {
3054
+ if (!(error instanceof UnsupportedStrictSchemaError)) throw error;
3055
+ }
3056
+ return {
3057
+ type: "function",
3058
+ name: tool.name,
3059
+ description: tool.description,
3060
+ parameters,
3061
+ strict
3062
+ };
3063
+ });
2923
3064
  }
2924
3065
  function parseCodexErrorBody(text, statusCode) {
2925
3066
  if (!text) return {};