Files
reverse-engineering-anything/src/application/binary/SessionProviderRouter.ts
T
Kaoru0822andClaude Opus 5.5 0ecf3f2438 perf(mcp): keep Playwright and type-only imports off MCP startup (#1691)
* fix(build): emit no runtime imports for type-only bindings

Under verbatimModuleSyntax, an import whose specifiers all carry inline
`type` modifiers compiles to `import {} from "..."`, a side-effect import
that still resolves and evaluates the module. 78 compiled modules carried
one, including `import {} from "playwright-core"` in the browser scenario
session, which loaded Playwright's module graph at MCP startup.

Use top-level `import type` for these bindings and enable oxlint's
typescript/no-import-type-side-effects rule so the pattern cannot return.

Refs #1685

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(mcp): load Playwright when a scenario launches, not at startup

The browser scenario and Electron active providers are constructed before
the stdio server serves, and both bound Playwright's launchers at module
load. Resolve `chromium` and `_electron` from playwright-core when a
capture opens instead, as the web module resolver already does.

MCP startup now resolves 1,312 modules (4,902 resolutions) instead of
1,598 (6,076) before reading the first request, and no longer loads
playwright-core. Provider construction, the tool catalog, and capability
inventory are unchanged.

A boundary test spawns the stdio server, initializes, lists tools, and
asserts playwright-core and isomorphic-git were not resolved; the CLI
probe now exercises the real launch path to prove detection.

Closes #1685

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:04:05 -07:00

202 lines
6.5 KiB
TypeScript

import type { AnalysisProviderSelector } from "../../contracts/providerSelection.js";
import type { AnalysisProfileCommitment } from "../../domain/analysisProfile.js";
import type { BinaryTarget } from "../../domain/binaryTargetTypes.js";
import { AnalysisCapabilityUnavailableError } from "../../domain/analysisErrorCore.js";
import { ProviderSelectionError } from "../../domain/providerSelectionError.js";
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
import { err, ok, type Result } from "../../domain/result.js";
import type {
AnalysisClient,
AnalysisClientContext,
AnalysisProvider,
CapabilityDescriptor,
ProviderIdentity,
} from "../AnalysisProvider.js";
import {
AnalysisProviderRegistry,
type AnalysisProviderBinding,
type AnalysisProviderCandidateStatus,
type AnalysisProviderSelection,
} from "./AnalysisProviderRegistry.js";
import { CompositeProvider } from "./CompositeProvider.js";
/** Immutable operation routes and binding metadata for one target transition. */
export interface SessionProviderRoute {
readonly identity: ProviderIdentity;
readonly capabilities: ReadonlyMap<string, CapabilityDescriptor>;
readonly profile: AnalysisProfileCommitment | null;
readonly binding: AnalysisProviderBinding | null;
readonly selection: AnalysisProviderSelection | undefined;
createClient(
target: BinaryTarget,
context: AnalysisClientContext,
): AnalysisClient;
}
/** Resolve one registry-selected deep provider and disjoint auxiliary families. */
export class SessionProviderRouter {
private constructor(
private readonly registry: AnalysisProviderRegistry,
private readonly auxiliaryProviders: readonly AnalysisProvider[],
private readonly route: SessionProviderRoute,
) {}
/** Create selection-aware routing while keeping auxiliary families disjoint. */
static selectable(
registry: AnalysisProviderRegistry,
auxiliaryProviders: readonly AnalysisProvider[],
): SessionProviderRouter {
const identities = [
...registry.identities(),
...auxiliaryProviders.map((provider) => provider.identity()),
];
assertUniqueProviderIds(identities);
const auxiliaryOperations = assertDisjoint(auxiliaryProviders);
for (const operation of registry.declaredOperations())
if (auxiliaryOperations.has(operation))
throw new TypeError(
`Deep and auxiliary providers both declare operation ${operation}`,
);
const initialRoute = selectableRoute(auxiliaryProviders, undefined);
return new SessionProviderRouter(
registry,
[...auxiliaryProviders],
initialRoute,
);
}
/** Target-free routes and discovery metadata. */
initialRoute(): SessionProviderRoute {
return this.route;
}
/** Resolve target routes before any provider client is created. */
async resolve(
target: BinaryTarget,
providerId?: AnalysisProviderSelector,
signal?: AbortSignal,
): Promise<Result<SessionProviderRoute, AnalysisError>> {
const resolutionOptions = signal === undefined ? undefined : { signal };
const selected = await this.registry.select(
target,
providerId,
resolutionOptions,
);
return selected.ok
? ok(selectableRoute(this.auxiliaryProviders, selected.value))
: selected;
}
/** Candidate status authoritative for the active or target-free route. */
candidateStatuses(
route: SessionProviderRoute,
): readonly AnalysisProviderCandidateStatus[] {
return route.selection?.candidates ?? this.registry.candidates();
}
/** Produce exact candidate rejections for an intentionally unbound route. */
unboundOperationError(
operation: string,
route: SessionProviderRoute,
): ProviderSelectionError | undefined {
if (
route.binding !== null ||
route.selection === undefined ||
!this.registry.declaredOperations().includes(operation)
)
return undefined;
return this.registry.unboundOperationError(operation, route.selection);
}
}
const selectableRoute = (
auxiliaryProviders: readonly AnalysisProvider[],
selection: AnalysisProviderSelection | undefined,
): SessionProviderRoute => {
const binding = selection?.binding ?? null;
const providers = [
...auxiliaryProviders,
...(binding === null ? [] : [binding.provider]),
];
const provider =
binding === null && providers.length === 1
? providers[0]
: providers.length === 0
? undefined
: new CompositeProvider(providers);
const identity = provider?.identity() ?? emptyProviderIdentity();
const capabilities = capabilityMap(provider?.capabilities() ?? []);
return {
identity,
capabilities,
profile: binding?.profile ?? null,
binding,
selection,
createClient: (target, context) =>
provider?.createClient(target, binding?.profile, context) ??
emptyClient(identity),
};
};
const emptyClient = (identity: ProviderIdentity): AnalysisClient => ({
execute: (operation) =>
operation === "health"
? Promise.resolve(
ok({
result: null,
rawResult: null,
provider: identity,
limitations: [],
locations: [],
subject: null,
}),
)
: Promise.resolve(
err(
new AnalysisCapabilityUnavailableError(
identity.id,
operation,
"operation is not declared by this provider set",
),
),
),
close: () => Promise.resolve(ok(null)),
});
const emptyProviderIdentity = (): ProviderIdentity => ({
id: "composite:none",
name: "REA composite analysis provider",
version: null,
});
const capabilityMap = (
capabilities: readonly CapabilityDescriptor[],
): ReadonlyMap<string, CapabilityDescriptor> =>
new Map(capabilities.map((descriptor) => [descriptor.operation, descriptor]));
const assertDisjoint = (
providers: readonly AnalysisProvider[],
): ReadonlySet<string> => {
const routes = new Set<string>();
for (const provider of providers)
for (const { operation } of provider.capabilities()) {
if (routes.has(operation))
throw new TypeError(
`Multiple auxiliary providers declare operation ${operation}`,
);
routes.add(operation);
}
return routes;
};
const assertUniqueProviderIds = (
identities: readonly ProviderIdentity[],
): void => {
const ids = new Set<string>();
for (const identity of identities) {
if (ids.has(identity.id))
throw new TypeError(`Duplicate configured provider ID: ${identity.id}`);
ids.add(identity.id);
}
};