Rebase: reconcile xAI post-main conflicts

This commit is contained in:
huntharo
2026-03-28 07:58:12 -04:00
committed by Peter Steinberger
parent fb989f0402
commit b918568b1e
5 changed files with 119 additions and 120 deletions

View File

@@ -4,7 +4,6 @@ import {
} from "openclaw/plugin-sdk/provider-auth";
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
import { createToolStreamWrapper } from "openclaw/plugin-sdk/provider-stream";
import { resolveProviderWebSearchPluginConfig } from "openclaw/plugin-sdk/provider-web-search";
import { normalizeSecretInputString } from "openclaw/plugin-sdk/secret-input";
import { applyXaiModelCompat, normalizeXaiModelId } from "./api.js";
import { applyXaiConfig, XAI_DEFAULT_MODEL_REF } from "./onboard.js";
@@ -51,6 +50,38 @@ function readLegacyGrokFallback(
return apiKey ? { apiKey, source: "tools.web.search.grok.apiKey" } : undefined;
}
function readPluginWebSearchFallback(
config: Record<string, unknown>,
): { apiKey: string; source: string } | undefined {
const plugins = config.plugins;
if (!plugins || typeof plugins !== "object") {
return undefined;
}
const entries = (plugins as Record<string, unknown>).entries;
if (!entries || typeof entries !== "object") {
return undefined;
}
const xai = (entries as Record<string, unknown>)[PROVIDER_ID];
if (!xai || typeof xai !== "object") {
return undefined;
}
const pluginConfig = (xai as Record<string, unknown>).config;
if (!pluginConfig || typeof pluginConfig !== "object") {
return undefined;
}
const webSearch = (pluginConfig as Record<string, unknown>).webSearch;
if (!webSearch || typeof webSearch !== "object") {
return undefined;
}
const apiKey = readConfiguredOrManagedApiKey((webSearch as Record<string, unknown>).apiKey);
return apiKey
? {
apiKey,
source: "plugins.entries.xai.config.webSearch.apiKey",
}
: undefined;
}
function resolveXaiProviderFallbackAuth(
config: unknown,
): { apiKey: string; source: string } | undefined {
@@ -58,14 +89,9 @@ function resolveXaiProviderFallbackAuth(
return undefined;
}
const record = config as Record<string, unknown>;
const pluginApiKey = readConfiguredOrManagedApiKey(
resolveProviderWebSearchPluginConfig(record, PROVIDER_ID)?.apiKey,
);
if (pluginApiKey) {
return {
apiKey: pluginApiKey,
source: "plugins.entries.xai.config.webSearch.apiKey",
};
const pluginFallback = readPluginWebSearchFallback(record);
if (pluginFallback) {
return pluginFallback;
}
return readLegacyGrokFallback(record);
}

View File

@@ -114,54 +114,6 @@ describe("provider discovery auth marker guardrails", () => {
expect(request?.headers?.Authorization).toBe("Bearer ALLCAPS_SAMPLE");
});
it("surfaces xai provider auth from plugin web search config without persisting plaintext", async () => {
const agentDir = await createAgentDirWithAuthProfiles({});
const providers = await resolveImplicitProvidersForTest({
agentDir,
env: {},
config: {
plugins: {
entries: {
xai: {
config: {
webSearch: {
apiKey: "xai-plugin-config-key", // pragma: allowlist secret
},
},
},
},
},
},
});
expect(providers?.xai?.apiKey).toBe(NON_ENV_SECRETREF_MARKER);
});
it("surfaces xai provider auth from SecretRef-backed plugin web search config", async () => {
const agentDir = await createAgentDirWithAuthProfiles({});
const providers = await resolveImplicitProvidersForTest({
agentDir,
env: {},
config: {
plugins: {
entries: {
xai: {
config: {
webSearch: {
apiKey: { source: "file", provider: "vault", id: "/xai/apiKey" },
},
},
},
},
},
},
});
expect(providers?.xai?.apiKey).toBe(NON_ENV_SECRETREF_MARKER);
});
it("surfaces xai provider auth from legacy grok web search config without persisting plaintext", async () => {
const agentDir = await createAgentDirWithAuthProfiles({});

View File

@@ -11,6 +11,10 @@ import {
runProviderCatalog,
} from "../plugins/provider-discovery.js";
import { ensureAuthProfileStore } from "./auth-profiles/store.js";
import {
isNonSecretApiKeyMarker,
resolveNonEnvSecretRefApiKeyMarker,
} from "./model-auth-markers.js";
import type {
ProviderApiKeyResolver,
ProviderAuthResolver,
@@ -174,14 +178,51 @@ async function resolvePluginImplicitProviders(
const discovered: Record<string, ProviderConfig> = {};
const catalogConfig = buildPluginCatalogConfig(ctx);
for (const provider of byOrder[order]) {
const resolveCatalogProviderApiKey = (providerId?: string) => {
const resolvedProviderId = providerId?.trim() || provider.id;
const resolved = ctx.resolveProviderApiKey(resolvedProviderId);
if (resolved.apiKey) {
return resolved;
}
if (
!findNormalizedProviderValue(
{
[provider.id]: true,
...Object.fromEntries((provider.aliases ?? []).map((alias) => [alias, true])),
...Object.fromEntries((provider.hookAliases ?? []).map((alias) => [alias, true])),
},
resolvedProviderId,
)
) {
return resolved;
}
const synthetic = provider.resolveSyntheticAuth?.({
config: catalogConfig,
provider: resolvedProviderId,
providerConfig: catalogConfig.models?.providers?.[resolvedProviderId],
});
const syntheticApiKey = synthetic?.apiKey?.trim();
if (!syntheticApiKey) {
return resolved;
}
return {
apiKey: isNonSecretApiKeyMarker(syntheticApiKey)
? syntheticApiKey
: resolveNonEnvSecretRefApiKeyMarker("file"),
discoveryApiKey: undefined,
};
};
const result = await runProviderCatalogWithTimeout({
provider,
config: catalogConfig,
agentDir: ctx.agentDir,
workspaceDir: ctx.workspaceDir,
env: ctx.env,
resolveProviderApiKey: (providerId) =>
ctx.resolveProviderApiKey(providerId?.trim() || provider.id),
resolveProviderApiKey: resolveCatalogProviderApiKey,
resolveProviderAuth: (providerId, options) =>
ctx.resolveProviderAuth(providerId?.trim() || provider.id, options),
timeoutMs: resolveLiveProviderCatalogTimeoutMs(ctx.env),

View File

@@ -2,7 +2,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 { resolvePluginDiscoveryProviders } from "../plugins/provider-discovery.js";
import { normalizeOptionalSecretInput } from "../utils/normalize-secret-input.js";
import { listProfilesForProvider } from "./auth-profiles/profiles.js";
import { ensureAuthProfileStore } from "./auth-profiles/store.js";
@@ -14,6 +14,7 @@ import {
resolveNonEnvSecretRefHeaderValueMarker,
} from "./model-auth-markers.js";
import { resolveAwsSdkEnvVarName } from "./model-auth-runtime-shared.js";
import { normalizeProviderId } from "./provider-id.js";
type ModelsConfig = NonNullable<OpenClawConfig["models"]>;
export type ProviderConfig = NonNullable<ModelsConfig["providers"]>[string];
@@ -441,16 +442,47 @@ function resolveConfigBackedProviderAuth(params: { provider: string; config?: Op
// Providers own any provider-specific fallback auth logic via
// resolveSyntheticAuth(...). Discovery/bootstrap callers may consume
// non-secret markers from source config, but must never persist plaintext.
const synthetic = resolveProviderSyntheticAuthWithPlugin({
provider: params.provider,
const normalizedProvider = normalizeProviderId(params.provider);
const providerPlugin = resolvePluginDiscoveryProviders({
config: params.config,
context: {
config: params.config,
provider: params.provider,
providerConfig: params.config?.models?.providers?.[params.provider],
},
}).find(
(provider) =>
normalizeProviderId(provider.id) === normalizedProvider ||
provider.aliases?.some((alias) => normalizeProviderId(alias) === normalizedProvider),
);
const synthetic = providerPlugin?.resolveSyntheticAuth?.({
config: params.config,
provider: params.provider,
providerConfig: params.config?.models?.providers?.[params.provider],
});
const apiKey = synthetic?.apiKey?.trim();
let apiKey = synthetic?.apiKey?.trim();
if (!apiKey && normalizeProviderId(params.provider) === "xai") {
const pluginApiKey = normalizeOptionalSecretInput(
params.config?.plugins?.entries?.xai?.config &&
typeof params.config.plugins.entries.xai.config === "object" &&
!Array.isArray(params.config.plugins.entries.xai.config)
? ((params.config.plugins.entries.xai.config as { webSearch?: { apiKey?: unknown } })
.webSearch?.apiKey ?? undefined)
: undefined,
);
const pluginApiKeyRef = coerceSecretRef(
params.config?.plugins?.entries?.xai?.config &&
typeof params.config.plugins.entries.xai.config === "object" &&
!Array.isArray(params.config.plugins.entries.xai.config)
? ((params.config.plugins.entries.xai.config as { webSearch?: { apiKey?: unknown } })
.webSearch?.apiKey ?? undefined)
: undefined,
);
const legacyApiKey = normalizeOptionalSecretInput(
params.config?.tools?.web?.search?.grok?.apiKey,
);
const legacyApiKeyRef = coerceSecretRef(params.config?.tools?.web?.search?.grok?.apiKey);
apiKey =
pluginApiKey ??
(pluginApiKeyRef ? resolveNonEnvSecretRefApiKeyMarker(pluginApiKeyRef.source) : undefined) ??
legacyApiKey ??
(legacyApiKeyRef ? resolveNonEnvSecretRefApiKeyMarker(legacyApiKeyRef.source) : undefined);
}
if (!apiKey) {
if (shouldTraceProviderAuth(params.provider)) {
log.info("[xai-auth] bootstrap config fallback: no config-backed key found");

View File

@@ -364,10 +364,6 @@ function applyPostPluginStreamWrappers(
// upstream model-ID heuristics for Gemini 3.1 variants.
ctx.agent.streamFn = createGoogleThinkingPayloadWrapper(ctx.agent.streamFn, ctx.thinkingLevel);
if (ctx.provider === "xai") {
ctx.agent.streamFn = createXaiReasoningEffortStripWrapper(ctx.agent.streamFn);
}
const anthropicFastMode = resolveAnthropicFastMode(ctx.effectiveExtraParams);
if (anthropicFastMode !== undefined) {
log.debug(
@@ -504,53 +500,5 @@ export function applyExtraParamsToAgent(
providerWrapperHandled,
});
if (typeof effectiveExtraParams?.fastMode === "boolean") {
log.debug(
`applying MiniMax fast mode=${effectiveExtraParams.fastMode} for ${provider}/${modelId}`,
);
agent.streamFn = createMinimaxFastModeWrapper(agent.streamFn, effectiveExtraParams.fastMode);
log.debug(`applying xAI fast mode=${effectiveExtraParams.fastMode} for ${provider}/${modelId}`);
agent.streamFn = createXaiFastModeWrapper(agent.streamFn, effectiveExtraParams.fastMode);
}
const openAIFastMode = resolveOpenAIFastMode(effectiveExtraParams);
if (openAIFastMode) {
log.debug(`applying OpenAI fast mode for ${provider}/${modelId}`);
agent.streamFn = createOpenAIFastModeWrapper(agent.streamFn);
}
const openAIServiceTier = resolveOpenAIServiceTier(effectiveExtraParams);
if (openAIServiceTier) {
log.debug(`applying OpenAI service_tier=${openAIServiceTier} for ${provider}/${modelId}`);
agent.streamFn = createOpenAIServiceTierWrapper(agent.streamFn, openAIServiceTier);
}
// Work around upstream pi-ai hardcoding `store: false` for Responses API.
// Force `store=true` for direct OpenAI Responses models and auto-enable
// server-side compaction for compatible OpenAI Responses payloads.
agent.streamFn = createOpenAIResponsesContextManagementWrapper(
agent.streamFn,
effectiveExtraParams,
);
const rawParallelToolCalls = resolveAliasedParamValue(
[resolvedExtraParams, override],
"parallel_tool_calls",
"parallelToolCalls",
);
if (rawParallelToolCalls !== undefined) {
if (typeof rawParallelToolCalls === "boolean") {
agent.streamFn = createParallelToolCallsWrapper(agent.streamFn, rawParallelToolCalls);
} else if (rawParallelToolCalls === null) {
log.debug("parallel_tool_calls suppressed by null override, skipping injection");
} else {
const summary =
typeof rawParallelToolCalls === "string"
? rawParallelToolCalls
: typeof rawParallelToolCalls;
log.warn(`ignoring invalid parallel_tool_calls param: ${summary}`);
}
}
return { effectiveExtraParams };
}