@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/harness-opencode",
3
- "version": "1.0.136",
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.134",
30
- "@ai-sdk/provider": "4.0.20",
31
- "@ai-sdk/provider-utils": "5.0.52",
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": {
@@ -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
- const prompted = await legacySessionPrompt({
804
- client,
805
- sessionId,
806
- start,
807
- });
808
- if (prompted.error) {
809
- eventsAbort.abort();
810
- turn.experimental_userMessages.close(
811
- new Error(`OpenCode prompt failed: ${formatError(prompted.error)}`),
812
- );
813
- throw new Error(`OpenCode prompt failed: ${formatError(prompted.error)}`);
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
  }
@@ -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
- let isSettled = false;
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
- settleError(
1064
+ const error =
1036
1065
  turnOpts.abortSignal?.reason ??
1037
- new DOMException('Aborted', 'AbortError'),
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