From f0ce658fbba8bef78f6a2a4f568d5a43bafe22e8 Mon Sep 17 00:00:00 2001 From: huntharo Date: Fri, 27 Mar 2026 13:54:22 -0400 Subject: [PATCH] xAI: add auth resolution diagnostics --- src/agents/model-auth.ts | 89 +++++++++++++++++-- src/agents/models-config.providers.secrets.ts | 26 ++++++ src/agents/pi-embedded-runner/run/attempt.ts | 18 ++++ .../pi-embedded-runner/run/auth-controller.ts | 31 +++++++ 4 files changed, 159 insertions(+), 5 deletions(-) diff --git a/src/agents/model-auth.ts b/src/agents/model-auth.ts index 5928beb3cbf..6a5dd3c4301 100644 --- a/src/agents/model-auth.ts +++ b/src/agents/model-auth.ts @@ -6,6 +6,7 @@ import type { ModelProviderAuthMode, ModelProviderConfig } from "../config/types import { coerceSecretRef } from "../config/types.secrets.js"; import { getShellEnvAppliedKeys } from "../infra/shell-env.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; +import { formatApiKeyPreview } from "../plugins/provider-auth-input.js"; import { buildProviderMissingAuthMessageWithPlugin, resolveProviderSyntheticAuthWithPlugin, @@ -38,6 +39,37 @@ export { requireApiKey, resolveAwsSdkEnvVarName } from "./model-auth-runtime-sha export type { ResolvedProviderAuth } from "./model-auth-runtime-shared.js"; const log = createSubsystemLogger("model-auth"); + +function shouldTraceProviderAuth(provider: string): boolean { + return normalizeProviderId(provider) === "xai"; +} + +function summarizeProviderAuthKey(apiKey: string | undefined): string { + const trimmed = apiKey?.trim() ?? ""; + if (!trimmed) { + return "missing"; + } + if (isNonSecretApiKeyMarker(trimmed)) { + return `marker:${trimmed}`; + } + return formatApiKeyPreview(trimmed); +} + +function logProviderAuthDecision(params: { + provider: string; + stage: string; + source?: string; + mode?: string; + profileId?: string; + apiKey?: string; +}): void { + if (!shouldTraceProviderAuth(params.provider)) { + return; + } + log.info( + `[xai-auth] ${params.stage}: source=${params.source ?? "unknown"} mode=${params.mode ?? "unknown"} profile=${params.profileId ?? "none"} key=${summarizeProviderAuthKey(params.apiKey)}`, + ); +} function resolveProviderConfig( cfg: OpenClawConfig | undefined, provider: string, @@ -308,12 +340,23 @@ export async function resolveApiKeyForProvider(params: { }); if (resolved) { const mode = store.profiles[candidate]?.type; - return { + const resolvedMode: ResolvedProviderAuth["mode"] = + mode === "oauth" ? "oauth" : mode === "token" ? "token" : "api-key"; + const result: ResolvedProviderAuth = { apiKey: resolved.apiKey, profileId: candidate, source: `profile:${candidate}`, - mode: mode === "oauth" ? "oauth" : mode === "token" ? "token" : "api-key", + mode: resolvedMode, }; + logProviderAuthDecision({ + provider, + stage: "resolved from profile", + source: result.source, + mode: result.mode, + profileId: result.profileId, + apiKey: result.apiKey, + }); + return result; } } catch (err) { log.debug?.(`auth profile "${candidate}" failed for provider "${provider}": ${String(err)}`); @@ -322,20 +365,46 @@ export async function resolveApiKeyForProvider(params: { const envResolved = resolveEnvApiKey(provider); if (envResolved) { - return { + const resolvedMode: ResolvedProviderAuth["mode"] = envResolved.source.includes("OAUTH_TOKEN") + ? "oauth" + : "api-key"; + const result: ResolvedProviderAuth = { apiKey: envResolved.apiKey, source: envResolved.source, - mode: envResolved.source.includes("OAUTH_TOKEN") ? "oauth" : "api-key", + mode: resolvedMode, }; + logProviderAuthDecision({ + provider, + stage: "resolved from env", + source: result.source, + mode: result.mode, + apiKey: result.apiKey, + }); + return result; } const customKey = resolveUsableCustomProviderApiKey({ cfg, provider }); if (customKey) { - return { apiKey: customKey.apiKey, source: customKey.source, mode: "api-key" }; + const result = { apiKey: customKey.apiKey, source: customKey.source, mode: "api-key" as const }; + logProviderAuthDecision({ + provider, + stage: "resolved from models.providers", + source: result.source, + mode: result.mode, + apiKey: result.apiKey, + }); + return result; } const syntheticLocalAuth = resolveSyntheticLocalProviderAuth({ cfg, provider }); if (syntheticLocalAuth) { + logProviderAuthDecision({ + provider, + stage: "resolved synthetic auth", + source: syntheticLocalAuth.source, + mode: syntheticLocalAuth.mode, + apiKey: syntheticLocalAuth.apiKey, + }); return syntheticLocalAuth; } @@ -366,12 +435,22 @@ export async function resolveApiKeyForProvider(params: { }, }); if (pluginMissingAuthMessage) { + logProviderAuthDecision({ + provider, + stage: "plugin missing auth message", + source: pluginMissingAuthMessage, + }); throw new Error(pluginMissingAuthMessage); } } const authStorePath = resolveAuthStorePathForDisplay(params.agentDir); const resolvedAgentDir = path.dirname(authStorePath); + logProviderAuthDecision({ + provider, + stage: "missing auth", + source: "no profiles/env/config fallback", + }); throw new Error( [ `No API key found for provider "${provider}".`, diff --git a/src/agents/models-config.providers.secrets.ts b/src/agents/models-config.providers.secrets.ts index d05a662c6e1..e84742f1210 100644 --- a/src/agents/models-config.providers.secrets.ts +++ b/src/agents/models-config.providers.secrets.ts @@ -1,5 +1,7 @@ import type { OpenClawConfig } from "../config/config.js"; import { coerceSecretRef, resolveSecretInputRef } from "../config/types.secrets.js"; +import { createSubsystemLogger } from "../logging/subsystem.js"; +import { formatApiKeyPreview } from "../plugins/provider-auth-input.js"; import { resolveProviderSyntheticAuthWithPlugin } from "../plugins/provider-runtime.js"; import { normalizeOptionalSecretInput } from "../utils/normalize-secret-input.js"; import { listProfilesForProvider } from "./auth-profiles/profiles.js"; @@ -45,6 +47,22 @@ export type ProviderAuthResolver = ( }; const ENV_VAR_NAME_RE = /^[A-Z_][A-Z0-9_]*$/; +const log = createSubsystemLogger("agents/model-providers"); + +function shouldTraceProviderAuth(provider: string): boolean { + return provider.trim().toLowerCase() === "xai"; +} + +function summarizeProviderAuthKey(apiKey: string | undefined): string { + const trimmed = apiKey?.trim() ?? ""; + if (!trimmed) { + return "missing"; + } + if (isNonSecretApiKeyMarker(trimmed)) { + return `marker:${trimmed}`; + } + return formatApiKeyPreview(trimmed); +} export function normalizeApiKeyConfig(value: string): string { const trimmed = value.trim(); @@ -431,8 +449,16 @@ function resolveConfigBackedProviderAuth(params: { provider: string; config?: Op }); const apiKey = synthetic?.apiKey?.trim(); if (!apiKey) { + if (shouldTraceProviderAuth(params.provider)) { + log.info("[xai-auth] bootstrap config fallback: no config-backed key found"); + } return undefined; } + if (shouldTraceProviderAuth(params.provider)) { + log.info( + `[xai-auth] bootstrap config fallback: key=${summarizeProviderAuthKey(apiKey)} marker=${isNonSecretApiKeyMarker(apiKey) ? "kept" : "secretref-managed"} source=config`, + ); + } return isNonSecretApiKeyMarker(apiKey) ? { apiKey, diff --git a/src/agents/pi-embedded-runner/run/attempt.ts b/src/agents/pi-embedded-runner/run/attempt.ts index 86a965e730d..b88a9dd1c38 100644 --- a/src/agents/pi-embedded-runner/run/attempt.ts +++ b/src/agents/pi-embedded-runner/run/attempt.ts @@ -213,6 +213,18 @@ export { const MAX_BTW_SNAPSHOT_MESSAGES = 100; +function shouldTraceProviderAuth(provider: string): boolean { + return provider.trim().toLowerCase() === "xai"; +} + +function summarizeProviderAuthKey(apiKey: string | undefined): string { + const trimmed = apiKey?.trim() ?? ""; + if (!trimmed) { + return "missing"; + } + return `${trimmed.slice(0, 4)}…${trimmed.slice(-4)}`; +} + function summarizeMessagePayload(msg: AgentMessage): { textChars: number; imageBlocks: number } { const content = (msg as { content?: unknown }).content; if (typeof content === "string") { @@ -850,6 +862,12 @@ export async function runEmbeddedAttempt( agentDir, workspaceDir: effectiveWorkspace, }); + if (shouldTraceProviderAuth(params.provider)) { + const runtimeApiKey = await params.authStorage.getApiKey(params.provider).catch(() => ""); + log.info( + `[xai-auth] pre-stream setup: modelApi=${params.model.api} baseUrl=${params.model.baseUrl ?? "default"} runtimeAuthKey=${summarizeProviderAuthKey(runtimeApiKey)} headersAuth=${params.model.headers?.Authorization ? "present" : "absent"} responsesAuthPath=apiKey-argument`, + ); + } if (providerStreamFn) { activeSession.agent.streamFn = providerStreamFn; } else if ( diff --git a/src/agents/pi-embedded-runner/run/auth-controller.ts b/src/agents/pi-embedded-runner/run/auth-controller.ts index 3ad15b289b1..c283873f0fa 100644 --- a/src/agents/pi-embedded-runner/run/auth-controller.ts +++ b/src/agents/pi-embedded-runner/run/auth-controller.ts @@ -1,5 +1,6 @@ import type { Api, Model } from "@mariozechner/pi-ai"; import type { ThinkLevel } from "../../../auto-reply/thinking.js"; +import { formatApiKeyPreview } from "../../../plugins/provider-auth-input.js"; import { prepareProviderRuntimeAuth } from "../../../plugins/provider-runtime.js"; import { type AuthProfileStore, @@ -32,9 +33,19 @@ type RuntimeApiKeySink = { type LogLike = { debug(message: string): void; + info(message: string): void; warn(message: string): void; }; +function shouldTraceProviderAuth(provider: string): boolean { + return provider.trim().toLowerCase() === "xai"; +} + +function summarizeProviderAuthKey(apiKey: string | undefined): string { + const trimmed = apiKey?.trim() ?? ""; + return trimmed ? formatApiKeyPreview(trimmed) : "missing"; +} + export function createEmbeddedRunAuthController(params: { config: RunEmbeddedPiAgentParams["config"]; agentDir: string; @@ -283,6 +294,11 @@ export function createEmbeddedRunAuthController(params: { const applyApiKeyInfo = async (candidate?: string): Promise => { const apiKeyInfo = await resolveApiKeyForCandidate(candidate); + if (shouldTraceProviderAuth(params.getRuntimeModel().provider)) { + params.log.info( + `[xai-auth] auth-controller resolved api key: source=${apiKeyInfo.source} mode=${apiKeyInfo.mode} profile=${apiKeyInfo.profileId ?? candidate ?? "none"} key=${summarizeProviderAuthKey(apiKeyInfo.apiKey)}`, + ); + } params.setApiKeyInfo(apiKeyInfo); const resolvedProfileId = apiKeyInfo.profileId ?? candidate; if (!apiKeyInfo.apiKey) { @@ -315,12 +331,22 @@ export function createEmbeddedRunAuthController(params: { profileId: apiKeyInfo.profileId, }, }); + if (shouldTraceProviderAuth(runtimeModel.provider)) { + params.log.info( + `[xai-auth] auth-controller prepared runtime auth: returnedKey=${summarizeProviderAuthKey(preparedAuth?.apiKey)} baseUrl=${preparedAuth?.baseUrl ?? runtimeModel.baseUrl ?? "default"} expiresAt=${preparedAuth?.expiresAt ?? "none"}`, + ); + } if (preparedAuth?.baseUrl) { params.setRuntimeModel({ ...runtimeModel, baseUrl: preparedAuth.baseUrl }); params.setEffectiveModel({ ...params.getEffectiveModel(), baseUrl: preparedAuth.baseUrl }); } if (preparedAuth?.apiKey) { params.authStorage.setRuntimeApiKey(runtimeModel.provider, preparedAuth.apiKey); + if (shouldTraceProviderAuth(runtimeModel.provider)) { + params.log.info( + `[xai-auth] auth-controller set runtime api key from prepared auth: key=${summarizeProviderAuthKey(preparedAuth.apiKey)}`, + ); + } params.setRuntimeAuthState({ sourceApiKey: apiKeyInfo.apiKey, authMode: apiKeyInfo.mode, @@ -334,6 +360,11 @@ export function createEmbeddedRunAuthController(params: { } if (!runtimeAuthHandled) { params.authStorage.setRuntimeApiKey(runtimeModel.provider, apiKeyInfo.apiKey); + if (shouldTraceProviderAuth(runtimeModel.provider)) { + params.log.info( + `[xai-auth] auth-controller set runtime api key directly: key=${summarizeProviderAuthKey(apiKeyInfo.apiKey)}`, + ); + } params.setRuntimeAuthState(null); } params.setLastProfileId(apiKeyInfo.profileId);