@oh-my-pi/pi-ai 16.0.3 → 16.0.5

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 (49) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/types/auth-broker/wire-schemas.d.ts +14 -0
  3. package/dist/types/auth-retry.d.ts +9 -0
  4. package/dist/types/auth-storage.d.ts +3 -0
  5. package/dist/types/dialect/anthropic.d.ts +7 -1
  6. package/dist/types/dialect/minimax.d.ts +3 -0
  7. package/dist/types/index.d.ts +1 -0
  8. package/dist/types/providers/google-gemini-cli.d.ts +23 -2
  9. package/dist/types/providers/openai-completions.d.ts +2 -0
  10. package/dist/types/registry/alibaba-coding-plan.d.ts +4 -3
  11. package/dist/types/registry/oauth/github-copilot.d.ts +1 -1
  12. package/dist/types/registry/oauth/google-oauth-shared.d.ts +0 -6
  13. package/dist/types/registry/oauth/openai-codex.d.ts +2 -0
  14. package/dist/types/registry/oauth/types.d.ts +1 -0
  15. package/dist/types/registry/registry.d.ts +2 -1
  16. package/dist/types/types.d.ts +8 -0
  17. package/dist/types/usage.d.ts +1 -0
  18. package/dist/types/utils/google-validation.d.ts +2 -0
  19. package/dist/types/utils/thinking-loop.d.ts +45 -0
  20. package/package.json +3 -3
  21. package/src/auth-broker/wire-schemas.ts +1 -0
  22. package/src/auth-retry.ts +19 -0
  23. package/src/auth-storage.ts +16 -12
  24. package/src/dialect/anthropic.ts +19 -8
  25. package/src/dialect/factory.ts +2 -0
  26. package/src/dialect/minimax.md +31 -0
  27. package/src/dialect/minimax.ts +95 -0
  28. package/src/dialect/owned-stream.ts +1 -0
  29. package/src/index.ts +1 -0
  30. package/src/providers/github-copilot-headers.ts +2 -1
  31. package/src/providers/google-gemini-cli.ts +298 -106
  32. package/src/providers/openai-completions.ts +54 -12
  33. package/src/providers/openai-responses-shared.ts +2 -2
  34. package/src/registry/alibaba-coding-plan.ts +55 -10
  35. package/src/registry/github-copilot.ts +1 -1
  36. package/src/registry/litellm.ts +2 -1
  37. package/src/registry/oauth/github-copilot.ts +37 -5
  38. package/src/registry/oauth/google-oauth-shared.ts +9 -1
  39. package/src/registry/oauth/index.ts +5 -1
  40. package/src/registry/oauth/openai-codex.ts +59 -26
  41. package/src/registry/oauth/types.ts +1 -0
  42. package/src/stream.ts +19 -3
  43. package/src/types.ts +9 -0
  44. package/src/usage/gemini.ts +10 -2
  45. package/src/usage/google-antigravity.ts +38 -17
  46. package/src/usage.ts +1 -0
  47. package/src/utils/google-validation.ts +25 -0
  48. package/src/utils/thinking-loop.ts +354 -0
  49. package/src/utils/validation.ts +72 -30
package/CHANGELOG.md CHANGED
@@ -2,6 +2,42 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [16.0.5] - 2026-06-17
6
+
7
+ ### Added
8
+
9
+ - Added `antigravityEndpointMode` stream option with `auto`, `production`, and `sandbox` values to control Antigravity endpoint routing
10
+ - Added `seedApiKeyResolver` for reusing a pre-resolved request key while preserving resolver-driven auth retry and credential rotation
11
+ - Added optional `contextSnapshot` property to `AssistantMessage` with token usage metadata via new `ContextSnapshot` interface (`promptTokens`, `nonMessageTokens`, and optional `lastMessageTimestamp`)
12
+ - Added `LITELLM_BASE_URL` guidance to the LiteLLM login prompt so non-default proxy endpoints are discoverable. ([#2726](https://github.com/can1357/oh-my-pi/issues/2726))
13
+ - Added a Gemini thinking-loop guard that watches streamed `thinking` deltas for degenerate reasoning loops — verbatim tail repetition and near-duplicate paragraph cycling — and terminates the stream with a retryable, empty-content `error` message (worded as a transient stream stall) so the turn is discarded and re-sampled instead of committing a runaway transcript. Gated to Gemini models across every transport (OpenRouter, direct Google, Vertex) and disarmed once visible answer text or a tool call starts; disable with `PI_NO_THINKING_LOOP_GUARD=1`.
14
+
15
+ ### Changed
16
+
17
+ - Changed the Antigravity (`google-antigravity`) request builder to mirror the captured `antigravity/hub` client: gemini-3.x send `thinkingConfig.thinkingBudget` per tier, a fixed per-model `maxOutputTokens`, a default `functionCallingConfig.mode: "VALIDATED"` tool mode (auto/unset tool choice only), a `role: "user"` system instruction, a structured `requestId` (`agent/<id>/<ts>/<trajectoryId>/<step>`), and `labels` (`model_enum`, `trajectory_id`, `last_step_index`, `last_execution_id`, `used_claude*`) tracked across the conversation via provider session state.
18
+
19
+ ### Fixed
20
+
21
+ - Fixed Gemini usage-tier mapping so `gemini-3.5-flash` is treated as `Flash` and `gemini-3.1-pro` plus `gemini-pro-agent` are treated as `Pro` in usage accounting
22
+ - Fixed Antigravity stream state handling so a request’s `last_execution_id` is committed only after a successful completion and cleared between retry attempts
23
+ - Fixed `streamSimple()` Gemini streams to run through the thinking-loop guard for custom API and pi-native transports, so degenerate `thinking` loops now abort with the same retryable empty-content error path as other Gemini stream paths
24
+ - Fixed Antigravity model streaming and usage fetch paths to retry on transient `429`/`5xx` errors by failing over to the alternate endpoint before surfacing an error
25
+ - Fixed Antigravity endpoint tracking to prefer a previously successful endpoint in `auto` mode for subsequent requests
26
+ - Fixed Antigravity and Gemini CLI model requests failing with an opaque error when Google requires account verification. Cloud Code Assist `403 VALIDATION_REQUIRED` responses now surface the `validation_url` and the signed-in account email when available, so users see an actionable account-verification message instead of the raw API error body.
27
+ - Fixed MiniMax M3 in-band tool calls by adding a MiniMax dialect that parses `<minimax:tool_call>` wrappers instead of falling back to generic XML. ([#2759](https://github.com/can1357/oh-my-pi/issues/2759))
28
+ - Fixed GitHub Copilot OAuth for Business seats by storing the login-discovered API endpoint and routing model enablement plus chat requests to that endpoint. ([#2876](https://github.com/can1357/oh-my-pi/issues/2876))
29
+
30
+ ## [16.0.4] - 2026-06-17
31
+
32
+ ### Fixed
33
+
34
+ - Fixed tool argument coercion to parse double-encoded JSON strings, including quoted values like `"300"`, when schema expects a number
35
+ - Fixed object-array coercion to parse JSON object and array strings into proper array arguments instead of wrapping raw strings
36
+ - Fixed handling of malformed JSON container strings for array schema fields so validation now surfaces a top-level `expected array, received string` error rather than nested element errors
37
+ - Fixed ChatGPT/Codex browser login missing connector OAuth scopes and rendering object-shaped token endpoint errors as `[object Object]`. ([#2825](https://github.com/can1357/oh-my-pi/issues/2825))
38
+ - Fixed Zhipu/BigModel GLM-5.2 chat-completions requests so internal `xhigh` effort serializes as provider-native `reasoning_effort: "max"` and tool calls opt into `tool_stream`. ([#2833](https://github.com/can1357/oh-my-pi/issues/2833))
39
+ - Fixed Google Gemini CLI and Antigravity tool calls with `toolChoice: "auto"` serializing an explicit `toolConfig` AUTO mode, which can cause Gemini-3 models to leak raw planning JSON instead of executing tools. ([#2830](https://github.com/can1357/oh-my-pi/issues/2830))
40
+
5
41
  ## [16.0.3] - 2026-06-16
6
42
 
7
43
  ### Added
@@ -15,6 +15,7 @@
15
15
  import { z } from "zod/v4";
16
16
  /** Real OAuth credential (broker-side) — refresh token is the actual upstream value. */
17
17
  export declare const oauthCredentialSchema: z.ZodObject<{
18
+ apiEndpoint: z.ZodOptional<z.ZodString>;
18
19
  type: z.ZodLiteral<"oauth">;
19
20
  refresh: z.ZodString;
20
21
  access: z.ZodString;
@@ -26,6 +27,7 @@ export declare const oauthCredentialSchema: z.ZodObject<{
26
27
  }, z.core.$loose>;
27
28
  /** OAuth credential as it appears in broker snapshots — refresh replaced with sentinel. */
28
29
  export declare const remoteOauthCredentialSchema: z.ZodObject<{
30
+ apiEndpoint: z.ZodOptional<z.ZodString>;
29
31
  type: z.ZodLiteral<"oauth">;
30
32
  access: z.ZodString;
31
33
  expires: z.ZodNumber;
@@ -41,6 +43,7 @@ export declare const apiKeyCredentialSchema: z.ZodObject<{
41
43
  }, z.core.$strict>;
42
44
  /** Discriminated union accepted on POST /v1/credential (writes). */
43
45
  export declare const writableAuthCredentialSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
46
+ apiEndpoint: z.ZodOptional<z.ZodString>;
44
47
  type: z.ZodLiteral<"oauth">;
45
48
  refresh: z.ZodString;
46
49
  access: z.ZodString;
@@ -55,6 +58,7 @@ export declare const writableAuthCredentialSchema: z.ZodDiscriminatedUnion<[z.Zo
55
58
  }, z.core.$strict>], "type">;
56
59
  /** Discriminated union returned in snapshots (refresh is sentinel for OAuth). */
57
60
  export declare const snapshotCredentialSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
61
+ apiEndpoint: z.ZodOptional<z.ZodString>;
58
62
  type: z.ZodLiteral<"oauth">;
59
63
  access: z.ZodString;
60
64
  expires: z.ZodNumber;
@@ -71,6 +75,7 @@ export declare const credentialSnapshotEntrySchema: z.ZodObject<{
71
75
  id: z.ZodNumber;
72
76
  provider: z.ZodString;
73
77
  credential: z.ZodDiscriminatedUnion<[z.ZodObject<{
78
+ apiEndpoint: z.ZodOptional<z.ZodString>;
74
79
  type: z.ZodLiteral<"oauth">;
75
80
  access: z.ZodString;
76
81
  expires: z.ZodNumber;
@@ -89,6 +94,7 @@ export declare const snapshotEntrySchema: z.ZodObject<{
89
94
  id: z.ZodNumber;
90
95
  provider: z.ZodString;
91
96
  credential: z.ZodDiscriminatedUnion<[z.ZodObject<{
97
+ apiEndpoint: z.ZodOptional<z.ZodString>;
92
98
  type: z.ZodLiteral<"oauth">;
93
99
  access: z.ZodString;
94
100
  expires: z.ZodNumber;
@@ -124,6 +130,7 @@ export declare const snapshotResponseSchema: z.ZodObject<{
124
130
  id: z.ZodNumber;
125
131
  provider: z.ZodString;
126
132
  credential: z.ZodDiscriminatedUnion<[z.ZodObject<{
133
+ apiEndpoint: z.ZodOptional<z.ZodString>;
127
134
  type: z.ZodLiteral<"oauth">;
128
135
  access: z.ZodString;
129
136
  expires: z.ZodNumber;
@@ -155,6 +162,7 @@ export declare const snapshotStreamSnapshotEventSchema: z.ZodObject<{
155
162
  id: z.ZodNumber;
156
163
  provider: z.ZodString;
157
164
  credential: z.ZodDiscriminatedUnion<[z.ZodObject<{
165
+ apiEndpoint: z.ZodOptional<z.ZodString>;
158
166
  type: z.ZodLiteral<"oauth">;
159
167
  access: z.ZodString;
160
168
  expires: z.ZodNumber;
@@ -187,6 +195,7 @@ export declare const snapshotStreamEntryEventSchema: z.ZodObject<{
187
195
  id: z.ZodNumber;
188
196
  provider: z.ZodString;
189
197
  credential: z.ZodDiscriminatedUnion<[z.ZodObject<{
198
+ apiEndpoint: z.ZodOptional<z.ZodString>;
190
199
  type: z.ZodLiteral<"oauth">;
191
200
  access: z.ZodString;
192
201
  expires: z.ZodNumber;
@@ -231,6 +240,7 @@ export declare const snapshotStreamEventSchema: z.ZodDiscriminatedUnion<[z.ZodOb
231
240
  id: z.ZodNumber;
232
241
  provider: z.ZodString;
233
242
  credential: z.ZodDiscriminatedUnion<[z.ZodObject<{
243
+ apiEndpoint: z.ZodOptional<z.ZodString>;
234
244
  type: z.ZodLiteral<"oauth">;
235
245
  access: z.ZodString;
236
246
  expires: z.ZodNumber;
@@ -261,6 +271,7 @@ export declare const snapshotStreamEventSchema: z.ZodDiscriminatedUnion<[z.ZodOb
261
271
  id: z.ZodNumber;
262
272
  provider: z.ZodString;
263
273
  credential: z.ZodDiscriminatedUnion<[z.ZodObject<{
274
+ apiEndpoint: z.ZodOptional<z.ZodString>;
264
275
  type: z.ZodLiteral<"oauth">;
265
276
  access: z.ZodString;
266
277
  expires: z.ZodNumber;
@@ -358,6 +369,7 @@ export declare const credentialRefreshResponseSchema: z.ZodObject<{
358
369
  id: z.ZodNumber;
359
370
  provider: z.ZodString;
360
371
  credential: z.ZodDiscriminatedUnion<[z.ZodObject<{
372
+ apiEndpoint: z.ZodOptional<z.ZodString>;
361
373
  type: z.ZodLiteral<"oauth">;
362
374
  access: z.ZodString;
363
375
  expires: z.ZodNumber;
@@ -382,6 +394,7 @@ export declare const credentialDisableResponseSchema: z.ZodObject<{
382
394
  export declare const credentialUploadRequestSchema: z.ZodObject<{
383
395
  provider: z.ZodString;
384
396
  credential: z.ZodDiscriminatedUnion<[z.ZodObject<{
397
+ apiEndpoint: z.ZodOptional<z.ZodString>;
385
398
  type: z.ZodLiteral<"oauth">;
386
399
  refresh: z.ZodString;
387
400
  access: z.ZodString;
@@ -400,6 +413,7 @@ export declare const credentialUploadResponseSchema: z.ZodObject<{
400
413
  id: z.ZodNumber;
401
414
  provider: z.ZodString;
402
415
  credential: z.ZodDiscriminatedUnion<[z.ZodObject<{
416
+ apiEndpoint: z.ZodOptional<z.ZodString>;
403
417
  type: z.ZodLiteral<"oauth">;
404
418
  access: z.ZodString;
405
419
  expires: z.ZodNumber;
@@ -37,6 +37,15 @@ export declare function isApiKeyResolver(key: ApiKey | undefined): key is ApiKey
37
37
  * `lastChance: false`). Static keys pass through unchanged.
38
38
  */
39
39
  export declare function resolveApiKeyOnce(key: ApiKey | undefined, signal?: AbortSignal): Promise<string | undefined>;
40
+ /**
41
+ * Wraps a resolver with a bearer that was already selected for this request.
42
+ *
43
+ * Callers that preflight credentials can pass the returned resolver to the
44
+ * auth-retry driver without making the driver know about that preflight: the
45
+ * first initial resolution reuses `seed`, and all later resolutions delegate to
46
+ * `resolver`.
47
+ */
48
+ export declare function seedApiKeyResolver(seed: string | undefined, resolver: ApiKeyResolver): ApiKeyResolver;
40
49
  /**
41
50
  * Classifies whether an error should trigger a credential refresh/rotation
42
51
  * retry: a hard `401`, or a rotatable usage-limit ("usage_limit_reached",
@@ -139,6 +139,7 @@ export type CompletionProbeCredential = {
139
139
  projectId?: string;
140
140
  email?: string;
141
141
  enterpriseUrl?: string;
142
+ apiEndpoint?: string;
142
143
  };
143
144
  /**
144
145
  * Caller-supplied bearer probe. Receives the post-refresh credential for a
@@ -435,6 +436,7 @@ export interface OAuthAccess {
435
436
  email?: string;
436
437
  projectId?: string;
437
438
  enterpriseUrl?: string;
439
+ apiEndpoint?: string;
438
440
  }
439
441
  export interface OAuthAccessFailure {
440
442
  credentialId?: number;
@@ -442,6 +444,7 @@ export interface OAuthAccessFailure {
442
444
  email?: string;
443
445
  projectId?: string;
444
446
  enterpriseUrl?: string;
447
+ apiEndpoint?: string;
445
448
  error: string;
446
449
  }
447
450
  /**
@@ -1,7 +1,13 @@
1
1
  import type { DialectDefinition, InbandScanEvent, InbandScanner, InbandScannerOptions } from "./types";
2
+ export declare const ANTHROPIC_THINKING_TAG_PREFIXES: readonly ["<thinking", "</thinking", "<think", "</think", "<scratchpad", "</scratchpad", "<antml:thinking", "</antml:thinking", "<antml:think", "</antml:think", "<antml:scratchpad", "</antml:scratchpad"];
3
+ export interface AnthropicInbandScannerConfig {
4
+ readonly wrapperTags?: Readonly<Record<string, true>>;
5
+ readonly baseTagPrefixes?: readonly string[];
6
+ readonly allTagPrefixes?: readonly string[];
7
+ }
2
8
  export declare class AnthropicInbandScanner implements InbandScanner {
3
9
  #private;
4
- constructor(options?: InbandScannerOptions);
10
+ constructor(options?: InbandScannerOptions, config?: AnthropicInbandScannerConfig);
5
11
  feed(text: string): InbandScanEvent[];
6
12
  flush(): InbandScanEvent[];
7
13
  }
@@ -0,0 +1,3 @@
1
+ import type { DialectDefinition } from "./types";
2
+ declare const definition: DialectDefinition;
3
+ export default definition;
@@ -41,4 +41,5 @@ export * from "./utils/event-stream";
41
41
  export * from "./utils/overflow";
42
42
  export * from "./utils/retry";
43
43
  export * from "./utils/schema";
44
+ export * from "./utils/thinking-loop";
44
45
  export * from "./utils/validation";
@@ -1,5 +1,5 @@
1
1
  import { ProviderHttpError } from "../errors";
2
- import type { Context, Model, StreamFunction, StreamOptions } from "../types";
2
+ import type { Context, Model, ProviderSessionState, StreamFunction, StreamOptions } from "../types";
3
3
  import type { Content, FunctionCallingConfigMode, ThinkingConfig } from "./google-shared";
4
4
  import { type GoogleThinkingLevel } from "./google-shared";
5
5
  /**
@@ -52,13 +52,33 @@ export interface GoogleGeminiCliOptions extends StreamOptions {
52
52
  */
53
53
  requestModelId?: string;
54
54
  projectId?: string;
55
+ /** Antigravity endpoint routing mode: "auto" (default with failover), "production", "sandbox". */
56
+ antigravityEndpointMode?: "auto" | "production" | "sandbox";
57
+ providerSessionState?: Map<string, ProviderSessionState>;
55
58
  }
56
- export { ANTIGRAVITY_NO_PREAMBLE_INSTRUCTION, ANTIGRAVITY_SYSTEM_INSTRUCTION, getAntigravityUserAgent, getGeminiCliHeaders, getGeminiCliUserAgent, } from "@oh-my-pi/pi-catalog/wire/gemini-headers";
59
+ export interface AntigravityProviderSessionState extends ProviderSessionState {
60
+ lastGoodEndpoint?: string;
61
+ /**
62
+ * Per-conversation request-envelope identity that mirrors the real
63
+ * Antigravity client. `sessionId` is the signed-decimal session id;
64
+ * `agentId`/`trajectoryId` are UUIDs; `stepIndex` is the monotonic step
65
+ * counter; `lastExecutionId` is the prior response id echoed as
66
+ * `labels.last_execution_id`.
67
+ */
68
+ agentId?: string;
69
+ trajectoryId?: string;
70
+ sessionId?: string;
71
+ stepIndex?: number;
72
+ lastExecutionId?: string;
73
+ }
74
+ export declare function getAntigravityProviderSessionState(providerSessionState: Map<string, ProviderSessionState> | undefined): AntigravityProviderSessionState | undefined;
75
+ export { ANTIGRAVITY_SYSTEM_INSTRUCTION, getAntigravityUserAgent, getGeminiCliHeaders, getGeminiCliUserAgent, } from "@oh-my-pi/pi-catalog/wire/gemini-headers";
57
76
  interface ParsedGeminiCliCredentials {
58
77
  accessToken: string;
59
78
  projectId: string;
60
79
  refreshToken?: string;
61
80
  expiresAt?: number;
81
+ email?: string;
62
82
  }
63
83
  export declare function parseGeminiCliCredentials(apiKeyRaw: string): ParsedGeminiCliCredentials;
64
84
  export declare function shouldRefreshGeminiCliCredentials(expiresAt: number | undefined, isAntigravity: boolean, nowMs?: number): boolean;
@@ -93,6 +113,7 @@ interface CloudCodeAssistRequest {
93
113
  allowedFunctionNames?: string[];
94
114
  };
95
115
  };
116
+ labels?: Record<string, string>;
96
117
  };
97
118
  requestType?: string;
98
119
  userAgent?: string;
@@ -21,6 +21,8 @@ export interface OpenAICompletionsOptions extends StreamOptions {
21
21
  /** Force-disable reasoning where supported, or request the lowest effort on generic effort endpoints. */
22
22
  disableReasoning?: boolean;
23
23
  serviceTier?: ServiceTier;
24
+ /** @internal True when maxTokens came from the caller, not the model default. */
25
+ maxTokensExplicit?: boolean;
24
26
  /**
25
27
  * Routing-variant suffix appended to OpenRouter model IDs when none is
26
28
  * already present (`anthropic/claude-haiku-latest` → `…:nitro`). Common
@@ -1,7 +1,8 @@
1
- import type { OAuthController, OAuthLoginCallbacks } from "./oauth/types";
2
- export declare function loginAlibabaCodingPlan(options: OAuthController): Promise<string>;
1
+ import type { OAuthController, OAuthCredentials, OAuthLoginCallbacks } from "./oauth/types";
2
+ export declare function loginAlibabaCodingPlan(options: OAuthController): Promise<OAuthCredentials>;
3
3
  export declare const alibabaCodingPlanProvider: {
4
4
  readonly id: "alibaba-coding-plan";
5
5
  readonly name: "Alibaba Coding Plan";
6
- readonly login: (cb: OAuthLoginCallbacks) => Promise<string>;
6
+ readonly login: (cb: OAuthLoginCallbacks) => Promise<OAuthCredentials>;
7
+ readonly getApiKey: (credentials: OAuthCredentials) => string;
7
8
  };
@@ -17,7 +17,7 @@ type GitHubCopilotLoginOptions = {
17
17
  * Refresh GitHub Copilot token.
18
18
  * With the opencode OAuth flow, the GitHub token is used directly — no JWT exchange needed.
19
19
  */
20
- export declare function refreshGitHubCopilotToken(refreshToken: string, enterpriseDomain?: string): OAuthCredentials;
20
+ export declare function refreshGitHubCopilotToken(refreshToken: string, enterpriseDomain?: string, apiEndpoint?: string): OAuthCredentials;
21
21
  /**
22
22
  * Login with GitHub Copilot OAuth (device code flow)
23
23
  *
@@ -1,9 +1,3 @@
1
- /**
2
- * Shared OAuth flow for Google-style providers (Gemini CLI, Antigravity).
3
- *
4
- * Both providers use the same authorization-code flow shape; only the client
5
- * credentials, scopes, endpoint constants, and project-discovery logic differ.
6
- */
7
1
  import { OAuthCallbackFlow } from "./callback-server";
8
2
  import type { OAuthController, OAuthCredentials } from "./types";
9
3
  export interface GoogleOAuthFlowConfig {
@@ -3,6 +3,8 @@
3
3
  */
4
4
  import type { OAuthController, OAuthCredentials } from "./types";
5
5
  export declare function decodeJwt<T = Record<string, unknown>>(token: string): T | null;
6
+ /** Formats OpenAI Codex OAuth token endpoint errors for login and refresh failures. */
7
+ export declare function formatOpenAICodexTokenEndpointError(status: number, bodyText: string): string;
6
8
  /** Builds the Codex browser OAuth URL used by browser login; exported for auth regression tests. */
7
9
  export declare function createOpenAICodexAuthorizationUrl(args: {
8
10
  state: string;
@@ -8,6 +8,7 @@ export type OAuthCredentials = {
8
8
  projectId?: string;
9
9
  email?: string;
10
10
  accountId?: string;
11
+ apiEndpoint?: string;
11
12
  };
12
13
  export type OAuthProvider = OAuthProviderUnion;
13
14
  export type OAuthProviderId = OAuthProvider | (string & {});
@@ -12,7 +12,8 @@ declare const ALL: ({
12
12
  } | {
13
13
  readonly id: "alibaba-coding-plan";
14
14
  readonly name: "Alibaba Coding Plan";
15
- readonly login: (cb: import("./oauth").OAuthLoginCallbacks) => Promise<string>;
15
+ readonly login: (cb: import("./oauth").OAuthLoginCallbacks) => Promise<import("./oauth").OAuthCredentials>;
16
+ readonly getApiKey: (credentials: import("./oauth").OAuthCredentials) => string;
16
17
  } | {
17
18
  readonly id: "amazon-bedrock";
18
19
  readonly name: "Amazon Bedrock";
@@ -304,6 +304,8 @@ export interface SimpleStreamOptions extends Omit<StreamOptions, "apiKey"> {
304
304
  * or the catalog entry already names the variant).
305
305
  */
306
306
  openrouterVariant?: string;
307
+ /** Antigravity endpoint routing mode: "auto" (default with failover), "production", "sandbox". */
308
+ antigravityEndpointMode?: "auto" | "production" | "sandbox";
307
309
  }
308
310
  export type StreamFunction<TApi extends Api> = (model: Model<TApi>, context: Context, options: OptionsForApi<TApi>) => AssistantMessageEventStream;
309
311
  export interface TextSignatureV1 {
@@ -388,12 +390,18 @@ export interface DeveloperMessage {
388
390
  providerPayload?: ProviderPayload;
389
391
  timestamp: number;
390
392
  }
393
+ export interface ContextSnapshot {
394
+ promptTokens: number;
395
+ nonMessageTokens: number;
396
+ lastMessageTimestamp?: number;
397
+ }
391
398
  export interface AssistantMessage {
392
399
  role: "assistant";
393
400
  content: (TextContent | ThinkingContent | RedactedThinkingContent | ToolCall)[];
394
401
  api: Api;
395
402
  provider: Provider;
396
403
  model: string;
404
+ contextSnapshot?: ContextSnapshot;
397
405
  responseId?: string;
398
406
  /**
399
407
  * Name of the upstream provider an aggregator routed this request to, as
@@ -277,6 +277,7 @@ export interface UsageCredential {
277
277
  email?: string;
278
278
  enterpriseUrl?: string;
279
279
  metadata?: Record<string, unknown>;
280
+ apiEndpoint?: string;
280
281
  }
281
282
  /** Parameters provided to a usage fetcher. */
282
283
  export interface UsageFetchParams {
@@ -0,0 +1,2 @@
1
+ export declare function extractGoogleValidationUrl(errorBody: string): string | undefined;
2
+ export declare function formatGoogleValidationRequiredMessage(validationUrl: string, nextAction: string, email?: string): string;
@@ -0,0 +1,45 @@
1
+ import type { Api, Model } from "../types";
2
+ import { AssistantMessageEventStream } from "./event-stream";
3
+ /** Stable lead phrase of the guard's error message; exported for tests. The
4
+ * message also carries "stream stall" so the session + transport retry
5
+ * classifiers treat it as a transient (retryable) stop without bespoke rules. */
6
+ export declare const THINKING_LOOP_ERROR_MARKER = "Thinking loop detected";
7
+ /**
8
+ * True when `model` should be guarded for thinking loops (Gemini only).
9
+ *
10
+ * OpenAI-compat transports can serve Gemini under an arbitrary provider/id, so
11
+ * for those we trust the family-derived (and user-overridable)
12
+ * `compat.enableGeminiThinkingLoopGuard` flag set by the catalog. Direct Google
13
+ * transports always carry a clearly gemini-shaped id/provider, so a string
14
+ * match is sufficient (and works for hand-built models without a compat record).
15
+ */
16
+ export declare function isGeminiThinkingLoopModel(model: Model<Api>): boolean;
17
+ /**
18
+ * Stateful detector fed the streamed thinking deltas. `push` returns a
19
+ * human-readable reason the first time a loop shape is recognized; the caller
20
+ * is responsible for stopping after the first hit.
21
+ */
22
+ export declare class ThinkingLoopDetector {
23
+ #private;
24
+ push(delta: string): string | null;
25
+ /** Process the buffered trailing paragraph (one with no blank-line / heading
26
+ * terminator). Called when the thinking block ends so the final segment —
27
+ * which may be the one that completes a duplicate cluster — is not dropped. */
28
+ flush(): string | null;
29
+ }
30
+ /**
31
+ * Wrap a provider stream with the loop guard. `controller` is the guard's own
32
+ * abort handle: aborting it (after wiring it into the provider's signal via
33
+ * {@link withGeminiThinkingLoopGuard}) tears down the upstream once a loop
34
+ * trips.
35
+ */
36
+ export declare function guardThinkingLoopStream(inner: AssistantMessageEventStream, model: Model<Api>, controller: AbortController): AssistantMessageEventStream;
37
+ /**
38
+ * Apply the Gemini loop guard around a provider dispatch. For non-Gemini models
39
+ * (or when disabled) this is a transparent pass-through. For Gemini it injects a
40
+ * guard abort signal into the provider call so a detected loop tears down the
41
+ * upstream, then wraps the returned stream.
42
+ */
43
+ export declare function withGeminiThinkingLoopGuard<O extends {
44
+ signal?: AbortSignal;
45
+ }>(model: Model<Api>, options: O | undefined, dispatch: (options: O | undefined) => AssistantMessageEventStream): AssistantMessageEventStream;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-ai",
4
- "version": "16.0.3",
4
+ "version": "16.0.5",
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,8 +38,8 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@bufbuild/protobuf": "^2.12.0",
41
- "@oh-my-pi/pi-catalog": "16.0.3",
42
- "@oh-my-pi/pi-utils": "16.0.3",
41
+ "@oh-my-pi/pi-catalog": "16.0.5",
42
+ "@oh-my-pi/pi-utils": "16.0.5",
43
43
  "partial-json": "^0.1.7",
44
44
  "zod": "^4"
45
45
  },
@@ -21,6 +21,7 @@ import { usageReportSchema } from "../usage";
21
21
  /** Real OAuth credential (broker-side) — refresh token is the actual upstream value. */
22
22
  export const oauthCredentialSchema = z
23
23
  .object({
24
+ apiEndpoint: z.string().optional(),
24
25
  type: z.literal("oauth"),
25
26
  refresh: z
26
27
  .string()
package/src/auth-retry.ts CHANGED
@@ -51,6 +51,25 @@ export async function resolveApiKeyOnce(key: ApiKey | undefined, signal?: AbortS
51
51
  return key;
52
52
  }
53
53
 
54
+ /**
55
+ * Wraps a resolver with a bearer that was already selected for this request.
56
+ *
57
+ * Callers that preflight credentials can pass the returned resolver to the
58
+ * auth-retry driver without making the driver know about that preflight: the
59
+ * first initial resolution reuses `seed`, and all later resolutions delegate to
60
+ * `resolver`.
61
+ */
62
+ export function seedApiKeyResolver(seed: string | undefined, resolver: ApiKeyResolver): ApiKeyResolver {
63
+ let seedPending = seed !== undefined;
64
+ return ctx => {
65
+ if (seedPending && ctx.error === undefined) {
66
+ seedPending = false;
67
+ return seed;
68
+ }
69
+ return resolver(ctx);
70
+ };
71
+ }
72
+
54
73
  /**
55
74
  * Classifies whether an error should trigger a credential refresh/rotation
56
75
  * retry: a hard `401`, or a rotatable usage-limit ("usage_limit_reached",
@@ -191,6 +191,7 @@ export type CompletionProbeCredential =
191
191
  projectId?: string;
192
192
  email?: string;
193
193
  enterpriseUrl?: string;
194
+ apiEndpoint?: string;
194
195
  };
195
196
 
196
197
  /**
@@ -638,6 +639,7 @@ export interface OAuthAccess {
638
639
  email?: string;
639
640
  projectId?: string;
640
641
  enterpriseUrl?: string;
642
+ apiEndpoint?: string;
641
643
  }
642
644
 
643
645
  export interface OAuthAccessFailure {
@@ -646,6 +648,7 @@ export interface OAuthAccessFailure {
646
648
  email?: string;
647
649
  projectId?: string;
648
650
  enterpriseUrl?: string;
651
+ apiEndpoint?: string;
649
652
  error: string;
650
653
  }
651
654
 
@@ -1512,17 +1515,6 @@ export class AuthStorage {
1512
1515
  this.#resetProviderAssignments(provider);
1513
1516
  }
1514
1517
 
1515
- async #upsertOAuthCredential(provider: string, credential: OAuthCredential): Promise<void> {
1516
- const stored = this.#store.upsertAuthCredentialRemote
1517
- ? await this.#store.upsertAuthCredentialRemote(provider, credential)
1518
- : this.#store.upsertAuthCredentialForProvider(provider, credential);
1519
- this.#setStoredCredentials(
1520
- provider,
1521
- stored.map(record => ({ id: record.id, credential: record.credential })),
1522
- );
1523
- this.#resetProviderAssignments(provider);
1524
- }
1525
-
1526
1518
  /**
1527
1519
  * List stored credential rows, optionally filtered by provider.
1528
1520
  */
@@ -1786,7 +1778,10 @@ export class AuthStorage {
1786
1778
  return;
1787
1779
  }
1788
1780
  const newCredential: OAuthCredential = { type: "oauth", ...result };
1789
- await this.#upsertOAuthCredential(def.storeCredentialsAs ?? provider, newCredential);
1781
+ // Use set() instead of #upsertOAuthCredential to replace ALL existing credentials
1782
+ // (including legacy api_key rows from older versions) with the new OAuth credential.
1783
+ // This ensures getApiKey() doesn't match an old api_key row before the new OAuth row.
1784
+ await this.set(def.storeCredentialsAs ?? provider, newCredential);
1790
1785
  }
1791
1786
 
1792
1787
  /**
@@ -1811,6 +1806,7 @@ export class AuthStorage {
1811
1806
  projectId: credential.projectId,
1812
1807
  email: credential.email,
1813
1808
  enterpriseUrl: credential.enterpriseUrl,
1809
+ apiEndpoint: credential.apiEndpoint,
1814
1810
  };
1815
1811
  }
1816
1812
 
@@ -1889,6 +1885,7 @@ export class AuthStorage {
1889
1885
  projectId: credential.projectId,
1890
1886
  email: credential.email,
1891
1887
  enterpriseUrl: credential.enterpriseUrl,
1888
+ apiEndpoint: credential.apiEndpoint,
1892
1889
  };
1893
1890
  }
1894
1891
 
@@ -1912,6 +1909,7 @@ export class AuthStorage {
1912
1909
  projectId: credential.projectId,
1913
1910
  email: credential.email,
1914
1911
  enterpriseUrl: credential.enterpriseUrl,
1912
+ apiEndpoint: credential.apiEndpoint,
1915
1913
  };
1916
1914
  }
1917
1915
 
@@ -1925,6 +1923,7 @@ export class AuthStorage {
1925
1923
  projectId: refreshed.projectId ?? credential.projectId,
1926
1924
  email: refreshed.email ?? credential.email,
1927
1925
  enterpriseUrl: refreshed.enterpriseUrl ?? credential.enterpriseUrl,
1926
+ apiEndpoint: refreshed.apiEndpoint ?? credential.apiEndpoint,
1928
1927
  };
1929
1928
  }
1930
1929
 
@@ -1972,6 +1971,7 @@ export class AuthStorage {
1972
1971
  projectId: next.projectId,
1973
1972
  email: next.email,
1974
1973
  enterpriseUrl: next.enterpriseUrl,
1974
+ apiEndpoint: next.apiEndpoint,
1975
1975
  });
1976
1976
  }
1977
1977
 
@@ -3392,6 +3392,7 @@ export class AuthStorage {
3392
3392
  email: result.newCredentials.email ?? selection.credential.email,
3393
3393
  projectId: result.newCredentials.projectId ?? selection.credential.projectId,
3394
3394
  enterpriseUrl: result.newCredentials.enterpriseUrl ?? selection.credential.enterpriseUrl,
3395
+ apiEndpoint: result.newCredentials.apiEndpoint ?? selection.credential.apiEndpoint,
3395
3396
  };
3396
3397
  this.#replaceCredentialAt(provider, selection.index, updated);
3397
3398
  if ((checkUsage && !allowBlocked) || requiresProModel) {
@@ -3518,6 +3519,7 @@ export class AuthStorage {
3518
3519
  return JSON.stringify({
3519
3520
  token: oauthSelection.credential.access,
3520
3521
  enterpriseUrl: oauthSelection.credential.enterpriseUrl,
3522
+ apiEndpoint: oauthSelection.credential.apiEndpoint,
3521
3523
  });
3522
3524
  }
3523
3525
  return oauthSelection.credential.access;
@@ -3615,6 +3617,7 @@ export class AuthStorage {
3615
3617
  email: credential.email,
3616
3618
  projectId: credential.projectId,
3617
3619
  enterpriseUrl: credential.enterpriseUrl,
3620
+ apiEndpoint: credential.apiEndpoint,
3618
3621
  };
3619
3622
  }
3620
3623
 
@@ -4075,6 +4078,7 @@ export class AuthStorage {
4075
4078
  email: refreshed.email ?? target.credential.email,
4076
4079
  projectId: refreshed.projectId ?? target.credential.projectId,
4077
4080
  enterpriseUrl: refreshed.enterpriseUrl ?? target.credential.enterpriseUrl,
4081
+ apiEndpoint: refreshed.apiEndpoint ?? target.credential.apiEndpoint,
4078
4082
  };
4079
4083
  this.#replaceCredentialAt(provider, index, updated);
4080
4084
  return {