Skip to content

Commit 1a98164

Browse files
authored
refactor(agent): dedup message context + tool-progress, skip idle-drain hop
Quality cleanups on the idle-drain change (no behavior change): - Extract buildMessageContext(sessionId), shared by the prompt turn loop and the idle drainer, replacing two inline copies of the MessageHandlerContext construction so a new context field can't silently go missing from one site. - Extract emitToolProgress(), replacing the tool_progress -> tool_call_update block that was copy-pasted between the main loop and the drainer. - Skip the settleIdleDrainStop() await when no drainer is running, so a plain prompt doesn't pay a microtask hop on the hot path. Generated-By: PostHog Code Task-Id: 39548658-f313-445b-bd1c-d07eaf9281cf
1 parent b8b5267 commit 1a98164

1 file changed

Lines changed: 65 additions & 77 deletions

File tree

packages/agent/src/adapters/claude/claude-agent.ts

Lines changed: 65 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -572,34 +572,17 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
572572
}
573573
let lastContextWindowSize = this.session.lastContextWindowSize;
574574

575-
const supportsTerminalOutput =
576-
(
577-
this.clientCapabilities?._meta as
578-
| ClientCapabilities["_meta"]
579-
| undefined
580-
)?.terminal_output === true;
581-
582-
const context = {
583-
session: this.session,
584-
sessionId: params.sessionId,
585-
client: this.client,
586-
toolUseCache: this.toolUseCache,
587-
toolUseStreamCache: this.toolUseStreamCache,
588-
fileContentCache: this.fileContentCache,
589-
enrichedReadCache: this.enrichedReadCache,
590-
logger: this.logger,
591-
supportsTerminalOutput,
592-
streamedAssistantBlocks: {
593-
textIds: new Set<string>(),
594-
thinkingIds: new Set<string>(),
595-
},
596-
};
575+
const context = this.buildMessageContext(params.sessionId);
597576

598577
// Wait for the idle drainer to release the query. Our user message was
599578
// already pushed above, so its echo is what unblocks the drainer's pending
600579
// `query.next()`; the drainer hands that message back to us here rather than
601-
// consuming it, so `promptReplayed` still flips below.
602-
let carriedMessage = await this.settleIdleDrainStop();
580+
// consuming it, so `promptReplayed` still flips below. Skip the await on the
581+
// common path where no drainer is running so a plain prompt pays no
582+
// microtask hop.
583+
let carriedMessage = this.idleDrain
584+
? await this.settleIdleDrainStop()
585+
: undefined;
603586

604587
try {
605588
while (true) {
@@ -1106,22 +1089,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
11061089
}
11071090

11081091
case "tool_progress": {
1109-
await this.client.sessionUpdate({
1110-
sessionId: params.sessionId,
1111-
update: {
1112-
sessionUpdate: "tool_call_update",
1113-
toolCallId: message.tool_use_id,
1114-
status: "in_progress",
1115-
_meta: {
1116-
claudeCode: {
1117-
toolName: message.tool_name,
1118-
toolResponse: {
1119-
elapsedTimeSeconds: message.elapsed_time_seconds,
1120-
},
1121-
},
1122-
} satisfies ToolUpdateMeta,
1123-
},
1124-
});
1092+
await this.emitToolProgress(params.sessionId, message);
11251093
break;
11261094
}
11271095
case "rate_limit_event": {
@@ -1315,6 +1283,61 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
13151283
return state.handoff;
13161284
}
13171285

1286+
/**
1287+
* Build the per-turn message-handler context shared by the prompt turn loop
1288+
* and the idle drainer. Fresh `streamedAssistantBlocks` Sets per call so each
1289+
* turn dedupes its own streamed blocks independently.
1290+
*/
1291+
private buildMessageContext(sessionId: string): MessageHandlerContext {
1292+
const supportsTerminalOutput =
1293+
(
1294+
this.clientCapabilities?._meta as
1295+
| ClientCapabilities["_meta"]
1296+
| undefined
1297+
)?.terminal_output === true;
1298+
return {
1299+
session: this.session,
1300+
sessionId,
1301+
client: this.client,
1302+
toolUseCache: this.toolUseCache,
1303+
toolUseStreamCache: this.toolUseStreamCache,
1304+
fileContentCache: this.fileContentCache,
1305+
enrichedReadCache: this.enrichedReadCache,
1306+
logger: this.logger,
1307+
supportsTerminalOutput,
1308+
streamedAssistantBlocks: {
1309+
textIds: new Set<string>(),
1310+
thinkingIds: new Set<string>(),
1311+
},
1312+
};
1313+
}
1314+
1315+
/**
1316+
* Forward a `tool_progress` SDK message to the client as an in-progress
1317+
* `tool_call_update`. Shared by the prompt turn loop and the idle drainer.
1318+
*/
1319+
private async emitToolProgress(
1320+
sessionId: string,
1321+
message: Extract<SDKMessage, { type: "tool_progress" }>,
1322+
): Promise<void> {
1323+
await this.client.sessionUpdate({
1324+
sessionId,
1325+
update: {
1326+
sessionUpdate: "tool_call_update",
1327+
toolCallId: message.tool_use_id,
1328+
status: "in_progress",
1329+
_meta: {
1330+
claudeCode: {
1331+
toolName: message.tool_name,
1332+
toolResponse: {
1333+
elapsedTimeSeconds: message.elapsed_time_seconds,
1334+
},
1335+
},
1336+
} satisfies ToolUpdateMeta,
1337+
},
1338+
});
1339+
}
1340+
13181341
/**
13191342
* Pump the SDK query stream between turns.
13201343
*
@@ -1340,27 +1363,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
13401363
// `this.session` out from under us; binding here keeps this loop scoped to
13411364
// the query it started on, which will end (iterator done) on that refresh.
13421365
const query = this.session.query;
1343-
const supportsTerminalOutput =
1344-
(
1345-
this.clientCapabilities?._meta as
1346-
| ClientCapabilities["_meta"]
1347-
| undefined
1348-
)?.terminal_output === true;
1349-
const context: MessageHandlerContext = {
1350-
session: this.session,
1351-
sessionId,
1352-
client: this.client,
1353-
toolUseCache: this.toolUseCache,
1354-
toolUseStreamCache: this.toolUseStreamCache,
1355-
fileContentCache: this.fileContentCache,
1356-
enrichedReadCache: this.enrichedReadCache,
1357-
logger: this.logger,
1358-
supportsTerminalOutput,
1359-
streamedAssistantBlocks: {
1360-
textIds: new Set<string>(),
1361-
thinkingIds: new Set<string>(),
1362-
},
1363-
};
1366+
const context = this.buildMessageContext(sessionId);
13641367

13651368
const state: IdleDrainState = { stop: false, done: Promise.resolve() };
13661369
this.idleDrain = state;
@@ -1465,22 +1468,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
14651468
resetStreamDedupe();
14661469
return;
14671470
case "tool_progress":
1468-
await this.client.sessionUpdate({
1469-
sessionId: context.sessionId,
1470-
update: {
1471-
sessionUpdate: "tool_call_update",
1472-
toolCallId: message.tool_use_id,
1473-
status: "in_progress",
1474-
_meta: {
1475-
claudeCode: {
1476-
toolName: message.tool_name,
1477-
toolResponse: {
1478-
elapsedTimeSeconds: message.elapsed_time_seconds,
1479-
},
1480-
},
1481-
} satisfies ToolUpdateMeta,
1482-
},
1483-
});
1471+
await this.emitToolProgress(context.sessionId, message);
14841472
return;
14851473
default:
14861474
// auth_status, rate_limit_event, prompt_suggestion, tool_use_summary,

0 commit comments

Comments
 (0)