@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
package/CHANGELOG.md CHANGED
@@ -2,6 +2,34 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [17.0.2] - 2026-07-17
6
+
7
+ ### Fixed
8
+
9
+ - Automatically invalidate and rotate OAuth credentials when an "invalidated oauth token" error occurs.
10
+ - Fixed auth-broker snapshot validation rejecting API keys stored via the `/login` flow, restoring support for gateway/broker setups serving login-sourced keys on custom hosts.
11
+ - Fixed an issue where literal reasoning tags (e.g., `<think>`) inside Markdown code blocks or inline code were incorrectly treated as reasoning boundaries, which corrupted the rendered Markdown.
12
+ - Classified HTTP 402 and "balance exhausted" quota responses as persistent usage limits, enabling automatic rotation of multi-account requests to a sibling credential.
13
+ - Fixed `kimi-code` Anthropic-format requests ignoring custom provider base URLs.
14
+ - Fixed an issue where GPT-5.6 Codex Responses-Lite requests failed with an HTTP 400 error due to invalid `tool_choice` parameters after tools were rewritten, by automatically downgrading forced hosted choices to `tool_choice: "auto"` while preserving explicit tool-use constraints.
15
+ - Fixed Cursor streams prematurely reporting success before late CONNECT or gRPC terminal failures were observed, and resolved issues rejecting transport ends without a `turnEnded` signal.
16
+
17
+ ## [17.0.1] - 2026-07-16
18
+
19
+ ### Fixed
20
+
21
+ - Fixed OpenRouter cost reporting to use the provider's authoritative account charge instead of catalog token-price estimates on both Responses and Chat Completions streams.
22
+ - Fixed OpenAI Responses and Chat Completions requests forwarding unsupported sampling parameters such as `temperature` to o-series and GPT-5+ models, preventing 400 errors for mnemopi memory calls through GitHub Copilot GPT-5.6 Luna. ([#5606](https://github.com/can1357/oh-my-pi/issues/5606))
23
+ - Fixed boolean JSON Schema subschemas (`true`/`false`) in MCP tool inputs triggering `400 INVALID_ARGUMENT` on the Google/Cloud Code Assist (Antigravity) transport by coercing them to their object equivalents (`true` → `{}`, `false` → `{ not: {} }`) before sending ([#5604](https://github.com/can1357/oh-my-pi/issues/5604)).
24
+ - Fixed thinking-enabled Claude requests routed to `google-vertex` sending the `effort-2025-11-24` beta as an `anthropic-beta` HTTP header, which Vertex rawPredict rejects with a 400. The effort beta and the `output_config.effort` field are now gated off the Vertex path the same way `context-management-2025-06-27` already is ([#5614](https://github.com/can1357/oh-my-pi/issues/5614)).
25
+ - Fixed custom and Foundry-routed Anthropic endpoints receiving first-party eager/legacy tool-streaming controls ([#5572](https://github.com/can1357/oh-my-pi/issues/5572)).
26
+ - Parsed Ollama NDJSON response bytes directly instead of decoding and buffering every network chunk as text. ([#5542](https://github.com/can1357/oh-my-pi/issues/5542))
27
+ - Fixed Amazon Bedrock stream error handling for non-`Error` values that `JSON.stringify` cannot serialize ([#5539](https://github.com/can1357/oh-my-pi/issues/5539)).
28
+ - Fixed concurrent provider OAuth refreshes by serializing rotating-token updates across processes, fencing stale writes, and preventing background usage probes from disabling otherwise usable credentials ([#5396](https://github.com/can1357/oh-my-pi/issues/5396)).
29
+ - Fixed OpenAI Codex WebSocket connections ignoring `PI_PROXY`, provider-specific proxy settings, and standard HTTPS/ALL proxy variables ([#5384](https://github.com/can1357/oh-my-pi/issues/5384)).
30
+ - Fixed Anthropic account quota exhaustion (`This request would exceed your account's monthly spend limit`) hanging until the local deadline instead of surfacing the error: the `rate_limit_error` "spend limit" wording is now classified as a persistent usage limit, so it fails fast and rotates to a sibling credential rather than looping in the provider retry backoff. ([#4787](https://github.com/can1357/oh-my-pi/issues/4787))
31
+ - Fixed OpenRouter daily free-model allowance errors (`free-models-per-day`) being treated as transient rate limits, so requests rotate from an exhausted API key to a healthy sibling credential. ([#4832](https://github.com/can1357/oh-my-pi/issues/4832))
32
+
5
33
  ## [17.0.0] - 2026-07-15
6
34
 
7
35
  ### Changed
@@ -29,6 +29,7 @@ export declare const remoteOauthCredentialSchema: import("arktype/internal/varia
29
29
  export declare const apiKeyCredentialSchema: import("arktype/internal/variants/object.ts").ObjectType<{
30
30
  type: "api_key";
31
31
  key: string;
32
+ source?: "login" | undefined;
32
33
  }, {}>;
33
34
  /** Discriminated union accepted on POST /v1/credential (writes). */
34
35
  export declare const writableAuthCredentialSchema: import("arktype/internal/variants/object.ts").ObjectType<{
@@ -46,6 +47,7 @@ export declare const writableAuthCredentialSchema: import("arktype/internal/vari
46
47
  } | {
47
48
  type: "api_key";
48
49
  key: string;
50
+ source?: "login" | undefined;
49
51
  }, {}>;
50
52
  /** Discriminated union returned in snapshots (refresh is sentinel for OAuth). */
51
53
  export declare const snapshotCredentialSchema: import("arktype/internal/variants/object.ts").ObjectType<{
@@ -63,6 +65,7 @@ export declare const snapshotCredentialSchema: import("arktype/internal/variants
63
65
  } | {
64
66
  type: "api_key";
65
67
  key: string;
68
+ source?: "login" | undefined;
66
69
  }, {}>;
67
70
  export declare const credentialSnapshotEntrySchema: import("arktype/internal/variants/object.ts").ObjectType<{
68
71
  id: number;
@@ -82,6 +85,7 @@ export declare const credentialSnapshotEntrySchema: import("arktype/internal/var
82
85
  } | {
83
86
  type: "api_key";
84
87
  key: string;
88
+ source?: "login" | undefined;
85
89
  };
86
90
  identityKey: string | null;
87
91
  }, {}>;
@@ -109,6 +113,7 @@ export declare const snapshotEntrySchema: import("arktype/internal/variants/obje
109
113
  } | {
110
114
  type: "api_key";
111
115
  key: string;
116
+ source?: "login" | undefined;
112
117
  };
113
118
  identityKey: string | null;
114
119
  rotatesInMs: number | null;
@@ -153,6 +158,7 @@ export declare const snapshotResponseSchema: import("arktype/internal/variants/o
153
158
  } | {
154
159
  type: "api_key";
155
160
  key: string;
161
+ source?: "login" | undefined;
156
162
  };
157
163
  identityKey: string | null;
158
164
  rotatesInMs: number | null;
@@ -193,6 +199,7 @@ export declare const snapshotStreamSnapshotEventSchema: import("arktype/internal
193
199
  } | {
194
200
  type: "api_key";
195
201
  key: string;
202
+ source?: "login" | undefined;
196
203
  };
197
204
  identityKey: string | null;
198
205
  rotatesInMs: number | null;
@@ -234,6 +241,7 @@ export declare const snapshotStreamEntryEventSchema: import("arktype/internal/va
234
241
  } | {
235
242
  type: "api_key";
236
243
  key: string;
244
+ source?: "login" | undefined;
237
245
  };
238
246
  identityKey: string | null;
239
247
  rotatesInMs: number | null;
@@ -287,6 +295,7 @@ export declare const snapshotStreamEventSchema: import("arktype/internal/variant
287
295
  } | {
288
296
  type: "api_key";
289
297
  key: string;
298
+ source?: "login" | undefined;
290
299
  };
291
300
  identityKey: string | null;
292
301
  rotatesInMs: number | null;
@@ -326,6 +335,7 @@ export declare const snapshotStreamEventSchema: import("arktype/internal/variant
326
335
  } | {
327
336
  type: "api_key";
328
337
  key: string;
338
+ source?: "login" | undefined;
329
339
  };
330
340
  identityKey: string | null;
331
341
  rotatesInMs: number | null;
@@ -427,6 +437,7 @@ export declare const credentialRefreshResponseSchema: import("arktype/internal/v
427
437
  } | {
428
438
  type: "api_key";
429
439
  key: string;
440
+ source?: "login" | undefined;
430
441
  };
431
442
  identityKey: string | null;
432
443
  };
@@ -469,6 +480,7 @@ export declare const credentialUploadRequestSchema: import("arktype/internal/var
469
480
  } | {
470
481
  type: "api_key";
471
482
  key: string;
483
+ source?: "login" | undefined;
472
484
  };
473
485
  }, {}>;
474
486
  export declare const credentialUploadResponseSchema: import("arktype/internal/variants/object.ts").ObjectType<{
@@ -490,6 +502,7 @@ export declare const credentialUploadResponseSchema: import("arktype/internal/va
490
502
  } | {
491
503
  type: "api_key";
492
504
  key: string;
505
+ source?: "login" | undefined;
493
506
  };
494
507
  identityKey: string | null;
495
508
  }[];
@@ -559,6 +559,8 @@ export interface InvalidateCredentialMatchingOptions {
559
559
  }
560
560
  /** Options for refreshing one stored OAuth row through durable ownership. */
561
561
  export interface StoredOAuthRefreshOptions<T extends OAuthCredential = OAuthCredential> {
562
+ /** Stable row id when a provider has multiple OAuth credentials. */
563
+ credentialId?: number;
562
564
  observedCredential?: T;
563
565
  credentialFromRow: (credential: OAuthCredential) => T | undefined;
564
566
  forceRefresh?: boolean;
@@ -5,6 +5,8 @@
5
5
  * `@oh-my-pi/pi-ai` entrypoint name used by the coding agent and auth-broker.
6
6
  */
7
7
  export declare function isDefinitiveOAuthFailure(errorMsg: string): boolean;
8
+ /** Whether an upstream response explicitly says the supplied OAuth bearer was invalidated. */
9
+ export declare function isInvalidatedOAuthTokenError(error: unknown): boolean;
8
10
  /**
9
11
  * Whether an upstream failure should rotate to a sibling credential: a hard
10
12
  * `401`, a body-classified usage limit (Codex `usage_limit_reached`, Anthropic
@@ -20,6 +20,9 @@ export declare function calculateRateLimitBackoffMs(reason: RateLimitReason): nu
20
20
  /**
21
21
  * HTTP status codes that, absent richer body classification, represent an
22
22
  * account-local usage cap rather than a bad credential or a transient blip.
23
+ * HTTP 402 Payment Required is categorically an account-billing cap (xAI
24
+ * Grok Build "usage balance exhausted", DeepSeek "Insufficient Balance",
25
+ * OpenRouter credit exhaustion) — never a transient blip or bad credential.
23
26
  * Always combine with {@link isUsageLimitOutcome} when a message is available
24
27
  * — a 429 carrying transient rate-limit wording is NOT a usage cap.
25
28
  */
@@ -31,7 +34,7 @@ export declare function isUsageLimitStatus(status: number | undefined): boolean;
31
34
  * 1. Body matches {@link isUsageLimitError} (Codex `usage_limit_reached`,
32
35
  * Anthropic account rate-limit, Google `resource_exhausted`, OpenAI
33
36
  * `insufficient_quota`, …) → rotate.
34
- * 2. Status is not 429 → backoff (caller's domain).
37
+ * 2. Status is not a usage-limit status (429/402) → backoff (caller's domain).
35
38
  * 3. Body is absent or {@link isOpaqueStatusBody opaque} (just the status,
36
39
  * empty JSON, HTTP framing only) → rotate conservatively: the server
37
40
  * gave us nothing else to go on.
@@ -44,10 +47,11 @@ export declare function isUsageLimitStatus(status: number | undefined): boolean;
44
47
  */
45
48
  export declare function isUsageLimitOutcome(status: number | undefined, message: string | undefined): boolean;
46
49
  /**
47
- * A 429 body is opaque when it carries no signal beyond the status itself —
48
- * empty, whitespace-only, the status digits with HTTP/JSON framing, or
49
- * generic punctuation. Anything else (retry hints, capacity wording, error
50
- * descriptions) is informative enough to defer to the classifier.
50
+ * A usage-limit status body is opaque when it carries no signal beyond the
51
+ * status itself — empty, whitespace-only, the status digits with HTTP/JSON
52
+ * framing, or generic punctuation. Anything else (retry hints, capacity
53
+ * wording, error descriptions) is informative enough to defer to the
54
+ * classifier.
51
55
  */
52
56
  export declare function isOpaqueStatusBody(message: string): boolean;
53
57
  /**
@@ -29,6 +29,8 @@ export interface BlockState {
29
29
  [kStreamingBlockIndex]: number;
30
30
  }) | null;
31
31
  currentToolCall: ToolCallState | null;
32
+ /** MCP call IDs executed through Cursor's exec channel before their stream block arrives. */
33
+ resolvedMcpToolCallIds: Set<string>;
32
34
  firstTokenTime: number | undefined;
33
35
  setTextBlock: (b: (TextContent & {
34
36
  [kStreamingBlockIndex]: number;
@@ -77,6 +77,7 @@ export declare function resolveCodexResponsesLite(model: Model<"openai-codex-res
77
77
  export interface CodexLiteShapedBody {
78
78
  instructions?: unknown;
79
79
  tools?: unknown;
80
+ tool_choice?: unknown;
80
81
  input?: unknown;
81
82
  parallel_tool_calls?: unknown;
82
83
  }
@@ -85,9 +86,14 @@ export interface CodexLiteShapedBody {
85
86
  * `build_responses_request` with `use_responses_lite`): strips pinned image
86
87
  * detail, forces parallel tool calling off, moves tools into a leading
87
88
  * `additional_tools` developer item and the base instructions into a
88
- * developer message, then omits top-level `instructions`/`tools`. Shared by
89
- * normal turns and both remote-compaction paths — codex-rs routes
90
- * `/responses/compact` through the same builder.
89
+ * developer message, then omits top-level `instructions`/`tools`. Because the
90
+ * rewrite removes top-level `tools`, a forced hosted-tool choice (e.g.
91
+ * `{ type: "web_search" }`) would leave the backend unable to validate the
92
+ * choice against a tools collection and it rejects the request with HTTP 400
93
+ * (#5771). Such choices must fall back to `"auto"`; explicit string constraints
94
+ * such as `"none"` and `"required"` remain valid. Shared by normal turns and
95
+ * both remote-compaction paths — codex-rs routes `/responses/compact` through
96
+ * the same builder.
91
97
  */
92
98
  export declare function applyCodexResponsesLiteShape(body: CodexLiteShapedBody): void;
93
99
  export declare function transformRequestBody(body: RequestBody, model: Model<"openai-codex-responses">, options?: CodexRequestOptions, prompt?: {
@@ -68,6 +68,8 @@ export declare function applyOpenAIServiceTier(params: {
68
68
  * proxy can never skew those costs.
69
69
  */
70
70
  export declare function applyOpenAIResponsesServiceTierCost(model: Pick<Model, "provider">, usage: AssistantMessage["usage"], responseServiceTier: unknown, requestServiceTier: ServiceTier | null | undefined): void;
71
+ /** Reconcile token-price estimates with OpenRouter's authoritative account charge. */
72
+ export declare function applyOpenRouterReportedCost(model: Pick<Model, "provider">, usage: Usage, rawUsage: unknown): void;
71
73
  export interface OpenAIUsageAccountingInput {
72
74
  promptTokens: number;
73
75
  outputTokens: number;
@@ -295,12 +297,14 @@ export declare function applyChatCompletionsReasoningParams(params: OpenAIComple
295
297
  }) | undefined): void;
296
298
  export declare function disableChatCompletionsReasoningForDialect(params: OpenAICompletionsParams, compat: ResolvedOpenAICompat): void;
297
299
  /**
298
- * Output-token clamp for the Z.AI/GLM-5.2 reasoning dialect: these hosts accept
299
- * the full model window on reasoning turns, so clamp to the model cap. Returns
300
- * `undefined` for every other model, leaving {@link resolveOpenAIOutputTokenParam}
301
- * on its default `OPENAI_MAX_OUTPUT_TOKENS` clamp.
300
+ * Provider-specific Chat Completions output clamp.
301
+ *
302
+ * Most OpenAI-compatible endpoints retain the conservative 64k ceiling from
303
+ * {@link resolveOpenAIOutputTokenParam}. Z.AI/GLM-5.2 reasoning and native
304
+ * Moonshot K3 explicitly accept their full advertised model caps, so those
305
+ * routes clamp to `model.maxTokens` instead.
302
306
  */
303
- export declare function resolveZaiReasoningOutputClamp(model: Model<"openai-completions">, compat: ResolvedOpenAICompat): number | undefined;
307
+ export declare function resolveOpenAICompletionsOutputClamp(model: Model<"openai-completions">, compat: ResolvedOpenAICompat): number | undefined;
304
308
  /**
305
309
  * Enable `tool_stream` for Z.AI/GLM-5.2 reasoning models when tools are present
306
310
  * (GLM-5.2 streams tool-call arguments incrementally and needs the flag to do so).
@@ -500,7 +504,9 @@ type CommonSamplingOptions = Pick<StreamOptions, "temperature" | "topP" | "topK"
500
504
  * can let the upstream apply its own default instead of 400-ing on `maxTokens` values that
501
505
  * reflect the model's context window rather than the upstream output limit.
502
506
  */
503
- export declare function applyCommonResponsesSamplingParams<P extends CommonResponsesParams>(params: P, options: CommonSamplingOptions | undefined, model: Pick<Model, "provider" | "api" | "id" | "omitMaxOutputTokens" | "maxTokens">): void;
507
+ export declare function applyCommonResponsesSamplingParams<P extends CommonResponsesParams>(params: P, options: CommonSamplingOptions | undefined, model: Pick<Model, "provider" | "api" | "id" | "omitMaxOutputTokens" | "maxTokens"> & {
508
+ compat: Pick<ResolvedOpenAISharedCompat, "supportsSamplingParams">;
509
+ }): void;
504
510
  type ReasoningOptions = {
505
511
  reasoning?: string;
506
512
  reasoningSummary?: "auto" | "detailed" | "concise" | null;
@@ -1,11 +1,7 @@
1
1
  import type { Api, AssistantMessage, Message, Model } from "../types.js";
2
- /**
3
- * Normalize tool call ID for cross-provider compatibility.
4
- * OpenAI Responses API generates IDs that are 450+ chars with special characters like `|`.
5
- * Anthropic APIs require IDs matching ^[a-zA-Z0-9_-]+$ (max 64 chars).
6
- *
7
- * For aborted/errored turns, this function:
8
- * - Preserves tool call structure (unlike converting to text summaries)
9
- * - Injects synthetic "aborted" tool results
10
- */
2
+ export declare function redactSensitiveCredentials(text: string): string;
3
+ export declare function redactSensitiveInObject(val: unknown): {
4
+ result: unknown;
5
+ changed: boolean;
6
+ };
11
7
  export declare function transformMessages<TApi extends Api>(messages: Message[], model: Model<TApi>, normalizeToolCallId?: (id: string, model: Model<TApi>, source: AssistantMessage) => string, maxNormalizedToolCallIdLength?: number, duplicateToolCallIdSuffixPrefix?: string, targetCompat?: Model<TApi>["compat"]): Message[];
@@ -525,7 +525,7 @@ export interface ContextSnapshot {
525
525
  }
526
526
  export interface AssistantMessage {
527
527
  role: "assistant";
528
- content: (TextContent | ThinkingContent | RedactedThinkingContent | AnthropicFallbackContent | ToolCall)[];
528
+ content: (TextContent | ThinkingContent | RedactedThinkingContent | AnthropicFallbackContent | ImageContent | ToolCall)[];
529
529
  api: Api;
530
530
  provider: Provider;
531
531
  model: string;
@@ -716,6 +716,11 @@ export type AssistantMessageEvent = {
716
716
  contentIndex: number;
717
717
  content: string;
718
718
  partial: AssistantMessage;
719
+ } | {
720
+ type: "image_end";
721
+ contentIndex: number;
722
+ content: ImageContent;
723
+ partial: AssistantMessage;
719
724
  } | {
720
725
  type: "toolcall_start";
721
726
  contentIndex: number;
@@ -3,9 +3,9 @@ import { AssistantMessageEventStream } from "./event-stream.js";
3
3
  export declare const MAX_EMPTY_COMPLETION_RETRIES = 2;
4
4
  export declare const EMPTY_COMPLETION_BASE_DELAY_MS = 500;
5
5
  /**
6
- * Whether a completed assistant message carries content worth delivering: a tool
7
- * call or any non-whitespace text. An empty/whitespace-only message — or one
8
- * that only ever produced thinking — is the "empty response" failure.
6
+ * Whether a completed assistant message carries content worth delivering: an
7
+ * image, tool call, or any non-whitespace text. An empty/whitespace-only message
8
+ * — or one that only ever produced thinking — is the "empty response" failure.
9
9
  */
10
10
  export declare function hasVisibleAssistantContent(message: AssistantMessage): boolean;
11
11
  interface EmptyCompletionRetryOptions {
@@ -1,7 +1,9 @@
1
1
  import { type DescriptionSpillFormat } from "./spill.js";
2
2
  import { type JsonObject } from "./types.js";
3
- export type ResidualSchemaIncompatibility = "type-array" | "type-null" | "nullable" | "combiners";
3
+ export type ResidualSchemaIncompatibility = "type-array" | "type-null" | "nullable" | "combiners" | "not";
4
4
  export interface NormalizeSchemaOptions {
5
+ /** Coerce JSON Schema boolean subschemas for providers whose wire cannot encode them. */
6
+ coerceBooleanSubschemas?: boolean;
5
7
  unsupportedFields: (key: string) => boolean;
6
8
  normalizeFieldNames: boolean;
7
9
  collapseNullFields: boolean;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-ai",
4
- "version": "17.0.0",
4
+ "version": "17.0.2",
5
5
  "description": "Unified LLM API with automatic model discovery and provider configuration",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -38,9 +38,9 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@bufbuild/protobuf": "^2.12.1",
41
- "@oh-my-pi/pi-catalog": "17.0.0",
42
- "@oh-my-pi/pi-utils": "17.0.0",
43
- "@oh-my-pi/pi-wire": "17.0.0",
41
+ "@oh-my-pi/pi-catalog": "17.0.2",
42
+ "@oh-my-pi/pi-utils": "17.0.2",
43
+ "@oh-my-pi/pi-wire": "17.0.2",
44
44
  "arktype": "2.2.3",
45
45
  "zod": "^4"
46
46
  },
@@ -55,6 +55,7 @@ export const apiKeyCredentialSchema = type({
55
55
  "+": "reject",
56
56
  type: "'api_key'",
57
57
  key: type("string").atLeastLength(1),
58
+ "source?": "'login'",
58
59
  });
59
60
 
60
61
  /** Discriminated union accepted on POST /v1/credential (writes). */
package/src/auth-retry.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { OAuthAccess } from "./auth-storage";
2
2
  import * as AIError from "./error";
3
- import { isAuthRetryableError } from "./error/auth-classify";
3
+ import { isAuthRetryableError, isInvalidatedOAuthTokenError } from "./error/auth-classify";
4
4
  import { isUsageLimit } from "./error/flags";
5
5
  import { isUsageLimitOutcome } from "./error/rate-limit";
6
6
 
@@ -90,7 +90,7 @@ export const AUTH_RETRY_STEPS: readonly boolean[] = [false, true];
90
90
  export const AUTH_RETRY_MAX_ATTEMPTS = 64;
91
91
 
92
92
  function isDirectCredentialRotationError(error: unknown): boolean {
93
- if (isUsageLimit(error)) return true;
93
+ if (isUsageLimit(error) || isInvalidatedOAuthTokenError(error)) return true;
94
94
  const status = AIError.status(error);
95
95
  const message = error instanceof Error ? error.message : typeof error === "string" ? error : undefined;
96
96
  return isUsageLimitOutcome(status, message);