@asm-agent/coding-agent 0.9.2 → 0.9.4
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 +4 -0
- package/bundled-extensions/00-COMECE-AQUI.txt +233 -0
- package/bundled-extensions/00-INDEX.md +267 -0
- package/bundled-extensions/OLLAMA-TOOL-FORCING-IMPLEMENTATION.md +323 -0
- package/bundled-extensions/OLLAMA-TOOL-FORCING-QUICK-START.txt +113 -0
- package/bundled-extensions/OLLAMA-TOOL-FORCING-TESTS.md +217 -0
- package/bundled-extensions/OLLAMA-TOOL-FORCING.md +155 -0
- package/bundled-extensions/README.txt +178 -0
- package/bundled-extensions/ollama-tool-forcing.ts +90 -0
- package/dist/asm-agent-runtime/run_test.py +19 -0
- package/dist/asm-agent-runtime/src/rlm/__pycache__/__init__.cpython-312.pyc +0 -0
- package/dist/asm-agent-runtime/src/rlm/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/asm-agent-runtime/src/rlm/__pycache__/bash.cpython-312.pyc +0 -0
- package/dist/asm-agent-runtime/src/rlm/__pycache__/bash.cpython-313.pyc +0 -0
- package/dist/asm-agent-runtime/src/rlm/__pycache__/repl.cpython-312.pyc +0 -0
- package/dist/asm-agent-runtime/src/rlm/__pycache__/repl.cpython-313.pyc +0 -0
- package/dist/asm-agent-runtime/test/__pycache__/test_bash.cpython-312.pyc +0 -0
- package/dist/asm-agent-runtime/test/test_bash.py +5 -3
- package/dist/bundle/{anthropic-2VRH36LG.js → anthropic-MHZBPOI3.js} +1 -1
- package/dist/bundle/{azure-openai-responses-COYWHJPT.js → azure-openai-responses-STVL56NS.js} +1 -1
- package/dist/bundle/{bundled-modules-N3FE2F3V.js → bundled-modules-ABB56VC3.js} +3 -3
- package/dist/bundle/{chunk-6GUVYK6P.js → chunk-EBIKTZ7C.js} +1 -0
- package/dist/bundle/{chunk-ZFAQZSZL.js → chunk-GUPU2EEK.js} +420 -31
- package/dist/bundle/{chunk-LRHUL5TM.js → chunk-NESKY6T7.js} +10 -10
- package/dist/bundle/{cli-main-PA7BZMDO.js → cli-main-ZYCGVTZE.js} +3 -3
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/{google-CSTXROSV.js → google-GMTMUI2A.js} +12 -3
- package/dist/bundle/{main-Y475CMOX.js → main-J36EPND6.js} +3 -3
- package/dist/bundle/{mistral-VAF7JADB.js → mistral-UCXDQPIP.js} +1 -1
- package/dist/bundle/{openai-codex-responses-RWZ6P4I3.js → openai-codex-responses-EGZH34H4.js} +1 -1
- package/dist/bundle/{openai-completions-CFB3BOTU.js → openai-completions-N3OFURZC.js} +1 -1
- package/dist/bundle/{openai-responses-4OPGGCBI.js → openai-responses-DTSHMHFQ.js} +1 -1
- package/dist/core/auth-storage.d.ts +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +22 -5
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +10 -1
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/provider-display-names.d.ts.map +1 -1
- package/dist/core/provider-display-names.js +1 -0
- package/dist/core/provider-display-names.js.map +1 -1
- package/dist/postinstall.d.ts.map +1 -1
- package/dist/postinstall.js +40 -3
- package/dist/postinstall.js.map +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +7 -6
|
@@ -301,7 +301,7 @@ import {
|
|
|
301
301
|
findEnvKeys,
|
|
302
302
|
getEnvApiKey,
|
|
303
303
|
getPrimeTeamId
|
|
304
|
-
} from "./chunk-
|
|
304
|
+
} from "./chunk-EBIKTZ7C.js";
|
|
305
305
|
import {
|
|
306
306
|
StreamFailureError,
|
|
307
307
|
appendAssistantMessageDiagnostic,
|
|
@@ -13575,7 +13575,7 @@ function createLazySimpleStream(loadModule) {
|
|
|
13575
13575
|
};
|
|
13576
13576
|
}
|
|
13577
13577
|
function loadAnthropicProviderModule() {
|
|
13578
|
-
anthropicProviderModulePromise ||= import("./anthropic-
|
|
13578
|
+
anthropicProviderModulePromise ||= import("./anthropic-MHZBPOI3.js").then((module) => {
|
|
13579
13579
|
const provider = module;
|
|
13580
13580
|
return {
|
|
13581
13581
|
stream: provider.streamAnthropic,
|
|
@@ -13585,7 +13585,7 @@ function loadAnthropicProviderModule() {
|
|
|
13585
13585
|
return anthropicProviderModulePromise;
|
|
13586
13586
|
}
|
|
13587
13587
|
function loadAzureOpenAIResponsesProviderModule() {
|
|
13588
|
-
azureOpenAIResponsesProviderModulePromise ||= import("./azure-openai-responses-
|
|
13588
|
+
azureOpenAIResponsesProviderModulePromise ||= import("./azure-openai-responses-STVL56NS.js").then((module) => {
|
|
13589
13589
|
const provider = module;
|
|
13590
13590
|
return {
|
|
13591
13591
|
stream: provider.streamAzureOpenAIResponses,
|
|
@@ -13595,7 +13595,7 @@ function loadAzureOpenAIResponsesProviderModule() {
|
|
|
13595
13595
|
return azureOpenAIResponsesProviderModulePromise;
|
|
13596
13596
|
}
|
|
13597
13597
|
function loadGoogleProviderModule() {
|
|
13598
|
-
googleProviderModulePromise ||= import("./google-
|
|
13598
|
+
googleProviderModulePromise ||= import("./google-GMTMUI2A.js").then((module) => {
|
|
13599
13599
|
const provider = module;
|
|
13600
13600
|
return {
|
|
13601
13601
|
stream: provider.streamGoogle,
|
|
@@ -13615,7 +13615,7 @@ function loadGoogleVertexProviderModule() {
|
|
|
13615
13615
|
return googleVertexProviderModulePromise;
|
|
13616
13616
|
}
|
|
13617
13617
|
function loadMistralProviderModule() {
|
|
13618
|
-
mistralProviderModulePromise ||= import("./mistral-
|
|
13618
|
+
mistralProviderModulePromise ||= import("./mistral-UCXDQPIP.js").then((module) => {
|
|
13619
13619
|
const provider = module;
|
|
13620
13620
|
return {
|
|
13621
13621
|
stream: provider.streamMistral,
|
|
@@ -13625,7 +13625,7 @@ function loadMistralProviderModule() {
|
|
|
13625
13625
|
return mistralProviderModulePromise;
|
|
13626
13626
|
}
|
|
13627
13627
|
function loadOpenAICodexResponsesProviderModule() {
|
|
13628
|
-
openAICodexResponsesProviderModulePromise ||= import("./openai-codex-responses-
|
|
13628
|
+
openAICodexResponsesProviderModulePromise ||= import("./openai-codex-responses-EGZH34H4.js").then((module) => {
|
|
13629
13629
|
const provider = module;
|
|
13630
13630
|
return {
|
|
13631
13631
|
stream: provider.streamOpenAICodexResponses,
|
|
@@ -13635,7 +13635,7 @@ function loadOpenAICodexResponsesProviderModule() {
|
|
|
13635
13635
|
return openAICodexResponsesProviderModulePromise;
|
|
13636
13636
|
}
|
|
13637
13637
|
function loadOpenAICompletionsProviderModule() {
|
|
13638
|
-
openAICompletionsProviderModulePromise ||= import("./openai-completions-
|
|
13638
|
+
openAICompletionsProviderModulePromise ||= import("./openai-completions-N3OFURZC.js").then((module) => {
|
|
13639
13639
|
const provider = module;
|
|
13640
13640
|
return {
|
|
13641
13641
|
stream: provider.streamOpenAICompletions,
|
|
@@ -13645,7 +13645,7 @@ function loadOpenAICompletionsProviderModule() {
|
|
|
13645
13645
|
return openAICompletionsProviderModulePromise;
|
|
13646
13646
|
}
|
|
13647
13647
|
function loadOpenAIResponsesProviderModule() {
|
|
13648
|
-
openAIResponsesProviderModulePromise ||= import("./openai-responses-
|
|
13648
|
+
openAIResponsesProviderModulePromise ||= import("./openai-responses-DTSHMHFQ.js").then((module) => {
|
|
13649
13649
|
const provider = module;
|
|
13650
13650
|
return {
|
|
13651
13651
|
stream: provider.streamOpenAIResponses,
|
|
@@ -37402,7 +37402,7 @@ async function loadExtensionModule(extensionPath) {
|
|
|
37402
37402
|
// (file-path aliases would load a second, divergent copy of each package).
|
|
37403
37403
|
// Also disable tryNative so jiti handles ALL imports (not just the entry point)
|
|
37404
37404
|
// In Node.js/dev: use aliases to resolve to node_modules paths
|
|
37405
|
-
...isBunBinary || isBundledCli ? { virtualModules: (await import("./bundled-modules-
|
|
37405
|
+
...isBunBinary || isBundledCli ? { virtualModules: (await import("./bundled-modules-ABB56VC3.js")).VIRTUAL_MODULES, tryNative: false } : { alias: getAliases() }
|
|
37406
37406
|
});
|
|
37407
37407
|
const module = await jiti.import(extensionPath, { default: true });
|
|
37408
37408
|
const factory = module;
|
|
@@ -59215,7 +59215,7 @@ import { resolve as resolve15 } from "node:path";
|
|
|
59215
59215
|
var ASM_AGENT_BUILD_ID_ENV = "ASM_AGENT_BUILD_ID";
|
|
59216
59216
|
var ASM_AGENT_LAUNCHER_PATH_ENV = "ASM_AGENT_LAUNCHER_PATH";
|
|
59217
59217
|
function bundledBuildId() {
|
|
59218
|
-
return false ? void 0 : "v0.9.
|
|
59218
|
+
return false ? void 0 : "v0.9.2-2-g5d478e59-dirty";
|
|
59219
59219
|
}
|
|
59220
59220
|
function getDaemonRuntimeIdentity(environment = process.env) {
|
|
59221
59221
|
const entrypoint = process.argv[1];
|
|
@@ -7,13 +7,13 @@ import {
|
|
|
7
7
|
isOwnedSessionWorkerProcess,
|
|
8
8
|
maybeRunOwnedSessionWorkerFrontend,
|
|
9
9
|
maybeStartDaemonEarly
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-NESKY6T7.js";
|
|
11
11
|
import "./chunk-5QCJ5DQU.js";
|
|
12
12
|
import "./chunk-CMMSLBAW.js";
|
|
13
13
|
import "./chunk-2T7M7VJ4.js";
|
|
14
14
|
import "./chunk-UIRUOAIQ.js";
|
|
15
15
|
import "./chunk-HXOBFFJX.js";
|
|
16
|
-
import "./chunk-
|
|
16
|
+
import "./chunk-EBIKTZ7C.js";
|
|
17
17
|
import "./chunk-UPDAFKL5.js";
|
|
18
18
|
import "./chunk-US4QGESM.js";
|
|
19
19
|
import "./chunk-BAWSWWEU.js";
|
|
@@ -182,7 +182,7 @@ async function runCli() {
|
|
|
182
182
|
}
|
|
183
183
|
const [{ EnvHttpProxyAgent, setGlobalDispatcher }, { main }] = await Promise.all([
|
|
184
184
|
import("undici"),
|
|
185
|
-
import("./main-
|
|
185
|
+
import("./main-J36EPND6.js")
|
|
186
186
|
]);
|
|
187
187
|
setGlobalDispatcher(new EnvHttpProxyAgent({ bodyTimeout: 0, headersTimeout: 0 }));
|
|
188
188
|
try {
|
package/dist/bundle/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire as __piBundleCreateRequire } from 'node:module'; const require = __piBundleCreateRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
getEnvApiKey
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-EBIKTZ7C.js";
|
|
5
5
|
import {
|
|
6
6
|
GoogleGenAI,
|
|
7
7
|
convertMessages,
|
|
@@ -267,8 +267,17 @@ function createClient(model, apiKey, optionsHeaders) {
|
|
|
267
267
|
httpOptions.baseUrl = model.baseUrl;
|
|
268
268
|
httpOptions.apiVersion = "";
|
|
269
269
|
}
|
|
270
|
-
|
|
271
|
-
|
|
270
|
+
const headers = { ...model.headers, ...optionsHeaders };
|
|
271
|
+
const isOAuthToken = apiKey && (apiKey.startsWith("ya29.") || apiKey.startsWith("Bearer "));
|
|
272
|
+
if (isOAuthToken) {
|
|
273
|
+
headers.Authorization = apiKey.startsWith("Bearer ") ? apiKey : `Bearer ${apiKey}`;
|
|
274
|
+
httpOptions.headers = headers;
|
|
275
|
+
return new GoogleGenAI({
|
|
276
|
+
httpOptions: Object.keys(httpOptions).length > 0 ? httpOptions : void 0
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
if (Object.keys(headers).length > 0) {
|
|
280
|
+
httpOptions.headers = headers;
|
|
272
281
|
}
|
|
273
282
|
return new GoogleGenAI({
|
|
274
283
|
apiKey,
|
|
@@ -17,8 +17,8 @@ import {
|
|
|
17
17
|
shouldUseDaemonClientRuntime,
|
|
18
18
|
shouldUseDaemonInteractive,
|
|
19
19
|
shouldUseEphemeralSessionManagerForDaemonInteractive
|
|
20
|
-
} from "./chunk-
|
|
21
|
-
import "./chunk-
|
|
20
|
+
} from "./chunk-GUPU2EEK.js";
|
|
21
|
+
import "./chunk-NESKY6T7.js";
|
|
22
22
|
import "./chunk-VWR3YFK5.js";
|
|
23
23
|
import "./chunk-N2CSAETN.js";
|
|
24
24
|
import "./chunk-5QCJ5DQU.js";
|
|
@@ -26,7 +26,7 @@ import "./chunk-CMMSLBAW.js";
|
|
|
26
26
|
import "./chunk-2T7M7VJ4.js";
|
|
27
27
|
import "./chunk-UIRUOAIQ.js";
|
|
28
28
|
import "./chunk-HXOBFFJX.js";
|
|
29
|
-
import "./chunk-
|
|
29
|
+
import "./chunk-EBIKTZ7C.js";
|
|
30
30
|
import "./chunk-UPDAFKL5.js";
|
|
31
31
|
import "./chunk-US4QGESM.js";
|
|
32
32
|
import "./chunk-BAWSWWEU.js";
|
|
@@ -26,7 +26,7 @@ export type AuthCredential = ApiKeyCredential | OAuthCredential;
|
|
|
26
26
|
export type AuthStorageData = Record<string, AuthCredential>;
|
|
27
27
|
export type AuthStatus = {
|
|
28
28
|
configured: boolean;
|
|
29
|
-
source?: "stored" | "runtime" | "environment" | "prime_cli" | "fallback" | "models_json_key" | "models_json_command" | "stale";
|
|
29
|
+
source?: "stored" | "runtime" | "environment" | "prime_cli" | "antigravity" | "fallback" | "models_json_key" | "models_json_command" | "stale";
|
|
30
30
|
label?: string;
|
|
31
31
|
};
|
|
32
32
|
export type AuthStorageOptions = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-storage.d.ts","sourceRoot":"","sources":["../../src/core/auth-storage.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Credential storage for API keys and OAuth tokens.\n * Handles loading, saving, and refreshing credentials from auth.json.\n *\n * Uses file locking to prevent race conditions when multiple pi instances\n * try to refresh tokens simultaneously.\n */\n\nimport { createHash } from \"node:crypto\";\nimport {\n\tfindEnvKeys,\n\tgetEnvApiKey,\n\ttype OAuthCredentials,\n\ttype OAuthLoginCallbacks,\n\ttype OAuthProviderId,\n} from \"@asm-agent/ai\";\nimport { getOAuthApiKey, getOAuthProvider, getOAuthProviders } from \"@asm-agent/ai/oauth\";\nimport { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport lockfile from \"proper-lockfile\";\nimport { getAgentDir } from \"../config.js\";\nimport {\n\tclearPrimeCliCredentials,\n\tgetPrimeCliConfigPath,\n\tloadPrimeCliConfig,\n\tPRIME_INFERENCE_PROVIDER_ID,\n\ttype PrimeCliConfig,\n\ttype PrimeTeam,\n\tsavePrimeCliApiKey,\n\tsavePrimeCliTeamSelection,\n} from \"./prime-inference-auth.js\";\nimport { resolveConfigValue, resolveConfigValueUncached } from \"./resolve-config-value.js\";\n\nexport type PrimeTeamCredential = {\n\tteamId: string;\n\tname: string;\n\tslug?: string;\n\trole?: string;\n\tcreatedAt?: string;\n};\n\nexport type ApiKeyCredential = {\n\ttype: \"api_key\";\n\tkey: string;\n\tprimeTeam?: PrimeTeamCredential | null;\n};\n\nexport type OAuthCredential = {\n\ttype: \"oauth\";\n} & OAuthCredentials;\n\nexport type AuthCredential = ApiKeyCredential | OAuthCredential;\n\nexport type AuthStorageData = Record<string, AuthCredential>;\n\nexport type AuthStatus = {\n\tconfigured: boolean;\n\tsource?:\n\t\t| \"stored\"\n\t\t| \"runtime\"\n\t\t| \"environment\"\n\t\t| \"prime_cli\"\n\t\t| \"fallback\"\n\t\t| \"models_json_key\"\n\t\t| \"models_json_command\"\n\t\t| \"stale\";\n\tlabel?: string;\n};\n\nexport type AuthStorageOptions = {\n\tprimeCliConfigPath?: string;\n\tusePrimeCliConfig?: boolean;\n};\n\ntype LockResult<T> = {\n\tresult: T;\n\tnext?: string;\n};\n\ntype ActiveAuthStatusSource = Exclude<NonNullable<AuthStatus[\"source\"]>, \"stale\">;\n\nexport type AuthSourceToken = {\n\tprovider: string;\n\tsource: ActiveAuthStatusSource;\n\tidentityFingerprint: string;\n\tvalueFingerprint: string;\n};\n\ntype AuthSourceCandidate = {\n\tsource: ActiveAuthStatusSource;\n\tconfigured: boolean;\n\tlabel?: string;\n\tidentityFingerprint: string;\n\tvalueFingerprint?: string;\n\tresolveValueFingerprint?: () => string | undefined;\n};\n\ntype AuthApiKeyResult = {\n\tapiKey?: string;\n\tsourceToken?: AuthSourceToken;\n};\n\nexport interface AuthStorageBackend {\n\twithLock<T>(fn: (current: string | undefined) => LockResult<T>): T;\n\twithLockAsync<T>(fn: (current: string | undefined) => Promise<LockResult<T>>): Promise<T>;\n}\n\nexport class FileAuthStorageBackend implements AuthStorageBackend {\n\tconstructor(private authPath: string = join(getAgentDir(), \"auth.json\")) {}\n\n\tprivate ensureParentDir(): void {\n\t\tconst dir = dirname(this.authPath);\n\t\tif (!existsSync(dir)) {\n\t\t\tmkdirSync(dir, { recursive: true, mode: 0o700 });\n\t\t}\n\t}\n\n\tprivate ensureFileExists(): void {\n\t\tif (!existsSync(this.authPath)) {\n\t\t\twriteFileSync(this.authPath, \"{}\", \"utf-8\");\n\t\t\tchmodSync(this.authPath, 0o600);\n\t\t}\n\t}\n\n\tprivate acquireLockSyncWithRetry(path: string): () => void {\n\t\tconst maxAttempts = 10;\n\t\tconst delayMs = 20;\n\t\tlet lastError: unknown;\n\n\t\tfor (let attempt = 1; attempt <= maxAttempts; attempt++) {\n\t\t\ttry {\n\t\t\t\treturn lockfile.lockSync(path, { realpath: false });\n\t\t\t} catch (error) {\n\t\t\t\tconst code =\n\t\t\t\t\ttypeof error === \"object\" && error !== null && \"code\" in error\n\t\t\t\t\t\t? String((error as { code?: unknown }).code)\n\t\t\t\t\t\t: undefined;\n\t\t\t\tif (code !== \"ELOCKED\" || attempt === maxAttempts) {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tlastError = error;\n\t\t\t\tconst start = Date.now();\n\t\t\t\twhile (Date.now() - start < delayMs) {\n\t\t\t\t\t// Sleep synchronously to avoid changing callers to async.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthrow (lastError as Error) ?? new Error(\"Failed to acquire auth storage lock\");\n\t}\n\n\twithLock<T>(fn: (current: string | undefined) => LockResult<T>): T {\n\t\tthis.ensureParentDir();\n\t\tthis.ensureFileExists();\n\n\t\tlet release: (() => void) | undefined;\n\t\ttry {\n\t\t\trelease = this.acquireLockSyncWithRetry(this.authPath);\n\t\t\tconst current = existsSync(this.authPath) ? readFileSync(this.authPath, \"utf-8\") : undefined;\n\t\t\tconst { result, next } = fn(current);\n\t\t\tif (next !== undefined) {\n\t\t\t\twriteFileSync(this.authPath, next, \"utf-8\");\n\t\t\t\tchmodSync(this.authPath, 0o600);\n\t\t\t}\n\t\t\treturn result;\n\t\t} finally {\n\t\t\tif (release) {\n\t\t\t\trelease();\n\t\t\t}\n\t\t}\n\t}\n\n\tasync withLockAsync<T>(fn: (current: string | undefined) => Promise<LockResult<T>>): Promise<T> {\n\t\tthis.ensureParentDir();\n\t\tthis.ensureFileExists();\n\n\t\tlet release: (() => Promise<void>) | undefined;\n\t\tlet lockCompromised = false;\n\t\tlet lockCompromisedError: Error | undefined;\n\t\tconst throwIfCompromised = () => {\n\t\t\tif (lockCompromised) {\n\t\t\t\tthrow lockCompromisedError ?? new Error(\"Auth storage lock was compromised\");\n\t\t\t}\n\t\t};\n\n\t\ttry {\n\t\t\trelease = await lockfile.lock(this.authPath, {\n\t\t\t\tretries: {\n\t\t\t\t\tretries: 10,\n\t\t\t\t\tfactor: 2,\n\t\t\t\t\tminTimeout: 100,\n\t\t\t\t\tmaxTimeout: 10000,\n\t\t\t\t\trandomize: true,\n\t\t\t\t},\n\t\t\t\tstale: 30000,\n\t\t\t\tonCompromised: (err) => {\n\t\t\t\t\tlockCompromised = true;\n\t\t\t\t\tlockCompromisedError = err;\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tthrowIfCompromised();\n\t\t\tconst current = existsSync(this.authPath) ? readFileSync(this.authPath, \"utf-8\") : undefined;\n\t\t\tconst { result, next } = await fn(current);\n\t\t\tthrowIfCompromised();\n\t\t\tif (next !== undefined) {\n\t\t\t\twriteFileSync(this.authPath, next, \"utf-8\");\n\t\t\t\tchmodSync(this.authPath, 0o600);\n\t\t\t}\n\t\t\tthrowIfCompromised();\n\t\t\treturn result;\n\t\t} finally {\n\t\t\tif (release) {\n\t\t\t\ttry {\n\t\t\t\t\tawait release();\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore unlock errors when lock is compromised.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nexport class InMemoryAuthStorageBackend implements AuthStorageBackend {\n\tprivate value: string | undefined;\n\n\twithLock<T>(fn: (current: string | undefined) => LockResult<T>): T {\n\t\tconst { result, next } = fn(this.value);\n\t\tif (next !== undefined) {\n\t\t\tthis.value = next;\n\t\t}\n\t\treturn result;\n\t}\n\n\tasync withLockAsync<T>(fn: (current: string | undefined) => Promise<LockResult<T>>): Promise<T> {\n\t\tconst { result, next } = await fn(this.value);\n\t\tif (next !== undefined) {\n\t\t\tthis.value = next;\n\t\t}\n\t\treturn result;\n\t}\n}\n\n/**\n * Credential storage backed by a JSON file.\n */\nexport class AuthStorage {\n\tprivate data: AuthStorageData = {};\n\tprivate runtimeOverrides: Map<string, string> = new Map();\n\tprivate staleAuthSources: Map<string, AuthSourceToken[]> = new Map();\n\tprivate fallbackResolver?: (provider: string) => string | undefined;\n\tprivate loadError: Error | null = null;\n\tprivate errors: Error[] = [];\n\n\tprivate constructor(\n\t\tprivate storage: AuthStorageBackend,\n\t\tprivate options: AuthStorageOptions = {},\n\t) {\n\t\tthis.reload();\n\t}\n\n\tstatic create(authPath?: string, options?: AuthStorageOptions): AuthStorage {\n\t\tconst authOptions = options ?? { usePrimeCliConfig: authPath === undefined };\n\t\treturn new AuthStorage(new FileAuthStorageBackend(authPath ?? join(getAgentDir(), \"auth.json\")), authOptions);\n\t}\n\n\tstatic fromStorage(storage: AuthStorageBackend, options?: AuthStorageOptions): AuthStorage {\n\t\treturn new AuthStorage(storage, options);\n\t}\n\n\tstatic inMemory(data: AuthStorageData = {}, options?: AuthStorageOptions): AuthStorage {\n\t\tconst storage = new InMemoryAuthStorageBackend();\n\t\tstorage.withLock(() => ({ result: undefined, next: JSON.stringify(data, null, 2) }));\n\t\treturn AuthStorage.fromStorage(storage, options);\n\t}\n\n\t/**\n\t * Set a runtime API key override (not persisted to disk).\n\t * Used for CLI --api-key flag.\n\t */\n\tsetRuntimeApiKey(provider: string, apiKey: string): void {\n\t\tthis.clearStaleAuthSource(provider, \"runtime\");\n\t\tthis.runtimeOverrides.set(provider, apiKey);\n\t}\n\n\t/**\n\t * Remove a runtime API key override.\n\t */\n\tremoveRuntimeApiKey(provider: string): void {\n\t\tthis.clearStaleAuthSource(provider, \"runtime\");\n\t\tthis.runtimeOverrides.delete(provider);\n\t}\n\n\t/**\n\t * Set a fallback resolver for API keys not found in auth.json or env vars.\n\t * Used for custom provider keys from models.json.\n\t */\n\tsetFallbackResolver(resolver: (provider: string) => string | undefined): void {\n\t\tthis.fallbackResolver = resolver;\n\t}\n\n\tprivate recordError(error: unknown): void {\n\t\tconst normalizedError = error instanceof Error ? error : new Error(String(error));\n\t\tthis.errors.push(normalizedError);\n\t}\n\n\tprivate fingerprintAuthSource(source: ActiveAuthStatusSource, material: string): string {\n\t\tconst digest = createHash(\"sha256\").update(source).update(\"\\0\").update(material).digest(\"hex\");\n\t\treturn `${source}:${digest}`;\n\t}\n\n\tprivate createAuthSourceCandidate(options: {\n\t\tsource: ActiveAuthStatusSource;\n\t\tconfigured: boolean;\n\t\tidentityMaterial: string;\n\t\tvalueMaterial?: string;\n\t\tlabel?: string;\n\t\tresolveValueMaterial?: () => string | undefined;\n\t}): AuthSourceCandidate {\n\t\treturn {\n\t\t\tconfigured: options.configured,\n\t\t\tsource: options.source,\n\t\t\t...(options.label ? { label: options.label } : {}),\n\t\t\tidentityFingerprint: this.fingerprintAuthSource(options.source, `identity:${options.identityMaterial}`),\n\t\t\t...(options.valueMaterial !== undefined\n\t\t\t\t? {\n\t\t\t\t\t\tvalueFingerprint: this.fingerprintAuthSource(\n\t\t\t\t\t\t\toptions.source,\n\t\t\t\t\t\t\t`value:${options.identityMaterial}\\0${options.valueMaterial}`,\n\t\t\t\t\t\t),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\t...(options.resolveValueMaterial\n\t\t\t\t? {\n\t\t\t\t\t\tresolveValueFingerprint: () => {\n\t\t\t\t\t\t\tconst valueMaterial = options.resolveValueMaterial?.();\n\t\t\t\t\t\t\treturn valueMaterial === undefined\n\t\t\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t\t\t: this.fingerprintAuthSource(\n\t\t\t\t\t\t\t\t\t\toptions.source,\n\t\t\t\t\t\t\t\t\t\t`value:${options.identityMaterial}\\0${valueMaterial}`,\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t};\n\t}\n\n\tprivate getStoredCredentialValueMaterial(providerId: string, credential: AuthCredential): string | undefined {\n\t\tif (credential.type === \"api_key\") {\n\t\t\tif (credential.key.startsWith(\"!\")) {\n\t\t\t\tconst resolvedKey = resolveConfigValueUncached(credential.key);\n\t\t\t\treturn resolvedKey === undefined ? undefined : `api_key:command:${credential.key}\\0${resolvedKey}`;\n\t\t\t}\n\t\t\treturn `api_key:${credential.key}\\0${resolveConfigValue(credential.key) ?? \"\"}`;\n\t\t}\n\t\tconst provider = getOAuthProvider(providerId);\n\t\tconst apiKey = provider?.getApiKey(credential) ?? credential.access;\n\t\treturn `oauth:${apiKey}\\0${credential.refresh}\\0${credential.expires}`;\n\t}\n\n\tprivate getRuntimeAuthCandidate(provider: string): AuthSourceCandidate | undefined {\n\t\tconst apiKey = this.runtimeOverrides.get(provider);\n\t\tif (!apiKey) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn {\n\t\t\tlabel: \"--api-key\",\n\t\t\t...this.createAuthSourceCandidate({\n\t\t\t\tconfigured: false,\n\t\t\t\tsource: \"runtime\",\n\t\t\t\tidentityMaterial: provider,\n\t\t\t\tvalueMaterial: apiKey,\n\t\t\t}),\n\t\t};\n\t}\n\n\tprivate getPrimeCliAuthCandidate(provider: string): AuthSourceCandidate | undefined {\n\t\tconst apiKey = this.getPrimeCliApiKey(provider);\n\t\tif (!apiKey) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn {\n\t\t\tlabel: \"Prime CLI\",\n\t\t\t...this.createAuthSourceCandidate({\n\t\t\t\tconfigured: false,\n\t\t\t\tsource: \"prime_cli\",\n\t\t\t\tidentityMaterial: provider,\n\t\t\t\tvalueMaterial: apiKey,\n\t\t\t}),\n\t\t};\n\t}\n\n\tprivate getStoredAuthCandidate(\n\t\tprovider: string,\n\t\toptions?: { resolveCommandValue?: boolean; resolvedCommandValue?: string },\n\t): AuthSourceCandidate | undefined {\n\t\tconst credential = this.data[provider];\n\t\tif (!credential) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst isCommandApiKey = credential.type === \"api_key\" && credential.key.startsWith(\"!\");\n\t\tconst identityMaterial = isCommandApiKey ? `api_key:command:${credential.key}` : `${provider}:${credential.type}`;\n\t\tconst commandValueMaterial =\n\t\t\tisCommandApiKey && options?.resolvedCommandValue !== undefined\n\t\t\t\t? `api_key:command:${credential.key}\\0${options.resolvedCommandValue}`\n\t\t\t\t: undefined;\n\t\treturn this.createAuthSourceCandidate({\n\t\t\tconfigured: true,\n\t\t\tsource: \"stored\",\n\t\t\tidentityMaterial,\n\t\t\tvalueMaterial:\n\t\t\t\tcommandValueMaterial ??\n\t\t\t\t(isCommandApiKey && !options?.resolveCommandValue\n\t\t\t\t\t? undefined\n\t\t\t\t\t: this.getStoredCredentialValueMaterial(provider, credential)),\n\t\t\tresolveValueMaterial: isCommandApiKey\n\t\t\t\t? () => this.getStoredCredentialValueMaterial(provider, credential)\n\t\t\t\t: undefined,\n\t\t});\n\t}\n\n\tprivate getEnvironmentAuthCandidate(provider: string): AuthSourceCandidate | undefined {\n\t\tconst envKeys = findEnvKeys(provider);\n\t\tconst envKey = envKeys?.[0];\n\t\tconst apiKey = getEnvApiKey(provider);\n\t\tif (!apiKey) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst label = envKey ?? \"ambient credentials\";\n\t\tconst identityMaterial = envKey ?? this.getAmbientEnvironmentIdentityMaterial(provider);\n\t\treturn this.createAuthSourceCandidate({\n\t\t\tconfigured: false,\n\t\t\tsource: \"environment\",\n\t\t\tlabel,\n\t\t\tidentityMaterial,\n\t\t\tvalueMaterial: `${identityMaterial}\\0${apiKey}`,\n\t\t});\n\t}\n\n\tprivate getAmbientEnvironmentIdentityMaterial(provider: string): string {\n\t\tif (provider === \"amazon-bedrock\") {\n\t\t\tif (process.env.AWS_PROFILE) return `amazon-bedrock:profile:${process.env.AWS_PROFILE}`;\n\t\t\tif (process.env.AWS_ACCESS_KEY_ID) {\n\t\t\t\treturn `amazon-bedrock:access-key:${process.env.AWS_ACCESS_KEY_ID}:${process.env.AWS_SECRET_ACCESS_KEY ?? \"\"}:${process.env.AWS_SESSION_TOKEN ?? \"\"}`;\n\t\t\t}\n\t\t\tif (process.env.AWS_BEARER_TOKEN_BEDROCK) {\n\t\t\t\treturn `amazon-bedrock:bearer:${process.env.AWS_BEARER_TOKEN_BEDROCK}`;\n\t\t\t}\n\t\t\tif (process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI) {\n\t\t\t\treturn `amazon-bedrock:ecs-relative:${process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI}`;\n\t\t\t}\n\t\t\tif (process.env.AWS_CONTAINER_CREDENTIALS_FULL_URI) {\n\t\t\t\treturn `amazon-bedrock:ecs-full:${process.env.AWS_CONTAINER_CREDENTIALS_FULL_URI}`;\n\t\t\t}\n\t\t\tif (process.env.AWS_WEB_IDENTITY_TOKEN_FILE) {\n\t\t\t\treturn `amazon-bedrock:web-identity:${process.env.AWS_WEB_IDENTITY_TOKEN_FILE}`;\n\t\t\t}\n\t\t}\n\t\tif (provider === \"google-vertex\") {\n\t\t\tconst project = process.env.GOOGLE_CLOUD_PROJECT ?? process.env.GCLOUD_PROJECT ?? \"\";\n\t\t\tconst location = process.env.GOOGLE_CLOUD_LOCATION ?? \"\";\n\t\t\tconst credentialsPath = process.env.GOOGLE_APPLICATION_CREDENTIALS ?? \"application-default\";\n\t\t\treturn `google-vertex:${project}:${location}:${credentialsPath}`;\n\t\t}\n\t\treturn provider;\n\t}\n\n\tprivate getFallbackAuthCandidate(provider: string): AuthSourceCandidate | undefined {\n\t\tconst apiKey = this.fallbackResolver?.(provider);\n\t\tif (!apiKey) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn this.createAuthSourceCandidate({\n\t\t\tconfigured: false,\n\t\t\tsource: \"fallback\",\n\t\t\tlabel: \"custom provider config\",\n\t\t\tidentityMaterial: provider,\n\t\t\tvalueMaterial: apiKey,\n\t\t});\n\t}\n\n\tprivate getAuthSourceCandidates(provider: string, options?: { includeFallback?: boolean }): AuthSourceCandidate[] {\n\t\tconst fallbackCandidate =\n\t\t\toptions?.includeFallback === false ? undefined : this.getFallbackAuthCandidate(provider);\n\t\tconst candidates =\n\t\t\tprovider === PRIME_INFERENCE_PROVIDER_ID\n\t\t\t\t? [\n\t\t\t\t\t\tthis.getRuntimeAuthCandidate(provider),\n\t\t\t\t\t\tthis.getEnvironmentAuthCandidate(provider),\n\t\t\t\t\t\tthis.getPrimeCliAuthCandidate(provider),\n\t\t\t\t\t\tthis.getStoredAuthCandidate(provider),\n\t\t\t\t\t\tfallbackCandidate,\n\t\t\t\t\t]\n\t\t\t\t: [\n\t\t\t\t\t\tthis.getRuntimeAuthCandidate(provider),\n\t\t\t\t\t\tthis.getStoredAuthCandidate(provider),\n\t\t\t\t\t\tthis.getEnvironmentAuthCandidate(provider),\n\t\t\t\t\t\tfallbackCandidate,\n\t\t\t\t\t];\n\t\treturn candidates.filter((candidate): candidate is AuthSourceCandidate => candidate !== undefined);\n\t}\n\n\tprivate isAuthSourceStale(provider: string, candidate: AuthSourceCandidate): boolean {\n\t\tconst matchingStale = this.getMatchingStaleAuthSources(provider, candidate);\n\t\tif (matchingStale.length === 0) {\n\t\t\treturn false;\n\t\t}\n\t\tconst valueFingerprint = candidate.valueFingerprint ?? candidate.resolveValueFingerprint?.();\n\t\treturn Boolean(valueFingerprint && matchingStale.some((token) => token.valueFingerprint === valueFingerprint));\n\t}\n\n\tprivate getMatchingStaleAuthSources(provider: string, candidate: AuthSourceCandidate): AuthSourceToken[] {\n\t\tconst stale = this.staleAuthSources.get(provider);\n\t\tif (!stale) {\n\t\t\treturn [];\n\t\t}\n\t\treturn stale.filter(\n\t\t\t(token) => token.source === candidate.source && token.identityFingerprint === candidate.identityFingerprint,\n\t\t);\n\t}\n\n\tprivate getAvailableAuthCandidate(\n\t\tprovider: string,\n\t\toptions?: { includeFallback?: boolean },\n\t): { candidate?: AuthSourceCandidate; hasStaleCandidate: boolean } {\n\t\tlet hasStaleCandidate = false;\n\t\tfor (const candidate of this.getAuthSourceCandidates(provider, options)) {\n\t\t\tif (this.isAuthSourceStale(provider, candidate)) {\n\t\t\t\thasStaleCandidate = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\treturn { candidate, hasStaleCandidate };\n\t\t}\n\t\treturn { hasStaleCandidate };\n\t}\n\n\tprivate toAuthStatus(candidate: AuthSourceCandidate): AuthStatus {\n\t\treturn {\n\t\t\tconfigured: candidate.configured,\n\t\t\tsource: candidate.source,\n\t\t\t...(candidate.label ? { label: candidate.label } : {}),\n\t\t};\n\t}\n\n\tprivate getAuthStatusFromCandidates(provider: string): AuthStatus {\n\t\tconst { candidate, hasStaleCandidate } = this.getAvailableAuthCandidate(provider);\n\t\tif (candidate) {\n\t\t\treturn this.toAuthStatus(candidate);\n\t\t}\n\t\tif (hasStaleCandidate) {\n\t\t\treturn { configured: false, source: \"stale\", label: \"expired\" };\n\t\t}\n\t\treturn { configured: false };\n\t}\n\n\tmarkAuthStale(provider: string): boolean {\n\t\tconst token = this.getCurrentAuthSourceToken(provider);\n\t\treturn token ? this.markAuthSourceStale(token) : false;\n\t}\n\n\tprivate getAuthSourceTokenForCandidate(\n\t\tprovider: string,\n\t\tcandidate: AuthSourceCandidate,\n\t): AuthSourceToken | undefined {\n\t\tconst valueFingerprint = candidate.valueFingerprint ?? candidate.resolveValueFingerprint?.();\n\t\tif (!valueFingerprint) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn {\n\t\t\tprovider,\n\t\t\tsource: candidate.source,\n\t\t\tidentityFingerprint: candidate.identityFingerprint,\n\t\t\tvalueFingerprint,\n\t\t};\n\t}\n\n\tgetCurrentAuthSourceToken(provider: string): AuthSourceToken | undefined {\n\t\tconst { candidate } = this.getAvailableAuthCandidate(provider);\n\t\tif (!candidate) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn this.getAuthSourceTokenForCandidate(provider, candidate);\n\t}\n\n\tmarkAuthSourceStale(token: AuthSourceToken): boolean {\n\t\tif (token.provider.length === 0) {\n\t\t\treturn false;\n\t\t}\n\t\tconst stale = this.staleAuthSources.get(token.provider) ?? [];\n\t\tif (\n\t\t\t!stale.some(\n\t\t\t\t(existing) =>\n\t\t\t\t\texisting.source === token.source &&\n\t\t\t\t\texisting.identityFingerprint === token.identityFingerprint &&\n\t\t\t\t\texisting.valueFingerprint === token.valueFingerprint,\n\t\t\t)\n\t\t) {\n\t\t\tstale.push(token);\n\t\t}\n\t\tthis.staleAuthSources.set(token.provider, stale);\n\t\treturn true;\n\t}\n\n\tprivate clearStaleAuthSource(provider: string, source: ActiveAuthStatusSource): void {\n\t\tconst stale = this.staleAuthSources.get(provider);\n\t\tif (!stale) {\n\t\t\treturn;\n\t\t}\n\t\tconst next = stale.filter((token) => token.source !== source);\n\t\tif (next.length === 0) {\n\t\t\tthis.staleAuthSources.delete(provider);\n\t\t} else {\n\t\t\tthis.staleAuthSources.set(provider, next);\n\t\t}\n\t}\n\n\tprivate parseStorageData(content: string | undefined): AuthStorageData {\n\t\tif (!content) {\n\t\t\treturn {};\n\t\t}\n\t\treturn JSON.parse(content) as AuthStorageData;\n\t}\n\n\t/**\n\t * Reload credentials from storage.\n\t */\n\treload(): void {\n\t\tlet content: string | undefined;\n\t\ttry {\n\t\t\tthis.storage.withLock((current) => {\n\t\t\t\tcontent = current;\n\t\t\t\treturn { result: undefined };\n\t\t\t});\n\t\t\tthis.data = this.parseStorageData(content);\n\t\t\tthis.loadError = null;\n\t\t} catch (error) {\n\t\t\tthis.loadError = error as Error;\n\t\t\tthis.recordError(error);\n\t\t}\n\t}\n\n\tprivate persistProviderChange(provider: string, credential: AuthCredential | undefined): void {\n\t\tif (this.loadError) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tthis.storage.withLock((current) => {\n\t\t\t\tconst currentData = this.parseStorageData(current);\n\t\t\t\tconst merged: AuthStorageData = { ...currentData };\n\t\t\t\tif (credential) {\n\t\t\t\t\tmerged[provider] = credential;\n\t\t\t\t} else {\n\t\t\t\t\tdelete merged[provider];\n\t\t\t\t}\n\t\t\t\treturn { result: undefined, next: JSON.stringify(merged, null, 2) };\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthis.recordError(error);\n\t\t}\n\t}\n\n\t/**\n\t * Get credential for a provider.\n\t */\n\tget(provider: string): AuthCredential | undefined {\n\t\treturn this.data[provider] ?? undefined;\n\t}\n\n\t/**\n\t * Set credential for a provider.\n\t */\n\tset(provider: string, credential: AuthCredential): void {\n\t\tthis.clearStaleAuthSource(provider, \"stored\");\n\t\tthis.data[provider] = credential;\n\t\tthis.persistProviderChange(provider, credential);\n\t}\n\n\t/**\n\t * Remove credential for a provider.\n\t */\n\tremove(provider: string): void {\n\t\tthis.clearStaleAuthSource(provider, \"stored\");\n\t\tdelete this.data[provider];\n\t\tthis.persistProviderChange(provider, undefined);\n\t}\n\n\t/**\n\t * Remove a provider's credential with the disk write verified: throws on any\n\t * load or write failure instead of recording it, so callers can refuse to\n\t * proceed while the credential may still exist on disk. Disk-authoritative\n\t * and idempotent — in-memory state is only updated after the write succeeds.\n\t */\n\tremoveVerified(provider: string): void {\n\t\tthis.storage.withLock((current) => {\n\t\t\tconst currentData = this.parseStorageData(current);\n\t\t\tif (!(provider in currentData)) return { result: undefined };\n\t\t\tconst merged: AuthStorageData = { ...currentData };\n\t\t\tdelete merged[provider];\n\t\t\treturn { result: undefined, next: JSON.stringify(merged, null, 2) };\n\t\t});\n\t\tdelete this.data[provider];\n\t\t// Post-success only: a failed removal must not make a stale-marked credential selectable again.\n\t\tthis.clearStaleAuthSource(provider, \"stored\");\n\t}\n\n\t/**\n\t * List all providers with credentials.\n\t */\n\tlist(): string[] {\n\t\treturn Object.keys(this.data);\n\t}\n\n\t/**\n\t * Check if credentials exist for a provider in auth.json.\n\t */\n\thas(provider: string): boolean {\n\t\treturn provider in this.data;\n\t}\n\n\t/**\n\t * Check if any form of auth is configured for a provider.\n\t * Unlike getApiKey(), this doesn't refresh OAuth tokens.\n\t */\n\thasAuth(provider: string): boolean {\n\t\treturn this.getAvailableAuthCandidate(provider).candidate !== undefined;\n\t}\n\n\t/**\n\t * Return auth status without exposing credential values or refreshing tokens.\n\t */\n\tgetAuthStatus(provider: string): AuthStatus {\n\t\treturn this.getAuthStatusFromCandidates(provider);\n\t}\n\n\t/**\n\t * Get all credentials (for passing to getOAuthApiKey).\n\t */\n\tgetAll(): AuthStorageData {\n\t\treturn { ...this.data };\n\t}\n\n\tdrainErrors(): Error[] {\n\t\tconst drained = [...this.errors];\n\t\tthis.errors = [];\n\t\treturn drained;\n\t}\n\n\t/**\n\t * Login to an OAuth provider.\n\t */\n\tasync login(providerId: OAuthProviderId, callbacks: OAuthLoginCallbacks): Promise<void> {\n\t\tconst provider = getOAuthProvider(providerId);\n\t\tif (!provider) {\n\t\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t\t}\n\n\t\tconst credentials = await provider.login(callbacks);\n\t\tthis.set(providerId, { type: \"oauth\", ...credentials });\n\t}\n\n\t/**\n\t * Logout from a provider.\n\t */\n\tlogout(provider: string): void {\n\t\tif (provider === PRIME_INFERENCE_PROVIDER_ID && this.isPrimeCliConfigEnabled()) {\n\t\t\ttry {\n\t\t\t\tclearPrimeCliCredentials(this.getEnabledPrimeCliConfigPath());\n\t\t\t\tthis.clearStaleAuthSource(provider, \"prime_cli\");\n\t\t\t} catch (error) {\n\t\t\t\tthis.recordError(error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\t\tthis.remove(provider);\n\t}\n\n\t/**\n\t * Refresh OAuth token with backend locking to prevent race conditions.\n\t * Multiple pi instances may try to refresh simultaneously when tokens expire.\n\t */\n\tprivate async refreshOAuthTokenWithLock(\n\t\tproviderId: OAuthProviderId,\n\t): Promise<{ apiKey: string; newCredentials: OAuthCredentials } | null> {\n\t\tconst provider = getOAuthProvider(providerId);\n\t\tif (!provider) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst result = await this.storage.withLockAsync(async (current) => {\n\t\t\tconst currentData = this.parseStorageData(current);\n\t\t\tthis.data = currentData;\n\t\t\tthis.loadError = null;\n\n\t\t\tconst cred = currentData[providerId];\n\t\t\tif (cred?.type !== \"oauth\") {\n\t\t\t\treturn { result: null };\n\t\t\t}\n\n\t\t\tif (Date.now() < cred.expires) {\n\t\t\t\treturn { result: { apiKey: provider.getApiKey(cred), newCredentials: cred } };\n\t\t\t}\n\n\t\t\tconst oauthCreds: Record<string, OAuthCredentials> = {};\n\t\t\tfor (const [key, value] of Object.entries(currentData)) {\n\t\t\t\tif (value.type === \"oauth\") {\n\t\t\t\t\toauthCreds[key] = value;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst refreshed = await getOAuthApiKey(providerId, oauthCreds);\n\t\t\tif (!refreshed) {\n\t\t\t\treturn { result: null };\n\t\t\t}\n\n\t\t\tconst merged: AuthStorageData = {\n\t\t\t\t...currentData,\n\t\t\t\t[providerId]: { type: \"oauth\", ...refreshed.newCredentials },\n\t\t\t};\n\t\t\tthis.data = merged;\n\t\t\tthis.loadError = null;\n\t\t\treturn { result: refreshed, next: JSON.stringify(merged, null, 2) };\n\t\t});\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Get API key for a provider.\n\t * Priority:\n\t * 1. Runtime override (CLI --api-key)\n\t * 2. Prime Inference: environment variable, Prime CLI config, auth.json\n\t * 3. Other providers: auth.json, environment variable\n\t * 4. Fallback resolver (models.json custom providers)\n\t */\n\tasync getApiKeyWithSourceToken(\n\t\tproviderId: string,\n\t\toptions?: { includeFallback?: boolean },\n\t): Promise<AuthApiKeyResult> {\n\t\t// Runtime overrides take precedence over stored credentials and environment keys.\n\t\tconst runtimeCandidate = this.getRuntimeAuthCandidate(providerId);\n\t\tconst runtimeKey = this.runtimeOverrides.get(providerId);\n\t\tif (runtimeKey && runtimeCandidate && !this.isAuthSourceStale(providerId, runtimeCandidate)) {\n\t\t\treturn {\n\t\t\t\tapiKey: runtimeKey,\n\t\t\t\tsourceToken: this.getAuthSourceTokenForCandidate(providerId, runtimeCandidate),\n\t\t\t};\n\t\t}\n\n\t\tconst envCandidate = this.getEnvironmentAuthCandidate(providerId);\n\t\tconst envKey = getEnvApiKey(providerId);\n\t\tif (\n\t\t\tproviderId === PRIME_INFERENCE_PROVIDER_ID &&\n\t\t\tenvKey &&\n\t\t\tenvCandidate &&\n\t\t\t!this.isAuthSourceStale(providerId, envCandidate)\n\t\t) {\n\t\t\treturn {\n\t\t\t\tapiKey: envKey,\n\t\t\t\tsourceToken: this.getAuthSourceTokenForCandidate(providerId, envCandidate),\n\t\t\t};\n\t\t}\n\n\t\tif (providerId === PRIME_INFERENCE_PROVIDER_ID) {\n\t\t\tconst primeCliCandidate = this.getPrimeCliAuthCandidate(providerId);\n\t\t\tconst primeCliKey = this.getPrimeCliApiKey(providerId);\n\t\t\tif (primeCliKey && primeCliCandidate && !this.isAuthSourceStale(providerId, primeCliCandidate)) {\n\t\t\t\treturn {\n\t\t\t\t\tapiKey: primeCliKey,\n\t\t\t\t\tsourceToken: this.getAuthSourceTokenForCandidate(providerId, primeCliCandidate),\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tconst cred = this.data[providerId];\n\n\t\tif (cred?.type === \"api_key\") {\n\t\t\tconst storedCandidate = this.getStoredAuthCandidate(providerId);\n\t\t\tif (storedCandidate && !this.isAuthSourceStale(providerId, storedCandidate)) {\n\t\t\t\tconst hasStaleRecord = this.getMatchingStaleAuthSources(providerId, storedCandidate).length > 0;\n\t\t\t\tconst apiKey =\n\t\t\t\t\tcred.key.startsWith(\"!\") && hasStaleRecord\n\t\t\t\t\t\t? resolveConfigValueUncached(cred.key)\n\t\t\t\t\t\t: resolveConfigValue(cred.key);\n\t\t\t\tconst sourceToken =\n\t\t\t\t\tapiKey === undefined\n\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t: this.getAuthSourceTokenForCandidate(\n\t\t\t\t\t\t\t\tproviderId,\n\t\t\t\t\t\t\t\tcred.key.startsWith(\"!\")\n\t\t\t\t\t\t\t\t\t? (this.getStoredAuthCandidate(providerId, { resolvedCommandValue: apiKey }) ??\n\t\t\t\t\t\t\t\t\t\t\tstoredCandidate)\n\t\t\t\t\t\t\t\t\t: storedCandidate,\n\t\t\t\t\t\t\t);\n\t\t\t\treturn { apiKey, sourceToken };\n\t\t\t}\n\t\t}\n\n\t\tif (cred?.type === \"oauth\") {\n\t\t\tconst storedCandidate = this.getStoredAuthCandidate(providerId);\n\t\t\tif (storedCandidate && !this.isAuthSourceStale(providerId, storedCandidate)) {\n\t\t\t\tconst provider = getOAuthProvider(providerId);\n\t\t\t\tif (!provider) {\n\t\t\t\t\treturn {};\n\t\t\t\t}\n\t\t\t\t// Lock refreshes so concurrent instances cannot race on the credential file.\n\t\t\t\tconst needsRefresh = Date.now() >= cred.expires;\n\n\t\t\t\tif (needsRefresh) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst result = await this.refreshOAuthTokenWithLock(providerId);\n\t\t\t\t\t\tif (result) {\n\t\t\t\t\t\t\tconst refreshedCandidate = this.getStoredAuthCandidate(providerId);\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tapiKey: result.apiKey,\n\t\t\t\t\t\t\t\tsourceToken: refreshedCandidate\n\t\t\t\t\t\t\t\t\t? this.getAuthSourceTokenForCandidate(providerId, refreshedCandidate)\n\t\t\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tthis.recordError(error);\n\t\t\t\t\t\t// A peer may have refreshed successfully; reload before treating this refresh as failed.\n\t\t\t\t\t\tthis.reload();\n\t\t\t\t\t\tconst updatedCred = this.data[providerId];\n\n\t\t\t\t\t\tif (updatedCred?.type === \"oauth\" && Date.now() < updatedCred.expires) {\n\t\t\t\t\t\t\tconst updatedCandidate = this.getStoredAuthCandidate(providerId);\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tapiKey: provider.getApiKey(updatedCred),\n\t\t\t\t\t\t\t\tsourceToken: updatedCandidate\n\t\t\t\t\t\t\t\t\t? this.getAuthSourceTokenForCandidate(providerId, updatedCandidate)\n\t\t\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Preserve credentials for a later /login retry while discovery skips this provider.\n\t\t\t\t\t\treturn {};\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tapiKey: provider.getApiKey(cred),\n\t\t\t\t\t\tsourceToken: this.getAuthSourceTokenForCandidate(providerId, storedCandidate),\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Stored auth wins over environment variables for non-Prime-Inference providers.\n\t\tif (\n\t\t\tproviderId !== PRIME_INFERENCE_PROVIDER_ID &&\n\t\t\tenvKey &&\n\t\t\tenvCandidate &&\n\t\t\t!this.isAuthSourceStale(providerId, envCandidate)\n\t\t) {\n\t\t\treturn {\n\t\t\t\tapiKey: envKey,\n\t\t\t\tsourceToken: this.getAuthSourceTokenForCandidate(providerId, envCandidate),\n\t\t\t};\n\t\t}\n\t\tif (options?.includeFallback !== false) {\n\t\t\tconst fallbackCandidate = this.getFallbackAuthCandidate(providerId);\n\t\t\tif (fallbackCandidate && !this.isAuthSourceStale(providerId, fallbackCandidate)) {\n\t\t\t\treturn {\n\t\t\t\t\tapiKey: this.fallbackResolver?.(providerId) ?? undefined,\n\t\t\t\t\tsourceToken: this.getAuthSourceTokenForCandidate(providerId, fallbackCandidate),\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\treturn {};\n\t}\n\n\tasync getApiKey(providerId: string, options?: { includeFallback?: boolean }): Promise<string | undefined> {\n\t\tconst result = await this.getApiKeyWithSourceToken(providerId, options);\n\t\treturn result.apiKey;\n\t}\n\n\t/**\n\t * Get all registered OAuth providers\n\t */\n\tgetOAuthProviders() {\n\t\treturn getOAuthProviders();\n\t}\n\n\tsetPrimeInferenceTeamSelection(team: PrimeTeam | null): void {\n\t\tif (this.isPrimeCliConfigEnabled()) {\n\t\t\ttry {\n\t\t\t\tsavePrimeCliTeamSelection(team, this.getEnabledPrimeCliConfigPath());\n\t\t\t} catch (error) {\n\t\t\t\tthis.recordError(error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst credential = this.data[PRIME_INFERENCE_PROVIDER_ID];\n\t\tif (credential?.type !== \"api_key\") {\n\t\t\treturn;\n\t\t}\n\t\tthis.set(PRIME_INFERENCE_PROVIDER_ID, {\n\t\t\t...credential,\n\t\t\tprimeTeam: team ? this.toPrimeTeamCredential(team) : null,\n\t\t});\n\t}\n\n\tsetPrimeInferenceApiKey(apiKey: string): void {\n\t\tif (this.isPrimeCliConfigEnabled()) {\n\t\t\ttry {\n\t\t\t\tconst configPath = this.getEnabledPrimeCliConfigPath();\n\t\t\t\tconst config = loadPrimeCliConfig(configPath);\n\t\t\t\tconst existingCredential = this.data[PRIME_INFERENCE_PROVIDER_ID];\n\t\t\t\tconst legacyPrimeTeam = existingCredential?.type === \"api_key\" ? existingCredential.primeTeam : undefined;\n\t\t\t\tif (config.apiKey !== apiKey) {\n\t\t\t\t\tsavePrimeCliApiKey(apiKey, configPath);\n\t\t\t\t} else if (!config.teamIdFromEnv && (legacyPrimeTeam === null || (!config.teamId && legacyPrimeTeam))) {\n\t\t\t\t\tsavePrimeCliTeamSelection(legacyPrimeTeam, configPath);\n\t\t\t\t}\n\t\t\t\tthis.clearStaleAuthSource(PRIME_INFERENCE_PROVIDER_ID, \"prime_cli\");\n\t\t\t} catch (error) {\n\t\t\t\tthis.recordError(error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tif (this.data[PRIME_INFERENCE_PROVIDER_ID]) {\n\t\t\t\tthis.remove(PRIME_INFERENCE_PROVIDER_ID);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst existingCredential = this.data[PRIME_INFERENCE_PROVIDER_ID];\n\t\tconst existingPrimeTeam = existingCredential?.type === \"api_key\" ? existingCredential.primeTeam : undefined;\n\t\tthis.set(PRIME_INFERENCE_PROVIDER_ID, {\n\t\t\ttype: \"api_key\",\n\t\t\tkey: apiKey,\n\t\t\t...(existingPrimeTeam !== undefined ? { primeTeam: existingPrimeTeam } : {}),\n\t\t});\n\t}\n\n\tgetPrimeInferenceTeamSelection(): PrimeTeamCredential | null | undefined {\n\t\tlet config: PrimeCliConfig | undefined;\n\t\tif (this.isPrimeCliConfigEnabled()) {\n\t\t\tconfig = this.getPrimeCliConfig(PRIME_INFERENCE_PROVIDER_ID);\n\t\t\tif (config?.teamIdFromEnv) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t}\n\n\t\tconst credential = this.data[PRIME_INFERENCE_PROVIDER_ID];\n\t\tconst authSource = this.getAuthStatus(PRIME_INFERENCE_PROVIDER_ID).source;\n\t\tif (authSource === \"runtime\" || authSource === \"environment\") {\n\t\t\treturn undefined;\n\t\t}\n\t\tif (authSource === \"prime_cli\") {\n\t\t\tif (credential?.type === \"api_key\" && credential.primeTeam === null) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tif (config?.teamId) {\n\t\t\t\treturn this.toPrimeTeamCredential({\n\t\t\t\t\tteamId: config.teamId,\n\t\t\t\t\tname: config.teamName ?? \"Prime CLI team\",\n\t\t\t\t\t...(config.teamRole ? { role: config.teamRole } : {}),\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (credential?.type === \"api_key\" && credential.primeTeam) {\n\t\t\t\treturn credential.primeTeam;\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\t\tif (credential?.type === \"api_key\" && credential.primeTeam !== undefined) {\n\t\t\treturn credential.primeTeam;\n\t\t}\n\t\tif (!config?.apiKey && config?.teamId) {\n\t\t\treturn this.toPrimeTeamCredential({\n\t\t\t\tteamId: config.teamId,\n\t\t\t\tname: config.teamName ?? \"Prime CLI team\",\n\t\t\t\t...(config.teamRole ? { role: config.teamRole } : {}),\n\t\t\t});\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tgetProviderHeaders(providerId: string): Record<string, string> | undefined {\n\t\tif (providerId !== PRIME_INFERENCE_PROVIDER_ID) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst primeCliConfig = this.getPrimeCliConfig(providerId);\n\t\tif (primeCliConfig?.teamIdFromEnv) {\n\t\t\treturn primeCliConfig.teamId ? { \"X-Prime-Team-ID\": primeCliConfig.teamId } : undefined;\n\t\t}\n\n\t\tconst teamId = this.getPrimeInferenceTeamSelection()?.teamId;\n\t\treturn teamId ? { \"X-Prime-Team-ID\": teamId } : undefined;\n\t}\n\n\tgetPrimeCliConfigPath(): string | undefined {\n\t\tif (!this.isPrimeCliConfigEnabled()) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn getPrimeCliConfigPath(this.options.primeCliConfigPath);\n\t}\n\n\tprivate toPrimeTeamCredential(team: PrimeTeam): PrimeTeamCredential {\n\t\tconst credential: PrimeTeamCredential = {\n\t\t\tteamId: team.teamId,\n\t\t\tname: team.name,\n\t\t};\n\t\tif (team.slug) {\n\t\t\tcredential.slug = team.slug;\n\t\t}\n\t\tif (team.role) {\n\t\t\tcredential.role = team.role;\n\t\t}\n\t\tif (team.createdAt) {\n\t\t\tcredential.createdAt = team.createdAt;\n\t\t}\n\t\treturn credential;\n\t}\n\n\tprivate getPrimeCliConfig(providerId: string): PrimeCliConfig | undefined {\n\t\tif (providerId !== PRIME_INFERENCE_PROVIDER_ID) {\n\t\t\treturn undefined;\n\t\t}\n\t\tif (!this.isPrimeCliConfigEnabled()) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn loadPrimeCliConfig(this.options.primeCliConfigPath);\n\t}\n\n\tprivate getPrimeCliApiKey(providerId: string): string | undefined {\n\t\treturn this.getPrimeCliConfig(providerId)?.apiKey;\n\t}\n\n\tprivate getEnabledPrimeCliConfigPath(): string {\n\t\tconst configPath = this.getPrimeCliConfigPath();\n\t\tif (!configPath) {\n\t\t\tthrow new Error(\"Prime CLI config is not enabled\");\n\t\t}\n\t\treturn configPath;\n\t}\n\n\tprivate isPrimeCliConfigEnabled(): boolean {\n\t\treturn Boolean(this.options.usePrimeCliConfig || this.options.primeCliConfigPath);\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"auth-storage.d.ts","sourceRoot":"","sources":["../../src/core/auth-storage.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Credential storage for API keys and OAuth tokens.\n * Handles loading, saving, and refreshing credentials from auth.json.\n *\n * Uses file locking to prevent race conditions when multiple pi instances\n * try to refresh tokens simultaneously.\n */\n\nimport { createHash } from \"node:crypto\";\nimport {\n\tfindEnvKeys,\n\tgetEnvApiKey,\n\ttype OAuthCredentials,\n\ttype OAuthLoginCallbacks,\n\ttype OAuthProviderId,\n} from \"@asm-agent/ai\";\nimport { getOAuthApiKey, getOAuthProvider, getOAuthProviders } from \"@asm-agent/ai/oauth\";\nimport { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport lockfile from \"proper-lockfile\";\nimport { getAgentDir } from \"../config.js\";\nimport {\n\tclearPrimeCliCredentials,\n\tgetPrimeCliConfigPath,\n\tloadPrimeCliConfig,\n\tPRIME_INFERENCE_PROVIDER_ID,\n\ttype PrimeCliConfig,\n\ttype PrimeTeam,\n\tsavePrimeCliApiKey,\n\tsavePrimeCliTeamSelection,\n} from \"./prime-inference-auth.js\";\nimport { resolveConfigValue, resolveConfigValueUncached } from \"./resolve-config-value.js\";\n\nexport type PrimeTeamCredential = {\n\tteamId: string;\n\tname: string;\n\tslug?: string;\n\trole?: string;\n\tcreatedAt?: string;\n};\n\nexport type ApiKeyCredential = {\n\ttype: \"api_key\";\n\tkey: string;\n\tprimeTeam?: PrimeTeamCredential | null;\n};\n\nexport type OAuthCredential = {\n\ttype: \"oauth\";\n} & OAuthCredentials;\n\nexport type AuthCredential = ApiKeyCredential | OAuthCredential;\n\nexport type AuthStorageData = Record<string, AuthCredential>;\n\nexport type AuthStatus = {\n\tconfigured: boolean;\n\tsource?:\n\t\t| \"stored\"\n\t\t| \"runtime\"\n\t\t| \"environment\"\n\t\t| \"prime_cli\"\n\t\t| \"antigravity\"\n\t\t| \"fallback\"\n\t\t| \"models_json_key\"\n\t\t| \"models_json_command\"\n\t\t| \"stale\";\n\tlabel?: string;\n};\n\nexport type AuthStorageOptions = {\n\tprimeCliConfigPath?: string;\n\tusePrimeCliConfig?: boolean;\n};\n\ntype LockResult<T> = {\n\tresult: T;\n\tnext?: string;\n};\n\ntype ActiveAuthStatusSource = Exclude<NonNullable<AuthStatus[\"source\"]>, \"stale\">;\n\nexport type AuthSourceToken = {\n\tprovider: string;\n\tsource: ActiveAuthStatusSource;\n\tidentityFingerprint: string;\n\tvalueFingerprint: string;\n};\n\ntype AuthSourceCandidate = {\n\tsource: ActiveAuthStatusSource;\n\tconfigured: boolean;\n\tlabel?: string;\n\tidentityFingerprint: string;\n\tvalueFingerprint?: string;\n\tresolveValueFingerprint?: () => string | undefined;\n};\n\ntype AuthApiKeyResult = {\n\tapiKey?: string;\n\tsourceToken?: AuthSourceToken;\n};\n\nexport interface AuthStorageBackend {\n\twithLock<T>(fn: (current: string | undefined) => LockResult<T>): T;\n\twithLockAsync<T>(fn: (current: string | undefined) => Promise<LockResult<T>>): Promise<T>;\n}\n\nexport class FileAuthStorageBackend implements AuthStorageBackend {\n\tconstructor(private authPath: string = join(getAgentDir(), \"auth.json\")) {}\n\n\tprivate ensureParentDir(): void {\n\t\tconst dir = dirname(this.authPath);\n\t\tif (!existsSync(dir)) {\n\t\t\tmkdirSync(dir, { recursive: true, mode: 0o700 });\n\t\t}\n\t}\n\n\tprivate ensureFileExists(): void {\n\t\tif (!existsSync(this.authPath)) {\n\t\t\twriteFileSync(this.authPath, \"{}\", \"utf-8\");\n\t\t\tchmodSync(this.authPath, 0o600);\n\t\t}\n\t}\n\n\tprivate acquireLockSyncWithRetry(path: string): () => void {\n\t\tconst maxAttempts = 10;\n\t\tconst delayMs = 20;\n\t\tlet lastError: unknown;\n\n\t\tfor (let attempt = 1; attempt <= maxAttempts; attempt++) {\n\t\t\ttry {\n\t\t\t\treturn lockfile.lockSync(path, { realpath: false });\n\t\t\t} catch (error) {\n\t\t\t\tconst code =\n\t\t\t\t\ttypeof error === \"object\" && error !== null && \"code\" in error\n\t\t\t\t\t\t? String((error as { code?: unknown }).code)\n\t\t\t\t\t\t: undefined;\n\t\t\t\tif (code !== \"ELOCKED\" || attempt === maxAttempts) {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tlastError = error;\n\t\t\t\tconst start = Date.now();\n\t\t\t\twhile (Date.now() - start < delayMs) {\n\t\t\t\t\t// Sleep synchronously to avoid changing callers to async.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthrow (lastError as Error) ?? new Error(\"Failed to acquire auth storage lock\");\n\t}\n\n\twithLock<T>(fn: (current: string | undefined) => LockResult<T>): T {\n\t\tthis.ensureParentDir();\n\t\tthis.ensureFileExists();\n\n\t\tlet release: (() => void) | undefined;\n\t\ttry {\n\t\t\trelease = this.acquireLockSyncWithRetry(this.authPath);\n\t\t\tconst current = existsSync(this.authPath) ? readFileSync(this.authPath, \"utf-8\") : undefined;\n\t\t\tconst { result, next } = fn(current);\n\t\t\tif (next !== undefined) {\n\t\t\t\twriteFileSync(this.authPath, next, \"utf-8\");\n\t\t\t\tchmodSync(this.authPath, 0o600);\n\t\t\t}\n\t\t\treturn result;\n\t\t} finally {\n\t\t\tif (release) {\n\t\t\t\trelease();\n\t\t\t}\n\t\t}\n\t}\n\n\tasync withLockAsync<T>(fn: (current: string | undefined) => Promise<LockResult<T>>): Promise<T> {\n\t\tthis.ensureParentDir();\n\t\tthis.ensureFileExists();\n\n\t\tlet release: (() => Promise<void>) | undefined;\n\t\tlet lockCompromised = false;\n\t\tlet lockCompromisedError: Error | undefined;\n\t\tconst throwIfCompromised = () => {\n\t\t\tif (lockCompromised) {\n\t\t\t\tthrow lockCompromisedError ?? new Error(\"Auth storage lock was compromised\");\n\t\t\t}\n\t\t};\n\n\t\ttry {\n\t\t\trelease = await lockfile.lock(this.authPath, {\n\t\t\t\tretries: {\n\t\t\t\t\tretries: 10,\n\t\t\t\t\tfactor: 2,\n\t\t\t\t\tminTimeout: 100,\n\t\t\t\t\tmaxTimeout: 10000,\n\t\t\t\t\trandomize: true,\n\t\t\t\t},\n\t\t\t\tstale: 30000,\n\t\t\t\tonCompromised: (err) => {\n\t\t\t\t\tlockCompromised = true;\n\t\t\t\t\tlockCompromisedError = err;\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tthrowIfCompromised();\n\t\t\tconst current = existsSync(this.authPath) ? readFileSync(this.authPath, \"utf-8\") : undefined;\n\t\t\tconst { result, next } = await fn(current);\n\t\t\tthrowIfCompromised();\n\t\t\tif (next !== undefined) {\n\t\t\t\twriteFileSync(this.authPath, next, \"utf-8\");\n\t\t\t\tchmodSync(this.authPath, 0o600);\n\t\t\t}\n\t\t\tthrowIfCompromised();\n\t\t\treturn result;\n\t\t} finally {\n\t\t\tif (release) {\n\t\t\t\ttry {\n\t\t\t\t\tawait release();\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore unlock errors when lock is compromised.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nexport class InMemoryAuthStorageBackend implements AuthStorageBackend {\n\tprivate value: string | undefined;\n\n\twithLock<T>(fn: (current: string | undefined) => LockResult<T>): T {\n\t\tconst { result, next } = fn(this.value);\n\t\tif (next !== undefined) {\n\t\t\tthis.value = next;\n\t\t}\n\t\treturn result;\n\t}\n\n\tasync withLockAsync<T>(fn: (current: string | undefined) => Promise<LockResult<T>>): Promise<T> {\n\t\tconst { result, next } = await fn(this.value);\n\t\tif (next !== undefined) {\n\t\t\tthis.value = next;\n\t\t}\n\t\treturn result;\n\t}\n}\n\n/**\n * Credential storage backed by a JSON file.\n */\nexport class AuthStorage {\n\tprivate data: AuthStorageData = {};\n\tprivate runtimeOverrides: Map<string, string> = new Map();\n\tprivate staleAuthSources: Map<string, AuthSourceToken[]> = new Map();\n\tprivate fallbackResolver?: (provider: string) => string | undefined;\n\tprivate loadError: Error | null = null;\n\tprivate errors: Error[] = [];\n\n\tprivate constructor(\n\t\tprivate storage: AuthStorageBackend,\n\t\tprivate options: AuthStorageOptions = {},\n\t) {\n\t\tthis.reload();\n\t}\n\n\tstatic create(authPath?: string, options?: AuthStorageOptions): AuthStorage {\n\t\tconst authOptions = options ?? { usePrimeCliConfig: authPath === undefined };\n\t\treturn new AuthStorage(new FileAuthStorageBackend(authPath ?? join(getAgentDir(), \"auth.json\")), authOptions);\n\t}\n\n\tstatic fromStorage(storage: AuthStorageBackend, options?: AuthStorageOptions): AuthStorage {\n\t\treturn new AuthStorage(storage, options);\n\t}\n\n\tstatic inMemory(data: AuthStorageData = {}, options?: AuthStorageOptions): AuthStorage {\n\t\tconst storage = new InMemoryAuthStorageBackend();\n\t\tstorage.withLock(() => ({ result: undefined, next: JSON.stringify(data, null, 2) }));\n\t\treturn AuthStorage.fromStorage(storage, options);\n\t}\n\n\t/**\n\t * Set a runtime API key override (not persisted to disk).\n\t * Used for CLI --api-key flag.\n\t */\n\tsetRuntimeApiKey(provider: string, apiKey: string): void {\n\t\tthis.clearStaleAuthSource(provider, \"runtime\");\n\t\tthis.runtimeOverrides.set(provider, apiKey);\n\t}\n\n\t/**\n\t * Remove a runtime API key override.\n\t */\n\tremoveRuntimeApiKey(provider: string): void {\n\t\tthis.clearStaleAuthSource(provider, \"runtime\");\n\t\tthis.runtimeOverrides.delete(provider);\n\t}\n\n\t/**\n\t * Set a fallback resolver for API keys not found in auth.json or env vars.\n\t * Used for custom provider keys from models.json.\n\t */\n\tsetFallbackResolver(resolver: (provider: string) => string | undefined): void {\n\t\tthis.fallbackResolver = resolver;\n\t}\n\n\tprivate recordError(error: unknown): void {\n\t\tconst normalizedError = error instanceof Error ? error : new Error(String(error));\n\t\tthis.errors.push(normalizedError);\n\t}\n\n\tprivate fingerprintAuthSource(source: ActiveAuthStatusSource, material: string): string {\n\t\tconst digest = createHash(\"sha256\").update(source).update(\"\\0\").update(material).digest(\"hex\");\n\t\treturn `${source}:${digest}`;\n\t}\n\n\tprivate createAuthSourceCandidate(options: {\n\t\tsource: ActiveAuthStatusSource;\n\t\tconfigured: boolean;\n\t\tidentityMaterial: string;\n\t\tvalueMaterial?: string;\n\t\tlabel?: string;\n\t\tresolveValueMaterial?: () => string | undefined;\n\t}): AuthSourceCandidate {\n\t\treturn {\n\t\t\tconfigured: options.configured,\n\t\t\tsource: options.source,\n\t\t\t...(options.label ? { label: options.label } : {}),\n\t\t\tidentityFingerprint: this.fingerprintAuthSource(options.source, `identity:${options.identityMaterial}`),\n\t\t\t...(options.valueMaterial !== undefined\n\t\t\t\t? {\n\t\t\t\t\t\tvalueFingerprint: this.fingerprintAuthSource(\n\t\t\t\t\t\t\toptions.source,\n\t\t\t\t\t\t\t`value:${options.identityMaterial}\\0${options.valueMaterial}`,\n\t\t\t\t\t\t),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\t...(options.resolveValueMaterial\n\t\t\t\t? {\n\t\t\t\t\t\tresolveValueFingerprint: () => {\n\t\t\t\t\t\t\tconst valueMaterial = options.resolveValueMaterial?.();\n\t\t\t\t\t\t\treturn valueMaterial === undefined\n\t\t\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t\t\t: this.fingerprintAuthSource(\n\t\t\t\t\t\t\t\t\t\toptions.source,\n\t\t\t\t\t\t\t\t\t\t`value:${options.identityMaterial}\\0${valueMaterial}`,\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t};\n\t}\n\n\tprivate getStoredCredentialValueMaterial(providerId: string, credential: AuthCredential): string | undefined {\n\t\tif (credential.type === \"api_key\") {\n\t\t\tif (credential.key.startsWith(\"!\")) {\n\t\t\t\tconst resolvedKey = resolveConfigValueUncached(credential.key);\n\t\t\t\treturn resolvedKey === undefined ? undefined : `api_key:command:${credential.key}\\0${resolvedKey}`;\n\t\t\t}\n\t\t\treturn `api_key:${credential.key}\\0${resolveConfigValue(credential.key) ?? \"\"}`;\n\t\t}\n\t\tconst provider = getOAuthProvider(providerId);\n\t\tconst apiKey = provider?.getApiKey(credential) ?? credential.access;\n\t\treturn `oauth:${apiKey}\\0${credential.refresh}\\0${credential.expires}`;\n\t}\n\n\tprivate getRuntimeAuthCandidate(provider: string): AuthSourceCandidate | undefined {\n\t\tconst apiKey = this.runtimeOverrides.get(provider);\n\t\tif (!apiKey) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn {\n\t\t\tlabel: \"--api-key\",\n\t\t\t...this.createAuthSourceCandidate({\n\t\t\t\tconfigured: false,\n\t\t\t\tsource: \"runtime\",\n\t\t\t\tidentityMaterial: provider,\n\t\t\t\tvalueMaterial: apiKey,\n\t\t\t}),\n\t\t};\n\t}\n\n\tprivate getPrimeCliAuthCandidate(provider: string): AuthSourceCandidate | undefined {\n\t\tconst apiKey = this.getPrimeCliApiKey(provider);\n\t\tif (!apiKey) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn {\n\t\t\tlabel: \"Prime CLI\",\n\t\t\t...this.createAuthSourceCandidate({\n\t\t\t\tconfigured: false,\n\t\t\t\tsource: \"prime_cli\",\n\t\t\t\tidentityMaterial: provider,\n\t\t\t\tvalueMaterial: apiKey,\n\t\t\t}),\n\t\t};\n\t}\n\n\tprivate getStoredAuthCandidate(\n\t\tprovider: string,\n\t\toptions?: { resolveCommandValue?: boolean; resolvedCommandValue?: string },\n\t): AuthSourceCandidate | undefined {\n\t\tconst credential =\n\t\t\tthis.data[provider] ??\n\t\t\t(provider === \"google\" ? this.data.gemini : provider === \"gemini\" ? this.data.google : undefined);\n\t\tif (!credential) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst isCommandApiKey = credential.type === \"api_key\" && credential.key.startsWith(\"!\");\n\t\tconst identityMaterial = isCommandApiKey ? `api_key:command:${credential.key}` : `${provider}:${credential.type}`;\n\t\tconst commandValueMaterial =\n\t\t\tisCommandApiKey && options?.resolvedCommandValue !== undefined\n\t\t\t\t? `api_key:command:${credential.key}\\0${options.resolvedCommandValue}`\n\t\t\t\t: undefined;\n\t\treturn this.createAuthSourceCandidate({\n\t\t\tconfigured: true,\n\t\t\tsource: \"stored\",\n\t\t\tidentityMaterial,\n\t\t\tvalueMaterial:\n\t\t\t\tcommandValueMaterial ??\n\t\t\t\t(isCommandApiKey && !options?.resolveCommandValue\n\t\t\t\t\t? undefined\n\t\t\t\t\t: this.getStoredCredentialValueMaterial(provider, credential)),\n\t\t\tresolveValueMaterial: isCommandApiKey\n\t\t\t\t? () => this.getStoredCredentialValueMaterial(provider, credential)\n\t\t\t\t: undefined,\n\t\t});\n\t}\n\n\tprivate getEnvironmentAuthCandidate(provider: string): AuthSourceCandidate | undefined {\n\t\tconst envKeys = findEnvKeys(provider);\n\t\tconst envKey = envKeys?.[0];\n\t\tconst apiKey = getEnvApiKey(provider);\n\t\tif (!apiKey) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst label = envKey ?? \"ambient credentials\";\n\t\tconst identityMaterial = envKey ?? this.getAmbientEnvironmentIdentityMaterial(provider);\n\t\treturn this.createAuthSourceCandidate({\n\t\t\tconfigured: false,\n\t\t\tsource: \"environment\",\n\t\t\tlabel,\n\t\t\tidentityMaterial,\n\t\t\tvalueMaterial: `${identityMaterial}\\0${apiKey}`,\n\t\t});\n\t}\n\n\tprivate getAmbientEnvironmentIdentityMaterial(provider: string): string {\n\t\tif (provider === \"amazon-bedrock\") {\n\t\t\tif (process.env.AWS_PROFILE) return `amazon-bedrock:profile:${process.env.AWS_PROFILE}`;\n\t\t\tif (process.env.AWS_ACCESS_KEY_ID) {\n\t\t\t\treturn `amazon-bedrock:access-key:${process.env.AWS_ACCESS_KEY_ID}:${process.env.AWS_SECRET_ACCESS_KEY ?? \"\"}:${process.env.AWS_SESSION_TOKEN ?? \"\"}`;\n\t\t\t}\n\t\t\tif (process.env.AWS_BEARER_TOKEN_BEDROCK) {\n\t\t\t\treturn `amazon-bedrock:bearer:${process.env.AWS_BEARER_TOKEN_BEDROCK}`;\n\t\t\t}\n\t\t\tif (process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI) {\n\t\t\t\treturn `amazon-bedrock:ecs-relative:${process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI}`;\n\t\t\t}\n\t\t\tif (process.env.AWS_CONTAINER_CREDENTIALS_FULL_URI) {\n\t\t\t\treturn `amazon-bedrock:ecs-full:${process.env.AWS_CONTAINER_CREDENTIALS_FULL_URI}`;\n\t\t\t}\n\t\t\tif (process.env.AWS_WEB_IDENTITY_TOKEN_FILE) {\n\t\t\t\treturn `amazon-bedrock:web-identity:${process.env.AWS_WEB_IDENTITY_TOKEN_FILE}`;\n\t\t\t}\n\t\t}\n\t\tif (provider === \"google-vertex\") {\n\t\t\tconst project = process.env.GOOGLE_CLOUD_PROJECT ?? process.env.GCLOUD_PROJECT ?? \"\";\n\t\t\tconst location = process.env.GOOGLE_CLOUD_LOCATION ?? \"\";\n\t\t\tconst credentialsPath = process.env.GOOGLE_APPLICATION_CREDENTIALS ?? \"application-default\";\n\t\t\treturn `google-vertex:${project}:${location}:${credentialsPath}`;\n\t\t}\n\t\treturn provider;\n\t}\n\n\tprivate getFallbackAuthCandidate(provider: string): AuthSourceCandidate | undefined {\n\t\tconst apiKey = this.fallbackResolver?.(provider);\n\t\tif (!apiKey) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn this.createAuthSourceCandidate({\n\t\t\tconfigured: false,\n\t\t\tsource: \"fallback\",\n\t\t\tlabel: \"custom provider config\",\n\t\t\tidentityMaterial: provider,\n\t\t\tvalueMaterial: apiKey,\n\t\t});\n\t}\n\n\tprivate getAuthSourceCandidates(provider: string, options?: { includeFallback?: boolean }): AuthSourceCandidate[] {\n\t\tconst fallbackCandidate =\n\t\t\toptions?.includeFallback === false ? undefined : this.getFallbackAuthCandidate(provider);\n\t\tconst candidates =\n\t\t\tprovider === PRIME_INFERENCE_PROVIDER_ID\n\t\t\t\t? [\n\t\t\t\t\t\tthis.getRuntimeAuthCandidate(provider),\n\t\t\t\t\t\tthis.getEnvironmentAuthCandidate(provider),\n\t\t\t\t\t\tthis.getPrimeCliAuthCandidate(provider),\n\t\t\t\t\t\tthis.getStoredAuthCandidate(provider),\n\t\t\t\t\t\tfallbackCandidate,\n\t\t\t\t\t]\n\t\t\t\t: [\n\t\t\t\t\t\tthis.getRuntimeAuthCandidate(provider),\n\t\t\t\t\t\tthis.getStoredAuthCandidate(provider),\n\t\t\t\t\t\tthis.getEnvironmentAuthCandidate(provider),\n\t\t\t\t\t\tfallbackCandidate,\n\t\t\t\t\t];\n\t\treturn candidates.filter((candidate): candidate is AuthSourceCandidate => candidate !== undefined);\n\t}\n\n\tprivate isAuthSourceStale(provider: string, candidate: AuthSourceCandidate): boolean {\n\t\tconst matchingStale = this.getMatchingStaleAuthSources(provider, candidate);\n\t\tif (matchingStale.length === 0) {\n\t\t\treturn false;\n\t\t}\n\t\tconst valueFingerprint = candidate.valueFingerprint ?? candidate.resolveValueFingerprint?.();\n\t\treturn Boolean(valueFingerprint && matchingStale.some((token) => token.valueFingerprint === valueFingerprint));\n\t}\n\n\tprivate getMatchingStaleAuthSources(provider: string, candidate: AuthSourceCandidate): AuthSourceToken[] {\n\t\tconst stale = this.staleAuthSources.get(provider);\n\t\tif (!stale) {\n\t\t\treturn [];\n\t\t}\n\t\treturn stale.filter(\n\t\t\t(token) => token.source === candidate.source && token.identityFingerprint === candidate.identityFingerprint,\n\t\t);\n\t}\n\n\tprivate getAvailableAuthCandidate(\n\t\tprovider: string,\n\t\toptions?: { includeFallback?: boolean },\n\t): { candidate?: AuthSourceCandidate; hasStaleCandidate: boolean } {\n\t\tlet hasStaleCandidate = false;\n\t\tfor (const candidate of this.getAuthSourceCandidates(provider, options)) {\n\t\t\tif (this.isAuthSourceStale(provider, candidate)) {\n\t\t\t\thasStaleCandidate = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\treturn { candidate, hasStaleCandidate };\n\t\t}\n\t\treturn { hasStaleCandidate };\n\t}\n\n\tprivate toAuthStatus(candidate: AuthSourceCandidate): AuthStatus {\n\t\treturn {\n\t\t\tconfigured: candidate.configured,\n\t\t\tsource: candidate.source,\n\t\t\t...(candidate.label ? { label: candidate.label } : {}),\n\t\t};\n\t}\n\n\tprivate getAuthStatusFromCandidates(provider: string): AuthStatus {\n\t\tconst { candidate, hasStaleCandidate } = this.getAvailableAuthCandidate(provider);\n\t\tif (candidate) {\n\t\t\treturn this.toAuthStatus(candidate);\n\t\t}\n\t\tif (hasStaleCandidate) {\n\t\t\treturn { configured: false, source: \"stale\", label: \"expired\" };\n\t\t}\n\t\treturn { configured: false };\n\t}\n\n\tmarkAuthStale(provider: string): boolean {\n\t\tconst token = this.getCurrentAuthSourceToken(provider);\n\t\treturn token ? this.markAuthSourceStale(token) : false;\n\t}\n\n\tprivate getAuthSourceTokenForCandidate(\n\t\tprovider: string,\n\t\tcandidate: AuthSourceCandidate,\n\t): AuthSourceToken | undefined {\n\t\tconst valueFingerprint = candidate.valueFingerprint ?? candidate.resolveValueFingerprint?.();\n\t\tif (!valueFingerprint) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn {\n\t\t\tprovider,\n\t\t\tsource: candidate.source,\n\t\t\tidentityFingerprint: candidate.identityFingerprint,\n\t\t\tvalueFingerprint,\n\t\t};\n\t}\n\n\tgetCurrentAuthSourceToken(provider: string): AuthSourceToken | undefined {\n\t\tconst { candidate } = this.getAvailableAuthCandidate(provider);\n\t\tif (!candidate) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn this.getAuthSourceTokenForCandidate(provider, candidate);\n\t}\n\n\tmarkAuthSourceStale(token: AuthSourceToken): boolean {\n\t\tif (token.provider.length === 0) {\n\t\t\treturn false;\n\t\t}\n\t\tconst stale = this.staleAuthSources.get(token.provider) ?? [];\n\t\tif (\n\t\t\t!stale.some(\n\t\t\t\t(existing) =>\n\t\t\t\t\texisting.source === token.source &&\n\t\t\t\t\texisting.identityFingerprint === token.identityFingerprint &&\n\t\t\t\t\texisting.valueFingerprint === token.valueFingerprint,\n\t\t\t)\n\t\t) {\n\t\t\tstale.push(token);\n\t\t}\n\t\tthis.staleAuthSources.set(token.provider, stale);\n\t\treturn true;\n\t}\n\n\tprivate clearStaleAuthSource(provider: string, source: ActiveAuthStatusSource): void {\n\t\tconst stale = this.staleAuthSources.get(provider);\n\t\tif (!stale) {\n\t\t\treturn;\n\t\t}\n\t\tconst next = stale.filter((token) => token.source !== source);\n\t\tif (next.length === 0) {\n\t\t\tthis.staleAuthSources.delete(provider);\n\t\t} else {\n\t\t\tthis.staleAuthSources.set(provider, next);\n\t\t}\n\t}\n\n\tprivate parseStorageData(content: string | undefined): AuthStorageData {\n\t\tif (!content) {\n\t\t\treturn {};\n\t\t}\n\t\treturn JSON.parse(content) as AuthStorageData;\n\t}\n\n\t/**\n\t * Reload credentials from storage.\n\t */\n\treload(): void {\n\t\tlet content: string | undefined;\n\t\ttry {\n\t\t\tthis.storage.withLock((current) => {\n\t\t\t\tcontent = current;\n\t\t\t\treturn { result: undefined };\n\t\t\t});\n\t\t\tthis.data = this.parseStorageData(content);\n\t\t\tthis.loadError = null;\n\t\t} catch (error) {\n\t\t\tthis.loadError = error as Error;\n\t\t\tthis.recordError(error);\n\t\t}\n\t}\n\n\tprivate persistProviderChange(provider: string, credential: AuthCredential | undefined): void {\n\t\tif (this.loadError) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tthis.storage.withLock((current) => {\n\t\t\t\tconst currentData = this.parseStorageData(current);\n\t\t\t\tconst merged: AuthStorageData = { ...currentData };\n\t\t\t\tif (credential) {\n\t\t\t\t\tmerged[provider] = credential;\n\t\t\t\t} else {\n\t\t\t\t\tdelete merged[provider];\n\t\t\t\t}\n\t\t\t\treturn { result: undefined, next: JSON.stringify(merged, null, 2) };\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthis.recordError(error);\n\t\t}\n\t}\n\n\t/**\n\t * Get credential for a provider.\n\t */\n\tget(provider: string): AuthCredential | undefined {\n\t\tconst direct = this.data[provider];\n\t\tif (direct) return direct;\n\t\tif (provider === \"google\") return this.data.gemini;\n\t\tif (provider === \"gemini\") return this.data.google;\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Set credential for a provider.\n\t */\n\tset(provider: string, credential: AuthCredential): void {\n\t\tthis.clearStaleAuthSource(provider, \"stored\");\n\t\tthis.data[provider] = credential;\n\t\tthis.persistProviderChange(provider, credential);\n\t}\n\n\t/**\n\t * Remove credential for a provider.\n\t */\n\tremove(provider: string): void {\n\t\tthis.clearStaleAuthSource(provider, \"stored\");\n\t\tdelete this.data[provider];\n\t\tthis.persistProviderChange(provider, undefined);\n\t}\n\n\t/**\n\t * Remove a provider's credential with the disk write verified: throws on any\n\t * load or write failure instead of recording it, so callers can refuse to\n\t * proceed while the credential may still exist on disk. Disk-authoritative\n\t * and idempotent — in-memory state is only updated after the write succeeds.\n\t */\n\tremoveVerified(provider: string): void {\n\t\tthis.storage.withLock((current) => {\n\t\t\tconst currentData = this.parseStorageData(current);\n\t\t\tif (!(provider in currentData)) return { result: undefined };\n\t\t\tconst merged: AuthStorageData = { ...currentData };\n\t\t\tdelete merged[provider];\n\t\t\treturn { result: undefined, next: JSON.stringify(merged, null, 2) };\n\t\t});\n\t\tdelete this.data[provider];\n\t\t// Post-success only: a failed removal must not make a stale-marked credential selectable again.\n\t\tthis.clearStaleAuthSource(provider, \"stored\");\n\t}\n\n\t/**\n\t * List all providers with credentials.\n\t */\n\tlist(): string[] {\n\t\treturn Object.keys(this.data);\n\t}\n\n\t/**\n\t * Check if credentials exist for a provider in auth.json.\n\t */\n\thas(provider: string): boolean {\n\t\treturn (\n\t\t\tprovider in this.data ||\n\t\t\t(provider === \"google\" && \"gemini\" in this.data) ||\n\t\t\t(provider === \"gemini\" && \"google\" in this.data)\n\t\t);\n\t}\n\n\t/**\n\t * Check if any form of auth is configured for a provider.\n\t * Unlike getApiKey(), this doesn't refresh OAuth tokens.\n\t */\n\thasAuth(provider: string): boolean {\n\t\treturn this.getAvailableAuthCandidate(provider).candidate !== undefined;\n\t}\n\n\t/**\n\t * Return auth status without exposing credential values or refreshing tokens.\n\t */\n\tgetAuthStatus(provider: string): AuthStatus {\n\t\treturn this.getAuthStatusFromCandidates(provider);\n\t}\n\n\t/**\n\t * Get all credentials (for passing to getOAuthApiKey).\n\t */\n\tgetAll(): AuthStorageData {\n\t\treturn { ...this.data };\n\t}\n\n\tdrainErrors(): Error[] {\n\t\tconst drained = [...this.errors];\n\t\tthis.errors = [];\n\t\treturn drained;\n\t}\n\n\t/**\n\t * Login to an OAuth provider.\n\t */\n\tasync login(providerId: OAuthProviderId, callbacks: OAuthLoginCallbacks): Promise<void> {\n\t\tconst provider = getOAuthProvider(providerId);\n\t\tif (!provider) {\n\t\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t\t}\n\n\t\tconst credentials = await provider.login(callbacks);\n\t\tthis.set(providerId, { type: \"oauth\", ...credentials });\n\t}\n\n\t/**\n\t * Logout from a provider.\n\t */\n\tlogout(provider: string): void {\n\t\tif (provider === PRIME_INFERENCE_PROVIDER_ID && this.isPrimeCliConfigEnabled()) {\n\t\t\ttry {\n\t\t\t\tclearPrimeCliCredentials(this.getEnabledPrimeCliConfigPath());\n\t\t\t\tthis.clearStaleAuthSource(provider, \"prime_cli\");\n\t\t\t} catch (error) {\n\t\t\t\tthis.recordError(error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\t\tthis.remove(provider);\n\t}\n\n\t/**\n\t * Refresh OAuth token with backend locking to prevent race conditions.\n\t * Multiple pi instances may try to refresh simultaneously when tokens expire.\n\t */\n\tprivate async refreshOAuthTokenWithLock(\n\t\tproviderId: OAuthProviderId,\n\t): Promise<{ apiKey: string; newCredentials: OAuthCredentials } | null> {\n\t\tconst provider = getOAuthProvider(providerId);\n\t\tif (!provider) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst result = await this.storage.withLockAsync(async (current) => {\n\t\t\tconst currentData = this.parseStorageData(current);\n\t\t\tthis.data = currentData;\n\t\t\tthis.loadError = null;\n\n\t\t\tconst storageKey = currentData[providerId]\n\t\t\t\t? providerId\n\t\t\t\t: providerId === \"google\" && currentData.gemini\n\t\t\t\t\t? \"gemini\"\n\t\t\t\t\t: providerId === \"gemini\" && currentData.google\n\t\t\t\t\t\t? \"google\"\n\t\t\t\t\t\t: providerId;\n\t\t\tconst cred = currentData[storageKey];\n\t\t\tif (cred?.type !== \"oauth\") {\n\t\t\t\treturn { result: null };\n\t\t\t}\n\n\t\t\tif (Date.now() < cred.expires) {\n\t\t\t\treturn { result: { apiKey: provider.getApiKey(cred), newCredentials: cred } };\n\t\t\t}\n\n\t\t\tconst oauthCreds: Record<string, OAuthCredentials> = {};\n\t\t\tfor (const [key, value] of Object.entries(currentData)) {\n\t\t\t\tif (value.type === \"oauth\") {\n\t\t\t\t\toauthCreds[key] = value;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst refreshed = await getOAuthApiKey(providerId, oauthCreds);\n\t\t\tif (!refreshed) {\n\t\t\t\treturn { result: null };\n\t\t\t}\n\n\t\t\tconst merged: AuthStorageData = {\n\t\t\t\t...currentData,\n\t\t\t\t[providerId]: { type: \"oauth\", ...refreshed.newCredentials },\n\t\t\t};\n\t\t\tthis.data = merged;\n\t\t\tthis.loadError = null;\n\t\t\treturn { result: refreshed, next: JSON.stringify(merged, null, 2) };\n\t\t});\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Get API key for a provider.\n\t * Priority:\n\t * 1. Runtime override (CLI --api-key)\n\t * 2. Prime Inference: environment variable, Prime CLI config, auth.json\n\t * 3. Other providers: auth.json, environment variable\n\t * 4. Fallback resolver (models.json custom providers)\n\t */\n\tasync getApiKeyWithSourceToken(\n\t\tproviderId: string,\n\t\toptions?: { includeFallback?: boolean },\n\t): Promise<AuthApiKeyResult> {\n\t\t// Runtime overrides take precedence over stored credentials and environment keys.\n\t\tconst runtimeCandidate = this.getRuntimeAuthCandidate(providerId);\n\t\tconst runtimeKey = this.runtimeOverrides.get(providerId);\n\t\tif (runtimeKey && runtimeCandidate && !this.isAuthSourceStale(providerId, runtimeCandidate)) {\n\t\t\treturn {\n\t\t\t\tapiKey: runtimeKey,\n\t\t\t\tsourceToken: this.getAuthSourceTokenForCandidate(providerId, runtimeCandidate),\n\t\t\t};\n\t\t}\n\n\t\tconst envCandidate = this.getEnvironmentAuthCandidate(providerId);\n\t\tconst envKey = getEnvApiKey(providerId);\n\t\tif (\n\t\t\tproviderId === PRIME_INFERENCE_PROVIDER_ID &&\n\t\t\tenvKey &&\n\t\t\tenvCandidate &&\n\t\t\t!this.isAuthSourceStale(providerId, envCandidate)\n\t\t) {\n\t\t\treturn {\n\t\t\t\tapiKey: envKey,\n\t\t\t\tsourceToken: this.getAuthSourceTokenForCandidate(providerId, envCandidate),\n\t\t\t};\n\t\t}\n\n\t\tif (providerId === PRIME_INFERENCE_PROVIDER_ID) {\n\t\t\tconst primeCliCandidate = this.getPrimeCliAuthCandidate(providerId);\n\t\t\tconst primeCliKey = this.getPrimeCliApiKey(providerId);\n\t\t\tif (primeCliKey && primeCliCandidate && !this.isAuthSourceStale(providerId, primeCliCandidate)) {\n\t\t\t\treturn {\n\t\t\t\t\tapiKey: primeCliKey,\n\t\t\t\t\tsourceToken: this.getAuthSourceTokenForCandidate(providerId, primeCliCandidate),\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tconst cred = this.get(providerId);\n\n\t\tif (cred?.type === \"api_key\") {\n\t\t\tconst storedCandidate = this.getStoredAuthCandidate(providerId);\n\t\t\tif (storedCandidate && !this.isAuthSourceStale(providerId, storedCandidate)) {\n\t\t\t\tconst hasStaleRecord = this.getMatchingStaleAuthSources(providerId, storedCandidate).length > 0;\n\t\t\t\tconst apiKey =\n\t\t\t\t\tcred.key.startsWith(\"!\") && hasStaleRecord\n\t\t\t\t\t\t? resolveConfigValueUncached(cred.key)\n\t\t\t\t\t\t: resolveConfigValue(cred.key);\n\t\t\t\tconst sourceToken =\n\t\t\t\t\tapiKey === undefined\n\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t: this.getAuthSourceTokenForCandidate(\n\t\t\t\t\t\t\t\tproviderId,\n\t\t\t\t\t\t\t\tcred.key.startsWith(\"!\")\n\t\t\t\t\t\t\t\t\t? (this.getStoredAuthCandidate(providerId, { resolvedCommandValue: apiKey }) ??\n\t\t\t\t\t\t\t\t\t\t\tstoredCandidate)\n\t\t\t\t\t\t\t\t\t: storedCandidate,\n\t\t\t\t\t\t\t);\n\t\t\t\treturn { apiKey, sourceToken };\n\t\t\t}\n\t\t}\n\n\t\tif (cred?.type === \"oauth\") {\n\t\t\tconst storedCandidate = this.getStoredAuthCandidate(providerId);\n\t\t\tif (storedCandidate && !this.isAuthSourceStale(providerId, storedCandidate)) {\n\t\t\t\tconst provider = getOAuthProvider(providerId);\n\t\t\t\tif (!provider) {\n\t\t\t\t\treturn {};\n\t\t\t\t}\n\t\t\t\t// Lock refreshes so concurrent instances cannot race on the credential file.\n\t\t\t\tconst needsRefresh = Date.now() >= cred.expires;\n\n\t\t\t\tif (needsRefresh) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst result = await this.refreshOAuthTokenWithLock(providerId);\n\t\t\t\t\t\tif (result) {\n\t\t\t\t\t\t\tconst refreshedCandidate = this.getStoredAuthCandidate(providerId);\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tapiKey: result.apiKey,\n\t\t\t\t\t\t\t\tsourceToken: refreshedCandidate\n\t\t\t\t\t\t\t\t\t? this.getAuthSourceTokenForCandidate(providerId, refreshedCandidate)\n\t\t\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tthis.recordError(error);\n\t\t\t\t\t\t// A peer may have refreshed successfully; reload before treating this refresh as failed.\n\t\t\t\t\t\tthis.reload();\n\t\t\t\t\t\tconst updatedCred = this.data[providerId];\n\n\t\t\t\t\t\tif (updatedCred?.type === \"oauth\" && Date.now() < updatedCred.expires) {\n\t\t\t\t\t\t\tconst updatedCandidate = this.getStoredAuthCandidate(providerId);\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tapiKey: provider.getApiKey(updatedCred),\n\t\t\t\t\t\t\t\tsourceToken: updatedCandidate\n\t\t\t\t\t\t\t\t\t? this.getAuthSourceTokenForCandidate(providerId, updatedCandidate)\n\t\t\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Preserve credentials for a later /login retry while discovery skips this provider.\n\t\t\t\t\t\treturn {};\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tapiKey: provider.getApiKey(cred),\n\t\t\t\t\t\tsourceToken: this.getAuthSourceTokenForCandidate(providerId, storedCandidate),\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Stored auth wins over environment variables for non-Prime-Inference providers.\n\t\tif (\n\t\t\tproviderId !== PRIME_INFERENCE_PROVIDER_ID &&\n\t\t\tenvKey &&\n\t\t\tenvCandidate &&\n\t\t\t!this.isAuthSourceStale(providerId, envCandidate)\n\t\t) {\n\t\t\treturn {\n\t\t\t\tapiKey: envKey,\n\t\t\t\tsourceToken: this.getAuthSourceTokenForCandidate(providerId, envCandidate),\n\t\t\t};\n\t\t}\n\t\tif (options?.includeFallback !== false) {\n\t\t\tconst fallbackCandidate = this.getFallbackAuthCandidate(providerId);\n\t\t\tif (fallbackCandidate && !this.isAuthSourceStale(providerId, fallbackCandidate)) {\n\t\t\t\treturn {\n\t\t\t\t\tapiKey: this.fallbackResolver?.(providerId) ?? undefined,\n\t\t\t\t\tsourceToken: this.getAuthSourceTokenForCandidate(providerId, fallbackCandidate),\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\treturn {};\n\t}\n\n\tasync getApiKey(providerId: string, options?: { includeFallback?: boolean }): Promise<string | undefined> {\n\t\tconst result = await this.getApiKeyWithSourceToken(providerId, options);\n\t\treturn result.apiKey;\n\t}\n\n\t/**\n\t * Get all registered OAuth providers\n\t */\n\tgetOAuthProviders() {\n\t\treturn getOAuthProviders();\n\t}\n\n\tsetPrimeInferenceTeamSelection(team: PrimeTeam | null): void {\n\t\tif (this.isPrimeCliConfigEnabled()) {\n\t\t\ttry {\n\t\t\t\tsavePrimeCliTeamSelection(team, this.getEnabledPrimeCliConfigPath());\n\t\t\t} catch (error) {\n\t\t\t\tthis.recordError(error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst credential = this.data[PRIME_INFERENCE_PROVIDER_ID];\n\t\tif (credential?.type !== \"api_key\") {\n\t\t\treturn;\n\t\t}\n\t\tthis.set(PRIME_INFERENCE_PROVIDER_ID, {\n\t\t\t...credential,\n\t\t\tprimeTeam: team ? this.toPrimeTeamCredential(team) : null,\n\t\t});\n\t}\n\n\tsetPrimeInferenceApiKey(apiKey: string): void {\n\t\tif (this.isPrimeCliConfigEnabled()) {\n\t\t\ttry {\n\t\t\t\tconst configPath = this.getEnabledPrimeCliConfigPath();\n\t\t\t\tconst config = loadPrimeCliConfig(configPath);\n\t\t\t\tconst existingCredential = this.data[PRIME_INFERENCE_PROVIDER_ID];\n\t\t\t\tconst legacyPrimeTeam = existingCredential?.type === \"api_key\" ? existingCredential.primeTeam : undefined;\n\t\t\t\tif (config.apiKey !== apiKey) {\n\t\t\t\t\tsavePrimeCliApiKey(apiKey, configPath);\n\t\t\t\t} else if (!config.teamIdFromEnv && (legacyPrimeTeam === null || (!config.teamId && legacyPrimeTeam))) {\n\t\t\t\t\tsavePrimeCliTeamSelection(legacyPrimeTeam, configPath);\n\t\t\t\t}\n\t\t\t\tthis.clearStaleAuthSource(PRIME_INFERENCE_PROVIDER_ID, \"prime_cli\");\n\t\t\t} catch (error) {\n\t\t\t\tthis.recordError(error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tif (this.data[PRIME_INFERENCE_PROVIDER_ID]) {\n\t\t\t\tthis.remove(PRIME_INFERENCE_PROVIDER_ID);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst existingCredential = this.data[PRIME_INFERENCE_PROVIDER_ID];\n\t\tconst existingPrimeTeam = existingCredential?.type === \"api_key\" ? existingCredential.primeTeam : undefined;\n\t\tthis.set(PRIME_INFERENCE_PROVIDER_ID, {\n\t\t\ttype: \"api_key\",\n\t\t\tkey: apiKey,\n\t\t\t...(existingPrimeTeam !== undefined ? { primeTeam: existingPrimeTeam } : {}),\n\t\t});\n\t}\n\n\tgetPrimeInferenceTeamSelection(): PrimeTeamCredential | null | undefined {\n\t\tlet config: PrimeCliConfig | undefined;\n\t\tif (this.isPrimeCliConfigEnabled()) {\n\t\t\tconfig = this.getPrimeCliConfig(PRIME_INFERENCE_PROVIDER_ID);\n\t\t\tif (config?.teamIdFromEnv) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t}\n\n\t\tconst credential = this.data[PRIME_INFERENCE_PROVIDER_ID];\n\t\tconst authSource = this.getAuthStatus(PRIME_INFERENCE_PROVIDER_ID).source;\n\t\tif (authSource === \"runtime\" || authSource === \"environment\") {\n\t\t\treturn undefined;\n\t\t}\n\t\tif (authSource === \"prime_cli\") {\n\t\t\tif (credential?.type === \"api_key\" && credential.primeTeam === null) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tif (config?.teamId) {\n\t\t\t\treturn this.toPrimeTeamCredential({\n\t\t\t\t\tteamId: config.teamId,\n\t\t\t\t\tname: config.teamName ?? \"Prime CLI team\",\n\t\t\t\t\t...(config.teamRole ? { role: config.teamRole } : {}),\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (credential?.type === \"api_key\" && credential.primeTeam) {\n\t\t\t\treturn credential.primeTeam;\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\t\tif (credential?.type === \"api_key\" && credential.primeTeam !== undefined) {\n\t\t\treturn credential.primeTeam;\n\t\t}\n\t\tif (!config?.apiKey && config?.teamId) {\n\t\t\treturn this.toPrimeTeamCredential({\n\t\t\t\tteamId: config.teamId,\n\t\t\t\tname: config.teamName ?? \"Prime CLI team\",\n\t\t\t\t...(config.teamRole ? { role: config.teamRole } : {}),\n\t\t\t});\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tgetProviderHeaders(providerId: string): Record<string, string> | undefined {\n\t\tif (providerId !== PRIME_INFERENCE_PROVIDER_ID) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst primeCliConfig = this.getPrimeCliConfig(providerId);\n\t\tif (primeCliConfig?.teamIdFromEnv) {\n\t\t\treturn primeCliConfig.teamId ? { \"X-Prime-Team-ID\": primeCliConfig.teamId } : undefined;\n\t\t}\n\n\t\tconst teamId = this.getPrimeInferenceTeamSelection()?.teamId;\n\t\treturn teamId ? { \"X-Prime-Team-ID\": teamId } : undefined;\n\t}\n\n\tgetPrimeCliConfigPath(): string | undefined {\n\t\tif (!this.isPrimeCliConfigEnabled()) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn getPrimeCliConfigPath(this.options.primeCliConfigPath);\n\t}\n\n\tprivate toPrimeTeamCredential(team: PrimeTeam): PrimeTeamCredential {\n\t\tconst credential: PrimeTeamCredential = {\n\t\t\tteamId: team.teamId,\n\t\t\tname: team.name,\n\t\t};\n\t\tif (team.slug) {\n\t\t\tcredential.slug = team.slug;\n\t\t}\n\t\tif (team.role) {\n\t\t\tcredential.role = team.role;\n\t\t}\n\t\tif (team.createdAt) {\n\t\t\tcredential.createdAt = team.createdAt;\n\t\t}\n\t\treturn credential;\n\t}\n\n\tprivate getPrimeCliConfig(providerId: string): PrimeCliConfig | undefined {\n\t\tif (providerId !== PRIME_INFERENCE_PROVIDER_ID) {\n\t\t\treturn undefined;\n\t\t}\n\t\tif (!this.isPrimeCliConfigEnabled()) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn loadPrimeCliConfig(this.options.primeCliConfigPath);\n\t}\n\n\tprivate getPrimeCliApiKey(providerId: string): string | undefined {\n\t\treturn this.getPrimeCliConfig(providerId)?.apiKey;\n\t}\n\n\tprivate getEnabledPrimeCliConfigPath(): string {\n\t\tconst configPath = this.getPrimeCliConfigPath();\n\t\tif (!configPath) {\n\t\t\tthrow new Error(\"Prime CLI config is not enabled\");\n\t\t}\n\t\treturn configPath;\n\t}\n\n\tprivate isPrimeCliConfigEnabled(): boolean {\n\t\treturn Boolean(this.options.usePrimeCliConfig || this.options.primeCliConfigPath);\n\t}\n}\n"]}
|
|
@@ -266,7 +266,8 @@ export class AuthStorage {
|
|
|
266
266
|
};
|
|
267
267
|
}
|
|
268
268
|
getStoredAuthCandidate(provider, options) {
|
|
269
|
-
const credential = this.data[provider]
|
|
269
|
+
const credential = this.data[provider] ??
|
|
270
|
+
(provider === "google" ? this.data.gemini : provider === "gemini" ? this.data.google : undefined);
|
|
270
271
|
if (!credential) {
|
|
271
272
|
return undefined;
|
|
272
273
|
}
|
|
@@ -505,7 +506,14 @@ export class AuthStorage {
|
|
|
505
506
|
* Get credential for a provider.
|
|
506
507
|
*/
|
|
507
508
|
get(provider) {
|
|
508
|
-
|
|
509
|
+
const direct = this.data[provider];
|
|
510
|
+
if (direct)
|
|
511
|
+
return direct;
|
|
512
|
+
if (provider === "google")
|
|
513
|
+
return this.data.gemini;
|
|
514
|
+
if (provider === "gemini")
|
|
515
|
+
return this.data.google;
|
|
516
|
+
return undefined;
|
|
509
517
|
}
|
|
510
518
|
/**
|
|
511
519
|
* Set credential for a provider.
|
|
@@ -552,7 +560,9 @@ export class AuthStorage {
|
|
|
552
560
|
* Check if credentials exist for a provider in auth.json.
|
|
553
561
|
*/
|
|
554
562
|
has(provider) {
|
|
555
|
-
return provider in this.data
|
|
563
|
+
return (provider in this.data ||
|
|
564
|
+
(provider === "google" && "gemini" in this.data) ||
|
|
565
|
+
(provider === "gemini" && "google" in this.data));
|
|
556
566
|
}
|
|
557
567
|
/**
|
|
558
568
|
* Check if any form of auth is configured for a provider.
|
|
@@ -618,7 +628,14 @@ export class AuthStorage {
|
|
|
618
628
|
const currentData = this.parseStorageData(current);
|
|
619
629
|
this.data = currentData;
|
|
620
630
|
this.loadError = null;
|
|
621
|
-
const
|
|
631
|
+
const storageKey = currentData[providerId]
|
|
632
|
+
? providerId
|
|
633
|
+
: providerId === "google" && currentData.gemini
|
|
634
|
+
? "gemini"
|
|
635
|
+
: providerId === "gemini" && currentData.google
|
|
636
|
+
? "google"
|
|
637
|
+
: providerId;
|
|
638
|
+
const cred = currentData[storageKey];
|
|
622
639
|
if (cred?.type !== "oauth") {
|
|
623
640
|
return { result: null };
|
|
624
641
|
}
|
|
@@ -684,7 +701,7 @@ export class AuthStorage {
|
|
|
684
701
|
};
|
|
685
702
|
}
|
|
686
703
|
}
|
|
687
|
-
const cred = this.
|
|
704
|
+
const cred = this.get(providerId);
|
|
688
705
|
if (cred?.type === "api_key") {
|
|
689
706
|
const storedCandidate = this.getStoredAuthCandidate(providerId);
|
|
690
707
|
if (storedCandidate && !this.isAuthSourceStale(providerId, storedCandidate)) {
|