Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/tools/systemMessageTools/buildToolsSystemMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const generateToolsSystemMessage = (
`\nAlso, these additional tool definitions show other tools you can call with the same syntax:`,
);

for (const tool of tools) {
for (const tool of withDynamicMessage) {
try {
const definition = framework.toolToSystemToolDefinition(tool);
instructions.push(`\n${definition}`);
Expand Down
13 changes: 7 additions & 6 deletions core/tools/systemMessageTools/interceptSystemToolCalls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export async function* interceptSystemToolCalls(
return result.value;
} else {
for await (const message of result.value) {
if (abortController.signal.aborted || parseState?.done) {
if (abortController.signal.aborted) {
break;
}
// Skip non-assistant messages or messages with native tool calls
Expand Down Expand Up @@ -96,12 +96,13 @@ export async function* interceptSystemToolCalls(
},
];
}
} else {
// Prevent content after tool calls for now
if (parseState) {
continue;
// Completed tool calls should not terminate parsing for subsequent
// chunks/messages; reset state so normal content (or another tool
// call) can be handled.
if (parseState.done) {
parseState = undefined;
}

} else {
// Yield normal assistant message
yield [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,11 @@ describe("generateToolsSystemMessage", () => {

const hasDynamicToolsSection = /additional tool definitions/i.test(result);
expect(hasDynamicToolsSection).toBe(true);

// Dynamic definitions should not duplicate tools that already have predefined messages.
expect(result.match(/TOOL_NAME: tool_with_description/g)?.length ?? 0).toBe(
1,
);
});

it("includes example tool definition and call", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,15 +242,15 @@
).toBe("}");
});

it("ignores content after a tool call", async () => {
it("preserves content after a tool call", async () => {
const messages: ChatMessage[][] = [
[{ role: "assistant", content: "```tool\n" }],
[{ role: "assistant", content: "TOOL_NAME: test_tool\n" }],
[{ role: "assistant", content: "BEGIN_ARG: arg1\n" }],
[{ role: "assistant", content: "value1\n" }],
[{ role: "assistant", content: "END_ARG\n" }],
[{ role: "assistant", content: "```\n" }],
[{ role: "assistant", content: "This content should be ignored" }],
[{ role: "assistant", content: "This content should be preserved" }],
];

const generator = interceptSystemToolCalls(
Expand All @@ -265,9 +265,79 @@
result = await generator.next();
}

// The content after the tool call should be ignored
// The content after the tool call should be preserved
result = await generator.next();
expect(result.value).toBeUndefined();
expect(result.value).toEqual([

Check failure on line 270 in core/tools/systemMessageTools/toolCodeblocks/interceptSystemToolCalls.vitest.ts

View workflow job for this annotation

GitHub Actions / core-checks

tools/systemMessageTools/toolCodeblocks/interceptSystemToolCalls.vitest.ts > interceptSystemToolCalls > preserves content after a tool call

AssertionError: expected undefined to deeply equal [ { role: 'assistant', …(1) } ] - Expected: [ { "content": [ { "text": "This content should be preserved", "type": "text", }, ], "role": "assistant", }, ] + Received: undefined ❯ tools/systemMessageTools/toolCodeblocks/interceptSystemToolCalls.vitest.ts:270:26
{
role: "assistant",
content: [{ type: "text", text: "This content should be preserved" }],
},
]);
});

it("parses a tool call that appears mid-message and preserves trailing content", async () => {
const messages: ChatMessage[][] = [
[
{
role: "assistant",
content:
"Before tool\n```tool\nTOOL_NAME: test_tool\nBEGIN_ARG: arg1\nvalue1\nEND_ARG\n```\nAfter tool",
},
],
];

const generator = interceptSystemToolCalls(
createAsyncGenerator(messages),
abortController,
framework,
);

let result = await generator.next();
expect(result.value).toEqual([
{
role: "assistant",
content: [{ type: "text", text: "Before tool" }],
},
]);

result = await generator.next();
expect(result.value).toEqual([
{
role: "assistant",
content: [{ type: "text", text: "\n" }],
},
]);

result = await generator.next();
expect(
(result.value as AssistantChatMessage[])[0].toolCalls?.[0].function?.name,
).toBe("test_tool");

result = await generator.next();
expect(
(result.value as AssistantChatMessage[])[0].toolCalls?.[0].function
?.arguments,
).toContain('{"arg1":');

result = await generator.next();
expect(
(result.value as AssistantChatMessage[])[0].toolCalls?.[0].function
?.arguments,
).toBe('"value1"');

result = await generator.next();
expect(
(result.value as AssistantChatMessage[])[0].toolCalls?.[0].function
?.arguments,
).toBe("}");

result = await generator.next();
expect(result.value).toEqual([

Check failure on line 335 in core/tools/systemMessageTools/toolCodeblocks/interceptSystemToolCalls.vitest.ts

View workflow job for this annotation

GitHub Actions / core-checks

tools/systemMessageTools/toolCodeblocks/interceptSystemToolCalls.vitest.ts > interceptSystemToolCalls > parses a tool call that appears mid-message and preserves trailing content

AssertionError: expected [ { role: 'assistant', …(1) } ] to deeply equal [ { role: 'assistant', …(1) } ] - Expected + Received @@ -1,10 +1,11 @@ [ { "content": [ { - "text": "After tool", + "text": " + ", "type": "text", }, ], "role": "assistant", }, ❯ tools/systemMessageTools/toolCodeblocks/interceptSystemToolCalls.vitest.ts:335:26
{
role: "assistant",
content: [{ type: "text", text: "After tool" }],
},
]);
});

it("stops processing when aborted", async () => {
Expand Down
Loading