@oh-my-pi/pi-ai 17.0.0 → 17.0.2

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/types/auth-broker/wire-schemas.d.ts +13 -0
  3. package/dist/types/auth-storage.d.ts +2 -0
  4. package/dist/types/error/auth-classify.d.ts +2 -0
  5. package/dist/types/error/rate-limit.d.ts +9 -5
  6. package/dist/types/providers/cursor.d.ts +2 -0
  7. package/dist/types/providers/openai-codex/request-transformer.d.ts +9 -3
  8. package/dist/types/providers/openai-shared.d.ts +12 -6
  9. package/dist/types/providers/transform-messages.d.ts +5 -9
  10. package/dist/types/types.d.ts +6 -1
  11. package/dist/types/utils/empty-completion-retry.d.ts +3 -3
  12. package/dist/types/utils/schema/normalize.d.ts +3 -1
  13. package/package.json +4 -4
  14. package/src/auth-broker/wire-schemas.ts +1 -0
  15. package/src/auth-retry.ts +2 -2
  16. package/src/auth-storage.ts +120 -79
  17. package/src/dialect/owned-stream.ts +11 -0
  18. package/src/dialect/thinking.ts +186 -16
  19. package/src/error/auth-classify.ts +13 -0
  20. package/src/error/flags.ts +2 -2
  21. package/src/error/rate-limit.ts +28 -9
  22. package/src/providers/__tests__/kimi-code-thinking.test.ts +62 -7
  23. package/src/providers/anthropic.ts +82 -25
  24. package/src/providers/cursor.ts +53 -30
  25. package/src/providers/gitlab-duo-workflow.ts +6 -2
  26. package/src/providers/kimi.ts +1 -4
  27. package/src/providers/openai-codex/request-transformer.ts +12 -3
  28. package/src/providers/openai-codex-responses.ts +22 -4
  29. package/src/providers/openai-completions.ts +28 -22
  30. package/src/providers/openai-responses.ts +5 -1
  31. package/src/providers/openai-shared.ts +66 -14
  32. package/src/providers/transform-messages.ts +171 -1
  33. package/src/stream.ts +2 -0
  34. package/src/types.ts +9 -1
  35. package/src/utils/empty-completion-retry.ts +6 -3
  36. package/src/utils/leaked-thinking-stream.ts +19 -1
  37. package/src/utils/proxy.ts +1 -1
  38. package/src/utils/schema/CONSTRAINTS.md +1 -0
  39. package/src/utils/schema/fields.ts +3 -0
  40. package/src/utils/schema/normalize.ts +94 -24
  41. package/src/utils.ts +4 -1
@@ -37,6 +37,9 @@ export const UNSUPPORTED_SCHEMA_FIELDS: Record<string, true> = {
37
37
  multipleOf: true,
38
38
  pattern: true,
39
39
  format: true,
40
+ dependencies: true,
41
+ dependentSchemas: true,
42
+ dependentRequired: true,
40
43
  };
41
44
 
42
45
  /**
@@ -26,9 +26,11 @@ import { enter, epochNext, exit, once, stamp } from "./stamps";
26
26
  import { isJsonObject, isJsonObjectEmpty, type JsonObject } from "./types";
27
27
  import { decontaminateZodInstance } from "./zod-decontaminate";
28
28
 
29
- export type ResidualSchemaIncompatibility = "type-array" | "type-null" | "nullable" | "combiners";
29
+ export type ResidualSchemaIncompatibility = "type-array" | "type-null" | "nullable" | "combiners" | "not";
30
30
 
31
31
  export interface NormalizeSchemaOptions {
32
+ /** Coerce JSON Schema boolean subschemas for providers whose wire cannot encode them. */
33
+ coerceBooleanSubschemas?: boolean;
32
34
  unsupportedFields: (key: string) => boolean;
33
35
  normalizeFieldNames: boolean;
34
36
  collapseNullFields: boolean;
@@ -55,7 +57,14 @@ export interface NormalizeSchemaOptions {
55
57
  }
56
58
 
57
59
  interface NormalizeSchemaWalkOptions extends NormalizeSchemaOptions {
58
- insideProperties: boolean;
60
+ insideSchemaMap: boolean;
61
+ /**
62
+ * True when the value currently being walked occupies a JSON Schema
63
+ * *subschema* slot (root, combiner branch, `items`, a property value, …).
64
+ * Only then is a bare `true`/`false` a boolean subschema to coerce; in a
65
+ * keyword slot (`nullable`, `enum` entries, `additionalProperties`) it stays.
66
+ */
67
+ booleanIsSubschema: boolean;
59
68
  }
60
69
 
61
70
  interface ResidualIncompatibilityChecks {
@@ -63,6 +72,7 @@ interface ResidualIncompatibilityChecks {
63
72
  typeNull: boolean;
64
73
  nullable: boolean;
65
74
  combiners: boolean;
75
+ not: boolean;
66
76
  }
67
77
 
68
78
  const SNAKE_TO_CAMEL_RENAMES = new Map<string, string>([
@@ -75,6 +85,42 @@ const SNAKE_TO_CAMEL_RENAMES = new Map<string, string>([
75
85
  const JSON_SCHEMA_COMBINERS = ["anyOf", "oneOf"] as const;
76
86
  const CCA_FORBIDDEN_COMBINERS = new Set(["anyOf", "oneOf", "allOf"]);
77
87
 
88
+ /**
89
+ * Keywords whose value is a single subschema (draft 2020-12). A bare `true` /
90
+ * `false` in one of these slots is a boolean subschema to coerce (issue #5604).
91
+ */
92
+ const SUBSCHEMA_VALUE_KEYS: Record<string, true> = {
93
+ items: true,
94
+ additionalItems: true,
95
+ unevaluatedItems: true,
96
+ not: true,
97
+ if: true,
98
+ // biome-ignore lint/suspicious/noThenProperty: JSON Schema keyword
99
+ then: true,
100
+ else: true,
101
+ contains: true,
102
+ propertyNames: true,
103
+ contentSchema: true,
104
+ };
105
+
106
+ /** Keywords whose value is an array of subschemas. */
107
+ const SUBSCHEMA_ARRAY_KEYS: Record<string, true> = {
108
+ anyOf: true,
109
+ oneOf: true,
110
+ allOf: true,
111
+ prefixItems: true,
112
+ };
113
+
114
+ /** Keywords whose object value maps arbitrary names to subschemas. */
115
+ const SUBSCHEMA_MAP_KEYS: Record<string, true> = {
116
+ properties: true,
117
+ patternProperties: true,
118
+ dependencies: true,
119
+ dependentSchemas: true,
120
+ $defs: true,
121
+ definitions: true,
122
+ };
123
+
78
124
  const CLOUD_CODE_ASSIST_CLAUDE_FALLBACK_SCHEMA = {
79
125
  type: "object",
80
126
  properties: {},
@@ -236,6 +282,15 @@ function normalizeSchemaNode(value: unknown, options: NormalizeSchemaWalkOptions
236
282
  exit(value);
237
283
  }
238
284
  }
285
+ if (typeof value === "boolean") {
286
+ // A bare boolean is a JSON Schema subschema only in a subschema slot.
287
+ // The Google/CCA protobuf Schema wire has no representation for it
288
+ // (issue #5604): `true` accepts anything -> `{}`, `false` accepts nothing
289
+ // -> `{ not: {} }`. In a keyword slot (`nullable`, `enum` entry, …) a
290
+ // boolean is a plain value and is left untouched.
291
+ if (!options.coerceBooleanSubschemas || !options.booleanIsSubschema) return value;
292
+ return value ? {} : { not: {} };
293
+ }
239
294
  if (!isJsonObject(value)) {
240
295
  return value;
241
296
  }
@@ -250,8 +305,8 @@ function normalizeSchemaNode(value: unknown, options: NormalizeSchemaWalkOptions
250
305
  }
251
306
 
252
307
  function normalizeSchemaObjectNode(value: JsonObject, options: NormalizeSchemaWalkOptions): unknown {
253
- let obj = options.normalizeFieldNames && !options.insideProperties ? applySnakeCaseRenames(value) : value;
254
- if (options.collapseNullFields && !options.insideProperties) {
308
+ let obj = options.normalizeFieldNames && !options.insideSchemaMap ? applySnakeCaseRenames(value) : value;
309
+ if (options.collapseNullFields && !options.insideSchemaMap) {
255
310
  obj = preHandleNullFields(obj);
256
311
  }
257
312
  const result: JsonObject = {};
@@ -298,14 +353,18 @@ function normalizeSchemaObjectNode(value: JsonObject, options: NormalizeSchemaWa
298
353
  for (const key in obj) {
299
354
  if (!Object.hasOwn(obj, key) || key === combiner || outHasOwn(result, key)) continue;
300
355
  const entry = obj[key];
301
- if (!options.insideProperties && options.unsupportedFields(key)) {
356
+ if (!options.insideSchemaMap && options.unsupportedFields(key)) {
302
357
  spill = pushStrippedDescriptionEntry(spill, key, entry, options);
303
358
  continue;
304
359
  }
305
360
  if (options.stripNullableKeyword && key === "nullable") continue;
306
361
  result[key] = normalizeSchemaNode(entry, {
307
362
  ...options,
308
- insideProperties: !options.insideProperties && key === "properties",
363
+ insideSchemaMap: !options.insideSchemaMap && Object.hasOwn(SUBSCHEMA_MAP_KEYS, key),
364
+ booleanIsSubschema:
365
+ options.insideSchemaMap ||
366
+ Object.hasOwn(SUBSCHEMA_VALUE_KEYS, key) ||
367
+ Object.hasOwn(SUBSCHEMA_ARRAY_KEYS, key),
309
368
  });
310
369
  }
311
370
  applyDescriptionSpill(result, spill, options);
@@ -316,7 +375,7 @@ function normalizeSchemaObjectNode(value: JsonObject, options: NormalizeSchemaWa
316
375
  for (const key in obj) {
317
376
  if (!Object.hasOwn(obj, key)) continue;
318
377
  const entry = obj[key];
319
- if (!options.insideProperties && options.unsupportedFields(key)) {
378
+ if (!options.insideSchemaMap && options.unsupportedFields(key)) {
320
379
  spill = pushStrippedDescriptionEntry(spill, key, entry, options);
321
380
  continue;
322
381
  }
@@ -327,7 +386,11 @@ function normalizeSchemaObjectNode(value: JsonObject, options: NormalizeSchemaWa
327
386
  }
328
387
  result[key] = normalizeSchemaNode(entry, {
329
388
  ...options,
330
- insideProperties: !options.insideProperties && key === "properties",
389
+ insideSchemaMap: !options.insideSchemaMap && Object.hasOwn(SUBSCHEMA_MAP_KEYS, key),
390
+ booleanIsSubschema:
391
+ options.insideSchemaMap ||
392
+ Object.hasOwn(SUBSCHEMA_VALUE_KEYS, key) ||
393
+ Object.hasOwn(SUBSCHEMA_ARRAY_KEYS, key),
331
394
  });
332
395
  }
333
396
 
@@ -835,6 +898,7 @@ function createResidualIncompatibilityChecks(
835
898
  typeNull: false,
836
899
  nullable: false,
837
900
  combiners: false,
901
+ not: false,
838
902
  };
839
903
  for (const check of checks) {
840
904
  switch (check) {
@@ -847,6 +911,9 @@ function createResidualIncompatibilityChecks(
847
911
  case "nullable":
848
912
  result.nullable = true;
849
913
  break;
914
+ case "not":
915
+ result.not = true;
916
+ break;
850
917
  case "combiners":
851
918
  result.combiners = true;
852
919
  break;
@@ -859,10 +926,11 @@ function hasResidualSchemaIncompatibilities(
859
926
  value: unknown,
860
927
  checks: ResidualIncompatibilityChecks,
861
928
  epoch: number = epochNext(),
929
+ insideSchemaMap = false,
862
930
  ): boolean {
863
931
  if (Array.isArray(value)) {
864
932
  if (!once(value, epoch)) return false;
865
- return value.some(entry => hasResidualSchemaIncompatibilities(entry, checks, epoch));
933
+ return value.some(entry => hasResidualSchemaIncompatibilities(entry, checks, epoch, insideSchemaMap));
866
934
  }
867
935
  if (!isJsonObject(value)) {
868
936
  return false;
@@ -874,14 +942,22 @@ function hasResidualSchemaIncompatibilities(
874
942
  if (checks.typeArray && Array.isArray(value.type)) return true;
875
943
  if (checks.typeNull && value.type === "null") return true;
876
944
  if (checks.nullable && Object.hasOwn(value, "nullable")) return true;
877
- if (checks.combiners) {
945
+ if (!insideSchemaMap && checks.not && Object.hasOwn(value, "not")) return true;
946
+ if (!insideSchemaMap && checks.combiners) {
878
947
  for (const combiner of CCA_FORBIDDEN_COMBINERS) {
879
948
  if (Array.isArray(value[combiner])) return true;
880
949
  }
881
950
  }
882
951
  for (const k in value) {
883
952
  if (!Object.hasOwn(value, k)) continue;
884
- if (hasResidualSchemaIncompatibilities(value[k], checks, epoch)) {
953
+ if (
954
+ hasResidualSchemaIncompatibilities(
955
+ value[k],
956
+ checks,
957
+ epoch,
958
+ !insideSchemaMap && Object.hasOwn(SUBSCHEMA_MAP_KEYS, k),
959
+ )
960
+ ) {
885
961
  return true;
886
962
  }
887
963
  }
@@ -894,7 +970,8 @@ export function normalizeSchema(value: unknown, options: NormalizeSchemaOptions)
894
970
  const dereferenced = dereferenceJsonSchema(upgraded);
895
971
  let normalized = normalizeSchemaNode(dereferenced, {
896
972
  ...options,
897
- insideProperties: false,
973
+ insideSchemaMap: false,
974
+ booleanIsSubschema: true,
898
975
  });
899
976
  if (options.stripResidualCombinersFixpoint) {
900
977
  normalized = stripResidualCombiners(normalized);
@@ -916,6 +993,7 @@ export function normalizeSchema(value: unknown, options: NormalizeSchemaOptions)
916
993
 
917
994
  export function normalizeSchemaForGoogle(value: unknown): unknown {
918
995
  return normalizeSchema(value, {
996
+ coerceBooleanSubschemas: true,
919
997
  unsupportedFields: isGoogleUnsupportedSchemaField,
920
998
  normalizeFieldNames: true,
921
999
  collapseNullFields: true,
@@ -937,6 +1015,7 @@ export function normalizeSchemaForGoogle(value: unknown): unknown {
937
1015
 
938
1016
  export function normalizeSchemaForCCA(value: unknown): unknown {
939
1017
  return normalizeSchema(value, {
1018
+ coerceBooleanSubschemas: true,
940
1019
  unsupportedFields: isGoogleUnsupportedSchemaField,
941
1020
  normalizeFieldNames: true,
942
1021
  collapseNullFields: false,
@@ -953,7 +1032,7 @@ export function normalizeSchemaForCCA(value: unknown): unknown {
953
1032
  inferTypeForBareEnum: true,
954
1033
  dropNonScalarEnum: false,
955
1034
  foldOneOfIntoAnyOf: false,
956
- rejectResidualIncompatibilities: ["type-array", "type-null", "nullable", "combiners"],
1035
+ rejectResidualIncompatibilities: ["type-array", "type-null", "nullable", "combiners", "not"],
957
1036
  validateAndFallback: { fallback: CLOUD_CODE_ASSIST_CLAUDE_FALLBACK_SCHEMA },
958
1037
  });
959
1038
  }
@@ -1031,15 +1110,6 @@ export function normalizeSchemaForMoonshot(value: unknown): unknown {
1031
1110
  // Ollama — Go schema parser compatibility
1032
1111
  // ---------------------------------------------------------------------------
1033
1112
 
1034
- const OLLAMA_SCHEMA_ARRAY_KEYS = new Set(["anyOf", "oneOf", "allOf", "prefixItems"]);
1035
- const OLLAMA_SCHEMA_MAP_KEYS = new Set([
1036
- "properties",
1037
- "patternProperties",
1038
- "dependencies",
1039
- "dependentSchemas",
1040
- "$defs",
1041
- "definitions",
1042
- ]);
1043
1113
  const OLLAMA_SCHEMA_VALUE_KEYS = new Set([
1044
1114
  "items",
1045
1115
  "additionalItems",
@@ -1121,7 +1191,7 @@ export function sanitizeSchemaForOllama(schema: JsonObject): JsonObject {
1121
1191
  }
1122
1192
 
1123
1193
  let next = child;
1124
- if (OLLAMA_SCHEMA_MAP_KEYS.has(key) && isJsonObject(child)) {
1194
+ if (Object.hasOwn(SUBSCHEMA_MAP_KEYS, key) && isJsonObject(child)) {
1125
1195
  let mapChanged = false;
1126
1196
  const mapOutput: JsonObject = {};
1127
1197
  for (const childKey in child) {
@@ -1132,7 +1202,7 @@ export function sanitizeSchemaForOllama(schema: JsonObject): JsonObject {
1132
1202
  mapOutput[childKey] = normalizedChild;
1133
1203
  }
1134
1204
  next = mapChanged ? mapOutput : child;
1135
- } else if (OLLAMA_SCHEMA_ARRAY_KEYS.has(key) && Array.isArray(child)) {
1205
+ } else if (Object.hasOwn(SUBSCHEMA_ARRAY_KEYS, key) && Array.isArray(child)) {
1136
1206
  let arrayChanged = false;
1137
1207
  const arrayOutput = child.map(item => {
1138
1208
  const normalizedItem = normalizeNode(item);
package/src/utils.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { $env } from "@oh-my-pi/pi-utils";
2
2
  import type { ResponseInput, ResponseInputItem } from "./providers/openai-responses-wire";
3
+ import { redactSensitiveCredentials } from "./providers/transform-messages";
3
4
  import type { CacheRetention, OpenAIResponsesHistoryPayload, ProviderPayload } from "./types";
4
5
 
5
6
  type OpenAIResponsesReplayItem = ResponseInput[number];
@@ -9,7 +10,9 @@ export { isRecord } from "@oh-my-pi/pi-utils";
9
10
  export function normalizeSystemPrompts(systemPrompt: readonly string[] | string | undefined | null): string[] {
10
11
  if (systemPrompt === undefined || systemPrompt === null) return [];
11
12
  const prompts = Array.isArray(systemPrompt) ? systemPrompt : typeof systemPrompt === "string" ? [systemPrompt] : [];
12
- return prompts.map(prompt => prompt.toWellFormed()).filter(prompt => prompt.trim().length > 0);
13
+ return prompts
14
+ .map(prompt => redactSensitiveCredentials(prompt.toWellFormed()))
15
+ .filter(prompt => prompt.trim().length > 0);
13
16
  }
14
17
 
15
18
  export function normalizeToolCallId(id: string): string {