feat(sdk): expose runtime bridge controls via GSD options

This commit is contained in:
Tom Boucher
2026-05-05 19:36:47 -04:00
parent 00ba404b60
commit 51b809e8e9
4 changed files with 52 additions and 1 deletions

View File

@@ -55,7 +55,7 @@ export class GSDTransport {
} else {
const reason = this.subprocessReason(request, policy);
onDecision?.({ dispatchMode: 'subprocess', reason });
if (!policy.allowFallbackToSubprocess) {
if (!policy.allowFallbackToSubprocess && reason === 'native_unregistered') {
throw GSDToolsError.failure(
`Subprocess fallback disabled: command '${request.registryCommand}' cannot run without native dispatch`,
request.legacyCommand,

View File

@@ -50,6 +50,8 @@ export class GSD {
private readonly defaultMaxTurns: number;
private readonly autoMode: boolean;
private readonly workstream?: string;
private readonly strictSdk?: boolean;
private readonly allowFallbackToSubprocess?: boolean;
readonly eventStream: GSDEventStream;
constructor(options: GSDOptions) {
@@ -62,6 +64,8 @@ export class GSD {
this.defaultMaxTurns = options.maxTurns ?? 50;
this.autoMode = options.autoMode ?? false;
this.workstream = options.workstream;
this.strictSdk = options.strictSdk;
this.allowFallbackToSubprocess = options.allowFallbackToSubprocess;
this.eventStream = new GSDEventStream();
}
@@ -128,6 +132,16 @@ export class GSD {
workstream: this.workstream,
eventStream: this.eventStream,
sessionId: this.sessionId,
strictSdk: this.strictSdk,
allowFallbackToSubprocess: this.allowFallbackToSubprocess,
onDispatchEvent: (event) => {
this.eventStream.emitEvent({
type: GSDEventType.StreamEvent,
timestamp: new Date().toISOString(),
sessionId: this.sessionId ?? '',
event,
});
},
});
}

View File

@@ -0,0 +1,33 @@
import { describe, it, expect } from 'vitest';
import { GSD } from './index.js';
import { GSDEventType, type GSDEvent } from './types.js';
describe('GSD runtime bridge options', () => {
it('strictSdk option is honored by createTools dispatch seam', async () => {
const gsd = new GSD({
projectDir: process.cwd(),
strictSdk: true,
allowFallbackToSubprocess: true,
sessionId: 'test-session',
});
const events: GSDEvent[] = [];
gsd.onEvent((event) => events.push(event));
await expect(gsd.createTools().exec('nonexistent-command', [])).rejects.toThrow(
"Strict SDK mode: command 'nonexistent-command' has no native adapter",
);
const streamEvent = events.find((event) => event.type === GSDEventType.StreamEvent);
expect(streamEvent).toBeDefined();
expect(streamEvent).toMatchObject({
type: GSDEventType.StreamEvent,
sessionId: 'test-session',
event: {
type: 'query_dispatch',
command: 'nonexistent-command',
outcome: 'error',
},
});
});
});

View File

@@ -203,6 +203,10 @@ export interface GSDOptions {
* Optional session correlation id for query mutation events when using {@link GSD.createTools}.
*/
sessionId?: string;
/** Strict SDK runtime bridge mode: fail fast when a query command has no native adapter. */
strictSdk?: boolean;
/** Explicit subprocess fallback policy for the runtime bridge. Default false. */
allowFallbackToSubprocess?: boolean;
/** Model to use for execution sessions. */
model?: string;
/** Maximum budget per plan execution in USD. Default: 5.0. */