xAI: add auth resolution diagnostics

This commit is contained in:
huntharo 2026-03-27 13:54:22 -04:00 committed by Peter Steinberger
parent d5fafbe3ce
commit f0ce658fbb
4 changed files with 159 additions and 5 deletions

View File

@ -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}".`,

View File

@ -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,

View File

@ -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 (

View File

@ -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<void> => {
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);