feat(acp): implemented multi-session support with forking and lifecycle management
- Added multi-session support to ACP mode enabling session forking, resumption, and closure operations. - Added session model state reporting and unstable_setSessionModel RPC command for direct model configuration. - Added turn-level usage tracking and stable message ID tracking for assistant chunks in ACP responses. - Added Settings.cloneForCwd() method and ExtensionRunner.getFlagValues() for configuration isolation across sessions. - Fixed ACP session cleanup to properly cancel in-flight prompts and dispose resources on disconnect. - Refactored AcpAgent to manage multiple concurrent sessions with per-session lifecycle instead of single session.
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import { mapAgentSessionEventToAcpSessionUpdates } from "../src/modes/acp/acp-event-mapper";
|
||||
import type { AgentSessionEvent } from "../src/session/agent-session";
|
||||
|
||||
function makeAssistantMessage(text: string) {
|
||||
return {
|
||||
role: "assistant" as const,
|
||||
content: [{ type: "text" as const, text }],
|
||||
api: "anthropic-messages" as const,
|
||||
provider: "anthropic" as const,
|
||||
model: "claude-sonnet-4-20250514",
|
||||
usage: {
|
||||
input: 10,
|
||||
output: 5,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
totalTokens: 15,
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
||||
},
|
||||
stopReason: "stop" as const,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
}
|
||||
|
||||
function getChunkMessageId(event: { update: object }): string | undefined {
|
||||
const update = event.update as { messageId?: string | null };
|
||||
return typeof update.messageId === "string" ? update.messageId : undefined;
|
||||
}
|
||||
|
||||
describe("ACP event mapper", () => {
|
||||
it("attaches a stable messageId to live assistant chunks", () => {
|
||||
const assistantMessage = makeAssistantMessage("chunk");
|
||||
const getMessageId = (message: unknown): string | undefined =>
|
||||
message === assistantMessage ? "a80f1ff7-4f0a-4e6b-9f09-c94857b62a4a" : undefined;
|
||||
|
||||
const textUpdates = mapAgentSessionEventToAcpSessionUpdates(
|
||||
{
|
||||
type: "message_update",
|
||||
message: assistantMessage,
|
||||
assistantMessageEvent: { type: "text_delta", delta: "chunk" },
|
||||
} as AgentSessionEvent,
|
||||
"session-1",
|
||||
{ getMessageId },
|
||||
);
|
||||
const thoughtUpdates = mapAgentSessionEventToAcpSessionUpdates(
|
||||
{
|
||||
type: "message_update",
|
||||
message: assistantMessage,
|
||||
assistantMessageEvent: { type: "thinking_delta", delta: "plan" },
|
||||
} as AgentSessionEvent,
|
||||
"session-1",
|
||||
{ getMessageId },
|
||||
);
|
||||
|
||||
expect(textUpdates).toHaveLength(1);
|
||||
expect(thoughtUpdates).toHaveLength(1);
|
||||
expect(textUpdates[0] ? getChunkMessageId(textUpdates[0]) : undefined).toBe(
|
||||
"a80f1ff7-4f0a-4e6b-9f09-c94857b62a4a",
|
||||
);
|
||||
expect(thoughtUpdates[0] ? getChunkMessageId(thoughtUpdates[0]) : undefined).toBe(
|
||||
"a80f1ff7-4f0a-4e6b-9f09-c94857b62a4a",
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user