mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix(providers): guard OpenAI web search tool detection
This commit is contained in:
@@ -485,6 +485,80 @@ describe("createOpenAIThinkingLevelWrapper", () => {
|
||||
expect(payloads[0]?.reasoning).toEqual({ effort: "low", summary: "auto" });
|
||||
});
|
||||
|
||||
it("skips unreadable payload tools while detecting web_search reasoning needs", () => {
|
||||
const payloads: Array<Record<string, unknown>> = [];
|
||||
const baseStreamFn: StreamFn = (model, context, options) => {
|
||||
const payload: Record<string, unknown> = {
|
||||
reasoning: { effort: "minimal", summary: "auto" },
|
||||
tools: [
|
||||
{
|
||||
type: "function",
|
||||
get function(): never {
|
||||
throw new Error("payload tool function getter exploded");
|
||||
},
|
||||
},
|
||||
{ type: "function", name: "web_search" },
|
||||
],
|
||||
};
|
||||
options?.onPayload?.(payload, model);
|
||||
payloads.push({ reasoning: payload.reasoning });
|
||||
return createAssistantMessageEventStream();
|
||||
};
|
||||
const wrapped = createOpenAIThinkingLevelWrapper(baseStreamFn, "minimal");
|
||||
|
||||
expect(
|
||||
() =>
|
||||
void wrapped(
|
||||
{
|
||||
api: "openai-responses",
|
||||
provider: "openai",
|
||||
id: "gpt-5",
|
||||
baseUrl: "http://127.0.0.1:19191/v1",
|
||||
} as Model<"openai-responses">,
|
||||
{ messages: [] },
|
||||
{},
|
||||
),
|
||||
).not.toThrow();
|
||||
expect(payloads[0]?.reasoning).toEqual({ effort: "low", summary: "auto" });
|
||||
});
|
||||
|
||||
it("detects nested web_search when the top-level payload tool name is unreadable", () => {
|
||||
const payloads: Array<Record<string, unknown>> = [];
|
||||
const baseStreamFn: StreamFn = (model, context, options) => {
|
||||
const payload: Record<string, unknown> = {
|
||||
reasoning: { effort: "minimal", summary: "auto" },
|
||||
tools: [
|
||||
{
|
||||
type: "function",
|
||||
get name(): never {
|
||||
throw new Error("payload tool name getter exploded");
|
||||
},
|
||||
function: { name: "web_search" },
|
||||
},
|
||||
],
|
||||
};
|
||||
options?.onPayload?.(payload, model);
|
||||
payloads.push({ reasoning: payload.reasoning });
|
||||
return createAssistantMessageEventStream();
|
||||
};
|
||||
const wrapped = createOpenAIThinkingLevelWrapper(baseStreamFn, "minimal");
|
||||
|
||||
expect(
|
||||
() =>
|
||||
void wrapped(
|
||||
{
|
||||
api: "openai-responses",
|
||||
provider: "openai",
|
||||
id: "gpt-5",
|
||||
baseUrl: "http://127.0.0.1:19191/v1",
|
||||
} as Model<"openai-responses">,
|
||||
{ messages: [] },
|
||||
{},
|
||||
),
|
||||
).not.toThrow();
|
||||
expect(payloads[0]?.reasoning).toEqual({ effort: "low", summary: "auto" });
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
api: "openai-responses",
|
||||
|
||||
@@ -40,6 +40,7 @@ type OpenClawSimpleStreamOptions = SimpleStreamOptions & {
|
||||
type OpenAIResponsesReplayOptions = Parameters<StreamFn>[2] & {
|
||||
replayResponsesItemIds?: boolean;
|
||||
};
|
||||
type PayloadFieldRead = { ok: true; value: unknown } | { ok: false };
|
||||
export { resolveOpenAITextVerbosity };
|
||||
|
||||
function resolveOpenAITextVerbosityForModel(
|
||||
@@ -236,6 +237,14 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||
}
|
||||
|
||||
function readPayloadField(record: Record<string, unknown>, field: string): PayloadFieldRead {
|
||||
try {
|
||||
return { ok: true, value: Reflect.get(record, field) };
|
||||
} catch {
|
||||
return { ok: false };
|
||||
}
|
||||
}
|
||||
|
||||
function hasResponsesWebSearchTool(tools: unknown): boolean {
|
||||
if (!Array.isArray(tools)) {
|
||||
return false;
|
||||
@@ -244,14 +253,23 @@ function hasResponsesWebSearchTool(tools: unknown): boolean {
|
||||
if (!isRecord(tool)) {
|
||||
return false;
|
||||
}
|
||||
if (tool.type === "web_search") {
|
||||
const type = readPayloadField(tool, "type");
|
||||
if (!type.ok) {
|
||||
return false;
|
||||
}
|
||||
if (type.value === "web_search") {
|
||||
return true;
|
||||
}
|
||||
if (tool.type === "function" && tool.name === "web_search") {
|
||||
const name = readPayloadField(tool, "name");
|
||||
if (name.ok && type.value === "function" && name.value === "web_search") {
|
||||
return true;
|
||||
}
|
||||
const fn = tool.function;
|
||||
return isRecord(fn) && fn.name === "web_search";
|
||||
const fn = readPayloadField(tool, "function");
|
||||
if (!fn.ok || !isRecord(fn.value)) {
|
||||
return false;
|
||||
}
|
||||
const functionName = readPayloadField(fn.value, "name");
|
||||
return functionName.ok && functionName.value === "web_search";
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user