Skip to content

Commit 25c5e39

Browse files
szsip239vincentkoc
authored andcommitted
fix(agent-core): stop loop after aborted tool run
1 parent 8cc5b37 commit 25c5e39

2 files changed

Lines changed: 57 additions & 0 deletions

File tree

packages/agent-core/src/agent-loop.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -767,6 +767,59 @@ describe("agentLoop tool termination", () => {
767767
expect(events.filter((event) => event.type === "tool_execution_start")).toHaveLength(1);
768768
expect(events.at(-1)).toMatchObject({ type: "agent_end" });
769769
});
770+
771+
it("does not request another model turn after a tool aborts the run", async () => {
772+
const controller = new AbortController();
773+
let streamCalls = 0;
774+
const streamFn: StreamFn = () => {
775+
streamCalls += 1;
776+
if (streamCalls > 1) {
777+
throw new Error("model was called after abort");
778+
}
779+
const stream = createAssistantMessageEventStream();
780+
queueMicrotask(() => {
781+
const message = makeAssistantMessage([
782+
{ type: "toolCall", id: "call-abort", name: "abort_tool", arguments: {} },
783+
]);
784+
stream.push({ type: "done", reason: "toolUse", message });
785+
stream.end();
786+
});
787+
return stream;
788+
};
789+
const abortTool: AgentTool = {
790+
name: "abort_tool",
791+
label: "abort_tool",
792+
description: "Abort the active run",
793+
parameters: Type.Object({}, { additionalProperties: false }),
794+
execute: async () => {
795+
controller.abort(new Error("user aborted"));
796+
return {
797+
content: [{ type: "text", text: "aborted" }],
798+
details: { aborted: true },
799+
};
800+
},
801+
};
802+
const events: AgentEvent[] = [];
803+
804+
const messages = await runAgentLoop(
805+
[{ role: "user", content: "abort during tool", timestamp: 1 }],
806+
{
807+
systemPrompt: "",
808+
messages: [],
809+
tools: [abortTool],
810+
},
811+
config,
812+
(event) => {
813+
events.push(event);
814+
},
815+
controller.signal,
816+
streamFn,
817+
);
818+
819+
expect(streamCalls).toBe(1);
820+
expect(messages.map((message) => message.role)).toEqual(["user", "assistant", "toolResult"]);
821+
expect(events.at(-1)).toMatchObject({ type: "agent_end" });
822+
});
770823
});
771824

772825
describe("agentLoop thinking state", () => {

packages/agent-core/src/agent-loop.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,10 @@ async function runLoop(
332332
}
333333

334334
await emit({ type: "turn_end", message, toolResults });
335+
if (signal?.aborted) {
336+
await emit({ type: "agent_end", messages: newMessages });
337+
return;
338+
}
335339

336340
const nextTurnContext = {
337341
message,

0 commit comments

Comments
 (0)