diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f96c8ce54c..f12cb90a1c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -145,6 +145,7 @@ 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. - 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. diff --git a/src/agents/auth-health.ts b/src/agents/auth-health.ts index ddfc2f68892..4dc938942c3 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"; + type: "oauth" | "token" | "api_key" | "aws-sdk"; status: AuthProfileHealthStatus; reasonCode?: AuthCredentialReasonCode; expiresAt?: number; @@ -127,6 +127,17 @@ 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 0f5b42c47a5..a00be87324d 100644 --- a/src/agents/auth-profiles.ensureauthprofilestore.test.ts +++ b/src/agents/auth-profiles.ensureauthprofilestore.test.ts @@ -996,6 +996,25 @@ 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/credential-state.ts b/src/agents/auth-profiles/credential-state.ts index 4aeb8c7e6ed..c6511e8a52d 100644 --- a/src/agents/auth-profiles/credential-state.ts +++ b/src/agents/auth-profiles/credential-state.ts @@ -85,6 +85,10 @@ 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 9ce4157542d..b7511ec66e8 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" | "token" | "oauth"; + mode: "api_key" | "aws-sdk" | "token" | "oauth"; allowOAuthTokenCompatibility?: boolean; }): boolean { const profileConfig = params.cfg?.auth?.profiles?.[params.profileId]; @@ -311,6 +311,9 @@ 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/persisted.ts b/src/agents/auth-profiles/persisted.ts index 1276a5aa725..6bbf41a1420 100644 --- a/src/agents/auth-profiles/persisted.ts +++ b/src/agents/auth-profiles/persisted.ts @@ -31,7 +31,12 @@ 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", "oauth", "token"]); +const AUTH_PROFILE_TYPES = new Set([ + "api_key", + "aws-sdk", + "oauth", + "token", +]); function normalizeSecretBackedField(params: { entry: Record; @@ -539,6 +544,14 @@ 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/policy.ts b/src/agents/auth-profiles/policy.ts index ff899206651..26a8e88377d 100644 --- a/src/agents/auth-profiles/policy.ts +++ b/src/agents/auth-profiles/policy.ts @@ -61,7 +61,7 @@ function collectOAuthModeSecretRefViolations(params: { profileId: string; credential: AuthProfileCredential; defaults: SecretDefaults | undefined; - configuredMode?: "api_key" | "oauth" | "token"; + configuredMode?: "api_key" | "aws-sdk" | "oauth" | "token"; violations: OAuthSecretRefPolicyViolation[]; }): void { if (params.configuredMode !== "oauth") { diff --git a/src/agents/auth-profiles/types.ts b/src/agents/auth-profiles/types.ts index a9a735f5748..474efb8e0f7 100644 --- a/src/agents/auth-profiles/types.ts +++ b/src/agents/auth-profiles/types.ts @@ -29,6 +29,17 @@ 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). @@ -59,7 +70,11 @@ export type OAuthCredential = OAuthCredentials & { displayName?: string; }; -export type AuthProfileCredential = ApiKeyCredential | TokenCredential | OAuthCredential; +export type AuthProfileCredential = + | ApiKeyCredential + | AwsSdkCredential + | TokenCredential + | OAuthCredential; export type AuthProfileFailureReason = | "auth" diff --git a/src/agents/cli-auth-epoch.ts b/src/agents/cli-auth-epoch.ts index d67aea8c33e..995fe0312ef 100644 --- a/src/agents/cli-auth-epoch.ts +++ b/src/agents/cli-auth-epoch.ts @@ -99,6 +99,14 @@ 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 299891c1d82..91b3392008f 100644 --- a/src/agents/model-auth.profiles.test.ts +++ b/src/agents/model-auth.profiles.test.ts @@ -290,6 +290,61 @@ 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); + + 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(); + 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(); + } +}); + function buildDemoLocalStore(keys: string[]) { return { version: 1 as const, diff --git a/src/agents/model-auth.ts b/src/agents/model-auth.ts index babb80f1c70..7522989f6d0 100644 --- a/src/agents/model-auth.ts +++ b/src/agents/model-auth.ts @@ -20,6 +20,7 @@ import { } from "../shared/string-coerce.js"; import { normalizeOptionalSecretInput } from "../utils/normalize-secret-input.js"; import { + type AuthProfileCredential, type AuthProfileStore, externalCliDiscoveryForProviderAuth, ensureAuthProfileStore, @@ -43,6 +44,7 @@ import { type ResolvedProviderAuth, } from "./model-auth-runtime-shared.js"; import { normalizeProviderId } from "./model-selection.js"; +import { resolveProviderIdForAuth } from "./provider-auth-aliases.js"; export { ensureAuthProfileStore, @@ -234,6 +236,58 @@ function resolveProviderAuthOverride( return undefined; } +function profileTypeToAuthMode(type: AuthProfileCredential["type"]): ResolvedProviderAuth["mode"] { + return type === "oauth" + ? "oauth" + : type === "token" + ? "token" + : type === "aws-sdk" + ? "aws-sdk" + : "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: { + cfg?: OpenClawConfig; + provider: string; + profileId: string; + credential: AuthProfileCredential | undefined; +}): ResolvedProviderAuth | null { + if (params.credential?.type !== "aws-sdk") { + 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, + source: `profile:${params.profileId}`, + }; +} + function isLocalBaseUrl(baseUrl: string): boolean { try { let host = normalizeLowercaseStringOrEmpty(new URL(baseUrl).hostname); @@ -539,6 +593,15 @@ 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, @@ -553,7 +616,7 @@ export async function resolveApiKeyForProvider(params: { apiKey: resolved.apiKey, profileId, source: `profile:${profileId}`, - mode: mode === "oauth" ? "oauth" : mode === "token" ? "token" : "api-key", + mode: mode ? profileTypeToAuthMode(mode) : "api-key", }; // When the resolved key is a provider-owned synthetic profile marker and // the caller has not locked this profile, fall through to env/config @@ -641,6 +704,15 @@ export async function resolveApiKeyForProvider(params: { let deferredAuthProfileResult: ResolvedProviderAuth | null = null; for (const candidate of order) { try { + const awsSdkProfileAuth = resolveAwsSdkProfileAuth({ + cfg, + provider, + profileId: candidate, + credential: store.profiles[candidate], + }); + if (awsSdkProfileAuth) { + return awsSdkProfileAuth; + } const resolved = await resolveApiKeyForProfile({ cfg, store, @@ -649,8 +721,9 @@ export async function resolveApiKeyForProvider(params: { }); if (resolved) { const mode = store.profiles[candidate]?.type; - const resolvedMode: ResolvedProviderAuth["mode"] = - mode === "oauth" ? "oauth" : mode === "token" ? "token" : "api-key"; + const resolvedMode: ResolvedProviderAuth["mode"] = mode + ? profileTypeToAuthMode(mode) + : "api-key"; const result: ResolvedProviderAuth = { apiKey: resolved.apiKey, profileId: candidate, @@ -770,10 +843,10 @@ export function resolveModelAuthMode( const modes = new Set( profiles .map((id) => authStore.profiles[id]?.type) - .filter((mode): mode is "api_key" | "oauth" | "token" => Boolean(mode)), + .filter((mode): mode is "api_key" | "aws-sdk" | "oauth" | "token" => Boolean(mode)), ); - const distinct = ["oauth", "token", "api_key"].filter((k) => - modes.has(k as "oauth" | "token" | "api_key"), + const distinct = ["oauth", "token", "api_key", "aws-sdk"].filter((k) => + modes.has(k as "oauth" | "token" | "api_key" | "aws-sdk"), ); if (distinct.length >= 2) { return "mixed"; @@ -787,6 +860,9 @@ 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") { @@ -855,6 +931,16 @@ export async function hasAvailableAuthForProvider(params: { }); for (const candidate of order) { try { + if ( + resolveAwsSdkProfileAuth({ + cfg, + provider, + profileId: candidate, + credential: store.profiles[candidate], + }) + ) { + return true; + } const resolved = await resolveApiKeyForProfile({ cfg, store, diff --git a/src/agents/models-config.providers.secret-helpers.ts b/src/agents/models-config.providers.secret-helpers.ts index 9735df227e4..19f137799f8 100644 --- a/src/agents/models-config.providers.secret-helpers.ts +++ b/src/agents/models-config.providers.secret-helpers.ts @@ -39,7 +39,7 @@ export type ProviderAuthResolver = ( ) => { apiKey: string | undefined; discoveryApiKey?: string; - mode: "api_key" | "oauth" | "token" | "none"; + mode: "api_key" | "aws-sdk" | "oauth" | "token" | "none"; source: "env" | "profile" | "none"; profileId?: string; }; diff --git a/src/auto-reply/reply/directive-handling.auth.ts b/src/auto-reply/reply/directive-handling.auth.ts index 320db4b1c28..2dbb1190810 100644 --- a/src/auto-reply/reply/directive-handling.auth.ts +++ b/src/auto-reply/reply/directive-handling.auth.ts @@ -113,6 +113,12 @@ 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); @@ -169,6 +175,10 @@ 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/auth-choice.test.ts b/src/commands/auth-choice.test.ts index dfe2a3b5550..a5ce3135d08 100644 --- a/src/commands/auth-choice.test.ts +++ b/src/commands/auth-choice.test.ts @@ -98,7 +98,7 @@ vi.mock("../plugins/provider-auth-helpers.js", () => ({ params: { profileId: string; provider: string; - mode: "api_key" | "oauth" | "token"; + mode: "api_key" | "aws-sdk" | "oauth" | "token"; email?: string; displayName?: string; }, diff --git a/src/commands/doctor-auth-flat-profiles.ts b/src/commands/doctor-auth-flat-profiles.ts index 8fcf9ed4bb1..a8f988e8dcc 100644 --- a/src/commands/doctor-auth-flat-profiles.ts +++ b/src/commands/doctor-auth-flat-profiles.ts @@ -51,7 +51,12 @@ function inferLegacyCredentialType( record: Record, ): AuthProfileCredential["type"] | undefined { const explicit = readNonEmptyString(record.type) ?? readNonEmptyString(record.mode); - if (explicit === "api_key" || explicit === "token" || explicit === "oauth") { + if ( + explicit === "api_key" || + explicit === "aws-sdk" || + explicit === "token" || + explicit === "oauth" + ) { return explicit; } if (readNonEmptyString(record.key) ?? readNonEmptyString(record.apiKey)) { diff --git a/src/commands/doctor-auth-profile-config.ts b/src/commands/doctor-auth-profile-config.ts index bd95da733fd..268e165d764 100644 --- a/src/commands/doctor-auth-profile-config.ts +++ b/src/commands/doctor-auth-profile-config.ts @@ -8,7 +8,12 @@ import { } from "../shared/string-coerce.js"; import { isRecord } from "../utils.js"; -const AUTH_PROFILE_MODES = new Set(["api_key", "oauth", "token"]); +const AUTH_PROFILE_MODES = new Set([ + "api_key", + "aws-sdk", + "oauth", + "token", +]); export type AuthProfileConfigProtectionResult = { config: OpenClawConfig; diff --git a/src/commands/models/auth-list.ts b/src/commands/models/auth-list.ts index 907dd5a90d9..41d9631c488 100644 --- a/src/commands/models/auth-list.ts +++ b/src/commands/models/auth-list.ts @@ -46,7 +46,9 @@ function formatTimestamp(value: number | undefined): string | undefined { } function resolveProfileExpiry(profile: AuthProfileCredential): string | undefined { - return profile.type === "api_key" ? undefined : formatTimestamp(profile.expires); + return profile.type === "oauth" || profile.type === "token" + ? formatTimestamp(profile.expires) + : undefined; } function summarizeProfile(params: { diff --git a/src/commands/models/auth.test.ts b/src/commands/models/auth.test.ts index 5c5dccba2ce..cb09062cced 100644 --- a/src/commands/models/auth.test.ts +++ b/src/commands/models/auth.test.ts @@ -47,7 +47,7 @@ vi.mock("../../plugins/provider-auth-helpers.js", () => ({ params: { profileId: string; provider: string; - mode: "api_key" | "oauth" | "token"; + mode: "api_key" | "aws-sdk" | "oauth" | "token"; email?: string; displayName?: string; }, diff --git a/src/commands/models/auth.ts b/src/commands/models/auth.ts index 9968591613f..033815c0816 100644 --- a/src/commands/models/auth.ts +++ b/src/commands/models/auth.ts @@ -588,10 +588,13 @@ export function resolveRequestedLoginProviderOrThrow( return resolveRequestedProviderOrThrow(providers, rawProvider); } -function credentialMode(credential: AuthProfileCredential): "api_key" | "oauth" | "token" { +function credentialMode(credential: AuthProfileCredential): AuthProfileCredential["type"] { 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 86442763fdf..19d8235cd41 100644 --- a/src/commands/models/list.auth-overview.ts +++ b/src/commands/models/list.auth-overview.ts @@ -110,6 +110,9 @@ 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 @@ -123,6 +126,7 @@ 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, @@ -171,6 +175,7 @@ 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 6517e099a55..e2494817029 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})`, + `${entry.profiles.count} (oauth=${entry.profiles.oauth}, token=${entry.profiles.token}, api_key=${entry.profiles.apiKey}, aws-sdk=${entry.profiles.awsSdk})`, ), ); if (entry.profiles.labels.length > 0) { diff --git a/src/commands/models/list.types.ts b/src/commands/models/list.types.ts index 060286a888d..e0acaefdc12 100644 --- a/src/commands/models/list.types.ts +++ b/src/commands/models/list.types.ts @@ -28,6 +28,7 @@ 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 ca9340dd6b9..8cfad18d663 100644 --- a/src/config/types.auth.ts +++ b/src/config/types.auth.ts @@ -5,8 +5,9 @@ export type AuthProfileConfig = { * - api_key: static provider API key * - oauth: refreshable OAuth credentials (access+refresh+expires) * - token: static bearer-style token (optionally expiring; no refresh) + * - aws-sdk: AWS SDK default credential chain (no secret in auth-profiles.json) */ - mode: "api_key" | "oauth" | "token"; + mode: "api_key" | "aws-sdk" | "oauth" | "token"; email?: string; displayName?: string; }; diff --git a/src/config/zod-schema.ts b/src/config/zod-schema.ts index 9a848290b20..73760e8c4ec 100644 --- a/src/config/zod-schema.ts +++ b/src/config/zod-schema.ts @@ -560,7 +560,12 @@ export const OpenClawSchema = z z .object({ provider: z.string(), - mode: z.union([z.literal("api_key"), z.literal("oauth"), z.literal("token")]), + mode: z.union([ + z.literal("api_key"), + z.literal("aws-sdk"), + z.literal("oauth"), + z.literal("token"), + ]), email: z.string().optional(), displayName: z.string().optional(), }) diff --git a/src/gateway/server-methods/models-auth-status.ts b/src/gateway/server-methods/models-auth-status.ts index 6f7d637046c..5ba6df52a9f 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"; + type: "oauth" | "token" | "api_key" | "aws-sdk"; status: AuthProfileHealthStatus; expiry?: ModelAuthExpiry; }; diff --git a/src/infra/provider-usage.auth.ts b/src/infra/provider-usage.auth.ts index 16e5b65563e..c9ac81379bf 100644 --- a/src/infra/provider-usage.auth.ts +++ b/src/infra/provider-usage.auth.ts @@ -337,7 +337,12 @@ function hasAuthProfileCredentialSource(params: { if ( dedupeProfileIds(order).some((profileId) => { const cred = store.profiles[profileId]; - return cred?.type === "api_key" || cred?.type === "oauth" || cred?.type === "token"; + return ( + cred?.type === "api_key" || + cred?.type === "aws-sdk" || + cred?.type === "oauth" || + cred?.type === "token" + ); }) ) { return true; diff --git a/src/plugin-sdk/test-helpers/provider-discovery-contract.ts b/src/plugin-sdk/test-helpers/provider-discovery-contract.ts index 6b49cd1f3a2..1a83175003e 100644 --- a/src/plugin-sdk/test-helpers/provider-discovery-contract.ts +++ b/src/plugin-sdk/test-helpers/provider-discovery-contract.ts @@ -90,7 +90,7 @@ function runCatalog( ) => { apiKey: string | undefined; discoveryApiKey?: string; - mode: "api_key" | "oauth" | "token" | "none"; + mode: "api_key" | "aws-sdk" | "oauth" | "token" | "none"; source: "env" | "profile" | "none"; profileId?: string; }; diff --git a/src/plugins/provider-auth-helpers.ts b/src/plugins/provider-auth-helpers.ts index b8e56335c3a..ce97bf4162a 100644 --- a/src/plugins/provider-auth-helpers.ts +++ b/src/plugins/provider-auth-helpers.ts @@ -138,7 +138,7 @@ export function applyAuthProfileConfig( params: { profileId: string; provider: string; - mode: "api_key" | "oauth" | "token"; + mode: "api_key" | "aws-sdk" | "oauth" | "token"; email?: string; displayName?: string; preferProfileFirst?: boolean; diff --git a/src/plugins/provider-discovery.ts b/src/plugins/provider-discovery.ts index 3b0723cda8b..6e30ac74ab8 100644 --- a/src/plugins/provider-discovery.ts +++ b/src/plugins/provider-discovery.ts @@ -155,7 +155,7 @@ export function runProviderCatalog(params: { ) => { apiKey: string | undefined; discoveryApiKey?: string; - mode: "api_key" | "oauth" | "token" | "none"; + mode: "api_key" | "aws-sdk" | "oauth" | "token" | "none"; source: "env" | "profile" | "none"; profileId?: string; }; diff --git a/src/plugins/types.ts b/src/plugins/types.ts index f7b228c8399..ab7fb2f39f5 100644 --- a/src/plugins/types.ts +++ b/src/plugins/types.ts @@ -441,7 +441,7 @@ export type ProviderCatalogContext = { ) => { apiKey: string | undefined; discoveryApiKey?: string; - mode: "api_key" | "oauth" | "token" | "none"; + mode: "api_key" | "aws-sdk" | "oauth" | "token" | "none"; source: "env" | "profile" | "none"; profileId?: string; }; diff --git a/src/secrets/runtime-auth-profiles-oauth-policy.test.ts b/src/secrets/runtime-auth-profiles-oauth-policy.test.ts index 0d718717eb7..f4dd3886bb2 100644 --- a/src/secrets/runtime-auth-profiles-oauth-policy.test.ts +++ b/src/secrets/runtime-auth-profiles-oauth-policy.test.ts @@ -7,7 +7,7 @@ import { const { prepareSecretsRuntimeSnapshot } = setupSecretsRuntimeSnapshotTestHooks(); -function withAuthProfileMode(mode: "api_key" | "oauth" | "token"): OpenClawConfig { +function withAuthProfileMode(mode: "api_key" | "aws-sdk" | "oauth" | "token"): OpenClawConfig { return { auth: { profiles: {