mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 13:44:58 +00:00
fix(agents): harden openai ws tool call id handling
This commit is contained in:
@@ -244,6 +244,54 @@ describe("wrapStreamFnTrimToolCallNames", () => {
|
||||
expect(finalToolCall.name).toBe("\t ");
|
||||
expect(baseFn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("assigns fallback ids to missing/blank tool call ids in streamed and final messages", async () => {
|
||||
const partialToolCall = { type: "toolCall", name: " read ", id: " " };
|
||||
const finalToolCallA = { type: "toolCall", name: " exec ", id: "" };
|
||||
const finalToolCallB = { type: "toolCall", name: " write " };
|
||||
const event = {
|
||||
type: "toolcall_delta",
|
||||
partial: { role: "assistant", content: [partialToolCall] },
|
||||
};
|
||||
const finalMessage = { role: "assistant", content: [finalToolCallA, finalToolCallB] };
|
||||
const baseFn = vi.fn(() =>
|
||||
createFakeStream({
|
||||
events: [event],
|
||||
resultMessage: finalMessage,
|
||||
}),
|
||||
);
|
||||
|
||||
const stream = await invokeWrappedStream(baseFn);
|
||||
for await (const _item of stream) {
|
||||
// drain
|
||||
}
|
||||
const result = await stream.result();
|
||||
|
||||
expect(partialToolCall.name).toBe("read");
|
||||
expect(partialToolCall.id).toBe("call_auto_1");
|
||||
expect(finalToolCallA.name).toBe("exec");
|
||||
expect(finalToolCallA.id).toBe("call_auto_1");
|
||||
expect(finalToolCallB.name).toBe("write");
|
||||
expect(finalToolCallB.id).toBe("call_auto_2");
|
||||
expect(result).toBe(finalMessage);
|
||||
});
|
||||
|
||||
it("trims surrounding whitespace on tool call ids", async () => {
|
||||
const finalToolCall = { type: "toolCall", name: " read ", id: " call_42 " };
|
||||
const finalMessage = { role: "assistant", content: [finalToolCall] };
|
||||
const baseFn = vi.fn(() =>
|
||||
createFakeStream({
|
||||
events: [],
|
||||
resultMessage: finalMessage,
|
||||
}),
|
||||
);
|
||||
|
||||
const stream = await invokeWrappedStream(baseFn);
|
||||
await stream.result();
|
||||
|
||||
expect(finalToolCall.name).toBe("read");
|
||||
expect(finalToolCall.id).toBe("call_42");
|
||||
});
|
||||
});
|
||||
|
||||
describe("isOllamaCompatProvider", () => {
|
||||
|
||||
@@ -259,6 +259,64 @@ function normalizeToolCallNameForDispatch(rawName: string, allowedToolNames?: Se
|
||||
return caseInsensitiveMatch ?? trimmed;
|
||||
}
|
||||
|
||||
function isToolCallBlockType(type: unknown): boolean {
|
||||
return type === "toolCall" || type === "toolUse" || type === "functionCall";
|
||||
}
|
||||
|
||||
function normalizeToolCallIdsInMessage(message: unknown): void {
|
||||
if (!message || typeof message !== "object") {
|
||||
return;
|
||||
}
|
||||
const content = (message as { content?: unknown }).content;
|
||||
if (!Array.isArray(content)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const usedIds = new Set<string>();
|
||||
for (const block of content) {
|
||||
if (!block || typeof block !== "object") {
|
||||
continue;
|
||||
}
|
||||
const typedBlock = block as { type?: unknown; id?: unknown };
|
||||
if (!isToolCallBlockType(typedBlock.type) || typeof typedBlock.id !== "string") {
|
||||
continue;
|
||||
}
|
||||
const trimmedId = typedBlock.id.trim();
|
||||
if (!trimmedId) {
|
||||
continue;
|
||||
}
|
||||
usedIds.add(trimmedId);
|
||||
}
|
||||
|
||||
let fallbackIndex = 1;
|
||||
for (const block of content) {
|
||||
if (!block || typeof block !== "object") {
|
||||
continue;
|
||||
}
|
||||
const typedBlock = block as { type?: unknown; id?: unknown };
|
||||
if (!isToolCallBlockType(typedBlock.type)) {
|
||||
continue;
|
||||
}
|
||||
if (typeof typedBlock.id === "string") {
|
||||
const trimmedId = typedBlock.id.trim();
|
||||
if (trimmedId) {
|
||||
if (typedBlock.id !== trimmedId) {
|
||||
typedBlock.id = trimmedId;
|
||||
}
|
||||
usedIds.add(trimmedId);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let fallbackId = "";
|
||||
while (!fallbackId || usedIds.has(fallbackId)) {
|
||||
fallbackId = `call_auto_${fallbackIndex++}`;
|
||||
}
|
||||
typedBlock.id = fallbackId;
|
||||
usedIds.add(fallbackId);
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveOllamaBaseUrlForRun(params: {
|
||||
modelBaseUrl?: string;
|
||||
providerBaseUrl?: string;
|
||||
@@ -298,6 +356,7 @@ function trimWhitespaceFromToolCallNamesInMessage(
|
||||
typedBlock.name = normalized;
|
||||
}
|
||||
}
|
||||
normalizeToolCallIdsInMessage(message);
|
||||
}
|
||||
|
||||
function wrapStreamTrimToolCallNames(
|
||||
|
||||
Reference in New Issue
Block a user