@ai-sdk/harness 0.0.0 → 1.0.0-beta.14

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.
Files changed (67) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/LICENSE +13 -0
  3. package/README.md +142 -0
  4. package/agent/index.ts +47 -0
  5. package/bridge/index.ts +10 -0
  6. package/dist/agent/index.d.ts +1521 -0
  7. package/dist/agent/index.js +2958 -0
  8. package/dist/agent/index.js.map +1 -0
  9. package/dist/bridge/index.d.ts +111 -0
  10. package/dist/bridge/index.js +415 -0
  11. package/dist/bridge/index.js.map +1 -0
  12. package/dist/index.d.ts +1536 -0
  13. package/dist/index.js +15834 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/utils/index.d.ts +225 -0
  16. package/dist/utils/index.js +12148 -0
  17. package/dist/utils/index.js.map +1 -0
  18. package/package.json +99 -1
  19. package/src/agent/harness-agent-session.ts +509 -0
  20. package/src/agent/harness-agent-settings.ts +131 -0
  21. package/src/agent/harness-agent-tool-approval-continuation.ts +94 -0
  22. package/src/agent/harness-agent-types.ts +50 -0
  23. package/src/agent/harness-agent.ts +819 -0
  24. package/src/agent/internal/bootstrap-recipe.ts +124 -0
  25. package/src/agent/internal/bridge-port-registry.ts +52 -0
  26. package/src/agent/internal/harness-stream-text-result.ts +720 -0
  27. package/src/agent/internal/lifecycle-state-validation.ts +95 -0
  28. package/src/agent/internal/permission-mode.ts +50 -0
  29. package/src/agent/internal/resolve-observability.ts +128 -0
  30. package/src/agent/internal/run-prompt.ts +813 -0
  31. package/src/agent/internal/strip-work-dir.ts +68 -0
  32. package/src/agent/internal/to-harness-stream.ts +75 -0
  33. package/src/agent/internal/translate-stream-part.ts +221 -0
  34. package/src/agent/internal/turn-telemetry.ts +359 -0
  35. package/src/agent/observability/file-reporter.ts +206 -0
  36. package/src/agent/observability/index.ts +15 -0
  37. package/src/agent/observability/trace-tree-reporter.ts +122 -0
  38. package/src/agent/observability/types.ts +86 -0
  39. package/src/agent/prewarm.ts +47 -0
  40. package/src/bridge/index.ts +702 -0
  41. package/src/errors/harness-capability-unsupported-error.ts +41 -0
  42. package/src/errors/harness-error.ts +22 -0
  43. package/src/index.ts +3 -0
  44. package/src/utils/bridge-ready.ts +277 -0
  45. package/src/utils/classify-disk-log.ts +43 -0
  46. package/src/utils/index.ts +15 -0
  47. package/src/utils/sandbox-channel.ts +453 -0
  48. package/src/v1/harness-v1-bootstrap.ts +46 -0
  49. package/src/v1/harness-v1-bridge-protocol.ts +310 -0
  50. package/src/v1/harness-v1-builtin-tool.ts +138 -0
  51. package/src/v1/harness-v1-call-warning.ts +22 -0
  52. package/src/v1/harness-v1-diagnostic.ts +66 -0
  53. package/src/v1/harness-v1-lifecycle-state.ts +65 -0
  54. package/src/v1/harness-v1-metadata.ts +13 -0
  55. package/src/v1/harness-v1-network-sandbox-session.ts +123 -0
  56. package/src/v1/harness-v1-observability.ts +20 -0
  57. package/src/v1/harness-v1-permission-mode.ts +11 -0
  58. package/src/v1/harness-v1-prompt-control.ts +41 -0
  59. package/src/v1/harness-v1-prompt.ts +11 -0
  60. package/src/v1/harness-v1-sandbox-provider.ts +76 -0
  61. package/src/v1/harness-v1-session.ts +272 -0
  62. package/src/v1/harness-v1-skill.ts +36 -0
  63. package/src/v1/harness-v1-stream-part.ts +363 -0
  64. package/src/v1/harness-v1-tool-spec.ts +31 -0
  65. package/src/v1/harness-v1.ts +83 -0
  66. package/src/v1/index.ts +93 -0
  67. package/utils/index.ts +1 -0
@@ -0,0 +1,720 @@
1
+ import {
2
+ DelayedPromise,
3
+ generateId,
4
+ type AssistantModelMessage,
5
+ type Context,
6
+ type ToolModelMessage,
7
+ type ToolSet,
8
+ } from '@ai-sdk/provider-utils';
9
+ import {
10
+ addLanguageModelUsage,
11
+ asLanguageModelUsage,
12
+ createAsyncIterableStream,
13
+ createNullLanguageModelUsage,
14
+ DefaultStepResult,
15
+ toResponseMessages,
16
+ } from 'ai/internal';
17
+ import type {
18
+ LanguageModelV4FinishReason,
19
+ LanguageModelV4Usage,
20
+ } from '@ai-sdk/provider';
21
+ import {
22
+ createUIMessageStreamResponse,
23
+ toUIMessageStream as toUIMessageStreamHelper,
24
+ type CallWarning,
25
+ type ContentPart,
26
+ type FinishReason,
27
+ type GenerateTextResult,
28
+ type InferUIMessageChunk,
29
+ type LanguageModelUsage,
30
+ type ProviderMetadata,
31
+ type StepResult,
32
+ type StreamTextResult,
33
+ type TextStreamPart,
34
+ type UIMessage,
35
+ type UIMessageStreamOptions,
36
+ } from 'ai';
37
+
38
+ type StreamProp<
39
+ TOOLS extends ToolSet,
40
+ RUNTIME_CONTEXT extends Context,
41
+ KEY extends keyof StreamTextResult<TOOLS, RUNTIME_CONTEXT, never>,
42
+ > = Awaited<StreamTextResult<TOOLS, RUNTIME_CONTEXT, never>[KEY]>;
43
+
44
+ /**
45
+ * Concrete `StreamTextResult` implementation backed by a single
46
+ * harness prompt turn.
47
+ *
48
+ * Wraps a `ReadableStream<TextStreamPart<TOOLS>>` that the calling
49
+ * driver pushes events into. Every `PromiseLike` accessor is backed by a
50
+ * `DelayedPromise` so the AI SDK consumer surface stays identical to
51
+ * `streamText`'s — consumers can `await result.text` or iterate
52
+ * `result.fullStream`, in either order.
53
+ *
54
+ * Each `finish-step` boundary in the driver translates to a `StepResult`
55
+ * built via `DefaultStepResult`. Step content is accumulated as
56
+ * `ContentPart[]` and fed straight to `DefaultStepResult`, which derives
57
+ * `text`, `toolCalls`, `toolResults`, `reasoning`, etc. via its getters.
58
+ *
59
+ * The Node.js response helpers (`pipeUIMessageStreamToResponse`,
60
+ * `pipeTextStreamToResponse`, `toTextStreamResponse`) and the
61
+ * output-specification surfaces (`partialOutputStream`/`elementStream`) are not
62
+ * implemented yet — they throw a clear error.
63
+ */
64
+ export class HarnessStreamTextResult<
65
+ TOOLS extends ToolSet,
66
+ RUNTIME_CONTEXT extends Context,
67
+ > implements StreamTextResult<TOOLS, RUNTIME_CONTEXT, never> {
68
+ // Delayed promises backing every PromiseLike accessor. Each is typed
69
+ // against the corresponding `StreamTextResult` property so the public
70
+ // surface stays in lockstep with AI SDK's interface as it evolves.
71
+ private readonly _content = new DelayedPromise<
72
+ StreamProp<TOOLS, RUNTIME_CONTEXT, 'content'>
73
+ >();
74
+ private readonly _text = new DelayedPromise<
75
+ StreamProp<TOOLS, RUNTIME_CONTEXT, 'text'>
76
+ >();
77
+ private readonly _reasoning = new DelayedPromise<
78
+ StreamProp<TOOLS, RUNTIME_CONTEXT, 'reasoning'>
79
+ >();
80
+ private readonly _reasoningText = new DelayedPromise<
81
+ StreamProp<TOOLS, RUNTIME_CONTEXT, 'reasoningText'>
82
+ >();
83
+ private readonly _files = new DelayedPromise<
84
+ StreamProp<TOOLS, RUNTIME_CONTEXT, 'files'>
85
+ >();
86
+ private readonly _sources = new DelayedPromise<
87
+ StreamProp<TOOLS, RUNTIME_CONTEXT, 'sources'>
88
+ >();
89
+ private readonly _toolCalls = new DelayedPromise<
90
+ StreamProp<TOOLS, RUNTIME_CONTEXT, 'toolCalls'>
91
+ >();
92
+ private readonly _staticToolCalls = new DelayedPromise<
93
+ StreamProp<TOOLS, RUNTIME_CONTEXT, 'staticToolCalls'>
94
+ >();
95
+ private readonly _dynamicToolCalls = new DelayedPromise<
96
+ StreamProp<TOOLS, RUNTIME_CONTEXT, 'dynamicToolCalls'>
97
+ >();
98
+ private readonly _toolResults = new DelayedPromise<
99
+ StreamProp<TOOLS, RUNTIME_CONTEXT, 'toolResults'>
100
+ >();
101
+ private readonly _staticToolResults = new DelayedPromise<
102
+ StreamProp<TOOLS, RUNTIME_CONTEXT, 'staticToolResults'>
103
+ >();
104
+ private readonly _dynamicToolResults = new DelayedPromise<
105
+ StreamProp<TOOLS, RUNTIME_CONTEXT, 'dynamicToolResults'>
106
+ >();
107
+ private readonly _finishReason = new DelayedPromise<FinishReason>();
108
+ private readonly _rawFinishReason = new DelayedPromise<string | undefined>();
109
+ private readonly _usage = new DelayedPromise<LanguageModelUsage>();
110
+ private readonly _warnings = new DelayedPromise<CallWarning[] | undefined>();
111
+ private readonly _steps = new DelayedPromise<
112
+ StreamProp<TOOLS, RUNTIME_CONTEXT, 'steps'>
113
+ >();
114
+ private readonly _finalStep = new DelayedPromise<
115
+ StreamProp<TOOLS, RUNTIME_CONTEXT, 'finalStep'>
116
+ >();
117
+ private readonly _request = new DelayedPromise<
118
+ StreamProp<TOOLS, RUNTIME_CONTEXT, 'request'>
119
+ >();
120
+ private readonly _response = new DelayedPromise<
121
+ StreamProp<TOOLS, RUNTIME_CONTEXT, 'response'>
122
+ >();
123
+ private readonly _responseMessages = new DelayedPromise<
124
+ Array<AssistantModelMessage | ToolModelMessage>
125
+ >();
126
+ private readonly _providerMetadata = new DelayedPromise<
127
+ ProviderMetadata | undefined
128
+ >();
129
+
130
+ // The driver pushes parts into this controller; consumers read via `stream`.
131
+ private readonly fullStreamController: ReadableStreamDefaultController<
132
+ TextStreamPart<TOOLS>
133
+ >;
134
+ readonly stream: AsyncIterableStream<TextStreamPart<TOOLS>>;
135
+ // `fullStream` is the deprecated alias that AI SDK still exposes on the
136
+ // public interface. Backed by the same underlying stream as `stream`.
137
+ readonly fullStream: AsyncIterableStream<TextStreamPart<TOOLS>>;
138
+ readonly textStream: AsyncIterableStream<string>;
139
+
140
+ private readonly stepsBuffer: StepResult<TOOLS, RUNTIME_CONTEXT>[] = [];
141
+ private currentStepContent: ContentPart<TOOLS>[] = [];
142
+ private currentStepWarnings: CallWarning[] = [];
143
+ private stepNumber = 0;
144
+
145
+ private readonly tools: TOOLS;
146
+ private readonly runtimeContext: RUNTIME_CONTEXT;
147
+ private readonly toolsContext: never;
148
+ private readonly providerName: string;
149
+ private readonly modelId: string;
150
+
151
+ // Accumulators that span the whole turn.
152
+ private accumulatedUsage: LanguageModelUsage = createNullLanguageModelUsage();
153
+ private finalProviderMetadata: ProviderMetadata | undefined = undefined;
154
+ private finalFinishReason: FinishReason = 'other';
155
+ private finalRawFinishReason: string | undefined = undefined;
156
+ private aggregateWarnings: CallWarning[] = [];
157
+ private settled = false;
158
+
159
+ constructor(options: {
160
+ tools: TOOLS;
161
+ runtimeContext: RUNTIME_CONTEXT;
162
+ toolsContext: never;
163
+ harnessId: string;
164
+ sessionId: string;
165
+ }) {
166
+ this.tools = options.tools;
167
+ this.runtimeContext = options.runtimeContext;
168
+ this.toolsContext = options.toolsContext;
169
+ this.providerName = `harness:${options.harnessId}`;
170
+ this.modelId = options.sessionId;
171
+
172
+ let controllerRef!: ReadableStreamDefaultController<TextStreamPart<TOOLS>>;
173
+ const baseStream = new ReadableStream<TextStreamPart<TOOLS>>({
174
+ start(c) {
175
+ controllerRef = c;
176
+ },
177
+ });
178
+ this.fullStreamController = controllerRef;
179
+
180
+ const [forFull, forText] = baseStream.tee();
181
+ this.stream = forFull as AsyncIterableStream<TextStreamPart<TOOLS>>;
182
+ this.fullStream = this.stream;
183
+ this.textStream = forText.pipeThrough(
184
+ new TransformStream<TextStreamPart<TOOLS>, string>({
185
+ transform(part, controller) {
186
+ if (part.type === 'text-delta') {
187
+ controller.enqueue(part.text);
188
+ }
189
+ },
190
+ }),
191
+ ) as AsyncIterableStream<string>;
192
+ }
193
+
194
+ // ─── Writer-side methods used by the driver ────────────────────────
195
+
196
+ /**
197
+ * Push a translated `TextStreamPart` into `fullStream` and accumulate it
198
+ * into the current step's content array where applicable.
199
+ */
200
+ enqueue(part: TextStreamPart<TOOLS>): void {
201
+ this.fullStreamController.enqueue(part);
202
+ this.appendToCurrentStepContent(part);
203
+ }
204
+
205
+ /**
206
+ * Mark the end of a step. Builds a `StepResult` from the accumulated
207
+ * content and records it in the steps array. Accepts the V4-shaped
208
+ * finish reason / usage the harness emits and normalizes to AI SDK's
209
+ * flat shape internally.
210
+ */
211
+ finishStep(input: {
212
+ finishReason: LanguageModelV4FinishReason;
213
+ usage: LanguageModelV4Usage;
214
+ providerMetadata: ProviderMetadata | undefined;
215
+ warnings: CallWarning[];
216
+ }): void {
217
+ const normalizedUsage = asLanguageModelUsage(input.usage);
218
+ const finishReason = input.finishReason.unified;
219
+ const rawFinishReason = input.finishReason.raw;
220
+
221
+ const step = new DefaultStepResult<TOOLS, RUNTIME_CONTEXT>({
222
+ callId: generateId(),
223
+ stepNumber: this.stepNumber,
224
+ provider: this.providerName,
225
+ modelId: this.modelId,
226
+ runtimeContext: this.runtimeContext,
227
+ toolsContext: this.toolsContext,
228
+ content: this.currentStepContent,
229
+ finishReason,
230
+ rawFinishReason,
231
+ usage: normalizedUsage,
232
+ performance: createEmptyPerformance(),
233
+ warnings: input.warnings.length > 0 ? input.warnings : undefined,
234
+ request: {},
235
+ response: {
236
+ id: generateId(),
237
+ timestamp: new Date(),
238
+ modelId: this.modelId,
239
+ messages: [],
240
+ },
241
+ providerMetadata: input.providerMetadata,
242
+ });
243
+ this.stepsBuffer.push(step);
244
+
245
+ // Forward an AI SDK finish-step event so consumers reading fullStream
246
+ // see step boundaries.
247
+ this.fullStreamController.enqueue({
248
+ type: 'finish-step',
249
+ finishReason,
250
+ rawFinishReason,
251
+ usage: normalizedUsage,
252
+ providerMetadata: input.providerMetadata,
253
+ response: step.response,
254
+ performance: createEmptyPerformance(),
255
+ } as TextStreamPart<TOOLS>);
256
+
257
+ this.accumulatedUsage = addLanguageModelUsage(
258
+ this.accumulatedUsage,
259
+ normalizedUsage,
260
+ );
261
+ this.finalFinishReason = finishReason;
262
+ this.finalRawFinishReason = rawFinishReason;
263
+ this.finalProviderMetadata = input.providerMetadata;
264
+ if (input.warnings.length > 0)
265
+ this.aggregateWarnings.push(...input.warnings);
266
+
267
+ this.stepNumber += 1;
268
+ this.currentStepContent = [];
269
+ this.currentStepWarnings = [];
270
+ }
271
+
272
+ /**
273
+ * Resolve every delayed promise and close `fullStream`. Idempotent.
274
+ */
275
+ async finish(): Promise<void> {
276
+ if (this.settled) return;
277
+ this.settled = true;
278
+
279
+ // Flush any trailing content not yet captured by a finish-step. We
280
+ // construct the step directly here (the public `finishStep` takes V4
281
+ // shapes; we already have AI SDK shapes at this point).
282
+ if (this.currentStepContent.length > 0) {
283
+ const trailingStep = new DefaultStepResult<TOOLS, RUNTIME_CONTEXT>({
284
+ callId: generateId(),
285
+ stepNumber: this.stepNumber,
286
+ provider: this.providerName,
287
+ modelId: this.modelId,
288
+ runtimeContext: this.runtimeContext,
289
+ toolsContext: this.toolsContext,
290
+ content: this.currentStepContent,
291
+ finishReason: this.finalFinishReason,
292
+ rawFinishReason: this.finalRawFinishReason,
293
+ usage: createNullLanguageModelUsage(),
294
+ performance: createEmptyPerformance(),
295
+ warnings:
296
+ this.currentStepWarnings.length > 0
297
+ ? this.currentStepWarnings
298
+ : undefined,
299
+ request: {},
300
+ response: {
301
+ id: generateId(),
302
+ timestamp: new Date(),
303
+ modelId: this.modelId,
304
+ messages: [],
305
+ },
306
+ providerMetadata: this.finalProviderMetadata,
307
+ });
308
+ this.stepsBuffer.push(trailingStep);
309
+ this.currentStepContent = [];
310
+ this.currentStepWarnings = [];
311
+ }
312
+
313
+ const finalStep =
314
+ this.stepsBuffer.length > 0
315
+ ? this.stepsBuffer[this.stepsBuffer.length - 1]!
316
+ : new DefaultStepResult<TOOLS, RUNTIME_CONTEXT>({
317
+ callId: generateId(),
318
+ stepNumber: 0,
319
+ provider: this.providerName,
320
+ modelId: this.modelId,
321
+ runtimeContext: this.runtimeContext,
322
+ toolsContext: this.toolsContext,
323
+ content: [],
324
+ finishReason: this.finalFinishReason,
325
+ rawFinishReason: this.finalRawFinishReason,
326
+ usage: createNullLanguageModelUsage(),
327
+ performance: createEmptyPerformance(),
328
+ warnings: undefined,
329
+ request: {},
330
+ response: {
331
+ id: generateId(),
332
+ timestamp: new Date(),
333
+ modelId: this.modelId,
334
+ messages: [],
335
+ },
336
+ providerMetadata: undefined,
337
+ });
338
+
339
+ const aggregatedContent = this.stepsBuffer.flatMap(s => s.content);
340
+
341
+ this._content.resolve(
342
+ aggregatedContent as StreamProp<TOOLS, RUNTIME_CONTEXT, 'content'>,
343
+ );
344
+ this._text.resolve(finalStep.text);
345
+ // Reasoning content parts are not yet derived from harness events; the
346
+ // foundation surfaces an empty array. Adapters that emit reasoning
347
+ // deltas can be wired up to produce real reasoning content in a later
348
+ // pass.
349
+ this._reasoning.resolve(
350
+ [] as StreamProp<TOOLS, RUNTIME_CONTEXT, 'reasoning'>,
351
+ );
352
+ this._reasoningText.resolve(undefined);
353
+ this._files.resolve(this.stepsBuffer.flatMap(s => s.files));
354
+ this._sources.resolve(this.stepsBuffer.flatMap(s => s.sources));
355
+ this._toolCalls.resolve(
356
+ this.stepsBuffer.flatMap(s => s.toolCalls) as StreamProp<
357
+ TOOLS,
358
+ RUNTIME_CONTEXT,
359
+ 'toolCalls'
360
+ >,
361
+ );
362
+ this._staticToolCalls.resolve(
363
+ this.stepsBuffer.flatMap(s => s.staticToolCalls) as StreamProp<
364
+ TOOLS,
365
+ RUNTIME_CONTEXT,
366
+ 'staticToolCalls'
367
+ >,
368
+ );
369
+ this._dynamicToolCalls.resolve(
370
+ this.stepsBuffer.flatMap(s => s.dynamicToolCalls) as StreamProp<
371
+ TOOLS,
372
+ RUNTIME_CONTEXT,
373
+ 'dynamicToolCalls'
374
+ >,
375
+ );
376
+ this._toolResults.resolve(
377
+ this.stepsBuffer.flatMap(s => s.toolResults) as StreamProp<
378
+ TOOLS,
379
+ RUNTIME_CONTEXT,
380
+ 'toolResults'
381
+ >,
382
+ );
383
+ this._staticToolResults.resolve(
384
+ this.stepsBuffer.flatMap(s => s.staticToolResults) as StreamProp<
385
+ TOOLS,
386
+ RUNTIME_CONTEXT,
387
+ 'staticToolResults'
388
+ >,
389
+ );
390
+ this._dynamicToolResults.resolve(
391
+ this.stepsBuffer.flatMap(s => s.dynamicToolResults) as StreamProp<
392
+ TOOLS,
393
+ RUNTIME_CONTEXT,
394
+ 'dynamicToolResults'
395
+ >,
396
+ );
397
+ this._finishReason.resolve(this.finalFinishReason);
398
+ this._rawFinishReason.resolve(this.finalRawFinishReason);
399
+ this._usage.resolve(this.accumulatedUsage);
400
+ this._warnings.resolve(
401
+ this.aggregateWarnings.length > 0 ? this.aggregateWarnings : undefined,
402
+ );
403
+ this._steps.resolve(this.stepsBuffer);
404
+ this._finalStep.resolve(finalStep);
405
+ this._request.resolve(finalStep.request);
406
+ this._response.resolve(finalStep.response);
407
+ this._providerMetadata.resolve(this.finalProviderMetadata);
408
+
409
+ const responseMessages = await toResponseMessages<TOOLS>({
410
+ content: aggregatedContent,
411
+ tools: this.tools,
412
+ });
413
+ this._responseMessages.resolve(responseMessages);
414
+
415
+ // Forward AI SDK finish event before closing.
416
+ this.fullStreamController.enqueue({
417
+ type: 'finish',
418
+ finishReason: this.finalFinishReason,
419
+ rawFinishReason: this.finalRawFinishReason,
420
+ totalUsage: this.accumulatedUsage,
421
+ providerMetadata: this.finalProviderMetadata,
422
+ } as TextStreamPart<TOOLS>);
423
+
424
+ this.fullStreamController.close();
425
+ }
426
+
427
+ /**
428
+ * Surface a fatal error as a stream `error` part + reject every delayed
429
+ * promise so awaiting consumers stop hanging. Idempotent.
430
+ */
431
+ fail(error: unknown): void {
432
+ if (this.settled) return;
433
+ this.settled = true;
434
+ this.fullStreamController.enqueue({
435
+ type: 'error',
436
+ error,
437
+ } as TextStreamPart<TOOLS>);
438
+ this.fullStreamController.close();
439
+ for (const dp of [
440
+ this._content,
441
+ this._text,
442
+ this._reasoning,
443
+ this._reasoningText,
444
+ this._files,
445
+ this._sources,
446
+ this._toolCalls,
447
+ this._staticToolCalls,
448
+ this._dynamicToolCalls,
449
+ this._toolResults,
450
+ this._staticToolResults,
451
+ this._dynamicToolResults,
452
+ this._finishReason,
453
+ this._rawFinishReason,
454
+ this._usage,
455
+ this._warnings,
456
+ this._steps,
457
+ this._finalStep,
458
+ this._request,
459
+ this._response,
460
+ this._responseMessages,
461
+ this._providerMetadata,
462
+ ]) {
463
+ try {
464
+ (dp as DelayedPromise<unknown>).reject(error);
465
+ } catch {
466
+ // ignore double-rejection
467
+ }
468
+ }
469
+ }
470
+
471
+ // ─── Reader-side public surface (StreamTextResult contract) ────────
472
+
473
+ get content() {
474
+ return this._content.promise;
475
+ }
476
+ get text() {
477
+ return this._text.promise;
478
+ }
479
+ get reasoning() {
480
+ return this._reasoning.promise;
481
+ }
482
+ get reasoningText() {
483
+ return this._reasoningText.promise;
484
+ }
485
+ get files() {
486
+ return this._files.promise;
487
+ }
488
+ get sources() {
489
+ return this._sources.promise;
490
+ }
491
+ get toolCalls() {
492
+ return this._toolCalls.promise;
493
+ }
494
+ get staticToolCalls() {
495
+ return this._staticToolCalls.promise;
496
+ }
497
+ get dynamicToolCalls() {
498
+ return this._dynamicToolCalls.promise;
499
+ }
500
+ get toolResults() {
501
+ return this._toolResults.promise;
502
+ }
503
+ get staticToolResults() {
504
+ return this._staticToolResults.promise;
505
+ }
506
+ get dynamicToolResults() {
507
+ return this._dynamicToolResults.promise;
508
+ }
509
+ get finishReason() {
510
+ return this._finishReason.promise;
511
+ }
512
+ get rawFinishReason() {
513
+ return this._rawFinishReason.promise;
514
+ }
515
+ get usage() {
516
+ return this._usage.promise;
517
+ }
518
+ get totalUsage() {
519
+ return this._usage.promise;
520
+ }
521
+ get warnings() {
522
+ return this._warnings.promise;
523
+ }
524
+ get steps() {
525
+ return this._steps.promise;
526
+ }
527
+ get finalStep() {
528
+ return this._finalStep.promise;
529
+ }
530
+ get request() {
531
+ return this._request.promise;
532
+ }
533
+ get response() {
534
+ return this._response.promise;
535
+ }
536
+ get responseMessages() {
537
+ return this._responseMessages.promise;
538
+ }
539
+ get providerMetadata() {
540
+ return this._providerMetadata.promise;
541
+ }
542
+
543
+ // Output-specification surfaces are not yet supported.
544
+ get experimental_partialOutputStream(): never {
545
+ throw notSupportedYet('partial output stream');
546
+ }
547
+ get partialOutputStream(): never {
548
+ throw notSupportedYet('partial output stream');
549
+ }
550
+ get elementStream(): never {
551
+ throw notSupportedYet('element stream');
552
+ }
553
+ get output(): never {
554
+ throw notSupportedYet('structured output');
555
+ }
556
+
557
+ async consumeStream(): Promise<void> {
558
+ const reader = this.fullStream.getReader();
559
+ try {
560
+ while (true) {
561
+ const { done } = await reader.read();
562
+ if (done) return;
563
+ }
564
+ } finally {
565
+ reader.releaseLock();
566
+ }
567
+ }
568
+
569
+ toUIMessageStream<UI_MESSAGE extends UIMessage>({
570
+ originalMessages,
571
+ generateMessageId,
572
+ onFinish,
573
+ messageMetadata,
574
+ sendReasoning,
575
+ sendSources,
576
+ sendStart,
577
+ sendFinish,
578
+ onError,
579
+ }: UIMessageStreamOptions<UI_MESSAGE> = {}): AsyncIterableStream<
580
+ InferUIMessageChunk<UI_MESSAGE>
581
+ > {
582
+ return createAsyncIterableStream(
583
+ toUIMessageStreamHelper<TOOLS, UI_MESSAGE>({
584
+ stream: this.stream,
585
+ tools: this.tools,
586
+ originalMessages,
587
+ generateMessageId,
588
+ onFinish,
589
+ messageMetadata,
590
+ sendReasoning,
591
+ sendSources,
592
+ sendStart,
593
+ sendFinish,
594
+ onError,
595
+ }),
596
+ ) as AsyncIterableStream<InferUIMessageChunk<UI_MESSAGE>>;
597
+ }
598
+
599
+ pipeUIMessageStreamToResponse(): never {
600
+ throw notSupportedYet('pipeUIMessageStreamToResponse');
601
+ }
602
+
603
+ pipeTextStreamToResponse(): never {
604
+ throw notSupportedYet('pipeTextStreamToResponse');
605
+ }
606
+
607
+ toUIMessageStreamResponse<UI_MESSAGE extends UIMessage>({
608
+ originalMessages,
609
+ generateMessageId,
610
+ onFinish,
611
+ messageMetadata,
612
+ sendReasoning,
613
+ sendSources,
614
+ sendStart,
615
+ sendFinish,
616
+ onError,
617
+ ...init
618
+ }: ResponseInit & {
619
+ consumeSseStream?: (options: {
620
+ stream: ReadableStream<string>;
621
+ }) => PromiseLike<void> | void;
622
+ } & UIMessageStreamOptions<UI_MESSAGE> = {}): Response {
623
+ return createUIMessageStreamResponse({
624
+ stream: this.toUIMessageStream<UI_MESSAGE>({
625
+ originalMessages,
626
+ generateMessageId,
627
+ onFinish,
628
+ messageMetadata,
629
+ sendReasoning,
630
+ sendSources,
631
+ sendStart,
632
+ sendFinish,
633
+ onError,
634
+ }),
635
+ ...init,
636
+ });
637
+ }
638
+
639
+ toTextStreamResponse(): never {
640
+ throw notSupportedYet('toTextStreamResponse');
641
+ }
642
+
643
+ // ─── Helpers ────────────────────────────────────────────────────────
644
+
645
+ private appendToCurrentStepContent(part: TextStreamPart<TOOLS>): void {
646
+ switch (part.type) {
647
+ case 'text-delta': {
648
+ // Coalesce contiguous text-deltas with the same id into one text part.
649
+ const last =
650
+ this.currentStepContent[this.currentStepContent.length - 1];
651
+ if (last && last.type === 'text') {
652
+ (last as { text: string }).text += part.text;
653
+ } else {
654
+ this.currentStepContent.push({
655
+ type: 'text',
656
+ text: part.text,
657
+ } as ContentPart<TOOLS>);
658
+ }
659
+ return;
660
+ }
661
+ case 'tool-call':
662
+ this.currentStepContent.push({
663
+ ...(part as object),
664
+ } as ContentPart<TOOLS>);
665
+ return;
666
+ case 'tool-approval-request':
667
+ this.currentStepContent.push({
668
+ ...(part as object),
669
+ } as ContentPart<TOOLS>);
670
+ return;
671
+ case 'tool-approval-response':
672
+ this.currentStepContent.push({
673
+ ...(part as object),
674
+ } as ContentPart<TOOLS>);
675
+ return;
676
+ case 'tool-result':
677
+ this.currentStepContent.push({
678
+ ...(part as object),
679
+ } as ContentPart<TOOLS>);
680
+ return;
681
+ default:
682
+ // text-start/end, reasoning-*, raw, error, finish-step, finish are
683
+ // not directly stored as ContentParts. (Reasoning content parts
684
+ // would belong here; we omit them for v0.)
685
+ return;
686
+ }
687
+ }
688
+ }
689
+
690
+ function createEmptyPerformance(): StepResult<ToolSet, Context>['performance'] {
691
+ return {
692
+ effectiveOutputTokensPerSecond: 0,
693
+ outputTokensPerSecond: undefined,
694
+ inputTokensPerSecond: undefined,
695
+ effectiveTotalTokensPerSecond: 0,
696
+ stepTimeMs: 0,
697
+ responseTimeMs: 0,
698
+ toolExecutionMs: {},
699
+ timeToFirstOutputMs: undefined,
700
+ };
701
+ }
702
+
703
+ function notSupportedYet(feature: string): Error {
704
+ return new Error(
705
+ `HarnessAgent: ${feature} is not implemented yet. Track the foundation review for follow-up.`,
706
+ );
707
+ }
708
+
709
+ // Re-declare `AsyncIterableStream` locally to avoid pulling AI SDK's internal
710
+ // async-iterable-stream helper. ReadableStreams already implement the
711
+ // async-iterator contract in modern runtimes; we expose them under the same
712
+ // nominal type AI SDK does.
713
+ type AsyncIterableStream<T> = ReadableStream<T> & AsyncIterable<T>;
714
+
715
+ // `GenerateTextResult` is re-exported only so downstream code can keep this
716
+ // file as the single source of return-shape constants; not otherwise used here.
717
+ export type _GenerateTextResultMarker<
718
+ TOOLS extends ToolSet,
719
+ RUNTIME_CONTEXT extends Context,
720
+ > = GenerateTextResult<TOOLS, RUNTIME_CONTEXT, never>;