mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-10-27 12:40:32 +00:00
This commit is contained in:
@@ -481,23 +481,17 @@ export class ClaudeAcpAgent implements Agent {
|
||||
|
||||
const userMessage = promptToClaude(params);
|
||||
|
||||
const promptUuid = randomUUID();
|
||||
userMessage.uuid = promptUuid;
|
||||
|
||||
let promptReplayed = false;
|
||||
|
||||
if (session.promptRunning) {
|
||||
const uuid = randomUUID();
|
||||
userMessage.uuid = uuid;
|
||||
session.input.push(userMessage);
|
||||
const order = session.nextPendingOrder++;
|
||||
const cancelled = await new Promise<boolean>((resolve) => {
|
||||
session.pendingMessages.set(promptUuid, { resolve, order });
|
||||
session.pendingMessages.set(uuid, { resolve, order });
|
||||
});
|
||||
if (cancelled) {
|
||||
return { stopReason: "cancelled" };
|
||||
}
|
||||
// The replay resolved the promise, mark in this loop too,
|
||||
// so we don't treat the next result as a background task's result.
|
||||
promptReplayed = true;
|
||||
} else {
|
||||
session.input.push(userMessage);
|
||||
}
|
||||
@@ -572,6 +566,10 @@ export class ClaudeAcpAgent implements Agent {
|
||||
}
|
||||
break;
|
||||
case "result": {
|
||||
if (session.cancelled) {
|
||||
return { stopReason: "cancelled" };
|
||||
}
|
||||
|
||||
// Accumulate usage from this result
|
||||
session.accumulatedUsage.inputTokens += message.usage.input_tokens;
|
||||
session.accumulatedUsage.outputTokens += message.usage.output_tokens;
|
||||
@@ -599,18 +597,6 @@ export class ClaudeAcpAgent implements Agent {
|
||||
});
|
||||
}
|
||||
|
||||
if (!promptReplayed) {
|
||||
// This result is from a background task that finished after
|
||||
// the previous prompt loop ended. Consume it and continue
|
||||
// waiting for our own prompt's result.
|
||||
this.logger.log(`Session ${params.sessionId}: consuming background task result`);
|
||||
break;
|
||||
}
|
||||
|
||||
if (session.cancelled) {
|
||||
return { stopReason: "cancelled" };
|
||||
}
|
||||
|
||||
// Build the usage response
|
||||
const usage: PromptResponse["usage"] = {
|
||||
inputTokens: session.accumulatedUsage.inputTokens,
|
||||
@@ -686,14 +672,8 @@ export class ClaudeAcpAgent implements Agent {
|
||||
break;
|
||||
}
|
||||
|
||||
// Check for prompt replay
|
||||
// Check for queued prompt replay
|
||||
if (message.type === "user" && "uuid" in message && message.uuid) {
|
||||
if (message.uuid === promptUuid) {
|
||||
// Our own prompt was replayed back — we're now processing
|
||||
// our prompt's response (not a background task's).
|
||||
promptReplayed = true;
|
||||
break;
|
||||
}
|
||||
const pending = session.pendingMessages.get(message.uuid as string);
|
||||
if (pending) {
|
||||
pending.resolve(false);
|
||||
|
||||
@@ -228,7 +228,7 @@ describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("ACP subprocess integration"
|
||||
sessionId: newSessionResponse.sessionId,
|
||||
});
|
||||
|
||||
expect(client.takeReceivedText()).toBe("Error: No messages to compact");
|
||||
expect(client.takeReceivedText()).toBe("Compacting...\n\nCompacting completed.");
|
||||
|
||||
// Send something
|
||||
await connection.prompt({
|
||||
@@ -249,7 +249,7 @@ describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("ACP subprocess integration"
|
||||
sessionId: newSessionResponse.sessionId,
|
||||
});
|
||||
|
||||
expect(client.takeReceivedText()).toContain("");
|
||||
expect(client.takeReceivedText()).toContain("Compacting...\n\nCompacting completed.");
|
||||
}, 30000);
|
||||
});
|
||||
|
||||
@@ -1305,27 +1305,15 @@ describe("stop reason propagation", () => {
|
||||
};
|
||||
}
|
||||
|
||||
function* messageGenerator(messages: any[]) {
|
||||
yield* messages;
|
||||
}
|
||||
|
||||
function injectSession(agent: ClaudeAcpAgent, messages: any[]) {
|
||||
const input = new Pushable<any>();
|
||||
async function* messageGenerator() {
|
||||
// Wait for the prompt to push its user message so we can replay it
|
||||
const iter = input[Symbol.asyncIterator]();
|
||||
const { value: userMessage, done } = await iter.next();
|
||||
if (!done && userMessage) {
|
||||
yield {
|
||||
type: "user",
|
||||
message: userMessage.message,
|
||||
parent_tool_use_id: null,
|
||||
uuid: userMessage.uuid,
|
||||
session_id: "test-session",
|
||||
isReplay: true,
|
||||
};
|
||||
}
|
||||
yield* messages;
|
||||
}
|
||||
const gen = messageGenerator(messages);
|
||||
agent.sessions["test-session"] = {
|
||||
query: messageGenerator() as any,
|
||||
input,
|
||||
query: gen as any,
|
||||
input: new Pushable(),
|
||||
cancelled: false,
|
||||
cwd: "/test",
|
||||
permissionMode: "default",
|
||||
@@ -1409,80 +1397,6 @@ describe("stop reason propagation", () => {
|
||||
expect(response.stopReason).toBe("end_turn");
|
||||
});
|
||||
|
||||
it("should consume background task results and return the prompt's own result", async () => {
|
||||
const agent = createMockAgent();
|
||||
const input = new Pushable<any>();
|
||||
|
||||
const backgroundTaskResult = createResultMessage({
|
||||
subtype: "success",
|
||||
stop_reason: null,
|
||||
is_error: false,
|
||||
});
|
||||
// Background task used some tokens
|
||||
backgroundTaskResult.usage.input_tokens = 100;
|
||||
backgroundTaskResult.usage.output_tokens = 50;
|
||||
|
||||
const promptResult = createResultMessage({
|
||||
subtype: "success",
|
||||
stop_reason: null,
|
||||
is_error: false,
|
||||
});
|
||||
|
||||
async function* messageGenerator() {
|
||||
// Background task init + result arrive before our prompt's replay
|
||||
yield { type: "system", subtype: "init", session_id: "test-session" };
|
||||
yield backgroundTaskResult;
|
||||
|
||||
// Now the prompt's user message replay arrives
|
||||
const iter = input[Symbol.asyncIterator]();
|
||||
const { value: userMessage } = await iter.next();
|
||||
yield {
|
||||
type: "user",
|
||||
message: userMessage.message,
|
||||
parent_tool_use_id: null,
|
||||
uuid: userMessage.uuid,
|
||||
session_id: "test-session",
|
||||
isReplay: true,
|
||||
};
|
||||
|
||||
// Then the prompt's own result
|
||||
yield promptResult;
|
||||
}
|
||||
|
||||
agent.sessions["test-session"] = {
|
||||
query: messageGenerator() as any,
|
||||
input,
|
||||
cwd: "/tmp/test",
|
||||
cancelled: false,
|
||||
permissionMode: "default",
|
||||
settingsManager: {} as any,
|
||||
accumulatedUsage: {
|
||||
inputTokens: 0,
|
||||
outputTokens: 0,
|
||||
cachedReadTokens: 0,
|
||||
cachedWriteTokens: 0,
|
||||
},
|
||||
configOptions: [],
|
||||
promptRunning: false,
|
||||
pendingMessages: new Map(),
|
||||
nextPendingOrder: 0,
|
||||
};
|
||||
|
||||
const response = await agent.prompt({
|
||||
sessionId: "test-session",
|
||||
prompt: [{ type: "text", text: "test" }],
|
||||
});
|
||||
|
||||
expect(response.stopReason).toBe("end_turn");
|
||||
// Usage should include both background task and prompt result tokens
|
||||
expect(response.usage?.inputTokens).toBe(
|
||||
backgroundTaskResult.usage.input_tokens + promptResult.usage.input_tokens,
|
||||
);
|
||||
expect(response.usage?.outputTokens).toBe(
|
||||
backgroundTaskResult.usage.output_tokens + promptResult.usage.output_tokens,
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw internal error for success with is_error true and no max_tokens", async () => {
|
||||
const agent = createMockAgent();
|
||||
injectSession(agent, [
|
||||
|
||||
Reference in New Issue
Block a user