refactor(core): F2 PR A W12 — SessionMcpView precompute filter Sets

W12 (filed as F2 follow-up from #4336 review): `applyTools` /
`applyPrompts` precompute `excludeSet` + `includeSet` once per pass
instead of scanning `cfg.includeTools` / `cfg.excludeTools` arrays
inside every per-tool iteration.

Pre-fix the per-tool predicate (`passesSessionFilter`) walked both
arrays for every snapshot entry → O(M × N) per `applyTools` call.
With M tools × N filter entries, typical M=5-20 / N=2-5 case
finishes in microseconds either way; the win is data-structure
correctness and code clarity, not perceived perf.

`passesSessionFilter` / `passesSessionPromptFilter` (the array-
based predicates) stay exported and unchanged for unit tests + any
caller wanting to test a single name without paying Set construction.
The bulk path uses two new private helpers `compileNameFilter` +
`compiledFilterAccepts` whose Sets live on the `applyTools` /
`applyPrompts` stack frame.

Same semantics: `excludeTools` is direct-equality match (no parens
strip — pre-F2 behavior preserved); `includeTools` strips the first
`(...)` suffix so `toolName(args)` matches `toolName`.

Filed bucket: F2 perf cleanup PR A (R9 + W11 done / W12 this commit
/ R10 to follow).

Test sweep: 13/13 session-mcp-view.test.ts pass; typecheck clean.
This commit is contained in:
doudouOUC 2026-05-22 00:49:22 +08:00
parent 20d2f1b90d
commit 6cf18f6414

View file

@ -28,6 +28,51 @@ function passesNameFilter(
});
}
/**
* F2 (#4175 commit 6 review fix wenshao W12 / PR A): precompute
* lookup `Set`s once per `applyTools` / `applyPrompts` pass so the
* per-tool predicate is O(1) instead of repeating the array scan
* inside `passesNameFilter` for every snapshot entry. Same semantics:
* `excludeTools` is direct-equality match (parens form not stripped
* intentional pre-F2 behavior preserved); `includeTools` strips the
* first `(...)` suffix so `toolName(args)` matches `toolName`.
*
* `passesSessionFilter` / `passesSessionPromptFilter` (the array-
* based predicates exported above) stay unchanged for unit tests
* and any caller that wants to test a single name without paying
* the Set-construction cost. The Sets live on `applyTools` /
* `applyPrompts`'s stack frame.
*/
interface CompiledNameFilter {
excludeSet?: ReadonlySet<string>;
includeSet?: ReadonlySet<string>;
}
function compileNameFilter(
includeTools?: readonly string[],
excludeTools?: readonly string[],
): CompiledNameFilter {
return {
excludeSet: excludeTools ? new Set(excludeTools) : undefined,
includeSet: includeTools
? new Set(
includeTools.map((entry) =>
entry.includes('(') ? entry.slice(0, entry.indexOf('(')) : entry,
),
)
: undefined,
};
}
function compiledFilterAccepts(
filter: CompiledNameFilter,
name: string,
): boolean {
if (filter.excludeSet?.has(name)) return false;
if (!filter.includeSet) return true;
return filter.includeSet.has(name);
}
/**
* Decide whether a tool from a snapshot passes a session's
* include/exclude filter. Exported for unit-testability and so the
@ -127,11 +172,17 @@ export class SessionMcpView {
*/
applyTools(snapshot: readonly DiscoveredMCPTool[]): void {
this.sessionToolRegistry.removeMcpToolsByServer(this.serverName);
// W12/PR A: precompute filter Sets once per pass so the per-tool
// predicate is O(1). Pre-fix `passesSessionFilter` re-scanned the
// includeTools / excludeTools arrays inside every iteration —
// O(M tools × N filter entries) per pass. Same semantics applied.
const filter = compileNameFilter(
this.cfg.includeTools,
this.cfg.excludeTools,
);
let registered = 0;
for (const tool of snapshot) {
if (
!passesSessionFilter(tool, this.cfg.includeTools, this.cfg.excludeTools)
) {
if (!compiledFilterAccepts(filter, tool.serverToolName)) {
continue;
}
// V21 C7: per-session trust copy. `withTrust` returns the same
@ -176,15 +227,14 @@ export class SessionMcpView {
*/
applyPrompts(snapshot: readonly DiscoveredMCPPrompt[]): void {
this.sessionPromptRegistry.removePromptsByServer(this.serverName);
// W12/PR A: same Set precompute as applyTools.
const filter = compileNameFilter(
this.cfg.includeTools,
this.cfg.excludeTools,
);
let registered = 0;
for (const prompt of snapshot) {
if (
!passesSessionPromptFilter(
prompt.name,
this.cfg.includeTools,
this.cfg.excludeTools,
)
) {
if (!compiledFilterAccepts(filter, prompt.name)) {
continue;
}
try {