mirror of https://github.com/openclaw/openclaw.git
xAI: add auth resolution diagnostics
This commit is contained in:
parent
d5fafbe3ce
commit
f0ce658fbb
|
|
@ -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}".`,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue