mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-30 19:32:27 +00:00
Rebase: reconcile xAI post-main conflicts
This commit is contained in:
committed by
Peter Steinberger
parent
fb989f0402
commit
b918568b1e
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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({});
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user