@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.
- package/CHANGELOG.md +110 -0
- package/LICENSE +13 -0
- package/README.md +142 -0
- package/agent/index.ts +47 -0
- package/bridge/index.ts +10 -0
- package/dist/agent/index.d.ts +1521 -0
- package/dist/agent/index.js +2958 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/bridge/index.d.ts +111 -0
- package/dist/bridge/index.js +415 -0
- package/dist/bridge/index.js.map +1 -0
- package/dist/index.d.ts +1536 -0
- package/dist/index.js +15834 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/index.d.ts +225 -0
- package/dist/utils/index.js +12148 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +99 -1
- package/src/agent/harness-agent-session.ts +509 -0
- package/src/agent/harness-agent-settings.ts +131 -0
- package/src/agent/harness-agent-tool-approval-continuation.ts +94 -0
- package/src/agent/harness-agent-types.ts +50 -0
- package/src/agent/harness-agent.ts +819 -0
- package/src/agent/internal/bootstrap-recipe.ts +124 -0
- package/src/agent/internal/bridge-port-registry.ts +52 -0
- package/src/agent/internal/harness-stream-text-result.ts +720 -0
- package/src/agent/internal/lifecycle-state-validation.ts +95 -0
- package/src/agent/internal/permission-mode.ts +50 -0
- package/src/agent/internal/resolve-observability.ts +128 -0
- package/src/agent/internal/run-prompt.ts +813 -0
- package/src/agent/internal/strip-work-dir.ts +68 -0
- package/src/agent/internal/to-harness-stream.ts +75 -0
- package/src/agent/internal/translate-stream-part.ts +221 -0
- package/src/agent/internal/turn-telemetry.ts +359 -0
- package/src/agent/observability/file-reporter.ts +206 -0
- package/src/agent/observability/index.ts +15 -0
- package/src/agent/observability/trace-tree-reporter.ts +122 -0
- package/src/agent/observability/types.ts +86 -0
- package/src/agent/prewarm.ts +47 -0
- package/src/bridge/index.ts +702 -0
- package/src/errors/harness-capability-unsupported-error.ts +41 -0
- package/src/errors/harness-error.ts +22 -0
- package/src/index.ts +3 -0
- package/src/utils/bridge-ready.ts +277 -0
- package/src/utils/classify-disk-log.ts +43 -0
- package/src/utils/index.ts +15 -0
- package/src/utils/sandbox-channel.ts +453 -0
- package/src/v1/harness-v1-bootstrap.ts +46 -0
- package/src/v1/harness-v1-bridge-protocol.ts +310 -0
- package/src/v1/harness-v1-builtin-tool.ts +138 -0
- package/src/v1/harness-v1-call-warning.ts +22 -0
- package/src/v1/harness-v1-diagnostic.ts +66 -0
- package/src/v1/harness-v1-lifecycle-state.ts +65 -0
- package/src/v1/harness-v1-metadata.ts +13 -0
- package/src/v1/harness-v1-network-sandbox-session.ts +123 -0
- package/src/v1/harness-v1-observability.ts +20 -0
- package/src/v1/harness-v1-permission-mode.ts +11 -0
- package/src/v1/harness-v1-prompt-control.ts +41 -0
- package/src/v1/harness-v1-prompt.ts +11 -0
- package/src/v1/harness-v1-sandbox-provider.ts +76 -0
- package/src/v1/harness-v1-session.ts +272 -0
- package/src/v1/harness-v1-skill.ts +36 -0
- package/src/v1/harness-v1-stream-part.ts +363 -0
- package/src/v1/harness-v1-tool-spec.ts +31 -0
- package/src/v1/harness-v1.ts +83 -0
- package/src/v1/index.ts +93 -0
- 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>;
|