@ai-sdk/harness 1.0.72 → 1.0.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # @ai-sdk/harness
2
2
 
3
+ ## 1.0.73
4
+
5
+ ### Patch Changes
6
+
7
+ - 62a9c2a: feat(harness): add support for structured output to `HarnessAgent` via `output` property
8
+ - d25cae2: fix(harness): claim the bridge event stream on start/resume instead of on connect
9
+
3
10
  ## 1.0.72
4
11
 
5
12
  ### Patch Changes
package/README.md CHANGED
@@ -81,6 +81,23 @@ try {
81
81
  }
82
82
  ```
83
83
 
84
+ Set `output` on `HarnessAgent` to require the same typed, schema-backed output
85
+ on every turn. `generate()` exposes the validated value as `result.output`, and
86
+ `stream()` additionally exposes `partialOutputStream`; the JSON also remains on
87
+ the normal text and stream surfaces.
88
+
89
+ ```ts
90
+ import { Output } from 'ai';
91
+
92
+ const agent = new HarnessAgent({
93
+ harness: claudeCode,
94
+ sandbox,
95
+ output: Output.object({
96
+ schema: z.object({ answer: z.string() }),
97
+ }),
98
+ });
99
+ ```
100
+
84
101
  Use `session.detach()` to park a bridge-backed session for later attach, `session.stop()` to save state and stop the sandbox, or `session.destroy()` to clean up without keeping resume state. Bridge-backed adapters such as Claude Code, Codex, OpenCode, and DeepAgents require a sandbox provider that exposes ports — `@ai-sdk/sandbox-vercel` is the supported choice today. `@ai-sdk/sandbox-just-bash` is suitable only for host-runtime or otherwise non-bridge flows, such as Pi.
85
102
 
86
103
  `sandbox` is a required `HarnessV1SandboxProvider` — the agent calls `provider.createSession()` when a session starts. Use `sandboxConfig` for agent specific sandbox configuration that works independently from the sandbox provider that is used:
@@ -111,6 +128,12 @@ See the [harness adapters documentation](https://ai-sdk.dev/v7/docs/ai-sdk-harne
111
128
 
112
129
  Implement the `HarnessV1` factory and a `HarnessV1Session` whose `doPromptTurn` emits events; the agent surface, streaming, tool execution, and multi-turn state are handled for you. Read `startOpts.sandboxSession` for the network sandbox session the agent created and will stop on cleanup. Call `sandboxSession.restricted()` for the tool-safe file-IO/exec/spawn surface.
113
130
 
131
+ Each prompt and continuation receives an optional `responseFormat`. JSON
132
+ formats carry a caller-provided JSON Schema plus optional name and description;
133
+ the adapter must enforce the schema and emit the resulting JSON through normal
134
+ text parts. If the runtime cannot honor the format, throw
135
+ `HarnessCapabilityUnsupportedError` before starting the turn.
136
+
114
137
  Bootstrap recipe paths may be absolute or relative. Relative `bootstrapDir` and
115
138
  file paths are resolved against `sandboxSession.defaultWorkingDirectory`.
116
139
  The framework creates `bootstrapDir` before writing files, and bootstrap
@@ -1,6 +1,6 @@
1
1
  import * as _ai_sdk_provider_utils from '@ai-sdk/provider-utils';
2
2
  import { Experimental_SandboxSession, UserModelMessage, ToolSet, FlexibleSchema, Tool, Context, Arrayable, ToolApprovalResponse, ModelMessage } from '@ai-sdk/provider-utils';
3
- import { StopCondition, ToolApprovalStatus, TelemetryOptions, ActiveTools, StreamTextResult, Agent, AgentCallParameters, GenerateTextResult, AgentStreamParameters, Telemetry } from 'ai';
3
+ import { OutputInterface, StopCondition, ToolApprovalStatus, TelemetryOptions, ActiveTools, StreamTextResult, Agent, AgentCallParameters, GenerateTextResult, AgentStreamParameters, Telemetry } from 'ai';
4
4
  import { z } from 'zod/v4';
5
5
  import { JSONValue, LanguageModelV4ToolCall, LanguageModelV4ToolApprovalRequest, LanguageModelV4ToolResult, LanguageModelV4FinishReason, LanguageModelV4Usage, JSONSchema7, AISDKError } from '@ai-sdk/provider';
6
6
 
@@ -435,6 +435,30 @@ type HarnessV1PromptControl = {
435
435
  readonly done: PromiseLike<void>;
436
436
  };
437
437
 
438
+ type HarnessV1JSONValue = null | boolean | number | string | HarnessV1JSONArray | HarnessV1JSONObject;
439
+ interface HarnessV1JSONArray extends Array<HarnessV1JSONValue> {
440
+ }
441
+ interface HarnessV1JSONObject {
442
+ [key: string]: HarnessV1JSONValue | undefined;
443
+ }
444
+ type HarnessV1JSONSchema = HarnessV1JSONObject;
445
+ /**
446
+ * Requested response format for one harness turn.
447
+ *
448
+ * This intentionally mirrors the AI SDK provider response-format shape
449
+ * without depending on `@ai-sdk/provider`. Harness implementations receive
450
+ * JSON Schema rather than the caller's original Zod or Standard Schema so the
451
+ * contract can cross process and package boundaries.
452
+ */
453
+ type HarnessV1ResponseFormat = {
454
+ readonly type: 'text';
455
+ } | {
456
+ readonly type: 'json';
457
+ readonly schema?: HarnessV1JSONSchema;
458
+ readonly name?: string;
459
+ readonly description?: string;
460
+ };
461
+
438
462
  type HarnessV1PendingToolApproval = {
439
463
  readonly approvalId: string;
440
464
  readonly toolCallId: string;
@@ -764,6 +788,11 @@ type HarnessV1PromptTurnOptions = {
764
788
  * so prior turns are never replayed across the contract.
765
789
  */
766
790
  readonly prompt: HarnessV1Prompt;
791
+ /**
792
+ * Response format requested for this turn. Adapters that cannot honor a
793
+ * JSON response format must throw `HarnessCapabilityUnsupportedError`.
794
+ */
795
+ readonly responseFormat?: HarnessV1ResponseFormat;
767
796
  /**
768
797
  * Host-defined tools to make available to the underlying runtime for this
769
798
  * turn. The harness emits `tool-call` events when the runtime calls one
@@ -799,6 +828,11 @@ type HarnessV1PromptTurnOptions = {
799
828
  * that was previously suspended temporarily, e.g. by the workflow slice loop.
800
829
  */
801
830
  type HarnessV1ContinueTurnOptions = {
831
+ /**
832
+ * Response format of the in-flight turn. Rerun-based adapters use this when
833
+ * reconstructing the turn; attach-based adapters may ignore it.
834
+ */
835
+ readonly responseFormat?: HarnessV1ResponseFormat;
802
836
  /**
803
837
  * Host-defined tools to make available for the continued turn. Same shape
804
838
  * as `doPromptTurn`'s `tools`. An adapter that purely attaches to a live turn
@@ -1235,7 +1269,7 @@ type HarnessAgentToolFilteringSettings<TOOLS extends ToolSet> = {
1235
1269
  */
1236
1270
  readonly inactiveTools?: HarnessTools<TOOLS>;
1237
1271
  };
1238
- type HarnessAgentSettings<THarness extends HarnessAgentAdapter<any> = HarnessAgentAdapter, TUserTools extends ToolSet = {}, RUNTIME_CONTEXT extends Context = Context> = {
1272
+ type HarnessAgentSettings<THarness extends HarnessAgentAdapter<any> = HarnessAgentAdapter, TUserTools extends ToolSet = {}, RUNTIME_CONTEXT extends Context = Context, OUTPUT extends OutputInterface = never> = {
1239
1273
  /**
1240
1274
  * The harness adapter driving the underlying agent runtime. Its
1241
1275
  * `builtinTools` are merged with the user-defined `tools` and exposed to
@@ -1269,6 +1303,11 @@ type HarnessAgentSettings<THarness extends HarnessAgentAdapter<any> = HarnessAge
1269
1303
  * it to the first user message of a fresh session.
1270
1304
  */
1271
1305
  readonly instructions?: string;
1306
+ /**
1307
+ * Optional specification for generating typed output. The same output
1308
+ * requirement is active for every turn run by this agent.
1309
+ */
1310
+ readonly output?: OUTPUT;
1272
1311
  /**
1273
1312
  * Conditions that stop the current result after a completed harness tool
1274
1313
  * step that can continue into another model step. The underlying turn remains
@@ -1366,8 +1405,8 @@ declare function collectHarnessAgentToolResultContinuations(input: {
1366
1405
  messages: readonly ModelMessage[];
1367
1406
  }): readonly HarnessAgentToolResultContinuation[];
1368
1407
 
1369
- type HarnessAgentTurnResult<TOOLS extends ToolSet, RUNTIME_CONTEXT extends Context> = {
1370
- result: StreamTextResult<TOOLS, RUNTIME_CONTEXT, never>;
1408
+ type HarnessAgentTurnResult<TOOLS extends ToolSet, RUNTIME_CONTEXT extends Context, OUTPUT extends OutputInterface> = {
1409
+ result: StreamTextResult<TOOLS, RUNTIME_CONTEXT, OUTPUT>;
1371
1410
  done: Promise<void>;
1372
1411
  };
1373
1412
  type HarnessAgentTurnState = 'idle' | 'running' | 'awaiting-approval' | 'awaiting-tool-result' | 'suspended';
@@ -1425,7 +1464,7 @@ declare class HarnessAgentSession {
1425
1464
  turnState?: HarnessAgentTurnState;
1426
1465
  });
1427
1466
  hasUnfinishedTurn(): boolean;
1428
- promptTurn<TOOLS extends ToolSet, RUNTIME_CONTEXT extends Context>(options: {
1467
+ promptTurn<TOOLS extends ToolSet, RUNTIME_CONTEXT extends Context, OUTPUT extends OutputInterface>(options: {
1429
1468
  prompt: HarnessAgentPrompt;
1430
1469
  instructions: string | undefined;
1431
1470
  tools: TOOLS;
@@ -1434,10 +1473,12 @@ declare class HarnessAgentSession {
1434
1473
  builtinToolFiltering: HarnessV1BuiltinToolFiltering | undefined;
1435
1474
  runtimeContext: RUNTIME_CONTEXT;
1436
1475
  abortSignal: AbortSignal | undefined;
1476
+ responseFormat: HarnessV1ResponseFormat | undefined;
1477
+ output: OUTPUT | undefined;
1437
1478
  telemetry: TelemetryOptions | undefined;
1438
1479
  stopConditions: ReadonlyArray<StopCondition<TOOLS, RUNTIME_CONTEXT>>;
1439
- }): HarnessAgentTurnResult<TOOLS, RUNTIME_CONTEXT>;
1440
- continueTurn<TOOLS extends ToolSet, RUNTIME_CONTEXT extends Context>(options: {
1480
+ }): HarnessAgentTurnResult<TOOLS, RUNTIME_CONTEXT, OUTPUT>;
1481
+ continueTurn<TOOLS extends ToolSet, RUNTIME_CONTEXT extends Context, OUTPUT extends OutputInterface>(options: {
1441
1482
  instructions: string | undefined;
1442
1483
  tools: TOOLS;
1443
1484
  activeTools: ToolSet;
@@ -1445,11 +1486,13 @@ declare class HarnessAgentSession {
1445
1486
  builtinToolFiltering: HarnessV1BuiltinToolFiltering | undefined;
1446
1487
  runtimeContext: RUNTIME_CONTEXT;
1447
1488
  abortSignal: AbortSignal | undefined;
1489
+ responseFormat: HarnessV1ResponseFormat | undefined;
1490
+ output: OUTPUT | undefined;
1448
1491
  telemetry: TelemetryOptions | undefined;
1449
1492
  stopConditions: ReadonlyArray<StopCondition<TOOLS, RUNTIME_CONTEXT>>;
1450
1493
  toolApprovalContinuations?: readonly HarnessAgentToolApprovalContinuation[] | undefined;
1451
1494
  toolResultContinuations?: readonly HarnessAgentToolResultContinuation[] | undefined;
1452
- }): HarnessAgentTurnResult<TOOLS, RUNTIME_CONTEXT>;
1495
+ }): HarnessAgentTurnResult<TOOLS, RUNTIME_CONTEXT, OUTPUT>;
1453
1496
  /**
1454
1497
  * Ask the underlying runtime to compact its context. The runtime performs
1455
1498
  * the compaction itself; when it completes, a `compaction` part appears on
@@ -1556,7 +1599,7 @@ interface HarnessAgentCallExtensions {
1556
1599
  * `Experimental_SandboxSession`) is handed to user-tool `execute()` calls
1557
1600
  * via `experimental_sandbox`.
1558
1601
  */
1559
- declare class HarnessAgent<THarness extends HarnessAgentAdapter<any> = HarnessAgentAdapter, TUserTools extends ToolSet = {}, RUNTIME_CONTEXT extends Context = Context> implements Agent<never, HarnessAllTools<THarness, TUserTools>, RUNTIME_CONTEXT, never> {
1602
+ declare class HarnessAgent<THarness extends HarnessAgentAdapter<any> = HarnessAgentAdapter, TUserTools extends ToolSet = {}, RUNTIME_CONTEXT extends Context = Context, OUTPUT extends OutputInterface = never> implements Agent<never, HarnessAllTools<THarness, TUserTools>, RUNTIME_CONTEXT, OUTPUT> {
1560
1603
  readonly version: "agent-v1";
1561
1604
  readonly id: string | undefined;
1562
1605
  /**
@@ -1572,7 +1615,7 @@ declare class HarnessAgent<THarness extends HarnessAgentAdapter<any> = HarnessAg
1572
1615
  private readonly activeUserTools;
1573
1616
  private readonly builtinToolFiltering;
1574
1617
  private readonly permissionMode;
1575
- constructor(settings: HarnessAgentSettings<THarness, TUserTools, RUNTIME_CONTEXT>);
1618
+ constructor(settings: HarnessAgentSettings<THarness, TUserTools, RUNTIME_CONTEXT, OUTPUT>);
1576
1619
  /** Identifier of the harness backing this agent. */
1577
1620
  get harnessId(): string;
1578
1621
  /**
@@ -1605,8 +1648,8 @@ declare class HarnessAgent<THarness extends HarnessAgentAdapter<any> = HarnessAg
1605
1648
  continueFrom?: HarnessAgentContinueTurnState;
1606
1649
  abortSignal?: AbortSignal;
1607
1650
  }): Promise<HarnessAgentSession>;
1608
- generate(options: AgentCallParameters<never, HarnessAllTools<THarness, TUserTools>, RUNTIME_CONTEXT> & HarnessAgentCallExtensions): Promise<GenerateTextResult<HarnessAllTools<THarness, TUserTools>, RUNTIME_CONTEXT, never>>;
1609
- stream(options: AgentStreamParameters<never, HarnessAllTools<THarness, TUserTools>, RUNTIME_CONTEXT> & HarnessAgentCallExtensions): Promise<StreamTextResult<HarnessAllTools<THarness, TUserTools>, RUNTIME_CONTEXT, never>>;
1651
+ generate(options: AgentCallParameters<never, HarnessAllTools<THarness, TUserTools>, RUNTIME_CONTEXT> & HarnessAgentCallExtensions): Promise<GenerateTextResult<HarnessAllTools<THarness, TUserTools>, RUNTIME_CONTEXT, OUTPUT>>;
1652
+ stream(options: AgentStreamParameters<never, HarnessAllTools<THarness, TUserTools>, RUNTIME_CONTEXT> & HarnessAgentCallExtensions): Promise<StreamTextResult<HarnessAllTools<THarness, TUserTools>, RUNTIME_CONTEXT, OUTPUT>>;
1610
1653
  /**
1611
1654
  * Continue the in-flight turn **without a new prompt**, draining it like
1612
1655
  * {@link generate}. Used after `createSession({ continueFrom })` to finish
@@ -1617,7 +1660,7 @@ declare class HarnessAgent<THarness extends HarnessAgentAdapter<any> = HarnessAg
1617
1660
  toolApprovalContinuations?: readonly HarnessAgentToolApprovalContinuation[];
1618
1661
  toolResultContinuations?: readonly HarnessAgentToolResultContinuation[];
1619
1662
  abortSignal?: AbortSignal;
1620
- }): Promise<GenerateTextResult<HarnessAllTools<THarness, TUserTools>, RUNTIME_CONTEXT, never>>;
1663
+ }): Promise<GenerateTextResult<HarnessAllTools<THarness, TUserTools>, RUNTIME_CONTEXT, OUTPUT>>;
1621
1664
  /**
1622
1665
  * Continue the in-flight turn **without a new prompt**, streaming its events
1623
1666
  * like {@link stream}. Used to keep consuming a turn that is still running
@@ -1631,12 +1674,13 @@ declare class HarnessAgent<THarness extends HarnessAgentAdapter<any> = HarnessAg
1631
1674
  toolApprovalContinuations?: readonly HarnessAgentToolApprovalContinuation[];
1632
1675
  toolResultContinuations?: readonly HarnessAgentToolResultContinuation[];
1633
1676
  abortSignal?: AbortSignal;
1634
- }): Promise<StreamTextResult<HarnessAllTools<THarness, TUserTools>, RUNTIME_CONTEXT, never>>;
1677
+ }): Promise<StreamTextResult<HarnessAllTools<THarness, TUserTools>, RUNTIME_CONTEXT, OUTPUT>>;
1635
1678
  private _startTurn;
1636
1679
  private _acquireSandbox;
1637
1680
  private _resolveTurnInput;
1638
1681
  private _toToolSpecs;
1639
1682
  private _toGenerateResult;
1683
+ private _resolveResponseFormat;
1640
1684
  }
1641
1685
 
1642
1686
  type SandboxBootstrapSettings = Omit<HarnessAgentSandboxConfig, 'onSession'>;
@@ -250,6 +250,7 @@ var HarnessStreamTextResult = class {
250
250
  this.toolsContext = options.toolsContext;
251
251
  this.providerName = `harness:${options.harnessId}`;
252
252
  this.modelId = options.sessionId;
253
+ this.outputSpecification = options.output;
253
254
  let controllerRef;
254
255
  const baseStream = new ReadableStream({
255
256
  start(c) {
@@ -258,18 +259,13 @@ var HarnessStreamTextResult = class {
258
259
  }
259
260
  });
260
261
  this.fullStreamController = controllerRef;
261
- const [forFull, forText] = baseStream.tee();
262
- this.stream = forFull;
263
- this.fullStream = this.stream;
264
- this.textStream = forText.pipeThrough(
262
+ this.baseStream = options.output == null ? baseStream.pipeThrough(
265
263
  new TransformStream({
266
264
  transform(part, controller) {
267
- if (part.type === "text-delta") {
268
- controller.enqueue(part.text);
269
- }
265
+ controller.enqueue({ part, partialOutput: void 0 });
270
266
  }
271
267
  })
272
- );
268
+ ) : baseStream.pipeThrough(createOutputTransformStream(options.output));
273
269
  }
274
270
  // ─── Writer-side methods used by the driver ────────────────────────
275
271
  /**
@@ -525,6 +521,38 @@ var HarnessStreamTextResult = class {
525
521
  }
526
522
  }
527
523
  // ─── Reader-side public surface (StreamTextResult contract) ────────
524
+ teeStream() {
525
+ const [stream, remainingStream] = this.baseStream.tee();
526
+ this.baseStream = remainingStream;
527
+ return stream;
528
+ }
529
+ get stream() {
530
+ return createAsyncIterableStream(
531
+ this.teeStream().pipeThrough(
532
+ new TransformStream({
533
+ transform({ part }, controller) {
534
+ controller.enqueue(part);
535
+ }
536
+ })
537
+ )
538
+ );
539
+ }
540
+ get fullStream() {
541
+ return this.stream;
542
+ }
543
+ get textStream() {
544
+ return createAsyncIterableStream(
545
+ this.stream.pipeThrough(
546
+ new TransformStream({
547
+ transform(part, controller) {
548
+ if (part.type === "text-delta") {
549
+ controller.enqueue(part.text);
550
+ }
551
+ }
552
+ })
553
+ )
554
+ );
555
+ }
528
556
  get content() {
529
557
  return this._content.promise;
530
558
  }
@@ -594,18 +622,48 @@ var HarnessStreamTextResult = class {
594
622
  get providerMetadata() {
595
623
  return this._providerMetadata.promise;
596
624
  }
597
- // Output-specification surfaces are not yet supported.
598
625
  get experimental_partialOutputStream() {
599
- throw notSupportedYet("partial output stream");
626
+ return this.partialOutputStream;
600
627
  }
601
628
  get partialOutputStream() {
602
- throw notSupportedYet("partial output stream");
629
+ return createAsyncIterableStream(
630
+ this.teeStream().pipeThrough(
631
+ new TransformStream({
632
+ transform({ partialOutput }, controller) {
633
+ if (partialOutput != null) {
634
+ controller.enqueue(partialOutput);
635
+ }
636
+ }
637
+ })
638
+ )
639
+ );
603
640
  }
604
641
  get elementStream() {
605
- throw notSupportedYet("element stream");
642
+ var _a4, _b4, _c;
643
+ const transform = (_a4 = this.outputSpecification) == null ? void 0 : _a4.createElementStreamTransform();
644
+ if (transform == null) {
645
+ throw notSupportedYet(
646
+ `element streams in ${(_c = (_b4 = this.outputSpecification) == null ? void 0 : _b4.name) != null ? _c : "text"} mode`
647
+ );
648
+ }
649
+ return createAsyncIterableStream(
650
+ this.teeStream().pipeThrough(transform)
651
+ );
606
652
  }
607
653
  get output() {
608
- throw notSupportedYet("structured output");
654
+ return this.finalStep.then((step) => {
655
+ if (this.outputSpecification == null) {
656
+ throw notSupportedYet("structured output");
657
+ }
658
+ return this.outputSpecification.parseCompleteOutput(
659
+ { text: step.text },
660
+ {
661
+ response: step.response,
662
+ usage: step.usage,
663
+ finishReason: step.finishReason
664
+ }
665
+ );
666
+ });
609
667
  }
610
668
  async consumeStream() {
611
669
  const reader = this.fullStream.getReader();
@@ -725,6 +783,73 @@ var HarnessStreamTextResult = class {
725
783
  }
726
784
  }
727
785
  };
786
+ function createOutputTransformStream(output) {
787
+ let firstTextChunkId;
788
+ let text = "";
789
+ let textChunk = "";
790
+ let textProviderMetadata;
791
+ let lastPublishedValue = "";
792
+ const publishTextChunk = (options) => {
793
+ options.controller.enqueue({
794
+ part: {
795
+ type: "text-delta",
796
+ id: firstTextChunkId,
797
+ text: textChunk,
798
+ providerMetadata: textProviderMetadata
799
+ },
800
+ partialOutput: options.partialOutput
801
+ });
802
+ textChunk = "";
803
+ };
804
+ return new TransformStream({
805
+ async transform(chunk, controller) {
806
+ var _a4;
807
+ if (chunk.type === "finish-step" && textChunk.length > 0) {
808
+ publishTextChunk({ controller });
809
+ }
810
+ if (chunk.type !== "text-delta" && chunk.type !== "text-start" && chunk.type !== "text-end") {
811
+ controller.enqueue({ part: chunk, partialOutput: void 0 });
812
+ return;
813
+ }
814
+ if (firstTextChunkId == null) {
815
+ firstTextChunkId = chunk.id;
816
+ } else if (chunk.id !== firstTextChunkId) {
817
+ controller.enqueue({ part: chunk, partialOutput: void 0 });
818
+ return;
819
+ }
820
+ if (chunk.type === "text-start") {
821
+ controller.enqueue({ part: chunk, partialOutput: void 0 });
822
+ return;
823
+ }
824
+ if (chunk.type === "text-end") {
825
+ if (textChunk.length > 0) {
826
+ publishTextChunk({ controller });
827
+ }
828
+ controller.enqueue({ part: chunk, partialOutput: void 0 });
829
+ return;
830
+ }
831
+ text += chunk.text;
832
+ textChunk += chunk.text;
833
+ textProviderMetadata = (_a4 = chunk.providerMetadata) != null ? _a4 : textProviderMetadata;
834
+ if (chunk.text.length === 0 && chunk.providerMetadata != null) {
835
+ controller.enqueue({ part: chunk, partialOutput: void 0 });
836
+ return;
837
+ }
838
+ const result = await output.parsePartialOutput({ text });
839
+ if (result === void 0) {
840
+ return;
841
+ }
842
+ const currentValue = typeof result.partial === "string" ? result.partial : JSON.stringify(result.partial);
843
+ if (currentValue !== lastPublishedValue) {
844
+ publishTextChunk({
845
+ controller,
846
+ partialOutput: result.partial
847
+ });
848
+ lastPublishedValue = currentValue;
849
+ }
850
+ }
851
+ });
852
+ }
728
853
  function createEmptyPerformance() {
729
854
  return {
730
855
  effectiveOutputTokensPerSecond: 0,
@@ -1372,7 +1497,8 @@ function runPrompt(input) {
1372
1497
  // toolsContext is not configurable for harnesses; pass undefined cast.
1373
1498
  toolsContext: void 0,
1374
1499
  harnessId: input.harness.harnessId,
1375
- sessionId: input.session.sessionId
1500
+ sessionId: input.session.sessionId,
1501
+ output: input.output
1376
1502
  });
1377
1503
  const pendingToolApprovals = (_a4 = input.pendingToolApprovals) != null ? _a4 : [];
1378
1504
  const pendingToolResults = (_b4 = input.pendingToolResults) != null ? _b4 : [];
@@ -1413,6 +1539,7 @@ function runPrompt(input) {
1413
1539
  try {
1414
1540
  bridge = await toHarnessStream({
1415
1541
  invoke: input.mode === "continue" ? (emit) => input.session.doContinueTurn({
1542
+ responseFormat: input.responseFormat,
1416
1543
  tools: input.toolSpecs,
1417
1544
  instructions: input.instructions,
1418
1545
  abortSignal: input.abortSignal,
@@ -1425,6 +1552,7 @@ function runPrompt(input) {
1425
1552
  }
1426
1553
  return input.session.doPromptTurn({
1427
1554
  prompt: input.prompt,
1555
+ responseFormat: input.responseFormat,
1428
1556
  tools: input.toolSpecs,
1429
1557
  instructions: input.instructions,
1430
1558
  abortSignal: input.abortSignal,
@@ -2273,6 +2401,8 @@ var HarnessAgentSession = class {
2273
2401
  sessionWorkDir: this.sessionWorkDir,
2274
2402
  runtimeContext: options.runtimeContext,
2275
2403
  abortSignal: options.abortSignal,
2404
+ responseFormat: options.responseFormat,
2405
+ output: options.output,
2276
2406
  telemetry: options.telemetry,
2277
2407
  stopConditions: options.stopConditions,
2278
2408
  toolApproval: this.toolApproval,
@@ -2326,6 +2456,8 @@ var HarnessAgentSession = class {
2326
2456
  sessionWorkDir: this.sessionWorkDir,
2327
2457
  runtimeContext: options.runtimeContext,
2328
2458
  abortSignal: options.abortSignal,
2459
+ responseFormat: options.responseFormat,
2460
+ output: options.output,
2329
2461
  telemetry: options.telemetry,
2330
2462
  stopConditions: options.stopConditions,
2331
2463
  toolApproval: this.toolApproval,
@@ -3342,11 +3474,13 @@ var HarnessAgent = class {
3342
3474
  async generate(options) {
3343
3475
  const turnInput = this._resolveTurnInput(options);
3344
3476
  const runtimeContext = {};
3477
+ const responseFormat = await this._resolveResponseFormat();
3345
3478
  const { result, done } = this._startTurn({
3346
3479
  session: options.session,
3347
3480
  turnInput,
3348
3481
  runtimeContext,
3349
- abortSignal: options.abortSignal
3482
+ abortSignal: options.abortSignal,
3483
+ responseFormat
3350
3484
  });
3351
3485
  await done;
3352
3486
  return this._toGenerateResult(result);
@@ -3354,11 +3488,13 @@ var HarnessAgent = class {
3354
3488
  async stream(options) {
3355
3489
  const turnInput = this._resolveTurnInput(options);
3356
3490
  const runtimeContext = {};
3491
+ const responseFormat = await this._resolveResponseFormat();
3357
3492
  const { result } = this._startTurn({
3358
3493
  session: options.session,
3359
3494
  turnInput,
3360
3495
  runtimeContext,
3361
- abortSignal: options.abortSignal
3496
+ abortSignal: options.abortSignal,
3497
+ responseFormat
3362
3498
  });
3363
3499
  return result;
3364
3500
  }
@@ -3370,6 +3506,7 @@ var HarnessAgent = class {
3370
3506
  async continueGenerate(options) {
3371
3507
  var _a4, _b4;
3372
3508
  const runtimeContext = {};
3509
+ const responseFormat = await this._resolveResponseFormat();
3373
3510
  const { result, done } = this._startTurn({
3374
3511
  session: options.session,
3375
3512
  turnInput: {
@@ -3378,7 +3515,8 @@ var HarnessAgent = class {
3378
3515
  toolResultContinuations: (_b4 = options.toolResultContinuations) != null ? _b4 : []
3379
3516
  },
3380
3517
  runtimeContext,
3381
- abortSignal: options.abortSignal
3518
+ abortSignal: options.abortSignal,
3519
+ responseFormat
3382
3520
  });
3383
3521
  await done;
3384
3522
  return this._toGenerateResult(result);
@@ -3394,6 +3532,7 @@ var HarnessAgent = class {
3394
3532
  async continueStream(options) {
3395
3533
  var _a4, _b4;
3396
3534
  const runtimeContext = {};
3535
+ const responseFormat = await this._resolveResponseFormat();
3397
3536
  const { result } = this._startTurn({
3398
3537
  session: options.session,
3399
3538
  turnInput: {
@@ -3402,7 +3541,8 @@ var HarnessAgent = class {
3402
3541
  toolResultContinuations: (_b4 = options.toolResultContinuations) != null ? _b4 : []
3403
3542
  },
3404
3543
  runtimeContext,
3405
- abortSignal: options.abortSignal
3544
+ abortSignal: options.abortSignal,
3545
+ responseFormat
3406
3546
  });
3407
3547
  return result;
3408
3548
  }
@@ -3417,6 +3557,8 @@ var HarnessAgent = class {
3417
3557
  builtinToolFiltering: this.builtinToolFiltering,
3418
3558
  runtimeContext: input.runtimeContext,
3419
3559
  abortSignal: input.abortSignal,
3560
+ responseFormat: input.responseFormat,
3561
+ output: this.settings.output,
3420
3562
  telemetry: this.settings.telemetry,
3421
3563
  stopConditions: this.stopConditions,
3422
3564
  toolApprovalContinuations: input.turnInput.toolApprovalContinuations,
@@ -3432,6 +3574,8 @@ var HarnessAgent = class {
3432
3574
  builtinToolFiltering: this.builtinToolFiltering,
3433
3575
  runtimeContext: input.runtimeContext,
3434
3576
  abortSignal: input.abortSignal,
3577
+ responseFormat: input.responseFormat,
3578
+ output: this.settings.output,
3435
3579
  telemetry: this.settings.telemetry,
3436
3580
  stopConditions: this.stopConditions
3437
3581
  });
@@ -3516,20 +3660,34 @@ var HarnessAgent = class {
3516
3660
  return specs;
3517
3661
  }
3518
3662
  async _toGenerateResult(streamResult) {
3519
- const [steps, usage, responseMessages] = await Promise.all([
3663
+ const [steps, usage, responseMessages, output] = await Promise.all([
3520
3664
  streamResult.steps,
3521
3665
  streamResult.usage,
3522
- streamResult.responseMessages
3666
+ streamResult.responseMessages,
3667
+ this.settings.output == null ? Promise.resolve(void 0) : streamResult.output
3523
3668
  ]);
3524
- return new HarnessGenerateTextResult({ steps, usage, responseMessages });
3669
+ return new HarnessGenerateTextResult({ steps, usage, responseMessages, output });
3670
+ }
3671
+ async _resolveResponseFormat() {
3672
+ var _a4;
3673
+ const responseFormat = await ((_a4 = this.settings.output) == null ? void 0 : _a4.responseFormat);
3674
+ if (responseFormat == null || responseFormat.type === "text") {
3675
+ return responseFormat == null ? void 0 : { type: "text" };
3676
+ }
3677
+ return {
3678
+ type: "json",
3679
+ ...responseFormat.schema == null ? {} : { schema: responseFormat.schema },
3680
+ ...responseFormat.name == null ? {} : { name: responseFormat.name },
3681
+ ...responseFormat.description == null ? {} : { description: responseFormat.description }
3682
+ };
3525
3683
  }
3526
3684
  };
3527
3685
  var HarnessGenerateTextResult = class {
3528
3686
  constructor(options) {
3529
- this.output = void 0;
3530
3687
  this.steps = options.steps;
3531
3688
  this.usage = options.usage;
3532
3689
  this.responseMessages = options.responseMessages;
3690
+ this.output = options.output;
3533
3691
  }
3534
3692
  get finalStep() {
3535
3693
  return this.steps.at(-1);