Skip to content

Commit ebfca09

Browse files
authored
perf(web): long threads stream with 70% less work-row derivation (#29971)
1 parent 741bcae commit ebfca09

2 files changed

Lines changed: 42 additions & 3 deletions

File tree

‎packages/client-runtime/src/work-log/presentation.ts‎

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,12 +291,31 @@ function workEntryIndicatesToolFailureFromOutput(
291291
return output.length > 0 && toolOutputIndicatesFailure(output);
292292
}
293293

294+
// Rows are re-derived on every timeline update but entries are reused while
295+
// their item is unchanged, so these per-entry checks are computed once.
296+
const entryChecks = new WeakMap<
297+
WorkLogPresentationEntry,
298+
{ failure?: boolean; displayFailure?: boolean; action?: ToolGroupAction }
299+
>();
300+
301+
function checksFor(entry: WorkLogPresentationEntry) {
302+
let checks = entryChecks.get(entry);
303+
if (!checks) {
304+
checks = {};
305+
entryChecks.set(entry, checks);
306+
}
307+
return checks;
308+
}
309+
294310
export function workEntryIndicatesToolFailure(entry: WorkLogPresentationEntry): boolean {
295-
return workEntryIndicatesToolFailureFromOutput(entry, true);
311+
return (checksFor(entry).failure ??= workEntryIndicatesToolFailureFromOutput(entry, true));
296312
}
297313

298314
export function workEntryDisplayIndicatesToolFailure(entry: WorkLogPresentationEntry): boolean {
299-
return workEntryIndicatesToolFailureFromOutput(entry, false);
315+
return (checksFor(entry).displayFailure ??= workEntryIndicatesToolFailureFromOutput(
316+
entry,
317+
false,
318+
));
300319
}
301320

302321
function workLogEntryIsLocalCodeSearch(entry: WorkLogPresentationEntry): boolean {
@@ -308,6 +327,10 @@ function workLogEntryIsLocalCodeSearch(entry: WorkLogPresentationEntry): boolean
308327
}
309328

310329
export function toolGroupAction(entry: WorkLogPresentationEntry): ToolGroupAction {
330+
return (checksFor(entry).action ??= computeToolGroupAction(entry));
331+
}
332+
333+
function computeToolGroupAction(entry: WorkLogPresentationEntry): ToolGroupAction {
311334
if (entry.itemType === "thread_created") return "thread-create";
312335
const presentation = resolveWorkEntryToolPresentation(entry);
313336
if (presentation?.action !== undefined) return presentation.action;

‎packages/shared/src/t3McpToolPresentation.ts‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,8 +380,24 @@ function normalizeT3McpToolLabel(value: string): string {
380380
return value.replace(/\s+(?:complete|completed)\s*$/i, "").trim();
381381
}
382382

383-
// ACP tool prefixes vary; match loosely but require a known display-name entry.
383+
// Timelines resolve every work row's tool name on each update; names repeat across updates.
384+
// ACP titles can carry whole commands, so long labels are parsed but not kept.
385+
const toolNameCache = new Map<string, string | null>();
386+
const TOOL_NAME_CACHE_LIMIT = 10_000;
387+
const TOOL_NAME_CACHE_MAX_KEY_LENGTH = 256;
388+
384389
function resolveT3McpToolName(value: string): string | null {
390+
if (value.length > TOOL_NAME_CACHE_MAX_KEY_LENGTH) return parseT3McpToolName(value);
391+
const cached = toolNameCache.get(value);
392+
if (cached !== undefined) return cached;
393+
const name = parseT3McpToolName(value);
394+
if (toolNameCache.size >= TOOL_NAME_CACHE_LIMIT) toolNameCache.clear();
395+
toolNameCache.set(value, name);
396+
return name;
397+
}
398+
399+
// ACP tool prefixes vary; match loosely but require a known display-name entry.
400+
function parseT3McpToolName(value: string): string | null {
385401
const label = normalizeT3McpToolLabel(value);
386402
const mcpMatch = /^mcp__(?<server>.+?)__(?<tool>.+)$/i.exec(label);
387403
if (mcpMatch?.groups) {

0 commit comments

Comments
 (0)