@ai-sdk/harness 1.0.71 → 1.0.73
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 +18 -0
- package/README.md +23 -0
- package/dist/agent/index.d.ts +148 -23
- package/dist/agent/index.js +185 -37
- package/dist/agent/index.js.map +1 -1
- package/dist/bridge/index.js +14 -20
- package/dist/bridge/index.js.map +1 -1
- package/dist/index.d.ts +124 -10
- package/dist/index.js +12 -1
- package/dist/index.js.map +1 -1
- package/dist/utils/index.d.ts +59 -7
- package/dist/utils/index.js +47 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +3 -3
- package/src/agent/harness-agent-session.ts +27 -7
- package/src/agent/harness-agent-settings.ts +8 -0
- package/src/agent/harness-agent.ts +74 -23
- package/src/agent/internal/harness-stream-text-result.ts +251 -59
- package/src/agent/internal/run-prompt.ts +10 -2
- package/src/agent/prepare-sandbox-for-harness.ts +8 -18
- package/src/bridge/index.ts +41 -37
- package/src/errors/harness-capability-unsupported-error.ts +2 -2
- package/src/utils/index.ts +5 -0
- package/src/utils/sandbox-credential-brokering.ts +41 -0
- package/src/v1/harness-v1-bridge-protocol.ts +13 -0
- package/src/v1/harness-v1-network-sandbox-session.ts +87 -5
- package/src/v1/harness-v1-response-format.ts +32 -0
- package/src/v1/harness-v1-session.ts +17 -2
- package/src/v1/index.ts +10 -0
|
@@ -24,9 +24,11 @@ import {
|
|
|
24
24
|
type CallWarning,
|
|
25
25
|
type ContentPart,
|
|
26
26
|
type FinishReason,
|
|
27
|
-
type GenerateTextResult,
|
|
28
27
|
type InferUIMessageChunk,
|
|
28
|
+
type InferGenerateOutput,
|
|
29
|
+
type InferStreamOutput,
|
|
29
30
|
type LanguageModelUsage,
|
|
31
|
+
type OutputInterface as Output,
|
|
30
32
|
type ProviderMetadata,
|
|
31
33
|
type StepResult,
|
|
32
34
|
type StreamTextResult,
|
|
@@ -38,8 +40,17 @@ import {
|
|
|
38
40
|
type StreamProp<
|
|
39
41
|
TOOLS extends ToolSet,
|
|
40
42
|
RUNTIME_CONTEXT extends Context,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
OUTPUT extends Output,
|
|
44
|
+
KEY extends keyof StreamTextResult<TOOLS, RUNTIME_CONTEXT, OUTPUT>,
|
|
45
|
+
> = Awaited<StreamTextResult<TOOLS, RUNTIME_CONTEXT, OUTPUT>[KEY]>;
|
|
46
|
+
|
|
47
|
+
type InferElementOutput<OUTPUT extends Output> =
|
|
48
|
+
OUTPUT extends Output<any, any, infer ELEMENT> ? ELEMENT : never;
|
|
49
|
+
|
|
50
|
+
type EnrichedStreamPart<TOOLS extends ToolSet, PARTIAL_OUTPUT> = {
|
|
51
|
+
part: TextStreamPart<TOOLS>;
|
|
52
|
+
partialOutput: PARTIAL_OUTPUT | undefined;
|
|
53
|
+
};
|
|
43
54
|
|
|
44
55
|
/**
|
|
45
56
|
* Concrete `StreamTextResult` implementation backed by a single
|
|
@@ -57,68 +68,68 @@ type StreamProp<
|
|
|
57
68
|
* `text`, `toolCalls`, `toolResults`, `reasoning`, etc. via its getters.
|
|
58
69
|
*
|
|
59
70
|
* The Node.js response helpers (`pipeUIMessageStreamToResponse`,
|
|
60
|
-
* `pipeTextStreamToResponse`, `toTextStreamResponse`)
|
|
61
|
-
*
|
|
62
|
-
* implemented yet — they throw a clear error.
|
|
71
|
+
* `pipeTextStreamToResponse`, `toTextStreamResponse`) are not implemented yet
|
|
72
|
+
* and throw a clear error.
|
|
63
73
|
*/
|
|
64
74
|
export class HarnessStreamTextResult<
|
|
65
75
|
TOOLS extends ToolSet,
|
|
66
76
|
RUNTIME_CONTEXT extends Context,
|
|
67
|
-
|
|
77
|
+
OUTPUT extends Output,
|
|
78
|
+
> implements StreamTextResult<TOOLS, RUNTIME_CONTEXT, OUTPUT> {
|
|
68
79
|
// Delayed promises backing every PromiseLike accessor. Each is typed
|
|
69
80
|
// against the corresponding `StreamTextResult` property so the public
|
|
70
81
|
// surface stays in lockstep with AI SDK's interface as it evolves.
|
|
71
82
|
private readonly _content = new DelayedPromise<
|
|
72
|
-
StreamProp<TOOLS, RUNTIME_CONTEXT, 'content'>
|
|
83
|
+
StreamProp<TOOLS, RUNTIME_CONTEXT, OUTPUT, 'content'>
|
|
73
84
|
>();
|
|
74
85
|
private readonly _text = new DelayedPromise<
|
|
75
|
-
StreamProp<TOOLS, RUNTIME_CONTEXT, 'text'>
|
|
86
|
+
StreamProp<TOOLS, RUNTIME_CONTEXT, OUTPUT, 'text'>
|
|
76
87
|
>();
|
|
77
88
|
private readonly _reasoning = new DelayedPromise<
|
|
78
|
-
StreamProp<TOOLS, RUNTIME_CONTEXT, 'reasoning'>
|
|
89
|
+
StreamProp<TOOLS, RUNTIME_CONTEXT, OUTPUT, 'reasoning'>
|
|
79
90
|
>();
|
|
80
91
|
private readonly _reasoningText = new DelayedPromise<
|
|
81
|
-
StreamProp<TOOLS, RUNTIME_CONTEXT, 'reasoningText'>
|
|
92
|
+
StreamProp<TOOLS, RUNTIME_CONTEXT, OUTPUT, 'reasoningText'>
|
|
82
93
|
>();
|
|
83
94
|
private readonly _files = new DelayedPromise<
|
|
84
|
-
StreamProp<TOOLS, RUNTIME_CONTEXT, 'files'>
|
|
95
|
+
StreamProp<TOOLS, RUNTIME_CONTEXT, OUTPUT, 'files'>
|
|
85
96
|
>();
|
|
86
97
|
private readonly _sources = new DelayedPromise<
|
|
87
|
-
StreamProp<TOOLS, RUNTIME_CONTEXT, 'sources'>
|
|
98
|
+
StreamProp<TOOLS, RUNTIME_CONTEXT, OUTPUT, 'sources'>
|
|
88
99
|
>();
|
|
89
100
|
private readonly _toolCalls = new DelayedPromise<
|
|
90
|
-
StreamProp<TOOLS, RUNTIME_CONTEXT, 'toolCalls'>
|
|
101
|
+
StreamProp<TOOLS, RUNTIME_CONTEXT, OUTPUT, 'toolCalls'>
|
|
91
102
|
>();
|
|
92
103
|
private readonly _staticToolCalls = new DelayedPromise<
|
|
93
|
-
StreamProp<TOOLS, RUNTIME_CONTEXT, 'staticToolCalls'>
|
|
104
|
+
StreamProp<TOOLS, RUNTIME_CONTEXT, OUTPUT, 'staticToolCalls'>
|
|
94
105
|
>();
|
|
95
106
|
private readonly _dynamicToolCalls = new DelayedPromise<
|
|
96
|
-
StreamProp<TOOLS, RUNTIME_CONTEXT, 'dynamicToolCalls'>
|
|
107
|
+
StreamProp<TOOLS, RUNTIME_CONTEXT, OUTPUT, 'dynamicToolCalls'>
|
|
97
108
|
>();
|
|
98
109
|
private readonly _toolResults = new DelayedPromise<
|
|
99
|
-
StreamProp<TOOLS, RUNTIME_CONTEXT, 'toolResults'>
|
|
110
|
+
StreamProp<TOOLS, RUNTIME_CONTEXT, OUTPUT, 'toolResults'>
|
|
100
111
|
>();
|
|
101
112
|
private readonly _staticToolResults = new DelayedPromise<
|
|
102
|
-
StreamProp<TOOLS, RUNTIME_CONTEXT, 'staticToolResults'>
|
|
113
|
+
StreamProp<TOOLS, RUNTIME_CONTEXT, OUTPUT, 'staticToolResults'>
|
|
103
114
|
>();
|
|
104
115
|
private readonly _dynamicToolResults = new DelayedPromise<
|
|
105
|
-
StreamProp<TOOLS, RUNTIME_CONTEXT, 'dynamicToolResults'>
|
|
116
|
+
StreamProp<TOOLS, RUNTIME_CONTEXT, OUTPUT, 'dynamicToolResults'>
|
|
106
117
|
>();
|
|
107
118
|
private readonly _finishReason = new DelayedPromise<FinishReason>();
|
|
108
119
|
private readonly _rawFinishReason = new DelayedPromise<string | undefined>();
|
|
109
120
|
private readonly _usage = new DelayedPromise<LanguageModelUsage>();
|
|
110
121
|
private readonly _warnings = new DelayedPromise<CallWarning[] | undefined>();
|
|
111
122
|
private readonly _steps = new DelayedPromise<
|
|
112
|
-
StreamProp<TOOLS, RUNTIME_CONTEXT, 'steps'>
|
|
123
|
+
StreamProp<TOOLS, RUNTIME_CONTEXT, OUTPUT, 'steps'>
|
|
113
124
|
>();
|
|
114
125
|
private readonly _finalStep = new DelayedPromise<
|
|
115
|
-
StreamProp<TOOLS, RUNTIME_CONTEXT, 'finalStep'>
|
|
126
|
+
StreamProp<TOOLS, RUNTIME_CONTEXT, OUTPUT, 'finalStep'>
|
|
116
127
|
>();
|
|
117
128
|
private readonly _request = new DelayedPromise<
|
|
118
|
-
StreamProp<TOOLS, RUNTIME_CONTEXT, 'request'>
|
|
129
|
+
StreamProp<TOOLS, RUNTIME_CONTEXT, OUTPUT, 'request'>
|
|
119
130
|
>();
|
|
120
131
|
private readonly _response = new DelayedPromise<
|
|
121
|
-
StreamProp<TOOLS, RUNTIME_CONTEXT, 'response'>
|
|
132
|
+
StreamProp<TOOLS, RUNTIME_CONTEXT, OUTPUT, 'response'>
|
|
122
133
|
>();
|
|
123
134
|
private readonly _responseMessages = new DelayedPromise<
|
|
124
135
|
Array<AssistantModelMessage | ToolModelMessage>
|
|
@@ -131,11 +142,9 @@ export class HarnessStreamTextResult<
|
|
|
131
142
|
private readonly fullStreamController: ReadableStreamDefaultController<
|
|
132
143
|
TextStreamPart<TOOLS>
|
|
133
144
|
>;
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
readonly fullStream: AsyncIterableStream<TextStreamPart<TOOLS>>;
|
|
138
|
-
readonly textStream: AsyncIterableStream<string>;
|
|
145
|
+
private baseStream: ReadableStream<
|
|
146
|
+
EnrichedStreamPart<TOOLS, InferStreamOutput<OUTPUT>>
|
|
147
|
+
>;
|
|
139
148
|
|
|
140
149
|
private readonly stepsBuffer: StepResult<TOOLS, RUNTIME_CONTEXT>[] = [];
|
|
141
150
|
private currentStepContent: ContentPart<TOOLS>[] = [];
|
|
@@ -148,6 +157,7 @@ export class HarnessStreamTextResult<
|
|
|
148
157
|
private readonly toolsContext: never;
|
|
149
158
|
private readonly providerName: string;
|
|
150
159
|
private readonly modelId: string;
|
|
160
|
+
private readonly outputSpecification: OUTPUT | undefined;
|
|
151
161
|
|
|
152
162
|
// Accumulators that span the whole turn.
|
|
153
163
|
private accumulatedUsage: LanguageModelUsage = createNullLanguageModelUsage();
|
|
@@ -163,12 +173,14 @@ export class HarnessStreamTextResult<
|
|
|
163
173
|
toolsContext: never;
|
|
164
174
|
harnessId: string;
|
|
165
175
|
sessionId: string;
|
|
176
|
+
output: OUTPUT | undefined;
|
|
166
177
|
}) {
|
|
167
178
|
this.tools = options.tools;
|
|
168
179
|
this.runtimeContext = options.runtimeContext;
|
|
169
180
|
this.toolsContext = options.toolsContext;
|
|
170
181
|
this.providerName = `harness:${options.harnessId}`;
|
|
171
182
|
this.modelId = options.sessionId;
|
|
183
|
+
this.outputSpecification = options.output;
|
|
172
184
|
|
|
173
185
|
let controllerRef!: ReadableStreamDefaultController<TextStreamPart<TOOLS>>;
|
|
174
186
|
const baseStream = new ReadableStream<TextStreamPart<TOOLS>>({
|
|
@@ -185,18 +197,19 @@ export class HarnessStreamTextResult<
|
|
|
185
197
|
});
|
|
186
198
|
this.fullStreamController = controllerRef;
|
|
187
199
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
+
this.baseStream =
|
|
201
|
+
options.output == null
|
|
202
|
+
? baseStream.pipeThrough(
|
|
203
|
+
new TransformStream<
|
|
204
|
+
TextStreamPart<TOOLS>,
|
|
205
|
+
EnrichedStreamPart<TOOLS, InferStreamOutput<OUTPUT>>
|
|
206
|
+
>({
|
|
207
|
+
transform(part, controller) {
|
|
208
|
+
controller.enqueue({ part, partialOutput: undefined });
|
|
209
|
+
},
|
|
210
|
+
}),
|
|
211
|
+
)
|
|
212
|
+
: baseStream.pipeThrough(createOutputTransformStream(options.output));
|
|
200
213
|
}
|
|
201
214
|
|
|
202
215
|
// ─── Writer-side methods used by the driver ────────────────────────
|
|
@@ -375,7 +388,12 @@ export class HarnessStreamTextResult<
|
|
|
375
388
|
const aggregatedContent = this.stepsBuffer.flatMap(s => s.content);
|
|
376
389
|
|
|
377
390
|
this._content.resolve(
|
|
378
|
-
aggregatedContent as StreamProp<
|
|
391
|
+
aggregatedContent as StreamProp<
|
|
392
|
+
TOOLS,
|
|
393
|
+
RUNTIME_CONTEXT,
|
|
394
|
+
OUTPUT,
|
|
395
|
+
'content'
|
|
396
|
+
>,
|
|
379
397
|
);
|
|
380
398
|
this._text.resolve(finalStep.text);
|
|
381
399
|
// Reasoning content parts are not yet derived from harness events; the
|
|
@@ -383,7 +401,7 @@ export class HarnessStreamTextResult<
|
|
|
383
401
|
// deltas can be wired up to produce real reasoning content in a later
|
|
384
402
|
// pass.
|
|
385
403
|
this._reasoning.resolve(
|
|
386
|
-
[] as StreamProp<TOOLS, RUNTIME_CONTEXT, 'reasoning'>,
|
|
404
|
+
[] as StreamProp<TOOLS, RUNTIME_CONTEXT, OUTPUT, 'reasoning'>,
|
|
387
405
|
);
|
|
388
406
|
this._reasoningText.resolve(undefined);
|
|
389
407
|
this._files.resolve(this.stepsBuffer.flatMap(s => s.files));
|
|
@@ -392,6 +410,7 @@ export class HarnessStreamTextResult<
|
|
|
392
410
|
this.stepsBuffer.flatMap(s => s.toolCalls) as StreamProp<
|
|
393
411
|
TOOLS,
|
|
394
412
|
RUNTIME_CONTEXT,
|
|
413
|
+
OUTPUT,
|
|
395
414
|
'toolCalls'
|
|
396
415
|
>,
|
|
397
416
|
);
|
|
@@ -399,6 +418,7 @@ export class HarnessStreamTextResult<
|
|
|
399
418
|
this.stepsBuffer.flatMap(s => s.staticToolCalls) as StreamProp<
|
|
400
419
|
TOOLS,
|
|
401
420
|
RUNTIME_CONTEXT,
|
|
421
|
+
OUTPUT,
|
|
402
422
|
'staticToolCalls'
|
|
403
423
|
>,
|
|
404
424
|
);
|
|
@@ -406,6 +426,7 @@ export class HarnessStreamTextResult<
|
|
|
406
426
|
this.stepsBuffer.flatMap(s => s.dynamicToolCalls) as StreamProp<
|
|
407
427
|
TOOLS,
|
|
408
428
|
RUNTIME_CONTEXT,
|
|
429
|
+
OUTPUT,
|
|
409
430
|
'dynamicToolCalls'
|
|
410
431
|
>,
|
|
411
432
|
);
|
|
@@ -413,6 +434,7 @@ export class HarnessStreamTextResult<
|
|
|
413
434
|
this.stepsBuffer.flatMap(s => s.toolResults) as StreamProp<
|
|
414
435
|
TOOLS,
|
|
415
436
|
RUNTIME_CONTEXT,
|
|
437
|
+
OUTPUT,
|
|
416
438
|
'toolResults'
|
|
417
439
|
>,
|
|
418
440
|
);
|
|
@@ -420,6 +442,7 @@ export class HarnessStreamTextResult<
|
|
|
420
442
|
this.stepsBuffer.flatMap(s => s.staticToolResults) as StreamProp<
|
|
421
443
|
TOOLS,
|
|
422
444
|
RUNTIME_CONTEXT,
|
|
445
|
+
OUTPUT,
|
|
423
446
|
'staticToolResults'
|
|
424
447
|
>,
|
|
425
448
|
);
|
|
@@ -427,6 +450,7 @@ export class HarnessStreamTextResult<
|
|
|
427
450
|
this.stepsBuffer.flatMap(s => s.dynamicToolResults) as StreamProp<
|
|
428
451
|
TOOLS,
|
|
429
452
|
RUNTIME_CONTEXT,
|
|
453
|
+
OUTPUT,
|
|
430
454
|
'dynamicToolResults'
|
|
431
455
|
>,
|
|
432
456
|
);
|
|
@@ -529,6 +553,47 @@ export class HarnessStreamTextResult<
|
|
|
529
553
|
|
|
530
554
|
// ─── Reader-side public surface (StreamTextResult contract) ────────
|
|
531
555
|
|
|
556
|
+
private teeStream(): ReadableStream<
|
|
557
|
+
EnrichedStreamPart<TOOLS, InferStreamOutput<OUTPUT>>
|
|
558
|
+
> {
|
|
559
|
+
const [stream, remainingStream] = this.baseStream.tee();
|
|
560
|
+
this.baseStream = remainingStream;
|
|
561
|
+
return stream;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
get stream(): AsyncIterableStream<TextStreamPart<TOOLS>> {
|
|
565
|
+
return createAsyncIterableStream(
|
|
566
|
+
this.teeStream().pipeThrough(
|
|
567
|
+
new TransformStream<
|
|
568
|
+
EnrichedStreamPart<TOOLS, InferStreamOutput<OUTPUT>>,
|
|
569
|
+
TextStreamPart<TOOLS>
|
|
570
|
+
>({
|
|
571
|
+
transform({ part }, controller) {
|
|
572
|
+
controller.enqueue(part);
|
|
573
|
+
},
|
|
574
|
+
}),
|
|
575
|
+
),
|
|
576
|
+
) as AsyncIterableStream<TextStreamPart<TOOLS>>;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
get fullStream(): AsyncIterableStream<TextStreamPart<TOOLS>> {
|
|
580
|
+
return this.stream;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
get textStream(): AsyncIterableStream<string> {
|
|
584
|
+
return createAsyncIterableStream(
|
|
585
|
+
this.stream.pipeThrough(
|
|
586
|
+
new TransformStream<TextStreamPart<TOOLS>, string>({
|
|
587
|
+
transform(part, controller) {
|
|
588
|
+
if (part.type === 'text-delta') {
|
|
589
|
+
controller.enqueue(part.text);
|
|
590
|
+
}
|
|
591
|
+
},
|
|
592
|
+
}),
|
|
593
|
+
),
|
|
594
|
+
) as AsyncIterableStream<string>;
|
|
595
|
+
}
|
|
596
|
+
|
|
532
597
|
get content() {
|
|
533
598
|
return this._content.promise;
|
|
534
599
|
}
|
|
@@ -599,18 +664,55 @@ export class HarnessStreamTextResult<
|
|
|
599
664
|
return this._providerMetadata.promise;
|
|
600
665
|
}
|
|
601
666
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
get partialOutputStream(): never {
|
|
607
|
-
throw notSupportedYet('partial output stream');
|
|
667
|
+
get experimental_partialOutputStream(): AsyncIterableStream<
|
|
668
|
+
InferStreamOutput<OUTPUT>
|
|
669
|
+
> {
|
|
670
|
+
return this.partialOutputStream;
|
|
608
671
|
}
|
|
609
|
-
|
|
610
|
-
|
|
672
|
+
|
|
673
|
+
get partialOutputStream(): AsyncIterableStream<InferStreamOutput<OUTPUT>> {
|
|
674
|
+
return createAsyncIterableStream(
|
|
675
|
+
this.teeStream().pipeThrough(
|
|
676
|
+
new TransformStream<
|
|
677
|
+
EnrichedStreamPart<TOOLS, InferStreamOutput<OUTPUT>>,
|
|
678
|
+
InferStreamOutput<OUTPUT>
|
|
679
|
+
>({
|
|
680
|
+
transform({ partialOutput }, controller) {
|
|
681
|
+
if (partialOutput != null) {
|
|
682
|
+
controller.enqueue(partialOutput);
|
|
683
|
+
}
|
|
684
|
+
},
|
|
685
|
+
}),
|
|
686
|
+
),
|
|
687
|
+
) as AsyncIterableStream<InferStreamOutput<OUTPUT>>;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
get elementStream(): AsyncIterableStream<InferElementOutput<OUTPUT>> {
|
|
691
|
+
const transform = this.outputSpecification?.createElementStreamTransform();
|
|
692
|
+
if (transform == null) {
|
|
693
|
+
throw notSupportedYet(
|
|
694
|
+
`element streams in ${this.outputSpecification?.name ?? 'text'} mode`,
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
return createAsyncIterableStream(
|
|
698
|
+
this.teeStream().pipeThrough(transform),
|
|
699
|
+
) as AsyncIterableStream<InferElementOutput<OUTPUT>>;
|
|
611
700
|
}
|
|
612
|
-
|
|
613
|
-
|
|
701
|
+
|
|
702
|
+
get output(): Promise<InferGenerateOutput<OUTPUT>> {
|
|
703
|
+
return this.finalStep.then(step => {
|
|
704
|
+
if (this.outputSpecification == null) {
|
|
705
|
+
throw notSupportedYet('structured output');
|
|
706
|
+
}
|
|
707
|
+
return this.outputSpecification.parseCompleteOutput(
|
|
708
|
+
{ text: step.text },
|
|
709
|
+
{
|
|
710
|
+
response: step.response,
|
|
711
|
+
usage: step.usage,
|
|
712
|
+
finishReason: step.finishReason,
|
|
713
|
+
},
|
|
714
|
+
);
|
|
715
|
+
});
|
|
614
716
|
}
|
|
615
717
|
|
|
616
718
|
async consumeStream(): Promise<void> {
|
|
@@ -750,6 +852,103 @@ export class HarnessStreamTextResult<
|
|
|
750
852
|
}
|
|
751
853
|
}
|
|
752
854
|
|
|
855
|
+
function createOutputTransformStream<
|
|
856
|
+
TOOLS extends ToolSet,
|
|
857
|
+
OUTPUT extends Output,
|
|
858
|
+
>(
|
|
859
|
+
output: OUTPUT,
|
|
860
|
+
): TransformStream<
|
|
861
|
+
TextStreamPart<TOOLS>,
|
|
862
|
+
EnrichedStreamPart<TOOLS, InferStreamOutput<OUTPUT>>
|
|
863
|
+
> {
|
|
864
|
+
let firstTextChunkId: string | undefined;
|
|
865
|
+
let text = '';
|
|
866
|
+
let textChunk = '';
|
|
867
|
+
let textProviderMetadata: ProviderMetadata | undefined;
|
|
868
|
+
let lastPublishedValue = '';
|
|
869
|
+
|
|
870
|
+
const publishTextChunk = (options: {
|
|
871
|
+
controller: TransformStreamDefaultController<
|
|
872
|
+
EnrichedStreamPart<TOOLS, InferStreamOutput<OUTPUT>>
|
|
873
|
+
>;
|
|
874
|
+
partialOutput?: InferStreamOutput<OUTPUT>;
|
|
875
|
+
}): void => {
|
|
876
|
+
options.controller.enqueue({
|
|
877
|
+
part: {
|
|
878
|
+
type: 'text-delta',
|
|
879
|
+
id: firstTextChunkId!,
|
|
880
|
+
text: textChunk,
|
|
881
|
+
providerMetadata: textProviderMetadata,
|
|
882
|
+
},
|
|
883
|
+
partialOutput: options.partialOutput,
|
|
884
|
+
});
|
|
885
|
+
textChunk = '';
|
|
886
|
+
};
|
|
887
|
+
|
|
888
|
+
return new TransformStream({
|
|
889
|
+
async transform(chunk, controller) {
|
|
890
|
+
if (chunk.type === 'finish-step' && textChunk.length > 0) {
|
|
891
|
+
publishTextChunk({ controller });
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
if (
|
|
895
|
+
chunk.type !== 'text-delta' &&
|
|
896
|
+
chunk.type !== 'text-start' &&
|
|
897
|
+
chunk.type !== 'text-end'
|
|
898
|
+
) {
|
|
899
|
+
controller.enqueue({ part: chunk, partialOutput: undefined });
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
if (firstTextChunkId == null) {
|
|
904
|
+
firstTextChunkId = chunk.id;
|
|
905
|
+
} else if (chunk.id !== firstTextChunkId) {
|
|
906
|
+
controller.enqueue({ part: chunk, partialOutput: undefined });
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
if (chunk.type === 'text-start') {
|
|
911
|
+
controller.enqueue({ part: chunk, partialOutput: undefined });
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
if (chunk.type === 'text-end') {
|
|
916
|
+
if (textChunk.length > 0) {
|
|
917
|
+
publishTextChunk({ controller });
|
|
918
|
+
}
|
|
919
|
+
controller.enqueue({ part: chunk, partialOutput: undefined });
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
text += chunk.text;
|
|
924
|
+
textChunk += chunk.text;
|
|
925
|
+
textProviderMetadata = chunk.providerMetadata ?? textProviderMetadata;
|
|
926
|
+
|
|
927
|
+
if (chunk.text.length === 0 && chunk.providerMetadata != null) {
|
|
928
|
+
controller.enqueue({ part: chunk, partialOutput: undefined });
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
const result = await output.parsePartialOutput({ text });
|
|
933
|
+
if (result === undefined) {
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
const currentValue =
|
|
938
|
+
typeof result.partial === 'string'
|
|
939
|
+
? result.partial
|
|
940
|
+
: JSON.stringify(result.partial);
|
|
941
|
+
if (currentValue !== lastPublishedValue) {
|
|
942
|
+
publishTextChunk({
|
|
943
|
+
controller,
|
|
944
|
+
partialOutput: result.partial,
|
|
945
|
+
});
|
|
946
|
+
lastPublishedValue = currentValue;
|
|
947
|
+
}
|
|
948
|
+
},
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
|
|
753
952
|
function createEmptyPerformance(): StepResult<ToolSet, Context>['performance'] {
|
|
754
953
|
return {
|
|
755
954
|
effectiveOutputTokensPerSecond: 0,
|
|
@@ -774,10 +973,3 @@ function notSupportedYet(feature: string): Error {
|
|
|
774
973
|
// async-iterator contract in modern runtimes; we expose them under the same
|
|
775
974
|
// nominal type AI SDK does.
|
|
776
975
|
type AsyncIterableStream<T> = ReadableStream<T> & AsyncIterable<T>;
|
|
777
|
-
|
|
778
|
-
// `GenerateTextResult` is re-exported only so downstream code can keep this
|
|
779
|
-
// file as the single source of return-shape constants; not otherwise used here.
|
|
780
|
-
export type _GenerateTextResultMarker<
|
|
781
|
-
TOOLS extends ToolSet,
|
|
782
|
-
RUNTIME_CONTEXT extends Context,
|
|
783
|
-
> = GenerateTextResult<TOOLS, RUNTIME_CONTEXT, never>;
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
type HarnessV1PendingToolResult,
|
|
8
8
|
type HarnessV1Prompt,
|
|
9
9
|
type HarnessV1PromptControl,
|
|
10
|
+
type HarnessV1ResponseFormat,
|
|
10
11
|
type HarnessV1Session,
|
|
11
12
|
type HarnessV1StreamPart,
|
|
12
13
|
type HarnessV1ToolSpec,
|
|
@@ -30,6 +31,7 @@ import {
|
|
|
30
31
|
import { parseToolCall } from 'ai/internal';
|
|
31
32
|
import type {
|
|
32
33
|
ContentPart,
|
|
34
|
+
OutputInterface as Output,
|
|
33
35
|
ProviderMetadata,
|
|
34
36
|
StepResult,
|
|
35
37
|
StopCondition,
|
|
@@ -66,6 +68,7 @@ import { pinSandboxChannelEventCheckpoint } from '../../utils/sandbox-channel';
|
|
|
66
68
|
export function runPrompt<
|
|
67
69
|
TOOLS extends ToolSet,
|
|
68
70
|
RUNTIME_CONTEXT extends Context,
|
|
71
|
+
OUTPUT extends Output,
|
|
69
72
|
>(input: {
|
|
70
73
|
harness: HarnessV1;
|
|
71
74
|
session: HarnessV1Session;
|
|
@@ -86,6 +89,8 @@ export function runPrompt<
|
|
|
86
89
|
sessionWorkDir: string;
|
|
87
90
|
runtimeContext: RUNTIME_CONTEXT;
|
|
88
91
|
abortSignal: AbortSignal | undefined;
|
|
92
|
+
responseFormat?: HarnessV1ResponseFormat | undefined;
|
|
93
|
+
output?: OUTPUT | undefined;
|
|
89
94
|
telemetry?: TelemetryOptions | undefined;
|
|
90
95
|
stopConditions?: ReadonlyArray<StopCondition<TOOLS, RUNTIME_CONTEXT>>;
|
|
91
96
|
toolApproval?: HarnessAgentToolApprovalConfiguration | undefined;
|
|
@@ -110,16 +115,17 @@ export function runPrompt<
|
|
|
110
115
|
isTurnSuspending?: () => boolean;
|
|
111
116
|
onStopConditionMet?: () => Promise<void>;
|
|
112
117
|
}): {
|
|
113
|
-
result: HarnessStreamTextResult<TOOLS, RUNTIME_CONTEXT>;
|
|
118
|
+
result: HarnessStreamTextResult<TOOLS, RUNTIME_CONTEXT, OUTPUT>;
|
|
114
119
|
done: Promise<void>;
|
|
115
120
|
} {
|
|
116
|
-
const result = new HarnessStreamTextResult<TOOLS, RUNTIME_CONTEXT>({
|
|
121
|
+
const result = new HarnessStreamTextResult<TOOLS, RUNTIME_CONTEXT, OUTPUT>({
|
|
117
122
|
tools: input.tools,
|
|
118
123
|
runtimeContext: input.runtimeContext,
|
|
119
124
|
// toolsContext is not configurable for harnesses; pass undefined cast.
|
|
120
125
|
toolsContext: undefined as never,
|
|
121
126
|
harnessId: input.harness.harnessId,
|
|
122
127
|
sessionId: input.session.sessionId,
|
|
128
|
+
output: input.output,
|
|
123
129
|
});
|
|
124
130
|
const pendingToolApprovals = input.pendingToolApprovals ?? [];
|
|
125
131
|
const pendingToolResults = input.pendingToolResults ?? [];
|
|
@@ -172,6 +178,7 @@ export function runPrompt<
|
|
|
172
178
|
input.mode === 'continue'
|
|
173
179
|
? emit =>
|
|
174
180
|
input.session.doContinueTurn({
|
|
181
|
+
responseFormat: input.responseFormat,
|
|
175
182
|
tools: input.toolSpecs,
|
|
176
183
|
instructions: input.instructions,
|
|
177
184
|
abortSignal: input.abortSignal,
|
|
@@ -185,6 +192,7 @@ export function runPrompt<
|
|
|
185
192
|
}
|
|
186
193
|
return input.session.doPromptTurn({
|
|
187
194
|
prompt: input.prompt,
|
|
195
|
+
responseFormat: input.responseFormat,
|
|
188
196
|
tools: input.toolSpecs,
|
|
189
197
|
instructions: input.instructions,
|
|
190
198
|
abortSignal: input.abortSignal,
|
|
@@ -36,6 +36,9 @@ export type PrepareSandboxForHarnessResult = {
|
|
|
36
36
|
* When a later `HarnessAgent` session uses a sandbox created from the persisted
|
|
37
37
|
* artifact, the adapter recomputes the same recipe identity and the existing
|
|
38
38
|
* bootstrap marker makes the bootstrap logic a no-op.
|
|
39
|
+
*
|
|
40
|
+
* Repeated harness IDs are prepared once. When multiple adapters use the same
|
|
41
|
+
* ID, the last adapter in `harnesses` is used.
|
|
39
42
|
*/
|
|
40
43
|
export async function prepareSandboxForHarness(options: {
|
|
41
44
|
readonly session: SandboxSession;
|
|
@@ -52,10 +55,11 @@ export async function prepareSandboxForHarness(options: {
|
|
|
52
55
|
);
|
|
53
56
|
}
|
|
54
57
|
|
|
55
|
-
const harnesses = [
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
const harnesses = [
|
|
59
|
+
...new Map(
|
|
60
|
+
options.harnesses.map(harness => [harness.harnessId, harness]),
|
|
61
|
+
).values(),
|
|
62
|
+
].sort((a, b) => a.harnessId.localeCompare(b.harnessId));
|
|
59
63
|
|
|
60
64
|
const workDir =
|
|
61
65
|
sandboxConfig.workDir == null
|
|
@@ -112,20 +116,6 @@ export async function prepareSandboxForHarness(options: {
|
|
|
112
116
|
};
|
|
113
117
|
}
|
|
114
118
|
|
|
115
|
-
function assertUniqueHarnessIds(
|
|
116
|
-
harnesses: ReadonlyArray<HarnessAgentAdapter>,
|
|
117
|
-
): void {
|
|
118
|
-
const seen = new Set<string>();
|
|
119
|
-
for (const harness of harnesses) {
|
|
120
|
-
if (seen.has(harness.harnessId)) {
|
|
121
|
-
throw new Error(
|
|
122
|
-
`prepareSandboxForHarness: duplicate harness id "${harness.harnessId}".`,
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
seen.add(harness.harnessId);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
119
|
async function resolvePreparedSandboxIdentity({
|
|
130
120
|
recipeIdentities,
|
|
131
121
|
bootstrapHash,
|