mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
fix(agent): stop prepared tools after preflight abort (#8936)
fixes #8935 Co-authored-by: yaogangqiang <yaogangqiang@kanyun.com>
This commit is contained in:
parent
56c6fb33c4
commit
afda4d6202
2 changed files with 81 additions and 0 deletions
|
|
@ -518,6 +518,15 @@ async function executeToolCallsParallel(
|
|||
}
|
||||
|
||||
finalizedCalls.push(async () => {
|
||||
if (signal?.aborted) {
|
||||
const finalized = {
|
||||
toolCall,
|
||||
result: createErrorToolResult("Operation aborted"),
|
||||
isError: true,
|
||||
} satisfies FinalizedToolCallOutcome;
|
||||
await emitToolExecutionEnd(finalized, emit);
|
||||
return finalized;
|
||||
}
|
||||
const executed = await executePreparedToolCall(preparation, signal, emit);
|
||||
const finalized = await finalizeExecutedToolCall(
|
||||
currentContext,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
||||
import { fauxAssistantMessage, fauxToolCall } from "@earendil-works/pi-ai";
|
||||
import { Type } from "typebox";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { createHarness, getMessageText, type Harness } from "../harness.ts";
|
||||
|
||||
describe("#8935 parallel preflight abort", () => {
|
||||
const harnesses: Harness[] = [];
|
||||
|
||||
afterEach(() => {
|
||||
while (harnesses.length > 0) {
|
||||
harnesses.pop()?.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
it("does not start prepared tools after a later preflight aborts", async () => {
|
||||
const executions: string[] = [];
|
||||
const preflights: string[] = [];
|
||||
const resultHooks: string[] = [];
|
||||
const externalWrite: AgentTool = {
|
||||
name: "external_write",
|
||||
label: "External write",
|
||||
description: "Perform an external write",
|
||||
parameters: Type.Object({ value: Type.String() }),
|
||||
execute: async (_toolCallId, params) => {
|
||||
const value =
|
||||
typeof params === "object" && params !== null && "value" in params ? String(params.value) : "";
|
||||
executions.push(value);
|
||||
return { content: [{ type: "text", text: value }], details: { value } };
|
||||
},
|
||||
};
|
||||
const harness = await createHarness({
|
||||
tools: [externalWrite],
|
||||
extensionFactories: [
|
||||
(pi) => {
|
||||
pi.on("tool_call", async (event, ctx) => {
|
||||
const value = "value" in event.input ? String(event.input.value) : "";
|
||||
preflights.push(value);
|
||||
if (value === "second") ctx.abort();
|
||||
});
|
||||
pi.on("tool_result", async (event) => {
|
||||
resultHooks.push(event.toolCallId);
|
||||
});
|
||||
},
|
||||
],
|
||||
});
|
||||
harnesses.push(harness);
|
||||
harness.setResponses([
|
||||
fauxAssistantMessage(
|
||||
[fauxToolCall("external_write", { value: "first" }), fauxToolCall("external_write", { value: "second" })],
|
||||
{ stopReason: "toolUse" },
|
||||
),
|
||||
]);
|
||||
|
||||
await harness.session.prompt("run both writes");
|
||||
|
||||
expect(preflights).toEqual(["first", "second"]);
|
||||
expect(executions).toEqual([]);
|
||||
expect(resultHooks).toEqual([]);
|
||||
|
||||
const starts = harness.eventsOfType("tool_execution_start");
|
||||
const ends = harness.eventsOfType("tool_execution_end");
|
||||
expect(starts).toHaveLength(2);
|
||||
expect(ends).toHaveLength(2);
|
||||
expect(new Set(ends.map((event) => event.toolCallId))).toEqual(new Set(starts.map((event) => event.toolCallId)));
|
||||
expect(ends.every((event) => event.isError)).toBe(true);
|
||||
|
||||
const toolResults = harness.session.messages.filter((message) => message.role === "toolResult");
|
||||
expect(toolResults.map((message) => message.toolCallId)).toEqual(starts.map((event) => event.toolCallId));
|
||||
expect(toolResults.map(getMessageText)).toEqual(["Operation aborted", "Operation aborted"]);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue