@ai-sdk/harness-opencode 1.0.136 → 1.0.139
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 +24 -0
- package/dist/bridge/index.mjs +54 -10
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -7
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/bridge/index.ts +66 -12
- package/src/opencode-harness.ts +41 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/harness-opencode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.139",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@ai-sdk/harness": "1.0.
|
|
30
|
-
"@ai-sdk/provider": "4.0.
|
|
31
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
29
|
+
"@ai-sdk/harness": "1.0.137",
|
|
30
|
+
"@ai-sdk/provider": "4.0.21",
|
|
31
|
+
"@ai-sdk/provider-utils": "5.0.53",
|
|
32
32
|
"ws": "8.21.0"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
package/src/bridge/index.ts
CHANGED
|
@@ -532,6 +532,16 @@ async function legacySessionPrompt({
|
|
|
532
532
|
});
|
|
533
533
|
}
|
|
534
534
|
|
|
535
|
+
async function legacySessionAbort({
|
|
536
|
+
client,
|
|
537
|
+
sessionId,
|
|
538
|
+
}: {
|
|
539
|
+
client: OpenCodeClient;
|
|
540
|
+
sessionId: string;
|
|
541
|
+
}): Promise<{ error?: unknown; data?: unknown }> {
|
|
542
|
+
return (client as any).session.abort({ sessionID: sessionId });
|
|
543
|
+
}
|
|
544
|
+
|
|
535
545
|
async function legacySessionSummarize({
|
|
536
546
|
client,
|
|
537
547
|
sessionId,
|
|
@@ -656,7 +666,26 @@ async function runPrompt({
|
|
|
656
666
|
emit: Emit;
|
|
657
667
|
}): Promise<HarnessUsage | undefined> {
|
|
658
668
|
const eventsAbort = new AbortController();
|
|
659
|
-
const turnSettled = createDeferred<'event' | 'stream-ended'>();
|
|
669
|
+
const turnSettled = createDeferred<'aborted' | 'event' | 'stream-ended'>();
|
|
670
|
+
const promptRequestSettled = createDeferred<void>();
|
|
671
|
+
let abortSessionPromise: Promise<void> | undefined;
|
|
672
|
+
const abortSession = () => {
|
|
673
|
+
eventsAbort.abort();
|
|
674
|
+
turn.experimental_userMessages.close();
|
|
675
|
+
turnSettled.resolve('aborted');
|
|
676
|
+
abortSessionPromise ??= (async () => {
|
|
677
|
+
await promptRequestSettled.promise;
|
|
678
|
+
const aborted = await legacySessionAbort({ client, sessionId });
|
|
679
|
+
if (aborted.error) {
|
|
680
|
+
throw new Error(
|
|
681
|
+
`OpenCode session abort failed: ${formatError(aborted.error)}`,
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
})();
|
|
685
|
+
void abortSessionPromise.catch(() => {});
|
|
686
|
+
};
|
|
687
|
+
turn.abortSignal.addEventListener('abort', abortSession, { once: true });
|
|
688
|
+
if (turn.abortSignal.aborted) abortSession();
|
|
660
689
|
let sawContent = false;
|
|
661
690
|
let sawFinishStep = false;
|
|
662
691
|
let sawBusy = false;
|
|
@@ -800,22 +829,47 @@ async function runPrompt({
|
|
|
800
829
|
}
|
|
801
830
|
}
|
|
802
831
|
})();
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
832
|
+
if (!turn.abortSignal.aborted) {
|
|
833
|
+
let prompted: { error?: unknown; data?: unknown };
|
|
834
|
+
try {
|
|
835
|
+
prompted = await legacySessionPrompt({
|
|
836
|
+
client,
|
|
837
|
+
sessionId,
|
|
838
|
+
start,
|
|
839
|
+
});
|
|
840
|
+
} catch (error) {
|
|
841
|
+
promptRequestSettled.resolve(undefined);
|
|
842
|
+
turn.abortSignal.removeEventListener('abort', abortSession);
|
|
843
|
+
eventsAbort.abort();
|
|
844
|
+
turn.experimental_userMessages.close(error);
|
|
845
|
+
throw error;
|
|
846
|
+
}
|
|
847
|
+
promptRequestSettled.resolve(undefined);
|
|
848
|
+
if (prompted.error) {
|
|
849
|
+
turn.abortSignal.removeEventListener('abort', abortSession);
|
|
850
|
+
eventsAbort.abort();
|
|
851
|
+
turn.experimental_userMessages.close(
|
|
852
|
+
new Error(`OpenCode prompt failed: ${formatError(prompted.error)}`),
|
|
853
|
+
);
|
|
854
|
+
throw new Error(`OpenCode prompt failed: ${formatError(prompted.error)}`);
|
|
855
|
+
}
|
|
856
|
+
} else {
|
|
857
|
+
promptRequestSettled.resolve(undefined);
|
|
814
858
|
}
|
|
815
859
|
const settlement = await turnSettled.promise;
|
|
860
|
+
turn.abortSignal.removeEventListener('abort', abortSession);
|
|
816
861
|
eventsAbort.abort();
|
|
817
862
|
await eventLoop.catch(() => {});
|
|
818
863
|
await userMessageLoop.catch(() => {});
|
|
864
|
+
if (settlement === 'aborted') {
|
|
865
|
+
try {
|
|
866
|
+
await abortSessionPromise;
|
|
867
|
+
} catch (error) {
|
|
868
|
+
closeRuntime();
|
|
869
|
+
throw error;
|
|
870
|
+
}
|
|
871
|
+
return undefined;
|
|
872
|
+
}
|
|
819
873
|
if (settlement === 'stream-ended') {
|
|
820
874
|
throw new Error('OpenCode event stream ended before the turn settled.');
|
|
821
875
|
}
|
package/src/opencode-harness.ts
CHANGED
|
@@ -930,6 +930,7 @@ function createSession({
|
|
|
930
930
|
: undefined;
|
|
931
931
|
let selectedModel: string | undefined;
|
|
932
932
|
let activeTurn = false;
|
|
933
|
+
let pendingTurnDrain: Promise<void> = Promise.resolve();
|
|
933
934
|
const pendingCompactionParts: HarnessV1StreamPart[] = [];
|
|
934
935
|
|
|
935
936
|
channel.on('bridge-thread', msg => {
|
|
@@ -940,6 +941,11 @@ function createSession({
|
|
|
940
941
|
abortSignal?: AbortSignal;
|
|
941
942
|
}): HarnessV1PromptControl => {
|
|
942
943
|
activeTurn = true;
|
|
944
|
+
let resolveTurnDrain!: () => void;
|
|
945
|
+
const turnDrain = new Promise<void>(resolve => {
|
|
946
|
+
resolveTurnDrain = resolve;
|
|
947
|
+
});
|
|
948
|
+
pendingTurnDrain = turnDrain;
|
|
943
949
|
let pendingResolve: (() => void) | undefined;
|
|
944
950
|
let pendingReject: ((err: unknown) => void) | undefined;
|
|
945
951
|
const done = new Promise<void>((resolve, reject) => {
|
|
@@ -955,7 +961,11 @@ function createSession({
|
|
|
955
961
|
: undefined;
|
|
956
962
|
|
|
957
963
|
const unsubs: Array<() => void> = [];
|
|
964
|
+
let isSettled = false;
|
|
965
|
+
let isFinished = false;
|
|
966
|
+
let drainingAfterAbort = false;
|
|
958
967
|
const forward = (event: HarnessV1StreamPart) => {
|
|
968
|
+
if (isSettled) return;
|
|
959
969
|
try {
|
|
960
970
|
turnOpts.emit(event);
|
|
961
971
|
} catch {}
|
|
@@ -977,22 +987,29 @@ function createSession({
|
|
|
977
987
|
'compaction',
|
|
978
988
|
'raw',
|
|
979
989
|
] as const;
|
|
980
|
-
|
|
990
|
+
const finishTurn = () => {
|
|
991
|
+
if (isFinished) return;
|
|
992
|
+
isFinished = true;
|
|
993
|
+
activeTurn = false;
|
|
994
|
+
if (turnOpts.abortSignal) {
|
|
995
|
+
turnOpts.abortSignal.removeEventListener('abort', onAbort);
|
|
996
|
+
}
|
|
997
|
+
for (const u of unsubs) u();
|
|
998
|
+
resolveTurnDrain();
|
|
999
|
+
};
|
|
981
1000
|
const settleSuccess = () => {
|
|
982
1001
|
if (isSettled) return;
|
|
983
1002
|
isSettled = true;
|
|
984
|
-
activeTurn = false;
|
|
985
1003
|
userMessageSubmitter?.close();
|
|
986
|
-
for (const u of unsubs) u();
|
|
987
1004
|
pendingResolve!();
|
|
1005
|
+
finishTurn();
|
|
988
1006
|
};
|
|
989
1007
|
const settleError = (err: unknown) => {
|
|
990
1008
|
if (isSettled) return;
|
|
991
1009
|
isSettled = true;
|
|
992
|
-
activeTurn = false;
|
|
993
1010
|
userMessageSubmitter?.close(err);
|
|
994
|
-
for (const u of unsubs) u();
|
|
995
1011
|
pendingReject!(err);
|
|
1012
|
+
finishTurn();
|
|
996
1013
|
};
|
|
997
1014
|
|
|
998
1015
|
for (const type of eventTypes) {
|
|
@@ -1004,18 +1021,30 @@ function createSession({
|
|
|
1004
1021
|
}
|
|
1005
1022
|
unsubs.push(
|
|
1006
1023
|
channel.on('finish', msg => {
|
|
1024
|
+
if (drainingAfterAbort) {
|
|
1025
|
+
finishTurn();
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1007
1028
|
forward(msg);
|
|
1008
1029
|
settleSuccess();
|
|
1009
1030
|
}),
|
|
1010
1031
|
);
|
|
1011
1032
|
unsubs.push(
|
|
1012
1033
|
channel.on('error', msg => {
|
|
1034
|
+
if (drainingAfterAbort) {
|
|
1035
|
+
// The bridge emits finish from its finally block after an error.
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1013
1038
|
forward(msg);
|
|
1014
1039
|
settleError(msg.error);
|
|
1015
1040
|
}),
|
|
1016
1041
|
);
|
|
1017
1042
|
|
|
1018
1043
|
const onClose = (_code?: number, reason?: string) => {
|
|
1044
|
+
if (drainingAfterAbort) {
|
|
1045
|
+
finishTurn();
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1019
1048
|
if (isSettled) return;
|
|
1020
1049
|
if (reason === 'suspended') {
|
|
1021
1050
|
settleSuccess();
|
|
@@ -1032,10 +1061,13 @@ function createSession({
|
|
|
1032
1061
|
try {
|
|
1033
1062
|
channel.send({ type: 'abort' });
|
|
1034
1063
|
} catch {}
|
|
1035
|
-
|
|
1064
|
+
const error =
|
|
1036
1065
|
turnOpts.abortSignal?.reason ??
|
|
1037
|
-
|
|
1038
|
-
|
|
1066
|
+
new DOMException('Aborted', 'AbortError');
|
|
1067
|
+
isSettled = true;
|
|
1068
|
+
drainingAfterAbort = true;
|
|
1069
|
+
userMessageSubmitter?.close(error);
|
|
1070
|
+
pendingReject!(error);
|
|
1039
1071
|
};
|
|
1040
1072
|
if (turnOpts.abortSignal) {
|
|
1041
1073
|
if (turnOpts.abortSignal.aborted) {
|
|
@@ -1108,6 +1140,7 @@ function createSession({
|
|
|
1108
1140
|
control: HarnessV1PromptControl;
|
|
1109
1141
|
skillWriteResult: WriteSkillsResult;
|
|
1110
1142
|
}> => {
|
|
1143
|
+
await pendingTurnDrain;
|
|
1111
1144
|
if (
|
|
1112
1145
|
opts.responseFormat?.type === 'json' &&
|
|
1113
1146
|
opts.responseFormat.schema == null
|