@oh-my-pi/pi-ai 17.2.11 → 17.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/dist/types/auth-retry.d.ts +3 -3
- package/dist/types/auth-storage.d.ts +2 -0
- package/dist/types/error/auth-classify.d.ts +5 -5
- package/dist/types/error/aws.d.ts +5 -1
- package/dist/types/error/flags.d.ts +4 -0
- package/dist/types/providers/aws-credentials.d.ts +4 -3
- package/dist/types/providers/cursor/exec-modern.d.ts +1 -1
- package/dist/types/providers/cursor-pi-args.d.ts +14 -0
- package/dist/types/providers/openai-shared.d.ts +9 -1
- package/dist/types/registry/api-key-login.d.ts +6 -4
- package/dist/types/registry/cloudflare-ai-gateway.d.ts +2 -2
- package/dist/types/registry/kagi.d.ts +2 -2
- package/dist/types/registry/litellm.d.ts +2 -2
- package/dist/types/registry/llama-cpp.d.ts +2 -2
- package/dist/types/registry/lm-studio.d.ts +2 -2
- package/dist/types/registry/parallel.d.ts +2 -2
- package/dist/types/registry/tavily.d.ts +1 -1
- package/dist/types/registry/vercel-ai-gateway.d.ts +2 -2
- package/dist/types/registry/vllm.d.ts +2 -2
- package/dist/types/types.d.ts +7 -0
- package/dist/types/usage/cursor.d.ts +11 -0
- package/dist/types/usage/openai-codex-reset.d.ts +0 -20
- package/dist/types/usage/shared.d.ts +13 -1
- package/dist/types/utils/block-symbols.d.ts +12 -0
- package/package.json +5 -5
- package/src/auth-retry.ts +5 -4
- package/src/auth-storage.ts +77 -33
- package/src/dialect/owned-stream.ts +3 -0
- package/src/error/auth-classify.ts +7 -6
- package/src/error/aws.ts +5 -1
- package/src/error/flags.ts +14 -0
- package/src/providers/amazon-bedrock.ts +38 -0
- package/src/providers/aws-credentials.ts +222 -29
- package/src/providers/cursor/exec-modern.ts +1 -0
- package/src/providers/cursor-pi-args.ts +22 -0
- package/src/providers/cursor.ts +81 -1
- package/src/providers/google-gemini-cli.ts +49 -15
- package/src/providers/google-shared.ts +7 -1
- package/src/providers/openai-codex/request-transformer.ts +38 -17
- package/src/providers/openai-codex-responses.ts +2 -3
- package/src/providers/openai-responses.ts +4 -0
- package/src/providers/openai-shared.ts +55 -1
- package/src/providers/pi-native-server.ts +1 -0
- package/src/providers/register-builtins.ts +18 -14
- package/src/registry/api-key-login.ts +26 -12
- package/src/registry/aws.ts +13 -6
- package/src/registry/cloudflare-ai-gateway.ts +10 -29
- package/src/registry/kagi.ts +11 -29
- package/src/registry/litellm.ts +11 -29
- package/src/registry/llama-cpp.ts +11 -21
- package/src/registry/lm-studio.ts +9 -20
- package/src/registry/oauth/callback-server.ts +93 -5
- package/src/registry/parallel.ts +10 -28
- package/src/registry/tavily.ts +9 -27
- package/src/registry/vercel-ai-gateway.ts +10 -28
- package/src/registry/vllm.ts +11 -21
- package/src/stream.ts +10 -7
- package/src/types.ts +7 -0
- package/src/usage/alibaba-token-plan.ts +8 -15
- package/src/usage/claude.ts +12 -19
- package/src/usage/cursor.ts +179 -59
- package/src/usage/gemini.ts +3 -2
- package/src/usage/github-copilot.ts +3 -2
- package/src/usage/google-antigravity.ts +10 -17
- package/src/usage/kimi.ts +35 -17
- package/src/usage/minimax-code.ts +7 -27
- package/src/usage/openai-codex-reset.ts +3 -2
- package/src/usage/openai-codex.ts +5 -5
- package/src/usage/opencode-go.ts +1 -2
- package/src/usage/shared.ts +33 -10
- package/src/usage/synthetic.ts +4 -11
- package/src/usage/umans.ts +2 -2
- package/src/usage/xai-oauth.ts +13 -26
- package/src/usage/zai.ts +4 -6
- package/src/utils/aws-profile.ts +39 -1
- package/src/utils/block-symbols.ts +18 -0
- package/src/utils/leaked-thinking-stream.ts +3 -0
- package/src/utils/openrouter-headers.ts +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [17.2.13] - 2026-08-11
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Standardized first-party outbound User-Agent headers on `omp/<version>` via the shared `USER_AGENT` utility.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Fixed the Amazon Bedrock and Cursor transports ignoring `StreamOptions.headers`; both built their request headers from scratch, so caller-supplied tracing or attribution headers were silently dropped while working on every other provider ([#8107](https://github.com/can1357/oh-my-pi/pull/8107) by [@svperfecta](https://github.com/svperfecta)).
|
|
14
|
+
- Fixed Antigravity Flash turns hanging after successful response headers when the endpoint never emitted an SSE event; the provider now cancels the stalled body and fails over after 60 seconds while retaining the longer allowance for Pro reasoning starts.
|
|
15
|
+
- Fixed Cursor exec-bridge bash/grep calls failing ArkType validation when the server omitted optional frame fields: synthesized and executed tool args now drop `undefined` keys (`cwd`, `case`, `skip`, `timeout`) instead of writing `optional: value || undefined`.
|
|
16
|
+
- Fixed Cursor sessions double-executing settled tools when `tools.format` is an owned dialect (e.g. `gemini`): `wrapInbandToolStream` rebuilt toolCall blocks without copying `kCursorExecResolved`, so agent-loop re-ran bash/grep/todo and appended a second result for the same call id.
|
|
17
|
+
- Fixed Codex Responses Lite requests for opaque model codenames such as Daybreak omitting the required `reasoning.context: "all_turns"` value and failing with HTTP 400.
|
|
18
|
+
- Fixed Cursor personal usage reporting for current Pro / Pro+ / Ultra `/api/usage-summary` payloads that expose `individualUsage.plan` (and optional `onDemand`) instead of the older `individualUsage.overall` bucket ([#7998](https://github.com/can1357/oh-my-pi/pull/7998) by [@dnth](https://github.com/dnth)).
|
|
19
|
+
- Allowed passive Google callers to accept empty or thinking-only `STOP` responses as successful silence instead of exhausting the provider's empty-response retry budget. ([#8223](https://github.com/can1357/oh-my-pi/issues/8223))
|
|
20
|
+
- Fixed the AWS credential resolver ignoring `role_arn` profiles: shared-config role chaining (`source_profile` recursion, `web_identity_token_file`, `credential_source`) now resolves via STS `AssumeRole`/`AssumeRoleWithWebIdentity`, honoring `role_session_name`/`duration_seconds`/`external_id`, so Bedrock is detected on EKS/IRSA and multi-account setups instead of reporting "No models available" ([#8209](https://github.com/can1357/oh-my-pi/issues/8209)).
|
|
21
|
+
- Fixed Bedrock availability being under-detected on Nitro/EKS hosts: the EC2 metadata probe now recognizes Nitro DMI markers (`board_asset_tag` instance ids, `Amazon EC2` vendor fields) in addition to the Xen `ec2` UUID prefix ([#8209](https://github.com/can1357/oh-my-pi/issues/8209)).
|
|
22
|
+
- Fixed DeepSeek Responses targets (opencode-go) rejecting a thinking-mode continuation with `400 The reasoning_text in the thinking mode must be passed back to the API` after a prewalk hand-off plus mid-run compaction: the Responses input builder re-encoded replayed assistant turns without a reasoning item, so the request enabled reasoning but shipped no `reasoning_text`. The encoder now synthesizes a `reasoning_text` reasoning item for every replayed assistant turn when the target requires reasoning replay in thinking mode (`requiresReasoningContentForAllAssistantTurns` / `requiresReasoningContentForToolCalls`), mirroring the chat-completions `reasoning_content` safety net ([#8248](https://github.com/can1357/oh-my-pi/issues/8248)).
|
|
23
|
+
|
|
24
|
+
## [17.2.12] - 2026-08-08
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- Fixed account-scoped Codex cyber-policy denials bypassing sibling credential rotation; replay-safe requests now try every configured account before surfacing the error.
|
|
29
|
+
|
|
5
30
|
## [17.2.11] - 2026-08-07
|
|
6
31
|
|
|
7
32
|
### Breaking Changes
|
|
@@ -14,9 +14,9 @@ import { isAuthRetryableError } from "./error/auth-classify.js";
|
|
|
14
14
|
* (invalidate/usage-limit the current credential and rotate to a sibling).
|
|
15
15
|
*
|
|
16
16
|
* Current drivers preserve that bounded a/b/c sequence for ordinary 401/auth
|
|
17
|
-
* failures.
|
|
18
|
-
* until the resolver returns `undefined`,
|
|
19
|
-
* {@link AUTH_RETRY_MAX_ATTEMPTS}.
|
|
17
|
+
* failures. Account-scoped policy denials, 403s, and usage-limit failures skip
|
|
18
|
+
* refresh and may repeat step (c) until the resolver returns `undefined`,
|
|
19
|
+
* cycles, or hits {@link AUTH_RETRY_MAX_ATTEMPTS}.
|
|
20
20
|
*/
|
|
21
21
|
export interface ApiKeyResolveContext {
|
|
22
22
|
/** True when the resolver should rotate to a sibling credential. */
|
|
@@ -1143,6 +1143,8 @@ export declare class AuthStorage {
|
|
|
1143
1143
|
* - usage-limit / account-rate-limit error → {@link AuthStorage.markUsageLimitReached}
|
|
1144
1144
|
* (temporary block via its own backoff — default plus server usage-report
|
|
1145
1145
|
* reset; sticky left intact so the next resolve re-ranks around the block).
|
|
1146
|
+
* - account-scoped policy denial → temporarily block that account without
|
|
1147
|
+
* marking its credential suspect, then rotate through eligible siblings.
|
|
1146
1148
|
* - otherwise (hard 401 / auth failure) → mark the credential suspect (or
|
|
1147
1149
|
* reload when no broker hook is wired) and block it, then drop matching
|
|
1148
1150
|
* sticky state.
|
|
@@ -10,11 +10,11 @@ export declare function isInvalidatedOAuthTokenError(error: unknown): boolean;
|
|
|
10
10
|
/**
|
|
11
11
|
* Whether an upstream failure should rotate to a sibling credential: a hard
|
|
12
12
|
* `401`, a `403` (token valid but access denied — plan, model policy, or org
|
|
13
|
-
* restriction a sibling account may not share),
|
|
14
|
-
*
|
|
13
|
+
* restriction a sibling account may not share), an account-scoped policy
|
|
14
|
+
* denial such as Codex `cyber_policy`, a body-classified usage limit (Codex
|
|
15
|
+
* `usage_limit_reached`, Anthropic account rate-limit, Google
|
|
15
16
|
* `resource_exhausted`, OpenAI `insufficient_quota`, …), or a bare `429`
|
|
16
|
-
* whose payload did not preserve a richer quota code.
|
|
17
|
-
*
|
|
18
|
-
* upstream-backoff lane.
|
|
17
|
+
* whose payload did not preserve a richer quota code. Transient 429s
|
|
18
|
+
* (`Too many requests`, per-minute caps) stay in the upstream-backoff lane.
|
|
19
19
|
*/
|
|
20
20
|
export declare function isAuthRetryableError(error: unknown): boolean;
|
|
@@ -13,7 +13,11 @@ export type AwsCredentialsErrorKind =
|
|
|
13
13
|
/** STS web-identity exchange failed or returned malformed credentials. */
|
|
14
14
|
| "web-identity"
|
|
15
15
|
/** ECS/container credential endpoint failed or returned malformed credentials. */
|
|
16
|
-
| "container"
|
|
16
|
+
| "container"
|
|
17
|
+
/** Shared-config role chain is misconfigured (cycle, missing source_profile, unsupported credential_source). */
|
|
18
|
+
| "profile"
|
|
19
|
+
/** STS `AssumeRole` call failed or returned malformed credentials. */
|
|
20
|
+
| "assume-role";
|
|
17
21
|
/** A failure resolving AWS credentials for the Bedrock provider. */
|
|
18
22
|
export declare class AwsCredentialsError extends Error {
|
|
19
23
|
readonly kind: AwsCredentialsErrorKind;
|
|
@@ -9,6 +9,8 @@ export declare const Flag: {
|
|
|
9
9
|
readonly MalformedFunctionCall: 2097152;
|
|
10
10
|
readonly ProviderFinishError: 4194304;
|
|
11
11
|
readonly ContentBlocked: 32768;
|
|
12
|
+
/** Account-scoped provider policy denial that may succeed with another credential. */
|
|
13
|
+
readonly AccountPolicy: 16384;
|
|
12
14
|
readonly ContextOverflow: 8388608;
|
|
13
15
|
readonly AuthFailed: 16777216;
|
|
14
16
|
readonly SilentAbort: 33554432;
|
|
@@ -53,6 +55,8 @@ export declare function classify(error: unknown, api?: Api): number;
|
|
|
53
55
|
* regexes at call sites.
|
|
54
56
|
*/
|
|
55
57
|
export declare function isUsageLimit(error: unknown, api?: Api): boolean;
|
|
58
|
+
/** Whether an upstream rejection is an account-scoped policy denial worth retrying with a sibling credential. */
|
|
59
|
+
export declare function isAccountPolicyError(error: unknown, api?: Api): boolean;
|
|
56
60
|
/**
|
|
57
61
|
* Strict-tool rejection: grammar too large, schema too complex, or structured
|
|
58
62
|
* outputs unsupported by the model/endpoint.
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* 1. Static credentials from the environment
|
|
6
6
|
* (`AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY` [+ `AWS_SESSION_TOKEN`]).
|
|
7
7
|
* 2. Web identity (`AWS_WEB_IDENTITY_TOKEN_FILE` + `AWS_ROLE_ARN`).
|
|
8
|
-
* 3. Profile in `~/.aws/credentials` (and `~/.aws/config` for SSO):
|
|
9
|
-
* - static keys, SSO, or `
|
|
8
|
+
* 3. Profile in `~/.aws/credentials` (and `~/.aws/config` for SSO/roles):
|
|
9
|
+
* - static keys, SSO, `credential_process`, or `role_arn` role chaining
|
|
10
|
+
* (`source_profile` recursion, `web_identity_token_file`, `credential_source`).
|
|
10
11
|
* 4. ECS/container credentials from `AWS_CONTAINER_CREDENTIALS_*`.
|
|
11
12
|
* 5. EC2 IMDSv2 when metadata is enabled.
|
|
12
13
|
*
|
|
@@ -14,7 +15,7 @@
|
|
|
14
15
|
* 60 s before `Expiration` to absorb clock skew.
|
|
15
16
|
*/
|
|
16
17
|
import type { FetchImpl } from "../types.js";
|
|
17
|
-
import type
|
|
18
|
+
import { type AwsCredentials } from "./aws-sigv4.js";
|
|
18
19
|
export interface ResolvedCredentials extends AwsCredentials {
|
|
19
20
|
/** Absolute expiration timestamp in ms. `undefined` for non-expiring static creds. */
|
|
20
21
|
expiresAt?: number;
|
|
@@ -17,7 +17,7 @@ import type { ToolResultMessage } from "../../types.js";
|
|
|
17
17
|
* virtual registry. Re-exported here because this is where the frame builders
|
|
18
18
|
* and their translation are consumed together.
|
|
19
19
|
*/
|
|
20
|
-
export { piEscapeRegexLiteral, piGrepSkip, piJoinPath, piLimit, piLsPath, piReadDisplayPath, piReadPath, piReadPathHasRange, piTimeout, } from "../cursor-pi-args.js";
|
|
20
|
+
export { omitUndefinedArgs, piEscapeRegexLiteral, piGrepSkip, piJoinPath, piLimit, piLsPath, piReadDisplayPath, piReadPath, piReadPathHasRange, piTimeout, } from "../cursor-pi-args.js";
|
|
21
21
|
/** Flatten a tool result's content into the single `output` string the Pi frames carry. */
|
|
22
22
|
export declare function piOutputText(toolResult: ToolResultMessage): string;
|
|
23
23
|
/**
|
|
@@ -103,3 +103,17 @@ export declare function piLimit(limit: number | undefined): number | undefined;
|
|
|
103
103
|
* Negative values have no local meaning and fall back to the default.
|
|
104
104
|
*/
|
|
105
105
|
export declare function piTimeout(timeout: number | undefined): number | undefined;
|
|
106
|
+
/**
|
|
107
|
+
* Drop keys whose value is `undefined` so optional local-tool kwargs stay
|
|
108
|
+
* absent rather than present-as-undefined.
|
|
109
|
+
*
|
|
110
|
+
* The Cursor exec bridge historically wrote forms like
|
|
111
|
+
* `cwd: workingDirectory || undefined` and
|
|
112
|
+
* `case: caseInsensitive === true ? false : undefined`. ArkType rejects a
|
|
113
|
+
* present `undefined` on an optional field (`was undefined`) even though
|
|
114
|
+
* omitting the key is valid — which flooded Cursor sessions with bash/grep
|
|
115
|
+
* validation errors for otherwise fine frames.
|
|
116
|
+
*/
|
|
117
|
+
export declare function omitUndefinedArgs<T extends Record<string, unknown>>(args: T): {
|
|
118
|
+
[K in keyof T]?: Exclude<T[K], undefined>;
|
|
119
|
+
};
|
|
@@ -419,6 +419,14 @@ export interface BuildResponsesInputOptions<TApi extends Api> {
|
|
|
419
419
|
repairOrphanOutputs?: boolean;
|
|
420
420
|
/** Preserve assistant message item IDs from text signatures during fallback replay. */
|
|
421
421
|
preserveAssistantMessageIds?: boolean;
|
|
422
|
+
/**
|
|
423
|
+
* Synthesize a reasoning item for every replayed assistant turn that carries
|
|
424
|
+
* content but no reasoning item. Set for DeepSeek-family Responses targets
|
|
425
|
+
* that reject a thinking-mode continuation lacking `reasoning_text`.
|
|
426
|
+
*/
|
|
427
|
+
requiresReasoningReplayForAllTurns?: boolean;
|
|
428
|
+
/** As {@link requiresReasoningReplayForAllTurns}, but only for turns that contain a tool call. */
|
|
429
|
+
requiresReasoningReplayForToolCalls?: boolean;
|
|
422
430
|
}
|
|
423
431
|
/**
|
|
424
432
|
* Escape reserved Harmony control tokens in the free-text fields of replayed
|
|
@@ -443,7 +451,7 @@ export interface BuildResponsesInputOptions<TApi extends Api> {
|
|
|
443
451
|
*/
|
|
444
452
|
export declare function escapeReplayedControlTokens(items: ResponseInput): ResponseInput;
|
|
445
453
|
export declare function buildResponsesInput<TApi extends Api>(options: BuildResponsesInputOptions<TApi>): ResponseInput;
|
|
446
|
-
export declare function convertResponsesAssistantMessage<TApi extends Api>(assistantMsg: AssistantMessage, model: Model<TApi>, msgIndex: number, knownCallIds: Set<string>, includeThinkingSignatures?: boolean, customCallIds?: Set<string>, preserveMessageIds?: boolean, supportsCustomToolCalls?: boolean, customToolWireNameMap?: ReadonlyMap<string, string>, computerCallIds?: Set<string
|
|
454
|
+
export declare function convertResponsesAssistantMessage<TApi extends Api>(assistantMsg: AssistantMessage, model: Model<TApi>, msgIndex: number, knownCallIds: Set<string>, includeThinkingSignatures?: boolean, customCallIds?: Set<string>, preserveMessageIds?: boolean, supportsCustomToolCalls?: boolean, customToolWireNameMap?: ReadonlyMap<string, string>, computerCallIds?: Set<string>, requiresReasoningReplayForAllTurns?: boolean, requiresReasoningReplayForToolCalls?: boolean): ResponseInput;
|
|
447
455
|
/** Appends one tool result while keeping consecutive outputs ahead of its synthetic image messages. */
|
|
448
456
|
export declare function appendResponsesToolResultMessages<TApi extends Api>(messages: ResponseInput, toolResult: ToolResultMessage, model: Model<TApi>, strictResponsesPairing: boolean, supportsImageDetailOriginal: boolean, knownCallIds: ReadonlySet<string>, customCallIds?: ReadonlySet<string>, supportsCustomToolCalls?: boolean, computerCallIds?: ReadonlySet<string>): void;
|
|
449
457
|
/**
|
|
@@ -27,16 +27,18 @@ type ModelsEndpointValidation = {
|
|
|
27
27
|
export type ApiKeyLoginConfig = {
|
|
28
28
|
/** Display name used in error messages, e.g. "Cerebras", "NanoGPT". */
|
|
29
29
|
providerLabel: string;
|
|
30
|
-
/** URL opened in browser for the user to grab their key. */
|
|
31
|
-
authUrl
|
|
32
|
-
/** Instructions shown with the onAuth callback. */
|
|
33
|
-
instructions
|
|
30
|
+
/** URL opened in browser for the user to grab their key, or omitted to skip onAuth. */
|
|
31
|
+
authUrl?: string;
|
|
32
|
+
/** Instructions shown with the onAuth callback, or omitted to skip onAuth. */
|
|
33
|
+
instructions?: string;
|
|
34
34
|
/** Prompt message shown when asking for the key paste. */
|
|
35
35
|
promptMessage: string;
|
|
36
36
|
/** Placeholder string for the prompt (e.g. "sk-...", "csk-..."). */
|
|
37
37
|
placeholder: string;
|
|
38
38
|
/** Validation strategy, or `null` to skip validation. */
|
|
39
39
|
validation: ChatCompletionsValidation | AnthropicMessagesValidation | ModelsEndpointValidation | null;
|
|
40
|
+
/** Value returned for an empty key; also allows an empty prompt response. */
|
|
41
|
+
emptyKeyFallback?: string;
|
|
40
42
|
};
|
|
41
43
|
export declare function createApiKeyLogin(config: ApiKeyLoginConfig): (options: OAuthController) => Promise<string>;
|
|
42
44
|
export {};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { OAuthLoginCallbacks } from "./oauth/types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Login to Cloudflare AI Gateway.
|
|
4
4
|
*
|
|
5
5
|
* Opens browser to Cloudflare AI Gateway authentication docs and prompts for a gateway token/API key.
|
|
6
6
|
* Returns the API key directly (not OAuthCredentials - this isn't OAuth).
|
|
7
7
|
*/
|
|
8
|
-
export declare
|
|
8
|
+
export declare const loginCloudflareAiGateway: (options: import("./oauth/index.js").OAuthController) => Promise<string>;
|
|
9
9
|
export declare const cloudflareAiGatewayProvider: {
|
|
10
10
|
readonly id: "cloudflare-ai-gateway";
|
|
11
11
|
readonly name: "Cloudflare AI Gateway";
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { OAuthLoginCallbacks } from "./oauth/types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Login to Kagi.
|
|
4
4
|
*
|
|
5
5
|
* Opens browser to API settings and prompts user to paste their API key.
|
|
6
6
|
* Returns the API key directly (not OAuthCredentials - this isn't OAuth).
|
|
7
7
|
*/
|
|
8
|
-
export declare
|
|
8
|
+
export declare const loginKagi: (options: import("./oauth/index.js").OAuthController) => Promise<string>;
|
|
9
9
|
export declare const kagiProvider: {
|
|
10
10
|
readonly id: "kagi";
|
|
11
11
|
readonly name: "Kagi";
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { OAuthLoginCallbacks } from "./oauth/types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Login to LiteLLM.
|
|
4
4
|
*
|
|
5
5
|
* Opens browser to LiteLLM setup docs, prompts user to paste their API key.
|
|
6
6
|
* Returns the API key directly (not OAuthCredentials - this isn't OAuth).
|
|
7
7
|
*/
|
|
8
|
-
export declare
|
|
8
|
+
export declare const loginLiteLLM: (options: import("./oauth/index.js").OAuthController) => Promise<string>;
|
|
9
9
|
export declare const litellmProvider: {
|
|
10
10
|
readonly id: "litellm";
|
|
11
11
|
readonly name: "LiteLLM";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare
|
|
1
|
+
import type { OAuthLoginCallbacks } from "./oauth/types.js";
|
|
2
|
+
export declare const loginLlamaCpp: (options: import("./oauth/index.js").OAuthController) => Promise<string>;
|
|
3
3
|
export declare const llamaCppProvider: {
|
|
4
4
|
readonly id: "llama.cpp";
|
|
5
5
|
readonly name: "llama.cpp (Local OpenAI-compatible)";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { OAuthLoginCallbacks } from "./oauth/types.js";
|
|
2
2
|
export declare const DEFAULT_LOCAL_TOKEN = "lm-studio-local";
|
|
3
|
-
export declare
|
|
3
|
+
export declare const loginLmStudio: (options: import("./oauth/index.js").OAuthController) => Promise<string>;
|
|
4
4
|
export declare const lmStudioProvider: {
|
|
5
5
|
readonly id: "lm-studio";
|
|
6
6
|
readonly name: "LM Studio (Local OpenAI-compatible)";
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { OAuthLoginCallbacks } from "./oauth/types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Login to Parallel.
|
|
4
4
|
*
|
|
5
5
|
* Opens browser to the API keys page, prompts the user to paste their API key,
|
|
6
6
|
* and returns the API key directly.
|
|
7
7
|
*/
|
|
8
|
-
export declare
|
|
8
|
+
export declare const loginParallel: (options: import("./oauth/index.js").OAuthController) => Promise<string>;
|
|
9
9
|
export declare const parallelProvider: {
|
|
10
10
|
readonly id: "parallel";
|
|
11
11
|
readonly name: "Parallel";
|
|
@@ -5,7 +5,7 @@ import type { OAuthLoginCallbacks } from "./oauth/types.js";
|
|
|
5
5
|
* Opens browser to API keys page and prompts user to paste their API key.
|
|
6
6
|
* Returns the API key directly (not OAuthCredentials - this isn't OAuth).
|
|
7
7
|
*/
|
|
8
|
-
export declare
|
|
8
|
+
export declare const loginTavily: (options: import("./oauth/index.js").OAuthController) => Promise<string>;
|
|
9
9
|
export declare const tavilyProvider: {
|
|
10
10
|
readonly id: "tavily";
|
|
11
11
|
readonly name: "Tavily";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare
|
|
1
|
+
import type { OAuthLoginCallbacks } from "./oauth/types.js";
|
|
2
|
+
export declare const loginVercelAiGateway: (options: import("./oauth/index.js").OAuthController) => Promise<string>;
|
|
3
3
|
export declare const vercelAiGatewayProvider: {
|
|
4
4
|
readonly id: "vercel-ai-gateway";
|
|
5
5
|
readonly name: "Vercel AI Gateway";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare
|
|
1
|
+
import type { OAuthLoginCallbacks } from "./oauth/types.js";
|
|
2
|
+
export declare const loginVllm: (options: import("./oauth/index.js").OAuthController) => Promise<string>;
|
|
3
3
|
export declare const vllmProvider: {
|
|
4
4
|
readonly id: "vllm";
|
|
5
5
|
readonly name: "vLLM (Local OpenAI-compatible)";
|
package/dist/types/types.d.ts
CHANGED
|
@@ -368,6 +368,13 @@ export interface StreamOptions {
|
|
|
368
368
|
* Optional retry delay hook for tests and transports that need custom scheduling.
|
|
369
369
|
*/
|
|
370
370
|
providerRetryWait?: (delayMs: number, signal?: AbortSignal) => Promise<void>;
|
|
371
|
+
/**
|
|
372
|
+
* Accept a Google `STOP` response with no visible text or tool call as a
|
|
373
|
+
* successful completion. Passive callers such as advisors use this because
|
|
374
|
+
* silence is a valid result; interactive agent turns retain empty-response
|
|
375
|
+
* retries by default. Ignored by non-Google providers.
|
|
376
|
+
*/
|
|
377
|
+
acceptEmptyResponse?: boolean;
|
|
371
378
|
/**
|
|
372
379
|
* Optional `fetch` implementation override. Providers route every HTTP
|
|
373
380
|
* request — direct calls, SDK clients, and retry helpers — through this
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import type { UsageProvider, UsageReport } from "../usage.js";
|
|
2
|
+
/**
|
|
3
|
+
* Cursor's `/api/usage-summary` has shipped two personal-bucket shapes:
|
|
4
|
+
* - Enterprise/team dashboards historically exposed `individualUsage.overall`
|
|
5
|
+
* - Current Pro / Pro+ / Ultra dashboards expose `individualUsage.plan`
|
|
6
|
+
* (plus optional `onDemand`)
|
|
7
|
+
*
|
|
8
|
+
* Prefer a *usable* overall bucket; if overall is absent/disabled/malformed,
|
|
9
|
+
* fall through to plan rails (`autoPercentUsed` / `apiPercentUsed`). Always
|
|
10
|
+
* consider on-demand afterward so a valid on-demand meter is not dropped when
|
|
11
|
+
* the included plan bucket is empty.
|
|
12
|
+
*/
|
|
2
13
|
export declare function parseCursorIndividualUsage(payload: unknown, fetchedAt?: number): UsageReport | null;
|
|
3
14
|
export declare function parseCursorUsage(payload: unknown, fetchedAt?: number): UsageReport | null;
|
|
4
15
|
export declare const cursorUsageProvider: UsageProvider;
|
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OpenAI Codex "saved rate limit reset" redemption client.
|
|
3
|
-
*
|
|
4
|
-
* OpenAI lets paid Codex accounts bank a usage-window reset and spend it on
|
|
5
|
-
* demand (announced 2026-06-11). The count is surfaced on `/wham/usage` as
|
|
6
|
-
* `rate_limit_reset_credits.available_count` (see `./openai-codex.ts`), but the
|
|
7
|
-
* actual credit objects and the redeem action live on two dedicated routes:
|
|
8
|
-
*
|
|
9
|
-
* GET /wham/rate-limit-reset-credits → list redeemable credits
|
|
10
|
-
* POST /wham/rate-limit-reset-credits/consume → spend one credit
|
|
11
|
-
* body: { credit_id, redeem_request_id, account_id? }
|
|
12
|
-
*
|
|
13
|
-
* `redeem_request_id` is a client-generated idempotency key (UUID). The consume
|
|
14
|
-
* response carries a `code`: `"reset"` on success, otherwise a business reason
|
|
15
|
-
* (`already_redeemed`, `no_credit`, `nothing_to_reset`).
|
|
16
|
-
*
|
|
17
|
-
* These are thin, dependency-light functions so both the interactive session
|
|
18
|
-
* (the `/usage reset` command + auto-redeem) and any out-of-band tooling can
|
|
19
|
-
* share one wire contract.
|
|
20
|
-
*/
|
|
21
1
|
import type { FetchImpl } from "../types.js";
|
|
22
2
|
/** A single redeemable (or already-spent) saved reset. */
|
|
23
3
|
export interface CodexResetCredit {
|
|
@@ -1 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
import type { UsageStatus } from "../usage.js";
|
|
2
|
+
/** Milliseconds in one hour. */
|
|
3
|
+
export declare const HOUR_MS: number;
|
|
4
|
+
/** Milliseconds in one day. */
|
|
5
|
+
export declare const DAY_MS: number;
|
|
6
|
+
/** Milliseconds in one seven-day week. */
|
|
7
|
+
export declare const WEEK_MS: number;
|
|
8
|
+
/** Parses a finite positive epoch timestamp, tolerating seconds or milliseconds. */
|
|
9
|
+
export declare function parsePositiveTimestamp(value: unknown): number | undefined;
|
|
10
|
+
/** Parses an ISO timestamp into epoch milliseconds. */
|
|
11
|
+
export declare function parseIsoTimestamp(value: unknown): number | undefined;
|
|
12
|
+
/** Maps a used fraction to the standard quota status thresholds. */
|
|
13
|
+
export declare function usageStatus(usedFraction: number | undefined): UsageStatus;
|
|
@@ -45,6 +45,18 @@ export declare const kCursorExecResolved: unique symbol;
|
|
|
45
45
|
export type CursorExecResolvedCarrier = object & {
|
|
46
46
|
[kCursorExecResolved]?: true;
|
|
47
47
|
};
|
|
48
|
+
/** True when a toolCall block was already executed by Cursor's exec channel. */
|
|
49
|
+
export declare function isCursorExecResolved(block: CursorExecResolvedCarrier | null | undefined): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Copy {@link kCursorExecResolved} onto a cloned/projected toolCall block.
|
|
52
|
+
*
|
|
53
|
+
* Stream projectors (owned/in-band dialect, leaked-thinking heal) rebuild
|
|
54
|
+
* toolCall objects field-by-field. Dropping this marker lets `agent-loop.ts`
|
|
55
|
+
* re-execute a call Cursor already settled — duplicate toolResults and a
|
|
56
|
+
* second bash/write/delete. Partial-JSON is already copied explicitly; this
|
|
57
|
+
* marker is the other load-bearing symbol that must survive the same way.
|
|
58
|
+
*/
|
|
59
|
+
export declare function copyCursorExecResolved(target: CursorExecResolvedCarrier, source: CursorExecResolvedCarrier): void;
|
|
48
60
|
/**
|
|
49
61
|
* Marks a text block synthesized by cross-model thinking demotion in
|
|
50
62
|
* `transformMessages`. Converters that flatten adjacent text blocks into one
|
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.2.
|
|
4
|
+
"version": "17.2.13",
|
|
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,10 +38,10 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@bufbuild/protobuf": "^2.12.1",
|
|
41
|
-
"@oh-my-pi/omptype": "17.2.
|
|
42
|
-
"@oh-my-pi/pi-catalog": "17.2.
|
|
43
|
-
"@oh-my-pi/pi-utils": "17.2.
|
|
44
|
-
"@oh-my-pi/pi-wire": "17.2.
|
|
41
|
+
"@oh-my-pi/omptype": "17.2.13",
|
|
42
|
+
"@oh-my-pi/pi-catalog": "17.2.13",
|
|
43
|
+
"@oh-my-pi/pi-utils": "17.2.13",
|
|
44
|
+
"@oh-my-pi/pi-wire": "17.2.13"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@bufbuild/protoc-gen-es": "^2.12.1",
|
package/src/auth-retry.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { extractHttpStatusFromError } from "@oh-my-pi/pi-utils";
|
|
|
2
2
|
import type { OAuthAccess } from "./auth-storage";
|
|
3
3
|
import * as AIError from "./error";
|
|
4
4
|
import { isAuthRetryableError, isInvalidatedOAuthTokenError } from "./error/auth-classify";
|
|
5
|
-
import { isUsageLimit } from "./error/flags";
|
|
5
|
+
import { isAccountPolicyError, isUsageLimit } from "./error/flags";
|
|
6
6
|
import { isConcurrencyCapExclusion, isUsageLimitOutcome } from "./error/rate-limit";
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -19,9 +19,9 @@ import { isConcurrencyCapExclusion, isUsageLimitOutcome } from "./error/rate-lim
|
|
|
19
19
|
* (invalidate/usage-limit the current credential and rotate to a sibling).
|
|
20
20
|
*
|
|
21
21
|
* Current drivers preserve that bounded a/b/c sequence for ordinary 401/auth
|
|
22
|
-
* failures.
|
|
23
|
-
* until the resolver returns `undefined`,
|
|
24
|
-
* {@link AUTH_RETRY_MAX_ATTEMPTS}.
|
|
22
|
+
* failures. Account-scoped policy denials, 403s, and usage-limit failures skip
|
|
23
|
+
* refresh and may repeat step (c) until the resolver returns `undefined`,
|
|
24
|
+
* cycles, or hits {@link AUTH_RETRY_MAX_ATTEMPTS}.
|
|
25
25
|
*/
|
|
26
26
|
export interface ApiKeyResolveContext {
|
|
27
27
|
/** True when the resolver should rotate to a sibling credential. */
|
|
@@ -91,6 +91,7 @@ export const AUTH_RETRY_STEPS: readonly boolean[] = [false, true];
|
|
|
91
91
|
export const AUTH_RETRY_MAX_ATTEMPTS = 64;
|
|
92
92
|
|
|
93
93
|
function isDirectCredentialRotationError(error: unknown): boolean {
|
|
94
|
+
if (isAccountPolicyError(error)) return true;
|
|
94
95
|
if (isUsageLimit(error) || isInvalidatedOAuthTokenError(error)) return true;
|
|
95
96
|
const status = AIError.status(error);
|
|
96
97
|
const message = error instanceof Error ? error.message : typeof error === "string" ? error : undefined;
|
package/src/auth-storage.ts
CHANGED
|
@@ -1211,6 +1211,14 @@ type OAuthCandidate = UsageCandidate<OAuthCredential>;
|
|
|
1211
1211
|
type ApiKeyCandidate = UsageCandidate<ApiKeyCredential>;
|
|
1212
1212
|
type UsageRankingResult<T extends AuthCredential> = UsageCandidate<T> & { blockedUntil: number | undefined };
|
|
1213
1213
|
|
|
1214
|
+
type CredentialBlockRouting = {
|
|
1215
|
+
providerKey: string;
|
|
1216
|
+
strategy: CredentialRankingStrategy | undefined;
|
|
1217
|
+
rankingContext: CredentialRankingContext;
|
|
1218
|
+
blockScope: string | undefined;
|
|
1219
|
+
siblingBlockScopes: readonly string[];
|
|
1220
|
+
};
|
|
1221
|
+
|
|
1214
1222
|
type UsageRankedCandidate<T extends AuthCredential> = UsageCandidate<T> & {
|
|
1215
1223
|
blocked: boolean;
|
|
1216
1224
|
blockedUntil?: number;
|
|
@@ -4271,6 +4279,58 @@ export class AuthStorage {
|
|
|
4271
4279
|
return sessionCredential ? { ...sessionCredential, explicit: false } : undefined;
|
|
4272
4280
|
}
|
|
4273
4281
|
|
|
4282
|
+
#credentialBlockRouting(
|
|
4283
|
+
provider: string,
|
|
4284
|
+
credentialType: AuthCredential["type"],
|
|
4285
|
+
modelId: string | undefined,
|
|
4286
|
+
): CredentialBlockRouting {
|
|
4287
|
+
const providerKey = this.#getProviderTypeKey(provider, credentialType);
|
|
4288
|
+
const strategy = this.#rankingStrategyResolver?.(provider);
|
|
4289
|
+
const rankingContext: CredentialRankingContext = { modelId };
|
|
4290
|
+
const blockScope = strategy?.blockScope?.(rankingContext);
|
|
4291
|
+
return {
|
|
4292
|
+
providerKey,
|
|
4293
|
+
strategy,
|
|
4294
|
+
rankingContext,
|
|
4295
|
+
blockScope,
|
|
4296
|
+
siblingBlockScopes: strategy?.blockScopes?.(rankingContext) ?? (blockScope ? [blockScope] : []),
|
|
4297
|
+
};
|
|
4298
|
+
}
|
|
4299
|
+
|
|
4300
|
+
#blockCredentialForRotation(
|
|
4301
|
+
provider: string,
|
|
4302
|
+
credentialType: AuthCredential["type"],
|
|
4303
|
+
targetIndex: number,
|
|
4304
|
+
blockedUntil: number,
|
|
4305
|
+
routing: CredentialBlockRouting,
|
|
4306
|
+
): UsageLimitMarkResult {
|
|
4307
|
+
if (targetIndex >= 0) {
|
|
4308
|
+
this.#markCredentialBlocked(provider, routing.providerKey, targetIndex, blockedUntil, routing.blockScope);
|
|
4309
|
+
}
|
|
4310
|
+
|
|
4311
|
+
const remainingCredentials = this.#getCredentialsForProvider(provider)
|
|
4312
|
+
.map((credential, index) => ({ credential, index }))
|
|
4313
|
+
.filter(
|
|
4314
|
+
(entry): entry is { credential: AuthCredential; index: number } =>
|
|
4315
|
+
entry.credential.type === credentialType && entry.index !== targetIndex,
|
|
4316
|
+
);
|
|
4317
|
+
|
|
4318
|
+
let retryAtMs: number | undefined;
|
|
4319
|
+
for (const candidate of remainingCredentials) {
|
|
4320
|
+
// Sibling availability must use the same scope set selection reads, or
|
|
4321
|
+
// this reports a sibling as free that selection will then refuse.
|
|
4322
|
+
const candidateBlockedUntil = this.#getCredentialBlockedUntil(
|
|
4323
|
+
provider,
|
|
4324
|
+
routing.providerKey,
|
|
4325
|
+
candidate.index,
|
|
4326
|
+
routing.siblingBlockScopes,
|
|
4327
|
+
);
|
|
4328
|
+
if (candidateBlockedUntil === undefined) return { switched: true };
|
|
4329
|
+
if (retryAtMs === undefined || candidateBlockedUntil < retryAtMs) retryAtMs = candidateBlockedUntil;
|
|
4330
|
+
}
|
|
4331
|
+
return { switched: false, retryAtMs };
|
|
4332
|
+
}
|
|
4333
|
+
|
|
4274
4334
|
/**
|
|
4275
4335
|
* Marks the current session's credential as temporarily blocked due to usage limits.
|
|
4276
4336
|
* Uses usage reports to determine accurate reset time when available.
|
|
@@ -4313,21 +4373,17 @@ export class AuthStorage {
|
|
|
4313
4373
|
const credentialType = sessionCredential.type;
|
|
4314
4374
|
const targetCredentialId = target.id;
|
|
4315
4375
|
|
|
4316
|
-
const
|
|
4317
|
-
const strategy = this.#rankingStrategyResolver?.(provider);
|
|
4318
|
-
const rankingContext: CredentialRankingContext = { modelId: options?.modelId };
|
|
4319
|
-
const blockScope = strategy?.blockScope?.(rankingContext);
|
|
4320
|
-
const siblingBlockScopes = strategy?.blockScopes?.(rankingContext) ?? (blockScope ? [blockScope] : []);
|
|
4376
|
+
const routing = this.#credentialBlockRouting(provider, credentialType, options?.modelId);
|
|
4321
4377
|
const now = Date.now();
|
|
4322
4378
|
let blockedUntil = now + (options?.retryAfterMs ?? AuthStorage.#defaultBackoffMs);
|
|
4323
4379
|
|
|
4324
|
-
if (credentialType === "oauth" && target.credential.type === "oauth" && strategy) {
|
|
4380
|
+
if (credentialType === "oauth" && target.credential.type === "oauth" && routing.strategy) {
|
|
4325
4381
|
const report = await raceUsageWithSignal(
|
|
4326
4382
|
this.#getUsageReport(provider, target.credential, options),
|
|
4327
4383
|
options?.signal,
|
|
4328
4384
|
);
|
|
4329
4385
|
if (report) {
|
|
4330
|
-
const scopedLimits = this.#getScopedUsageLimits(strategy, report, rankingContext);
|
|
4386
|
+
const scopedLimits = this.#getScopedUsageLimits(routing.strategy, report, routing.rankingContext);
|
|
4331
4387
|
if (this.#isUsageLimitReached(scopedLimits)) {
|
|
4332
4388
|
const resetAtMs = this.#getUsageResetAtMs(scopedLimits, Date.now());
|
|
4333
4389
|
if (resetAtMs && resetAtMs > blockedUntil) {
|
|
@@ -4343,32 +4399,7 @@ export class AuthStorage {
|
|
|
4343
4399
|
const targetIndex = this.#getStoredCredentials(provider).findIndex(
|
|
4344
4400
|
entry => entry.id === targetCredentialId && entry.credential.type === credentialType,
|
|
4345
4401
|
);
|
|
4346
|
-
|
|
4347
|
-
this.#markCredentialBlocked(provider, providerKey, targetIndex, blockedUntil, blockScope);
|
|
4348
|
-
}
|
|
4349
|
-
|
|
4350
|
-
const remainingCredentials = this.#getCredentialsForProvider(provider)
|
|
4351
|
-
.map((credential, index) => ({ credential, index }))
|
|
4352
|
-
.filter(
|
|
4353
|
-
(entry): entry is { credential: AuthCredential; index: number } =>
|
|
4354
|
-
entry.credential.type === credentialType && entry.index !== targetIndex,
|
|
4355
|
-
);
|
|
4356
|
-
|
|
4357
|
-
let retryAtMs: number | undefined;
|
|
4358
|
-
for (const candidate of remainingCredentials) {
|
|
4359
|
-
// Sibling availability must use the same scope set selection reads, or
|
|
4360
|
-
// this reports a sibling as free that selection will then refuse, most
|
|
4361
|
-
// visibly when the sibling still carries a legacy shared block.
|
|
4362
|
-
const candidateBlockedUntil = this.#getCredentialBlockedUntil(
|
|
4363
|
-
provider,
|
|
4364
|
-
providerKey,
|
|
4365
|
-
candidate.index,
|
|
4366
|
-
siblingBlockScopes,
|
|
4367
|
-
);
|
|
4368
|
-
if (candidateBlockedUntil === undefined) return { switched: true };
|
|
4369
|
-
if (retryAtMs === undefined || candidateBlockedUntil < retryAtMs) retryAtMs = candidateBlockedUntil;
|
|
4370
|
-
}
|
|
4371
|
-
return { switched: false, retryAtMs };
|
|
4402
|
+
return this.#blockCredentialForRotation(provider, credentialType, targetIndex, blockedUntil, routing);
|
|
4372
4403
|
}
|
|
4373
4404
|
|
|
4374
4405
|
#resolveWindowResetAt(window: UsageLimit["window"]): number | undefined {
|
|
@@ -6078,6 +6109,8 @@ export class AuthStorage {
|
|
|
6078
6109
|
* - usage-limit / account-rate-limit error → {@link AuthStorage.markUsageLimitReached}
|
|
6079
6110
|
* (temporary block via its own backoff — default plus server usage-report
|
|
6080
6111
|
* reset; sticky left intact so the next resolve re-ranks around the block).
|
|
6112
|
+
* - account-scoped policy denial → temporarily block that account without
|
|
6113
|
+
* marking its credential suspect, then rotate through eligible siblings.
|
|
6081
6114
|
* - otherwise (hard 401 / auth failure) → mark the credential suspect (or
|
|
6082
6115
|
* reload when no broker hook is wired) and block it, then drop matching
|
|
6083
6116
|
* sticky state.
|
|
@@ -6114,6 +6147,17 @@ export class AuthStorage {
|
|
|
6114
6147
|
});
|
|
6115
6148
|
if (!sessionCredential) return false;
|
|
6116
6149
|
|
|
6150
|
+
if (AIError.isAccountPolicyError(error)) {
|
|
6151
|
+
const routing = this.#credentialBlockRouting(provider, sessionCredential.type, options?.modelId);
|
|
6152
|
+
return this.#blockCredentialForRotation(
|
|
6153
|
+
provider,
|
|
6154
|
+
sessionCredential.type,
|
|
6155
|
+
sessionCredential.index,
|
|
6156
|
+
Date.now() + AuthStorage.#defaultBackoffMs,
|
|
6157
|
+
routing,
|
|
6158
|
+
).switched;
|
|
6159
|
+
}
|
|
6160
|
+
|
|
6117
6161
|
const providerKey = this.#getProviderTypeKey(provider, sessionCredential.type);
|
|
6118
6162
|
// Snapshot sibling availability before mutating so a soft-deleting
|
|
6119
6163
|
// suspect hook can't reindex the answer out from under us.
|