@ai-sdk/harness 0.0.0 → 1.0.0-canary.11

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 +87 -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,2958 @@
1
+ // src/errors/harness-capability-unsupported-error.ts
2
+ import { AISDKError as AISDKError2 } from "@ai-sdk/provider";
3
+
4
+ // src/errors/harness-error.ts
5
+ import { AISDKError } from "@ai-sdk/provider";
6
+ var name = "AI_HarnessError";
7
+ var marker = `vercel.ai.error.${name}`;
8
+ var symbol = Symbol.for(marker);
9
+ var _a, _b;
10
+ var HarnessError = class extends (_b = AISDKError, _a = symbol, _b) {
11
+ constructor({ message, cause }) {
12
+ super({ name, message, cause });
13
+ this[_a] = true;
14
+ }
15
+ static isInstance(error) {
16
+ return AISDKError.hasMarker(error, marker);
17
+ }
18
+ };
19
+
20
+ // src/errors/harness-capability-unsupported-error.ts
21
+ var name2 = "AI_HarnessCapabilityUnsupportedError";
22
+ var marker2 = `vercel.ai.error.${name2}`;
23
+ var symbol2 = Symbol.for(marker2);
24
+ var _a2, _b2;
25
+ var HarnessCapabilityUnsupportedError = class extends (_b2 = HarnessError, _a2 = symbol2, _b2) {
26
+ constructor({
27
+ message,
28
+ harnessId,
29
+ cause
30
+ }) {
31
+ super({ message, cause });
32
+ this[_a2] = true;
33
+ Object.defineProperty(this, "name", { value: name2 });
34
+ this.harnessId = harnessId;
35
+ }
36
+ static isInstance(error) {
37
+ return AISDKError2.hasMarker(error, marker2);
38
+ }
39
+ };
40
+
41
+ // src/agent/harness-agent.ts
42
+ import {
43
+ asSchema,
44
+ generateId as generateId5
45
+ } from "@ai-sdk/provider-utils";
46
+
47
+ // src/agent/internal/bridge-port-registry.ts
48
+ var registries = /* @__PURE__ */ new WeakMap();
49
+ function acquireBridgePort(options) {
50
+ let entry = registries.get(options.poolKey);
51
+ if (entry == null) {
52
+ entry = { pool: options.pool, leases: /* @__PURE__ */ new Map() };
53
+ registries.set(options.poolKey, entry);
54
+ }
55
+ const existing = entry.leases.get(options.sessionId);
56
+ if (existing != null) return existing;
57
+ const leased = new Set(entry.leases.values());
58
+ for (const port of entry.pool) {
59
+ if (!leased.has(port)) {
60
+ entry.leases.set(options.sessionId, port);
61
+ return port;
62
+ }
63
+ }
64
+ throw new Error(
65
+ `No available bridge port \u2014 pool of ${entry.pool.length} ports is fully leased.`
66
+ );
67
+ }
68
+ function releaseBridgePort(options) {
69
+ const entry = registries.get(options.poolKey);
70
+ if (entry == null) return;
71
+ entry.leases.delete(options.sessionId);
72
+ }
73
+
74
+ // src/agent/internal/lifecycle-state-validation.ts
75
+ import { safeValidateTypes } from "@ai-sdk/provider-utils";
76
+ async function validateLifecycleStateData(input) {
77
+ const { harness, state } = input;
78
+ if (state.type !== input.expectedType) {
79
+ throw new HarnessError({
80
+ message: `Lifecycle state has unexpected type '${state.type}'; expected '${input.expectedType}'.`
81
+ });
82
+ }
83
+ if (state.specificationVersion !== "harness-v1") {
84
+ throw new HarnessError({
85
+ message: `Lifecycle state has unexpected specificationVersion '${state.specificationVersion}'; expected 'harness-v1'.`
86
+ });
87
+ }
88
+ if (state.harnessId !== harness.harnessId) {
89
+ throw new HarnessError({
90
+ message: `Lifecycle state was produced by harness '${state.harnessId}' but this agent uses '${harness.harnessId}'.`
91
+ });
92
+ }
93
+ if (state.type === "resume-session" && "pendingToolApprovals" in state && state.pendingToolApprovals !== void 0) {
94
+ throw new HarnessError({
95
+ message: "Resume session state cannot contain pending tool approvals; unfinished turns must be stored as `continueFrom`."
96
+ });
97
+ }
98
+ const data = harness.lifecycleStateSchema == null ? state.data : await (async () => {
99
+ const result = await safeValidateTypes({
100
+ value: state.data,
101
+ schema: harness.lifecycleStateSchema
102
+ });
103
+ if (!result.success) {
104
+ throw new HarnessError({
105
+ message: `Lifecycle state failed schema validation for harness '${harness.harnessId}': ${result.error.message}`,
106
+ cause: result.error
107
+ });
108
+ }
109
+ return result.value;
110
+ })();
111
+ if (state.type === "resume-session") {
112
+ const continueFrom = state.continueFrom == null ? void 0 : await validateLifecycleStateData({
113
+ harness,
114
+ state: state.continueFrom,
115
+ expectedType: "continue-turn"
116
+ });
117
+ return {
118
+ type: state.type,
119
+ harnessId: state.harnessId,
120
+ specificationVersion: state.specificationVersion,
121
+ data,
122
+ ...continueFrom !== void 0 ? { continueFrom } : {}
123
+ };
124
+ }
125
+ return {
126
+ type: state.type,
127
+ harnessId: state.harnessId,
128
+ specificationVersion: state.specificationVersion,
129
+ data,
130
+ ...state.pendingToolApprovals !== void 0 ? { pendingToolApprovals: state.pendingToolApprovals } : {}
131
+ };
132
+ }
133
+
134
+ // src/agent/internal/to-harness-stream.ts
135
+ async function toHarnessStream(options) {
136
+ let controller;
137
+ let closed = false;
138
+ const stream = new ReadableStream({
139
+ start(c) {
140
+ controller = c;
141
+ }
142
+ });
143
+ const safeEnqueue = (part) => {
144
+ if (closed) return;
145
+ controller.enqueue(part);
146
+ };
147
+ const safeClose = () => {
148
+ if (closed) return;
149
+ closed = true;
150
+ controller.close();
151
+ };
152
+ const control = await options.invoke(safeEnqueue);
153
+ Promise.resolve(control.done).then(
154
+ () => safeClose(),
155
+ (err) => {
156
+ safeEnqueue({ type: "error", error: err });
157
+ safeClose();
158
+ }
159
+ ).catch(() => {
160
+ });
161
+ return { stream, control };
162
+ }
163
+
164
+ // src/agent/internal/run-prompt.ts
165
+ import {
166
+ executeTool,
167
+ generateId as generateId4,
168
+ isExecutableTool,
169
+ safeParseJSON
170
+ } from "@ai-sdk/provider-utils";
171
+ import { parseToolCall } from "ai/internal";
172
+
173
+ // src/agent/internal/harness-stream-text-result.ts
174
+ import {
175
+ DelayedPromise,
176
+ generateId
177
+ } from "@ai-sdk/provider-utils";
178
+ import {
179
+ addLanguageModelUsage,
180
+ asLanguageModelUsage,
181
+ createAsyncIterableStream,
182
+ createNullLanguageModelUsage,
183
+ DefaultStepResult,
184
+ toResponseMessages
185
+ } from "ai/internal";
186
+ import {
187
+ createUIMessageStreamResponse,
188
+ toUIMessageStream as toUIMessageStreamHelper
189
+ } from "ai";
190
+ var HarnessStreamTextResult = class {
191
+ constructor(options) {
192
+ // Delayed promises backing every PromiseLike accessor. Each is typed
193
+ // against the corresponding `StreamTextResult` property so the public
194
+ // surface stays in lockstep with AI SDK's interface as it evolves.
195
+ this._content = new DelayedPromise();
196
+ this._text = new DelayedPromise();
197
+ this._reasoning = new DelayedPromise();
198
+ this._reasoningText = new DelayedPromise();
199
+ this._files = new DelayedPromise();
200
+ this._sources = new DelayedPromise();
201
+ this._toolCalls = new DelayedPromise();
202
+ this._staticToolCalls = new DelayedPromise();
203
+ this._dynamicToolCalls = new DelayedPromise();
204
+ this._toolResults = new DelayedPromise();
205
+ this._staticToolResults = new DelayedPromise();
206
+ this._dynamicToolResults = new DelayedPromise();
207
+ this._finishReason = new DelayedPromise();
208
+ this._rawFinishReason = new DelayedPromise();
209
+ this._usage = new DelayedPromise();
210
+ this._warnings = new DelayedPromise();
211
+ this._steps = new DelayedPromise();
212
+ this._finalStep = new DelayedPromise();
213
+ this._request = new DelayedPromise();
214
+ this._response = new DelayedPromise();
215
+ this._responseMessages = new DelayedPromise();
216
+ this._providerMetadata = new DelayedPromise();
217
+ this.stepsBuffer = [];
218
+ this.currentStepContent = [];
219
+ this.currentStepWarnings = [];
220
+ this.stepNumber = 0;
221
+ // Accumulators that span the whole turn.
222
+ this.accumulatedUsage = createNullLanguageModelUsage();
223
+ this.finalProviderMetadata = void 0;
224
+ this.finalFinishReason = "other";
225
+ this.finalRawFinishReason = void 0;
226
+ this.aggregateWarnings = [];
227
+ this.settled = false;
228
+ this.tools = options.tools;
229
+ this.runtimeContext = options.runtimeContext;
230
+ this.toolsContext = options.toolsContext;
231
+ this.providerName = `harness:${options.harnessId}`;
232
+ this.modelId = options.sessionId;
233
+ let controllerRef;
234
+ const baseStream = new ReadableStream({
235
+ start(c) {
236
+ controllerRef = c;
237
+ }
238
+ });
239
+ this.fullStreamController = controllerRef;
240
+ const [forFull, forText] = baseStream.tee();
241
+ this.stream = forFull;
242
+ this.fullStream = this.stream;
243
+ this.textStream = forText.pipeThrough(
244
+ new TransformStream({
245
+ transform(part, controller) {
246
+ if (part.type === "text-delta") {
247
+ controller.enqueue(part.text);
248
+ }
249
+ }
250
+ })
251
+ );
252
+ }
253
+ // ─── Writer-side methods used by the driver ────────────────────────
254
+ /**
255
+ * Push a translated `TextStreamPart` into `fullStream` and accumulate it
256
+ * into the current step's content array where applicable.
257
+ */
258
+ enqueue(part) {
259
+ this.fullStreamController.enqueue(part);
260
+ this.appendToCurrentStepContent(part);
261
+ }
262
+ /**
263
+ * Mark the end of a step. Builds a `StepResult` from the accumulated
264
+ * content and records it in the steps array. Accepts the V4-shaped
265
+ * finish reason / usage the harness emits and normalizes to AI SDK's
266
+ * flat shape internally.
267
+ */
268
+ finishStep(input) {
269
+ const normalizedUsage = asLanguageModelUsage(input.usage);
270
+ const finishReason = input.finishReason.unified;
271
+ const rawFinishReason = input.finishReason.raw;
272
+ const step = new DefaultStepResult({
273
+ callId: generateId(),
274
+ stepNumber: this.stepNumber,
275
+ provider: this.providerName,
276
+ modelId: this.modelId,
277
+ runtimeContext: this.runtimeContext,
278
+ toolsContext: this.toolsContext,
279
+ content: this.currentStepContent,
280
+ finishReason,
281
+ rawFinishReason,
282
+ usage: normalizedUsage,
283
+ performance: createEmptyPerformance(),
284
+ warnings: input.warnings.length > 0 ? input.warnings : void 0,
285
+ request: {},
286
+ response: {
287
+ id: generateId(),
288
+ timestamp: /* @__PURE__ */ new Date(),
289
+ modelId: this.modelId,
290
+ messages: []
291
+ },
292
+ providerMetadata: input.providerMetadata
293
+ });
294
+ this.stepsBuffer.push(step);
295
+ this.fullStreamController.enqueue({
296
+ type: "finish-step",
297
+ finishReason,
298
+ rawFinishReason,
299
+ usage: normalizedUsage,
300
+ providerMetadata: input.providerMetadata,
301
+ response: step.response,
302
+ performance: createEmptyPerformance()
303
+ });
304
+ this.accumulatedUsage = addLanguageModelUsage(
305
+ this.accumulatedUsage,
306
+ normalizedUsage
307
+ );
308
+ this.finalFinishReason = finishReason;
309
+ this.finalRawFinishReason = rawFinishReason;
310
+ this.finalProviderMetadata = input.providerMetadata;
311
+ if (input.warnings.length > 0)
312
+ this.aggregateWarnings.push(...input.warnings);
313
+ this.stepNumber += 1;
314
+ this.currentStepContent = [];
315
+ this.currentStepWarnings = [];
316
+ }
317
+ /**
318
+ * Resolve every delayed promise and close `fullStream`. Idempotent.
319
+ */
320
+ async finish() {
321
+ if (this.settled) return;
322
+ this.settled = true;
323
+ if (this.currentStepContent.length > 0) {
324
+ const trailingStep = new DefaultStepResult({
325
+ callId: generateId(),
326
+ stepNumber: this.stepNumber,
327
+ provider: this.providerName,
328
+ modelId: this.modelId,
329
+ runtimeContext: this.runtimeContext,
330
+ toolsContext: this.toolsContext,
331
+ content: this.currentStepContent,
332
+ finishReason: this.finalFinishReason,
333
+ rawFinishReason: this.finalRawFinishReason,
334
+ usage: createNullLanguageModelUsage(),
335
+ performance: createEmptyPerformance(),
336
+ warnings: this.currentStepWarnings.length > 0 ? this.currentStepWarnings : void 0,
337
+ request: {},
338
+ response: {
339
+ id: generateId(),
340
+ timestamp: /* @__PURE__ */ new Date(),
341
+ modelId: this.modelId,
342
+ messages: []
343
+ },
344
+ providerMetadata: this.finalProviderMetadata
345
+ });
346
+ this.stepsBuffer.push(trailingStep);
347
+ this.currentStepContent = [];
348
+ this.currentStepWarnings = [];
349
+ }
350
+ const finalStep = this.stepsBuffer.length > 0 ? this.stepsBuffer[this.stepsBuffer.length - 1] : new DefaultStepResult({
351
+ callId: generateId(),
352
+ stepNumber: 0,
353
+ provider: this.providerName,
354
+ modelId: this.modelId,
355
+ runtimeContext: this.runtimeContext,
356
+ toolsContext: this.toolsContext,
357
+ content: [],
358
+ finishReason: this.finalFinishReason,
359
+ rawFinishReason: this.finalRawFinishReason,
360
+ usage: createNullLanguageModelUsage(),
361
+ performance: createEmptyPerformance(),
362
+ warnings: void 0,
363
+ request: {},
364
+ response: {
365
+ id: generateId(),
366
+ timestamp: /* @__PURE__ */ new Date(),
367
+ modelId: this.modelId,
368
+ messages: []
369
+ },
370
+ providerMetadata: void 0
371
+ });
372
+ const aggregatedContent = this.stepsBuffer.flatMap((s) => s.content);
373
+ this._content.resolve(
374
+ aggregatedContent
375
+ );
376
+ this._text.resolve(finalStep.text);
377
+ this._reasoning.resolve(
378
+ []
379
+ );
380
+ this._reasoningText.resolve(void 0);
381
+ this._files.resolve(this.stepsBuffer.flatMap((s) => s.files));
382
+ this._sources.resolve(this.stepsBuffer.flatMap((s) => s.sources));
383
+ this._toolCalls.resolve(
384
+ this.stepsBuffer.flatMap((s) => s.toolCalls)
385
+ );
386
+ this._staticToolCalls.resolve(
387
+ this.stepsBuffer.flatMap((s) => s.staticToolCalls)
388
+ );
389
+ this._dynamicToolCalls.resolve(
390
+ this.stepsBuffer.flatMap((s) => s.dynamicToolCalls)
391
+ );
392
+ this._toolResults.resolve(
393
+ this.stepsBuffer.flatMap((s) => s.toolResults)
394
+ );
395
+ this._staticToolResults.resolve(
396
+ this.stepsBuffer.flatMap((s) => s.staticToolResults)
397
+ );
398
+ this._dynamicToolResults.resolve(
399
+ this.stepsBuffer.flatMap((s) => s.dynamicToolResults)
400
+ );
401
+ this._finishReason.resolve(this.finalFinishReason);
402
+ this._rawFinishReason.resolve(this.finalRawFinishReason);
403
+ this._usage.resolve(this.accumulatedUsage);
404
+ this._warnings.resolve(
405
+ this.aggregateWarnings.length > 0 ? this.aggregateWarnings : void 0
406
+ );
407
+ this._steps.resolve(this.stepsBuffer);
408
+ this._finalStep.resolve(finalStep);
409
+ this._request.resolve(finalStep.request);
410
+ this._response.resolve(finalStep.response);
411
+ this._providerMetadata.resolve(this.finalProviderMetadata);
412
+ const responseMessages = await toResponseMessages({
413
+ content: aggregatedContent,
414
+ tools: this.tools
415
+ });
416
+ this._responseMessages.resolve(responseMessages);
417
+ this.fullStreamController.enqueue({
418
+ type: "finish",
419
+ finishReason: this.finalFinishReason,
420
+ rawFinishReason: this.finalRawFinishReason,
421
+ totalUsage: this.accumulatedUsage,
422
+ providerMetadata: this.finalProviderMetadata
423
+ });
424
+ this.fullStreamController.close();
425
+ }
426
+ /**
427
+ * Surface a fatal error as a stream `error` part + reject every delayed
428
+ * promise so awaiting consumers stop hanging. Idempotent.
429
+ */
430
+ fail(error) {
431
+ if (this.settled) return;
432
+ this.settled = true;
433
+ this.fullStreamController.enqueue({
434
+ type: "error",
435
+ error
436
+ });
437
+ this.fullStreamController.close();
438
+ for (const dp of [
439
+ this._content,
440
+ this._text,
441
+ this._reasoning,
442
+ this._reasoningText,
443
+ this._files,
444
+ this._sources,
445
+ this._toolCalls,
446
+ this._staticToolCalls,
447
+ this._dynamicToolCalls,
448
+ this._toolResults,
449
+ this._staticToolResults,
450
+ this._dynamicToolResults,
451
+ this._finishReason,
452
+ this._rawFinishReason,
453
+ this._usage,
454
+ this._warnings,
455
+ this._steps,
456
+ this._finalStep,
457
+ this._request,
458
+ this._response,
459
+ this._responseMessages,
460
+ this._providerMetadata
461
+ ]) {
462
+ try {
463
+ dp.reject(error);
464
+ } catch (e) {
465
+ }
466
+ }
467
+ }
468
+ // ─── Reader-side public surface (StreamTextResult contract) ────────
469
+ get content() {
470
+ return this._content.promise;
471
+ }
472
+ get text() {
473
+ return this._text.promise;
474
+ }
475
+ get reasoning() {
476
+ return this._reasoning.promise;
477
+ }
478
+ get reasoningText() {
479
+ return this._reasoningText.promise;
480
+ }
481
+ get files() {
482
+ return this._files.promise;
483
+ }
484
+ get sources() {
485
+ return this._sources.promise;
486
+ }
487
+ get toolCalls() {
488
+ return this._toolCalls.promise;
489
+ }
490
+ get staticToolCalls() {
491
+ return this._staticToolCalls.promise;
492
+ }
493
+ get dynamicToolCalls() {
494
+ return this._dynamicToolCalls.promise;
495
+ }
496
+ get toolResults() {
497
+ return this._toolResults.promise;
498
+ }
499
+ get staticToolResults() {
500
+ return this._staticToolResults.promise;
501
+ }
502
+ get dynamicToolResults() {
503
+ return this._dynamicToolResults.promise;
504
+ }
505
+ get finishReason() {
506
+ return this._finishReason.promise;
507
+ }
508
+ get rawFinishReason() {
509
+ return this._rawFinishReason.promise;
510
+ }
511
+ get usage() {
512
+ return this._usage.promise;
513
+ }
514
+ get totalUsage() {
515
+ return this._usage.promise;
516
+ }
517
+ get warnings() {
518
+ return this._warnings.promise;
519
+ }
520
+ get steps() {
521
+ return this._steps.promise;
522
+ }
523
+ get finalStep() {
524
+ return this._finalStep.promise;
525
+ }
526
+ get request() {
527
+ return this._request.promise;
528
+ }
529
+ get response() {
530
+ return this._response.promise;
531
+ }
532
+ get responseMessages() {
533
+ return this._responseMessages.promise;
534
+ }
535
+ get providerMetadata() {
536
+ return this._providerMetadata.promise;
537
+ }
538
+ // Output-specification surfaces are not yet supported.
539
+ get experimental_partialOutputStream() {
540
+ throw notSupportedYet("partial output stream");
541
+ }
542
+ get partialOutputStream() {
543
+ throw notSupportedYet("partial output stream");
544
+ }
545
+ get elementStream() {
546
+ throw notSupportedYet("element stream");
547
+ }
548
+ get output() {
549
+ throw notSupportedYet("structured output");
550
+ }
551
+ async consumeStream() {
552
+ const reader = this.fullStream.getReader();
553
+ try {
554
+ while (true) {
555
+ const { done } = await reader.read();
556
+ if (done) return;
557
+ }
558
+ } finally {
559
+ reader.releaseLock();
560
+ }
561
+ }
562
+ toUIMessageStream({
563
+ originalMessages,
564
+ generateMessageId,
565
+ onFinish,
566
+ messageMetadata,
567
+ sendReasoning,
568
+ sendSources,
569
+ sendStart,
570
+ sendFinish,
571
+ onError
572
+ } = {}) {
573
+ return createAsyncIterableStream(
574
+ toUIMessageStreamHelper({
575
+ stream: this.stream,
576
+ tools: this.tools,
577
+ originalMessages,
578
+ generateMessageId,
579
+ onFinish,
580
+ messageMetadata,
581
+ sendReasoning,
582
+ sendSources,
583
+ sendStart,
584
+ sendFinish,
585
+ onError
586
+ })
587
+ );
588
+ }
589
+ pipeUIMessageStreamToResponse() {
590
+ throw notSupportedYet("pipeUIMessageStreamToResponse");
591
+ }
592
+ pipeTextStreamToResponse() {
593
+ throw notSupportedYet("pipeTextStreamToResponse");
594
+ }
595
+ toUIMessageStreamResponse({
596
+ originalMessages,
597
+ generateMessageId,
598
+ onFinish,
599
+ messageMetadata,
600
+ sendReasoning,
601
+ sendSources,
602
+ sendStart,
603
+ sendFinish,
604
+ onError,
605
+ ...init
606
+ } = {}) {
607
+ return createUIMessageStreamResponse({
608
+ stream: this.toUIMessageStream({
609
+ originalMessages,
610
+ generateMessageId,
611
+ onFinish,
612
+ messageMetadata,
613
+ sendReasoning,
614
+ sendSources,
615
+ sendStart,
616
+ sendFinish,
617
+ onError
618
+ }),
619
+ ...init
620
+ });
621
+ }
622
+ toTextStreamResponse() {
623
+ throw notSupportedYet("toTextStreamResponse");
624
+ }
625
+ // ─── Helpers ────────────────────────────────────────────────────────
626
+ appendToCurrentStepContent(part) {
627
+ switch (part.type) {
628
+ case "text-delta": {
629
+ const last = this.currentStepContent[this.currentStepContent.length - 1];
630
+ if (last && last.type === "text") {
631
+ last.text += part.text;
632
+ } else {
633
+ this.currentStepContent.push({
634
+ type: "text",
635
+ text: part.text
636
+ });
637
+ }
638
+ return;
639
+ }
640
+ case "tool-call":
641
+ this.currentStepContent.push({
642
+ ...part
643
+ });
644
+ return;
645
+ case "tool-approval-request":
646
+ this.currentStepContent.push({
647
+ ...part
648
+ });
649
+ return;
650
+ case "tool-approval-response":
651
+ this.currentStepContent.push({
652
+ ...part
653
+ });
654
+ return;
655
+ case "tool-result":
656
+ this.currentStepContent.push({
657
+ ...part
658
+ });
659
+ return;
660
+ default:
661
+ return;
662
+ }
663
+ }
664
+ };
665
+ function createEmptyPerformance() {
666
+ return {
667
+ effectiveOutputTokensPerSecond: 0,
668
+ outputTokensPerSecond: void 0,
669
+ inputTokensPerSecond: void 0,
670
+ effectiveTotalTokensPerSecond: 0,
671
+ stepTimeMs: 0,
672
+ responseTimeMs: 0,
673
+ toolExecutionMs: {},
674
+ timeToFirstOutputMs: void 0
675
+ };
676
+ }
677
+ function notSupportedYet(feature) {
678
+ return new Error(
679
+ `HarnessAgent: ${feature} is not implemented yet. Track the foundation review for follow-up.`
680
+ );
681
+ }
682
+
683
+ // src/agent/internal/translate-stream-part.ts
684
+ import { generateId as generateId2 } from "@ai-sdk/provider-utils";
685
+ function translateStreamPart(event) {
686
+ switch (event.type) {
687
+ case "stream-start":
688
+ return [];
689
+ case "text-start":
690
+ return [
691
+ {
692
+ type: "text-start",
693
+ id: event.id,
694
+ providerMetadata: event.harnessMetadata
695
+ }
696
+ ];
697
+ case "text-delta":
698
+ return [
699
+ {
700
+ type: "text-delta",
701
+ id: event.id,
702
+ text: event.delta,
703
+ providerMetadata: event.harnessMetadata
704
+ }
705
+ ];
706
+ case "text-end":
707
+ return [
708
+ {
709
+ type: "text-end",
710
+ id: event.id,
711
+ providerMetadata: event.harnessMetadata
712
+ }
713
+ ];
714
+ case "reasoning-start":
715
+ return [
716
+ {
717
+ type: "reasoning-start",
718
+ id: event.id,
719
+ providerMetadata: event.harnessMetadata
720
+ }
721
+ ];
722
+ case "reasoning-delta":
723
+ return [
724
+ {
725
+ type: "reasoning-delta",
726
+ id: event.id,
727
+ text: event.delta,
728
+ providerMetadata: event.harnessMetadata
729
+ }
730
+ ];
731
+ case "reasoning-end":
732
+ return [
733
+ {
734
+ type: "reasoning-end",
735
+ id: event.id,
736
+ providerMetadata: event.harnessMetadata
737
+ }
738
+ ];
739
+ case "tool-call":
740
+ return [];
741
+ case "tool-approval-request":
742
+ return [];
743
+ case "tool-result":
744
+ return [
745
+ {
746
+ type: "tool-result",
747
+ toolCallId: event.toolCallId,
748
+ toolName: event.toolName,
749
+ input: void 0,
750
+ output: event.result,
751
+ ...event.preliminary !== void 0 ? { preliminary: event.preliminary } : {},
752
+ ...event.providerMetadata !== void 0 ? { providerMetadata: event.providerMetadata } : {}
753
+ }
754
+ ];
755
+ case "file-change": {
756
+ const toolCallId = `harness-file-change-${generateId2()}`;
757
+ const payload = { event: event.event, path: event.path };
758
+ return [
759
+ {
760
+ type: "tool-call",
761
+ toolCallId,
762
+ toolName: "fileChange",
763
+ input: payload,
764
+ dynamic: true,
765
+ providerExecuted: true,
766
+ ...event.harnessMetadata !== void 0 ? { providerMetadata: event.harnessMetadata } : {}
767
+ },
768
+ {
769
+ type: "tool-result",
770
+ toolCallId,
771
+ toolName: "fileChange",
772
+ input: payload,
773
+ output: payload,
774
+ dynamic: true,
775
+ providerExecuted: true,
776
+ ...event.harnessMetadata !== void 0 ? { providerMetadata: event.harnessMetadata } : {}
777
+ }
778
+ ];
779
+ }
780
+ case "compaction": {
781
+ const toolCallId = `harness-compaction-${generateId2()}`;
782
+ const output = {
783
+ trigger: event.trigger,
784
+ summary: event.summary,
785
+ ...event.tokensBefore !== void 0 ? { tokensBefore: event.tokensBefore } : {},
786
+ ...event.tokensAfter !== void 0 ? { tokensAfter: event.tokensAfter } : {}
787
+ };
788
+ return [
789
+ {
790
+ type: "tool-call",
791
+ toolCallId,
792
+ toolName: "compaction",
793
+ input: {},
794
+ dynamic: true,
795
+ providerExecuted: true,
796
+ ...event.harnessMetadata !== void 0 ? { providerMetadata: event.harnessMetadata } : {}
797
+ },
798
+ {
799
+ type: "tool-result",
800
+ toolCallId,
801
+ toolName: "compaction",
802
+ input: {},
803
+ output,
804
+ dynamic: true,
805
+ providerExecuted: true,
806
+ ...event.harnessMetadata !== void 0 ? { providerMetadata: event.harnessMetadata } : {}
807
+ }
808
+ ];
809
+ }
810
+ case "error":
811
+ return [{ type: "error", error: event.error }];
812
+ case "raw":
813
+ return [
814
+ { type: "raw", rawValue: event.rawValue }
815
+ ];
816
+ case "finish-step":
817
+ case "finish":
818
+ return [];
819
+ }
820
+ }
821
+
822
+ // src/agent/internal/strip-work-dir.ts
823
+ function stripWorkDir(part, sessionWorkDir) {
824
+ if (sessionWorkDir.length === 0) return part;
825
+ switch (part.type) {
826
+ case "tool-call":
827
+ return { ...part, input: stripString(part.input, sessionWorkDir) };
828
+ case "tool-result":
829
+ return {
830
+ ...part,
831
+ result: stripDeep(part.result, sessionWorkDir)
832
+ };
833
+ case "file-change":
834
+ return { ...part, path: stripString(part.path, sessionWorkDir) };
835
+ default:
836
+ return part;
837
+ }
838
+ }
839
+ function stripString(value, workDir) {
840
+ return value.split(`${workDir}/`).join("").split(workDir).join(".");
841
+ }
842
+ function stripDeep(value, workDir) {
843
+ if (typeof value === "string") return stripString(value, workDir);
844
+ if (Array.isArray(value)) return value.map((item) => stripDeep(item, workDir));
845
+ if (value !== null && typeof value === "object") {
846
+ const out = {};
847
+ for (const [key, val] of Object.entries(value)) {
848
+ out[key] = stripDeep(val, workDir);
849
+ }
850
+ return out;
851
+ }
852
+ return value;
853
+ }
854
+
855
+ // src/agent/internal/turn-telemetry.ts
856
+ import { generateId as generateId3 } from "@ai-sdk/provider-utils";
857
+ import { createTelemetryDispatcher } from "ai/internal";
858
+ var NOOP = {
859
+ start() {
860
+ },
861
+ ensureStepOpen() {
862
+ },
863
+ stepFinish() {
864
+ },
865
+ toolStart() {
866
+ },
867
+ toolEnd() {
868
+ },
869
+ end() {
870
+ },
871
+ error() {
872
+ }
873
+ };
874
+ function createTurnTelemetry(opts) {
875
+ var _a3;
876
+ if (opts.telemetry == null) return NOOP;
877
+ const dispatcher = createTelemetryDispatcher({ telemetry: opts.telemetry });
878
+ const callId = generateId3();
879
+ const provider = opts.harnessId;
880
+ let modelId = (_a3 = opts.modelId) != null ? _a3 : "";
881
+ const runtimeContext = opts.runtimeContext;
882
+ const inputMessages = [
883
+ { role: "user", content: opts.promptText }
884
+ ];
885
+ let started = false;
886
+ let stepOpen = false;
887
+ let stepNumber = 0;
888
+ let ended = false;
889
+ const openTools = /* @__PURE__ */ new Map();
890
+ const cast = (event) => event;
891
+ const fireStart = () => {
892
+ var _a4;
893
+ if (started) return;
894
+ started = true;
895
+ (_a4 = dispatcher.onStart) == null ? void 0 : _a4.call(
896
+ dispatcher,
897
+ cast({
898
+ callId,
899
+ operationId: "ai.harness",
900
+ provider,
901
+ modelId,
902
+ tools: void 0,
903
+ toolChoice: void 0,
904
+ activeTools: void 0,
905
+ maxRetries: 0,
906
+ timeout: void 0,
907
+ headers: void 0,
908
+ providerOptions: void 0,
909
+ output: void 0,
910
+ toolsContext: void 0,
911
+ runtimeContext,
912
+ instructions: opts.instructions,
913
+ messages: inputMessages
914
+ })
915
+ );
916
+ };
917
+ const start = (overrideModelId) => {
918
+ if (started) return;
919
+ if (overrideModelId) modelId = overrideModelId;
920
+ fireStart();
921
+ };
922
+ const ensureStepOpen = () => {
923
+ var _a4, _b3;
924
+ if (!started) fireStart();
925
+ if (stepOpen || ended) return;
926
+ stepOpen = true;
927
+ (_a4 = dispatcher.onStepStart) == null ? void 0 : _a4.call(
928
+ dispatcher,
929
+ cast({
930
+ callId,
931
+ provider,
932
+ modelId,
933
+ stepNumber,
934
+ tools: void 0,
935
+ toolChoice: void 0,
936
+ activeTools: void 0,
937
+ steps: new Array(stepNumber),
938
+ providerOptions: void 0,
939
+ output: void 0,
940
+ runtimeContext,
941
+ messages: inputMessages
942
+ })
943
+ );
944
+ (_b3 = dispatcher.onLanguageModelCallStart) == null ? void 0 : _b3.call(
945
+ dispatcher,
946
+ cast({
947
+ callId,
948
+ provider,
949
+ modelId,
950
+ messages: inputMessages,
951
+ tools: void 0
952
+ })
953
+ );
954
+ };
955
+ const inferenceEnd = (info) => {
956
+ var _a4;
957
+ (_a4 = dispatcher.onLanguageModelCallEnd) == null ? void 0 : _a4.call(
958
+ dispatcher,
959
+ cast({
960
+ callId,
961
+ finishReason: info.finishReason,
962
+ responseId: callId,
963
+ usage: info.usage,
964
+ content: info.content
965
+ })
966
+ );
967
+ };
968
+ const closeOpenTools = () => {
969
+ var _a4;
970
+ for (const call of openTools.values()) {
971
+ (_a4 = dispatcher.onToolExecutionEnd) == null ? void 0 : _a4.call(
972
+ dispatcher,
973
+ cast({
974
+ callId,
975
+ toolExecutionMs: 0,
976
+ messages: [],
977
+ toolCall: {
978
+ type: "tool-call",
979
+ toolCallId: call.toolCallId,
980
+ toolName: call.toolName,
981
+ input: call.input,
982
+ dynamic: true
983
+ },
984
+ toolContext: void 0,
985
+ toolOutput: { type: "error", error: new Error("tool span unclosed") }
986
+ })
987
+ );
988
+ }
989
+ openTools.clear();
990
+ };
991
+ return {
992
+ start,
993
+ ensureStepOpen,
994
+ stepFinish(info) {
995
+ var _a4, _b3;
996
+ if (!stepOpen) return;
997
+ const content = (_a4 = info.content) != null ? _a4 : [];
998
+ closeOpenTools();
999
+ inferenceEnd({
1000
+ finishReason: info.finishReason,
1001
+ usage: info.usage,
1002
+ content
1003
+ });
1004
+ (_b3 = dispatcher.onStepEnd) == null ? void 0 : _b3.call(
1005
+ dispatcher,
1006
+ cast({
1007
+ callId,
1008
+ finishReason: info.finishReason,
1009
+ usage: info.usage,
1010
+ providerMetadata: info.providerMetadata,
1011
+ content,
1012
+ response: {
1013
+ id: callId,
1014
+ modelId,
1015
+ timestamp: /* @__PURE__ */ new Date(0),
1016
+ messages: []
1017
+ }
1018
+ })
1019
+ );
1020
+ stepOpen = false;
1021
+ stepNumber += 1;
1022
+ },
1023
+ toolStart(call) {
1024
+ var _a4;
1025
+ ensureStepOpen();
1026
+ openTools.set(call.toolCallId, call);
1027
+ (_a4 = dispatcher.onToolExecutionStart) == null ? void 0 : _a4.call(
1028
+ dispatcher,
1029
+ cast({
1030
+ callId,
1031
+ messages: [],
1032
+ toolCall: {
1033
+ type: "tool-call",
1034
+ toolCallId: call.toolCallId,
1035
+ toolName: call.toolName,
1036
+ input: call.input,
1037
+ dynamic: true
1038
+ },
1039
+ toolContext: void 0
1040
+ })
1041
+ );
1042
+ },
1043
+ toolEnd(toolCallId, output) {
1044
+ var _a4;
1045
+ const call = openTools.get(toolCallId);
1046
+ if (call == null) return;
1047
+ openTools.delete(toolCallId);
1048
+ (_a4 = dispatcher.onToolExecutionEnd) == null ? void 0 : _a4.call(
1049
+ dispatcher,
1050
+ cast({
1051
+ callId,
1052
+ toolExecutionMs: 0,
1053
+ messages: [],
1054
+ toolCall: {
1055
+ type: "tool-call",
1056
+ toolCallId: call.toolCallId,
1057
+ toolName: call.toolName,
1058
+ input: call.input,
1059
+ dynamic: true
1060
+ },
1061
+ toolContext: void 0,
1062
+ toolOutput: output.ok ? { type: "tool-result", output: output.output } : { type: "error", error: output.error }
1063
+ })
1064
+ );
1065
+ },
1066
+ end(info) {
1067
+ var _a4, _b3;
1068
+ if (ended) return;
1069
+ if (!started) fireStart();
1070
+ if (stepOpen) {
1071
+ closeOpenTools();
1072
+ inferenceEnd({
1073
+ finishReason: info.finishReason,
1074
+ usage: info.usage,
1075
+ content: []
1076
+ });
1077
+ (_a4 = dispatcher.onStepEnd) == null ? void 0 : _a4.call(
1078
+ dispatcher,
1079
+ cast({
1080
+ callId,
1081
+ finishReason: info.finishReason,
1082
+ usage: info.usage,
1083
+ providerMetadata: void 0,
1084
+ content: [],
1085
+ response: {
1086
+ id: callId,
1087
+ modelId,
1088
+ timestamp: /* @__PURE__ */ new Date(0),
1089
+ messages: []
1090
+ }
1091
+ })
1092
+ );
1093
+ stepOpen = false;
1094
+ }
1095
+ ended = true;
1096
+ (_b3 = dispatcher.onEnd) == null ? void 0 : _b3.call(
1097
+ dispatcher,
1098
+ cast({
1099
+ callId,
1100
+ operationId: "ai.harness",
1101
+ finishReason: info.finishReason,
1102
+ usage: info.usage,
1103
+ totalUsage: info.usage,
1104
+ content: [],
1105
+ steps: new Array(stepNumber),
1106
+ response: {
1107
+ id: callId,
1108
+ modelId,
1109
+ timestamp: /* @__PURE__ */ new Date(0),
1110
+ messages: []
1111
+ },
1112
+ runtimeContext
1113
+ })
1114
+ );
1115
+ },
1116
+ error(err) {
1117
+ var _a4;
1118
+ if (ended) return;
1119
+ if (!started) fireStart();
1120
+ closeOpenTools();
1121
+ ended = true;
1122
+ (_a4 = dispatcher.onError) == null ? void 0 : _a4.call(dispatcher, err);
1123
+ }
1124
+ };
1125
+ }
1126
+
1127
+ // src/agent/internal/permission-mode.ts
1128
+ var DEFAULT_PERMISSION_MODE = "allow-all";
1129
+ function resolvePermissionMode(input) {
1130
+ var _a3;
1131
+ return (_a3 = input.permissionMode) != null ? _a3 : DEFAULT_PERMISSION_MODE;
1132
+ }
1133
+ function permissionModeNeedsBuiltinSupport(input) {
1134
+ return input.permissionMode !== "allow-all";
1135
+ }
1136
+ function resolveCustomToolApproval(input) {
1137
+ var _a3;
1138
+ const status = normalizeToolApprovalStatus({
1139
+ status: (_a3 = input.toolApproval) == null ? void 0 : _a3[input.toolName]
1140
+ });
1141
+ switch (status.type) {
1142
+ case "not-applicable":
1143
+ case "approved":
1144
+ return { type: "allow", reason: status.reason };
1145
+ case "denied":
1146
+ return { type: "deny", reason: status.reason };
1147
+ case "user-approval":
1148
+ return { type: "request" };
1149
+ }
1150
+ }
1151
+ function normalizeToolApprovalStatus(input) {
1152
+ if (input.status === void 0) return { type: "not-applicable" };
1153
+ if (typeof input.status === "string") return { type: input.status };
1154
+ return input.status;
1155
+ }
1156
+
1157
+ // src/agent/internal/run-prompt.ts
1158
+ function runPrompt(input) {
1159
+ var _a3, _b3, _c;
1160
+ const result = new HarnessStreamTextResult({
1161
+ tools: input.tools,
1162
+ runtimeContext: input.runtimeContext,
1163
+ // toolsContext is not configurable for harnesses; pass undefined cast.
1164
+ toolsContext: void 0,
1165
+ harnessId: input.harness.harnessId,
1166
+ sessionId: input.session.sessionId
1167
+ });
1168
+ const pendingToolApprovals = (_a3 = input.pendingToolApprovals) != null ? _a3 : [];
1169
+ const onPendingToolApproval = (_b3 = input.onPendingToolApproval) != null ? _b3 : (() => {
1170
+ });
1171
+ const onToolApprovalSettled = (_c = input.onToolApprovalSettled) != null ? _c : (() => {
1172
+ });
1173
+ const telemetry = createTurnTelemetry({
1174
+ telemetry: input.telemetry,
1175
+ harnessId: input.harness.harnessId,
1176
+ modelId: input.session.modelId,
1177
+ instructions: input.instructions,
1178
+ promptText: input.prompt != null ? promptToText(input.prompt) : "",
1179
+ runtimeContext: input.runtimeContext
1180
+ });
1181
+ const done = (async () => {
1182
+ var _a4, _b4, _c2, _d, _e, _f, _g;
1183
+ let bridge;
1184
+ try {
1185
+ bridge = await toHarnessStream({
1186
+ invoke: input.mode === "continue" ? (emit) => input.session.doContinueTurn({
1187
+ tools: input.toolSpecs,
1188
+ abortSignal: input.abortSignal,
1189
+ emit
1190
+ }) : (emit) => {
1191
+ if (input.prompt == null) {
1192
+ throw new Error(
1193
+ 'runPrompt: `prompt` is required for mode "prompt".'
1194
+ );
1195
+ }
1196
+ return input.session.doPromptTurn({
1197
+ prompt: input.prompt,
1198
+ tools: input.toolSpecs,
1199
+ instructions: input.instructions,
1200
+ abortSignal: input.abortSignal,
1201
+ emit
1202
+ });
1203
+ }
1204
+ });
1205
+ } catch (err) {
1206
+ telemetry.error(err);
1207
+ result.fail(err);
1208
+ return;
1209
+ }
1210
+ const { stream, control } = bridge;
1211
+ const reader = stream.getReader();
1212
+ const toolCallsByToolCallId = /* @__PURE__ */ new Map();
1213
+ const rawToolCallsByToolCallId = /* @__PURE__ */ new Map();
1214
+ const pendingApprovalsByApprovalId = new Map(
1215
+ pendingToolApprovals.map((approval) => [approval.approvalId, approval])
1216
+ );
1217
+ const pendingApprovalsByToolCallId = new Map(
1218
+ pendingToolApprovals.map((approval) => [approval.toolCallId, approval])
1219
+ );
1220
+ const continuationsByApprovalId = new Map(
1221
+ ((_a4 = input.toolApprovalContinuations) != null ? _a4 : []).map((continuation) => [
1222
+ continuation.approvalResponse.approvalId,
1223
+ continuation
1224
+ ])
1225
+ );
1226
+ const settledApprovalToolCallIds = /* @__PURE__ */ new Set();
1227
+ let stepText = "";
1228
+ let stepReasoning = "";
1229
+ let stepToolCalls = [];
1230
+ const buildStepContent = () => {
1231
+ const parts = [];
1232
+ if (stepText) parts.push({ type: "text", text: stepText });
1233
+ if (stepReasoning) parts.push({ type: "reasoning", text: stepReasoning });
1234
+ parts.push(...stepToolCalls);
1235
+ return parts;
1236
+ };
1237
+ const resetStepContent = () => {
1238
+ stepText = "";
1239
+ stepReasoning = "";
1240
+ stepToolCalls = [];
1241
+ };
1242
+ const zeroUsage = {
1243
+ inputTokens: {
1244
+ total: void 0,
1245
+ noCache: void 0,
1246
+ cacheRead: void 0,
1247
+ cacheWrite: void 0
1248
+ },
1249
+ outputTokens: {
1250
+ total: void 0,
1251
+ text: void 0,
1252
+ reasoning: void 0
1253
+ }
1254
+ };
1255
+ const toolCallsFinishReason = {
1256
+ unified: "tool-calls",
1257
+ raw: void 0
1258
+ };
1259
+ const finishForToolApprovalPause = async () => {
1260
+ telemetry.stepFinish({
1261
+ finishReason: toolCallsFinishReason,
1262
+ usage: zeroUsage,
1263
+ content: buildStepContent()
1264
+ });
1265
+ resetStepContent();
1266
+ result.finishStep({
1267
+ finishReason: toolCallsFinishReason,
1268
+ usage: zeroUsage,
1269
+ providerMetadata: void 0,
1270
+ warnings: []
1271
+ });
1272
+ telemetry.end({
1273
+ finishReason: toolCallsFinishReason,
1274
+ usage: zeroUsage
1275
+ });
1276
+ await result.finish();
1277
+ };
1278
+ const enqueueApprovalRequest = (approval) => {
1279
+ result.enqueue({
1280
+ type: "tool-approval-request",
1281
+ approvalId: approval.approvalId,
1282
+ toolCall: approval.toolCall,
1283
+ ...approval.isAutomatic !== void 0 ? { isAutomatic: approval.isAutomatic } : {}
1284
+ });
1285
+ };
1286
+ const enqueueAutomaticApprovalResponse = (input2) => {
1287
+ result.enqueue({
1288
+ type: "tool-approval-response",
1289
+ approvalId: input2.approvalId,
1290
+ toolCall: input2.toolCall,
1291
+ approved: input2.approved,
1292
+ ...input2.reason !== void 0 ? { reason: input2.reason } : {},
1293
+ ...input2.providerExecuted !== void 0 ? { providerExecuted: input2.providerExecuted } : {}
1294
+ });
1295
+ };
1296
+ const enqueueApprovalResponse = (approval, continuation) => {
1297
+ result.enqueue({
1298
+ type: "tool-approval-response",
1299
+ approvalId: approval.approvalId,
1300
+ toolCall: continuation.toolCall,
1301
+ approved: continuation.approvalResponse.approved,
1302
+ ...continuation.approvalResponse.reason !== void 0 ? { reason: continuation.approvalResponse.reason } : {},
1303
+ ...approval.providerExecuted !== void 0 ? { providerExecuted: approval.providerExecuted } : {}
1304
+ });
1305
+ };
1306
+ const processPendingApprovalContinuation = async (approval, continuation) => {
1307
+ var _a5;
1308
+ enqueueApprovalResponse(approval, continuation);
1309
+ onToolApprovalSettled(approval.approvalId);
1310
+ pendingApprovalsByApprovalId.delete(approval.approvalId);
1311
+ pendingApprovalsByToolCallId.delete(approval.toolCallId);
1312
+ settledApprovalToolCallIds.add(approval.toolCallId);
1313
+ if (approval.kind === "builtin") {
1314
+ if (control.submitToolApproval == null) {
1315
+ throw new Error(
1316
+ `Harness '${input.harness.harnessId}' emitted a built-in tool approval request but does not support approval responses.`
1317
+ );
1318
+ }
1319
+ await control.submitToolApproval({
1320
+ approvalId: approval.approvalId,
1321
+ approved: continuation.approvalResponse.approved,
1322
+ reason: continuation.approvalResponse.reason
1323
+ });
1324
+ return;
1325
+ }
1326
+ if (!continuation.approvalResponse.approved) {
1327
+ await control.submitToolResult({
1328
+ toolCallId: approval.toolCallId,
1329
+ output: {
1330
+ type: "execution-denied",
1331
+ reason: continuation.approvalResponse.reason
1332
+ }
1333
+ });
1334
+ return;
1335
+ }
1336
+ const rawToolCall = (_a5 = rawToolCallsByToolCallId.get(approval.toolCallId)) != null ? _a5 : {
1337
+ type: "tool-call",
1338
+ toolCallId: approval.toolCallId,
1339
+ toolName: approval.toolName,
1340
+ input: approval.input
1341
+ };
1342
+ const outcome = await maybeExecuteHostTool({
1343
+ event: rawToolCall,
1344
+ tools: input.tools,
1345
+ sandboxSession: input.sandboxSession,
1346
+ abortSignal: input.abortSignal,
1347
+ control,
1348
+ onPreliminaryResult: (preliminaryOutput) => {
1349
+ const stripped = stripWorkDir(
1350
+ {
1351
+ type: "tool-result",
1352
+ toolCallId: rawToolCall.toolCallId,
1353
+ toolName: rawToolCall.toolName,
1354
+ result: preliminaryOutput
1355
+ },
1356
+ input.sessionWorkDir
1357
+ );
1358
+ result.enqueue({
1359
+ type: "tool-result",
1360
+ toolCallId: rawToolCall.toolCallId,
1361
+ toolName: rawToolCall.toolName,
1362
+ input: void 0,
1363
+ output: stripped.result,
1364
+ preliminary: true
1365
+ });
1366
+ }
1367
+ });
1368
+ telemetry.toolEnd(rawToolCall.toolCallId, outcome);
1369
+ };
1370
+ try {
1371
+ for (const approval of pendingToolApprovals) {
1372
+ const continuation = continuationsByApprovalId.get(approval.approvalId);
1373
+ if (continuation != null) {
1374
+ await processPendingApprovalContinuation(approval, continuation);
1375
+ }
1376
+ }
1377
+ while (true) {
1378
+ const { value, done: done2 } = await reader.read();
1379
+ if (done2) break;
1380
+ if (value == null) continue;
1381
+ if (value.type === "stream-start") {
1382
+ telemetry.start((_b4 = value.modelId) != null ? _b4 : input.session.modelId);
1383
+ }
1384
+ if (value.type !== "stream-start" && value.type !== "finish-step" && value.type !== "finish" && value.type !== "error") {
1385
+ telemetry.ensureStepOpen();
1386
+ }
1387
+ const displayValue = stripWorkDir(value, input.sessionWorkDir);
1388
+ const settledApprovalToolCallReplay = displayValue.type === "tool-call" && !displayValue.providerExecuted && settledApprovalToolCallIds.has(displayValue.toolCallId);
1389
+ if (settledApprovalToolCallReplay) {
1390
+ continue;
1391
+ }
1392
+ for (const part of translateStreamPart(displayValue)) {
1393
+ result.enqueue(part);
1394
+ }
1395
+ if (displayValue.type === "tool-call") {
1396
+ const parsed = await validateToolCall({
1397
+ event: displayValue,
1398
+ tools: input.tools
1399
+ });
1400
+ const parsedToolCall = asToolCallTextStreamPart({ part: parsed });
1401
+ rawToolCallsByToolCallId.set(displayValue.toolCallId, displayValue);
1402
+ toolCallsByToolCallId.set(displayValue.toolCallId, parsedToolCall);
1403
+ result.enqueue(parsed);
1404
+ }
1405
+ if (value.type === "text-delta") {
1406
+ stepText += value.delta;
1407
+ } else if (value.type === "reasoning-delta") {
1408
+ stepReasoning += value.delta;
1409
+ }
1410
+ if (value.type === "tool-call") {
1411
+ stepToolCalls.push({
1412
+ type: "tool-call",
1413
+ toolCallId: value.toolCallId,
1414
+ toolName: value.toolName,
1415
+ input: value.input
1416
+ });
1417
+ telemetry.toolStart({
1418
+ toolCallId: value.toolCallId,
1419
+ toolName: value.toolName,
1420
+ input: value.input
1421
+ });
1422
+ }
1423
+ if (value.type === "tool-result") {
1424
+ telemetry.toolEnd(
1425
+ value.toolCallId,
1426
+ value.isError ? { ok: false, error: value.result } : { ok: true, output: value.result }
1427
+ );
1428
+ }
1429
+ if (value.type === "tool-approval-request") {
1430
+ const toolCall = toolCallsByToolCallId.get(value.toolCallId);
1431
+ if (toolCall == null) {
1432
+ throw new Error(
1433
+ `Harness '${input.harness.harnessId}' emitted approval request '${value.approvalId}' for unknown tool call '${value.toolCallId}'.`
1434
+ );
1435
+ }
1436
+ const rawToolCall = rawToolCallsByToolCallId.get(value.toolCallId);
1437
+ const pendingApproval = (_e = pendingApprovalsByApprovalId.get(value.approvalId)) != null ? _e : {
1438
+ approvalId: value.approvalId,
1439
+ toolCallId: value.toolCallId,
1440
+ toolName: toolCall.toolName,
1441
+ input: (_c2 = rawToolCall == null ? void 0 : rawToolCall.input) != null ? _c2 : JSON.stringify(toolCall.input),
1442
+ kind: "builtin",
1443
+ providerExecuted: (_d = rawToolCall == null ? void 0 : rawToolCall.providerExecuted) != null ? _d : true,
1444
+ ...(rawToolCall == null ? void 0 : rawToolCall.nativeName) !== void 0 ? { nativeName: rawToolCall.nativeName } : {}
1445
+ };
1446
+ pendingApprovalsByApprovalId.set(
1447
+ pendingApproval.approvalId,
1448
+ pendingApproval
1449
+ );
1450
+ pendingApprovalsByToolCallId.set(
1451
+ pendingApproval.toolCallId,
1452
+ pendingApproval
1453
+ );
1454
+ const continuation = continuationsByApprovalId.get(
1455
+ pendingApproval.approvalId
1456
+ );
1457
+ if (continuation != null) {
1458
+ await processPendingApprovalContinuation(
1459
+ pendingApproval,
1460
+ continuation
1461
+ );
1462
+ continue;
1463
+ }
1464
+ onPendingToolApproval(pendingApproval);
1465
+ enqueueApprovalRequest({
1466
+ approvalId: pendingApproval.approvalId,
1467
+ toolCall
1468
+ });
1469
+ await finishForToolApprovalPause();
1470
+ return;
1471
+ }
1472
+ if (value.type === "finish-step") {
1473
+ telemetry.stepFinish({
1474
+ finishReason: value.finishReason,
1475
+ usage: value.usage,
1476
+ providerMetadata: value.harnessMetadata,
1477
+ content: buildStepContent()
1478
+ });
1479
+ resetStepContent();
1480
+ result.finishStep({
1481
+ finishReason: value.finishReason,
1482
+ usage: value.usage,
1483
+ providerMetadata: value.harnessMetadata,
1484
+ warnings: []
1485
+ });
1486
+ }
1487
+ if (value.type === "finish") {
1488
+ telemetry.end({
1489
+ finishReason: value.finishReason,
1490
+ usage: value.totalUsage
1491
+ });
1492
+ }
1493
+ if (value.type === "tool-call" && !value.providerExecuted) {
1494
+ const toolCall = value;
1495
+ const parsedToolCall = toolCallsByToolCallId.get(toolCall.toolCallId);
1496
+ if (parsedToolCall == null) {
1497
+ throw new Error(
1498
+ `Harness '${input.harness.harnessId}' could not find parsed tool call '${toolCall.toolCallId}' for custom tool approval.`
1499
+ );
1500
+ }
1501
+ const customToolApprovalDecision = resolveCustomToolApproval({
1502
+ toolName: toolCall.toolName,
1503
+ toolApproval: input.toolApproval
1504
+ });
1505
+ if (customToolApprovalDecision.type === "deny") {
1506
+ const approvalId = generateId4();
1507
+ enqueueApprovalRequest({
1508
+ approvalId,
1509
+ toolCall: parsedToolCall,
1510
+ isAutomatic: true
1511
+ });
1512
+ enqueueAutomaticApprovalResponse({
1513
+ approvalId,
1514
+ toolCall: parsedToolCall,
1515
+ approved: false,
1516
+ reason: customToolApprovalDecision.reason,
1517
+ providerExecuted: false
1518
+ });
1519
+ const output = {
1520
+ type: "execution-denied",
1521
+ reason: customToolApprovalDecision.reason
1522
+ };
1523
+ await control.submitToolResult({
1524
+ toolCallId: toolCall.toolCallId,
1525
+ output
1526
+ });
1527
+ telemetry.toolEnd(toolCall.toolCallId, { ok: true, output });
1528
+ continue;
1529
+ }
1530
+ const pendingApproval = (_f = pendingApprovalsByToolCallId.get(toolCall.toolCallId)) != null ? _f : customToolApprovalDecision.type === "request" ? {
1531
+ approvalId: generateId4(),
1532
+ toolCallId: toolCall.toolCallId,
1533
+ toolName: toolCall.toolName,
1534
+ input: toolCall.input,
1535
+ kind: "custom",
1536
+ providerExecuted: false,
1537
+ ...toolCall.nativeName !== void 0 ? { nativeName: toolCall.nativeName } : {}
1538
+ } : void 0;
1539
+ if (pendingApproval != null) {
1540
+ pendingApprovalsByApprovalId.set(
1541
+ pendingApproval.approvalId,
1542
+ pendingApproval
1543
+ );
1544
+ pendingApprovalsByToolCallId.set(
1545
+ pendingApproval.toolCallId,
1546
+ pendingApproval
1547
+ );
1548
+ const continuation = continuationsByApprovalId.get(
1549
+ pendingApproval.approvalId
1550
+ );
1551
+ if (continuation != null) {
1552
+ await processPendingApprovalContinuation(
1553
+ pendingApproval,
1554
+ continuation
1555
+ );
1556
+ continue;
1557
+ }
1558
+ const pendingParsedToolCall = toolCallsByToolCallId.get(
1559
+ pendingApproval.toolCallId
1560
+ );
1561
+ if (pendingParsedToolCall == null) {
1562
+ throw new Error(
1563
+ `Harness '${input.harness.harnessId}' could not find parsed tool call '${pendingApproval.toolCallId}' for approval request '${pendingApproval.approvalId}'.`
1564
+ );
1565
+ }
1566
+ onPendingToolApproval(pendingApproval);
1567
+ enqueueApprovalRequest({
1568
+ approvalId: pendingApproval.approvalId,
1569
+ toolCall: pendingParsedToolCall
1570
+ });
1571
+ await finishForToolApprovalPause();
1572
+ return;
1573
+ }
1574
+ const outcome = await maybeExecuteHostTool({
1575
+ event: toolCall,
1576
+ tools: input.tools,
1577
+ sandboxSession: input.sandboxSession,
1578
+ abortSignal: input.abortSignal,
1579
+ control,
1580
+ onPreliminaryResult: (preliminaryOutput) => {
1581
+ const stripped = stripWorkDir(
1582
+ {
1583
+ type: "tool-result",
1584
+ toolCallId: toolCall.toolCallId,
1585
+ toolName: toolCall.toolName,
1586
+ result: preliminaryOutput
1587
+ },
1588
+ input.sessionWorkDir
1589
+ );
1590
+ result.enqueue({
1591
+ type: "tool-result",
1592
+ toolCallId: toolCall.toolCallId,
1593
+ toolName: toolCall.toolName,
1594
+ input: void 0,
1595
+ output: stripped.result,
1596
+ preliminary: true
1597
+ });
1598
+ }
1599
+ });
1600
+ telemetry.toolEnd(toolCall.toolCallId, outcome);
1601
+ }
1602
+ if (value.type === "error") {
1603
+ telemetry.error(value.error);
1604
+ result.fail(value.error);
1605
+ return;
1606
+ }
1607
+ }
1608
+ (_g = input.onTurnFinished) == null ? void 0 : _g.call(input);
1609
+ await result.finish();
1610
+ } catch (err) {
1611
+ telemetry.error(err);
1612
+ result.fail(err);
1613
+ } finally {
1614
+ reader.releaseLock();
1615
+ }
1616
+ })();
1617
+ done.catch(() => {
1618
+ });
1619
+ return { result, done };
1620
+ }
1621
+ function asToolCallTextStreamPart(input) {
1622
+ if (input.part.type !== "tool-call") {
1623
+ throw new Error(
1624
+ `Expected parsed tool-call stream part, got '${input.part.type}'.`
1625
+ );
1626
+ }
1627
+ return input.part;
1628
+ }
1629
+ async function maybeExecuteHostTool(input) {
1630
+ const tool = input.tools[input.event.toolName];
1631
+ if (!isExecutableTool(tool)) return { ok: true, output: void 0 };
1632
+ const parsed = await safeParseJSON({ text: input.event.input });
1633
+ const args = parsed.success ? parsed.value : input.event.input;
1634
+ try {
1635
+ let output;
1636
+ const stream = executeTool({
1637
+ tool,
1638
+ input: args,
1639
+ options: {
1640
+ toolCallId: input.event.toolCallId,
1641
+ messages: [],
1642
+ abortSignal: input.abortSignal,
1643
+ context: void 0,
1644
+ experimental_sandbox: input.sandboxSession
1645
+ }
1646
+ });
1647
+ for await (const part of stream) {
1648
+ if (part.type === "preliminary") {
1649
+ input.onPreliminaryResult(part.output);
1650
+ } else {
1651
+ output = part.output;
1652
+ }
1653
+ }
1654
+ await input.control.submitToolResult({
1655
+ toolCallId: input.event.toolCallId,
1656
+ output
1657
+ });
1658
+ return { ok: true, output };
1659
+ } catch (err) {
1660
+ await input.control.submitToolResult({
1661
+ toolCallId: input.event.toolCallId,
1662
+ output: { error: String(err) },
1663
+ isError: true
1664
+ });
1665
+ return { ok: false, error: err };
1666
+ }
1667
+ }
1668
+ async function validateToolCall(args) {
1669
+ const { event, tools } = args;
1670
+ const toolCall = {
1671
+ type: "tool-call",
1672
+ toolCallId: event.toolCallId,
1673
+ toolName: event.toolName,
1674
+ input: event.input,
1675
+ ...event.providerExecuted !== void 0 ? { providerExecuted: event.providerExecuted } : {},
1676
+ ...event.providerMetadata !== void 0 ? { providerMetadata: event.providerMetadata } : {}
1677
+ };
1678
+ const parsed = await parseToolCall({
1679
+ toolCall,
1680
+ tools,
1681
+ repairToolCall: void 0,
1682
+ refineToolInput: void 0,
1683
+ instructions: void 0,
1684
+ messages: []
1685
+ });
1686
+ return parsed;
1687
+ }
1688
+ function promptToText(prompt) {
1689
+ if (typeof prompt === "string") return prompt;
1690
+ const content = prompt.content;
1691
+ if (typeof content === "string") return content;
1692
+ if (Array.isArray(content)) {
1693
+ return content.filter(
1694
+ (part) => typeof part === "object" && part != null && part.type === "text"
1695
+ ).map((part) => part.text).join("");
1696
+ }
1697
+ return "";
1698
+ }
1699
+
1700
+ // src/agent/harness-agent-session.ts
1701
+ var HarnessAgentSession = class {
1702
+ constructor(options) {
1703
+ this.pendingToolApprovals = /* @__PURE__ */ new Map();
1704
+ this.sessionState = "active";
1705
+ this.turnSequence = 0;
1706
+ this.activeTurnSequence = 0;
1707
+ var _a3, _b3;
1708
+ this.sessionId = options.sessionId;
1709
+ this.harness = options.harness;
1710
+ this.underlyingSession = options.underlyingSession;
1711
+ this.sandboxSession = options.sandboxSession;
1712
+ this.sandboxProvider = options.sandboxProvider;
1713
+ this.leasedBridgePort = options.leasedBridgePort;
1714
+ this.sessionWorkDir = options.sessionWorkDir;
1715
+ this.toolApproval = options.toolApproval;
1716
+ for (const approval of (_a3 = options.pendingToolApprovals) != null ? _a3 : []) {
1717
+ this.pendingToolApprovals.set(approval.approvalId, approval);
1718
+ }
1719
+ this.turnState = (_b3 = options.turnState) != null ? _b3 : this.pendingToolApprovals.size > 0 ? "awaiting-approval" : "idle";
1720
+ this.isResume = options.underlyingSession.isResume;
1721
+ }
1722
+ /**
1723
+ * Active network sandbox session.
1724
+ *
1725
+ * @internal — accessed by session turn and lifecycle drivers.
1726
+ */
1727
+ getSandboxSession() {
1728
+ if (this.sessionState !== "active" || this.sandboxSession == null) {
1729
+ throw new Error(
1730
+ `Harness session ${this.sessionId} has ended and cannot be reused.`
1731
+ );
1732
+ }
1733
+ return this.sandboxSession;
1734
+ }
1735
+ /**
1736
+ * Working directory the agent runs in for this session. Used to strip the
1737
+ * prefix from absolute paths in stream events before they reach consumers.
1738
+ *
1739
+ * @internal — accessed by session turn drivers.
1740
+ */
1741
+ getSessionWorkDir() {
1742
+ return this.sessionWorkDir;
1743
+ }
1744
+ promptTurn(options) {
1745
+ const session = this.requireReusableSession();
1746
+ this.requirePromptableTurn();
1747
+ const sandboxSession = this.getSandboxSession();
1748
+ const turnId = this.startTrackedTurn();
1749
+ try {
1750
+ const turn = runPrompt({
1751
+ harness: this.harness,
1752
+ session,
1753
+ prompt: options.prompt,
1754
+ instructions: options.instructions,
1755
+ tools: options.tools,
1756
+ toolSpecs: options.toolSpecs,
1757
+ sandboxSession: sandboxSession.restricted(),
1758
+ sessionWorkDir: this.sessionWorkDir,
1759
+ runtimeContext: options.runtimeContext,
1760
+ abortSignal: options.abortSignal,
1761
+ telemetry: options.telemetry,
1762
+ toolApproval: this.toolApproval,
1763
+ pendingToolApprovals: this.getPendingToolApprovals(),
1764
+ onPendingToolApproval: (approval) => {
1765
+ this.pendingToolApprovals.set(approval.approvalId, approval);
1766
+ this.markAwaitingApprovalIfActive();
1767
+ },
1768
+ onToolApprovalSettled: (approvalId) => {
1769
+ this.pendingToolApprovals.delete(approvalId);
1770
+ },
1771
+ onTurnFinished: () => {
1772
+ this.finishTrackedTurn({ turnId });
1773
+ }
1774
+ });
1775
+ this.trackTurnCompletion({ done: turn.done, turnId });
1776
+ return turn;
1777
+ } catch (error) {
1778
+ this.finishTrackedTurn({ turnId });
1779
+ throw error;
1780
+ }
1781
+ }
1782
+ continueTurn(options) {
1783
+ const session = this.requireReusableSession();
1784
+ this.requireContinuableTurn();
1785
+ const sandboxSession = this.getSandboxSession();
1786
+ const turnId = this.startTrackedTurn();
1787
+ try {
1788
+ const turn = runPrompt({
1789
+ harness: this.harness,
1790
+ session,
1791
+ mode: "continue",
1792
+ instructions: options.instructions,
1793
+ tools: options.tools,
1794
+ toolSpecs: options.toolSpecs,
1795
+ sandboxSession: sandboxSession.restricted(),
1796
+ sessionWorkDir: this.sessionWorkDir,
1797
+ runtimeContext: options.runtimeContext,
1798
+ abortSignal: options.abortSignal,
1799
+ telemetry: options.telemetry,
1800
+ toolApproval: this.toolApproval,
1801
+ pendingToolApprovals: this.getPendingToolApprovals(),
1802
+ toolApprovalContinuations: options.toolApprovalContinuations,
1803
+ onPendingToolApproval: (approval) => {
1804
+ this.pendingToolApprovals.set(approval.approvalId, approval);
1805
+ this.markAwaitingApprovalIfActive();
1806
+ },
1807
+ onToolApprovalSettled: (approvalId) => {
1808
+ this.pendingToolApprovals.delete(approvalId);
1809
+ },
1810
+ onTurnFinished: () => {
1811
+ this.finishTrackedTurn({ turnId });
1812
+ }
1813
+ });
1814
+ this.trackTurnCompletion({ done: turn.done, turnId });
1815
+ return turn;
1816
+ } catch (error) {
1817
+ this.finishTrackedTurn({ turnId });
1818
+ throw error;
1819
+ }
1820
+ }
1821
+ /**
1822
+ * Ask the underlying runtime to compact its context. The runtime performs
1823
+ * the compaction itself; when it completes, a `compaction` part appears on
1824
+ * the active (or next) turn's stream. Safe to call between turns for
1825
+ * runtimes whose compaction is session-scoped (e.g. Pi).
1826
+ *
1827
+ * Throws `HarnessCapabilityUnsupportedError` for harnesses that cannot
1828
+ * trigger compaction manually (e.g. Codex, which still auto-compacts under
1829
+ * the hood). Throws if the session has ended.
1830
+ */
1831
+ async compact(customInstructions) {
1832
+ await this.requireReusableSession().doCompact(customInstructions);
1833
+ }
1834
+ /**
1835
+ * Park the session, returning a payload the caller can persist and later
1836
+ * pass to `agent.createSession({ sessionId, resumeFrom })` to reconnect.
1837
+ * The runtime and sandbox keep running; this local session handle becomes
1838
+ * unusable.
1839
+ */
1840
+ async detach() {
1841
+ if (this.sessionState !== "active" || this.underlyingSession == null) {
1842
+ throw new Error(
1843
+ `Harness session ${this.sessionId} is not active and cannot be detached.`
1844
+ );
1845
+ }
1846
+ const session = this.underlyingSession;
1847
+ try {
1848
+ if (this.turnState !== "idle") {
1849
+ return this.toResumeStateWithContinuation({
1850
+ continueFrom: await this.suspendCurrentTurn({ session })
1851
+ });
1852
+ }
1853
+ const raw = await session.doDetach();
1854
+ const validated = await validateLifecycleStateData({
1855
+ harness: this.harness,
1856
+ state: raw,
1857
+ expectedType: "resume-session"
1858
+ });
1859
+ return validated;
1860
+ } finally {
1861
+ this.endLocalHandle({
1862
+ sessionState: "detached",
1863
+ releasePortLease: false
1864
+ });
1865
+ }
1866
+ }
1867
+ /**
1868
+ * Persist enough state to resume later, then stop the runtime and sandbox.
1869
+ * Returns the resume state for a future
1870
+ * `agent.createSession({ sessionId, resumeFrom })` call.
1871
+ */
1872
+ async stop() {
1873
+ if (this.sessionState !== "active" || this.underlyingSession == null) {
1874
+ throw new Error(
1875
+ `Harness session ${this.sessionId} is not active and cannot be stopped.`
1876
+ );
1877
+ }
1878
+ const session = this.underlyingSession;
1879
+ const sandboxSession = this.getSandboxSession();
1880
+ try {
1881
+ if (this.turnState !== "idle") {
1882
+ return this.toResumeStateWithContinuation({
1883
+ continueFrom: await this.suspendCurrentTurn({ session })
1884
+ });
1885
+ }
1886
+ const raw = await session.doStop();
1887
+ const validated = await validateLifecycleStateData({
1888
+ harness: this.harness,
1889
+ state: raw,
1890
+ expectedType: "resume-session"
1891
+ });
1892
+ return validated;
1893
+ } finally {
1894
+ this.endLocalHandle({
1895
+ sessionState: "stopped",
1896
+ releasePortLease: true
1897
+ });
1898
+ await Promise.resolve(sandboxSession.stop()).catch(() => {
1899
+ });
1900
+ }
1901
+ }
1902
+ /**
1903
+ * Stop the runtime and discard resumability. The sandbox is destroyed when
1904
+ * the provider supports destruction; otherwise it is stopped.
1905
+ */
1906
+ async destroy() {
1907
+ var _a3, _b3;
1908
+ if (this.sessionState !== "active") return;
1909
+ const session = this.underlyingSession;
1910
+ const sandboxSession = this.getSandboxSession();
1911
+ this.endLocalHandle({ sessionState: "destroyed", releasePortLease: true });
1912
+ if (session != null) {
1913
+ await Promise.resolve(session.doDestroy()).catch(() => {
1914
+ });
1915
+ }
1916
+ await Promise.resolve(
1917
+ (_b3 = (_a3 = sandboxSession.destroy) == null ? void 0 : _a3.call(sandboxSession)) != null ? _b3 : sandboxSession.stop()
1918
+ ).catch(() => {
1919
+ });
1920
+ }
1921
+ /**
1922
+ * Gracefully freeze the active turn at the slice boundary and return the
1923
+ * continuation payload, **leaving the sandbox/runtime running** so the next
1924
+ * process can continue. Resolves once the in-flight `stream()` /
1925
+ * `continueStream()` has cleanly wound down at a precise cursor (see
1926
+ * `doSuspendTurn`).
1927
+ *
1928
+ * After this call the session is detached. This in-process handle no
1929
+ * longer drives turns; a future slice creates a fresh session from the
1930
+ * returned state. The sandbox is **not** stopped and no port lease is
1931
+ * released, because bridge-backed adapters may still have a live bridge on
1932
+ * that port.
1933
+ */
1934
+ async suspendTurn() {
1935
+ if (this.sessionState !== "active" || this.underlyingSession == null) {
1936
+ throw new Error(
1937
+ `Harness session ${this.sessionId} is not active and cannot be suspended.`
1938
+ );
1939
+ }
1940
+ if (this.turnState === "idle") {
1941
+ throw new Error(
1942
+ `Harness session ${this.sessionId} has no unfinished turn to suspend.`
1943
+ );
1944
+ }
1945
+ const session = this.underlyingSession;
1946
+ try {
1947
+ return await this.suspendCurrentTurn({ session });
1948
+ } finally {
1949
+ this.endLocalHandle({
1950
+ sessionState: "detached",
1951
+ releasePortLease: false
1952
+ });
1953
+ }
1954
+ }
1955
+ getPendingToolApprovals() {
1956
+ return Array.from(this.pendingToolApprovals.values());
1957
+ }
1958
+ addPendingToolApprovals(state) {
1959
+ const pendingToolApprovals = this.getPendingToolApprovals();
1960
+ if (pendingToolApprovals.length === 0) {
1961
+ return {
1962
+ type: state.type,
1963
+ harnessId: state.harnessId,
1964
+ specificationVersion: state.specificationVersion,
1965
+ data: state.data
1966
+ };
1967
+ }
1968
+ return {
1969
+ ...state,
1970
+ pendingToolApprovals
1971
+ };
1972
+ }
1973
+ async suspendCurrentTurn(options) {
1974
+ const raw = await options.session.doSuspendTurn();
1975
+ const validated = await validateLifecycleStateData({
1976
+ harness: this.harness,
1977
+ state: raw,
1978
+ expectedType: "continue-turn"
1979
+ });
1980
+ this.turnState = "suspended";
1981
+ return this.addPendingToolApprovals(validated);
1982
+ }
1983
+ toResumeStateWithContinuation(options) {
1984
+ const { continueFrom } = options;
1985
+ return {
1986
+ type: "resume-session",
1987
+ harnessId: continueFrom.harnessId,
1988
+ specificationVersion: continueFrom.specificationVersion,
1989
+ data: continueFrom.data,
1990
+ continueFrom
1991
+ };
1992
+ }
1993
+ requirePromptableTurn() {
1994
+ if (this.turnState === "idle") return;
1995
+ if (this.turnState === "running") {
1996
+ throw new Error(
1997
+ `Harness session ${this.sessionId} already has a turn in progress.`
1998
+ );
1999
+ }
2000
+ throw new Error(
2001
+ `Harness session ${this.sessionId} has an unfinished turn and must be continued before accepting a new prompt.`
2002
+ );
2003
+ }
2004
+ requireContinuableTurn() {
2005
+ if (this.turnState === "awaiting-approval" || this.turnState === "suspended") {
2006
+ return;
2007
+ }
2008
+ if (this.turnState === "running") {
2009
+ throw new Error(
2010
+ `Harness session ${this.sessionId} already has a turn in progress.`
2011
+ );
2012
+ }
2013
+ throw new Error(
2014
+ `Harness session ${this.sessionId} has no unfinished turn to continue.`
2015
+ );
2016
+ }
2017
+ markAwaitingApprovalIfActive() {
2018
+ if (this.sessionState === "active") {
2019
+ this.turnState = "awaiting-approval";
2020
+ }
2021
+ }
2022
+ startTrackedTurn() {
2023
+ const turnId = ++this.turnSequence;
2024
+ this.activeTurnSequence = turnId;
2025
+ this.turnState = "running";
2026
+ return turnId;
2027
+ }
2028
+ trackTurnCompletion(options) {
2029
+ void Promise.resolve(options.done).finally(() => {
2030
+ this.finishTrackedTurn({ turnId: options.turnId });
2031
+ }).catch(() => {
2032
+ });
2033
+ }
2034
+ finishTrackedTurn(options) {
2035
+ if (this.sessionState !== "active") return;
2036
+ if (this.activeTurnSequence !== options.turnId) return;
2037
+ this.turnState = this.pendingToolApprovals.size > 0 ? "awaiting-approval" : "idle";
2038
+ }
2039
+ endLocalHandle(options) {
2040
+ this.sessionState = options.sessionState;
2041
+ this.underlyingSession = void 0;
2042
+ this.sandboxSession = void 0;
2043
+ if (options.releasePortLease) {
2044
+ this.releasePortLease();
2045
+ }
2046
+ }
2047
+ releasePortLease() {
2048
+ if (this.leasedBridgePort == null) return;
2049
+ releaseBridgePort({
2050
+ poolKey: this.sandboxProvider,
2051
+ sessionId: this.sessionId
2052
+ });
2053
+ this.leasedBridgePort = void 0;
2054
+ }
2055
+ requireReusableSession() {
2056
+ if (this.sessionState !== "active" || this.underlyingSession == null) {
2057
+ throw new Error(
2058
+ `Harness session ${this.sessionId} has ended and cannot be reused.`
2059
+ );
2060
+ }
2061
+ return this.underlyingSession;
2062
+ }
2063
+ };
2064
+
2065
+ // src/agent/harness-agent-tool-approval-continuation.ts
2066
+ function collectHarnessAgentToolApprovalContinuations(input) {
2067
+ const lastMessage = input.messages.at(-1);
2068
+ if ((lastMessage == null ? void 0 : lastMessage.role) !== "tool") return [];
2069
+ const toolCallsByToolCallId = /* @__PURE__ */ new Map();
2070
+ const approvalRequestsByApprovalId = /* @__PURE__ */ new Map();
2071
+ for (const message of input.messages) {
2072
+ if (message.role !== "assistant" || typeof message.content === "string") {
2073
+ continue;
2074
+ }
2075
+ for (const part of message.content) {
2076
+ if (part.type === "tool-call") {
2077
+ toolCallsByToolCallId.set(part.toolCallId, {
2078
+ type: "tool-call",
2079
+ toolCallId: part.toolCallId,
2080
+ toolName: part.toolName,
2081
+ input: part.input,
2082
+ ...part.providerExecuted !== void 0 ? { providerExecuted: part.providerExecuted } : {}
2083
+ });
2084
+ } else if (part.type === "tool-approval-request") {
2085
+ approvalRequestsByApprovalId.set(part.approvalId, part);
2086
+ }
2087
+ }
2088
+ }
2089
+ const toolResultIds = /* @__PURE__ */ new Set();
2090
+ for (const part of lastMessage.content) {
2091
+ if (part.type === "tool-result") {
2092
+ toolResultIds.add(part.toolCallId);
2093
+ }
2094
+ }
2095
+ const continuations = [];
2096
+ for (const part of lastMessage.content) {
2097
+ if (part.type !== "tool-approval-response") continue;
2098
+ const approvalRequest = approvalRequestsByApprovalId.get(part.approvalId);
2099
+ if (approvalRequest == null) {
2100
+ throw new HarnessError({
2101
+ message: `Tool approval response '${part.approvalId}' does not match a prior tool approval request.`
2102
+ });
2103
+ }
2104
+ if (toolResultIds.has(approvalRequest.toolCallId)) continue;
2105
+ const toolCall = toolCallsByToolCallId.get(approvalRequest.toolCallId);
2106
+ if (toolCall == null) {
2107
+ throw new HarnessError({
2108
+ message: `Tool approval request '${approvalRequest.approvalId}' references unknown tool call '${approvalRequest.toolCallId}'.`
2109
+ });
2110
+ }
2111
+ continuations.push({
2112
+ approvalResponse: part,
2113
+ toolCall
2114
+ });
2115
+ }
2116
+ return continuations;
2117
+ }
2118
+
2119
+ // src/agent/internal/bootstrap-recipe.ts
2120
+ var BOOTSTRAP_SCHEMA_VERSION = 1;
2121
+ async function hashBootstrap(recipe) {
2122
+ const encoder = new TextEncoder();
2123
+ const chunks = [];
2124
+ const pushString = (value) => {
2125
+ chunks.push(encoder.encode(value));
2126
+ chunks.push(encoder.encode("\0"));
2127
+ };
2128
+ pushString(recipe.harnessId);
2129
+ pushString(recipe.bootstrapDir);
2130
+ const sortedFiles = [...recipe.files].sort(
2131
+ (a, b) => a.path.localeCompare(b.path)
2132
+ );
2133
+ for (const file of sortedFiles) {
2134
+ pushString(file.path);
2135
+ pushString(file.content);
2136
+ }
2137
+ pushString(JSON.stringify(recipe.commands));
2138
+ pushString(String(BOOTSTRAP_SCHEMA_VERSION));
2139
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
2140
+ const buffer = new Uint8Array(totalLength);
2141
+ let offset = 0;
2142
+ for (const chunk of chunks) {
2143
+ buffer.set(chunk, offset);
2144
+ offset += chunk.length;
2145
+ }
2146
+ const digest = await crypto.subtle.digest("SHA-256", buffer);
2147
+ const bytes = new Uint8Array(digest);
2148
+ let hex = "";
2149
+ for (let i = 0; i < 8; i++) {
2150
+ hex += bytes[i].toString(16).padStart(2, "0");
2151
+ }
2152
+ return hex;
2153
+ }
2154
+ function bootstrapMarkerPath(recipe, identity) {
2155
+ return `${recipe.bootstrapDir}/.bootstrap-${identity}.ok`;
2156
+ }
2157
+ async function applyBootstrapRecipe(session, recipe, identity, options) {
2158
+ const markerPath = bootstrapMarkerPath(recipe, identity);
2159
+ const existingMarker = await session.readTextFile({
2160
+ path: markerPath,
2161
+ abortSignal: options == null ? void 0 : options.abortSignal
2162
+ });
2163
+ if (existingMarker !== null) {
2164
+ return;
2165
+ }
2166
+ for (const file of recipe.files) {
2167
+ await session.writeTextFile({
2168
+ path: file.path,
2169
+ content: file.content,
2170
+ abortSignal: options == null ? void 0 : options.abortSignal
2171
+ });
2172
+ }
2173
+ for (const cmd of recipe.commands) {
2174
+ const result = await session.run({
2175
+ command: cmd.command,
2176
+ workingDirectory: cmd.workingDirectory,
2177
+ abortSignal: options == null ? void 0 : options.abortSignal
2178
+ });
2179
+ if (result.exitCode !== 0) {
2180
+ throw new Error(
2181
+ `Bootstrap command failed for harness '${recipe.harnessId}' (exit ${result.exitCode}): ${cmd.command}
2182
+ ${result.stderr || result.stdout}`
2183
+ );
2184
+ }
2185
+ }
2186
+ await session.writeTextFile({
2187
+ path: markerPath,
2188
+ content: "",
2189
+ abortSignal: options == null ? void 0 : options.abortSignal
2190
+ });
2191
+ }
2192
+
2193
+ // src/agent/internal/resolve-observability.ts
2194
+ import { asArray } from "@ai-sdk/provider-utils";
2195
+ var ENV_TRUTHY = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
2196
+ function parseList(value) {
2197
+ if (!value) return void 0;
2198
+ const items = value.split(",").map((item) => item.trim()).filter(Boolean);
2199
+ return items.length > 0 ? items : void 0;
2200
+ }
2201
+ function resolveDebugConfig(debug, env = process.env) {
2202
+ var _a3, _b3, _c, _d;
2203
+ const enabled = (_b3 = debug == null ? void 0 : debug.enabled) != null ? _b3 : ENV_TRUTHY.has(((_a3 = env.HARNESS_DEBUG) != null ? _a3 : "").toLowerCase());
2204
+ const level = (_d = (_c = debug == null ? void 0 : debug.level) != null ? _c : env.HARNESS_DEBUG_LEVEL) != null ? _d : "debug";
2205
+ const subsystems = (debug == null ? void 0 : debug.subsystems) ? [...debug.subsystems] : parseList(env.HARNESS_DEBUG_SUBSYSTEMS);
2206
+ return { enabled, level, subsystems };
2207
+ }
2208
+ function toHarnessDiagnostic(d) {
2209
+ return {
2210
+ level: d.level,
2211
+ message: d.message,
2212
+ subsystem: d.subsystem,
2213
+ kind: d.kind,
2214
+ source: d.source,
2215
+ stream: d.stream,
2216
+ attrs: d.attrs,
2217
+ error: d.error,
2218
+ sessionId: d.sessionId,
2219
+ timestamp: d.timestamp
2220
+ };
2221
+ }
2222
+ function formatForStderr(d) {
2223
+ var _a3;
2224
+ const parts = [`[harness:${d.level}]`, d.subsystem, d.message].filter(
2225
+ Boolean
2226
+ );
2227
+ let line = parts.join(" ");
2228
+ if (d.error) {
2229
+ line += ` (${(_a3 = d.error.name) != null ? _a3 : "Error"}: ${d.error.message})`;
2230
+ }
2231
+ return `${line}
2232
+ `;
2233
+ }
2234
+ function buildObservability(options) {
2235
+ var _a3;
2236
+ const resolved = resolveDebugConfig(options.settings.debug);
2237
+ if (!resolved.enabled) return void 0;
2238
+ const onLog = options.settings.onLog;
2239
+ const integrations = ((_a3 = options.settings.telemetry) == null ? void 0 : _a3.integrations) ? asArray(options.settings.telemetry.integrations) : [];
2240
+ const diagnosticConsumers = integrations.filter(
2241
+ (integration) => typeof integration.ingestDiagnostic === "function"
2242
+ );
2243
+ const report = (emitted) => {
2244
+ var _a4;
2245
+ const diagnostic = toHarnessDiagnostic(emitted);
2246
+ try {
2247
+ process.stderr.write(formatForStderr(diagnostic));
2248
+ } catch (e) {
2249
+ }
2250
+ onLog == null ? void 0 : onLog(diagnostic);
2251
+ for (const consumer of diagnosticConsumers) {
2252
+ (_a4 = consumer.ingestDiagnostic) == null ? void 0 : _a4.call(consumer, diagnostic);
2253
+ }
2254
+ };
2255
+ return {
2256
+ debug: {
2257
+ enabled: true,
2258
+ level: resolved.level,
2259
+ ...resolved.subsystems ? { subsystems: resolved.subsystems } : {}
2260
+ },
2261
+ report
2262
+ };
2263
+ }
2264
+
2265
+ // src/agent/harness-agent.ts
2266
+ var HarnessAgent = class {
2267
+ constructor(settings) {
2268
+ this.version = "agent-v1";
2269
+ var _a3;
2270
+ this.settings = settings;
2271
+ this.id = settings.id;
2272
+ this.userTools = (_a3 = settings.tools) != null ? _a3 : {};
2273
+ this.permissionMode = resolvePermissionMode({
2274
+ permissionMode: settings.permissionMode
2275
+ });
2276
+ if (Object.keys(settings.harness.builtinTools).length > 0 && permissionModeNeedsBuiltinSupport({
2277
+ permissionMode: this.permissionMode
2278
+ }) && settings.harness.supportsBuiltinToolApprovals !== true) {
2279
+ throw new HarnessCapabilityUnsupportedError({
2280
+ message: `Harness '${settings.harness.harnessId}' does not support built-in tool approval requests; use permissionMode: 'allow-all'.`,
2281
+ harnessId: settings.harness.harnessId
2282
+ });
2283
+ }
2284
+ this.tools = {
2285
+ ...settings.harness.builtinTools,
2286
+ ...this.userTools
2287
+ };
2288
+ }
2289
+ /** Identifier of the harness backing this agent. */
2290
+ get harnessId() {
2291
+ return this.settings.harness.harnessId;
2292
+ }
2293
+ /**
2294
+ * Start a fresh session, or resume from state previously returned by
2295
+ * `session.detach()` or `session.stop()`. The returned
2296
+ * `HarnessAgentSession` must be passed to subsequent `generate` / `stream`
2297
+ * calls; end it with `session.detach()`, `session.stop()`, or
2298
+ * `session.destroy()`.
2299
+ */
2300
+ async createSession(options) {
2301
+ var _a3;
2302
+ const sessionId = (_a3 = options == null ? void 0 : options.sessionId) != null ? _a3 : generateId5();
2303
+ const resumeFrom = options == null ? void 0 : options.resumeFrom;
2304
+ const continueFrom = options == null ? void 0 : options.continueFrom;
2305
+ const abortSignal = options == null ? void 0 : options.abortSignal;
2306
+ const harness = this.settings.harness;
2307
+ const sandboxProvider = this.settings.sandbox;
2308
+ if (resumeFrom != null && continueFrom != null) {
2309
+ throw new Error(
2310
+ "HarnessAgent.createSession: pass either `resumeFrom` or `continueFrom`, not both."
2311
+ );
2312
+ }
2313
+ let validatedResumeFrom;
2314
+ if (resumeFrom != null) {
2315
+ validatedResumeFrom = await validateLifecycleStateData({
2316
+ harness,
2317
+ state: resumeFrom,
2318
+ expectedType: "resume-session"
2319
+ });
2320
+ }
2321
+ let validatedContinueFrom;
2322
+ if (continueFrom != null) {
2323
+ validatedContinueFrom = await validateLifecycleStateData({
2324
+ harness,
2325
+ state: continueFrom,
2326
+ expectedType: "continue-turn"
2327
+ });
2328
+ }
2329
+ const effectiveContinueFrom = validatedContinueFrom != null ? validatedContinueFrom : validatedResumeFrom == null ? void 0 : validatedResumeFrom.continueFrom;
2330
+ const isResumedSession = validatedResumeFrom != null || effectiveContinueFrom != null;
2331
+ let recipe;
2332
+ let identity;
2333
+ if (harness.getBootstrap != null) {
2334
+ recipe = await harness.getBootstrap({ abortSignal });
2335
+ identity = await hashBootstrap(recipe);
2336
+ }
2337
+ const acquiredSandboxSession = await this._acquireSandbox({
2338
+ sandboxProvider,
2339
+ sessionId,
2340
+ isResume: isResumedSession,
2341
+ recipe,
2342
+ identity,
2343
+ abortSignal
2344
+ });
2345
+ const leased = applyPortLease({
2346
+ provider: sandboxProvider,
2347
+ sandboxSession: acquiredSandboxSession,
2348
+ sessionId
2349
+ });
2350
+ const sandboxSession = leased.sandboxSession;
2351
+ const leasedBridgePort = leased.port;
2352
+ const sessionWorkDir = `${sandboxSession.defaultWorkingDirectory}/${harness.harnessId}-${sessionId}`;
2353
+ try {
2354
+ if (!isResumedSession && recipe != null && identity != null) {
2355
+ await applyBootstrapRecipe(
2356
+ sandboxSession.restricted(),
2357
+ recipe,
2358
+ identity,
2359
+ { abortSignal }
2360
+ );
2361
+ }
2362
+ await sandboxSession.run({
2363
+ command: `mkdir -p ${sessionWorkDir}`,
2364
+ abortSignal
2365
+ });
2366
+ if (this.settings.onSandboxSession != null) {
2367
+ await this.settings.onSandboxSession({
2368
+ session: sandboxSession.restricted(),
2369
+ sessionWorkDir,
2370
+ abortSignal
2371
+ });
2372
+ }
2373
+ } catch (err) {
2374
+ await cleanupAfterStartFailure({
2375
+ sandboxProvider,
2376
+ sandboxSession,
2377
+ sessionId,
2378
+ leasedBridgePort
2379
+ });
2380
+ throw err;
2381
+ }
2382
+ try {
2383
+ const baseStartOptions = {
2384
+ sessionId,
2385
+ skills: this.settings.skills,
2386
+ resumeFrom: validatedResumeFrom,
2387
+ continueFrom: effectiveContinueFrom,
2388
+ permissionMode: this.permissionMode,
2389
+ abortSignal,
2390
+ observability: buildObservability({ settings: this.settings })
2391
+ };
2392
+ const underlyingSession = await harness.doStart({
2393
+ ...baseStartOptions,
2394
+ sandboxSession,
2395
+ sessionWorkDir
2396
+ });
2397
+ return new HarnessAgentSession({
2398
+ sessionId,
2399
+ harness,
2400
+ underlyingSession,
2401
+ sandboxSession,
2402
+ sandboxProvider,
2403
+ leasedBridgePort,
2404
+ sessionWorkDir,
2405
+ toolApproval: this.settings.toolApproval,
2406
+ pendingToolApprovals: effectiveContinueFrom == null ? void 0 : effectiveContinueFrom.pendingToolApprovals,
2407
+ turnState: effectiveContinueFrom == null ? "idle" : effectiveContinueFrom.pendingToolApprovals != null && effectiveContinueFrom.pendingToolApprovals.length > 0 ? "awaiting-approval" : "suspended"
2408
+ });
2409
+ } catch (error) {
2410
+ await cleanupAfterStartFailure({
2411
+ sandboxProvider,
2412
+ sandboxSession,
2413
+ sessionId,
2414
+ leasedBridgePort
2415
+ });
2416
+ throw error;
2417
+ }
2418
+ }
2419
+ async generate(options) {
2420
+ const turnInput = this._resolveTurnInput(options);
2421
+ const runtimeContext = {};
2422
+ const { result, done } = this._startTurn({
2423
+ session: options.session,
2424
+ turnInput,
2425
+ runtimeContext,
2426
+ abortSignal: options.abortSignal
2427
+ });
2428
+ await done;
2429
+ return this._toGenerateResult(result);
2430
+ }
2431
+ async stream(options) {
2432
+ const turnInput = this._resolveTurnInput(options);
2433
+ const runtimeContext = {};
2434
+ const { result } = this._startTurn({
2435
+ session: options.session,
2436
+ turnInput,
2437
+ runtimeContext,
2438
+ abortSignal: options.abortSignal
2439
+ });
2440
+ return result;
2441
+ }
2442
+ /**
2443
+ * Continue the in-flight turn **without a new prompt**, draining it like
2444
+ * {@link generate}. Used after `createSession({ continueFrom })` to finish
2445
+ * consuming a turn that crossed a process boundary.
2446
+ */
2447
+ async continueGenerate(options) {
2448
+ var _a3;
2449
+ const runtimeContext = {};
2450
+ const { result, done } = this._startTurn({
2451
+ session: options.session,
2452
+ turnInput: {
2453
+ mode: "continue",
2454
+ toolApprovalContinuations: (_a3 = options.toolApprovalContinuations) != null ? _a3 : []
2455
+ },
2456
+ runtimeContext,
2457
+ abortSignal: options.abortSignal
2458
+ });
2459
+ await done;
2460
+ return this._toGenerateResult(result);
2461
+ }
2462
+ /**
2463
+ * Continue the in-flight turn **without a new prompt**, streaming its events
2464
+ * like {@link stream}. Used to keep consuming a turn that is still running
2465
+ * (or finished) in the runtime after a process boundary — the workflow slice
2466
+ * loop calls this on every slice after the first. Routes through the adapter's
2467
+ * `doContinueTurn`; what it can guarantee (lossless attach vs. lossy rerun)
2468
+ * follows from how the adapter resumed the session.
2469
+ */
2470
+ async continueStream(options) {
2471
+ var _a3;
2472
+ const runtimeContext = {};
2473
+ const { result } = this._startTurn({
2474
+ session: options.session,
2475
+ turnInput: {
2476
+ mode: "continue",
2477
+ toolApprovalContinuations: (_a3 = options.toolApprovalContinuations) != null ? _a3 : []
2478
+ },
2479
+ runtimeContext,
2480
+ abortSignal: options.abortSignal
2481
+ });
2482
+ return result;
2483
+ }
2484
+ // ─── Internals ──────────────────────────────────────────────────────
2485
+ _startTurn(input) {
2486
+ if (input.turnInput.mode === "continue") {
2487
+ return input.session.continueTurn({
2488
+ instructions: this.settings.instructions,
2489
+ tools: this.tools,
2490
+ toolSpecs: this._toToolSpecs(),
2491
+ runtimeContext: input.runtimeContext,
2492
+ abortSignal: input.abortSignal,
2493
+ telemetry: this.settings.telemetry,
2494
+ toolApprovalContinuations: input.turnInput.toolApprovalContinuations
2495
+ });
2496
+ }
2497
+ return input.session.promptTurn({
2498
+ prompt: input.turnInput.prompt,
2499
+ instructions: this.settings.instructions,
2500
+ tools: this.tools,
2501
+ toolSpecs: this._toToolSpecs(),
2502
+ runtimeContext: input.runtimeContext,
2503
+ abortSignal: input.abortSignal,
2504
+ telemetry: this.settings.telemetry
2505
+ });
2506
+ }
2507
+ async _acquireSandbox(input) {
2508
+ const { sandboxProvider } = input;
2509
+ if (input.isResume) {
2510
+ if (sandboxProvider.resumeSession == null) {
2511
+ throw new HarnessCapabilityUnsupportedError({
2512
+ message: `Sandbox provider '${sandboxProvider.providerId}' does not support resume.`,
2513
+ harnessId: this.settings.harness.harnessId
2514
+ });
2515
+ }
2516
+ return sandboxProvider.resumeSession({
2517
+ sessionId: input.sessionId,
2518
+ abortSignal: input.abortSignal
2519
+ });
2520
+ }
2521
+ return sandboxProvider.createSession({
2522
+ sessionId: input.sessionId,
2523
+ abortSignal: input.abortSignal,
2524
+ identity: input.identity,
2525
+ onFirstCreate: input.recipe != null && input.identity != null ? (session, opts) => applyBootstrapRecipe(
2526
+ session,
2527
+ input.recipe,
2528
+ input.identity,
2529
+ opts
2530
+ ) : void 0
2531
+ });
2532
+ }
2533
+ /*
2534
+ * Reduce AI SDK input to the single user message the harness should run
2535
+ * for this turn. The harness session owns prior-turn state (system
2536
+ * prompt, assistant turns, tool results) — we never replay it. A bare
2537
+ * string is forwarded as-is; a message array is collapsed to its last
2538
+ * `role: 'user'` entry. Inputs whose only messages are non-user (system,
2539
+ * assistant, tool) have no fresh user input and are rejected.
2540
+ */
2541
+ _resolveTurnInput(options) {
2542
+ if (typeof options.prompt === "string") {
2543
+ return { mode: "prompt", prompt: options.prompt };
2544
+ }
2545
+ const messages = Array.isArray(options.prompt) ? options.prompt : options.messages;
2546
+ if (Array.isArray(messages)) {
2547
+ const toolApprovalContinuations = collectHarnessAgentToolApprovalContinuations({ messages });
2548
+ if (toolApprovalContinuations.length > 0) {
2549
+ return {
2550
+ mode: "continue",
2551
+ toolApprovalContinuations
2552
+ };
2553
+ }
2554
+ for (let i = messages.length - 1; i >= 0; i--) {
2555
+ const message = messages[i];
2556
+ if ((message == null ? void 0 : message.role) === "user")
2557
+ return { mode: "prompt", prompt: message };
2558
+ }
2559
+ throw new Error(
2560
+ 'HarnessAgent: messages must contain at least one `role: "user"` entry.'
2561
+ );
2562
+ }
2563
+ throw new Error("HarnessAgent: either `prompt` or `messages` is required.");
2564
+ }
2565
+ /*
2566
+ * Wire-format projection of user-defined tools only. Harness builtins are
2567
+ * executed by the runtime and the bridge already knows about them — we
2568
+ * never re-declare them over the wire.
2569
+ */
2570
+ _toToolSpecs() {
2571
+ const specs = [];
2572
+ for (const [name3, tool] of Object.entries(
2573
+ this.userTools
2574
+ )) {
2575
+ const t = tool;
2576
+ let inputSchema;
2577
+ if (t.inputSchema != null) {
2578
+ try {
2579
+ inputSchema = asSchema(
2580
+ t.inputSchema
2581
+ ).jsonSchema;
2582
+ } catch (e) {
2583
+ }
2584
+ }
2585
+ specs.push({ name: name3, description: t.description, inputSchema });
2586
+ }
2587
+ return specs;
2588
+ }
2589
+ async _toGenerateResult(streamResult) {
2590
+ const [steps, usage, responseMessages] = await Promise.all([
2591
+ streamResult.steps,
2592
+ streamResult.usage,
2593
+ streamResult.responseMessages
2594
+ ]);
2595
+ return new HarnessGenerateTextResult({ steps, usage, responseMessages });
2596
+ }
2597
+ };
2598
+ var HarnessGenerateTextResult = class {
2599
+ constructor(options) {
2600
+ this.output = void 0;
2601
+ this.steps = options.steps;
2602
+ this.usage = options.usage;
2603
+ this.responseMessages = options.responseMessages;
2604
+ }
2605
+ get finalStep() {
2606
+ return this.steps.at(-1);
2607
+ }
2608
+ get content() {
2609
+ return this.steps.flatMap((step) => step.content);
2610
+ }
2611
+ get text() {
2612
+ return this.finalStep.text;
2613
+ }
2614
+ get files() {
2615
+ return this.steps.flatMap((step) => step.files);
2616
+ }
2617
+ get sources() {
2618
+ return this.steps.flatMap((step) => step.sources);
2619
+ }
2620
+ get toolCalls() {
2621
+ return this.steps.flatMap((step) => step.toolCalls);
2622
+ }
2623
+ get staticToolCalls() {
2624
+ return this.steps.flatMap((step) => step.staticToolCalls);
2625
+ }
2626
+ get dynamicToolCalls() {
2627
+ return this.steps.flatMap((step) => step.dynamicToolCalls);
2628
+ }
2629
+ get toolResults() {
2630
+ return this.steps.flatMap((step) => step.toolResults);
2631
+ }
2632
+ get staticToolResults() {
2633
+ return this.steps.flatMap((step) => step.staticToolResults);
2634
+ }
2635
+ get dynamicToolResults() {
2636
+ return this.steps.flatMap((step) => step.dynamicToolResults);
2637
+ }
2638
+ get finishReason() {
2639
+ return this.finalStep.finishReason;
2640
+ }
2641
+ get rawFinishReason() {
2642
+ return this.finalStep.rawFinishReason;
2643
+ }
2644
+ get warnings() {
2645
+ return this.steps.flatMap((step) => {
2646
+ var _a3;
2647
+ return (_a3 = step.warnings) != null ? _a3 : [];
2648
+ });
2649
+ }
2650
+ get reasoning() {
2651
+ return this.finalStep.content.filter(
2652
+ (part) => part.type === "reasoning" || part.type === "reasoning-file"
2653
+ );
2654
+ }
2655
+ get reasoningText() {
2656
+ return this.finalStep.reasoningText;
2657
+ }
2658
+ get totalUsage() {
2659
+ return this.usage;
2660
+ }
2661
+ get request() {
2662
+ return this.finalStep.request;
2663
+ }
2664
+ get response() {
2665
+ return this.finalStep.response;
2666
+ }
2667
+ get providerMetadata() {
2668
+ return this.finalStep.providerMetadata;
2669
+ }
2670
+ };
2671
+ function applyPortLease(input) {
2672
+ const pool = input.provider.bridgePorts;
2673
+ if (pool == null || pool.length === 0) {
2674
+ return { sandboxSession: input.sandboxSession, port: void 0 };
2675
+ }
2676
+ const port = acquireBridgePort({
2677
+ poolKey: input.provider,
2678
+ pool,
2679
+ sessionId: input.sessionId
2680
+ });
2681
+ return {
2682
+ sandboxSession: narrowNetworkSessionPorts(input.sandboxSession, port),
2683
+ port
2684
+ };
2685
+ }
2686
+ function narrowNetworkSessionPorts(sandboxSession, leasedPort) {
2687
+ return Object.create(sandboxSession, {
2688
+ ports: {
2689
+ value: [leasedPort],
2690
+ enumerable: true
2691
+ }
2692
+ });
2693
+ }
2694
+ async function cleanupAfterStartFailure(input) {
2695
+ await Promise.resolve(input.sandboxSession.stop()).catch(() => {
2696
+ });
2697
+ if (input.leasedBridgePort != null) {
2698
+ releaseBridgePort({
2699
+ poolKey: input.sandboxProvider,
2700
+ sessionId: input.sessionId
2701
+ });
2702
+ }
2703
+ }
2704
+
2705
+ // src/agent/prewarm.ts
2706
+ async function prewarmHarness(options) {
2707
+ var _a3, _b3;
2708
+ const recipe = await ((_b3 = (_a3 = options.harness).getBootstrap) == null ? void 0 : _b3.call(_a3, {
2709
+ abortSignal: options.abortSignal
2710
+ }));
2711
+ if (recipe == null) return;
2712
+ const identity = await hashBootstrap(recipe);
2713
+ const sandboxSession = await options.sandboxProvider.createSession({
2714
+ abortSignal: options.abortSignal,
2715
+ identity,
2716
+ onFirstCreate: (session, opts) => applyBootstrapRecipe(session, recipe, identity, opts)
2717
+ });
2718
+ try {
2719
+ await applyBootstrapRecipe(sandboxSession.restricted(), recipe, identity, {
2720
+ abortSignal: options.abortSignal
2721
+ });
2722
+ } finally {
2723
+ await Promise.resolve(sandboxSession.stop()).catch(() => {
2724
+ });
2725
+ }
2726
+ }
2727
+
2728
+ // src/agent/observability/file-reporter.ts
2729
+ import { appendFileSync, mkdirSync } from "fs";
2730
+ function createFileReporter(options) {
2731
+ var _a3, _b3;
2732
+ const fileName = (_a3 = options.fileName) != null ? _a3 : "events.jsonl";
2733
+ const path = `${options.dir}/${fileName}`;
2734
+ const failOnly = (_b3 = options.failOnly) != null ? _b3 : false;
2735
+ const turns = /* @__PURE__ */ new Map();
2736
+ let lastOpenCallId;
2737
+ let dirReady = false;
2738
+ const bucketFor = (callId) => {
2739
+ let bucket = turns.get(callId);
2740
+ if (!bucket) {
2741
+ bucket = { lines: [], errored: false };
2742
+ turns.set(callId, bucket);
2743
+ }
2744
+ return bucket;
2745
+ };
2746
+ const record = (callId, rec) => {
2747
+ const id = callId != null ? callId : lastOpenCallId;
2748
+ if (id == null) {
2749
+ flushLines([rec]);
2750
+ return;
2751
+ }
2752
+ bucketFor(id).lines.push(rec);
2753
+ };
2754
+ const flushLines = (lines) => {
2755
+ if (lines.length === 0) return;
2756
+ try {
2757
+ if (!dirReady) {
2758
+ mkdirSync(options.dir, { recursive: true });
2759
+ dirReady = true;
2760
+ }
2761
+ appendFileSync(path, lines.map((l) => JSON.stringify(l)).join("\n") + "\n");
2762
+ } catch (e) {
2763
+ }
2764
+ };
2765
+ const finishTurn = (callId) => {
2766
+ const bucket = turns.get(callId);
2767
+ if (!bucket) return;
2768
+ turns.delete(callId);
2769
+ if (lastOpenCallId === callId) lastOpenCallId = void 0;
2770
+ if (failOnly && !bucket.errored) return;
2771
+ flushLines(bucket.lines);
2772
+ };
2773
+ return {
2774
+ onStart(event) {
2775
+ const e = event;
2776
+ lastOpenCallId = e.callId;
2777
+ record(e.callId, {
2778
+ ts: Date.now(),
2779
+ kind: "turn-start",
2780
+ callId: e.callId,
2781
+ operationId: e.operationId,
2782
+ provider: e.provider,
2783
+ modelId: e.modelId,
2784
+ // Input prompt, unless the consumer opted out via `recordInputs: false`.
2785
+ ...e.recordInputs === false ? {} : { input: { messages: e.messages, instructions: e.instructions } }
2786
+ });
2787
+ },
2788
+ onStepStart(event) {
2789
+ const e = event;
2790
+ record(e.callId, {
2791
+ ts: Date.now(),
2792
+ kind: "step-start",
2793
+ callId: e.callId,
2794
+ step: e.stepNumber
2795
+ });
2796
+ },
2797
+ onToolExecutionStart(event) {
2798
+ const e = event;
2799
+ record(e.callId, {
2800
+ ts: Date.now(),
2801
+ kind: "tool-start",
2802
+ callId: e.callId,
2803
+ toolName: e.toolCall.toolName,
2804
+ toolCallId: e.toolCall.toolCallId,
2805
+ input: e.toolCall.input
2806
+ });
2807
+ },
2808
+ onToolExecutionEnd(event) {
2809
+ const e = event;
2810
+ const isError = e.toolOutput.type === "error";
2811
+ if (isError) bucketFor(e.callId).errored = true;
2812
+ record(e.callId, {
2813
+ ts: Date.now(),
2814
+ kind: "tool-end",
2815
+ callId: e.callId,
2816
+ toolCallId: e.toolCall.toolCallId,
2817
+ isError,
2818
+ output: isError ? e.toolOutput.error : e.toolOutput.output
2819
+ });
2820
+ },
2821
+ onStepFinish(event) {
2822
+ const e = event;
2823
+ record(e.callId, {
2824
+ ts: Date.now(),
2825
+ kind: "step-finish",
2826
+ callId: e.callId,
2827
+ usage: e.usage,
2828
+ // The model's output content, unless `recordOutputs: false`.
2829
+ ...e.recordOutputs === false || e.content == null ? {} : { output: e.content }
2830
+ });
2831
+ },
2832
+ onEnd(event) {
2833
+ var _a4;
2834
+ const e = event;
2835
+ record(e.callId, {
2836
+ ts: Date.now(),
2837
+ kind: "turn-finish",
2838
+ callId: e.callId,
2839
+ finishReason: e.finishReason,
2840
+ usage: (_a4 = e.totalUsage) != null ? _a4 : e.usage
2841
+ });
2842
+ finishTurn(e.callId);
2843
+ },
2844
+ onError(error) {
2845
+ if (lastOpenCallId != null) bucketFor(lastOpenCallId).errored = true;
2846
+ record(lastOpenCallId, {
2847
+ ts: Date.now(),
2848
+ kind: "error",
2849
+ error: error instanceof Error ? { name: error.name, message: error.message } : error
2850
+ });
2851
+ },
2852
+ ingestDiagnostic(diagnostic) {
2853
+ if (diagnostic.level === "error" && lastOpenCallId != null) {
2854
+ bucketFor(lastOpenCallId).errored = true;
2855
+ }
2856
+ record(lastOpenCallId, {
2857
+ ts: diagnostic.timestamp,
2858
+ kind: "diagnostic",
2859
+ diagnostic
2860
+ });
2861
+ }
2862
+ };
2863
+ }
2864
+
2865
+ // src/agent/observability/trace-tree-reporter.ts
2866
+ function createTraceTreeReporter(options = {}) {
2867
+ var _a3;
2868
+ const write = (_a3 = options.write) != null ? _a3 : ((chunk) => void process.stderr.write(chunk));
2869
+ const turns = /* @__PURE__ */ new Map();
2870
+ const render = (node, depth) => {
2871
+ const indent = " ".repeat(depth);
2872
+ const dur = node.endMs != null ? `${Math.max(0, Math.round(node.endMs - node.startMs))}ms` : "(open)";
2873
+ let out = `${indent}- ${node.label} ${dur}
2874
+ `;
2875
+ for (const child of node.children) out += render(child, depth + 1);
2876
+ return out;
2877
+ };
2878
+ return {
2879
+ onStart(event) {
2880
+ var _a4;
2881
+ const e = event;
2882
+ turns.set(e.callId, {
2883
+ root: {
2884
+ label: `${(_a4 = e.operationId) != null ? _a4 : "turn"}${e.modelId ? ` ${e.modelId}` : ""}`,
2885
+ startMs: Date.now(),
2886
+ children: []
2887
+ },
2888
+ tools: /* @__PURE__ */ new Map()
2889
+ });
2890
+ },
2891
+ onStepStart(event) {
2892
+ var _a4;
2893
+ const e = event;
2894
+ const turn = turns.get(e.callId);
2895
+ if (!turn) return;
2896
+ const step = {
2897
+ label: `step ${((_a4 = e.stepNumber) != null ? _a4 : turn.root.children.length) + 1}`,
2898
+ startMs: Date.now(),
2899
+ children: []
2900
+ };
2901
+ turn.step = step;
2902
+ turn.root.children.push(step);
2903
+ },
2904
+ onToolExecutionStart(event) {
2905
+ var _a4;
2906
+ const e = event;
2907
+ const turn = turns.get(e.callId);
2908
+ const parent = (_a4 = turn == null ? void 0 : turn.step) != null ? _a4 : turn == null ? void 0 : turn.root;
2909
+ if (!turn || !parent) return;
2910
+ const node = {
2911
+ label: `tool ${e.toolCall.toolName}`,
2912
+ startMs: Date.now(),
2913
+ children: []
2914
+ };
2915
+ turn.tools.set(e.toolCall.toolCallId, node);
2916
+ parent.children.push(node);
2917
+ },
2918
+ onToolExecutionEnd(event) {
2919
+ var _a4;
2920
+ const e = event;
2921
+ const node = (_a4 = turns.get(e.callId)) == null ? void 0 : _a4.tools.get(e.toolCall.toolCallId);
2922
+ if (!node) return;
2923
+ node.endMs = Date.now();
2924
+ if (e.toolOutput.type === "error") node.label += " [error]";
2925
+ },
2926
+ onStepFinish(event) {
2927
+ const e = event;
2928
+ const turn = turns.get(e.callId);
2929
+ if (turn == null ? void 0 : turn.step) {
2930
+ turn.step.endMs = Date.now();
2931
+ turn.step = void 0;
2932
+ }
2933
+ },
2934
+ onEnd(event) {
2935
+ const e = event;
2936
+ const turn = turns.get(e.callId);
2937
+ if (!turn) return;
2938
+ turn.root.endMs = Date.now();
2939
+ turns.delete(e.callId);
2940
+ try {
2941
+ write(`
2942
+ ${render(turn.root, 0)}`);
2943
+ } catch (e2) {
2944
+ }
2945
+ }
2946
+ };
2947
+ }
2948
+ export {
2949
+ HarnessAgent,
2950
+ HarnessAgentSession,
2951
+ HarnessCapabilityUnsupportedError,
2952
+ HarnessError,
2953
+ collectHarnessAgentToolApprovalContinuations,
2954
+ createFileReporter,
2955
+ createTraceTreeReporter,
2956
+ prewarmHarness
2957
+ };
2958
+ //# sourceMappingURL=index.js.map