@ai-sdk/harness 1.0.36 → 1.0.38
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 +22 -0
- package/agent/index.ts +5 -0
- package/dist/agent/index.d.ts +36 -4
- package/dist/agent/index.js +290 -102
- package/dist/agent/index.js.map +1 -1
- package/dist/index.d.ts +11 -1
- package/package.json +3 -3
- package/src/agent/harness-agent-session.ts +66 -20
- package/src/agent/harness-agent-tool-result-continuation.ts +62 -0
- package/src/agent/harness-agent-types.ts +2 -0
- package/src/agent/harness-agent.ts +23 -2
- package/src/agent/internal/harness-stream-text-result.ts +60 -35
- package/src/agent/internal/lifecycle-state-validation.ts +13 -0
- package/src/agent/internal/run-prompt.ts +159 -42
- package/src/agent/internal/translate-stream-part.ts +3 -2
- package/src/v1/harness-v1-lifecycle-state.ts +12 -0
- package/src/v1/index.ts +1 -0
package/dist/index.d.ts
CHANGED
|
@@ -297,6 +297,11 @@ type HarnessV1PendingToolApproval = {
|
|
|
297
297
|
readonly providerExecuted?: boolean;
|
|
298
298
|
readonly nativeName?: string;
|
|
299
299
|
};
|
|
300
|
+
type HarnessV1PendingToolResult = {
|
|
301
|
+
readonly toolCallId: string;
|
|
302
|
+
readonly toolName: string;
|
|
303
|
+
readonly input: string;
|
|
304
|
+
};
|
|
300
305
|
type HarnessV1LifecycleStateBase = {
|
|
301
306
|
/**
|
|
302
307
|
* Identifier of the harness that produced this state. Used by adapters to
|
|
@@ -339,6 +344,11 @@ type HarnessV1ContinueTurnState = HarnessV1LifecycleStateBase & {
|
|
|
339
344
|
* without the harness framework owning storage.
|
|
340
345
|
*/
|
|
341
346
|
readonly pendingToolApprovals?: readonly HarnessV1PendingToolApproval[];
|
|
347
|
+
/**
|
|
348
|
+
* Framework-owned client tool calls that are waiting for a caller-provided
|
|
349
|
+
* result before the underlying turn can continue.
|
|
350
|
+
*/
|
|
351
|
+
readonly pendingToolResults?: readonly HarnessV1PendingToolResult[];
|
|
342
352
|
};
|
|
343
353
|
type HarnessV1LifecycleState = HarnessV1ResumeSessionState | HarnessV1ContinueTurnState;
|
|
344
354
|
|
|
@@ -1584,4 +1594,4 @@ declare class HarnessCapabilityUnsupportedError extends HarnessError {
|
|
|
1584
1594
|
static isInstance(error: unknown): error is HarnessCapabilityUnsupportedError;
|
|
1585
1595
|
}
|
|
1586
1596
|
|
|
1587
|
-
export { HARNESS_V1_BUILTIN_TOOLS, HARNESS_V1_BUILTIN_TOOL_NAMES, HarnessCapabilityUnsupportedError, HarnessError, type HarnessV1, type HarnessV1Bootstrap, type HarnessV1BootstrapCommand, type HarnessV1BootstrapFile, type HarnessV1BridgeDebugEvent, type HarnessV1BridgeOutboundMessage, type HarnessV1BridgeReady, type HarnessV1BridgeSandboxLog, type HarnessV1BridgeToolWire, type HarnessV1BuiltinTool, type HarnessV1BuiltinToolFiltering, type HarnessV1BuiltinToolName, type HarnessV1BuiltinToolUseKind, type HarnessV1CallWarning, type HarnessV1ContinueTurnOptions, type HarnessV1ContinueTurnState, type HarnessV1DebugConfig, type HarnessV1DebugLevel, type HarnessV1Diagnostic, type HarnessV1LifecycleState, type HarnessV1Metadata, type HarnessV1NetworkPolicy, type HarnessV1NetworkSandboxSession, type HarnessV1Observability, type HarnessV1PendingToolApproval, type HarnessV1PermissionMode, type HarnessV1Prompt, type HarnessV1PromptControl, type HarnessV1PromptTurnOptions, type HarnessV1ResumeSessionState, type HarnessV1SandboxProvider, type HarnessV1Session, type HarnessV1Skill, type HarnessV1StartOptions, type HarnessV1StreamPart, type HarnessV1ToolSpec, commonTool, getHarnessV1BuiltinToolFilteringDenialReason, harnessV1BridgeAbortInboundSchema, harnessV1BridgeBuiltinToolFilteringSchema, harnessV1BridgeDebugEventSchema, harnessV1BridgeDetachInboundSchema, harnessV1BridgeDetachSchema, harnessV1BridgeHelloSchema, harnessV1BridgeInboundCommandSchemas, harnessV1BridgeOutboundMessageSchema, harnessV1BridgePermissionModeSchema, harnessV1BridgeReadySchema, harnessV1BridgeResumeInboundSchema, harnessV1BridgeSandboxLogSchema, harnessV1BridgeShutdownInboundSchema, harnessV1BridgeStartBaseSchema, harnessV1BridgeThreadSchema, harnessV1BridgeToolApprovalResponseInboundSchema, harnessV1BridgeToolResultInboundSchema, harnessV1BridgeToolWireSchema, harnessV1BridgeUserMessageInboundSchema, harnessV1DebugConfigSchema, harnessV1DebugLevelSchema, harnessV1DiagnosticFromBridgeFrame, harnessV1ErrorPartSchema, harnessV1FileChangePartSchema, harnessV1FinishPartSchema, harnessV1FinishStepPartSchema, harnessV1RawPartSchema, harnessV1ReasoningDeltaPartSchema, harnessV1ReasoningEndPartSchema, harnessV1ReasoningStartPartSchema, harnessV1StreamPartSchema, harnessV1StreamStartPartSchema, harnessV1TextDeltaPartSchema, harnessV1TextEndPartSchema, harnessV1TextStartPartSchema, harnessV1ToolApprovalRequestPartSchema, harnessV1ToolCallPartSchema, harnessV1ToolResultPartSchema, isHarnessV1BuiltinToolIncluded };
|
|
1597
|
+
export { HARNESS_V1_BUILTIN_TOOLS, HARNESS_V1_BUILTIN_TOOL_NAMES, HarnessCapabilityUnsupportedError, HarnessError, type HarnessV1, type HarnessV1Bootstrap, type HarnessV1BootstrapCommand, type HarnessV1BootstrapFile, type HarnessV1BridgeDebugEvent, type HarnessV1BridgeOutboundMessage, type HarnessV1BridgeReady, type HarnessV1BridgeSandboxLog, type HarnessV1BridgeToolWire, type HarnessV1BuiltinTool, type HarnessV1BuiltinToolFiltering, type HarnessV1BuiltinToolName, type HarnessV1BuiltinToolUseKind, type HarnessV1CallWarning, type HarnessV1ContinueTurnOptions, type HarnessV1ContinueTurnState, type HarnessV1DebugConfig, type HarnessV1DebugLevel, type HarnessV1Diagnostic, type HarnessV1LifecycleState, type HarnessV1Metadata, type HarnessV1NetworkPolicy, type HarnessV1NetworkSandboxSession, type HarnessV1Observability, type HarnessV1PendingToolApproval, type HarnessV1PendingToolResult, type HarnessV1PermissionMode, type HarnessV1Prompt, type HarnessV1PromptControl, type HarnessV1PromptTurnOptions, type HarnessV1ResumeSessionState, type HarnessV1SandboxProvider, type HarnessV1Session, type HarnessV1Skill, type HarnessV1StartOptions, type HarnessV1StreamPart, type HarnessV1ToolSpec, commonTool, getHarnessV1BuiltinToolFilteringDenialReason, harnessV1BridgeAbortInboundSchema, harnessV1BridgeBuiltinToolFilteringSchema, harnessV1BridgeDebugEventSchema, harnessV1BridgeDetachInboundSchema, harnessV1BridgeDetachSchema, harnessV1BridgeHelloSchema, harnessV1BridgeInboundCommandSchemas, harnessV1BridgeOutboundMessageSchema, harnessV1BridgePermissionModeSchema, harnessV1BridgeReadySchema, harnessV1BridgeResumeInboundSchema, harnessV1BridgeSandboxLogSchema, harnessV1BridgeShutdownInboundSchema, harnessV1BridgeStartBaseSchema, harnessV1BridgeThreadSchema, harnessV1BridgeToolApprovalResponseInboundSchema, harnessV1BridgeToolResultInboundSchema, harnessV1BridgeToolWireSchema, harnessV1BridgeUserMessageInboundSchema, harnessV1DebugConfigSchema, harnessV1DebugLevelSchema, harnessV1DiagnosticFromBridgeFrame, harnessV1ErrorPartSchema, harnessV1FileChangePartSchema, harnessV1FinishPartSchema, harnessV1FinishStepPartSchema, harnessV1RawPartSchema, harnessV1ReasoningDeltaPartSchema, harnessV1ReasoningEndPartSchema, harnessV1ReasoningStartPartSchema, harnessV1StreamPartSchema, harnessV1StreamStartPartSchema, harnessV1TextDeltaPartSchema, harnessV1TextEndPartSchema, harnessV1TextStartPartSchema, harnessV1ToolApprovalRequestPartSchema, harnessV1ToolCallPartSchema, harnessV1ToolResultPartSchema, isHarnessV1BuiltinToolIncluded };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/harness",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.38",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@ai-sdk/provider": "4.0.3",
|
|
48
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
49
|
-
"ai": "7.0.
|
|
48
|
+
"@ai-sdk/provider-utils": "5.0.12",
|
|
49
|
+
"ai": "7.0.33"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"ws": "^8.21.0",
|
|
@@ -11,11 +11,13 @@ import type {
|
|
|
11
11
|
HarnessAgentAdapterSession,
|
|
12
12
|
HarnessAgentContinueTurnState,
|
|
13
13
|
HarnessAgentPendingToolApproval,
|
|
14
|
+
HarnessAgentPendingToolResult,
|
|
14
15
|
HarnessAgentPrompt,
|
|
15
16
|
HarnessAgentResumeSessionState,
|
|
16
17
|
HarnessAgentToolSpec,
|
|
17
18
|
} from './harness-agent-types';
|
|
18
19
|
import type { HarnessAgentToolApprovalContinuation } from './harness-agent-tool-approval-continuation';
|
|
20
|
+
import type { HarnessAgentToolResultContinuation } from './harness-agent-tool-result-continuation';
|
|
19
21
|
import { releaseBridgePort } from './internal/bridge-port-registry';
|
|
20
22
|
import { validateLifecycleStateData } from './internal/lifecycle-state-validation';
|
|
21
23
|
import { runPrompt } from './internal/run-prompt';
|
|
@@ -34,6 +36,7 @@ type HarnessAgentTurnState =
|
|
|
34
36
|
| 'idle'
|
|
35
37
|
| 'running'
|
|
36
38
|
| 'awaiting-approval'
|
|
39
|
+
| 'awaiting-tool-result'
|
|
37
40
|
| 'suspended';
|
|
38
41
|
|
|
39
42
|
/**
|
|
@@ -71,6 +74,10 @@ export class HarnessAgentSession {
|
|
|
71
74
|
string,
|
|
72
75
|
HarnessAgentPendingToolApproval
|
|
73
76
|
>();
|
|
77
|
+
private readonly pendingToolResults = new Map<
|
|
78
|
+
string,
|
|
79
|
+
HarnessAgentPendingToolResult
|
|
80
|
+
>();
|
|
74
81
|
private sessionState: HarnessAgentSessionState = 'active';
|
|
75
82
|
private turnState: HarnessAgentTurnState;
|
|
76
83
|
private turnSequence = 0;
|
|
@@ -92,6 +99,7 @@ export class HarnessAgentSession {
|
|
|
92
99
|
sessionWorkDir: string;
|
|
93
100
|
toolApproval: HarnessAgentToolApprovalConfiguration | undefined;
|
|
94
101
|
pendingToolApprovals?: readonly HarnessAgentPendingToolApproval[];
|
|
102
|
+
pendingToolResults?: readonly HarnessAgentPendingToolResult[];
|
|
95
103
|
turnState?: HarnessAgentTurnState;
|
|
96
104
|
}) {
|
|
97
105
|
this.sessionId = options.sessionId;
|
|
@@ -105,9 +113,16 @@ export class HarnessAgentSession {
|
|
|
105
113
|
for (const approval of options.pendingToolApprovals ?? []) {
|
|
106
114
|
this.pendingToolApprovals.set(approval.approvalId, approval);
|
|
107
115
|
}
|
|
116
|
+
for (const pendingResult of options.pendingToolResults ?? []) {
|
|
117
|
+
this.pendingToolResults.set(pendingResult.toolCallId, pendingResult);
|
|
118
|
+
}
|
|
108
119
|
this.turnState =
|
|
109
120
|
options.turnState ??
|
|
110
|
-
(this.pendingToolApprovals.size > 0
|
|
121
|
+
(this.pendingToolApprovals.size > 0
|
|
122
|
+
? 'awaiting-approval'
|
|
123
|
+
: this.pendingToolResults.size > 0
|
|
124
|
+
? 'awaiting-tool-result'
|
|
125
|
+
: 'idle');
|
|
111
126
|
this.isResume = options.underlyingSession.isResume;
|
|
112
127
|
}
|
|
113
128
|
|
|
@@ -135,6 +150,10 @@ export class HarnessAgentSession {
|
|
|
135
150
|
return this.sessionWorkDir;
|
|
136
151
|
}
|
|
137
152
|
|
|
153
|
+
hasUnfinishedTurn(): boolean {
|
|
154
|
+
return this.turnState !== 'idle';
|
|
155
|
+
}
|
|
156
|
+
|
|
138
157
|
promptTurn<TOOLS extends ToolSet, RUNTIME_CONTEXT extends Context>(options: {
|
|
139
158
|
prompt: HarnessAgentPrompt;
|
|
140
159
|
instructions: string | undefined;
|
|
@@ -167,6 +186,7 @@ export class HarnessAgentSession {
|
|
|
167
186
|
telemetry: options.telemetry,
|
|
168
187
|
toolApproval: this.toolApproval,
|
|
169
188
|
pendingToolApprovals: this.getPendingToolApprovals(),
|
|
189
|
+
pendingToolResults: this.getPendingToolResults(),
|
|
170
190
|
onPendingToolApproval: approval => {
|
|
171
191
|
this.pendingToolApprovals.set(approval.approvalId, approval);
|
|
172
192
|
this.markAwaitingApprovalIfActive();
|
|
@@ -174,11 +194,20 @@ export class HarnessAgentSession {
|
|
|
174
194
|
onToolApprovalSettled: approvalId => {
|
|
175
195
|
this.pendingToolApprovals.delete(approvalId);
|
|
176
196
|
},
|
|
197
|
+
onPendingToolResult: pendingResult => {
|
|
198
|
+
this.pendingToolResults.set(pendingResult.toolCallId, pendingResult);
|
|
199
|
+
this.markAwaitingToolResultIfActive();
|
|
200
|
+
},
|
|
201
|
+
onToolResultSettled: toolCallId => {
|
|
202
|
+
this.pendingToolResults.delete(toolCallId);
|
|
203
|
+
},
|
|
177
204
|
onTurnFinished: () => {
|
|
178
205
|
this.finishTrackedTurn({ turnId });
|
|
179
206
|
},
|
|
207
|
+
onTurnFailed: () => {
|
|
208
|
+
this.finishTrackedTurn({ turnId });
|
|
209
|
+
},
|
|
180
210
|
});
|
|
181
|
-
this.trackTurnCompletion({ done: turn.done, turnId });
|
|
182
211
|
return turn;
|
|
183
212
|
} catch (error) {
|
|
184
213
|
this.finishTrackedTurn({ turnId });
|
|
@@ -201,6 +230,9 @@ export class HarnessAgentSession {
|
|
|
201
230
|
toolApprovalContinuations?:
|
|
202
231
|
| readonly HarnessAgentToolApprovalContinuation[]
|
|
203
232
|
| undefined;
|
|
233
|
+
toolResultContinuations?:
|
|
234
|
+
| readonly HarnessAgentToolResultContinuation[]
|
|
235
|
+
| undefined;
|
|
204
236
|
}): HarnessAgentTurnResult<TOOLS, RUNTIME_CONTEXT> {
|
|
205
237
|
const session = this.requireReusableSession();
|
|
206
238
|
this.requireContinuableTurn();
|
|
@@ -223,7 +255,9 @@ export class HarnessAgentSession {
|
|
|
223
255
|
telemetry: options.telemetry,
|
|
224
256
|
toolApproval: this.toolApproval,
|
|
225
257
|
pendingToolApprovals: this.getPendingToolApprovals(),
|
|
258
|
+
pendingToolResults: this.getPendingToolResults(),
|
|
226
259
|
toolApprovalContinuations: options.toolApprovalContinuations,
|
|
260
|
+
toolResultContinuations: options.toolResultContinuations,
|
|
227
261
|
onPendingToolApproval: approval => {
|
|
228
262
|
this.pendingToolApprovals.set(approval.approvalId, approval);
|
|
229
263
|
this.markAwaitingApprovalIfActive();
|
|
@@ -231,11 +265,20 @@ export class HarnessAgentSession {
|
|
|
231
265
|
onToolApprovalSettled: approvalId => {
|
|
232
266
|
this.pendingToolApprovals.delete(approvalId);
|
|
233
267
|
},
|
|
268
|
+
onPendingToolResult: pendingResult => {
|
|
269
|
+
this.pendingToolResults.set(pendingResult.toolCallId, pendingResult);
|
|
270
|
+
this.markAwaitingToolResultIfActive();
|
|
271
|
+
},
|
|
272
|
+
onToolResultSettled: toolCallId => {
|
|
273
|
+
this.pendingToolResults.delete(toolCallId);
|
|
274
|
+
},
|
|
234
275
|
onTurnFinished: () => {
|
|
235
276
|
this.finishTrackedTurn({ turnId });
|
|
236
277
|
},
|
|
278
|
+
onTurnFailed: () => {
|
|
279
|
+
this.finishTrackedTurn({ turnId });
|
|
280
|
+
},
|
|
237
281
|
});
|
|
238
|
-
this.trackTurnCompletion({ done: turn.done, turnId });
|
|
239
282
|
return turn;
|
|
240
283
|
} catch (error) {
|
|
241
284
|
this.finishTrackedTurn({ turnId });
|
|
@@ -382,11 +425,16 @@ export class HarnessAgentSession {
|
|
|
382
425
|
return Array.from(this.pendingToolApprovals.values());
|
|
383
426
|
}
|
|
384
427
|
|
|
385
|
-
private
|
|
428
|
+
private getPendingToolResults(): readonly HarnessAgentPendingToolResult[] {
|
|
429
|
+
return Array.from(this.pendingToolResults.values());
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
private addPendingToolState(
|
|
386
433
|
state: HarnessAgentContinueTurnState,
|
|
387
434
|
): HarnessAgentContinueTurnState {
|
|
388
435
|
const pendingToolApprovals = this.getPendingToolApprovals();
|
|
389
|
-
|
|
436
|
+
const pendingToolResults = this.getPendingToolResults();
|
|
437
|
+
if (pendingToolApprovals.length === 0 && pendingToolResults.length === 0) {
|
|
390
438
|
return {
|
|
391
439
|
type: state.type,
|
|
392
440
|
harnessId: state.harnessId,
|
|
@@ -396,7 +444,8 @@ export class HarnessAgentSession {
|
|
|
396
444
|
}
|
|
397
445
|
return {
|
|
398
446
|
...state,
|
|
399
|
-
pendingToolApprovals,
|
|
447
|
+
...(pendingToolApprovals.length > 0 ? { pendingToolApprovals } : {}),
|
|
448
|
+
...(pendingToolResults.length > 0 ? { pendingToolResults } : {}),
|
|
400
449
|
};
|
|
401
450
|
}
|
|
402
451
|
|
|
@@ -410,7 +459,7 @@ export class HarnessAgentSession {
|
|
|
410
459
|
expectedType: 'continue-turn',
|
|
411
460
|
});
|
|
412
461
|
this.turnState = 'suspended';
|
|
413
|
-
return this.
|
|
462
|
+
return this.addPendingToolState(validated);
|
|
414
463
|
}
|
|
415
464
|
|
|
416
465
|
private toResumeStateWithContinuation(options: {
|
|
@@ -441,6 +490,7 @@ export class HarnessAgentSession {
|
|
|
441
490
|
private requireContinuableTurn(): void {
|
|
442
491
|
if (
|
|
443
492
|
this.turnState === 'awaiting-approval' ||
|
|
493
|
+
this.turnState === 'awaiting-tool-result' ||
|
|
444
494
|
this.turnState === 'suspended'
|
|
445
495
|
) {
|
|
446
496
|
return;
|
|
@@ -461,6 +511,12 @@ export class HarnessAgentSession {
|
|
|
461
511
|
}
|
|
462
512
|
}
|
|
463
513
|
|
|
514
|
+
private markAwaitingToolResultIfActive(): void {
|
|
515
|
+
if (this.sessionState === 'active') {
|
|
516
|
+
this.turnState = 'awaiting-tool-result';
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
464
520
|
private startTrackedTurn(): number {
|
|
465
521
|
const turnId = ++this.turnSequence;
|
|
466
522
|
this.activeTurnSequence = turnId;
|
|
@@ -468,22 +524,12 @@ export class HarnessAgentSession {
|
|
|
468
524
|
return turnId;
|
|
469
525
|
}
|
|
470
526
|
|
|
471
|
-
private trackTurnCompletion(options: {
|
|
472
|
-
done: Promise<void>;
|
|
473
|
-
turnId: number;
|
|
474
|
-
}): void {
|
|
475
|
-
void Promise.resolve(options.done)
|
|
476
|
-
.finally(() => {
|
|
477
|
-
this.finishTrackedTurn({ turnId: options.turnId });
|
|
478
|
-
})
|
|
479
|
-
.catch(() => {});
|
|
480
|
-
}
|
|
481
|
-
|
|
482
527
|
private finishTrackedTurn(options: { turnId: number }): void {
|
|
483
528
|
if (this.sessionState !== 'active') return;
|
|
484
529
|
if (this.activeTurnSequence !== options.turnId) return;
|
|
485
|
-
this.
|
|
486
|
-
|
|
530
|
+
this.pendingToolApprovals.clear();
|
|
531
|
+
this.pendingToolResults.clear();
|
|
532
|
+
this.turnState = 'idle';
|
|
487
533
|
}
|
|
488
534
|
|
|
489
535
|
private endLocalHandle(options: {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { ModelMessage, ToolResultOutput } from '@ai-sdk/provider-utils';
|
|
2
|
+
|
|
3
|
+
export type HarnessAgentToolResultContinuation = {
|
|
4
|
+
readonly toolCallId: string;
|
|
5
|
+
readonly output: unknown;
|
|
6
|
+
readonly isError?: boolean;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Extract client-provided tool results from the trailing tool message and
|
|
11
|
+
* convert AI SDK model-output wrappers back into values a harness runtime can
|
|
12
|
+
* return from its pending host-tool invocation.
|
|
13
|
+
*/
|
|
14
|
+
export function collectHarnessAgentToolResultContinuations(input: {
|
|
15
|
+
messages: readonly ModelMessage[];
|
|
16
|
+
}): readonly HarnessAgentToolResultContinuation[] {
|
|
17
|
+
const lastMessage = input.messages.at(-1);
|
|
18
|
+
if (lastMessage?.role !== 'tool') return [];
|
|
19
|
+
|
|
20
|
+
return lastMessage.content
|
|
21
|
+
.filter(part => part.type === 'tool-result')
|
|
22
|
+
.map(part =>
|
|
23
|
+
toToolResultContinuation({
|
|
24
|
+
toolCallId: part.toolCallId,
|
|
25
|
+
output: part.output,
|
|
26
|
+
}),
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function toToolResultContinuation(input: {
|
|
31
|
+
toolCallId: string;
|
|
32
|
+
output: ToolResultOutput;
|
|
33
|
+
}): HarnessAgentToolResultContinuation {
|
|
34
|
+
switch (input.output.type) {
|
|
35
|
+
case 'text':
|
|
36
|
+
case 'json':
|
|
37
|
+
return {
|
|
38
|
+
toolCallId: input.toolCallId,
|
|
39
|
+
output: input.output.value,
|
|
40
|
+
};
|
|
41
|
+
case 'error-text':
|
|
42
|
+
case 'error-json':
|
|
43
|
+
return {
|
|
44
|
+
toolCallId: input.toolCallId,
|
|
45
|
+
output: input.output.value,
|
|
46
|
+
isError: true,
|
|
47
|
+
};
|
|
48
|
+
case 'execution-denied':
|
|
49
|
+
return {
|
|
50
|
+
toolCallId: input.toolCallId,
|
|
51
|
+
output: {
|
|
52
|
+
type: input.output.type,
|
|
53
|
+
reason: input.output.reason,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
case 'content':
|
|
57
|
+
return {
|
|
58
|
+
toolCallId: input.toolCallId,
|
|
59
|
+
output: input.output,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
HarnessV1ContinueTurnState,
|
|
10
10
|
HarnessV1LifecycleState,
|
|
11
11
|
HarnessV1PendingToolApproval,
|
|
12
|
+
HarnessV1PendingToolResult,
|
|
12
13
|
HarnessV1PermissionMode,
|
|
13
14
|
HarnessV1Prompt,
|
|
14
15
|
HarnessV1PromptControl,
|
|
@@ -45,6 +46,7 @@ export type HarnessAgentLifecycleState = HarnessV1LifecycleState;
|
|
|
45
46
|
export type HarnessAgentResumeSessionState = HarnessV1ResumeSessionState;
|
|
46
47
|
export type HarnessAgentContinueTurnState = HarnessV1ContinueTurnState;
|
|
47
48
|
export type HarnessAgentPendingToolApproval = HarnessV1PendingToolApproval;
|
|
49
|
+
export type HarnessAgentPendingToolResult = HarnessV1PendingToolResult;
|
|
48
50
|
|
|
49
51
|
export type HarnessAgentSkill = HarnessV1Skill;
|
|
50
52
|
export type HarnessAgentPermissionMode = HarnessV1PermissionMode;
|
|
@@ -39,6 +39,10 @@ import {
|
|
|
39
39
|
collectHarnessAgentToolApprovalContinuations,
|
|
40
40
|
type HarnessAgentToolApprovalContinuation,
|
|
41
41
|
} from './harness-agent-tool-approval-continuation';
|
|
42
|
+
import {
|
|
43
|
+
collectHarnessAgentToolResultContinuations,
|
|
44
|
+
type HarnessAgentToolResultContinuation,
|
|
45
|
+
} from './harness-agent-tool-result-continuation';
|
|
42
46
|
import { applyBootstrapRecipe } from './internal/bootstrap-recipe';
|
|
43
47
|
import {
|
|
44
48
|
acquireBridgePort,
|
|
@@ -348,13 +352,17 @@ export class HarnessAgent<
|
|
|
348
352
|
sessionWorkDir,
|
|
349
353
|
toolApproval: this.settings.toolApproval,
|
|
350
354
|
pendingToolApprovals: effectiveContinueFrom?.pendingToolApprovals,
|
|
355
|
+
pendingToolResults: effectiveContinueFrom?.pendingToolResults,
|
|
351
356
|
turnState:
|
|
352
357
|
effectiveContinueFrom == null
|
|
353
358
|
? 'idle'
|
|
354
359
|
: effectiveContinueFrom.pendingToolApprovals != null &&
|
|
355
360
|
effectiveContinueFrom.pendingToolApprovals.length > 0
|
|
356
361
|
? 'awaiting-approval'
|
|
357
|
-
:
|
|
362
|
+
: effectiveContinueFrom.pendingToolResults != null &&
|
|
363
|
+
effectiveContinueFrom.pendingToolResults.length > 0
|
|
364
|
+
? 'awaiting-tool-result'
|
|
365
|
+
: 'suspended',
|
|
358
366
|
});
|
|
359
367
|
} catch (error) {
|
|
360
368
|
await cleanupAfterStartFailure({
|
|
@@ -426,6 +434,7 @@ export class HarnessAgent<
|
|
|
426
434
|
async continueGenerate(options: {
|
|
427
435
|
session: HarnessAgentSession;
|
|
428
436
|
toolApprovalContinuations?: readonly HarnessAgentToolApprovalContinuation[];
|
|
437
|
+
toolResultContinuations?: readonly HarnessAgentToolResultContinuation[];
|
|
429
438
|
abortSignal?: AbortSignal;
|
|
430
439
|
}): Promise<
|
|
431
440
|
GenerateTextResult<
|
|
@@ -441,6 +450,7 @@ export class HarnessAgent<
|
|
|
441
450
|
turnInput: {
|
|
442
451
|
mode: 'continue',
|
|
443
452
|
toolApprovalContinuations: options.toolApprovalContinuations ?? [],
|
|
453
|
+
toolResultContinuations: options.toolResultContinuations ?? [],
|
|
444
454
|
},
|
|
445
455
|
runtimeContext,
|
|
446
456
|
abortSignal: options.abortSignal,
|
|
@@ -460,6 +470,7 @@ export class HarnessAgent<
|
|
|
460
470
|
async continueStream(options: {
|
|
461
471
|
session: HarnessAgentSession;
|
|
462
472
|
toolApprovalContinuations?: readonly HarnessAgentToolApprovalContinuation[];
|
|
473
|
+
toolResultContinuations?: readonly HarnessAgentToolResultContinuation[];
|
|
463
474
|
abortSignal?: AbortSignal;
|
|
464
475
|
}): Promise<
|
|
465
476
|
StreamTextResult<
|
|
@@ -475,6 +486,7 @@ export class HarnessAgent<
|
|
|
475
486
|
turnInput: {
|
|
476
487
|
mode: 'continue',
|
|
477
488
|
toolApprovalContinuations: options.toolApprovalContinuations ?? [],
|
|
489
|
+
toolResultContinuations: options.toolResultContinuations ?? [],
|
|
478
490
|
},
|
|
479
491
|
runtimeContext,
|
|
480
492
|
abortSignal: options.abortSignal,
|
|
@@ -491,6 +503,7 @@ export class HarnessAgent<
|
|
|
491
503
|
| {
|
|
492
504
|
mode: 'continue';
|
|
493
505
|
toolApprovalContinuations: readonly HarnessAgentToolApprovalContinuation[];
|
|
506
|
+
toolResultContinuations: readonly HarnessAgentToolResultContinuation[];
|
|
494
507
|
};
|
|
495
508
|
runtimeContext: RUNTIME_CONTEXT;
|
|
496
509
|
abortSignal: AbortSignal | undefined;
|
|
@@ -516,6 +529,7 @@ export class HarnessAgent<
|
|
|
516
529
|
abortSignal: input.abortSignal,
|
|
517
530
|
telemetry: this.settings.telemetry,
|
|
518
531
|
toolApprovalContinuations: input.turnInput.toolApprovalContinuations,
|
|
532
|
+
toolResultContinuations: input.turnInput.toolResultContinuations,
|
|
519
533
|
});
|
|
520
534
|
}
|
|
521
535
|
|
|
@@ -579,6 +593,7 @@ export class HarnessAgent<
|
|
|
579
593
|
| {
|
|
580
594
|
mode: 'continue';
|
|
581
595
|
toolApprovalContinuations: readonly HarnessAgentToolApprovalContinuation[];
|
|
596
|
+
toolResultContinuations: readonly HarnessAgentToolResultContinuation[];
|
|
582
597
|
} {
|
|
583
598
|
if (typeof options.prompt === 'string') {
|
|
584
599
|
return { mode: 'prompt', prompt: options.prompt };
|
|
@@ -589,10 +604,16 @@ export class HarnessAgent<
|
|
|
589
604
|
if (Array.isArray(messages)) {
|
|
590
605
|
const toolApprovalContinuations =
|
|
591
606
|
collectHarnessAgentToolApprovalContinuations({ messages });
|
|
592
|
-
|
|
607
|
+
const toolResultContinuations =
|
|
608
|
+
collectHarnessAgentToolResultContinuations({ messages });
|
|
609
|
+
if (
|
|
610
|
+
toolApprovalContinuations.length > 0 ||
|
|
611
|
+
toolResultContinuations.length > 0
|
|
612
|
+
) {
|
|
593
613
|
return {
|
|
594
614
|
mode: 'continue',
|
|
595
615
|
toolApprovalContinuations,
|
|
616
|
+
toolResultContinuations,
|
|
596
617
|
};
|
|
597
618
|
}
|
|
598
619
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -141,6 +141,7 @@ export class HarnessStreamTextResult<
|
|
|
141
141
|
private currentStepContent: ContentPart<TOOLS>[] = [];
|
|
142
142
|
private currentStepWarnings: CallWarning[] = [];
|
|
143
143
|
private stepNumber = 0;
|
|
144
|
+
private stepStarted = false;
|
|
144
145
|
|
|
145
146
|
private readonly tools: TOOLS;
|
|
146
147
|
private readonly runtimeContext: RUNTIME_CONTEXT;
|
|
@@ -173,6 +174,13 @@ export class HarnessStreamTextResult<
|
|
|
173
174
|
const baseStream = new ReadableStream<TextStreamPart<TOOLS>>({
|
|
174
175
|
start(c) {
|
|
175
176
|
controllerRef = c;
|
|
177
|
+
// Send the message-level start event as the first part, mirroring
|
|
178
|
+
// `streamText`. Downstream UI message stream consumers depend on it:
|
|
179
|
+
// `toUIMessageStream`'s persistence mode injects the response message
|
|
180
|
+
// id into this part (it never synthesizes one), so without it
|
|
181
|
+
// `useChat` clients keep a locally generated assistant message id
|
|
182
|
+
// that diverges from the id the server persists under.
|
|
183
|
+
c.enqueue({ type: 'start' });
|
|
176
184
|
},
|
|
177
185
|
});
|
|
178
186
|
this.fullStreamController = controllerRef;
|
|
@@ -198,10 +206,30 @@ export class HarnessStreamTextResult<
|
|
|
198
206
|
* into the current step's content array where applicable.
|
|
199
207
|
*/
|
|
200
208
|
enqueue(part: TextStreamPart<TOOLS>): void {
|
|
209
|
+
this.startStep();
|
|
201
210
|
this.fullStreamController.enqueue(part);
|
|
202
211
|
this.appendToCurrentStepContent(part);
|
|
203
212
|
}
|
|
204
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Push a continuation input into the consumer stream without attributing it
|
|
216
|
+
* to the next model step. Approval responses and client tool results arrive
|
|
217
|
+
* between model calls and therefore must not create or alter a StepResult.
|
|
218
|
+
*/
|
|
219
|
+
enqueueContinuation(part: TextStreamPart<TOOLS>): void {
|
|
220
|
+
this.fullStreamController.enqueue(part);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Drop content replayed while a suspended host-input pause closes its
|
|
225
|
+
* already-recorded model step.
|
|
226
|
+
*/
|
|
227
|
+
discardCurrentStepContent(): void {
|
|
228
|
+
this.currentStepContent = [];
|
|
229
|
+
this.currentStepWarnings = [];
|
|
230
|
+
this.stepStarted = false;
|
|
231
|
+
}
|
|
232
|
+
|
|
205
233
|
/**
|
|
206
234
|
* Mark the end of a step. Builds a `StepResult` from the accumulated
|
|
207
235
|
* content and records it in the steps array. Accepts the V4-shaped
|
|
@@ -213,7 +241,9 @@ export class HarnessStreamTextResult<
|
|
|
213
241
|
usage: LanguageModelV4Usage;
|
|
214
242
|
providerMetadata: ProviderMetadata | undefined;
|
|
215
243
|
warnings: CallWarning[];
|
|
216
|
-
}):
|
|
244
|
+
}): StepResult<TOOLS, RUNTIME_CONTEXT> {
|
|
245
|
+
this.startStep();
|
|
246
|
+
|
|
217
247
|
const normalizedUsage = asLanguageModelUsage(input.usage);
|
|
218
248
|
const finishReason = input.finishReason.unified;
|
|
219
249
|
const rawFinishReason = input.finishReason.raw;
|
|
@@ -267,6 +297,20 @@ export class HarnessStreamTextResult<
|
|
|
267
297
|
this.stepNumber += 1;
|
|
268
298
|
this.currentStepContent = [];
|
|
269
299
|
this.currentStepWarnings = [];
|
|
300
|
+
this.stepStarted = false;
|
|
301
|
+
|
|
302
|
+
return step;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private startStep(): void {
|
|
306
|
+
if (this.stepStarted) return;
|
|
307
|
+
|
|
308
|
+
this.stepStarted = true;
|
|
309
|
+
this.fullStreamController.enqueue({
|
|
310
|
+
type: 'start-step',
|
|
311
|
+
request: {},
|
|
312
|
+
warnings: this.currentStepWarnings,
|
|
313
|
+
} as TextStreamPart<TOOLS>);
|
|
270
314
|
}
|
|
271
315
|
|
|
272
316
|
/**
|
|
@@ -278,7 +322,20 @@ export class HarnessStreamTextResult<
|
|
|
278
322
|
providerMetadata: ProviderMetadata | undefined;
|
|
279
323
|
}): Promise<void> {
|
|
280
324
|
if (this.settled) return;
|
|
281
|
-
|
|
325
|
+
|
|
326
|
+
/*
|
|
327
|
+
* Do not flush trailing content that has not been captured by a
|
|
328
|
+
* `finish-step`. A terminal `finish` closes the turn but is not a semantic
|
|
329
|
+
* step boundary, so buffered content indicates an invalid harness stream.
|
|
330
|
+
*/
|
|
331
|
+
if (this.currentStepContent.length > 0) {
|
|
332
|
+
this.fail(
|
|
333
|
+
new Error(
|
|
334
|
+
'HarnessAgent: received terminal finish with unclosed step content. Harness adapters must emit `finish-step` before `finish`.',
|
|
335
|
+
),
|
|
336
|
+
);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
282
339
|
|
|
283
340
|
if (input != null) {
|
|
284
341
|
this.finalFinishReason = input.finishReason.unified;
|
|
@@ -287,39 +344,7 @@ export class HarnessStreamTextResult<
|
|
|
287
344
|
this.accumulatedUsage = asLanguageModelUsage(input.totalUsage);
|
|
288
345
|
}
|
|
289
346
|
|
|
290
|
-
|
|
291
|
-
// construct the step directly here (the public `finishStep` takes V4
|
|
292
|
-
// shapes; we already have AI SDK shapes at this point).
|
|
293
|
-
if (this.currentStepContent.length > 0) {
|
|
294
|
-
const trailingStep = new DefaultStepResult<TOOLS, RUNTIME_CONTEXT>({
|
|
295
|
-
callId: generateId(),
|
|
296
|
-
stepNumber: this.stepNumber,
|
|
297
|
-
provider: this.providerName,
|
|
298
|
-
modelId: this.modelId,
|
|
299
|
-
runtimeContext: this.runtimeContext,
|
|
300
|
-
toolsContext: this.toolsContext,
|
|
301
|
-
content: this.currentStepContent,
|
|
302
|
-
finishReason: this.finalFinishReason,
|
|
303
|
-
rawFinishReason: this.finalRawFinishReason,
|
|
304
|
-
usage: createNullLanguageModelUsage(),
|
|
305
|
-
performance: createEmptyPerformance(),
|
|
306
|
-
warnings:
|
|
307
|
-
this.currentStepWarnings.length > 0
|
|
308
|
-
? this.currentStepWarnings
|
|
309
|
-
: undefined,
|
|
310
|
-
request: {},
|
|
311
|
-
response: {
|
|
312
|
-
id: generateId(),
|
|
313
|
-
timestamp: new Date(),
|
|
314
|
-
modelId: this.modelId,
|
|
315
|
-
messages: [],
|
|
316
|
-
},
|
|
317
|
-
providerMetadata: this.finalProviderMetadata,
|
|
318
|
-
});
|
|
319
|
-
this.stepsBuffer.push(trailingStep);
|
|
320
|
-
this.currentStepContent = [];
|
|
321
|
-
this.currentStepWarnings = [];
|
|
322
|
-
}
|
|
347
|
+
this.settled = true;
|
|
323
348
|
|
|
324
349
|
const finalStep =
|
|
325
350
|
this.stepsBuffer.length > 0
|
|
@@ -46,6 +46,16 @@ export async function validateLifecycleStateData<
|
|
|
46
46
|
'Resume session state cannot contain pending tool approvals; unfinished turns must be stored as `continueFrom`.',
|
|
47
47
|
});
|
|
48
48
|
}
|
|
49
|
+
if (
|
|
50
|
+
state.type === 'resume-session' &&
|
|
51
|
+
'pendingToolResults' in state &&
|
|
52
|
+
state.pendingToolResults !== undefined
|
|
53
|
+
) {
|
|
54
|
+
throw new HarnessError({
|
|
55
|
+
message:
|
|
56
|
+
'Resume session state cannot contain pending tool results; unfinished turns must be stored as `continueFrom`.',
|
|
57
|
+
});
|
|
58
|
+
}
|
|
49
59
|
|
|
50
60
|
const data =
|
|
51
61
|
harness.lifecycleStateSchema == null
|
|
@@ -91,5 +101,8 @@ export async function validateLifecycleStateData<
|
|
|
91
101
|
...(state.pendingToolApprovals !== undefined
|
|
92
102
|
? { pendingToolApprovals: state.pendingToolApprovals }
|
|
93
103
|
: {}),
|
|
104
|
+
...(state.pendingToolResults !== undefined
|
|
105
|
+
? { pendingToolResults: state.pendingToolResults }
|
|
106
|
+
: {}),
|
|
94
107
|
} as STATE;
|
|
95
108
|
}
|