diff --git a/extensions/codex/src/app-server/auth-bridge.test.ts b/extensions/codex/src/app-server/auth-bridge.test.ts index a23457b21cfa..849f77340132 100644 --- a/extensions/codex/src/app-server/auth-bridge.test.ts +++ b/extensions/codex/src/app-server/auth-bridge.test.ts @@ -2518,38 +2518,6 @@ describe("bridgeCodexAppServerStartOptions", () => { } }); - it("accepts a legacy Codex auth-provider alias for app-server login", async () => { - const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-app-server-")); - const request = vi.fn(async () => ({ type: "chatgptAuthTokens" })); - try { - upsertAuthProfile({ - agentDir, - profileId: "openai:work", - credential: { - type: "token", - provider: "codex-cli", - token: "legacy-access-token", - email: "legacy-codex@example.test", - }, - }); - - await applyCodexAppServerAuthProfile({ - client: { request } as never, - agentDir, - authProfileId: "openai:work", - }); - - expect(request).toHaveBeenCalledWith("account/login/start", { - type: "chatgptAuthTokens", - accessToken: "legacy-access-token", - chatgptAccountId: "legacy-codex@example.test", - chatgptPlanType: null, - }); - } finally { - await fs.rm(agentDir, { recursive: true, force: true }); - } - }); - it("answers app-server ChatGPT token refresh requests from the bound profile", async () => { const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-app-server-")); oauthMocks.refreshOpenAICodexToken.mockResolvedValueOnce({ @@ -2769,45 +2737,6 @@ describe("bridgeCodexAppServerStartOptions", () => { } }); - it("accepts a refreshed Codex OAuth credential when the stored provider is a legacy alias", async () => { - const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-app-server-")); - oauthMocks.refreshOpenAICodexToken.mockResolvedValueOnce({ - access: "refreshed-alias-access-token", - refresh: "refreshed-alias-refresh-token", - expires: Date.now() + 60_000, - accountId: "account-alias", - }); - try { - upsertAuthProfile({ - agentDir, - profileId: "openai:work", - credential: { - type: "oauth", - provider: "codex-cli", - access: "stale-alias-access-token", - refresh: "alias-refresh-token", - expires: Date.now() + 60_000, - accountId: "account-legacy", - email: "legacy-codex@example.test", - }, - }); - - await expect( - refreshCodexAppServerAuthTokens({ - agentDir, - authProfileId: "openai:work", - }), - ).resolves.toEqual({ - accessToken: "refreshed-alias-access-token", - chatgptAccountId: "account-alias", - chatgptPlanType: null, - }); - expect(oauthMocks.refreshOpenAICodexToken).toHaveBeenCalledWith("alias-refresh-token"); - } finally { - await fs.rm(agentDir, { recursive: true, force: true }); - } - }); - it("preserves a stored ChatGPT plan type when building token login params", async () => { const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-app-server-")); const request = vi.fn(async () => ({ type: "chatgptAuthTokens" })); diff --git a/extensions/codex/src/app-server/auth-bridge.ts b/extensions/codex/src/app-server/auth-bridge.ts index 8c709751ef5c..c14d441fb148 100644 --- a/extensions/codex/src/app-server/auth-bridge.ts +++ b/extensions/codex/src/app-server/auth-bridge.ts @@ -43,13 +43,7 @@ import { isCodexAppServerNativeAuthProfile } from "./session-binding.js"; import { resolveCodexAppServerSpawnEnv } from "./transport-stdio.js"; const CODEX_APP_SERVER_AUTH_PROVIDER = "openai"; -const OPENAI_CODEX_APP_SERVER_AUTH_PROVIDER = "openai-codex"; -const LEGACY_CODEX_APP_SERVER_AUTH_PROVIDER = "codex-cli"; -const CODEX_APP_SERVER_EXTERNAL_CLI_PROVIDER_IDS = [ - CODEX_APP_SERVER_AUTH_PROVIDER, - LEGACY_CODEX_APP_SERVER_AUTH_PROVIDER, -]; -const OPENAI_PROVIDER = "openai"; +const CODEX_APP_SERVER_EXTERNAL_CLI_PROVIDER_IDS = [CODEX_APP_SERVER_AUTH_PROVIDER]; const OPENAI_CODEX_DEFAULT_PROFILE_ID = "openai:default"; const CODEX_HOME_ENV_VAR = "CODEX_HOME"; const HOME_ENV_VAR = "HOME"; @@ -112,7 +106,6 @@ export async function bridgeCodexAppServerStartOptions(params: { const shouldClearInheritedOpenAiApiKey = shouldClearOpenAiApiKeyForCodexAuthProfile({ store, authProfileId, - config: params.config, }); return shouldClearInheritedOpenAiApiKey ? withClearedEnvironmentVariables(scopedStartOptions, CODEX_APP_SERVER_API_KEY_ENV_VARS) @@ -227,7 +220,7 @@ export async function resolveCodexAppServerPreparedAuthProfileSnapshot(params: { return undefined; } const credential = store.profiles[profileId]; - if (!credential || !isCodexAppServerAuthProfileCredential(credential, params.config)) { + if (!credential || !isCodexAppServerAuthProfileCredential(credential)) { return undefined; } const loginParams = await resolveCodexAppServerAuthProfileLoginParamsInternal({ @@ -340,7 +333,7 @@ export async function resolveCodexAppServerAuthAccountCacheKey(params: { return undefined; } const credential = store.profiles[profileId]; - if (!credential || !isCodexAppServerAuthProfileCredential(credential, params.config)) { + if (!credential || !isCodexAppServerAuthProfileCredential(credential)) { return undefined; } if (credential.type === "api_key") { @@ -627,7 +620,7 @@ async function resolveCodexAppServerAuthProfileLoginParams(params: { `Codex app-server auth profile "${profileId}" was not found.`, ); } - if (profileId && profile && !isCodexAppServerAuthProfileCredential(profile, params.config)) { + if (profileId && profile && !isCodexAppServerAuthProfileCredential(profile)) { throw new CodexAppServerAuthProfileUnavailableError( `Codex app-server auth profile "${profileId}" must be OpenAI Codex auth or an OpenAI API-key backup.`, ); @@ -683,7 +676,7 @@ async function resolveCodexAppServerAuthProfileLoginParamsInternal(params: { if (!credential) { throw new Error(`Codex app-server auth profile "${profileId}" was not found.`); } - if (!isCodexAppServerAuthProfileCredential(credential, params.config)) { + if (!isCodexAppServerAuthProfileCredential(credential)) { throw new Error( `Codex app-server auth profile "${profileId}" must be OpenAI Codex auth or an OpenAI API-key backup.`, ); @@ -867,13 +860,12 @@ async function resolveOAuthCredentialForCodexAppServer( const persistedOAuthCredential = !useScopedCredential && persistedCredential?.type === "oauth" && - isCodexAppServerAuthProvider(persistedCredential.provider, params.config) + isCodexAppServerAuthProvider(persistedCredential.provider) ? persistedCredential : undefined; const ownerCredential = store.profiles[profileId]; const overlaidOAuthCredential = - ownerCredential?.type === "oauth" && - isCodexAppServerAuthProvider(ownerCredential.provider, params.config) + ownerCredential?.type === "oauth" && isCodexAppServerAuthProvider(ownerCredential.provider) ? ownerCredential : undefined; if (useScopedCredential && overlaidOAuthCredential) { @@ -904,7 +896,7 @@ async function resolveOAuthCredentialForCodexAppServer( ? undefined : loadAuthProfileStoreForSecretsRuntime(ownerAgentDir).profiles[profileId]; const refreshedOAuthCredential = - refreshed?.type === "oauth" && isCodexAppServerAuthProvider(refreshed.provider, params.config) + refreshed?.type === "oauth" && isCodexAppServerAuthProvider(refreshed.provider) ? refreshed : undefined; if (refreshedOAuthCredential && isDeepStrictEqual(params.store.profiles[profileId], credential)) { @@ -915,8 +907,7 @@ async function resolveOAuthCredentialForCodexAppServer( const storedCredential = store.profiles[profileId]; const candidate = refreshedOAuthCredential ? refreshedOAuthCredential - : storedCredential?.type === "oauth" && - isCodexAppServerAuthProvider(storedCredential.provider, params.config) + : storedCredential?.type === "oauth" && isCodexAppServerAuthProvider(storedCredential.provider) ? storedCredential : credential; return resolved?.apiKey ? { ...candidate, access: resolved.apiKey } : candidate; @@ -1005,54 +996,37 @@ async function resolveScopedOAuthCredential(params: { } } -function isCodexAppServerAuthProvider(provider: string, config?: AuthProfileOrderConfig): boolean { - const resolvedProvider = resolveProviderIdForAuth(provider, { config }); - return ( - resolvedProvider === CODEX_APP_SERVER_AUTH_PROVIDER || - resolvedProvider === OPENAI_CODEX_APP_SERVER_AUTH_PROVIDER || - // Older Codex auth profiles stored the CLI runtime id here. The app-server - // login protocol still receives the same externally managed ChatGPT token. - resolvedProvider === LEGACY_CODEX_APP_SERVER_AUTH_PROVIDER - ); +// Runtime consumes canonical auth state; doctor owns retired profile-id migration. +function isCodexAppServerAuthProvider(provider: string): boolean { + return provider.trim().toLowerCase() === CODEX_APP_SERVER_AUTH_PROVIDER; } -function isOpenAIApiKeyBackupCredential( - credential: AuthProfileCredential, - config?: AuthProfileOrderConfig, -): boolean { +function isOpenAIApiKeyBackupCredential(credential: AuthProfileCredential): boolean { return ( credential.type === "api_key" && - resolveProviderIdForAuth(credential.provider, { config }) === OPENAI_PROVIDER + credential.provider.trim().toLowerCase() === CODEX_APP_SERVER_AUTH_PROVIDER ); } -function isCodexAppServerAuthProfileCredential( - credential: AuthProfileCredential, - config?: AuthProfileOrderConfig, -): boolean { +function isCodexAppServerAuthProfileCredential(credential: AuthProfileCredential): boolean { return ( - isCodexAppServerAuthProvider(credential.provider, config) || - isOpenAIApiKeyBackupCredential(credential, config) + isCodexAppServerAuthProvider(credential.provider) || isOpenAIApiKeyBackupCredential(credential) ); } function shouldClearOpenAiApiKeyForCodexAuthProfile(params: { store: ReturnType; authProfileId?: string; - config?: AuthProfileOrderConfig; }): boolean { const profileId = params.authProfileId?.trim(); const credential = profileId ? params.store.profiles[profileId] : params.store.profiles[OPENAI_CODEX_DEFAULT_PROFILE_ID]; - return isCodexSubscriptionCredential(credential, params.config); + return isCodexSubscriptionCredential(credential); } -function isCodexSubscriptionCredential( - credential: AuthProfileCredential | undefined, - config?: AuthProfileOrderConfig, -): boolean { - if (!credential || !isCodexAppServerAuthProvider(credential.provider, config)) { +function isCodexSubscriptionCredential(credential: AuthProfileCredential | undefined): boolean { + if (!credential || !isCodexAppServerAuthProvider(credential.provider)) { return false; } return credential.type === "oauth" || credential.type === "token"; diff --git a/packages/ai/src/providers/cache-retention.ts b/packages/ai/src/providers/cache-retention.ts index 9953895eba55..e76ed7f771b8 100644 --- a/packages/ai/src/providers/cache-retention.ts +++ b/packages/ai/src/providers/cache-retention.ts @@ -5,7 +5,7 @@ import type { CacheRetention } from "../types.js"; * Defaults to "short" and uses OPENCLAW_CACHE_RETENTION for backward compatibility. */ export function resolveCacheRetention(cacheRetention?: CacheRetention): CacheRetention { - if (cacheRetention) { + if (cacheRetention === "short" || cacheRetention === "long" || cacheRetention === "none") { return cacheRetention; } if (typeof process !== "undefined" && process.env.OPENCLAW_CACHE_RETENTION === "long") { diff --git a/packages/ai/src/transports/anthropic-payload-policy.ts b/packages/ai/src/transports/anthropic-payload-policy.ts index 45bddc0162f9..265b1dfa1633 100644 --- a/packages/ai/src/transports/anthropic-payload-policy.ts +++ b/packages/ai/src/transports/anthropic-payload-policy.ts @@ -1,3 +1,4 @@ +import { resolveCacheRetention } from "../providers/cache-retention.js"; import { splitSystemPromptCacheBoundary, stripSystemPromptCacheBoundary, @@ -64,8 +65,7 @@ export function resolveAnthropicEphemeralCacheControl( baseUrl: string | undefined, cacheRetention: AnthropicPayloadPolicyInput["cacheRetention"], ): AnthropicEphemeralCacheControl | undefined { - const retention = - cacheRetention ?? (process.env.OPENCLAW_CACHE_RETENTION === "long" ? "long" : "short"); + const retention = resolveCacheRetention(cacheRetention); if (retention === "none") { return undefined; } diff --git a/packages/ai/src/transports/openai-completions-transport.ts b/packages/ai/src/transports/openai-completions-transport.ts index 547cae72b8f3..f457fe4bc003 100644 --- a/packages/ai/src/transports/openai-completions-transport.ts +++ b/packages/ai/src/transports/openai-completions-transport.ts @@ -8,6 +8,7 @@ import { getEnvApiKey } from "../env-api-keys.js"; import { applyProviderReportedUsageCost, calculateCost } from "../model-utils.js"; import { convertMessages } from "../openai-completions-messages.js"; import type { OpenAICompletionsOptions } from "../provider-options.js"; +import { resolveCacheRetention } from "../providers/cache-retention.js"; import { isOpenAIGpt54MiniModel, isOpenAIGpt55Model, @@ -70,7 +71,6 @@ import { GEMINI_THOUGHT_SIGNATURE_VALIDATOR_SKIP, createModelStreamCooperativeScheduler, log, - resolveCacheRetention, resolvePromptCacheKey, sortTransportToolsByName, throwIfModelStreamAborted, diff --git a/packages/ai/src/transports/openai-responses-params-internal.ts b/packages/ai/src/transports/openai-responses-params-internal.ts index 0db6c595c4ef..1c706f8f8267 100644 --- a/packages/ai/src/transports/openai-responses-params-internal.ts +++ b/packages/ai/src/transports/openai-responses-params-internal.ts @@ -5,6 +5,7 @@ import type { ResponseFormatTextConfig, ResponseInput, } from "openai/resources/responses/responses.js"; +import { resolveCacheRetention } from "../providers/cache-retention.js"; import { normalizeOpenAIReasoningEffort, resolveOpenAIReasoningEffortForModel, @@ -39,7 +40,6 @@ import { usesNativeOpenAICodexResponsesBackend, } from "./openai-transport-params.js"; import { - resolveCacheRetention, resolvePromptCacheKey, sortTransportToolsByName, type OpenAIModeModel, diff --git a/packages/ai/src/transports/openai-transport-shared.ts b/packages/ai/src/transports/openai-transport-shared.ts index 861bab9ec25b..de700c6e0f14 100644 --- a/packages/ai/src/transports/openai-transport-shared.ts +++ b/packages/ai/src/transports/openai-transport-shared.ts @@ -98,18 +98,6 @@ export function createModelStreamCooperativeScheduler( }; } -export function resolveCacheRetention( - cacheRetention: string | undefined, -): "short" | "long" | "none" { - if (cacheRetention === "short" || cacheRetention === "long" || cacheRetention === "none") { - return cacheRetention; - } - if (typeof process !== "undefined" && process.env.OPENCLAW_CACHE_RETENTION === "long") { - return "long"; - } - return "short"; -} - export function resolvePromptCacheKey( options: Pick | undefined, cacheRetention: "short" | "long" | "none", diff --git a/src/agents/fast-mode.test.ts b/src/agents/fast-mode.test.ts index 6c1b192cb37b..2d249a88c79c 100644 --- a/src/agents/fast-mode.test.ts +++ b/src/agents/fast-mode.test.ts @@ -76,53 +76,6 @@ describe("resolveFastModeState", () => { expect(state.source).toBe("config"); }); - it("uses OpenAI model config for the Codex app-server runtime provider", () => { - const cfg = { - agents: { - defaults: { - models: { - "openai/gpt-5.5": { params: { fastMode: "auto", fastAutoOnSeconds: 30 } }, - }, - }, - }, - } as OpenClawConfig; - - const state = resolveFastModeState({ - cfg, - provider: "openai-codex", - model: "gpt-5.5", - }); - - expect(state.mode).toBe("auto"); - expect(state.enabled).toBe(true); - expect(state.source).toBe("config"); - expect(state.fastAutoOnSeconds).toBe(30); - }); - - it("prefers exact Codex app-server model config over the OpenAI alias", () => { - const cfg = { - agents: { - defaults: { - models: { - "openai/gpt-5.5": { params: { fastMode: true, fastAutoOnSeconds: 30 } }, - "openai-codex/gpt-5.5": { params: { fastMode: false, fastAutoOnSeconds: 45 } }, - }, - }, - }, - } as OpenClawConfig; - - const state = resolveFastModeState({ - cfg, - provider: "openai-codex", - model: "gpt-5.5", - }); - - expect(state.enabled).toBe(false); - expect(state.mode).toBe(false); - expect(state.source).toBe("config"); - expect(state.fastAutoOnSeconds).toBe(45); - }); - it("formats auto mode with the default threshold", () => { expect(formatFastModeAutoLabel()).toBe("auto (60 sec)"); expect(formatFastModeStatusValue({ mode: "auto" })).toBe("auto (60 sec)"); diff --git a/src/agents/model-auth.test.ts b/src/agents/model-auth.test.ts index 0b2e85d8a7fe..03b899de4c33 100644 --- a/src/agents/model-auth.test.ts +++ b/src/agents/model-auth.test.ts @@ -718,38 +718,6 @@ describe("resolveUsableCustomProviderApiKey", () => { } }); - it("resolves legacy __env__ markers from process env for custom providers", () => { - const previous = process.env.BAILIAN_API_KEY; - process.env.BAILIAN_API_KEY = "sk-bailian-env"; // pragma: allowlist secret - try { - const resolved = resolveUsableCustomProviderApiKey({ - cfg: { - models: { - providers: { - bailian: { - baseUrl: "https://coding.dashscope.aliyuncs.com/v1", - api: "openai-completions", - apiKey: "__env__:BAILIAN_API_KEY", // pragma: allowlist secret - models: [], - }, - }, - }, - }, - provider: "bailian", - secretSentinels: true, - }); - expect(looksLikeSecretSentinel(resolved?.apiKey ?? "")).toBe(true); - expect(resolveSecretSentinel(resolved?.apiKey ?? "")).toBe("sk-bailian-env"); - expect(resolved?.source).toContain("BAILIAN_API_KEY"); - } finally { - if (previous === undefined) { - delete process.env.BAILIAN_API_KEY; - } else { - process.env.BAILIAN_API_KEY = previous; - } - } - }); - it("does not resolve env SecretRefs when provider allowlist excludes the env id", () => { const previous = process.env.MY_CUSTOM_KEY; process.env.MY_CUSTOM_KEY = "sk-custom-secretref-env"; // pragma: allowlist secret diff --git a/src/auto-reply/reply/agent-runner-failure-reply.ts b/src/auto-reply/reply/agent-runner-failure-reply.ts index 9815cac68277..b6519697838c 100644 --- a/src/auto-reply/reply/agent-runner-failure-reply.ts +++ b/src/auto-reply/reply/agent-runner-failure-reply.ts @@ -375,11 +375,7 @@ function supportsChannelCodexLogin(provider: string | null | undefined): boolean return false; } const normalizedProvider = provider.trim().toLowerCase().replace(/_/gu, "-"); - return ( - normalizedProvider === "openai" || - normalizedProvider === "codex" || - normalizedProvider === "openai-codex" - ); + return normalizedProvider === "openai" || normalizedProvider === "codex"; } export function buildExternalRunFailureReply( diff --git a/src/auto-reply/reply/commands-login.test.ts b/src/auto-reply/reply/commands-login.test.ts index f43842521bb5..ad2359ce2a3d 100644 --- a/src/auto-reply/reply/commands-login.test.ts +++ b/src/auto-reply/reply/commands-login.test.ts @@ -569,19 +569,6 @@ describe("handleLoginCommand", () => { expect(runModelsAuthLoginFlowMock).not.toHaveBeenCalled(); }); - it("normalizes Codex login aliases to the OpenAI provider", async () => { - mockSuccessfulLoginFlow(); - - await handleLoginCommand( - buildLoginParams("/login openai-codex", { opts: blockReplyOpts() }), - true, - ); - - expect(runModelsAuthLoginFlowMock).toHaveBeenCalledWith( - expect.objectContaining({ provider: "openai" }), - ); - }); - it("returns a friendly error for unsupported providers", async () => { const result = await handleLoginCommand(buildLoginParams("/login anthropic"), true); diff --git a/src/auto-reply/reply/commands-status.test.ts b/src/auto-reply/reply/commands-status.test.ts index fbc9967a5d66..f6212808eeaa 100644 --- a/src/auto-reply/reply/commands-status.test.ts +++ b/src/auto-reply/reply/commands-status.test.ts @@ -170,7 +170,7 @@ function registerStatusCodexHarness(): void { function saveStatusTestAuthProfile(params: { dir: string; profileId: string; - provider: "openai" | "openai-codex" | "anthropic"; + provider: "openai" | "anthropic"; }): void { saveStatusTestAuthProfiles({ dir: params.dir, @@ -180,7 +180,7 @@ function saveStatusTestAuthProfile(params: { function saveStatusTestAuthProfiles(params: { dir: string; - profiles: Array<{ profileId: string; provider: "openai" | "openai-codex" | "anthropic" }>; + profiles: Array<{ profileId: string; provider: "openai" | "anthropic" }>; }): void { const agentDir = path.join(params.dir, ".openclaw", "agents", "main", "agent"); fs.mkdirSync(agentDir, { recursive: true }); @@ -190,7 +190,7 @@ function saveStatusTestAuthProfiles(params: { profiles: Object.fromEntries( params.profiles.map((profile) => [ profile.profileId, - profile.provider === "openai" || profile.provider === "openai-codex" + profile.provider === "openai" ? { type: "oauth", provider: profile.provider, @@ -1229,72 +1229,6 @@ describe("buildStatusReply subagent summary", () => { ); }); - it("forwards legacy Codex profile providers to Codex synthetic usage", async () => { - registerStatusCodexHarness(); - providerUsageMock.loadProviderUsageSummary.mockResolvedValue({ - updatedAt: Date.now(), - providers: [ - { - provider: "openai", - displayName: "OpenAI", - windows: [{ label: "5h", usedPercent: 9 }], - }, - ], - }); - - await withTempHome( - async (dir) => { - saveStatusTestAuthProfile({ - dir, - profileId: "openai-codex:legacy", - provider: "openai-codex", - }); - - await buildStatusText({ - cfg: { - ...baseCfg, - agents: { - defaults: { - agentRuntime: { id: "codex" }, - }, - }, - }, - sessionEntry: { - sessionId: "sess-status-codex-legacy-profile", - updatedAt: 0, - authProfileOverride: "openai-codex:legacy", - }, - sessionKey: "agent:main:main", - parentSessionKey: "agent:main:main", - sessionScope: "per-sender", - statusChannel: "mobilechat", - provider: "openai", - model: "gpt-5.5", - contextTokens: 32_000, - resolvedFastMode: false, - resolvedVerboseLevel: "off", - resolvedReasoningLevel: "off", - resolveDefaultThinkingLevel: async () => undefined, - isGroup: false, - defaultGroupActivation: () => "mention", - modelAuthOverride: "oauth", - activeModelAuthOverride: "oauth", - }); - - const providerUsageCall = providerUsageMock.loadProviderUsageSummary.mock.calls.find( - ([params]) => params?.providers?.includes("openai"), - ); - expect(providerUsageCall?.[0]?.auth).toEqual([ - { - ...expectedCodexRuntimeUsageAuth[0], - authProfileId: "openai-codex:legacy", - }, - ]); - }, - { skipSessionCleanup: true, skipHomeCleanup: true }, - ); - }); - it("loads Codex synthetic usage when no local OpenAI profile label exists", async () => { registerStatusCodexHarness(); providerUsageMock.loadProviderUsageSummary.mockResolvedValue({ diff --git a/src/commands/doctor-legacy-config.migrations.test.ts b/src/commands/doctor-legacy-config.migrations.test.ts index 4060d79c2bb7..d32055450271 100644 --- a/src/commands/doctor-legacy-config.migrations.test.ts +++ b/src/commands/doctor-legacy-config.migrations.test.ts @@ -356,7 +356,7 @@ describe("normalizeCompatibilityConfigValues", () => { token: "secretref-env:DISCORD_BOT_TOKEN", accounts: { work: { - token: "secretref-env:DISCORD_WORK_TOKEN", + token: "__env__:DISCORD_WORK_TOKEN", }, }, }, @@ -378,7 +378,7 @@ describe("normalizeCompatibilityConfigValues", () => { "Moved channels.discord.accounts.default.token secretref-env:DISCORD_BOT_TOKEN marker → structured env SecretRef.", ); expect(res.changes).toContain( - "Moved channels.discord.accounts.work.token secretref-env:DISCORD_WORK_TOKEN marker → structured env SecretRef.", + "Moved channels.discord.accounts.work.token __env__:DISCORD_WORK_TOKEN marker → structured env SecretRef.", ); }); diff --git a/src/commands/models/auth.test.ts b/src/commands/models/auth.test.ts index 4d94035ca31d..0c0250ad8d72 100644 --- a/src/commands/models/auth.test.ts +++ b/src/commands/models/auth.test.ts @@ -1318,6 +1318,15 @@ describe("modelsAuthLoginCommand", () => { ); }); + it.each(["openai-codex", "codex-cli"])( + "rejects retired manual auth provider %s", + async (provider) => { + await expect(modelsAuthLoginCommand({ provider }, createRuntime())).rejects.toThrow( + `"${provider}" is a legacy provider ID; use --provider openai.`, + ); + }, + ); + it("does not persist a cancelled manual token entry", async () => { const runtime = createRuntime(); const exitSpy = vi.spyOn(process, "exit").mockImplementation((( diff --git a/src/commands/models/auth.ts b/src/commands/models/auth.ts index fcbaf2b9cd68..04e0b39c63ef 100644 --- a/src/commands/models/auth.ts +++ b/src/commands/models/auth.ts @@ -151,9 +151,10 @@ function resolveDefaultTokenProfileId(provider: string): string { function normalizeManualAuthProvider(provider: string): string { const normalized = normalizeProviderId(provider); - return normalized === "openai" || normalized === "codex" || normalized === "openai-codex" - ? "openai" - : normalized; + if (normalized === "openai-codex" || normalized === "codex-cli") { + throw new Error(`"${normalized}" is a legacy provider ID; use --provider openai.`); + } + return normalized === "openai" || normalized === "codex" ? "openai" : normalized; } function isOpenAIProvider(provider: string): boolean { diff --git a/src/config/types.secrets.ts b/src/config/types.secrets.ts index 6d57fc9b758e..4c21ed7840fd 100644 --- a/src/config/types.secrets.ts +++ b/src/config/types.secrets.ts @@ -102,12 +102,24 @@ export function parseEnvTemplateSecretRef( }; } -/** Parse legacy env SecretRef marker strings kept for config migration/read compatibility. */ +/** Detect retired env SecretRef marker strings for migration and explicit rejection. */ +export function isLegacySecretRefEnvMarker(value: unknown): value is string { + if (typeof value !== "string") { + return false; + } + const trimmed = value.trim(); + return ( + trimmed.startsWith(LEGACY_SECRETREF_ENV_MARKER_PREFIX) || + trimmed.startsWith(LEGACY_DOUBLE_UNDERSCORE_ENV_MARKER_PREFIX) + ); +} + +/** Parse legacy env SecretRef marker strings for config migration. */ export function parseLegacySecretRefEnvMarker( value: unknown, provider = DEFAULT_SECRET_PROVIDER_ALIAS, ): SecretRef | null { - if (typeof value !== "string") { + if (!isLegacySecretRefEnvMarker(value)) { return null; } const trimmed = value.trim(); @@ -130,15 +142,12 @@ export function parseLegacySecretRefEnvMarker( }; } -/** Coerce canonical, legacy, and env-shorthand secret inputs into a SecretRef. */ +/** Coerce canonical and env-shorthand secret inputs into a SecretRef. + * Retired string markers are parsed only by doctor migration above. */ export function coerceSecretRef(value: unknown, defaults?: SecretDefaults): SecretRef | null { if (isSecretRef(value)) { return value; } - const legacyEnvMarker = parseLegacySecretRefEnvMarker(value, defaults?.env); - if (legacyEnvMarker) { - return legacyEnvMarker; - } if (isLegacySecretRefWithoutProvider(value)) { const provider = value.source === "env" diff --git a/src/plugin-sdk/provider-auth-login-flow-runtime.ts b/src/plugin-sdk/provider-auth-login-flow-runtime.ts index e41ad19c4748..999afe8f382c 100644 --- a/src/plugin-sdk/provider-auth-login-flow-runtime.ts +++ b/src/plugin-sdk/provider-auth-login-flow-runtime.ts @@ -22,7 +22,7 @@ const CODEX_LOGIN_PROVIDER = "openai"; const CODEX_LOGIN_METHOD = "device-code"; const CODEX_LOGIN_FLOW_TTL_MS = 15 * 60_000; -const CODEX_LOGIN_PROVIDER_ALIASES = new Set(["codex", "openai", "openai-codex"]); +const CODEX_LOGIN_PROVIDER_ALIASES = new Set(["codex", "openai"]); type CodexLoginFlowRecord = { expiresAt: number; diff --git a/src/secrets/legacy-secretref-env-marker.ts b/src/secrets/legacy-secretref-env-marker.ts index c4b6761f2fec..05406f17aa1d 100644 --- a/src/secrets/legacy-secretref-env-marker.ts +++ b/src/secrets/legacy-secretref-env-marker.ts @@ -1,7 +1,7 @@ /** Detects legacy SecretRef env markers in config values. */ import type { OpenClawConfig } from "../config/types.openclaw.js"; import { - LEGACY_SECRETREF_ENV_MARKER_PREFIX, + isLegacySecretRefEnvMarker, parseLegacySecretRefEnvMarker, type SecretRef, } from "../config/types.secrets.js"; @@ -19,10 +19,6 @@ type LegacySecretRefEnvMarkerCandidate = { ref: SecretRef | null; }; -function isLegacySecretRefEnvMarker(value: unknown): value is string { - return typeof value === "string" && value.trim().startsWith(LEGACY_SECRETREF_ENV_MARKER_PREFIX); -} - function toCandidate( target: DiscoveredConfigSecretTarget, defaults: NonNullable["defaults"] | undefined, @@ -73,9 +69,7 @@ export function migrateLegacySecretRefEnvMarkers(config: OpenClawConfig): { } // Only registered existing paths are rewritten; malformed markers remain for explicit repair. if (setPathExistingStrict(next, candidate.pathSegments, ref)) { - changes.push( - `Moved ${candidate.path} ${LEGACY_SECRETREF_ENV_MARKER_PREFIX}${ref.id} marker → structured env SecretRef.`, - ); + changes.push(`Moved ${candidate.path} ${candidate.value} marker → structured env SecretRef.`); } } return { config: next, changes }; diff --git a/src/shared/fast-mode.ts b/src/shared/fast-mode.ts index 694030b0c532..8b3369b14ab7 100644 --- a/src/shared/fast-mode.ts +++ b/src/shared/fast-mode.ts @@ -40,16 +40,6 @@ function modelConfigKey(provider?: string, model?: string): string { : `${providerId}/${modelId}`; } -function modelConfigKeys(provider?: string, model?: string): string[] { - const key = modelConfigKey(provider, model); - const providerId = normalizeLowercaseStringOrEmpty(provider?.trim() ?? ""); - if (providerId !== "openai-codex") { - return [key]; - } - const openAiKey = modelConfigKey("openai", model); - return openAiKey === key ? [key] : [key, openAiKey]; -} - export function resolveFastModeModelParams(params: { cfg: FastModeConfig | undefined; provider?: string; @@ -59,13 +49,7 @@ export function resolveFastModeModelParams(params: { if (!models) { return undefined; } - for (const key of modelConfigKeys(params.provider, params.model)) { - const modelConfig = models[key]; - if (modelConfig?.params) { - return modelConfig.params; - } - } - return undefined; + return models[modelConfigKey(params.provider, params.model)]?.params; } function normalizeFastModeAutoOnSeconds(value: unknown): number | undefined { diff --git a/src/status/status-text.ts b/src/status/status-text.ts index 3ed70526bd9b..254666816913 100644 --- a/src/status/status-text.ts +++ b/src/status/status-text.ts @@ -21,7 +21,6 @@ import { } from "../agents/model-runtime-aliases.js"; import { resolveDefaultModelForAgent } from "../agents/model-selection.js"; import { listOpenAIAuthProfileProvidersForAgentRuntime } from "../agents/openai-routing.js"; -import { resolveProviderIdForAuth } from "../agents/provider-auth-aliases.js"; import { resolveSessionRuntimeOverrideForProvider } from "../agents/session-runtime-compat.js"; import { resolveInternalSessionKey, @@ -184,11 +183,7 @@ function resolveCodexSyntheticUsageAuthProfileId(params: { if (!credential) { return undefined; } - const credentialProvider = normalizeOptionalLowercaseString(credential.provider); - const resolvedProvider = resolveProviderIdForAuth(credential.provider, { config: params.cfg }); - return resolvedProvider === "openai" || - credentialProvider === "openai-codex" || - credentialProvider === "codex-cli" + return normalizeOptionalLowercaseString(credential.provider) === "openai" ? normalizedProfileId : undefined; } catch { diff --git a/src/web/provider-runtime-shared.test.ts b/src/web/provider-runtime-shared.test.ts index 4edc11718d52..e713df0c959a 100644 --- a/src/web/provider-runtime-shared.test.ts +++ b/src/web/provider-runtime-shared.test.ts @@ -105,6 +105,22 @@ describe("hasWebProviderEntryCredential", () => { ).toBe(false); }); + it.each([ + { raw: "secretref-env:CUSTOM_API_KEY", fallback: undefined }, + { raw: undefined, fallback: "__env__:CUSTOM_API_KEY" }, + ])("rejects retired secret markers instead of treating them as literals", ({ raw, fallback }) => { + expect( + hasWebProviderEntryCredential({ + provider, + config: {}, + toolConfig: undefined, + resolveRawValue: () => raw, + resolveFallbackRawValue: () => fallback, + resolveEnvValue: () => undefined, + }), + ).toBe(false); + }); + it("keeps non-reference config strings as literal credentials", () => { expect( hasWebProviderEntryCredential({ diff --git a/src/web/provider-runtime-shared.ts b/src/web/provider-runtime-shared.ts index e4179cb851a7..b8ec1ff10c00 100644 --- a/src/web/provider-runtime-shared.ts +++ b/src/web/provider-runtime-shared.ts @@ -1,4 +1,6 @@ // Shared web provider config, credential, and definition resolution. +import { coerceSecretRef, isLegacySecretRefEnvMarker } from "../config/types.secrets.js"; + type WebProviderConfigSource = { tools?: { web?: { @@ -8,21 +10,6 @@ type WebProviderConfigSource = { }; }; -type SecretRefSource = "env" | "file" | "exec"; - -type SecretRef = { - source: SecretRefSource; - provider: string; - id: string; -}; - -const DEFAULT_SECRET_PROVIDER_ALIAS = "default"; -const ENV_SECRET_REF_ID_RE = /^[A-Z][A-Z0-9_]{0,127}$/; -const LEGACY_SECRETREF_ENV_MARKER_PREFIX = "secretref-env:"; -const LEGACY_DOUBLE_UNDERSCORE_ENV_MARKER_PREFIX = "__env__:"; -const ENV_SECRET_TEMPLATE_RE = /^\$\{([A-Z][A-Z0-9_]{0,127})\}$/; -const ENV_SECRET_SHORTHAND_RE = /^\$([A-Z][A-Z0-9_]{0,127})$/; - type RuntimeWebProviderMetadata = { providerConfigured?: string; selectedProvider?: string; @@ -36,10 +23,6 @@ type ProviderWithCredential = { type WebContentProcessEnv = Record; -function isRecord(value: unknown): value is Record { - return typeof value === "object" && value !== null && !Array.isArray(value); -} - function normalizeSecretInputString(value: unknown): string | undefined { if (typeof value !== "string") { return undefined; @@ -68,59 +51,6 @@ function normalizeSecretInput(value: unknown): string { return latin1Only.trim(); } -function isSecretRef(value: unknown): value is SecretRef { - if (!isRecord(value)) { - return false; - } - if (Object.keys(value).length !== 3) { - return false; - } - return ( - (value.source === "env" || value.source === "file" || value.source === "exec") && - typeof value.provider === "string" && - value.provider.trim().length > 0 && - typeof value.id === "string" && - value.id.trim().length > 0 - ); -} - -function coerceSecretRef(value: unknown): SecretRef | null { - if (isSecretRef(value)) { - return value; - } - if (typeof value === "string") { - const trimmed = value.trim(); - const legacyPrefix = trimmed.startsWith(LEGACY_SECRETREF_ENV_MARKER_PREFIX) - ? LEGACY_SECRETREF_ENV_MARKER_PREFIX - : trimmed.startsWith(LEGACY_DOUBLE_UNDERSCORE_ENV_MARKER_PREFIX) - ? LEGACY_DOUBLE_UNDERSCORE_ENV_MARKER_PREFIX - : undefined; - if (legacyPrefix) { - const id = trimmed.slice(legacyPrefix.length); - return ENV_SECRET_REF_ID_RE.test(id) - ? { source: "env", provider: DEFAULT_SECRET_PROVIDER_ALIAS, id } - : null; - } - const match = ENV_SECRET_TEMPLATE_RE.exec(trimmed) ?? ENV_SECRET_SHORTHAND_RE.exec(trimmed); - const id = match?.[1]; - return id ? { source: "env", provider: DEFAULT_SECRET_PROVIDER_ALIAS, id } : null; - } - if ( - isRecord(value) && - (value.source === "env" || value.source === "file" || value.source === "exec") && - typeof value.id === "string" && - value.id.trim().length > 0 && - value.provider === undefined - ) { - return { - source: value.source, - provider: DEFAULT_SECRET_PROVIDER_ALIAS, - id: value.id, - }; - } - return null; -} - export function resolveWebProviderConfig( cfg: WebProviderConfigSource | undefined, kind: "search" | "fetch", @@ -187,6 +117,9 @@ export function hasWebProviderEntryCredential< config: params.config, toolConfig: params.toolConfig, }); + if (isLegacySecretRefEnvMarker(rawValue)) { + return false; + } const configuredRef = coerceSecretRef(rawValue); if (configuredRef && configuredRef.source !== "env") { return true; @@ -216,6 +149,9 @@ export function hasWebProviderEntryCredential< config: params.config, toolConfig: params.toolConfig, }); + if (isLegacySecretRefEnvMarker(fallbackRawValue)) { + return false; + } const fallbackRef = coerceSecretRef(fallbackRawValue); if (fallbackRef && fallbackRef.source !== "env") { return true;