@oh-my-pi/pi-ai 18.2.8 → 18.2.9

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 (45) hide show
  1. package/CHANGELOG.md +33 -18
  2. package/dist/types/auth-gateway/types.d.ts +5 -0
  3. package/dist/types/auth-storage.d.ts +35 -32
  4. package/dist/types/index.d.ts +1 -0
  5. package/dist/types/providers/amazon-bedrock.d.ts +7 -0
  6. package/dist/types/providers/claude-code-fingerprint.d.ts +22 -3
  7. package/dist/types/providers/google-gemini-cli.d.ts +0 -2
  8. package/dist/types/providers/openai-chat-server-schema.d.ts +2 -2
  9. package/dist/types/usage/claude-api.d.ts +22 -0
  10. package/dist/types/usage/claude-reset.d.ts +44 -0
  11. package/dist/types/usage.d.ts +111 -5
  12. package/dist/types/utils/schema/json-schema-validator.d.ts +5 -2
  13. package/dist/types/utils/tool-call-loop-guard.d.ts +1 -1
  14. package/package.json +6 -6
  15. package/src/auth/sqlite-credential-store.ts +44 -1
  16. package/src/auth-broker/remote-store.ts +6 -6
  17. package/src/auth-broker/wire-schemas.ts +14 -0
  18. package/src/auth-gateway/server.ts +4 -0
  19. package/src/auth-gateway/types.ts +5 -0
  20. package/src/auth-storage.ts +263 -140
  21. package/src/error/flags.ts +10 -0
  22. package/src/index.ts +1 -0
  23. package/src/providers/amazon-bedrock.ts +55 -5
  24. package/src/providers/anthropic.ts +45 -11
  25. package/src/providers/aws-credentials.ts +124 -11
  26. package/src/providers/claude-code-fingerprint.ts +55 -3
  27. package/src/providers/gitlab-duo.ts +20 -4
  28. package/src/providers/google-gemini-cli.ts +0 -8
  29. package/src/providers/google-shared.ts +1 -18
  30. package/src/providers/openai-chat-server-schema.ts +1 -1
  31. package/src/providers/openai-chat-server.ts +3 -1
  32. package/src/providers/openai-codex-responses.ts +27 -5
  33. package/src/providers/openai-completions.ts +122 -19
  34. package/src/providers/pi-native-server.ts +1 -0
  35. package/src/registry/oauth/anthropic.ts +2 -3
  36. package/src/stream.ts +13 -4
  37. package/src/usage/alibaba-token-plan.ts +7 -1
  38. package/src/usage/claude-api.ts +66 -0
  39. package/src/usage/claude-reset.ts +638 -0
  40. package/src/usage/claude.ts +37 -59
  41. package/src/usage/kimi.ts +32 -1
  42. package/src/usage.ts +52 -5
  43. package/src/utils/schema/json-schema-validator.ts +23 -10
  44. package/src/utils/tool-call-loop-guard.ts +2 -2
  45. package/src/utils/validation.ts +145 -50
@@ -17,6 +17,7 @@ import * as AIError from "../error";
17
17
  import type { Tool, ToolCall } from "../types";
18
18
  import { upgradeJsonSchemaTo202012 } from "./schema/draft";
19
19
  import {
20
+ getTagSelectedUnionBranch,
20
21
  isJsonSchemaValueValid,
21
22
  type JsonSchemaValidationIssue,
22
23
  validateJsonSchemaValue,
@@ -665,14 +666,17 @@ function deleteAtSegment(node: unknown, segments: string[], depth: number): unkn
665
666
  * small and synchronous so validation does not need to compile legacy schemas
666
667
  * into another schema language.
667
668
  */
668
- function branchMatchesSchema(branch: unknown, value: unknown): boolean {
669
- return isJsonSchemaValueValid(branch, value);
669
+ function branchMatchesSchema(branch: unknown, value: unknown, root: unknown = branch): boolean {
670
+ return isJsonSchemaValueValid(branch, value, root);
670
671
  }
671
672
 
672
673
  function normalizeOptionalNullsForSchema(
673
674
  schema: unknown,
674
675
  value: unknown,
675
676
  isRoot = true,
677
+ root: unknown = schema,
678
+ insideContent = false,
679
+ speculativeUnion = false,
676
680
  ): { value: unknown; changed: boolean } {
677
681
  if (value === null || value === undefined) return { value, changed: false };
678
682
  if (schema === null || typeof schema !== "object") return { value, changed: false };
@@ -683,22 +687,64 @@ function normalizeOptionalNullsForSchema(
683
687
  const branches = schemaObject[keyword];
684
688
  if (!Array.isArray(branches)) return { value, changed: false };
685
689
 
686
- let changedCandidate: { value: unknown; changed: true } | null = null;
687
-
690
+ // Prefer an already matching branch before trying repairs against other
691
+ // alternatives. Normalize each match independently: a closed sibling can
692
+ // delete nullable keys and invalidate itself while a later matching branch
693
+ // still requires that data.
688
694
  for (const branch of branches) {
689
- const normalized = normalizeOptionalNullsForSchema(branch, value, isRoot);
690
- if (!normalized.changed) continue;
691
-
692
- if (branchMatchesSchema(branch, normalized.value)) {
695
+ if (!branchMatchesSchema(branch, value, root)) continue;
696
+ const normalized = normalizeOptionalNullsForSchema(
697
+ branch,
698
+ value,
699
+ isRoot,
700
+ root,
701
+ insideContent,
702
+ speculativeUnion,
703
+ );
704
+ if (
705
+ branchMatchesSchema(branch, normalized.value, root) &&
706
+ branchMatchesSchema(schemaObject, normalized.value, root)
707
+ ) {
693
708
  return normalized;
694
709
  }
710
+ }
695
711
 
696
- if (!changedCandidate) {
697
- changedCandidate = { value: normalized.value, changed: true };
712
+ const selectedBranch = getTagSelectedUnionBranch(branches, value);
713
+ for (const branch of branches) {
714
+ // Only unresolved ancestors restrict descendants. A failed branch
715
+ // with a unique discriminator is still authoritative.
716
+ const normalized = normalizeOptionalNullsForSchema(
717
+ branch,
718
+ value,
719
+ isRoot,
720
+ root,
721
+ insideContent,
722
+ speculativeUnion || selectedBranch !== branch,
723
+ );
724
+ if (!normalized.changed) continue;
725
+
726
+ const branchContext: ValidationContext = {
727
+ kind: "json",
728
+ json: branch,
729
+ root,
730
+ union: schemaObject,
731
+ unionBranches: branches,
732
+ speculativeUnion,
733
+ };
734
+ const result = validateContext(branchContext, normalized.value);
735
+ if (result.success && branchMatchesSchema(branch, normalized.value, root)) return normalized;
736
+
737
+ // Diagnose against the enclosing union to retain authoritative vs.
738
+ // speculative issue provenance, but normalize only this candidate's
739
+ // branch. Failed candidates never escape to a sibling.
740
+ const repaired = runCoercionPasses(branchContext, normalized.value, result, isRoot, insideContent);
741
+ if (repaired.result.success && branchMatchesSchema(branch, repaired.args, root)) {
742
+ return { value: repaired.args, changed: true };
698
743
  }
699
744
  }
700
745
 
701
- return changedCandidate ?? { value, changed: false };
746
+ // Never adopt a destructive repair from a branch that still rejects it.
747
+ return { value, changed: false };
702
748
  };
703
749
 
704
750
  const anyOfNormalization = normalizeAnyOfLike("anyOf");
@@ -711,7 +757,14 @@ function normalizeOptionalNullsForSchema(
711
757
  let changed = false;
712
758
  let nextValue: unknown = value;
713
759
  for (const branch of schemaObject.allOf) {
714
- const normalized = normalizeOptionalNullsForSchema(branch, nextValue, isRoot);
760
+ const normalized = normalizeOptionalNullsForSchema(
761
+ branch,
762
+ nextValue,
763
+ isRoot,
764
+ root,
765
+ insideContent,
766
+ speculativeUnion,
767
+ );
715
768
  if (!normalized.changed) continue;
716
769
  nextValue = normalized.value;
717
770
  changed = true;
@@ -728,7 +781,14 @@ function normalizeOptionalNullsForSchema(
728
781
  let changed = false;
729
782
  let nextValue = value;
730
783
  for (let i = 0; i < value.length; i += 1) {
731
- const normalized = normalizeOptionalNullsForSchema(itemSchema, value[i], false);
784
+ const normalized = normalizeOptionalNullsForSchema(
785
+ itemSchema,
786
+ value[i],
787
+ false,
788
+ root,
789
+ insideContent,
790
+ speculativeUnion,
791
+ );
732
792
  if (!normalized.changed) continue;
733
793
  if (!changed) {
734
794
  nextValue = [...value];
@@ -764,7 +824,7 @@ function normalizeOptionalNullsForSchema(
764
824
  const currentValue = nextValue[key];
765
825
  const isNullish = currentValue === null || currentValue === "null";
766
826
  const isInvalidEmptyString =
767
- currentValue === "" && !required.has(key) && !branchMatchesSchema(propertySchema, currentValue);
827
+ currentValue === "" && !required.has(key) && !branchMatchesSchema(propertySchema, currentValue, root);
768
828
 
769
829
  // Strip null/string "null" from optional fields, and strip empty
770
830
  // strings only when the property schema would reject the explicit value.
@@ -794,7 +854,14 @@ function normalizeOptionalNullsForSchema(
794
854
  continue;
795
855
  }
796
856
  }
797
- const normalized = normalizeOptionalNullsForSchema(propertySchema, currentValue, false);
857
+ const normalized = normalizeOptionalNullsForSchema(
858
+ propertySchema,
859
+ currentValue,
860
+ false,
861
+ root,
862
+ insideContent || CONTENT_CARRYING_KEYS.has(key),
863
+ speculativeUnion,
864
+ );
798
865
  if (!normalized.changed) continue;
799
866
 
800
867
  if (!changed) {
@@ -813,7 +880,9 @@ function normalizeOptionalNullsForSchema(
813
880
  //
814
881
  // At the root level unknown null-valued keys stay intact; the
815
882
  // post-validation `preserveUnknownRootFields` pass re-attaches root extras.
816
- if (!isRoot && schemaObject.additionalProperties === false) {
883
+ // They also stay intact while guessing a union branch because another
884
+ // candidate may require the nullable data.
885
+ if (!isRoot && !speculativeUnion && schemaObject.additionalProperties === false) {
817
886
  const knownKeys = new Set(Object.keys(properties));
818
887
  for (const key of Object.keys(nextValue)) {
819
888
  if (knownKeys.has(key)) continue;
@@ -864,26 +933,15 @@ function normalizeEnumStringWhitespace(
864
933
  return normalizeEnumStringWhitespace(resolved, value, root, new Set([...refs, ref]));
865
934
  }
866
935
 
867
- const branchMatches = (branch: unknown, candidate: unknown): boolean => {
868
- if (branch !== null && typeof branch === "object") {
869
- const branchRef = (branch as Record<string, unknown>).$ref;
870
- if (typeof branchRef === "string" && !refs.has(branchRef)) {
871
- const resolved = resolveLocalJsonSchemaRef(root, branchRef);
872
- if (resolved !== undefined) return branchMatchesSchema(resolved, candidate);
873
- }
874
- }
875
- return branchMatchesSchema(branch, candidate);
876
- };
877
-
878
936
  const normalizeAnyOfLike = (keyword: "anyOf" | "oneOf"): { value: unknown; changed: boolean } => {
879
937
  const branches = schemaObject[keyword];
880
938
  if (!Array.isArray(branches)) return { value, changed: false };
881
- if (branches.some(branch => branchMatches(branch, value))) return { value, changed: false };
939
+ if (branches.some(branch => branchMatchesSchema(branch, value, root))) return { value, changed: false };
882
940
 
883
941
  for (const branch of branches) {
884
942
  const normalized = normalizeEnumStringWhitespace(branch, value, root, refs);
885
943
  if (!normalized.changed) continue;
886
- if (branchMatches(branch, normalized.value)) return normalized;
944
+ if (branchMatchesSchema(branch, normalized.value, root)) return normalized;
887
945
  }
888
946
  return { value, changed: false };
889
947
  };
@@ -1046,7 +1104,11 @@ function trimIdentifierStringLeaf(input: unknown): unknown {
1046
1104
  * key matches {@link IDENTIFIER_STRING_KEYS}. Runs by property name only so it
1047
1105
  * fires uniformly across ArkType and plain JSON Schema tools.
1048
1106
  */
1049
- function normalizeIdentifierStringWhitespace(value: unknown): { value: unknown; changed: boolean } {
1107
+ function normalizeIdentifierStringWhitespace(
1108
+ value: unknown,
1109
+ insideContent = false,
1110
+ ): { value: unknown; changed: boolean } {
1111
+ if (insideContent) return { value, changed: false };
1050
1112
  if (Array.isArray(value)) {
1051
1113
  let changed = false;
1052
1114
  let next = value;
@@ -1650,6 +1712,10 @@ type ValidationContext =
1650
1712
  | {
1651
1713
  kind: "json";
1652
1714
  json: Record<string, unknown>;
1715
+ root?: unknown;
1716
+ union?: Record<string, unknown>;
1717
+ unionBranches?: readonly unknown[];
1718
+ speculativeUnion?: boolean;
1653
1719
  };
1654
1720
 
1655
1721
  /**
@@ -1680,9 +1746,12 @@ function preserveUnknownRootFields(input: unknown, parsed: unknown): unknown {
1680
1746
  return { ...input, ...parsed };
1681
1747
  }
1682
1748
 
1683
- function flattenJsonSchemaIssues(issues: ReadonlyArray<JsonSchemaValidationIssue>): FlatIssue[] {
1749
+ function flattenJsonSchemaIssues(
1750
+ issues: ReadonlyArray<JsonSchemaValidationIssue>,
1751
+ speculativeUnion = false,
1752
+ ): FlatIssue[] {
1684
1753
  return issues.map(issue => {
1685
- const unionBranch = issue.fromUnionBranch === true;
1754
+ const unionBranch = speculativeUnion || issue.fromUnionBranch === true;
1686
1755
  if (issue.keyword === "additionalProperties") {
1687
1756
  return {
1688
1757
  keyword: "unrecognized",
@@ -1723,11 +1792,11 @@ function validateContext(ctx: ValidationContext, value: unknown): ContextValidat
1723
1792
  };
1724
1793
  }
1725
1794
 
1726
- const result = validateJsonSchemaValue(ctx.json, value);
1795
+ const result = validateJsonSchemaValue(ctx.union ?? ctx.json, value, ctx.root);
1727
1796
  if (result.success) return { success: true, value };
1728
1797
  return {
1729
1798
  success: false,
1730
- flatIssues: flattenJsonSchemaIssues(result.issues),
1799
+ flatIssues: flattenJsonSchemaIssues(result.issues, ctx.speculativeUnion),
1731
1800
  messages: result.issues.map(issue => ` - ${formatIssuePath(issue.path)}: ${issue.message}`),
1732
1801
  };
1733
1802
  }
@@ -2065,27 +2134,28 @@ export function validateToolArguments(tool: Tool, toolCall: ToolCall): ToolCall[
2065
2134
  }
2066
2135
 
2067
2136
  /**
2068
- * Runs up to {@link MAX_COERCION_PASSES} issue-driven coercion rounds,
2069
- * re-applying the schema normalizations after each round because a coercion
2070
- * may unwrap JSON-string containers and expose fields the pre-validation
2071
- * passes could not reach.
2137
+ * Runs up to {@link MAX_COERCION_PASSES} repair rounds, re-applying the schema
2138
+ * normalizations even when issue-driven coercion cannot change the value.
2139
+ * Later passes (such as identifier trimming) can make an earlier branch-local
2140
+ * repair viable. Stop once neither coercion nor normalization makes progress.
2072
2141
  */
2073
2142
  function runCoercionPasses(
2074
2143
  ctx: ValidationContext,
2075
2144
  args: unknown,
2076
2145
  initial: ContextValidationResult,
2146
+ isRoot = true,
2147
+ insideContent = false,
2077
2148
  ): { args: unknown; result: ContextValidationResult; changed: boolean } {
2078
2149
  const { json } = ctx;
2150
+ const root = ctx.kind === "json" ? (ctx.root ?? json) : json;
2079
2151
  let normalizedArgs = args;
2080
2152
  let result = initial;
2081
2153
  let changed = false;
2082
2154
  for (let pass = 0; pass < MAX_COERCION_PASSES; pass += 1) {
2083
2155
  if (result.success) break;
2084
2156
  const coercion = coerceArgsFromIssues(normalizedArgs, result.flatIssues);
2085
- if (!coercion.changed) break;
2086
-
2157
+ let passChanged = coercion.changed;
2087
2158
  normalizedArgs = coercion.value;
2088
- changed = true;
2089
2159
 
2090
2160
  // `coerceArgsFromIssues` may have just parsed a JSON-string container at
2091
2161
  // the root or a nested field, exposing double-encoded keys the initial
@@ -2094,21 +2164,36 @@ function runCoercionPasses(
2094
2164
  const keyNormalizationPass = normalizeDoubleEncodedKeys(normalizedArgs);
2095
2165
  if (keyNormalizationPass.changed) {
2096
2166
  normalizedArgs = keyNormalizationPass.value;
2097
- }
2098
-
2099
- const nullNormalization = normalizeOptionalNullsForSchema(json, normalizedArgs);
2167
+ passChanged = true;
2168
+ }
2169
+
2170
+ const speculativeUnion =
2171
+ ctx.kind === "json" &&
2172
+ (ctx.speculativeUnion === true ||
2173
+ (ctx.unionBranches !== undefined && getTagSelectedUnionBranch(ctx.unionBranches, normalizedArgs) !== json));
2174
+ const nullNormalization = normalizeOptionalNullsForSchema(
2175
+ json,
2176
+ normalizedArgs,
2177
+ isRoot,
2178
+ root,
2179
+ insideContent,
2180
+ speculativeUnion,
2181
+ );
2100
2182
  if (nullNormalization.changed) {
2101
2183
  normalizedArgs = nullNormalization.value;
2184
+ passChanged = true;
2102
2185
  }
2103
2186
 
2104
- const enumStringNormalizationPass = normalizeEnumStringWhitespace(json, normalizedArgs);
2187
+ const enumStringNormalizationPass = normalizeEnumStringWhitespace(json, normalizedArgs, root);
2105
2188
  if (enumStringNormalizationPass.changed) {
2106
2189
  normalizedArgs = enumStringNormalizationPass.value;
2190
+ passChanged = true;
2107
2191
  }
2108
2192
 
2109
- const identifierStringNormalizationPass = normalizeIdentifierStringWhitespace(normalizedArgs);
2193
+ const identifierStringNormalizationPass = normalizeIdentifierStringWhitespace(normalizedArgs, insideContent);
2110
2194
  if (identifierStringNormalizationPass.changed) {
2111
2195
  normalizedArgs = identifierStringNormalizationPass.value;
2196
+ passChanged = true;
2112
2197
  }
2113
2198
 
2114
2199
  // Re-run the union-string coercion because `coerceArgsFromIssues` may
@@ -2118,21 +2203,31 @@ function runCoercionPasses(
2118
2203
  const stringEncodedArrayNormPass = normalizeStringEncodedArrayUnions(json, normalizedArgs);
2119
2204
  if (stringEncodedArrayNormPass.changed) {
2120
2205
  normalizedArgs = stringEncodedArrayNormPass.value;
2206
+ passChanged = true;
2121
2207
  }
2122
2208
 
2123
- const identifierStringNormalizationAfterArrayPass = normalizeIdentifierStringWhitespace(normalizedArgs);
2209
+ const identifierStringNormalizationAfterArrayPass = normalizeIdentifierStringWhitespace(
2210
+ normalizedArgs,
2211
+ insideContent,
2212
+ );
2124
2213
  if (identifierStringNormalizationAfterArrayPass.changed) {
2125
2214
  normalizedArgs = identifierStringNormalizationAfterArrayPass.value;
2215
+ passChanged = true;
2126
2216
  }
2127
2217
 
2128
2218
  // Re-run single-string remap: `coerceArgsFromIssues` may have just
2129
2219
  // unwrapped a JSON-stringified root object, exposing a mislabelled lone
2130
2220
  // string field the initial pre-pass could not see.
2131
- const singleStringNormPass = normalizeSingleStringField(json, normalizedArgs);
2132
- if (singleStringNormPass.changed) {
2133
- normalizedArgs = singleStringNormPass.value;
2221
+ if (isRoot && !(ctx.kind === "json" && ctx.union)) {
2222
+ const singleStringNormPass = normalizeSingleStringField(json, normalizedArgs);
2223
+ if (singleStringNormPass.changed) {
2224
+ normalizedArgs = singleStringNormPass.value;
2225
+ passChanged = true;
2226
+ }
2134
2227
  }
2135
2228
 
2229
+ if (!passChanged) break;
2230
+ changed = true;
2136
2231
  result = validateContext(ctx, normalizedArgs);
2137
2232
  }
2138
2233
  return { args: normalizedArgs, result, changed };