diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c63dbbd728..f2cfe8a8393 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -160,8 +160,8 @@ Docs: https://docs.openclaw.ai - CLI backends: keep versioned OAuth identity matches reusable when auth profile ids rotate, so Claude CLI sessions do not reset and lose continuity during same-account OAuth refresh/profile alias changes. Fixes #78541. - Model providers: normalize APNG sniffed PNG uploads, preserve Gemini 3 tool-call thought-signature replay with documented fallback signatures, accept legacy `__env__:VAR` custom-provider keys, and repair snake_case tool-call transcript sanitization. Fixes #51881, #48915, #77566, and #42858. - Telegram/models: parse provider ids containing dots in `/models` callback buttons so `hf.co` model lists render as inline keyboard buttons. Fixes #38745. -- Auth profiles/Bedrock: accept persisted `type: "aws-sdk"` auth profiles so EC2/IMDS and shared AWS credential-chain Bedrock setups are not dropped as `invalid_type`. Fixes #69708. - Amazon Bedrock: refresh shared AWS profile/config file credentials before Bedrock model, discovery, and embedding requests so long-running Gateway processes pick up renewed profile credentials without restart. Fixes #77551. +- Amazon Bedrock: treat named `aws-sdk` auth profiles as config routing metadata instead of stored credentials, and let `doctor --fix` move legacy markers out of `auth-profiles.json`. Fixes #69708. - Anthropic: reject uppercase provider-prefixed forward-compat model ids locally instead of sending malformed dynamic ids upstream. Fixes #73715. - OpenAI/embeddings: pass configured output dimensionality through single and batched embedding requests so memory embedding indexes can request smaller vectors. Fixes #55126. - CLI/infer: normalize HEIC/HEIF image files to JPEG before model-run requests, avoiding providers that reject Apple image container formats. Fixes #50081. diff --git a/docs/auth-credential-semantics.md b/docs/auth-credential-semantics.md index ac7ca384328..2b4b22511e4 100644 --- a/docs/auth-credential-semantics.md +++ b/docs/auth-credential-semantics.md @@ -62,6 +62,18 @@ Explicit copy flows, such as `openclaw agents add`, use this portability policy: Non-portable profiles remain available through read-through inheritance unless the target agent signs in separately and creates its own local profile. +## Config-only auth routes + +`auth.profiles` entries with `mode: "aws-sdk"` are routing metadata, not stored +credentials. They are valid when the target provider uses +`models.providers..auth: "aws-sdk"` or the built-in Amazon Bedrock default +AWS SDK route. These profile ids may appear in `auth.order` and session +overrides even when no matching entry exists in `auth-profiles.json`. + +Do not write `type: "aws-sdk"` into `auth-profiles.json`. If a legacy install +has such a marker, `openclaw doctor --fix` moves it to `auth.profiles` and +removes the marker from the credential store. + ## Explicit auth order filtering - When `auth.order.` or the auth-store order override is set for a diff --git a/docs/gateway/authentication.md b/docs/gateway/authentication.md index d06ee1ac45a..48aa73f822c 100644 --- a/docs/gateway/authentication.md +++ b/docs/gateway/authentication.md @@ -110,6 +110,8 @@ openclaw models auth paste-token --provider openrouter OpenClaw expects the canonical `version` + `profiles` shape at runtime. If an older install still has a flat file such as `{ "openrouter": { "apiKey": "..." } }`, run `openclaw doctor --fix` to rewrite it as an `openrouter:default` API-key profile; doctor keeps a `.legacy-flat.*.bak` copy beside the original. Endpoint details such as `baseUrl`, `api`, model ids, headers, and timeouts belong under `models.providers.` in `openclaw.json` or `models.json`, not in `auth-profiles.json`. +External auth routes such as Bedrock `auth: "aws-sdk"` are also not credentials. If you want a named Bedrock route, put `auth.profiles..mode: "aws-sdk"` in `openclaw.json`; do not write `type: "aws-sdk"` into `auth-profiles.json`. `openclaw doctor --fix` moves legacy AWS SDK markers from the credential store into config metadata. + Auth profile refs are also supported for static credentials: - `api_key` credentials can use `keyRef: { source, provider, id }` diff --git a/src/agents/auth-health.ts b/src/agents/auth-health.ts index 4dc938942c3..ddfc2f68892 100644 --- a/src/agents/auth-health.ts +++ b/src/agents/auth-health.ts @@ -17,7 +17,7 @@ export type AuthProfileHealthStatus = "ok" | "expiring" | "expired" | "missing" type AuthProfileHealth = { profileId: string; provider: string; - type: "oauth" | "token" | "api_key" | "aws-sdk"; + type: "oauth" | "token" | "api_key"; status: AuthProfileHealthStatus; reasonCode?: AuthCredentialReasonCode; expiresAt?: number; @@ -127,17 +127,6 @@ function buildProfileHealth(params: { }; } - if (healthCredential.type === "aws-sdk") { - return { - profileId, - provider, - type: "aws-sdk", - status: "static", - source, - label, - }; - } - if (healthCredential.type === "token") { const eligibility = evaluateStoredCredentialEligibility({ credential: healthCredential, diff --git a/src/agents/auth-profiles.ensureauthprofilestore.test.ts b/src/agents/auth-profiles.ensureauthprofilestore.test.ts index a00be87324d..0f5b42c47a5 100644 --- a/src/agents/auth-profiles.ensureauthprofilestore.test.ts +++ b/src/agents/auth-profiles.ensureauthprofilestore.test.ts @@ -996,25 +996,6 @@ describe("ensureAuthProfileStore", () => { } }); - it("accepts aws-sdk auth profiles without static credential material (#69708)", () => { - withTempAgentDir("openclaw-auth-aws-sdk-", (agentDir) => { - writeAuthProfileStore(agentDir, { - "amazon-bedrock:default": { - type: "aws-sdk", - provider: "amazon-bedrock", - createdAt: "2026-03-15T10:00:00.000Z", - }, - }); - - const profile = loadAuthProfile(agentDir, "amazon-bedrock:default"); - - expect(profile).toMatchObject({ - type: "aws-sdk", - provider: "amazon-bedrock", - }); - }); - }); - it.each([ { name: "migrates SecretRef object in `key` to `keyRef` and clears `key`", diff --git a/src/agents/auth-profiles.resolve-auth-profile-order.does-not-prioritize-lastgood-round-robin-ordering.test.ts b/src/agents/auth-profiles.resolve-auth-profile-order.does-not-prioritize-lastgood-round-robin-ordering.test.ts index ce4d92e4eb5..276095eb0ff 100644 --- a/src/agents/auth-profiles.resolve-auth-profile-order.does-not-prioritize-lastgood-round-robin-ordering.test.ts +++ b/src/agents/auth-profiles.resolve-auth-profile-order.does-not-prioritize-lastgood-round-robin-ordering.test.ts @@ -42,6 +42,67 @@ describe("resolveAuthProfileOrder", () => { const store = ANTHROPIC_STORE; const cfg = ANTHROPIC_CFG; + it("keeps config-only aws-sdk profiles for aws-sdk providers", () => { + const order = resolveAuthProfileOrder({ + cfg: { + models: { + providers: { + "amazon-bedrock": { + auth: "aws-sdk", + baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com", + api: "bedrock-converse-stream", + models: [], + }, + }, + }, + auth: { + order: { + "amazon-bedrock": ["amazon-bedrock:default"], + }, + profiles: { + "amazon-bedrock:default": { + provider: "amazon-bedrock", + mode: "aws-sdk", + }, + }, + }, + }, + store: { version: 1, profiles: {} }, + provider: "amazon-bedrock", + }); + + expect(order).toEqual(["amazon-bedrock:default"]); + }); + + it("rejects config-only aws-sdk profiles for non aws-sdk providers", () => { + const order = resolveAuthProfileOrder({ + cfg: { + models: { + providers: { + anthropic: { + auth: "api-key", + baseUrl: "https://api.anthropic.com", + api: "anthropic-messages", + models: [], + }, + }, + }, + auth: { + profiles: { + "anthropic:aws": { + provider: "anthropic", + mode: "aws-sdk", + }, + }, + }, + }, + store: { version: 1, profiles: {} }, + provider: "anthropic", + }); + + expect(order).toEqual([]); + }); + function resolveWithAnthropicOrderAndUsage(params: { orderSource: "store" | "config"; usageStats: NonNullable; diff --git a/src/agents/auth-profiles.ts b/src/agents/auth-profiles.ts index 0722e0961e2..d54cf7a9260 100644 --- a/src/agents/auth-profiles.ts +++ b/src/agents/auth-profiles.ts @@ -16,7 +16,11 @@ export { type ExternalCliAuthDiscovery, } from "./auth-profiles/external-cli-discovery.js"; export { resolveApiKeyForProfile } from "./auth-profiles/oauth.js"; -export { resolveAuthProfileEligibility, resolveAuthProfileOrder } from "./auth-profiles/order.js"; +export { + isConfiguredAwsSdkAuthProfileForProvider, + resolveAuthProfileEligibility, + resolveAuthProfileOrder, +} from "./auth-profiles/order.js"; export { resolveAuthStatePathForDisplay, resolveAuthStorePathForDisplay, diff --git a/src/agents/auth-profiles/credential-state.ts b/src/agents/auth-profiles/credential-state.ts index c6511e8a52d..4aeb8c7e6ed 100644 --- a/src/agents/auth-profiles/credential-state.ts +++ b/src/agents/auth-profiles/credential-state.ts @@ -85,10 +85,6 @@ export function evaluateStoredCredentialEligibility(params: { return { eligible: true, reasonCode: "ok" }; } - if (credential.type === "aws-sdk") { - return { eligible: true, reasonCode: "ok" }; - } - if (credential.type === "token") { const hasToken = hasConfiguredSecretString(credential.token); const hasTokenRef = hasConfiguredSecretRef(credential.tokenRef); diff --git a/src/agents/auth-profiles/oauth.ts b/src/agents/auth-profiles/oauth.ts index b7511ec66e8..9ce4157542d 100644 --- a/src/agents/auth-profiles/oauth.ts +++ b/src/agents/auth-profiles/oauth.ts @@ -80,7 +80,7 @@ function isProfileConfigCompatible(params: { cfg?: OpenClawConfig; profileId: string; provider: string; - mode: "api_key" | "aws-sdk" | "token" | "oauth"; + mode: "api_key" | "token" | "oauth"; allowOAuthTokenCompatibility?: boolean; }): boolean { const profileConfig = params.cfg?.auth?.profiles?.[params.profileId]; @@ -311,9 +311,6 @@ export async function resolveApiKeyForProfile( } return buildApiKeyProfileResult({ apiKey: key, provider: cred.provider, email: cred.email }); } - if (cred.type === "aws-sdk") { - return null; - } if (cred.type === "token") { const expiryState = resolveTokenExpiryState(cred.expires); if (expiryState === "expired" || expiryState === "invalid_expires") { diff --git a/src/agents/auth-profiles/order.ts b/src/agents/auth-profiles/order.ts index 1b59b4f8964..e9fb60ebee1 100644 --- a/src/agents/auth-profiles/order.ts +++ b/src/agents/auth-profiles/order.ts @@ -24,6 +24,45 @@ export type AuthProfileEligibility = { reasonCode: AuthProfileEligibilityReasonCode; }; +function resolveProviderAuthMode( + cfg: OpenClawConfig | undefined, + provider: string, +): string | undefined { + const providers = cfg?.models?.providers; + if (!providers) { + return undefined; + } + const entry = findNormalizedProviderValue(providers, provider); + const auth = entry?.auth; + return typeof auth === "string" ? auth : undefined; +} + +function providerAllowsAwsSdkAuth(cfg: OpenClawConfig | undefined, provider: string): boolean { + const authMode = resolveProviderAuthMode(cfg, provider); + return ( + authMode === "aws-sdk" || + (authMode === undefined && normalizeProviderId(provider) === "amazon-bedrock") + ); +} + +export function isConfiguredAwsSdkAuthProfileForProvider(params: { + cfg?: OpenClawConfig; + provider: string; + profileId: string; +}): boolean { + const profileConfig = params.cfg?.auth?.profiles?.[params.profileId]; + if (!profileConfig || profileConfig.mode !== "aws-sdk") { + return false; + } + const providerAuthKey = resolveProviderIdForAuth(params.provider, { config: params.cfg }); + if ( + resolveProviderIdForAuth(profileConfig.provider, { config: params.cfg }) !== providerAuthKey + ) { + return false; + } + return providerAllowsAwsSdkAuth(params.cfg, params.provider); +} + export function resolveAuthProfileEligibility(params: { cfg?: OpenClawConfig; store: AuthProfileStore; @@ -34,6 +73,15 @@ export function resolveAuthProfileEligibility(params: { const providerAuthKey = resolveProviderIdForAuth(params.provider, { config: params.cfg }); const cred = params.store.profiles[params.profileId]; if (!cred) { + if ( + isConfiguredAwsSdkAuthProfileForProvider({ + cfg: params.cfg, + provider: params.provider, + profileId: params.profileId, + }) + ) { + return { eligible: true, reasonCode: "ok" }; + } return { eligible: false, reasonCode: "profile_missing" }; } if (resolveProviderIdForAuth(cred.provider, { config: params.cfg }) !== providerAuthKey) { diff --git a/src/agents/auth-profiles/persisted.ts b/src/agents/auth-profiles/persisted.ts index 6bbf41a1420..1276a5aa725 100644 --- a/src/agents/auth-profiles/persisted.ts +++ b/src/agents/auth-profiles/persisted.ts @@ -31,12 +31,7 @@ export type LegacyAuthStore = Record; type CredentialRejectReason = "non_object" | "invalid_type" | "missing_provider"; type RejectedCredentialEntry = { key: string; reason: CredentialRejectReason }; -const AUTH_PROFILE_TYPES = new Set([ - "api_key", - "aws-sdk", - "oauth", - "token", -]); +const AUTH_PROFILE_TYPES = new Set(["api_key", "oauth", "token"]); function normalizeSecretBackedField(params: { entry: Record; @@ -544,14 +539,6 @@ export function applyLegacyAuthStore(store: AuthProfileStore, legacy: LegacyAuth }; continue; } - if (cred.type === "aws-sdk") { - store.profiles[profileId] = { - type: "aws-sdk", - provider: credentialProvider, - ...(cred.email ? { email: cred.email } : {}), - }; - continue; - } if (cred.type === "token") { store.profiles[profileId] = { type: "token", diff --git a/src/agents/auth-profiles/session-override.test.ts b/src/agents/auth-profiles/session-override.test.ts index 42d6874bf7e..b55cb471790 100644 --- a/src/agents/auth-profiles/session-override.test.ts +++ b/src/agents/auth-profiles/session-override.test.ts @@ -25,7 +25,15 @@ const authStoreMocks = vi.hoisted(() => { state.store = { version: 1, profiles: {} }; }, resolveAuthProfileOrder: vi.fn( - ({ store, provider }: { store: AuthProfileStore; provider: string }) => { + ({ + cfg, + store, + provider, + }: { + cfg?: OpenClawConfig; + store: AuthProfileStore; + provider: string; + }) => { const providerKey = normalizeProvider(provider); const ordered = Object.entries(store.order ?? {}).find( ([key]) => normalizeProvider(key) === providerKey, @@ -33,6 +41,18 @@ const authStoreMocks = vi.hoisted(() => { if (ordered) { return ordered; } + const configured = Object.entries(cfg?.auth?.profiles ?? {}) + .filter(([profileId, profile]) => { + if (normalizeProvider(profile.provider) !== providerKey) { + return false; + } + const stored = store.profiles[profileId]; + return !stored || normalizeProvider(stored.provider) === providerKey; + }) + .map(([profileId]) => profileId); + if (configured.length > 0) { + return configured; + } return Object.entries(store.profiles) .filter(([, profile]) => normalizeProvider(profile.provider) === providerKey) .map(([profileId]) => profileId); @@ -47,6 +67,22 @@ vi.mock("./store.js", () => ({ })); vi.mock("./order.js", () => ({ + isConfiguredAwsSdkAuthProfileForProvider: ({ + cfg, + provider, + profileId, + }: { + cfg?: OpenClawConfig; + provider: string; + profileId: string; + }) => { + const normalizeProvider = (value: string) => value.toLowerCase().replace(/[^a-z0-9]+/g, ""); + const profile = cfg?.auth?.profiles?.[profileId]; + return ( + profile?.mode === "aws-sdk" && + normalizeProvider(profile.provider) === normalizeProvider(provider) + ); + }, resolveAuthProfileOrder: authStoreMocks.resolveAuthProfileOrder, })); @@ -157,6 +193,115 @@ describe("resolveSessionAuthProfileOverride", () => { }); }); + it("keeps config-only aws-sdk user overrides", async () => { + await withAuthState(async (state) => { + const agentDir = state.agentDir(); + await fs.mkdir(agentDir, { recursive: true }); + authStoreMocks.state.hasSource = false; + authStoreMocks.state.store = { version: 1, profiles: {} }; + + const sessionEntry: SessionEntry = { + sessionId: "s1", + updatedAt: Date.now(), + authProfileOverride: "amazon-bedrock:default", + authProfileOverrideSource: "user", + }; + const sessionStore = { "agent:main:main": sessionEntry }; + + const resolved = await resolveSessionAuthProfileOverride({ + cfg: { + models: { + providers: { + "amazon-bedrock": { + auth: "aws-sdk", + baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com", + api: "bedrock-converse-stream", + models: [], + }, + }, + }, + auth: { + profiles: { + "amazon-bedrock:default": { + provider: "amazon-bedrock", + mode: "aws-sdk", + }, + }, + }, + } as OpenClawConfig, + provider: "amazon-bedrock", + agentDir, + sessionEntry, + sessionStore, + sessionKey: "agent:main:main", + storePath: undefined, + isNewSession: false, + }); + + expect(resolved).toBe("amazon-bedrock:default"); + expect(sessionEntry.authProfileOverride).toBe("amazon-bedrock:default"); + }); + }); + + it("clears aws-sdk config override when stored profile drifted to another provider", async () => { + await withAuthState(async (state) => { + const agentDir = state.agentDir(); + await fs.mkdir(agentDir, { recursive: true }); + authStoreMocks.state.hasSource = true; + authStoreMocks.state.store = createAuthStoreWithProfiles({ + profiles: { + "amazon-bedrock:default": { + type: "api_key", + provider: "openrouter", + key: "sk-drifted", + }, + }, + }); + + const sessionEntry: SessionEntry = { + sessionId: "s1", + updatedAt: Date.now(), + authProfileOverride: "amazon-bedrock:default", + authProfileOverrideSource: "user", + }; + const sessionStore = { "agent:main:main": sessionEntry }; + + const resolved = await resolveSessionAuthProfileOverride({ + cfg: { + models: { + providers: { + "amazon-bedrock": { + auth: "aws-sdk", + baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com", + api: "bedrock-converse-stream", + models: [], + }, + }, + }, + auth: { + profiles: { + "amazon-bedrock:default": { + provider: "amazon-bedrock", + mode: "aws-sdk", + }, + }, + }, + } as OpenClawConfig, + provider: "amazon-bedrock", + agentDir, + sessionEntry, + sessionStore, + sessionKey: "agent:main:main", + storePath: undefined, + isNewSession: false, + }); + + expect(resolved).toBeUndefined(); + expect(sessionEntry.authProfileOverride).toBeUndefined(); + expect(sessionEntry.authProfileOverrideSource).toBeUndefined(); + }); + }); + it("keeps explicit user override when stored order prefers another profile", async () => { await withAuthState(async (state) => { const agentDir = state.agentDir(); diff --git a/src/agents/auth-profiles/session-override.ts b/src/agents/auth-profiles/session-override.ts index 653ca48342d..e10d7aa9f19 100644 --- a/src/agents/auth-profiles/session-override.ts +++ b/src/agents/auth-profiles/session-override.ts @@ -1,7 +1,10 @@ import type { SessionEntry } from "../../config/sessions/types.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { createLazyImportLoader } from "../../shared/lazy-promise.js"; -import { resolveAuthProfileOrder } from "../auth-profiles/order.js"; +import { + isConfiguredAwsSdkAuthProfileForProvider, + resolveAuthProfileOrder, +} from "../auth-profiles/order.js"; import { ensureAuthProfileStore, hasAnyAuthProfileStoreSource } from "../auth-profiles/store.js"; import { isProfileInCooldown } from "../auth-profiles/usage.js"; import { resolveProviderIdForAuth } from "../provider-auth-aliases.js"; @@ -20,12 +23,24 @@ function isProfileForProvider(params: { profileId: string; store: ReturnType; }): boolean { + const providerKey = resolveProviderIdForAuth(params.provider, { config: params.cfg }); const entry = params.store.profiles[params.profileId]; - if (!entry?.provider) { + if (entry) { + if (!entry.provider) { + return false; + } + return resolveProviderIdForAuth(entry.provider, { config: params.cfg }) === providerKey; + } + if ( + !isConfiguredAwsSdkAuthProfileForProvider({ + cfg: params.cfg, + provider: params.provider, + profileId: params.profileId, + }) + ) { return false; } - const providerKey = resolveProviderIdForAuth(params.provider, { config: params.cfg }); - return resolveProviderIdForAuth(entry.provider, { config: params.cfg }) === providerKey; + return true; } export async function clearSessionAuthProfileOverride(params: { @@ -95,7 +110,11 @@ export async function resolveSessionAuthProfileOverride(params: { ? "user" : undefined); - if (current && !store.profiles[current]) { + if ( + current && + !store.profiles[current] && + !isConfiguredAwsSdkAuthProfileForProvider({ cfg, provider, profileId: current }) + ) { await clearSessionAuthProfileOverride({ sessionEntry, sessionStore, sessionKey, storePath }); current = undefined; } diff --git a/src/agents/auth-profiles/types.ts b/src/agents/auth-profiles/types.ts index 474efb8e0f7..a9a735f5748 100644 --- a/src/agents/auth-profiles/types.ts +++ b/src/agents/auth-profiles/types.ts @@ -29,17 +29,6 @@ export type ApiKeyCredential = { metadata?: Record; }; -export type AwsSdkCredential = { - type: "aws-sdk"; - provider: string; - /** Explicit opt-out for copying this profile when creating another agent. */ - copyToAgents?: boolean; - email?: string; - displayName?: string; - /** Optional provider-specific metadata (e.g., account IDs, regions). */ - metadata?: Record; -}; - export type TokenCredential = { /** * Static bearer-style token (often OAuth access token / PAT). @@ -70,11 +59,7 @@ export type OAuthCredential = OAuthCredentials & { displayName?: string; }; -export type AuthProfileCredential = - | ApiKeyCredential - | AwsSdkCredential - | TokenCredential - | OAuthCredential; +export type AuthProfileCredential = ApiKeyCredential | TokenCredential | OAuthCredential; export type AuthProfileFailureReason = | "auth" diff --git a/src/agents/cli-auth-epoch.ts b/src/agents/cli-auth-epoch.ts index 995fe0312ef..d67aea8c33e 100644 --- a/src/agents/cli-auth-epoch.ts +++ b/src/agents/cli-auth-epoch.ts @@ -99,14 +99,6 @@ function encodeAuthProfileCredential(credential: AuthProfileCredential): string credential.displayName ?? null, encodeUnknown(credential.metadata), ]); - case "aws-sdk": - return JSON.stringify([ - "aws-sdk", - credential.provider, - credential.email ?? null, - credential.displayName ?? null, - encodeUnknown(credential.metadata), - ]); case "token": return JSON.stringify([ "token", diff --git a/src/agents/model-auth.profiles.test.ts b/src/agents/model-auth.profiles.test.ts index 91b3392008f..b3221e8d299 100644 --- a/src/agents/model-auth.profiles.test.ts +++ b/src/agents/model-auth.profiles.test.ts @@ -270,6 +270,21 @@ const BEDROCK_PROVIDER_CFG = { }, } as const; +const BEDROCK_PROVIDER_CFG_WITH_PROFILE = { + ...BEDROCK_PROVIDER_CFG, + auth: { + order: { + "amazon-bedrock": ["amazon-bedrock:default"], + }, + profiles: { + "amazon-bedrock:default": { + provider: "amazon-bedrock", + mode: "aws-sdk", + }, + }, + }, +} as const; + async function resolveBedrockProvider() { return resolveApiKeyForProvider({ provider: "amazon-bedrock", @@ -290,59 +305,35 @@ async function expectBedrockAuthSource(params: { }); } -it("resolves persisted aws-sdk auth profiles without static keys (#69708)", async () => { - const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-bedrock-auth-profile-")); - try { - await fs.writeFile( - path.join(agentDir, "auth-profiles.json"), - `${JSON.stringify( - { - version: 1, - profiles: { - "amazon-bedrock:default": { - type: "aws-sdk", - provider: "amazon-bedrock", - createdAt: "2026-03-15T10:00:00.000Z", - }, - }, - }, - null, - 2, - )}\n`, - "utf8", - ); - clearRuntimeAuthProfileStoreSnapshots(); - const store = ensureAuthProfileStore(agentDir); +it("resolves config-only aws-sdk profiles without stored credentials", async () => { + const resolved = await resolveApiKeyForProvider({ + provider: "amazon-bedrock", + profileId: "amazon-bedrock:default", + store: { version: 1, profiles: {} }, + cfg: BEDROCK_PROVIDER_CFG_WITH_PROFILE as never, + }); - const resolved = await resolveApiKeyForProvider({ - provider: "amazon-bedrock", - profileId: "amazon-bedrock:default", - cfg: BEDROCK_PROVIDER_CFG as never, - store, - agentDir, - }); + expect(resolved).toMatchObject({ + mode: "aws-sdk", + profileId: "amazon-bedrock:default", + source: "profile:amazon-bedrock:default", + }); + expect(resolved.apiKey).toBeUndefined(); +}); - expect(resolved).toMatchObject({ - mode: "aws-sdk", - profileId: "amazon-bedrock:default", - source: "profile:amazon-bedrock:default", - }); - expect(resolved.apiKey).toBeUndefined(); - await expect( - hasAvailableAuthForProvider({ - provider: "amazon-bedrock", - cfg: BEDROCK_PROVIDER_CFG as never, - store, - agentDir, - }), - ).resolves.toBe(true); - expect(resolveModelAuthMode("amazon-bedrock", BEDROCK_PROVIDER_CFG as never, store)).toBe( - "aws-sdk", - ); - } finally { - await fs.rm(agentDir, { recursive: true, force: true }); - clearRuntimeAuthProfileStoreSnapshots(); - } +it("uses configured aws-sdk profile order without stored credentials", async () => { + const resolved = await resolveApiKeyForProvider({ + provider: "amazon-bedrock", + store: { version: 1, profiles: {} }, + cfg: BEDROCK_PROVIDER_CFG_WITH_PROFILE as never, + }); + + expect(resolved).toMatchObject({ + mode: "aws-sdk", + profileId: "amazon-bedrock:default", + source: "profile:amazon-bedrock:default", + }); + expect(resolved.apiKey).toBeUndefined(); }); function buildDemoLocalStore(keys: string[]) { diff --git a/src/agents/model-auth.ts b/src/agents/model-auth.ts index 7522989f6d0..b8e0b98e3fa 100644 --- a/src/agents/model-auth.ts +++ b/src/agents/model-auth.ts @@ -25,6 +25,7 @@ import { externalCliDiscoveryForProviderAuth, ensureAuthProfileStore, ensureAuthProfileStoreWithoutExternalProfiles, + isConfiguredAwsSdkAuthProfileForProvider, listProfilesForProvider, resolveApiKeyForProfile, resolveAuthProfileOrder, @@ -44,7 +45,6 @@ import { type ResolvedProviderAuth, } from "./model-auth-runtime-shared.js"; import { normalizeProviderId } from "./model-selection.js"; -import { resolveProviderIdForAuth } from "./provider-auth-aliases.js"; export { ensureAuthProfileStore, @@ -237,50 +237,17 @@ function resolveProviderAuthOverride( } function profileTypeToAuthMode(type: AuthProfileCredential["type"]): ResolvedProviderAuth["mode"] { - return type === "oauth" - ? "oauth" - : type === "token" - ? "token" - : type === "aws-sdk" - ? "aws-sdk" - : "api-key"; + return type === "oauth" ? "oauth" : type === "token" ? "token" : "api-key"; } -function isProfileForProvider(params: { - cfg?: OpenClawConfig; - credential: AuthProfileCredential; - provider: string; -}): boolean { - return ( - resolveProviderIdForAuth(params.credential.provider, { config: params.cfg }) === - resolveProviderIdForAuth(params.provider, { config: params.cfg }) - ); -} - -function resolveAwsSdkProfileAuth(params: { +function resolveConfiguredAwsSdkProfileAuth(params: { cfg?: OpenClawConfig; provider: string; profileId: string; - credential: AuthProfileCredential | undefined; }): ResolvedProviderAuth | null { - if (params.credential?.type !== "aws-sdk") { + if (!isConfiguredAwsSdkAuthProfileForProvider(params)) { return null; } - if ( - !isProfileForProvider({ - cfg: params.cfg, - credential: params.credential, - provider: params.provider, - }) - ) { - return null; - } - const profileConfig = params.cfg?.auth?.profiles?.[params.profileId]; - if (profileConfig) { - if (profileConfig.provider !== params.credential.provider || profileConfig.mode !== "aws-sdk") { - return null; - } - } return { ...resolveAwsSdkAuthInfo(), profileId: params.profileId, @@ -582,8 +549,13 @@ export async function resolveApiKeyForProvider(params: { credentialPrecedence?: ProviderCredentialPrecedence; }): Promise { const { provider, cfg, profileId, preferredProfile } = params; + let scopedStore: AuthProfileStore | undefined = params.store; if (profileId) { + const awsSdkProfileAuth = resolveConfiguredAwsSdkProfileAuth({ cfg, provider, profileId }); + if (awsSdkProfileAuth) { + return awsSdkProfileAuth; + } const store = params.store ?? resolveScopedAuthProfileStore({ @@ -593,15 +565,6 @@ export async function resolveApiKeyForProvider(params: { profileId, preferredProfile, }); - const awsSdkProfileAuth = resolveAwsSdkProfileAuth({ - cfg, - provider, - profileId, - credential: store.profiles[profileId], - }); - if (awsSdkProfileAuth) { - return awsSdkProfileAuth; - } const resolved = await resolveApiKeyForProfile({ cfg, store, @@ -637,6 +600,31 @@ export async function resolveApiKeyForProvider(params: { return result; } + if (cfg?.auth?.profiles || cfg?.auth?.order) { + scopedStore ??= resolveScopedAuthProfileStore({ + agentDir: params.agentDir, + cfg, + provider, + preferredProfile, + }); + const configuredProfileOrder = resolveAuthProfileOrder({ + cfg, + store: scopedStore, + provider, + preferredProfile, + }); + for (const candidate of configuredProfileOrder) { + const awsSdkProfileAuth = resolveConfiguredAwsSdkProfileAuth({ + cfg, + provider, + profileId: candidate, + }); + if (awsSdkProfileAuth) { + return awsSdkProfileAuth; + } + } + } + const authOverride = resolveProviderAuthOverride(cfg, provider); if (authOverride === "aws-sdk") { return resolveAwsSdkAuthInfo(); @@ -688,7 +676,7 @@ export async function resolveApiKeyForProvider(params: { }; } const store = - params.store ?? + scopedStore ?? resolveScopedAuthProfileStore({ agentDir: params.agentDir, cfg, @@ -704,11 +692,10 @@ export async function resolveApiKeyForProvider(params: { let deferredAuthProfileResult: ResolvedProviderAuth | null = null; for (const candidate of order) { try { - const awsSdkProfileAuth = resolveAwsSdkProfileAuth({ + const awsSdkProfileAuth = resolveConfiguredAwsSdkProfileAuth({ cfg, provider, profileId: candidate, - credential: store.profiles[candidate], }); if (awsSdkProfileAuth) { return awsSdkProfileAuth; @@ -843,10 +830,10 @@ export function resolveModelAuthMode( const modes = new Set( profiles .map((id) => authStore.profiles[id]?.type) - .filter((mode): mode is "api_key" | "aws-sdk" | "oauth" | "token" => Boolean(mode)), + .filter((mode): mode is "api_key" | "oauth" | "token" => Boolean(mode)), ); - const distinct = ["oauth", "token", "api_key", "aws-sdk"].filter((k) => - modes.has(k as "oauth" | "token" | "api_key" | "aws-sdk"), + const distinct = ["oauth", "token", "api_key"].filter((k) => + modes.has(k as "oauth" | "token" | "api_key"), ); if (distinct.length >= 2) { return "mixed"; @@ -860,9 +847,6 @@ export function resolveModelAuthMode( if (modes.has("api_key")) { return "api-key"; } - if (modes.has("aws-sdk")) { - return "aws-sdk"; - } } if (authOverride === undefined && normalizeProviderId(resolved) === "amazon-bedrock") { @@ -931,14 +915,7 @@ export async function hasAvailableAuthForProvider(params: { }); for (const candidate of order) { try { - if ( - resolveAwsSdkProfileAuth({ - cfg, - provider, - profileId: candidate, - credential: store.profiles[candidate], - }) - ) { + if (resolveConfiguredAwsSdkProfileAuth({ cfg, provider, profileId: candidate })) { return true; } const resolved = await resolveApiKeyForProfile({ diff --git a/src/auto-reply/reply/directive-handling.auth.test.ts b/src/auto-reply/reply/directive-handling.auth.test.ts index 874a3b5257f..78a0449462e 100644 --- a/src/auto-reply/reply/directive-handling.auth.test.ts +++ b/src/auto-reply/reply/directive-handling.auth.test.ts @@ -24,6 +24,21 @@ vi.mock("../../agents/auth-health.js", () => ({ })); vi.mock("../../agents/auth-profiles.js", () => ({ + isConfiguredAwsSdkAuthProfileForProvider: ({ + cfg, + provider, + profileId, + }: { + cfg?: OpenClawConfig; + provider: string; + profileId: string; + }) => { + const profile = cfg?.auth?.profiles?.[profileId]; + return ( + profile?.mode === "aws-sdk" && + profile.provider.trim().toLowerCase() === provider.trim().toLowerCase() + ); + }, isProfileInCooldown: () => false, resolveAuthProfileDisplayLabel: ({ profileId }: { profileId: string }) => profileId, resolveAuthStorePathForDisplay: () => "/tmp/auth-profiles.json", @@ -128,6 +143,72 @@ describe("resolveAuthLabel ref-aware labels", () => { expect(result.label).not.toContain("token:missing"); }); + it("labels config-only aws-sdk profiles as valid in compact mode", async () => { + mockOrder = ["amazon-bedrock:default"]; + const result = await resolveAuthLabel( + "amazon-bedrock", + { + models: { + providers: { + "amazon-bedrock": { + auth: "aws-sdk", + baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com", + api: "bedrock-converse-stream", + models: [], + }, + }, + }, + auth: { + profiles: { + "amazon-bedrock:default": { + provider: "amazon-bedrock", + mode: "aws-sdk", + }, + }, + }, + } as OpenClawConfig, + "/tmp/models.json", + undefined, + "compact", + ); + + expect(result.label).toBe("amazon-bedrock:default aws-sdk"); + expect(result.label).not.toContain("missing"); + }); + + it("labels config-only aws-sdk profiles as valid in verbose mode", async () => { + mockOrder = ["amazon-bedrock:default"]; + const result = await resolveAuthLabel( + "amazon-bedrock", + { + models: { + providers: { + "amazon-bedrock": { + auth: "aws-sdk", + baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com", + api: "bedrock-converse-stream", + models: [], + }, + }, + }, + auth: { + profiles: { + "amazon-bedrock:default": { + provider: "amazon-bedrock", + mode: "aws-sdk", + }, + }, + }, + } as OpenClawConfig, + "/tmp/models.json", + undefined, + "verbose", + ); + + expect(result.label).toContain("amazon-bedrock:default=aws-sdk"); + expect(result.label).not.toContain("missing"); + }); + it("passes workspace scope to env auth labels", async () => { const cfg = { plugins: { allow: ["workspace-auth-label"] } } as OpenClawConfig; resolveEnvApiKeyMock.mockReturnValue({ diff --git a/src/auto-reply/reply/directive-handling.auth.ts b/src/auto-reply/reply/directive-handling.auth.ts index 2dbb1190810..1c107928c74 100644 --- a/src/auto-reply/reply/directive-handling.auth.ts +++ b/src/auto-reply/reply/directive-handling.auth.ts @@ -1,5 +1,6 @@ import { formatRemainingShort } from "../../agents/auth-health.js"; import { + isConfiguredAwsSdkAuthProfileForProvider, isProfileInCooldown, resolveAuthProfileDisplayLabel, resolveAuthStorePathForDisplay, @@ -78,6 +79,13 @@ export const resolveAuthLabel = async ( } const profile = store.profiles[profileId]; const configProfile = cfg.auth?.profiles?.[profileId]; + const configOnlyAwsSdk = !profile + ? isConfiguredAwsSdkAuthProfileForProvider({ cfg, provider, profileId }) + : false; + const more = order.length > 1 ? ` (+${order.length - 1})` : ""; + if (configOnlyAwsSdk) { + return { label: `${profileId} aws-sdk${more}`, source: "" }; + } const missing = !profile || (configProfile?.provider && configProfile.provider !== profile.provider) || @@ -85,7 +93,6 @@ export const resolveAuthLabel = async ( configProfile.mode !== profile.type && !(configProfile.mode === "oauth" && profile.type === "token")); - const more = order.length > 1 ? ` (+${order.length - 1})` : ""; if (missing) { return { label: `${profileId} missing${more}`, source: "" }; } @@ -113,12 +120,6 @@ export const resolveAuthLabel = async ( source: "", }; } - if (profile.type === "aws-sdk") { - return { - label: `${profileId} aws-sdk${more}`, - source: "", - }; - } const display = resolveAuthProfileDisplayLabel({ cfg, store, profileId }); const label = display === profileId ? profileId : display; const exp = formatExpirationLabel(profile.expires, now, formatUntil); @@ -143,6 +144,10 @@ export const resolveAuthLabel = async ( flags.push("cooldown"); } } + if (!profile && isConfiguredAwsSdkAuthProfileForProvider({ cfg, provider, profileId })) { + const suffix = formatFlagsSuffix(flags); + return `${profileId}=aws-sdk${suffix}`; + } if ( !profile || (configProfile?.provider && configProfile.provider !== profile.provider) || @@ -175,10 +180,6 @@ export const resolveAuthLabel = async ( const suffix = formatFlagsSuffix(flags); return `${profileId}=token:${tokenLabel}${suffix}`; } - if (profile.type === "aws-sdk") { - const suffix = formatFlagsSuffix(flags); - return `${profileId}=aws-sdk${suffix}`; - } const display = resolveAuthProfileDisplayLabel({ cfg, store, diff --git a/src/commands/doctor-auth-flat-profiles.test.ts b/src/commands/doctor-auth-flat-profiles.test.ts index 9f6c533c352..c7c99330bed 100644 --- a/src/commands/doctor-auth-flat-profiles.test.ts +++ b/src/commands/doctor-auth-flat-profiles.test.ts @@ -100,4 +100,57 @@ describe("maybeRepairLegacyFlatAuthProfileStores", () => { expect(result.warnings).toEqual([]); expect(JSON.parse(fs.readFileSync(authPath, "utf8"))).toEqual(legacy); }); + + it("moves aws-sdk auth profile markers into config metadata", async () => { + const state = await makeTestState(); + const legacy = { + version: 1, + profiles: { + "amazon-bedrock:default": { + type: "aws-sdk", + createdAt: "2026-03-15T10:00:00.000Z", + }, + "openrouter:default": { + type: "api_key", + provider: "openrouter", + key: "sk-openrouter", + }, + }, + }; + const authPath = await state.writeAuthProfiles(legacy); + const cfg = {}; + + const result = await maybeRepairLegacyFlatAuthProfileStores({ + cfg, + prompter: makePrompter(true), + now: () => 456, + }); + + expect(result.detected).toEqual([authPath]); + expect(result.changes).toHaveLength(1); + expect(result.warnings).toEqual([]); + expect(cfg).toEqual({ + auth: { + profiles: { + "amazon-bedrock:default": { + provider: "amazon-bedrock", + mode: "aws-sdk", + }, + }, + }, + }); + expect(JSON.parse(fs.readFileSync(authPath, "utf8"))).toEqual({ + version: 1, + profiles: { + "openrouter:default": { + type: "api_key", + provider: "openrouter", + key: "sk-openrouter", + }, + }, + }); + expect(JSON.parse(fs.readFileSync(`${authPath}.aws-sdk-profile.456.bak`, "utf8"))).toEqual( + legacy, + ); + }); }); diff --git a/src/commands/doctor-auth-flat-profiles.ts b/src/commands/doctor-auth-flat-profiles.ts index a8f988e8dcc..2812ecd3534 100644 --- a/src/commands/doctor-auth-flat-profiles.ts +++ b/src/commands/doctor-auth-flat-profiles.ts @@ -10,6 +10,7 @@ import { import type { AuthProfileCredential, AuthProfileStore } from "../agents/auth-profiles/types.js"; import { formatCliCommand } from "../cli/command-format.js"; import { resolveStateDir } from "../config/paths.js"; +import type { AuthProfileConfig } from "../config/types.auth.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { loadJsonFile } from "../infra/json-file.js"; import { note } from "../terminal/note.js"; @@ -27,6 +28,20 @@ type LegacyFlatAuthProfileStore = { store: AuthProfileStore; }; +type AwsSdkProfileMarker = { + profileId: string; + provider: string; + email?: string; + displayName?: string; +}; + +type AwsSdkAuthProfileMarkerStore = { + agentDir?: string; + authPath: string; + raw: Record; + profiles: AwsSdkProfileMarker[]; +}; + export type LegacyFlatAuthProfileRepairResult = { detected: string[]; changes: string[]; @@ -47,16 +62,19 @@ function isSafeLegacyProviderKey(key: string): boolean { return key.trim().length > 0 && !UNSAFE_LEGACY_AUTH_PROFILE_KEYS.has(key); } +function extractProviderFromProfileId(profileId: string): string | undefined { + const colon = profileId.indexOf(":"); + if (colon <= 0) { + return undefined; + } + return readNonEmptyString(profileId.slice(0, colon)); +} + function inferLegacyCredentialType( record: Record, ): AuthProfileCredential["type"] | undefined { const explicit = readNonEmptyString(record.type) ?? readNonEmptyString(record.mode); - if ( - explicit === "api_key" || - explicit === "aws-sdk" || - explicit === "token" || - explicit === "oauth" - ) { + if (explicit === "api_key" || explicit === "token" || explicit === "oauth") { return explicit; } if (readNonEmptyString(record.key) ?? readNonEmptyString(record.apiKey)) { @@ -216,6 +234,74 @@ function backupAuthProfileStore(authPath: string, now: () => number): string { return backupPath; } +function backupAwsSdkProfileMarkerStore(authPath: string, now: () => number): string { + const backupPath = `${authPath}.aws-sdk-profile.${now()}.bak`; + fs.copyFileSync(authPath, backupPath); + return backupPath; +} + +function resolveAwsSdkAuthProfileMarkerStore( + candidate: AuthProfileRepairCandidate, +): AwsSdkAuthProfileMarkerStore | null { + if (!fs.existsSync(candidate.authPath)) { + return null; + } + const raw = loadJsonFile(candidate.authPath); + if (!isRecord(raw) || !isRecord(raw.profiles)) { + return null; + } + const markers: AwsSdkProfileMarker[] = []; + for (const [profileId, value] of Object.entries(raw.profiles)) { + if (!isRecord(value)) { + continue; + } + const mode = readNonEmptyString(value.type) ?? readNonEmptyString(value.mode); + if (mode !== "aws-sdk") { + continue; + } + const provider = readNonEmptyString(value.provider) ?? extractProviderFromProfileId(profileId); + if (!provider || !isSafeLegacyProviderKey(provider)) { + continue; + } + markers.push({ + profileId, + provider, + ...(readNonEmptyString(value.email) ? { email: readNonEmptyString(value.email) } : {}), + ...(readNonEmptyString(value.displayName) + ? { displayName: readNonEmptyString(value.displayName) } + : {}), + }); + } + return markers.length > 0 + ? { + ...candidate, + raw, + profiles: markers, + } + : null; +} + +function ensureConfigAuthProfiles(config: OpenClawConfig): Record { + const root = config as Record; + const auth = isRecord(root.auth) ? root.auth : {}; + if (root.auth !== auth) { + root.auth = auth; + } + if (!isRecord(auth.profiles)) { + auth.profiles = {}; + } + return auth.profiles as Record; +} + +function removeAwsSdkProfileMarkers(raw: Record, profileIds: string[]): void { + if (!isRecord(raw.profiles)) { + return; + } + for (const profileId of profileIds) { + delete raw.profiles[profileId]; + } +} + export async function maybeRepairLegacyFlatAuthProfileStores(params: { cfg: OpenClawConfig; prompter: DoctorPrompter; @@ -225,28 +311,45 @@ export async function maybeRepairLegacyFlatAuthProfileStores(params: { const legacyStores = listAuthProfileRepairCandidates(params.cfg) .map(resolveLegacyFlatStore) .filter((entry): entry is LegacyFlatAuthProfileStore => entry !== null); + const awsSdkMarkerStores = listAuthProfileRepairCandidates(params.cfg) + .map(resolveAwsSdkAuthProfileMarkerStore) + .filter((entry): entry is AwsSdkAuthProfileMarkerStore => entry !== null); const result: LegacyFlatAuthProfileRepairResult = { - detected: legacyStores.map((entry) => entry.authPath), + detected: [ + ...legacyStores.map((entry) => entry.authPath), + ...awsSdkMarkerStores.map((entry) => entry.authPath), + ], changes: [], warnings: [], }; - if (legacyStores.length === 0) { + if (legacyStores.length === 0 && awsSdkMarkerStores.length === 0) { return result; } - note( - [ - ...legacyStores.map( - (entry) => `- ${shortenHomePath(entry.authPath)} uses the legacy flat auth profile format.`, - ), + const noteLines = [ + ...legacyStores.map( + (entry) => `- ${shortenHomePath(entry.authPath)} uses the legacy flat auth profile format.`, + ), + ...awsSdkMarkerStores.map( + (entry) => + `- ${shortenHomePath(entry.authPath)} contains aws-sdk profile markers that belong in openclaw.json auth.profiles.`, + ), + ]; + if (legacyStores.length > 0) { + noteLines.push( `- The gateway expects the canonical version/profiles store; ${formatCliCommand("openclaw doctor --fix")} rewrites this legacy shape with a backup.`, - ].join("\n"), - "Auth profiles", - ); + ); + } + if (awsSdkMarkerStores.length > 0) { + noteLines.push( + `- AWS SDK profile markers are routing metadata, not stored credentials; ${formatCliCommand("openclaw doctor --fix")} moves them to config with a backup.`, + ); + } + note(noteLines.join("\n"), "Auth profiles"); const shouldRepair = await params.prompter.confirmAutoFix({ - message: "Rewrite legacy flat auth-profiles.json files now?", + message: "Repair legacy auth-profiles.json files now?", initialValue: true, }); if (!shouldRepair) { @@ -264,6 +367,32 @@ export async function maybeRepairLegacyFlatAuthProfileStores(params: { result.warnings.push(`Failed to rewrite ${shortenHomePath(entry.authPath)}: ${String(err)}`); } } + for (const entry of awsSdkMarkerStores) { + try { + const backupPath = backupAwsSdkProfileMarkerStore(entry.authPath, now); + const configProfiles = ensureConfigAuthProfiles(params.cfg); + for (const marker of entry.profiles) { + configProfiles[marker.profileId] = { + provider: marker.provider, + mode: "aws-sdk", + ...(marker.email ? { email: marker.email } : {}), + ...(marker.displayName ? { displayName: marker.displayName } : {}), + }; + } + removeAwsSdkProfileMarkers( + entry.raw, + entry.profiles.map((profile) => profile.profileId), + ); + fs.writeFileSync(entry.authPath, `${JSON.stringify(entry.raw, null, 2)}\n`); + result.changes.push( + `Moved aws-sdk profile metadata from ${shortenHomePath(entry.authPath)} to auth.profiles (backup: ${shortenHomePath(backupPath)}).`, + ); + } catch (err) { + result.warnings.push( + `Failed to migrate aws-sdk profile markers from ${shortenHomePath(entry.authPath)}: ${String(err)}`, + ); + } + } clearRuntimeAuthProfileStoreSnapshots(); if (result.changes.length > 0) { note(result.changes.map((change) => `- ${change}`).join("\n"), "Doctor changes"); diff --git a/src/commands/models/auth-list.ts b/src/commands/models/auth-list.ts index 41d9631c488..907dd5a90d9 100644 --- a/src/commands/models/auth-list.ts +++ b/src/commands/models/auth-list.ts @@ -46,9 +46,7 @@ function formatTimestamp(value: number | undefined): string | undefined { } function resolveProfileExpiry(profile: AuthProfileCredential): string | undefined { - return profile.type === "oauth" || profile.type === "token" - ? formatTimestamp(profile.expires) - : undefined; + return profile.type === "api_key" ? undefined : formatTimestamp(profile.expires); } function summarizeProfile(params: { diff --git a/src/commands/models/auth.ts b/src/commands/models/auth.ts index 033815c0816..9968591613f 100644 --- a/src/commands/models/auth.ts +++ b/src/commands/models/auth.ts @@ -588,13 +588,10 @@ export function resolveRequestedLoginProviderOrThrow( return resolveRequestedProviderOrThrow(providers, rawProvider); } -function credentialMode(credential: AuthProfileCredential): AuthProfileCredential["type"] { +function credentialMode(credential: AuthProfileCredential): "api_key" | "oauth" | "token" { if (credential.type === "api_key") { return "api_key"; } - if (credential.type === "aws-sdk") { - return "aws-sdk"; - } if (credential.type === "token") { return "token"; } diff --git a/src/commands/models/list.auth-overview.ts b/src/commands/models/list.auth-overview.ts index 19d8235cd41..86442763fdf 100644 --- a/src/commands/models/list.auth-overview.ts +++ b/src/commands/models/list.auth-overview.ts @@ -110,9 +110,6 @@ export function resolveProviderAuthOverview(params: { profileId, ); } - if (profile.type === "aws-sdk") { - return withUnusableSuffix(`${profileId}=AWS SDK`, profileId); - } const display = resolveAuthProfileDisplayLabel({ cfg, store, profileId }); const suffix = display === profileId @@ -126,7 +123,6 @@ export function resolveProviderAuthOverview(params: { const oauthCount = profiles.filter((id) => store.profiles[id]?.type === "oauth").length; const tokenCount = profiles.filter((id) => store.profiles[id]?.type === "token").length; const apiKeyCount = profiles.filter((id) => store.profiles[id]?.type === "api_key").length; - const awsSdkCount = profiles.filter((id) => store.profiles[id]?.type === "aws-sdk").length; const envKey = resolveEnvApiKey(provider, process.env, { config: cfg, @@ -175,7 +171,6 @@ export function resolveProviderAuthOverview(params: { oauth: oauthCount, token: tokenCount, apiKey: apiKeyCount, - awsSdk: awsSdkCount, labels, }, ...(envKey diff --git a/src/commands/models/list.status-command.ts b/src/commands/models/list.status-command.ts index e2494817029..6517e099a55 100644 --- a/src/commands/models/list.status-command.ts +++ b/src/commands/models/list.status-command.ts @@ -671,7 +671,7 @@ export async function modelsStatusCommand( bits.push( formatKeyValue( "profiles", - `${entry.profiles.count} (oauth=${entry.profiles.oauth}, token=${entry.profiles.token}, api_key=${entry.profiles.apiKey}, aws-sdk=${entry.profiles.awsSdk})`, + `${entry.profiles.count} (oauth=${entry.profiles.oauth}, token=${entry.profiles.token}, api_key=${entry.profiles.apiKey})`, ), ); if (entry.profiles.labels.length > 0) { diff --git a/src/commands/models/list.types.ts b/src/commands/models/list.types.ts index e0acaefdc12..060286a888d 100644 --- a/src/commands/models/list.types.ts +++ b/src/commands/models/list.types.ts @@ -28,7 +28,6 @@ export type ProviderAuthOverview = { oauth: number; token: number; apiKey: number; - awsSdk: number; labels: string[]; }; env?: { value: string; source: string }; diff --git a/src/config/types.auth.ts b/src/config/types.auth.ts index 8cfad18d663..6aae74fe0ed 100644 --- a/src/config/types.auth.ts +++ b/src/config/types.auth.ts @@ -1,7 +1,7 @@ export type AuthProfileConfig = { provider: string; /** - * Credential type expected in auth-profiles.json for this profile id. + * Auth route selected by this profile id. * - api_key: static provider API key * - oauth: refreshable OAuth credentials (access+refresh+expires) * - token: static bearer-style token (optionally expiring; no refresh) diff --git a/src/gateway/server-methods/models-auth-status.ts b/src/gateway/server-methods/models-auth-status.ts index 5ba6df52a9f..6f7d637046c 100644 --- a/src/gateway/server-methods/models-auth-status.ts +++ b/src/gateway/server-methods/models-auth-status.ts @@ -42,7 +42,7 @@ export type ModelAuthExpiry = { export type ModelAuthStatusProfile = { profileId: string; - type: "oauth" | "token" | "api_key" | "aws-sdk"; + type: "oauth" | "token" | "api_key"; status: AuthProfileHealthStatus; expiry?: ModelAuthExpiry; }; diff --git a/src/infra/provider-usage.auth.ts b/src/infra/provider-usage.auth.ts index c9ac81379bf..16e5b65563e 100644 --- a/src/infra/provider-usage.auth.ts +++ b/src/infra/provider-usage.auth.ts @@ -337,12 +337,7 @@ function hasAuthProfileCredentialSource(params: { if ( dedupeProfileIds(order).some((profileId) => { const cred = store.profiles[profileId]; - return ( - cred?.type === "api_key" || - cred?.type === "aws-sdk" || - cred?.type === "oauth" || - cred?.type === "token" - ); + return cred?.type === "api_key" || cred?.type === "oauth" || cred?.type === "token"; }) ) { return true;