@sayknow-cli/ai 0.3.8 → 0.3.10
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/dist/types/auth-storage.d.ts +16 -0
- package/dist/types/provider-models/openai-compat.d.ts +5 -0
- package/dist/types/providers/cursor/client-version.d.ts +10 -0
- package/dist/types/providers/cursor.d.ts +2 -1
- package/dist/types/types.d.ts +5 -6
- package/dist/types/utils/oauth/types.d.ts +1 -1
- package/package.json +2 -2
- package/src/auth-storage.ts +134 -12
- package/src/models.json +3589 -673
- package/src/provider-models/descriptors.ts +7 -0
- package/src/provider-models/openai-compat.ts +13 -0
- package/src/providers/cursor/client-version.ts +10 -0
- package/src/providers/cursor.ts +2 -1
- package/src/providers/google-gemini-headers.ts +1 -1
- package/src/providers/openai-completions.ts +3 -3
- package/src/rate-limit-utils.ts +4 -2
- package/src/stream.ts +1 -0
- package/src/types.ts +10 -7
- package/src/utils/discovery/cursor.ts +2 -2
- package/src/utils/oauth/fugu.ts +2 -2
- package/src/utils/oauth/index.ts +5 -0
- package/src/utils/oauth/types.ts +1 -0
|
@@ -323,7 +323,14 @@ type AuthApiKeyOptions = {
|
|
|
323
323
|
* stranding the caller for `timeoutMs * (maxRetries + 1)`.
|
|
324
324
|
*/
|
|
325
325
|
signal?: AbortSignal;
|
|
326
|
+
/** Pin selection to one stored credential instead of using round-robin/ranking. */
|
|
327
|
+
credentialSelector?: AuthCredentialSelector;
|
|
326
328
|
};
|
|
329
|
+
export type AuthCredentialSelectorKind = "id" | "email" | "account" | "project";
|
|
330
|
+
export interface AuthCredentialSelector {
|
|
331
|
+
kind: AuthCredentialSelectorKind;
|
|
332
|
+
value: string;
|
|
333
|
+
}
|
|
327
334
|
/**
|
|
328
335
|
* Refreshed OAuth access plus identity metadata returned by
|
|
329
336
|
* {@link AuthStorage.getOAuthAccess}. Callers that authenticate via a bearer
|
|
@@ -390,6 +397,15 @@ export declare class AuthStorage {
|
|
|
390
397
|
* Used for CLI --api-key flag.
|
|
391
398
|
*/
|
|
392
399
|
setRuntimeApiKey(provider: string, apiKey: string): void;
|
|
400
|
+
/**
|
|
401
|
+
* Pin credential selection for a provider (not persisted to disk).
|
|
402
|
+
* Used for CLI --credential.
|
|
403
|
+
*/
|
|
404
|
+
setRuntimeCredentialSelector(provider: string, selector: AuthCredentialSelector): void;
|
|
405
|
+
/**
|
|
406
|
+
* Remove a runtime credential selector.
|
|
407
|
+
*/
|
|
408
|
+
removeRuntimeCredentialSelector(provider: string): void;
|
|
393
409
|
/**
|
|
394
410
|
* Remove a runtime API key override.
|
|
395
411
|
*/
|
|
@@ -58,6 +58,11 @@ export interface DeepSeekModelManagerConfig {
|
|
|
58
58
|
baseUrl?: string;
|
|
59
59
|
}
|
|
60
60
|
export declare function deepseekModelManagerOptions(config?: DeepSeekModelManagerConfig): ModelManagerOptions<"openai-completions">;
|
|
61
|
+
export interface DeepInfraModelManagerConfig {
|
|
62
|
+
apiKey?: string;
|
|
63
|
+
baseUrl?: string;
|
|
64
|
+
}
|
|
65
|
+
export declare function deepinfraModelManagerOptions(config?: DeepInfraModelManagerConfig): ModelManagerOptions<"openai-completions">;
|
|
61
66
|
export interface FireworksModelManagerConfig {
|
|
62
67
|
apiKey?: string;
|
|
63
68
|
baseUrl?: string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Cursor client version reported to api2.cursor.sh.
|
|
3
|
+
*
|
|
4
|
+
* Every call against the Cursor backend must send the same
|
|
5
|
+
* x-cursor-client-version: the backend gates features and minimum versions on
|
|
6
|
+
* it, so a drift between the agent Run path and model discovery makes one of
|
|
7
|
+
* them fail while the other keeps working. Keep this as the single source of
|
|
8
|
+
* truth for the header value.
|
|
9
|
+
*/
|
|
10
|
+
export declare const CURSOR_CLIENT_VERSION = "cli-2026.02.13-41ac335";
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type JsonValue } from "@bufbuild/protobuf";
|
|
2
2
|
import type { CursorExecHandlerResult, CursorExecHandlers, CursorToolResultHandler, Message, StreamFunction, StreamOptions, ToolResultMessage } from "../types";
|
|
3
|
+
import { CURSOR_CLIENT_VERSION } from "./cursor/client-version";
|
|
3
4
|
export declare const CURSOR_API_URL = "https://api2.cursor.sh";
|
|
4
|
-
export
|
|
5
|
+
export { CURSOR_CLIENT_VERSION };
|
|
5
6
|
/** Drop all cached state + blob bytes for a conversation (F15 bound + session-teardown hook). */
|
|
6
7
|
export declare function disposeCursorConversation(conversationId: string): void;
|
|
7
8
|
export interface CursorOptions extends StreamOptions {
|
package/dist/types/types.d.ts
CHANGED
|
@@ -48,7 +48,7 @@ export interface ThinkingConfig {
|
|
|
48
48
|
/** Provider-specific transport used to encode the selected effort. */
|
|
49
49
|
mode: ThinkingControlMode;
|
|
50
50
|
}
|
|
51
|
-
export type KnownProvider = "alibaba-coding-plan" | "amazon-bedrock" | "azure-openai" | "anthropic" | "google" | "google-gemini-cli" | "google-antigravity" | "google-vertex" | "openai" | "openai-codex" | "kimi-code" | "minimax-code" | "minimax-code-cn" | "github-copilot" | "fireworks" | "firepass" | "fugu" | "gitlab-duo" | "cursor" | "deepseek" | "xai" | "groq" | "cerebras" | "openrouter" | "kilo" | "vercel-ai-gateway" | "zai" | "glm-zcode" | "mistral" | "minimax" | "opencode-go" | "opencode-zen" | "synthetic" | "cloudflare-ai-gateway" | "huggingface" | "litellm" | "moonshot" | "nvidia" | "nanogpt" | "ollama" | "ollama-cloud" | "qianfan" | "qwen-portal" | "together" | "venice" | "vllm" | "xiaomi" | "xiaomi-token-plan-sgp" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "zenmux" | "lm-studio";
|
|
51
|
+
export type KnownProvider = "alibaba-coding-plan" | "amazon-bedrock" | "azure-openai" | "anthropic" | "google" | "google-gemini-cli" | "google-antigravity" | "google-vertex" | "openai" | "openai-codex" | "kimi-code" | "minimax-code" | "minimax-code-cn" | "github-copilot" | "fireworks" | "firepass" | "fugu" | "gitlab-duo" | "cursor" | "deepseek" | "deepinfra" | "xai" | "groq" | "cerebras" | "openrouter" | "kilo" | "vercel-ai-gateway" | "zai" | "glm-zcode" | "mistral" | "minimax" | "opencode-go" | "opencode-zen" | "synthetic" | "cloudflare-ai-gateway" | "huggingface" | "litellm" | "moonshot" | "nvidia" | "nanogpt" | "ollama" | "ollama-cloud" | "qianfan" | "qwen-portal" | "together" | "venice" | "vllm" | "xiaomi" | "xiaomi-token-plan-sgp" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "zenmux" | "lm-studio";
|
|
52
52
|
export type Provider = KnownProvider | string;
|
|
53
53
|
import type { Effort } from "./model-thinking";
|
|
54
54
|
/** Token budgets for each thinking level (token-based providers only) */
|
|
@@ -84,7 +84,7 @@ export type CacheRetention = "none" | "short" | "long";
|
|
|
84
84
|
*
|
|
85
85
|
* The unscoped values (`"auto"`, `"default"`, `"flex"`, `"scale"`,
|
|
86
86
|
* `"priority"`) are passed through to providers that understand them
|
|
87
|
-
* (OpenAI's `service_tier` field directly; Anthropic translates
|
|
87
|
+
* (OpenAI and DeepInfra's `service_tier` field directly; Anthropic translates
|
|
88
88
|
* `"priority"` into `speed: "fast"` on supported Opus models).
|
|
89
89
|
*
|
|
90
90
|
* The scoped values target a specific provider family and behave as the
|
|
@@ -105,10 +105,9 @@ export type ResolvedServiceTier = Exclude<ServiceTier, "openai-only" | "claude-o
|
|
|
105
105
|
*/
|
|
106
106
|
export declare function resolveServiceTier(serviceTier: ServiceTier | null | undefined, provider: Provider | undefined): ResolvedServiceTier | undefined;
|
|
107
107
|
/**
|
|
108
|
-
* True when the (possibly scoped) tier should be sent as OpenAI
|
|
109
|
-
* `service_tier` request field for
|
|
110
|
-
*
|
|
111
|
-
* mismatches all return false.
|
|
108
|
+
* True when the (possibly scoped) tier should be sent as an OpenAI-compatible
|
|
109
|
+
* `service_tier` request field for providers that support it. Unsupported tiers
|
|
110
|
+
* (`"auto"`, `"default"`) and scope mismatches all return false.
|
|
112
111
|
*/
|
|
113
112
|
export declare function shouldSendServiceTier(serviceTier: ServiceTier | null | undefined, provider: Provider | undefined): boolean;
|
|
114
113
|
/**
|
|
@@ -7,7 +7,7 @@ export type OAuthCredentials = {
|
|
|
7
7
|
email?: string;
|
|
8
8
|
accountId?: string;
|
|
9
9
|
};
|
|
10
|
-
export type OAuthProvider = "alibaba-coding-plan" | "anthropic" | "cerebras" | "cloudflare-ai-gateway" | "cursor" | "deepseek" | "fireworks" | "firepass" | "fugu" | "github-copilot" | "google-gemini-cli" | "google-antigravity" | "gitlab-duo" | "huggingface" | "kimi-code" | "kilo" | "kagi" | "litellm" | "lm-studio" | "minimax-code" | "minimax-code-cn" | "moonshot" | "nvidia" | "nanogpt" | "ollama" | "ollama-cloud" | "openai-codex" | "openai-codex-device" | "opencode-go" | "opencode-zen" | "parallel" | "perplexity" | "qianfan" | "qwen-portal" | "synthetic" | "tavily" | "together" | "venice" | "vercel-ai-gateway" | "vllm" | "xai" | "glm-zcode" | "xiaomi" | "xiaomi-token-plan-sgp" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "zenmux" | "zai";
|
|
10
|
+
export type OAuthProvider = "alibaba-coding-plan" | "anthropic" | "cerebras" | "cloudflare-ai-gateway" | "cursor" | "deepseek" | "deepinfra" | "fireworks" | "firepass" | "fugu" | "github-copilot" | "google-gemini-cli" | "google-antigravity" | "gitlab-duo" | "huggingface" | "kimi-code" | "kilo" | "kagi" | "litellm" | "lm-studio" | "minimax-code" | "minimax-code-cn" | "moonshot" | "nvidia" | "nanogpt" | "ollama" | "ollama-cloud" | "openai-codex" | "openai-codex-device" | "opencode-go" | "opencode-zen" | "parallel" | "perplexity" | "qianfan" | "qwen-portal" | "synthetic" | "tavily" | "together" | "venice" | "vercel-ai-gateway" | "vllm" | "xai" | "glm-zcode" | "xiaomi" | "xiaomi-token-plan-sgp" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "zenmux" | "zai";
|
|
11
11
|
export type OAuthProviderId = OAuthProvider | (string & {});
|
|
12
12
|
export type OAuthPrompt = {
|
|
13
13
|
message: string;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@sayknow-cli/ai",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.10",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
6
|
"homepage": "https://sayknow-cli.com",
|
|
7
7
|
"author": "jaybeyond",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@anthropic-ai/sdk": "^0.94.0",
|
|
45
45
|
"@bufbuild/protobuf": "^2.12.0",
|
|
46
|
-
"@sayknow-cli/utils": "0.3.
|
|
46
|
+
"@sayknow-cli/utils": "0.3.10",
|
|
47
47
|
"openai": "^6.36.0",
|
|
48
48
|
"partial-json": "^0.1.7",
|
|
49
49
|
"zod": "4.4.3"
|
package/src/auth-storage.ts
CHANGED
|
@@ -493,7 +493,16 @@ type AuthApiKeyOptions = {
|
|
|
493
493
|
* stranding the caller for `timeoutMs * (maxRetries + 1)`.
|
|
494
494
|
*/
|
|
495
495
|
signal?: AbortSignal;
|
|
496
|
+
/** Pin selection to one stored credential instead of using round-robin/ranking. */
|
|
497
|
+
credentialSelector?: AuthCredentialSelector;
|
|
496
498
|
};
|
|
499
|
+
export type AuthCredentialSelectorKind = "id" | "email" | "account" | "project";
|
|
500
|
+
|
|
501
|
+
export interface AuthCredentialSelector {
|
|
502
|
+
kind: AuthCredentialSelectorKind;
|
|
503
|
+
value: string;
|
|
504
|
+
}
|
|
505
|
+
|
|
497
506
|
type OAuthResolutionResult = { apiKey: string; credential: OAuthCredential };
|
|
498
507
|
|
|
499
508
|
/**
|
|
@@ -696,6 +705,7 @@ export class AuthStorage {
|
|
|
696
705
|
#data: Map<string, StoredCredential[]> = new Map();
|
|
697
706
|
#runtimeOverrides: Map<string, string> = new Map();
|
|
698
707
|
#configOverrides: Map<string, string> = new Map();
|
|
708
|
+
#runtimeCredentialSelectors: Map<string, AuthCredentialSelector> = new Map();
|
|
699
709
|
/** Tracks next credential index per provider:type key for round-robin distribution (non-session use). */
|
|
700
710
|
#providerRoundRobinIndex: Map<string, number> = new Map();
|
|
701
711
|
/** Tracks the last used credential per provider for a session (used for rate-limit switching). */
|
|
@@ -846,6 +856,23 @@ export class AuthStorage {
|
|
|
846
856
|
this.#runtimeOverrides.set(provider, apiKey);
|
|
847
857
|
}
|
|
848
858
|
|
|
859
|
+
/**
|
|
860
|
+
* Pin credential selection for a provider (not persisted to disk).
|
|
861
|
+
* Used for CLI --credential.
|
|
862
|
+
*/
|
|
863
|
+
setRuntimeCredentialSelector(provider: string, selector: AuthCredentialSelector): void {
|
|
864
|
+
const storageProvider = resolveOAuthStorageProvider(provider);
|
|
865
|
+
this.#assertCredentialSelectorUsable(storageProvider, selector);
|
|
866
|
+
this.#runtimeCredentialSelectors.set(storageProvider, selector);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* Remove a runtime credential selector.
|
|
871
|
+
*/
|
|
872
|
+
removeRuntimeCredentialSelector(provider: string): void {
|
|
873
|
+
this.#runtimeCredentialSelectors.delete(resolveOAuthStorageProvider(provider));
|
|
874
|
+
}
|
|
875
|
+
|
|
849
876
|
/**
|
|
850
877
|
* Remove a runtime API key override.
|
|
851
878
|
*/
|
|
@@ -1122,6 +1149,72 @@ export class AuthStorage {
|
|
|
1122
1149
|
}
|
|
1123
1150
|
}
|
|
1124
1151
|
|
|
1152
|
+
#formatCredentialSelector(selector: AuthCredentialSelector): string {
|
|
1153
|
+
return `${selector.kind}:${selector.value}`;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
#credentialMatchesSelector(entry: StoredCredential, selector: AuthCredentialSelector): boolean {
|
|
1157
|
+
switch (selector.kind) {
|
|
1158
|
+
case "id":
|
|
1159
|
+
return String(entry.id) === selector.value;
|
|
1160
|
+
case "email":
|
|
1161
|
+
return (
|
|
1162
|
+
entry.credential.type === "oauth" &&
|
|
1163
|
+
typeof entry.credential.email === "string" &&
|
|
1164
|
+
entry.credential.email.toLowerCase() === selector.value.toLowerCase()
|
|
1165
|
+
);
|
|
1166
|
+
case "account":
|
|
1167
|
+
return entry.credential.type === "oauth" && entry.credential.accountId === selector.value;
|
|
1168
|
+
case "project":
|
|
1169
|
+
return entry.credential.type === "oauth" && entry.credential.projectId === selector.value;
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
#findCredentialBySelector(
|
|
1174
|
+
provider: string,
|
|
1175
|
+
selector: AuthCredentialSelector,
|
|
1176
|
+
): ({ index: number } & StoredCredential) | undefined {
|
|
1177
|
+
const stored = this.#getStoredCredentials(provider);
|
|
1178
|
+
for (let index = 0; index < stored.length; index++) {
|
|
1179
|
+
const entry = stored[index];
|
|
1180
|
+
if (entry && this.#credentialMatchesSelector(entry, selector)) return { ...entry, index };
|
|
1181
|
+
}
|
|
1182
|
+
return undefined;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
#getCredentialSelector(provider: string, options?: AuthApiKeyOptions): AuthCredentialSelector | undefined {
|
|
1186
|
+
return options?.credentialSelector ?? this.#runtimeCredentialSelectors.get(resolveOAuthStorageProvider(provider));
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
#assertCredentialSelectorUsable(provider: string, selector: AuthCredentialSelector): void {
|
|
1190
|
+
if (this.#runtimeOverrides.has(provider)) {
|
|
1191
|
+
throw new Error(
|
|
1192
|
+
`Credential selector ${this.#formatCredentialSelector(selector)} cannot be used for ${provider} while a runtime API key override is active`,
|
|
1193
|
+
);
|
|
1194
|
+
}
|
|
1195
|
+
if (this.#configOverrides.has(provider)) {
|
|
1196
|
+
throw new Error(
|
|
1197
|
+
`Credential selector ${this.#formatCredentialSelector(selector)} cannot be used for ${provider} while a config API key override is active`,
|
|
1198
|
+
);
|
|
1199
|
+
}
|
|
1200
|
+
if (!this.#findCredentialBySelector(provider, selector)) {
|
|
1201
|
+
throw new Error(`No credential found for ${provider} matching ${this.#formatCredentialSelector(selector)}`);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
#resolveSelectedStoredCredential(
|
|
1206
|
+
provider: string,
|
|
1207
|
+
options?: AuthApiKeyOptions,
|
|
1208
|
+
): ({ index: number } & StoredCredential) | undefined {
|
|
1209
|
+
const selector = this.#getCredentialSelector(provider, options);
|
|
1210
|
+
if (!selector) return undefined;
|
|
1211
|
+
this.#assertCredentialSelectorUsable(resolveOAuthStorageProvider(provider), selector);
|
|
1212
|
+
const selected = this.#findCredentialBySelector(provider, selector);
|
|
1213
|
+
if (!selected) {
|
|
1214
|
+
throw new Error(`No credential found for ${provider} matching ${this.#formatCredentialSelector(selector)}`);
|
|
1215
|
+
}
|
|
1216
|
+
return selected;
|
|
1217
|
+
}
|
|
1125
1218
|
/**
|
|
1126
1219
|
* Selects a credential of the specified type for a provider.
|
|
1127
1220
|
* Returns both the credential and its index in the original array (for updates/removal).
|
|
@@ -2734,17 +2827,28 @@ export class AuthStorage {
|
|
|
2734
2827
|
sessionId?: string,
|
|
2735
2828
|
options?: AuthApiKeyOptions,
|
|
2736
2829
|
): Promise<OAuthResolutionResult | undefined> {
|
|
2737
|
-
const
|
|
2738
|
-
|
|
2739
|
-
|
|
2830
|
+
const selectedCredential = this.#resolveSelectedStoredCredential(provider, options);
|
|
2831
|
+
const selectedOAuthCredential =
|
|
2832
|
+
selectedCredential?.credential.type === "oauth"
|
|
2833
|
+
? { credential: selectedCredential.credential, index: selectedCredential.index }
|
|
2834
|
+
: undefined;
|
|
2835
|
+
if (selectedCredential && !selectedOAuthCredential) return undefined;
|
|
2836
|
+
const credentials = selectedOAuthCredential
|
|
2837
|
+
? [selectedOAuthCredential]
|
|
2838
|
+
: this.#getCredentialsForProvider(provider)
|
|
2839
|
+
.map((credential, index) => ({ credential, index }))
|
|
2840
|
+
.filter(
|
|
2841
|
+
(entry): entry is { credential: OAuthCredential; index: number } => entry.credential.type === "oauth",
|
|
2842
|
+
);
|
|
2740
2843
|
|
|
2741
2844
|
if (credentials.length === 0) return undefined;
|
|
2742
2845
|
|
|
2743
2846
|
const providerKey = this.#getProviderTypeKey(provider, "oauth");
|
|
2744
|
-
const order = this.#getCredentialOrder(providerKey, sessionId, credentials.length);
|
|
2847
|
+
const order = selectedCredential ? [0] : this.#getCredentialOrder(providerKey, sessionId, credentials.length);
|
|
2745
2848
|
const strategy = this.#rankingStrategyResolver?.(provider);
|
|
2746
2849
|
const requiresProModel = requiresOpenAICodexProModel(provider, options?.modelId);
|
|
2747
|
-
const checkUsage =
|
|
2850
|
+
const checkUsage =
|
|
2851
|
+
strategy !== undefined && (selectedCredential !== undefined || credentials.length > 1 || requiresProModel);
|
|
2748
2852
|
const sessionCredential = this.#getSessionCredential(provider, sessionId);
|
|
2749
2853
|
const sessionPreferredIndex = sessionCredential?.type === "oauth" ? sessionCredential.index : undefined;
|
|
2750
2854
|
// Skip ranking only when the session already has a working preferred credential — re-ranking
|
|
@@ -2753,7 +2857,7 @@ export class AuthStorage {
|
|
|
2753
2857
|
// with the most headroom proactively and fall back intelligently when rate-limited.
|
|
2754
2858
|
const sessionPreferredIsAvailable =
|
|
2755
2859
|
sessionPreferredIndex !== undefined && !this.#isCredentialBlocked(providerKey, sessionPreferredIndex);
|
|
2756
|
-
const shouldRank = checkUsage && (!sessionPreferredIsAvailable || requiresProModel);
|
|
2860
|
+
const shouldRank = !selectedCredential && checkUsage && (!sessionPreferredIsAvailable || requiresProModel);
|
|
2757
2861
|
const candidates = shouldRank
|
|
2758
2862
|
? await this.#rankOAuthSelections({ providerKey, provider, order, credentials, options, strategy: strategy! })
|
|
2759
2863
|
: order
|
|
@@ -2761,7 +2865,7 @@ export class AuthStorage {
|
|
|
2761
2865
|
.filter((selection): selection is { credential: OAuthCredential; index: number } => Boolean(selection))
|
|
2762
2866
|
.map(selection => ({ selection, usage: null, usageChecked: false }));
|
|
2763
2867
|
|
|
2764
|
-
if (sessionPreferredIndex !== undefined && !requiresProModel) {
|
|
2868
|
+
if (!selectedCredential && sessionPreferredIndex !== undefined && !requiresProModel) {
|
|
2765
2869
|
const sessionPreferredCandidate = candidates.findIndex(
|
|
2766
2870
|
candidate =>
|
|
2767
2871
|
!this.#isCredentialBlocked(providerKey, candidate.selection.index) &&
|
|
@@ -3118,7 +3222,10 @@ export class AuthStorage {
|
|
|
3118
3222
|
await this.reload();
|
|
3119
3223
|
return this.#resolveOAuthSelection(provider, sessionId, options);
|
|
3120
3224
|
}
|
|
3121
|
-
if (
|
|
3225
|
+
if (
|
|
3226
|
+
!this.#getCredentialSelector(provider, options) &&
|
|
3227
|
+
this.#getCredentialsForProvider(provider).some(credential => credential.type === "oauth")
|
|
3228
|
+
) {
|
|
3122
3229
|
return this.#resolveOAuthSelection(provider, sessionId, options);
|
|
3123
3230
|
}
|
|
3124
3231
|
} else {
|
|
@@ -3126,6 +3233,12 @@ export class AuthStorage {
|
|
|
3126
3233
|
this.#markCredentialBlocked(providerKey, selection.index, Date.now() + 5 * 60 * 1000);
|
|
3127
3234
|
}
|
|
3128
3235
|
}
|
|
3236
|
+
if (this.#getCredentialSelector(provider, options)) {
|
|
3237
|
+
const selector = this.#getCredentialSelector(provider, options);
|
|
3238
|
+
throw new Error(
|
|
3239
|
+
`Selected credential for ${provider} (${selector ? this.#formatCredentialSelector(selector) : "unknown"}) is unavailable`,
|
|
3240
|
+
);
|
|
3241
|
+
}
|
|
3129
3242
|
|
|
3130
3243
|
return undefined;
|
|
3131
3244
|
}
|
|
@@ -3184,6 +3297,8 @@ export class AuthStorage {
|
|
|
3184
3297
|
* 6. Fallback resolver (models.yml custom providers, last-resort)
|
|
3185
3298
|
*/
|
|
3186
3299
|
async getApiKey(provider: string, sessionId?: string, options?: AuthApiKeyOptions): Promise<string | undefined> {
|
|
3300
|
+
const selectedCredential = this.#resolveSelectedStoredCredential(provider, options);
|
|
3301
|
+
|
|
3187
3302
|
// Runtime override takes highest priority
|
|
3188
3303
|
const runtimeKey = this.#runtimeOverrides.get(provider);
|
|
3189
3304
|
if (runtimeKey) {
|
|
@@ -3200,10 +3315,17 @@ export class AuthStorage {
|
|
|
3200
3315
|
return configKey;
|
|
3201
3316
|
}
|
|
3202
3317
|
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
this.#
|
|
3206
|
-
|
|
3318
|
+
if (selectedCredential?.credential.type === "api_key") {
|
|
3319
|
+
this.#recordSessionCredential(provider, sessionId, "api_key", selectedCredential.index);
|
|
3320
|
+
return this.#configValueResolver(selectedCredential.credential.key);
|
|
3321
|
+
}
|
|
3322
|
+
|
|
3323
|
+
if (!selectedCredential) {
|
|
3324
|
+
const apiKeySelection = this.#selectCredentialByType(provider, "api_key", sessionId);
|
|
3325
|
+
if (apiKeySelection) {
|
|
3326
|
+
this.#recordSessionCredential(provider, sessionId, "api_key", apiKeySelection.index);
|
|
3327
|
+
return this.#configValueResolver(apiKeySelection.credential.key);
|
|
3328
|
+
}
|
|
3207
3329
|
}
|
|
3208
3330
|
|
|
3209
3331
|
const oauthResolved = await this.#resolveOAuthSelection(provider, sessionId, options);
|