feat(sdk): emit runtime bridge dispatch observability events
This commit is contained in:
@@ -5,6 +5,7 @@ import { resolveQueryCommand } from './query/query-command-resolution-strategy.j
|
||||
import { QueryExecutionPolicy } from './query-execution-policy.js';
|
||||
import { QueryNativeHotpathAdapter } from './query-native-hotpath-adapter.js';
|
||||
import { GSDToolsError } from './gsd-tools-error.js';
|
||||
import type { TransportDecision } from './gsd-transport.js';
|
||||
|
||||
export interface RuntimeBridgeExecuteInput {
|
||||
legacyCommand: string;
|
||||
@@ -16,6 +17,39 @@ export interface RuntimeBridgeExecuteInput {
|
||||
workstream?: string;
|
||||
}
|
||||
|
||||
export interface RuntimeBridgeDispatchEvent {
|
||||
type: 'query_dispatch';
|
||||
command: string;
|
||||
legacyCommand: string;
|
||||
mode: TransportMode;
|
||||
dispatchMode: 'native' | 'subprocess' | 'native_hotpath';
|
||||
reason?: TransportDecision['reason'];
|
||||
durationMs: number;
|
||||
outcome: 'success' | 'error';
|
||||
errorKind?: 'timeout' | 'failure';
|
||||
}
|
||||
|
||||
export interface RuntimeBridgeHotpathEvent {
|
||||
type: 'query_hotpath_dispatch';
|
||||
command: string;
|
||||
legacyCommand: string;
|
||||
mode: TransportMode;
|
||||
dispatchMode: 'native_hotpath';
|
||||
durationMs: number;
|
||||
outcome: 'success' | 'error';
|
||||
errorKind?: 'timeout' | 'failure';
|
||||
}
|
||||
|
||||
export interface RuntimeBridgeEvent {
|
||||
type: 'query_dispatch' | 'query_hotpath_dispatch';
|
||||
}
|
||||
|
||||
export interface RuntimeBridgeOptions {
|
||||
strictSdk?: boolean;
|
||||
allowFallbackToSubprocess?: boolean;
|
||||
onDispatchEvent?: (event: RuntimeBridgeDispatchEvent | RuntimeBridgeHotpathEvent) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* SDK Runtime Bridge Module.
|
||||
* Owns dispatch routing through the execution policy seam and hotpath/native fallback behavior.
|
||||
@@ -26,10 +60,7 @@ export class QueryRuntimeBridge {
|
||||
private readonly executionPolicy: QueryExecutionPolicy,
|
||||
private readonly nativeHotpathAdapter: QueryNativeHotpathAdapter,
|
||||
private readonly shouldUseNativeQuery: () => boolean,
|
||||
private readonly options?: {
|
||||
strictSdk?: boolean;
|
||||
allowFallbackToSubprocess?: boolean;
|
||||
},
|
||||
private readonly options?: RuntimeBridgeOptions,
|
||||
) {}
|
||||
|
||||
getRegistry(): QueryRegistry {
|
||||
@@ -41,26 +72,71 @@ export class QueryRuntimeBridge {
|
||||
}
|
||||
|
||||
async execute(input: RuntimeBridgeExecuteInput): Promise<unknown> {
|
||||
const startedAt = Date.now();
|
||||
if (this.options?.strictSdk && !this.registry.has(input.registryCommand)) {
|
||||
throw GSDToolsError.failure(
|
||||
const error = GSDToolsError.failure(
|
||||
`Strict SDK mode: command '${input.registryCommand}' has no native adapter`,
|
||||
input.legacyCommand,
|
||||
input.legacyArgs,
|
||||
null,
|
||||
);
|
||||
this.options?.onDispatchEvent?.({
|
||||
type: 'query_dispatch',
|
||||
command: input.registryCommand,
|
||||
legacyCommand: input.legacyCommand,
|
||||
mode: input.mode,
|
||||
dispatchMode: 'subprocess',
|
||||
reason: 'native_unregistered',
|
||||
durationMs: Date.now() - startedAt,
|
||||
outcome: 'error',
|
||||
errorKind: 'failure',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
return this.executionPolicy.execute({
|
||||
legacyCommand: input.legacyCommand,
|
||||
legacyArgs: input.legacyArgs,
|
||||
registryCommand: input.registryCommand,
|
||||
registryArgs: input.registryArgs,
|
||||
mode: input.mode,
|
||||
projectDir: input.projectDir,
|
||||
workstream: input.workstream,
|
||||
preferNativeQuery: this.shouldUseNativeQuery(),
|
||||
allowFallbackToSubprocess: this.options?.allowFallbackToSubprocess,
|
||||
});
|
||||
let transportDecision: TransportDecision | undefined;
|
||||
try {
|
||||
const result = await this.executionPolicy.execute({
|
||||
legacyCommand: input.legacyCommand,
|
||||
legacyArgs: input.legacyArgs,
|
||||
registryCommand: input.registryCommand,
|
||||
registryArgs: input.registryArgs,
|
||||
mode: input.mode,
|
||||
projectDir: input.projectDir,
|
||||
workstream: input.workstream,
|
||||
preferNativeQuery: this.shouldUseNativeQuery(),
|
||||
allowFallbackToSubprocess: this.options?.allowFallbackToSubprocess,
|
||||
onTransportDecision: (decision) => {
|
||||
transportDecision = decision;
|
||||
},
|
||||
});
|
||||
|
||||
this.options?.onDispatchEvent?.({
|
||||
type: 'query_dispatch',
|
||||
command: input.registryCommand,
|
||||
legacyCommand: input.legacyCommand,
|
||||
mode: input.mode,
|
||||
dispatchMode: transportDecision?.dispatchMode ?? 'native',
|
||||
reason: transportDecision?.reason,
|
||||
durationMs: Date.now() - startedAt,
|
||||
outcome: 'success',
|
||||
});
|
||||
return result;
|
||||
} catch (error) {
|
||||
const kind = error instanceof GSDToolsError ? error.classification.kind : 'failure';
|
||||
this.options?.onDispatchEvent?.({
|
||||
type: 'query_dispatch',
|
||||
command: input.registryCommand,
|
||||
legacyCommand: input.legacyCommand,
|
||||
mode: input.mode,
|
||||
dispatchMode: transportDecision?.dispatchMode ?? 'native',
|
||||
reason: transportDecision?.reason,
|
||||
durationMs: Date.now() - startedAt,
|
||||
outcome: 'error',
|
||||
errorKind: kind,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async dispatchHotpath(
|
||||
@@ -70,12 +146,38 @@ export class QueryRuntimeBridge {
|
||||
registryArgs: string[],
|
||||
mode: TransportMode,
|
||||
): Promise<unknown> {
|
||||
return this.nativeHotpathAdapter.dispatch(
|
||||
legacyCommand,
|
||||
legacyArgs,
|
||||
registryCommand,
|
||||
registryArgs,
|
||||
mode,
|
||||
);
|
||||
const startedAt = Date.now();
|
||||
try {
|
||||
const result = await this.nativeHotpathAdapter.dispatch(
|
||||
legacyCommand,
|
||||
legacyArgs,
|
||||
registryCommand,
|
||||
registryArgs,
|
||||
mode,
|
||||
);
|
||||
this.options?.onDispatchEvent?.({
|
||||
type: 'query_hotpath_dispatch',
|
||||
command: registryCommand,
|
||||
legacyCommand,
|
||||
mode,
|
||||
dispatchMode: 'native_hotpath',
|
||||
durationMs: Date.now() - startedAt,
|
||||
outcome: 'success',
|
||||
});
|
||||
return result;
|
||||
} catch (error) {
|
||||
const kind = error instanceof GSDToolsError ? error.classification.kind : 'failure';
|
||||
this.options?.onDispatchEvent?.({
|
||||
type: 'query_hotpath_dispatch',
|
||||
command: registryCommand,
|
||||
legacyCommand,
|
||||
mode,
|
||||
dispatchMode: 'native_hotpath',
|
||||
durationMs: Date.now() - startedAt,
|
||||
outcome: 'error',
|
||||
errorKind: kind,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user