@ai-sdk/harness-opencode 1.0.78 → 1.0.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/bridge/index.mjs +196 -23
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +192 -41
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/bridge/index.ts +62 -15
- package/src/opencode-harness.ts +242 -45
package/src/opencode-harness.ts
CHANGED
|
@@ -21,12 +21,15 @@ import {
|
|
|
21
21
|
} from '@ai-sdk/harness';
|
|
22
22
|
import {
|
|
23
23
|
classifyDiskLog,
|
|
24
|
+
experimental_createBridgeUserMessageSubmitter,
|
|
24
25
|
createBridgeErrorHandler,
|
|
25
26
|
createBridgeStartupError,
|
|
26
27
|
drainBridgeProcessStream,
|
|
27
28
|
forwardBridgeProcessStream,
|
|
29
|
+
getRestrictedSandboxSession,
|
|
28
30
|
markBridgeStarting,
|
|
29
31
|
maskSandboxCredentials,
|
|
32
|
+
resolveSandboxDefaultWorkingDirectory,
|
|
30
33
|
resolveSandboxHomeDir,
|
|
31
34
|
SandboxChannel,
|
|
32
35
|
shellQuote,
|
|
@@ -35,9 +38,10 @@ import {
|
|
|
35
38
|
writeSkills as writeHarnessSkills,
|
|
36
39
|
} from '@ai-sdk/harness/utils';
|
|
37
40
|
import {
|
|
41
|
+
safeParseJSON,
|
|
38
42
|
tool,
|
|
39
43
|
type Experimental_SandboxProcess,
|
|
40
|
-
type Experimental_SandboxSession,
|
|
44
|
+
type Experimental_SandboxSession as SandboxSession,
|
|
41
45
|
} from '@ai-sdk/provider-utils';
|
|
42
46
|
import { WebSocket } from 'ws';
|
|
43
47
|
import { z } from 'zod/v4';
|
|
@@ -88,6 +92,11 @@ export type OpenCodeHarnessSettings = {
|
|
|
88
92
|
*/
|
|
89
93
|
readonly reasoningVariant?: string;
|
|
90
94
|
readonly port?: number;
|
|
95
|
+
/**
|
|
96
|
+
* Override the host endpoint used to connect to the sandbox bridge. Required
|
|
97
|
+
* together with `port` when using a basic sandbox session.
|
|
98
|
+
*/
|
|
99
|
+
readonly portEndpoint?: HarnessV1PortEndpoint;
|
|
91
100
|
readonly startupTimeoutMs?: number;
|
|
92
101
|
/**
|
|
93
102
|
* Creates the authentication token used by the sandbox bridge. Defaults to
|
|
@@ -233,6 +242,26 @@ export function createOpenCode(
|
|
|
233
242
|
getBootstrap: getOpenCodeBootstrap,
|
|
234
243
|
doStart: async startOpts => {
|
|
235
244
|
const sandboxSession = startOpts.sandboxSession;
|
|
245
|
+
const toolSafeSandboxSession =
|
|
246
|
+
getRestrictedSandboxSession(sandboxSession);
|
|
247
|
+
const sandboxId = 'id' in sandboxSession ? sandboxSession.id : undefined;
|
|
248
|
+
validateBasicSandboxSettings({
|
|
249
|
+
sandboxSession,
|
|
250
|
+
port: settings.port,
|
|
251
|
+
portEndpoint: settings.portEndpoint,
|
|
252
|
+
});
|
|
253
|
+
if (settings.mintBridgeToken != null && sandboxId == null) {
|
|
254
|
+
throw new HarnessCapabilityUnsupportedError({
|
|
255
|
+
harnessId: 'opencode',
|
|
256
|
+
message:
|
|
257
|
+
'The OpenCode harness cannot use `mintBridgeToken` with a sandbox session that does not expose an id.',
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
const defaultWorkingDirectory =
|
|
261
|
+
await resolveSandboxDefaultWorkingDirectory({
|
|
262
|
+
sandboxSession,
|
|
263
|
+
abortSignal: startOpts.abortSignal,
|
|
264
|
+
});
|
|
236
265
|
const authenticationMode = resolveOpenCodeAuthenticationMode({
|
|
237
266
|
auth: settings.auth,
|
|
238
267
|
model: settings.model,
|
|
@@ -244,7 +273,10 @@ export function createOpenCode(
|
|
|
244
273
|
provider: settings.provider,
|
|
245
274
|
});
|
|
246
275
|
let sandboxAuthEnvironment = resolvedAuthEnvironment;
|
|
247
|
-
if (
|
|
276
|
+
if (
|
|
277
|
+
'addRequestTransformations' in sandboxSession &&
|
|
278
|
+
sandboxSession.addRequestTransformations != null
|
|
279
|
+
) {
|
|
248
280
|
const requestTransformations = createOpenCodeRequestTransformations(
|
|
249
281
|
resolvedAuthEnvironment,
|
|
250
282
|
authenticationMode,
|
|
@@ -262,10 +294,8 @@ export function createOpenCode(
|
|
|
262
294
|
} else {
|
|
263
295
|
warnCredentialBrokeringUnavailable();
|
|
264
296
|
}
|
|
265
|
-
const session = sandboxSession.restricted();
|
|
266
|
-
const sandboxId = sandboxSession.id;
|
|
267
297
|
const bootstrapDir = path.posix.resolve(
|
|
268
|
-
|
|
298
|
+
defaultWorkingDirectory,
|
|
269
299
|
BOOTSTRAP_DIR,
|
|
270
300
|
);
|
|
271
301
|
const lifecycleState = startOpts.continueFrom ?? startOpts.resumeFrom;
|
|
@@ -286,7 +316,7 @@ export function createOpenCode(
|
|
|
286
316
|
const coords = resumeData?.bridge;
|
|
287
317
|
|
|
288
318
|
const workDir = startOpts.sessionWorkDir;
|
|
289
|
-
const sessionDataDir = `${
|
|
319
|
+
const sessionDataDir = `${defaultWorkingDirectory}/.agent-runs/${startOpts.sessionId}`;
|
|
290
320
|
const bridgeStateDir = `${sessionDataDir}/bridge`;
|
|
291
321
|
const timeoutMs = settings.startupTimeoutMs ?? 120_000;
|
|
292
322
|
const model = splitOpenCodeModel(settings.model, settings.provider).model;
|
|
@@ -308,16 +338,25 @@ export function createOpenCode(
|
|
|
308
338
|
|
|
309
339
|
if (coords) {
|
|
310
340
|
try {
|
|
311
|
-
const endpoint = await
|
|
341
|
+
const endpoint = await resolveBridgeEndpoint({
|
|
342
|
+
sandboxSession,
|
|
343
|
+
override: settings.portEndpoint,
|
|
312
344
|
port: coords.port,
|
|
313
|
-
protocol: 'ws',
|
|
314
345
|
});
|
|
315
346
|
const attachEndpoint = withBridgeToken({
|
|
316
347
|
endpoint,
|
|
317
348
|
token: coords.token,
|
|
318
349
|
});
|
|
350
|
+
let supportsUserMessageResponses = false;
|
|
319
351
|
const attachChannel: OpenCodeChannel = new SandboxChannel({
|
|
320
|
-
connect: () =>
|
|
352
|
+
connect: () =>
|
|
353
|
+
openWebSocket({
|
|
354
|
+
endpoint: attachEndpoint,
|
|
355
|
+
helloTimeoutMs: Math.min(timeoutMs, 5_000),
|
|
356
|
+
onHello: supported => {
|
|
357
|
+
supportsUserMessageResponses = supported;
|
|
358
|
+
},
|
|
359
|
+
}),
|
|
321
360
|
outboundSchema: outboundMessageSchema,
|
|
322
361
|
initialLastSeenEventId: coords.lastSeenEventId,
|
|
323
362
|
onDiagnostic,
|
|
@@ -342,6 +381,7 @@ export function createOpenCode(
|
|
|
342
381
|
debug: startOpts.observability?.debug,
|
|
343
382
|
permissionMode: startOpts.permissionMode,
|
|
344
383
|
builtinToolFiltering: startOpts.builtinToolFiltering,
|
|
384
|
+
supportsUserMessageResponses: () => supportsUserMessageResponses,
|
|
345
385
|
});
|
|
346
386
|
} catch {}
|
|
347
387
|
}
|
|
@@ -351,7 +391,7 @@ export function createOpenCode(
|
|
|
351
391
|
: undefined;
|
|
352
392
|
if (coords && isContinue) {
|
|
353
393
|
const logRaw = await Promise.resolve(
|
|
354
|
-
|
|
394
|
+
toolSafeSandboxSession.readTextFile({
|
|
355
395
|
path: `${bridgeStateDir}/event-log.ndjson`,
|
|
356
396
|
abortSignal: startOpts.abortSignal,
|
|
357
397
|
}),
|
|
@@ -361,13 +401,16 @@ export function createOpenCode(
|
|
|
361
401
|
}
|
|
362
402
|
}
|
|
363
403
|
|
|
364
|
-
const port = resolveBridgePort(
|
|
404
|
+
const port = resolveBridgePort({
|
|
405
|
+
sandboxSession,
|
|
406
|
+
override: settings.port,
|
|
407
|
+
});
|
|
365
408
|
const token =
|
|
366
409
|
settings.mintBridgeToken == null
|
|
367
410
|
? randomBytes(32).toString('hex')
|
|
368
|
-
: settings.mintBridgeToken(sandboxId);
|
|
411
|
+
: settings.mintBridgeToken(sandboxId!);
|
|
369
412
|
const sandboxHomeDir = await resolveSandboxHomeDir({
|
|
370
|
-
sandbox:
|
|
413
|
+
sandbox: toolSafeSandboxSession,
|
|
371
414
|
abortSignal: startOpts.abortSignal,
|
|
372
415
|
});
|
|
373
416
|
const xdgConfigHome = `${sandboxHomeDir}/.config`;
|
|
@@ -377,7 +420,7 @@ export function createOpenCode(
|
|
|
377
420
|
const skillSetup =
|
|
378
421
|
startOpts.skills && startOpts.skills.length > 0
|
|
379
422
|
? await writeOpenCodeSkills({
|
|
380
|
-
sandbox:
|
|
423
|
+
sandbox: toolSafeSandboxSession,
|
|
381
424
|
skills: startOpts.skills,
|
|
382
425
|
homeDir: sandboxHomeDir,
|
|
383
426
|
abortSignal: startOpts.abortSignal,
|
|
@@ -400,20 +443,20 @@ export function createOpenCode(
|
|
|
400
443
|
};
|
|
401
444
|
|
|
402
445
|
if (respawnStrategy === undefined) {
|
|
403
|
-
await
|
|
446
|
+
await toolSafeSandboxSession.run({
|
|
404
447
|
command: `mkdir -p ${shellQuote(workDir)} ${shellQuote(bridgeStateDir)} ${shellQuote(xdgConfigHome)} ${shellQuote(xdgCacheHome)} ${shellQuote(xdgDataHome)} ${shellQuote(xdgStateHome)}`,
|
|
405
448
|
abortSignal: startOpts.abortSignal,
|
|
406
449
|
});
|
|
407
450
|
}
|
|
408
451
|
|
|
409
452
|
await markBridgeStarting({
|
|
410
|
-
sandbox:
|
|
453
|
+
sandbox: toolSafeSandboxSession,
|
|
411
454
|
bridgeStateDir,
|
|
412
455
|
bridgeType: 'opencode',
|
|
413
456
|
abortSignal: startOpts.abortSignal,
|
|
414
457
|
});
|
|
415
458
|
|
|
416
|
-
const proc = await
|
|
459
|
+
const proc = await toolSafeSandboxSession.spawn({
|
|
417
460
|
command: `node ${shellQuote(`${bootstrapDir}/bridge.mjs`)} --workdir ${shellQuote(workDir)} --bridge-state-dir ${shellQuote(bridgeStateDir)} --bootstrap-dir ${shellQuote(bootstrapDir)}${skillSetup ? ` --skills-dir ${shellQuote(skillSetup.skillsDir)}` : ''}`,
|
|
418
461
|
env,
|
|
419
462
|
abortSignal: startOpts.abortSignal,
|
|
@@ -428,7 +471,7 @@ export function createOpenCode(
|
|
|
428
471
|
|
|
429
472
|
const { port: boundPort } = await waitForBridgeReady({
|
|
430
473
|
proc,
|
|
431
|
-
sandbox:
|
|
474
|
+
sandbox: toolSafeSandboxSession,
|
|
432
475
|
bridgeStateDir,
|
|
433
476
|
bridgeType: 'opencode',
|
|
434
477
|
timeoutMs,
|
|
@@ -452,14 +495,23 @@ export function createOpenCode(
|
|
|
452
495
|
});
|
|
453
496
|
void drainBridgeProcessStream(proc.stdout);
|
|
454
497
|
|
|
455
|
-
const endpoint = await
|
|
498
|
+
const endpoint = await resolveBridgeEndpoint({
|
|
499
|
+
sandboxSession,
|
|
500
|
+
override: settings.portEndpoint,
|
|
456
501
|
port: boundPort,
|
|
457
|
-
protocol: 'ws',
|
|
458
502
|
});
|
|
459
503
|
const bridgeEndpoint = withBridgeToken({ endpoint, token });
|
|
504
|
+
let supportsUserMessageResponses = false;
|
|
460
505
|
|
|
461
506
|
const channel: OpenCodeChannel = new SandboxChannel({
|
|
462
|
-
connect: () =>
|
|
507
|
+
connect: () =>
|
|
508
|
+
openWebSocket({
|
|
509
|
+
endpoint: bridgeEndpoint,
|
|
510
|
+
helloTimeoutMs: Math.min(timeoutMs, 5_000),
|
|
511
|
+
onHello: supported => {
|
|
512
|
+
supportsUserMessageResponses = supported;
|
|
513
|
+
},
|
|
514
|
+
}),
|
|
463
515
|
outboundSchema: outboundMessageSchema,
|
|
464
516
|
onDiagnostic,
|
|
465
517
|
onBridgeError,
|
|
@@ -489,17 +541,23 @@ export function createOpenCode(
|
|
|
489
541
|
debug: startOpts.observability?.debug,
|
|
490
542
|
permissionMode: startOpts.permissionMode,
|
|
491
543
|
builtinToolFiltering: startOpts.builtinToolFiltering,
|
|
544
|
+
supportsUserMessageResponses: () => supportsUserMessageResponses,
|
|
492
545
|
});
|
|
493
546
|
},
|
|
494
547
|
};
|
|
495
548
|
}
|
|
496
549
|
|
|
497
|
-
function resolveBridgePort(
|
|
498
|
-
sandboxSession
|
|
499
|
-
override
|
|
500
|
-
|
|
550
|
+
function resolveBridgePort({
|
|
551
|
+
sandboxSession,
|
|
552
|
+
override,
|
|
553
|
+
}: {
|
|
554
|
+
sandboxSession: HarnessV1NetworkSandboxSession | SandboxSession;
|
|
555
|
+
override: number | undefined;
|
|
556
|
+
}): number {
|
|
501
557
|
if (override !== undefined) return override;
|
|
502
|
-
if (sandboxSession.ports.length > 0)
|
|
558
|
+
if ('ports' in sandboxSession && sandboxSession.ports.length > 0) {
|
|
559
|
+
return sandboxSession.ports[0];
|
|
560
|
+
}
|
|
503
561
|
throw new HarnessCapabilityUnsupportedError({
|
|
504
562
|
harnessId: 'opencode',
|
|
505
563
|
message:
|
|
@@ -508,13 +566,59 @@ function resolveBridgePort(
|
|
|
508
566
|
});
|
|
509
567
|
}
|
|
510
568
|
|
|
569
|
+
function validateBasicSandboxSettings({
|
|
570
|
+
sandboxSession,
|
|
571
|
+
port,
|
|
572
|
+
portEndpoint,
|
|
573
|
+
}: {
|
|
574
|
+
sandboxSession: HarnessV1NetworkSandboxSession | SandboxSession;
|
|
575
|
+
port: number | undefined;
|
|
576
|
+
portEndpoint: HarnessV1PortEndpoint | undefined;
|
|
577
|
+
}): void {
|
|
578
|
+
if ('getPortEndpoint' in sandboxSession) return;
|
|
579
|
+
if (port == null) {
|
|
580
|
+
throw new HarnessCapabilityUnsupportedError({
|
|
581
|
+
harnessId: 'opencode',
|
|
582
|
+
message:
|
|
583
|
+
'The OpenCode harness requires an explicit `port` when using a basic sandbox session.',
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
if (portEndpoint == null) {
|
|
587
|
+
throw new HarnessCapabilityUnsupportedError({
|
|
588
|
+
harnessId: 'opencode',
|
|
589
|
+
message:
|
|
590
|
+
'The OpenCode harness requires an explicit `portEndpoint` when using a basic sandbox session.',
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
async function resolveBridgeEndpoint({
|
|
596
|
+
sandboxSession,
|
|
597
|
+
override,
|
|
598
|
+
port,
|
|
599
|
+
}: {
|
|
600
|
+
sandboxSession: HarnessV1NetworkSandboxSession | SandboxSession;
|
|
601
|
+
override: HarnessV1PortEndpoint | undefined;
|
|
602
|
+
port: number;
|
|
603
|
+
}): Promise<HarnessV1PortEndpoint> {
|
|
604
|
+
if (override != null) return override;
|
|
605
|
+
if ('getPortEndpoint' in sandboxSession) {
|
|
606
|
+
return sandboxSession.getPortEndpoint({ port, protocol: 'ws' });
|
|
607
|
+
}
|
|
608
|
+
throw new HarnessCapabilityUnsupportedError({
|
|
609
|
+
harnessId: 'opencode',
|
|
610
|
+
message:
|
|
611
|
+
'The OpenCode harness requires an explicit `portEndpoint` when using a basic sandbox session.',
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
|
|
511
615
|
async function writeOpenCodeSkills({
|
|
512
616
|
sandbox,
|
|
513
617
|
skills,
|
|
514
618
|
homeDir,
|
|
515
619
|
abortSignal,
|
|
516
620
|
}: {
|
|
517
|
-
sandbox:
|
|
621
|
+
sandbox: SandboxSession;
|
|
518
622
|
skills: ReadonlyArray<HarnessV1Skill>;
|
|
519
623
|
homeDir: string;
|
|
520
624
|
abortSignal?: AbortSignal;
|
|
@@ -535,26 +639,105 @@ async function writeOpenCodeSkills({
|
|
|
535
639
|
}
|
|
536
640
|
|
|
537
641
|
function openWebSocket({
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
642
|
+
endpoint,
|
|
643
|
+
helloTimeoutMs,
|
|
644
|
+
onHello,
|
|
645
|
+
}: {
|
|
646
|
+
endpoint: HarnessV1PortEndpoint;
|
|
647
|
+
helloTimeoutMs: number;
|
|
648
|
+
onHello(supportsUserMessageResponses: boolean): void;
|
|
649
|
+
}): Promise<WebSocket> {
|
|
541
650
|
return new Promise((resolve, reject) => {
|
|
542
|
-
const ws = new WebSocket(url, {
|
|
543
|
-
headers: headers == null ? undefined : { ...headers },
|
|
651
|
+
const ws = new WebSocket(endpoint.url, {
|
|
652
|
+
headers: endpoint.headers == null ? undefined : { ...endpoint.headers },
|
|
544
653
|
});
|
|
545
|
-
|
|
654
|
+
let opened = false;
|
|
655
|
+
let receivedHello = false;
|
|
656
|
+
let settled = false;
|
|
657
|
+
const cleanup = () => {
|
|
658
|
+
clearTimeout(helloTimer);
|
|
659
|
+
ws.off('open', onOpen);
|
|
660
|
+
ws.off('message', onMessage);
|
|
661
|
+
ws.off('close', onClose);
|
|
546
662
|
ws.off('error', onError);
|
|
547
|
-
resolve(ws);
|
|
548
663
|
};
|
|
664
|
+
const settle = (error?: unknown) => {
|
|
665
|
+
if (settled) return;
|
|
666
|
+
settled = true;
|
|
667
|
+
cleanup();
|
|
668
|
+
if (error == null) {
|
|
669
|
+
resolve(ws);
|
|
670
|
+
} else {
|
|
671
|
+
reject(error);
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
const tryResolve = () => {
|
|
675
|
+
if (opened && receivedHello) settle();
|
|
676
|
+
};
|
|
677
|
+
const onOpen = () => {
|
|
678
|
+
opened = true;
|
|
679
|
+
tryResolve();
|
|
680
|
+
};
|
|
681
|
+
const onMessage = (raw: unknown) => {
|
|
682
|
+
void (async () => {
|
|
683
|
+
const parsed = await safeParseJSON({
|
|
684
|
+
text: webSocketMessageToString(raw),
|
|
685
|
+
});
|
|
686
|
+
if (!parsed.success) return;
|
|
687
|
+
const value = parsed.value;
|
|
688
|
+
if (
|
|
689
|
+
typeof value !== 'object' ||
|
|
690
|
+
value == null ||
|
|
691
|
+
Array.isArray(value) ||
|
|
692
|
+
(value as { type?: unknown }).type !== 'bridge-hello'
|
|
693
|
+
) {
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
const capabilities = (
|
|
697
|
+
value as {
|
|
698
|
+
capabilities?: { experimental_userMessageResponses?: unknown };
|
|
699
|
+
}
|
|
700
|
+
).capabilities;
|
|
701
|
+
onHello(capabilities?.experimental_userMessageResponses === true);
|
|
702
|
+
receivedHello = true;
|
|
703
|
+
tryResolve();
|
|
704
|
+
})();
|
|
705
|
+
};
|
|
706
|
+
const onClose = () =>
|
|
707
|
+
settle(new Error('OpenCode bridge closed before sending bridge-hello.'));
|
|
549
708
|
const onError = (err: Error) => {
|
|
550
|
-
|
|
551
|
-
reject(err);
|
|
709
|
+
settle(err);
|
|
552
710
|
};
|
|
553
|
-
ws.
|
|
554
|
-
ws.
|
|
711
|
+
ws.on('open', onOpen);
|
|
712
|
+
ws.on('message', onMessage);
|
|
713
|
+
ws.on('close', onClose);
|
|
714
|
+
ws.on('error', onError);
|
|
715
|
+
const helloTimer = setTimeout(
|
|
716
|
+
() =>
|
|
717
|
+
settle(
|
|
718
|
+
new Error(
|
|
719
|
+
`OpenCode bridge did not send bridge-hello within ${helloTimeoutMs}ms.`,
|
|
720
|
+
),
|
|
721
|
+
),
|
|
722
|
+
helloTimeoutMs,
|
|
723
|
+
);
|
|
724
|
+
helloTimer.unref?.();
|
|
555
725
|
});
|
|
556
726
|
}
|
|
557
727
|
|
|
728
|
+
function webSocketMessageToString(raw: unknown): string {
|
|
729
|
+
if (typeof raw === 'string') return raw;
|
|
730
|
+
if (Buffer.isBuffer(raw)) return raw.toString('utf8');
|
|
731
|
+
if (Array.isArray(raw)) return Buffer.concat(raw).toString('utf8');
|
|
732
|
+
if (raw instanceof ArrayBuffer) return Buffer.from(raw).toString('utf8');
|
|
733
|
+
if (ArrayBuffer.isView(raw)) {
|
|
734
|
+
return Buffer.from(raw.buffer, raw.byteOffset, raw.byteLength).toString(
|
|
735
|
+
'utf8',
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
return String(raw);
|
|
739
|
+
}
|
|
740
|
+
|
|
558
741
|
function withBridgeToken({
|
|
559
742
|
endpoint,
|
|
560
743
|
token,
|
|
@@ -585,6 +768,7 @@ function createSession({
|
|
|
585
768
|
debug,
|
|
586
769
|
permissionMode,
|
|
587
770
|
builtinToolFiltering,
|
|
771
|
+
supportsUserMessageResponses,
|
|
588
772
|
}: {
|
|
589
773
|
sessionId: string;
|
|
590
774
|
channel: OpenCodeChannel;
|
|
@@ -599,10 +783,11 @@ function createSession({
|
|
|
599
783
|
rerunContinue: boolean;
|
|
600
784
|
bridgePort: number;
|
|
601
785
|
bridgeToken: string;
|
|
602
|
-
sandboxId: string;
|
|
786
|
+
sandboxId: string | undefined;
|
|
603
787
|
debug: HarnessV1DebugConfig | undefined;
|
|
604
788
|
permissionMode: HarnessV1PermissionMode | undefined;
|
|
605
789
|
builtinToolFiltering: HarnessV1BuiltinToolFiltering | undefined;
|
|
790
|
+
supportsUserMessageResponses: () => boolean;
|
|
606
791
|
}): HarnessV1Session {
|
|
607
792
|
let stopped = false;
|
|
608
793
|
let stopPromise: Promise<void> | undefined;
|
|
@@ -616,7 +801,6 @@ function createSession({
|
|
|
616
801
|
channel.on('bridge-thread', msg => {
|
|
617
802
|
latestOpenCodeSessionId = msg.threadId;
|
|
618
803
|
});
|
|
619
|
-
|
|
620
804
|
const wireTurn = (turnOpts: {
|
|
621
805
|
emit: (event: HarnessV1StreamPart) => void;
|
|
622
806
|
abortSignal?: AbortSignal;
|
|
@@ -628,6 +812,13 @@ function createSession({
|
|
|
628
812
|
pendingResolve = resolve;
|
|
629
813
|
pendingReject = reject;
|
|
630
814
|
});
|
|
815
|
+
const userMessageSubmitter = supportsUserMessageResponses()
|
|
816
|
+
? experimental_createBridgeUserMessageSubmitter({
|
|
817
|
+
send: message => channel.send(message),
|
|
818
|
+
onResponse: listener => channel.on('user-message-response', listener),
|
|
819
|
+
onReconnect: listener => channel.onReconnect(listener),
|
|
820
|
+
})
|
|
821
|
+
: undefined;
|
|
631
822
|
|
|
632
823
|
const unsubs: Array<() => void> = [];
|
|
633
824
|
const forward = (event: HarnessV1StreamPart) => {
|
|
@@ -657,6 +848,7 @@ function createSession({
|
|
|
657
848
|
if (isSettled) return;
|
|
658
849
|
isSettled = true;
|
|
659
850
|
activeTurn = false;
|
|
851
|
+
userMessageSubmitter?.close();
|
|
660
852
|
for (const u of unsubs) u();
|
|
661
853
|
pendingResolve!();
|
|
662
854
|
};
|
|
@@ -664,6 +856,7 @@ function createSession({
|
|
|
664
856
|
if (isSettled) return;
|
|
665
857
|
isSettled = true;
|
|
666
858
|
activeTurn = false;
|
|
859
|
+
userMessageSubmitter?.close(err);
|
|
667
860
|
for (const u of unsubs) u();
|
|
668
861
|
pendingReject!(err);
|
|
669
862
|
};
|
|
@@ -741,9 +934,13 @@ function createSession({
|
|
|
741
934
|
reason: input.reason,
|
|
742
935
|
});
|
|
743
936
|
},
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
937
|
+
...(userMessageSubmitter == null
|
|
938
|
+
? {}
|
|
939
|
+
: {
|
|
940
|
+
submitUserMessage: async (text: string) => {
|
|
941
|
+
await userMessageSubmitter.submit(text);
|
|
942
|
+
},
|
|
943
|
+
}),
|
|
747
944
|
done,
|
|
748
945
|
};
|
|
749
946
|
};
|
|
@@ -885,7 +1082,7 @@ function createSession({
|
|
|
885
1082
|
port: bridgePort,
|
|
886
1083
|
token: bridgeToken,
|
|
887
1084
|
lastSeenEventId,
|
|
888
|
-
sandboxId,
|
|
1085
|
+
...(sandboxId == null ? {} : { sandboxId }),
|
|
889
1086
|
},
|
|
890
1087
|
},
|
|
891
1088
|
};
|
|
@@ -1002,7 +1199,7 @@ function createSession({
|
|
|
1002
1199
|
port: bridgePort,
|
|
1003
1200
|
token: bridgeToken,
|
|
1004
1201
|
lastSeenEventId,
|
|
1005
|
-
sandboxId,
|
|
1202
|
+
...(sandboxId == null ? {} : { sandboxId }),
|
|
1006
1203
|
},
|
|
1007
1204
|
},
|
|
1008
1205
|
};
|