mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 20:35:39 -06:00
b080dd1e76
* refactor: consolidate coercion contracts Centralize exact string, record, numeric, date, Boolean, argument, and structured-error coercions while preserving call-site semantics. Migrate canonical-name collisions and deprecated internal SDK bypasses, deleting 55 net production/tooling lines. Expand declaration ownership enforcement to 101 allowed helpers and add a narrow export-completeness audit. * fix: preserve standalone script coercions Keep copied Control UI tooling self-contained and retain the trusted release harness module-relative source seam when the harness runs against an old target cwd.
98 lines
3.7 KiB
TypeScript
98 lines
3.7 KiB
TypeScript
import {
|
|
readStringParam,
|
|
resolveSearchTimeoutSeconds,
|
|
type SearchConfigRecord,
|
|
type WebSearchProviderToolExecutionContext,
|
|
wrapWebContent,
|
|
} from "openclaw/plugin-sdk/provider-web-search";
|
|
import type { WebSearchProviderPlugin } from "openclaw/plugin-sdk/provider-web-search-contract";
|
|
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
import {
|
|
runBoundedCodexAppServerTurn,
|
|
type CodexBoundedTurnOptions,
|
|
} from "./app-server/bounded-turn.js";
|
|
import { isJsonObject, type CodexThreadItem, type JsonObject } from "./app-server/protocol.js";
|
|
import { buildCodexNativeWebSearchThreadConfig } from "./app-server/web-search.js";
|
|
|
|
type WebSearchProviderContext = Parameters<WebSearchProviderPlugin["createTool"]>[0];
|
|
|
|
export async function executeCodexWebSearchProviderTool(
|
|
ctx: WebSearchProviderContext,
|
|
args: Record<string, unknown>,
|
|
executionContext: WebSearchProviderToolExecutionContext | undefined,
|
|
options: CodexBoundedTurnOptions,
|
|
): Promise<Record<string, unknown>> {
|
|
const query = readStringParam(args, "query", { required: true });
|
|
const start = Date.now();
|
|
const result = await runBoundedCodexAppServerTurn({
|
|
config: ctx.config,
|
|
model: { mode: "live-default" },
|
|
modelProvider: "openai",
|
|
timeoutMs: resolveSearchTimeoutSeconds(ctx.searchConfig as SearchConfigRecord) * 1_000,
|
|
signal: executionContext?.signal,
|
|
agentDir: ctx.agentDir,
|
|
options,
|
|
taskLabel: "hosted search",
|
|
developerInstructions:
|
|
"You are OpenClaw's bounded web-search worker. You must use Codex hosted web_search to answer the user's search query. Return a concise grounded answer with source URLs. Do not call other tools, edit files, or ask follow-up questions.",
|
|
input: [{ type: "text", text: query, text_elements: [] }],
|
|
requiredModalities: ["text"],
|
|
isolation: "private-stdio",
|
|
threadConfig: buildCodexNativeWebSearchThreadConfig(ctx.config),
|
|
});
|
|
const searches = result.items
|
|
.filter((item) => item.type === "webSearch")
|
|
.map(summarizeCodexWebSearchItem);
|
|
if (searches.length === 0) {
|
|
throw new Error("Codex hosted search completed without invoking web search.");
|
|
}
|
|
return {
|
|
query,
|
|
provider: "codex",
|
|
model: result.model,
|
|
tookMs: Date.now() - start,
|
|
externalContent: {
|
|
untrusted: true,
|
|
source: "web_search",
|
|
provider: "codex",
|
|
wrapped: true,
|
|
},
|
|
content: wrapWebContent(result.text, "web_search"),
|
|
searches,
|
|
};
|
|
}
|
|
|
|
function summarizeCodexWebSearchItem(item: CodexThreadItem): Record<string, unknown> {
|
|
const action = isJsonObject(item.action) ? item.action : undefined;
|
|
const actionType = readNonEmptyString(action, "type");
|
|
const queries = actionType === "search" ? readNonEmptyStringArray(action, "queries") : [];
|
|
const query =
|
|
normalizeOptionalString(item.query) ??
|
|
(actionType === "search" ? readNonEmptyString(action, "query") : undefined) ??
|
|
queries[0];
|
|
const url = readNonEmptyString(action, "url");
|
|
const pattern = readNonEmptyString(action, "pattern");
|
|
return {
|
|
...(query ? { query } : {}),
|
|
...(queries.length > 0 ? { queries } : {}),
|
|
...(actionType && actionType !== "search" ? { action: actionType } : {}),
|
|
...(url ? { url } : {}),
|
|
...(pattern ? { pattern } : {}),
|
|
};
|
|
}
|
|
|
|
function readNonEmptyString(record: JsonObject | undefined, key: string): string | undefined {
|
|
return record ? normalizeOptionalString(record[key]) : undefined;
|
|
}
|
|
|
|
function readNonEmptyStringArray(record: JsonObject | undefined, key: string): string[] {
|
|
const value = record?.[key];
|
|
if (!Array.isArray(value)) {
|
|
return [];
|
|
}
|
|
return value.flatMap((entry) => {
|
|
const normalized = normalizeOptionalString(entry);
|
|
return normalized ? [normalized] : [];
|
|
});
|
|
}
|