feat: establish tool call state machine
This commit is contained in:
@@ -71,4 +71,59 @@ describe("InteractionKernel", () => {
|
||||
// The first id has expired from the bounded set and is accepted again.
|
||||
expect(kernel.ingest({ raw: envelope({ id: "evt_status_thinking" }), source: "live" }).disposition).toBe("accepted");
|
||||
});
|
||||
|
||||
it("projects tool calls by call_id and applies a result only after submission", () => {
|
||||
const kernel = new InteractionKernel();
|
||||
const call = kernel.ingest({
|
||||
raw: envelope({
|
||||
id: "evt_tool_call",
|
||||
type: "lineup.v1.tool.call",
|
||||
payload: { call_id: "call_kernel_001", tool: "choice", title: "Choose", prompt: "One", data: {} },
|
||||
}),
|
||||
source: "sync",
|
||||
received_at: "2026-08-03T08:00:00.000Z",
|
||||
});
|
||||
const duplicateCallID = kernel.ingest({
|
||||
raw: envelope({
|
||||
id: "evt_tool_call_duplicate_id",
|
||||
type: "lineup.v1.tool.call",
|
||||
payload: { call_id: "call_kernel_001", tool: "choice", title: "Ignored", prompt: "Ignored", data: {} },
|
||||
}),
|
||||
source: "live",
|
||||
});
|
||||
const earlyResult = kernel.ingest({
|
||||
raw: envelope({ id: "evt_tool_result_early", type: "lineup.v1.tool.result", payload: { call_id: "call_kernel_001", status: "completed", result: {} } }),
|
||||
source: "live",
|
||||
});
|
||||
|
||||
expect(call.snapshot.tool_calls).toEqual([expect.objectContaining({ call_id: "call_kernel_001", status: "pending" })]);
|
||||
expect(duplicateCallID.snapshot.tool_calls).toEqual([
|
||||
expect.objectContaining({ request: expect.objectContaining({ title: "Choose" }) }),
|
||||
]);
|
||||
expect(earlyResult.snapshot.tool_calls).toEqual([expect.objectContaining({ status: "pending" })]);
|
||||
expect(kernel.submitToolCall("call_kernel_001", "2026-08-03T08:01:00.000Z")).toMatchObject({ disposition: "accepted" });
|
||||
|
||||
const completed = kernel.ingest({
|
||||
raw: envelope({ id: "evt_tool_result_completed", type: "lineup.v1.tool.result", payload: { call_id: "call_kernel_001", status: "completed", result: { selected: "alpha" } } }),
|
||||
source: "live",
|
||||
});
|
||||
expect(completed.snapshot.tool_calls).toEqual([
|
||||
expect.objectContaining({ call_id: "call_kernel_001", status: "completed", result: { selected: "alpha" } }),
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps malformed tool payloads on the safe fallback path", () => {
|
||||
const kernel = new InteractionKernel();
|
||||
const invalidCall = kernel.ingest({
|
||||
raw: envelope({ id: "evt_invalid_tool_call", type: "lineup.v1.tool.call", payload: { call_id: "call_bad", tool: "shell", data: {} } }),
|
||||
source: "sync",
|
||||
});
|
||||
const invalidResult = kernel.ingest({
|
||||
raw: envelope({ id: "evt_invalid_tool_result", type: "lineup.v1.tool.result", payload: { call_id: "call_bad", status: "submitted", result: {} } }),
|
||||
source: "sync",
|
||||
});
|
||||
|
||||
expect(invalidCall.items).toEqual([expect.objectContaining({ kind: "fallback", reason: "invalid_payload" })]);
|
||||
expect(invalidResult.items).toEqual([expect.objectContaining({ kind: "fallback", reason: "invalid_payload" })]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user