fix(runtime): drop legacy x_search auth shim

This commit is contained in:
Vincent Koc
2026-04-06 11:03:34 +01:00
parent 3ff606e490
commit 6efbebefbf
4 changed files with 73 additions and 62 deletions

View File

@@ -80,6 +80,42 @@ describe("legacy x_search config migration", () => {
});
});
it("moves legacy x_search SecretRefs into the xai plugin auth slot unchanged", () => {
const res = migrateLegacyXSearchConfig({
tools: {
web: {
x_search: {
apiKey: {
source: "env",
provider: "default",
id: "X_SEARCH_KEY_REF",
},
enabled: true,
},
} as Record<string, unknown>,
},
} as OpenClawConfig);
expect((res.config.tools?.web as Record<string, unknown> | undefined)?.x_search).toEqual({
enabled: true,
});
expect(res.config.plugins?.entries?.xai).toEqual({
enabled: true,
config: {
webSearch: {
apiKey: {
source: "env",
provider: "default",
id: "X_SEARCH_KEY_REF",
},
},
},
});
expect(res.changes).toEqual([
"Moved tools.web.x_search.apiKey → plugins.entries.xai.config.webSearch.apiKey.",
]);
});
it("does nothing for knob-only x_search config without a legacy apiKey", () => {
const config = {
tools: {

View File

@@ -651,6 +651,41 @@ describe("config strict validation", () => {
});
});
it("accepts legacy x_search SecretRefs via auto-migration and reports legacyIssues", async () => {
await withTempHome(async (home) => {
await writeOpenClawConfig(home, {
tools: {
web: {
x_search: {
apiKey: {
source: "env",
provider: "default",
id: "X_SEARCH_KEY_REF",
},
},
},
},
});
const snap = await readConfigFileSnapshot();
expect(snap.valid).toBe(true);
expect(snap.legacyIssues.some((issue) => issue.path === "tools.web.x_search.apiKey")).toBe(
true,
);
expect(snap.sourceConfig.plugins?.entries?.xai?.config?.webSearch).toMatchObject({
apiKey: {
source: "env",
provider: "default",
id: "X_SEARCH_KEY_REF",
},
});
expect(
(snap.sourceConfig.tools?.web?.x_search as Record<string, unknown> | undefined)?.apiKey,
).toBeUndefined();
});
});
it("accepts legacy thread binding ttlHours via auto-migration and reports legacyIssues", async () => {
await withTempHome(async (home) => {
await writeOpenClawConfig(home, {

View File

@@ -382,16 +382,6 @@ export async function resolveRuntimeWebTools(params: {
const sourceTools = isRecord(params.sourceConfig.tools) ? params.sourceConfig.tools : undefined;
const sourceWeb = isRecord(sourceTools?.web) ? sourceTools.web : undefined;
const resolvedTools = isRecord(params.resolvedConfig.tools)
? params.resolvedConfig.tools
: undefined;
const resolvedWeb = isRecord(resolvedTools?.web) ? resolvedTools.web : undefined;
const legacyXSearchSourceRaw: unknown = sourceWeb?.x_search;
const legacyXSearchResolvedRaw: unknown = resolvedWeb?.x_search;
const legacyXSearchSource = isRecord(legacyXSearchSourceRaw) ? legacyXSearchSourceRaw : undefined;
const legacyXSearchResolved = isRecord(legacyXSearchResolvedRaw)
? legacyXSearchResolvedRaw
: undefined;
if (!sourceWeb && !hasPluginWebToolConfig(params.sourceConfig)) {
return {
search: {
@@ -405,24 +395,6 @@ export async function resolveRuntimeWebTools(params: {
diagnostics,
};
}
if (
legacyXSearchSource &&
legacyXSearchResolved &&
Object.prototype.hasOwnProperty.call(legacyXSearchSource, "apiKey")
) {
const apiKey = legacyXSearchSource["apiKey"];
const resolution = await resolveSecretInputWithEnvFallback({
sourceConfig: params.sourceConfig,
context: params.context,
defaults,
value: apiKey,
path: "tools.web.x_search.apiKey",
envVars: ["XAI_API_KEY"],
});
if (resolution.value) {
legacyXSearchResolved["apiKey"] = resolution.value;
}
}
const search = isRecord(sourceWeb?.search) ? sourceWeb.search : undefined;
const rawProvider =
typeof search?.provider === "string" ? search.provider.trim().toLowerCase() : "";

View File

@@ -1947,7 +1947,7 @@ describe("secrets runtime snapshot", () => {
}
});
it("keeps legacy x_search SecretRefs in place until doctor repairs them", async () => {
it("leaves legacy x_search SecretRefs untouched so doctor owns the migration", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
tools: {
@@ -1968,45 +1968,13 @@ describe("secrets runtime snapshot", () => {
});
expect((snapshot.config.tools?.web as Record<string, unknown> | undefined)?.x_search).toEqual({
apiKey: "xai-runtime-key",
apiKey: { source: "env", provider: "default", id: "X_SEARCH_KEY_REF" },
enabled: true,
model: "grok-4-1-fast",
});
expect(snapshot.config.plugins?.entries?.xai).toBeUndefined();
});
it("still resolves legacy x_search auth in place even when unrelated legacy config is present", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
tools: {
web: {
x_search: {
apiKey: { source: "env", provider: "default", id: "X_SEARCH_KEY_REF" },
enabled: true,
},
},
},
channels: {
telegram: {
groupMentionsOnly: true,
groups: [],
},
},
}),
env: {
X_SEARCH_KEY_REF: "xai-runtime-key-invalid-config",
},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect((snapshot.config.tools?.web as Record<string, unknown> | undefined)?.x_search).toEqual({
apiKey: "xai-runtime-key-invalid-config",
enabled: true,
});
expect(snapshot.config.plugins?.entries?.xai).toBeUndefined();
});
it("does not force-enable xai at runtime for knob-only x_search config", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({