@ai-sdk/harness 1.0.38 → 1.0.39
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",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.39",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@ai-sdk/provider": "4.0.3",
|
|
48
48
|
"@ai-sdk/provider-utils": "5.0.12",
|
|
49
|
-
"ai": "7.0.
|
|
49
|
+
"ai": "7.0.34"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"ws": "^8.21.0",
|
|
@@ -460,6 +460,25 @@ export class HarnessStreamTextResult<
|
|
|
460
460
|
this.fullStreamController.close();
|
|
461
461
|
}
|
|
462
462
|
|
|
463
|
+
/**
|
|
464
|
+
* Settle the turn as user-aborted: emit a final `abort` part — matching
|
|
465
|
+
* `streamText`'s abort contract — and close the stream, instead of
|
|
466
|
+
* surfacing an `error` part. `toUIMessageStream` consumers then observe
|
|
467
|
+
* an `abort` chunk (and `isAborted: true`) rather than a spurious
|
|
468
|
+
* `onError`. The delayed promise accessors still reject with the
|
|
469
|
+
* underlying abort error so awaiting consumers do not hang. Idempotent.
|
|
470
|
+
*/
|
|
471
|
+
abort(input: { error: unknown; reason?: string }): void {
|
|
472
|
+
if (this.settled) return;
|
|
473
|
+
this.settled = true;
|
|
474
|
+
this.fullStreamController.enqueue({
|
|
475
|
+
type: 'abort',
|
|
476
|
+
...(input.reason !== undefined ? { reason: input.reason } : {}),
|
|
477
|
+
} as TextStreamPart<TOOLS>);
|
|
478
|
+
this.fullStreamController.close();
|
|
479
|
+
this.rejectDelayedPromises(input.error);
|
|
480
|
+
}
|
|
481
|
+
|
|
463
482
|
/**
|
|
464
483
|
* Surface a fatal error as a stream `error` part + reject every delayed
|
|
465
484
|
* promise so awaiting consumers stop hanging. Idempotent.
|
|
@@ -472,6 +491,10 @@ export class HarnessStreamTextResult<
|
|
|
472
491
|
error,
|
|
473
492
|
} as TextStreamPart<TOOLS>);
|
|
474
493
|
this.fullStreamController.close();
|
|
494
|
+
this.rejectDelayedPromises(error);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
private rejectDelayedPromises(error: unknown): void {
|
|
475
498
|
for (const dp of [
|
|
476
499
|
this._content,
|
|
477
500
|
this._text,
|
|
@@ -605,6 +628,7 @@ export class HarnessStreamTextResult<
|
|
|
605
628
|
toUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
606
629
|
originalMessages,
|
|
607
630
|
generateMessageId,
|
|
631
|
+
onEnd,
|
|
608
632
|
onFinish,
|
|
609
633
|
messageMetadata,
|
|
610
634
|
sendReasoning,
|
|
@@ -621,6 +645,7 @@ export class HarnessStreamTextResult<
|
|
|
621
645
|
tools: this.tools,
|
|
622
646
|
originalMessages,
|
|
623
647
|
generateMessageId,
|
|
648
|
+
onEnd,
|
|
624
649
|
onFinish,
|
|
625
650
|
messageMetadata,
|
|
626
651
|
sendReasoning,
|
|
@@ -643,6 +668,7 @@ export class HarnessStreamTextResult<
|
|
|
643
668
|
toUIMessageStreamResponse<UI_MESSAGE extends UIMessage>({
|
|
644
669
|
originalMessages,
|
|
645
670
|
generateMessageId,
|
|
671
|
+
onEnd,
|
|
646
672
|
onFinish,
|
|
647
673
|
messageMetadata,
|
|
648
674
|
sendReasoning,
|
|
@@ -660,6 +686,7 @@ export class HarnessStreamTextResult<
|
|
|
660
686
|
stream: this.toUIMessageStream<UI_MESSAGE>({
|
|
661
687
|
originalMessages,
|
|
662
688
|
generateMessageId,
|
|
689
|
+
onEnd,
|
|
663
690
|
onFinish,
|
|
664
691
|
messageMetadata,
|
|
665
692
|
sendReasoning,
|
|
@@ -21,10 +21,11 @@ import {
|
|
|
21
21
|
type Experimental_SandboxSession as SandboxSession,
|
|
22
22
|
type ToolSet,
|
|
23
23
|
} from '@ai-sdk/provider-utils';
|
|
24
|
-
import
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
import {
|
|
25
|
+
getErrorMessage,
|
|
26
|
+
type LanguageModelV4FinishReason,
|
|
27
|
+
type LanguageModelV4ToolCall,
|
|
28
|
+
type LanguageModelV4Usage,
|
|
28
29
|
} from '@ai-sdk/provider';
|
|
29
30
|
import { parseToolCall } from 'ai/internal';
|
|
30
31
|
import type {
|
|
@@ -124,6 +125,29 @@ export function runPrompt<
|
|
|
124
125
|
runtimeContext: input.runtimeContext,
|
|
125
126
|
});
|
|
126
127
|
|
|
128
|
+
/*
|
|
129
|
+
* Settle a failed turn. When the caller's own `abortSignal` has fired, the
|
|
130
|
+
* failure is a user-initiated stop, not an error: surface it as an `abort`
|
|
131
|
+
* stream part — matching `streamText`'s abort contract — so
|
|
132
|
+
* `toUIMessageStream` consumers observe an `abort` chunk and
|
|
133
|
+
* `isAborted: true` instead of a spurious `onError`. Every other failure
|
|
134
|
+
* stays a real `error` part. Both outcomes notify `onTurnFailed` so the
|
|
135
|
+
* session's turn tracking returns to idle and the session stays usable.
|
|
136
|
+
*/
|
|
137
|
+
const settleFailure = (err: unknown) => {
|
|
138
|
+
input.onTurnFailed?.();
|
|
139
|
+
if (input.abortSignal?.aborted) {
|
|
140
|
+
result.abort({
|
|
141
|
+
error: err,
|
|
142
|
+
...(input.abortSignal.reason !== undefined
|
|
143
|
+
? { reason: getErrorMessage(input.abortSignal.reason) }
|
|
144
|
+
: {}),
|
|
145
|
+
});
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
result.fail(err);
|
|
149
|
+
};
|
|
150
|
+
|
|
127
151
|
const done = (async () => {
|
|
128
152
|
let bridge: Awaited<ReturnType<typeof toHarnessStream>>;
|
|
129
153
|
try {
|
|
@@ -159,8 +183,7 @@ export function runPrompt<
|
|
|
159
183
|
context: 'failed to start harness turn',
|
|
160
184
|
error: err,
|
|
161
185
|
});
|
|
162
|
-
|
|
163
|
-
result.fail(err);
|
|
186
|
+
settleFailure(err);
|
|
164
187
|
return;
|
|
165
188
|
}
|
|
166
189
|
|
|
@@ -531,6 +554,28 @@ export function runPrompt<
|
|
|
531
554
|
}
|
|
532
555
|
}
|
|
533
556
|
|
|
557
|
+
/*
|
|
558
|
+
* Settle failures before the translate-and-forward step below: the
|
|
559
|
+
* translated `error` part must not reach the consumer stream, or the
|
|
560
|
+
* turn surfaces BOTH a forwarded `error` part and the settle-owned
|
|
561
|
+
* terminal part (an `abort` part for user stops via `settleFailure`,
|
|
562
|
+
* or a second `error` part from `fail`).
|
|
563
|
+
*/
|
|
564
|
+
if (value.type === 'error' && displayValue.type === 'error') {
|
|
565
|
+
// Telemetry and stderr diagnostics keep the raw error (absolute
|
|
566
|
+
// paths help debugging); the consumer-facing settle uses the
|
|
567
|
+
// workDir-stripped one, like every other forwarded part.
|
|
568
|
+
telemetry.error(value.error);
|
|
569
|
+
logBridgeError({
|
|
570
|
+
harnessId: input.harness.harnessId,
|
|
571
|
+
sessionId: input.session.sessionId,
|
|
572
|
+
context: 'harness stream error',
|
|
573
|
+
error: value.error,
|
|
574
|
+
});
|
|
575
|
+
settleFailure(displayValue.error);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
|
|
534
579
|
// Forward to consumer as soon as possible.
|
|
535
580
|
for (const part of translateStreamPart<TOOLS>(displayValue)) {
|
|
536
581
|
result.enqueue(part);
|
|
@@ -799,19 +844,6 @@ export function runPrompt<
|
|
|
799
844
|
}
|
|
800
845
|
telemetry.toolEnd(toolCall.toolCallId, execution.outcome);
|
|
801
846
|
}
|
|
802
|
-
|
|
803
|
-
if (value.type === 'error') {
|
|
804
|
-
telemetry.error(value.error);
|
|
805
|
-
logBridgeError({
|
|
806
|
-
harnessId: input.harness.harnessId,
|
|
807
|
-
sessionId: input.session.sessionId,
|
|
808
|
-
context: 'harness stream error',
|
|
809
|
-
error: value.error,
|
|
810
|
-
});
|
|
811
|
-
input.onTurnFailed?.();
|
|
812
|
-
result.fail(value.error);
|
|
813
|
-
return;
|
|
814
|
-
}
|
|
815
847
|
}
|
|
816
848
|
if (finalFinish != null) {
|
|
817
849
|
input.onTurnFinished?.();
|
|
@@ -835,8 +867,7 @@ export function runPrompt<
|
|
|
835
867
|
context: 'harness turn failed',
|
|
836
868
|
error: err,
|
|
837
869
|
});
|
|
838
|
-
|
|
839
|
-
result.fail(err);
|
|
870
|
+
settleFailure(err);
|
|
840
871
|
} finally {
|
|
841
872
|
reader.releaseLock();
|
|
842
873
|
}
|