mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-30 16:40:21 +00:00
* fix(xai): make x_search auth plugin-owned * fix(xai): restore x_search runtime migration fallback * fix(xai): narrow legacy x_search auth migration * fix(secrets): drop legacy x_search target registry entry * fix(xai): no-op knob-only x_search migration fallback
109 lines
3.3 KiB
TypeScript
109 lines
3.3 KiB
TypeScript
type JsonRecord = Record<string, unknown>;
|
|
|
|
const XAI_PLUGIN_ID = "xai";
|
|
const X_SEARCH_LEGACY_PATH = "tools.web.x_search";
|
|
const XAI_WEB_SEARCH_PLUGIN_KEY_PATH = `plugins.entries.${XAI_PLUGIN_ID}.config.webSearch.apiKey`;
|
|
|
|
function isRecord(value: unknown): value is JsonRecord {
|
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
}
|
|
|
|
function cloneRecord<T extends JsonRecord | undefined>(value: T): T {
|
|
if (!value) {
|
|
return value;
|
|
}
|
|
return { ...value } as T;
|
|
}
|
|
|
|
function ensureRecord(target: JsonRecord, key: string): JsonRecord {
|
|
const current = target[key];
|
|
if (isRecord(current)) {
|
|
return current;
|
|
}
|
|
const next: JsonRecord = {};
|
|
target[key] = next;
|
|
return next;
|
|
}
|
|
|
|
function resolveLegacyXSearchConfig(raw: unknown): JsonRecord | undefined {
|
|
if (!isRecord(raw)) {
|
|
return undefined;
|
|
}
|
|
const tools = isRecord(raw.tools) ? raw.tools : undefined;
|
|
const web = isRecord(tools?.web) ? tools.web : undefined;
|
|
return isRecord(web?.x_search) ? web.x_search : undefined;
|
|
}
|
|
|
|
function resolveLegacyXSearchAuth(legacy: JsonRecord): unknown {
|
|
return legacy.apiKey;
|
|
}
|
|
|
|
export function listLegacyXSearchConfigPaths(raw: unknown): string[] {
|
|
const legacy = resolveLegacyXSearchConfig(raw);
|
|
if (!legacy || !Object.prototype.hasOwnProperty.call(legacy, "apiKey")) {
|
|
return [];
|
|
}
|
|
return [`${X_SEARCH_LEGACY_PATH}.apiKey`];
|
|
}
|
|
|
|
export function migrateLegacyXSearchConfig<T>(raw: T): { config: T; changes: string[] } {
|
|
if (!isRecord(raw)) {
|
|
return { config: raw, changes: [] };
|
|
}
|
|
const legacy = resolveLegacyXSearchConfig(raw);
|
|
if (!legacy || !Object.prototype.hasOwnProperty.call(legacy, "apiKey")) {
|
|
return { config: raw, changes: [] };
|
|
}
|
|
|
|
const nextRoot = structuredClone(raw);
|
|
const tools = ensureRecord(nextRoot, "tools");
|
|
const web = ensureRecord(tools, "web");
|
|
const nextLegacy = cloneRecord(legacy) ?? {};
|
|
delete nextLegacy.apiKey;
|
|
if (Object.keys(nextLegacy).length === 0) {
|
|
delete web.x_search;
|
|
} else {
|
|
web.x_search = nextLegacy;
|
|
}
|
|
|
|
const plugins = ensureRecord(nextRoot, "plugins");
|
|
const entries = ensureRecord(plugins, "entries");
|
|
const entry = ensureRecord(entries, XAI_PLUGIN_ID);
|
|
const hadEnabled = entry.enabled !== undefined;
|
|
if (!hadEnabled) {
|
|
entry.enabled = true;
|
|
}
|
|
const config = ensureRecord(entry, "config");
|
|
const auth = resolveLegacyXSearchAuth(legacy);
|
|
const changes: string[] = [];
|
|
|
|
if (auth !== undefined) {
|
|
const existingWebSearch = isRecord(config.webSearch)
|
|
? cloneRecord(config.webSearch)
|
|
: undefined;
|
|
if (!existingWebSearch) {
|
|
config.webSearch = { apiKey: auth };
|
|
changes.push(`Moved ${X_SEARCH_LEGACY_PATH}.apiKey → ${XAI_WEB_SEARCH_PLUGIN_KEY_PATH}.`);
|
|
} else if (!Object.prototype.hasOwnProperty.call(existingWebSearch, "apiKey")) {
|
|
existingWebSearch.apiKey = auth;
|
|
config.webSearch = existingWebSearch;
|
|
changes.push(
|
|
`Merged ${X_SEARCH_LEGACY_PATH}.apiKey → ${XAI_WEB_SEARCH_PLUGIN_KEY_PATH} (filled missing plugin auth).`,
|
|
);
|
|
} else {
|
|
changes.push(
|
|
`Removed ${X_SEARCH_LEGACY_PATH}.apiKey (${XAI_WEB_SEARCH_PLUGIN_KEY_PATH} already set).`,
|
|
);
|
|
}
|
|
}
|
|
|
|
if (auth !== undefined && Object.keys(nextLegacy).length === 0 && !hadEnabled) {
|
|
changes.push(`Removed empty ${X_SEARCH_LEGACY_PATH}.`);
|
|
}
|
|
|
|
return {
|
|
config: nextRoot as T,
|
|
changes,
|
|
};
|
|
}
|