@ai-sdk/harness 1.0.149 → 1.0.151
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 +20 -0
- package/dist/agent/index.d.ts +6 -5
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +263 -70
- package/dist/agent/index.js.map +1 -1
- package/dist/bridge/index.d.ts +18 -1
- package/dist/bridge/index.d.ts.map +1 -1
- package/dist/bridge/index.js +51 -1
- package/dist/bridge/index.js.map +1 -1
- package/dist/index.d.ts +3 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/utils/index.d.ts +24 -3
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +164 -4
- package/dist/utils/index.js.map +1 -1
- package/package.json +3 -3
- package/src/agent/harness-agent-tool-approval-continuation.ts +7 -1
- package/src/agent/internal/harness-stream-text-result.ts +143 -27
- package/src/agent/internal/run-prompt.ts +155 -58
- package/src/agent/internal/translate-stream-part.ts +20 -3
- package/src/bridge/index.ts +4 -0
- package/src/utils/convert-harness-tool-model-output.ts +66 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/normalize-harness-tool-model-output.ts +66 -0
- package/src/v1/harness-v1-prompt-control.ts +3 -0
- package/src/v1/harness-v1-stream-part.ts +10 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/harness",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.151",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@ai-sdk/provider": "4.0.27",
|
|
48
48
|
"@ai-sdk/provider-utils": "5.0.59",
|
|
49
|
-
"ai": "7.0.
|
|
49
|
+
"ai": "7.0.140"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"ws": "^8.21.0",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
}
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"@ai-sdk/otel": "1.0.
|
|
61
|
+
"@ai-sdk/otel": "1.0.140",
|
|
62
62
|
"@opentelemetry/sdk-trace-base": "2.7.1",
|
|
63
63
|
"@types/node": "22.19.19",
|
|
64
64
|
"@types/ws": "^8.5.13",
|
|
@@ -14,6 +14,9 @@ import type {
|
|
|
14
14
|
* locally to find the original tool call before it can resume the paused turn.
|
|
15
15
|
* Responses that already have a tool result are ignored, because those
|
|
16
16
|
* approvals were already consumed by a prior continuation.
|
|
17
|
+
* An `execution-denied` result is synthesized by `convertToModelMessages`
|
|
18
|
+
* for a newly denied approval; it does not mean the harness has received
|
|
19
|
+
* the decision, so that approval response must still continue the turn.
|
|
17
20
|
*/
|
|
18
21
|
export function collectHarnessAgentToolApprovalContinuations(input: {
|
|
19
22
|
messages: readonly ModelMessage[];
|
|
@@ -38,7 +41,10 @@ export function collectHarnessAgentToolApprovalContinuations(input: {
|
|
|
38
41
|
|
|
39
42
|
const toolResultIds = new Set<string>();
|
|
40
43
|
for (const part of lastMessage.content) {
|
|
41
|
-
if (
|
|
44
|
+
if (
|
|
45
|
+
part.type === 'tool-result' &&
|
|
46
|
+
part.output.type !== 'execution-denied'
|
|
47
|
+
) {
|
|
42
48
|
toolResultIds.add(part.toolCallId);
|
|
43
49
|
}
|
|
44
50
|
}
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
type AssistantModelMessage,
|
|
5
5
|
type Context,
|
|
6
6
|
type ToolModelMessage,
|
|
7
|
+
type ToolResultOutput,
|
|
7
8
|
type ToolSet,
|
|
8
9
|
} from '@ai-sdk/provider-utils';
|
|
9
10
|
import {
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
asLanguageModelUsage,
|
|
12
13
|
createAsyncIterableStream,
|
|
13
14
|
createNullLanguageModelUsage,
|
|
15
|
+
createToolModelOutput,
|
|
14
16
|
DefaultStepResult,
|
|
15
17
|
toResponseMessages,
|
|
16
18
|
} from 'ai/internal';
|
|
@@ -22,6 +24,7 @@ import {
|
|
|
22
24
|
createUIMessageStreamResponse,
|
|
23
25
|
toUIMessageStream as toUIMessageStreamHelper,
|
|
24
26
|
type CallWarning,
|
|
27
|
+
type Citation,
|
|
25
28
|
type ContentPart,
|
|
26
29
|
type FinishReason,
|
|
27
30
|
type InferUIMessageChunk,
|
|
@@ -53,6 +56,19 @@ type EnrichedStreamPart<TOOLS extends ToolSet, PARTIAL_OUTPUT> = {
|
|
|
53
56
|
partialOutput: PARTIAL_OUTPUT | undefined;
|
|
54
57
|
};
|
|
55
58
|
|
|
59
|
+
type AccumulatedTextContent = {
|
|
60
|
+
type: 'text';
|
|
61
|
+
text: string;
|
|
62
|
+
citations?: Citation[];
|
|
63
|
+
providerMetadata?: ProviderMetadata;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
type AccumulatedReasoningContent = {
|
|
67
|
+
type: 'reasoning';
|
|
68
|
+
text: string;
|
|
69
|
+
providerMetadata?: ProviderMetadata;
|
|
70
|
+
};
|
|
71
|
+
|
|
56
72
|
/**
|
|
57
73
|
* Concrete `StreamTextResult` implementation backed by a single
|
|
58
74
|
* harness prompt turn.
|
|
@@ -149,11 +165,17 @@ export class HarnessStreamTextResult<
|
|
|
149
165
|
|
|
150
166
|
private readonly stepsBuffer: StepResult<TOOLS, RUNTIME_CONTEXT>[] = [];
|
|
151
167
|
private currentStepContent: ContentPart<TOOLS>[] = [];
|
|
168
|
+
private currentStepTextContent = new Map<string, AccumulatedTextContent>();
|
|
169
|
+
private currentStepReasoningContent = new Map<
|
|
170
|
+
string,
|
|
171
|
+
AccumulatedReasoningContent
|
|
172
|
+
>();
|
|
152
173
|
private currentStepWarnings: CallWarning[] = [];
|
|
153
174
|
private stepNumber = 0;
|
|
154
175
|
private stepStarted = false;
|
|
155
176
|
|
|
156
177
|
private readonly tools: TOOLS;
|
|
178
|
+
private readonly toolModelOutputs = new Map<string, ToolResultOutput>();
|
|
157
179
|
private readonly runtimeContext: RUNTIME_CONTEXT;
|
|
158
180
|
private readonly toolsContext: InferToolSetContext<TOOLS>;
|
|
159
181
|
private readonly providerName: string;
|
|
@@ -169,6 +191,13 @@ export class HarnessStreamTextResult<
|
|
|
169
191
|
private aggregateWarnings: CallWarning[] = [];
|
|
170
192
|
private settled = false;
|
|
171
193
|
|
|
194
|
+
setToolModelOutput(input: {
|
|
195
|
+
toolCallId: string;
|
|
196
|
+
output: ToolResultOutput;
|
|
197
|
+
}): void {
|
|
198
|
+
this.toolModelOutputs.set(input.toolCallId, input.output);
|
|
199
|
+
}
|
|
200
|
+
|
|
172
201
|
constructor(options: {
|
|
173
202
|
tools: TOOLS;
|
|
174
203
|
runtimeContext: RUNTIME_CONTEXT;
|
|
@@ -261,6 +290,8 @@ export class HarnessStreamTextResult<
|
|
|
261
290
|
*/
|
|
262
291
|
discardCurrentStepContent(): void {
|
|
263
292
|
this.currentStepContent = [];
|
|
293
|
+
this.currentStepTextContent.clear();
|
|
294
|
+
this.currentStepReasoningContent.clear();
|
|
264
295
|
this.currentStepWarnings = [];
|
|
265
296
|
this.stepStarted = false;
|
|
266
297
|
}
|
|
@@ -334,6 +365,8 @@ export class HarnessStreamTextResult<
|
|
|
334
365
|
|
|
335
366
|
this.stepNumber += 1;
|
|
336
367
|
this.currentStepContent = [];
|
|
368
|
+
this.currentStepTextContent.clear();
|
|
369
|
+
this.currentStepReasoningContent.clear();
|
|
337
370
|
this.currentStepWarnings = [];
|
|
338
371
|
this.stepStarted = false;
|
|
339
372
|
|
|
@@ -421,14 +454,12 @@ export class HarnessStreamTextResult<
|
|
|
421
454
|
>,
|
|
422
455
|
);
|
|
423
456
|
this._text.resolve(finalStep.text);
|
|
424
|
-
// Reasoning content parts are not yet derived from harness events; the
|
|
425
|
-
// foundation surfaces an empty array. Adapters that emit reasoning
|
|
426
|
-
// deltas can be wired up to produce real reasoning content in a later
|
|
427
|
-
// pass.
|
|
428
457
|
this._reasoning.resolve(
|
|
429
|
-
|
|
458
|
+
finalStep.content.filter(
|
|
459
|
+
part => part.type === 'reasoning' || part.type === 'reasoning-file',
|
|
460
|
+
) as StreamProp<TOOLS, RUNTIME_CONTEXT, OUTPUT, 'reasoning'>,
|
|
430
461
|
);
|
|
431
|
-
this._reasoningText.resolve(
|
|
462
|
+
this._reasoningText.resolve(finalStep.reasoningText);
|
|
432
463
|
this._files.resolve(this.stepsBuffer.flatMap(s => s.files));
|
|
433
464
|
this._sources.resolve(this.stepsBuffer.flatMap(s => s.sources));
|
|
434
465
|
this._toolCalls.resolve(
|
|
@@ -491,10 +522,29 @@ export class HarnessStreamTextResult<
|
|
|
491
522
|
this._response.resolve(finalStep.response);
|
|
492
523
|
this._providerMetadata.resolve(this.finalProviderMetadata);
|
|
493
524
|
|
|
494
|
-
const
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
525
|
+
const responseTools: ToolSet = {};
|
|
526
|
+
for (const [name, tool] of Object.entries(this.tools)) {
|
|
527
|
+
responseTools[name] = {
|
|
528
|
+
...tool,
|
|
529
|
+
toModelOutput: (options: {
|
|
530
|
+
toolCallId: string;
|
|
531
|
+
input: unknown;
|
|
532
|
+
output: unknown;
|
|
533
|
+
}) =>
|
|
534
|
+
this.toolModelOutputs.get(options.toolCallId) ??
|
|
535
|
+
createToolModelOutput({ ...options, tool, errorMode: 'none' }),
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
const responseMessages: Array<AssistantModelMessage | ToolModelMessage> =
|
|
539
|
+
[];
|
|
540
|
+
for (const step of this.stepsBuffer) {
|
|
541
|
+
responseMessages.push(
|
|
542
|
+
...(await toResponseMessages<TOOLS>({
|
|
543
|
+
content: step.content,
|
|
544
|
+
tools: responseTools as TOOLS,
|
|
545
|
+
})),
|
|
546
|
+
);
|
|
547
|
+
}
|
|
498
548
|
this._responseMessages.resolve(responseMessages);
|
|
499
549
|
|
|
500
550
|
// Forward AI SDK finish event before closing.
|
|
@@ -755,6 +805,8 @@ export class HarnessStreamTextResult<
|
|
|
755
805
|
toUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
756
806
|
originalMessages,
|
|
757
807
|
generateMessageId,
|
|
808
|
+
onStepEnd,
|
|
809
|
+
onStepFinish,
|
|
758
810
|
onEnd,
|
|
759
811
|
onFinish,
|
|
760
812
|
messageMetadata,
|
|
@@ -772,6 +824,8 @@ export class HarnessStreamTextResult<
|
|
|
772
824
|
tools: this.tools,
|
|
773
825
|
originalMessages,
|
|
774
826
|
generateMessageId,
|
|
827
|
+
onStepEnd,
|
|
828
|
+
onStepFinish,
|
|
775
829
|
onEnd,
|
|
776
830
|
onFinish,
|
|
777
831
|
messageMetadata,
|
|
@@ -795,6 +849,8 @@ export class HarnessStreamTextResult<
|
|
|
795
849
|
toUIMessageStreamResponse<UI_MESSAGE extends UIMessage>({
|
|
796
850
|
originalMessages,
|
|
797
851
|
generateMessageId,
|
|
852
|
+
onStepEnd,
|
|
853
|
+
onStepFinish,
|
|
798
854
|
onEnd,
|
|
799
855
|
onFinish,
|
|
800
856
|
messageMetadata,
|
|
@@ -813,6 +869,8 @@ export class HarnessStreamTextResult<
|
|
|
813
869
|
stream: this.toUIMessageStream<UI_MESSAGE>({
|
|
814
870
|
originalMessages,
|
|
815
871
|
generateMessageId,
|
|
872
|
+
onStepEnd,
|
|
873
|
+
onStepFinish,
|
|
816
874
|
onEnd,
|
|
817
875
|
onFinish,
|
|
818
876
|
messageMetadata,
|
|
@@ -834,30 +892,88 @@ export class HarnessStreamTextResult<
|
|
|
834
892
|
|
|
835
893
|
private appendToCurrentStepContent(part: TextStreamPart<TOOLS>): void {
|
|
836
894
|
switch (part.type) {
|
|
895
|
+
case 'text-start': {
|
|
896
|
+
const content: AccumulatedTextContent = {
|
|
897
|
+
type: 'text',
|
|
898
|
+
text: '',
|
|
899
|
+
...(part.providerMetadata != null
|
|
900
|
+
? { providerMetadata: part.providerMetadata }
|
|
901
|
+
: {}),
|
|
902
|
+
};
|
|
903
|
+
this.currentStepTextContent.set(part.id, content);
|
|
904
|
+
this.currentStepContent.push(content);
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
837
907
|
case 'text-delta': {
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
if (last && last.type === 'text') {
|
|
842
|
-
(last as { text: string }).text += part.text;
|
|
843
|
-
} else {
|
|
844
|
-
this.currentStepContent.push({
|
|
908
|
+
let content = this.currentStepTextContent.get(part.id);
|
|
909
|
+
if (content == null) {
|
|
910
|
+
content = {
|
|
845
911
|
type: 'text',
|
|
846
|
-
text:
|
|
847
|
-
|
|
912
|
+
text: '',
|
|
913
|
+
...(part.providerMetadata != null
|
|
914
|
+
? { providerMetadata: part.providerMetadata }
|
|
915
|
+
: {}),
|
|
916
|
+
};
|
|
917
|
+
this.currentStepTextContent.set(part.id, content);
|
|
918
|
+
this.currentStepContent.push(content);
|
|
919
|
+
}
|
|
920
|
+
content.text += part.text;
|
|
921
|
+
if (part.providerMetadata != null) {
|
|
922
|
+
content.providerMetadata = part.providerMetadata;
|
|
923
|
+
}
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
case 'text-end': {
|
|
927
|
+
const content = this.currentStepTextContent.get(part.id);
|
|
928
|
+
if (content != null) {
|
|
929
|
+
if (part.providerMetadata != null) {
|
|
930
|
+
content.providerMetadata = part.providerMetadata;
|
|
931
|
+
}
|
|
932
|
+
if (part.citations != null) {
|
|
933
|
+
content.citations = part.citations;
|
|
934
|
+
}
|
|
935
|
+
this.currentStepTextContent.delete(part.id);
|
|
848
936
|
}
|
|
849
937
|
return;
|
|
850
938
|
}
|
|
939
|
+
case 'reasoning-start': {
|
|
940
|
+
const content: AccumulatedReasoningContent = {
|
|
941
|
+
type: 'reasoning',
|
|
942
|
+
text: '',
|
|
943
|
+
...(part.providerMetadata != null
|
|
944
|
+
? { providerMetadata: part.providerMetadata }
|
|
945
|
+
: {}),
|
|
946
|
+
};
|
|
947
|
+
this.currentStepReasoningContent.set(part.id, content);
|
|
948
|
+
this.currentStepContent.push(content);
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
851
951
|
case 'reasoning-delta': {
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
(last as { text: string }).text += part.text;
|
|
856
|
-
} else {
|
|
857
|
-
this.currentStepContent.push({
|
|
952
|
+
let content = this.currentStepReasoningContent.get(part.id);
|
|
953
|
+
if (content == null) {
|
|
954
|
+
content = {
|
|
858
955
|
type: 'reasoning',
|
|
859
|
-
text:
|
|
860
|
-
|
|
956
|
+
text: '',
|
|
957
|
+
...(part.providerMetadata != null
|
|
958
|
+
? { providerMetadata: part.providerMetadata }
|
|
959
|
+
: {}),
|
|
960
|
+
};
|
|
961
|
+
this.currentStepReasoningContent.set(part.id, content);
|
|
962
|
+
this.currentStepContent.push(content);
|
|
963
|
+
}
|
|
964
|
+
content.text += part.text;
|
|
965
|
+
if (part.providerMetadata != null) {
|
|
966
|
+
content.providerMetadata = part.providerMetadata;
|
|
967
|
+
}
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
case 'reasoning-end': {
|
|
971
|
+
const content = this.currentStepReasoningContent.get(part.id);
|
|
972
|
+
if (content != null) {
|
|
973
|
+
if (part.providerMetadata != null) {
|
|
974
|
+
content.providerMetadata = part.providerMetadata;
|
|
975
|
+
}
|
|
976
|
+
this.currentStepReasoningContent.delete(part.id);
|
|
861
977
|
}
|
|
862
978
|
return;
|
|
863
979
|
}
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
type Experimental_SandboxSession as SandboxSession,
|
|
25
25
|
type ToolApprovalResponse,
|
|
26
26
|
type ToolResultPart,
|
|
27
|
+
type ToolResultOutput,
|
|
27
28
|
type ToolSet,
|
|
28
29
|
} from '@ai-sdk/provider-utils';
|
|
29
30
|
import {
|
|
@@ -36,6 +37,7 @@ import {
|
|
|
36
37
|
addLanguageModelUsage,
|
|
37
38
|
asLanguageModelUsage,
|
|
38
39
|
createNullLanguageModelUsage,
|
|
40
|
+
createToolModelOutput,
|
|
39
41
|
parseToolCall,
|
|
40
42
|
validateToolContext,
|
|
41
43
|
} from 'ai/internal';
|
|
@@ -68,6 +70,8 @@ import {
|
|
|
68
70
|
import { resolveCustomToolApproval } from './permission-mode';
|
|
69
71
|
import { logBridgeError } from '../../utils/bridge-diagnostics';
|
|
70
72
|
import { pinSandboxChannelEventCheckpoint } from '../../utils/sandbox-channel';
|
|
73
|
+
import { normalizeHarnessToolModelOutput } from '../../utils/normalize-harness-tool-model-output';
|
|
74
|
+
import { HarnessCapabilityUnsupportedError } from '../../errors/harness-capability-unsupported-error';
|
|
71
75
|
|
|
72
76
|
const invalidToolInputMessage = 'Tool input validation failed.';
|
|
73
77
|
|
|
@@ -303,12 +307,15 @@ export function runPrompt<
|
|
|
303
307
|
>();
|
|
304
308
|
/*
|
|
305
309
|
* Host results are echoed back as `tool-result` events too, so the event
|
|
306
|
-
* alone cannot say who ran the tool —
|
|
307
|
-
* `tool-call`. Only an explicit `true` counts:
|
|
308
|
-
* defines an omitted flag as client-executed. A
|
|
309
|
-
* arrived in an earlier slice, where nothing
|
|
310
|
+
* alone cannot provide the parsed input or say who ran the tool — restore
|
|
311
|
+
* both from the originating `tool-call`. Only an explicit `true` counts:
|
|
312
|
+
* `LanguageModelV4ToolCall` defines an omitted flag as client-executed. A
|
|
313
|
+
* call missing from the map arrived in an earlier slice, where nothing
|
|
314
|
+
* here can classify it.
|
|
310
315
|
*/
|
|
311
316
|
const translateOptions = {
|
|
317
|
+
getToolCall: (toolCallId: string) =>
|
|
318
|
+
toolCallsByToolCallId.get(toolCallId),
|
|
312
319
|
isProviderExecuted: (toolCallId: string): boolean => {
|
|
313
320
|
const rawToolCall = rawToolCallsByToolCallId.get(toolCallId);
|
|
314
321
|
return rawToolCall == null || rawToolCall.providerExecuted === true;
|
|
@@ -421,9 +428,24 @@ export function runPrompt<
|
|
|
421
428
|
toolExecutions.clear();
|
|
422
429
|
bufferedToolOutcomes = [];
|
|
423
430
|
};
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
431
|
+
type FinishStepPart = Extract<HarnessV1StreamPart, { type: 'finish-step' }>;
|
|
432
|
+
/*
|
|
433
|
+
* An approval request or a pending client tool result does not end the
|
|
434
|
+
* response right away: the rest of the step (other tool calls, native
|
|
435
|
+
* results, host tool executions) still has to be surfaced. The pause
|
|
436
|
+
* happens at the next `finish-step`, once every host tool started in
|
|
437
|
+
* this response has been echoed back by the runtime.
|
|
438
|
+
*/
|
|
439
|
+
let hostInputPauseRequested = false;
|
|
440
|
+
let deferredPauseFinishStep: FinishStepPart | undefined;
|
|
441
|
+
/*
|
|
442
|
+
* A `finish-step` that arrives while host tools started in this step have
|
|
443
|
+
* not been echoed back is the adapter signalling that the runtime waits on
|
|
444
|
+
* host input, not the end of the step. It is remembered so the step can
|
|
445
|
+
* still be closed if the turn finishes without another `finish-step`.
|
|
446
|
+
*/
|
|
447
|
+
let ignoredFinishStep: FinishStepPart | undefined;
|
|
448
|
+
const hostToolCallIdsAwaitingEcho = new Set<string>();
|
|
427
449
|
const buildModelCallContent = (): ContentPart<TOOLS>[] => {
|
|
428
450
|
const parts: ContentPart<TOOLS>[] = [];
|
|
429
451
|
if (stepText) parts.push({ type: 'text', text: stepText });
|
|
@@ -441,9 +463,7 @@ export function runPrompt<
|
|
|
441
463
|
stepApprovalRequests = [];
|
|
442
464
|
bufferedToolOutcomes = [];
|
|
443
465
|
toolExecutions.clear();
|
|
444
|
-
|
|
445
|
-
observedStepToolCallCount = 0;
|
|
446
|
-
pauseAfterStepToolCalls = false;
|
|
466
|
+
ignoredFinishStep = undefined;
|
|
447
467
|
};
|
|
448
468
|
const zeroUsage: LanguageModelV4Usage = {
|
|
449
469
|
inputTokens: {
|
|
@@ -484,25 +504,27 @@ export function runPrompt<
|
|
|
484
504
|
resetStepContent();
|
|
485
505
|
return step;
|
|
486
506
|
};
|
|
487
|
-
const finishForHostInputPause = async (
|
|
488
|
-
completeCurrentStep: boolean;
|
|
489
|
-
}): Promise<void> => {
|
|
507
|
+
const finishForHostInputPause = async (): Promise<void> => {
|
|
490
508
|
await waitForOutstandingHostToolExecutions();
|
|
491
|
-
|
|
492
|
-
await completeStep({
|
|
493
|
-
finishReason: toolCallsFinishReason,
|
|
494
|
-
usage: zeroUsage,
|
|
495
|
-
providerMetadata: undefined,
|
|
496
|
-
});
|
|
497
|
-
} else {
|
|
498
|
-
await publishToolExecutions();
|
|
499
|
-
}
|
|
509
|
+
await publishToolExecutions();
|
|
500
510
|
await lifecycle.end({
|
|
501
511
|
steps: completedSteps,
|
|
502
512
|
usage: asLanguageModelUsage(zeroUsage),
|
|
503
513
|
});
|
|
504
514
|
await result.finish();
|
|
505
515
|
};
|
|
516
|
+
const pauseForHostInput = async (
|
|
517
|
+
finishStep: FinishStepPart | undefined,
|
|
518
|
+
): Promise<void> => {
|
|
519
|
+
await waitForOutstandingHostToolExecutions();
|
|
520
|
+
closingResumedStep = false;
|
|
521
|
+
await completeStep({
|
|
522
|
+
finishReason: toolCallsFinishReason,
|
|
523
|
+
usage: finishStep?.usage ?? zeroUsage,
|
|
524
|
+
providerMetadata: finishStep?.harnessMetadata,
|
|
525
|
+
});
|
|
526
|
+
await finishForHostInputPause();
|
|
527
|
+
};
|
|
506
528
|
const enqueueApprovalRequest = (approval: {
|
|
507
529
|
approvalId: string;
|
|
508
530
|
toolCall: ToolCallTextStreamPart;
|
|
@@ -609,7 +631,37 @@ export function runPrompt<
|
|
|
609
631
|
};
|
|
610
632
|
const submitToolResult: HarnessV1PromptControl['submitToolResult'] =
|
|
611
633
|
async submission => {
|
|
634
|
+
if (submission.toolResult != null) {
|
|
635
|
+
let output: ToolResultOutput;
|
|
636
|
+
try {
|
|
637
|
+
output = normalizeHarnessToolModelOutput({
|
|
638
|
+
output: submission.toolResult.output,
|
|
639
|
+
});
|
|
640
|
+
} catch (error) {
|
|
641
|
+
if (
|
|
642
|
+
!continuationsByToolCallId.has(submission.toolCallId) ||
|
|
643
|
+
!HarnessCapabilityUnsupportedError.isInstance(error)
|
|
644
|
+
) {
|
|
645
|
+
throw error;
|
|
646
|
+
}
|
|
647
|
+
output = {
|
|
648
|
+
type: 'text',
|
|
649
|
+
value: JSON.stringify(submission.output) ?? 'null',
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
submission = {
|
|
653
|
+
...submission,
|
|
654
|
+
toolResult: { ...submission.toolResult, output },
|
|
655
|
+
};
|
|
656
|
+
result.setToolModelOutput({
|
|
657
|
+
toolCallId: submission.toolCallId,
|
|
658
|
+
output,
|
|
659
|
+
});
|
|
660
|
+
}
|
|
612
661
|
if (!input.isTurnSuspending?.()) {
|
|
662
|
+
if (!settledHostToolCallIds.has(submission.toolCallId)) {
|
|
663
|
+
hostToolCallIdsAwaitingEcho.add(submission.toolCallId);
|
|
664
|
+
}
|
|
613
665
|
return control.submitToolResult(submission);
|
|
614
666
|
}
|
|
615
667
|
const toolCall =
|
|
@@ -808,7 +860,7 @@ export function runPrompt<
|
|
|
808
860
|
});
|
|
809
861
|
if (!execution.executed) {
|
|
810
862
|
recordPendingToolResult({ toolCall: rawToolCall });
|
|
811
|
-
await finishForHostInputPause(
|
|
863
|
+
await finishForHostInputPause();
|
|
812
864
|
return 'awaiting-tool-result';
|
|
813
865
|
}
|
|
814
866
|
const toolExecution = toolExecutions.get(rawToolCall.toolCallId)!;
|
|
@@ -852,11 +904,10 @@ export function runPrompt<
|
|
|
852
904
|
|
|
853
905
|
while (true) {
|
|
854
906
|
if (
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
observedStepToolCallCount >= expectedStepToolCallCount
|
|
907
|
+
deferredPauseFinishStep != null &&
|
|
908
|
+
hostToolCallIdsAwaitingEcho.size === 0
|
|
858
909
|
) {
|
|
859
|
-
await
|
|
910
|
+
await pauseForHostInput(deferredPauseFinishStep);
|
|
860
911
|
return;
|
|
861
912
|
}
|
|
862
913
|
|
|
@@ -874,6 +925,9 @@ export function runPrompt<
|
|
|
874
925
|
result.enqueueRaw(value.rawValue);
|
|
875
926
|
continue;
|
|
876
927
|
}
|
|
928
|
+
if (value.type === 'tool-result') {
|
|
929
|
+
hostToolCallIdsAwaitingEcho.delete(value.toolCallId);
|
|
930
|
+
}
|
|
877
931
|
|
|
878
932
|
if (pendingStopBoundary != null) {
|
|
879
933
|
if (value.type === 'finish') {
|
|
@@ -964,6 +1018,45 @@ export function runPrompt<
|
|
|
964
1018
|
if (settledHostInputReplay || settledBuiltinApprovalReplay) {
|
|
965
1019
|
continue;
|
|
966
1020
|
}
|
|
1021
|
+
if (value.type === 'finish-step') {
|
|
1022
|
+
if (hostInputPauseRequested) {
|
|
1023
|
+
await waitForOutstandingHostToolExecutions();
|
|
1024
|
+
if (hostToolCallIdsAwaitingEcho.size === 0) {
|
|
1025
|
+
await pauseForHostInput(value);
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
deferredPauseFinishStep = value;
|
|
1029
|
+
continue;
|
|
1030
|
+
}
|
|
1031
|
+
if (hostToolCallIdsAwaitingEcho.size > 0) {
|
|
1032
|
+
ignoredFinishStep = value;
|
|
1033
|
+
continue;
|
|
1034
|
+
}
|
|
1035
|
+
ignoredFinishStep = undefined;
|
|
1036
|
+
}
|
|
1037
|
+
if (value.type === 'finish') {
|
|
1038
|
+
if (hostInputPauseRequested) {
|
|
1039
|
+
await pauseForHostInput(deferredPauseFinishStep);
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
if (ignoredFinishStep != null) {
|
|
1043
|
+
const finishStep = ignoredFinishStep;
|
|
1044
|
+
ignoredFinishStep = undefined;
|
|
1045
|
+
await waitForOutstandingHostToolExecutions();
|
|
1046
|
+
if (closingResumedStep) {
|
|
1047
|
+
closingResumedStep = false;
|
|
1048
|
+
await publishToolExecutions();
|
|
1049
|
+
resetStepContent();
|
|
1050
|
+
result.discardCurrentStepContent();
|
|
1051
|
+
} else {
|
|
1052
|
+
await completeStep({
|
|
1053
|
+
finishReason: finishStep.finishReason,
|
|
1054
|
+
usage: finishStep.usage,
|
|
1055
|
+
providerMetadata: finishStep.harnessMetadata,
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
967
1060
|
if (displayValue.type === 'finish-step' && closingResumedStep) {
|
|
968
1061
|
closingResumedStep = false;
|
|
969
1062
|
await publishToolExecutions();
|
|
@@ -994,6 +1087,12 @@ export function runPrompt<
|
|
|
994
1087
|
`Harness '${input.harness.harnessId}' emitted a built-in tool approval request but does not support approval responses.`,
|
|
995
1088
|
);
|
|
996
1089
|
}
|
|
1090
|
+
/*
|
|
1091
|
+
* The runtime may report a step boundary while it waits for this
|
|
1092
|
+
* decision. The decision is answered here, so that boundary must
|
|
1093
|
+
* not end the step before the denied call's result arrives.
|
|
1094
|
+
*/
|
|
1095
|
+
hostToolCallIdsAwaitingEcho.add(displayValue.toolCallId);
|
|
997
1096
|
await control.submitToolApproval({
|
|
998
1097
|
approvalId: displayValue.approvalId,
|
|
999
1098
|
approved: false,
|
|
@@ -1100,8 +1199,6 @@ export function runPrompt<
|
|
|
1100
1199
|
}
|
|
1101
1200
|
|
|
1102
1201
|
if (value.type === 'tool-call') {
|
|
1103
|
-
observedStepToolCallCount += 1;
|
|
1104
|
-
expectedStepToolCallCount ??= value.stepToolCallCount;
|
|
1105
1202
|
const toolCall = toolCallsByToolCallId.get(value.toolCallId);
|
|
1106
1203
|
if (toolCall != null) {
|
|
1107
1204
|
stepToolCalls.push(toolCall as ContentPart<TOOLS>);
|
|
@@ -1201,8 +1298,8 @@ export function runPrompt<
|
|
|
1201
1298
|
approvalId: pendingApproval.approvalId,
|
|
1202
1299
|
toolCall,
|
|
1203
1300
|
});
|
|
1204
|
-
|
|
1205
|
-
|
|
1301
|
+
hostInputPauseRequested = true;
|
|
1302
|
+
continue;
|
|
1206
1303
|
}
|
|
1207
1304
|
|
|
1208
1305
|
// Drive step boundaries.
|
|
@@ -1270,15 +1367,8 @@ export function runPrompt<
|
|
|
1270
1367
|
}
|
|
1271
1368
|
if (isClientExecutedBuiltin) {
|
|
1272
1369
|
recordPendingToolResult({ toolCall });
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
observedStepToolCallCount < expectedStepToolCallCount
|
|
1276
|
-
) {
|
|
1277
|
-
pauseAfterStepToolCalls = true;
|
|
1278
|
-
continue;
|
|
1279
|
-
}
|
|
1280
|
-
await finishForHostInputPause({ completeCurrentStep: true });
|
|
1281
|
-
return;
|
|
1370
|
+
hostInputPauseRequested = true;
|
|
1371
|
+
continue;
|
|
1282
1372
|
}
|
|
1283
1373
|
if (validatedHostToolCall == null) {
|
|
1284
1374
|
throw new Error(
|
|
@@ -1393,28 +1483,15 @@ export function runPrompt<
|
|
|
1393
1483
|
approvalId: pendingApproval.approvalId,
|
|
1394
1484
|
toolCall: pendingParsedToolCall,
|
|
1395
1485
|
});
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
observedStepToolCallCount < expectedStepToolCallCount
|
|
1399
|
-
) {
|
|
1400
|
-
pauseAfterStepToolCalls = true;
|
|
1401
|
-
continue;
|
|
1402
|
-
}
|
|
1403
|
-
await finishForHostInputPause({ completeCurrentStep: true });
|
|
1404
|
-
return;
|
|
1486
|
+
hostInputPauseRequested = true;
|
|
1487
|
+
continue;
|
|
1405
1488
|
}
|
|
1406
1489
|
if (!isExecutableTool(activeTools[toolCall.toolName])) {
|
|
1407
1490
|
recordPendingToolResult({ toolCall });
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
observedStepToolCallCount < expectedStepToolCallCount
|
|
1411
|
-
) {
|
|
1412
|
-
pauseAfterStepToolCalls = true;
|
|
1413
|
-
continue;
|
|
1414
|
-
}
|
|
1415
|
-
await finishForHostInputPause({ completeCurrentStep: true });
|
|
1416
|
-
return;
|
|
1491
|
+
hostInputPauseRequested = true;
|
|
1492
|
+
continue;
|
|
1417
1493
|
}
|
|
1494
|
+
hostToolCallIdsAwaitingEcho.add(toolCall.toolCallId);
|
|
1418
1495
|
startHostToolExecution(
|
|
1419
1496
|
(async () => {
|
|
1420
1497
|
const toolExecution = toolExecutions.get(toolCall.toolCallId);
|
|
@@ -1481,6 +1558,10 @@ export function runPrompt<
|
|
|
1481
1558
|
);
|
|
1482
1559
|
}
|
|
1483
1560
|
}
|
|
1561
|
+
if (hostInputPauseRequested && input.isTurnSuspending?.() !== true) {
|
|
1562
|
+
await pauseForHostInput(deferredPauseFinishStep);
|
|
1563
|
+
return;
|
|
1564
|
+
}
|
|
1484
1565
|
await waitForOutstandingHostToolExecutions();
|
|
1485
1566
|
const isTurnSuspending = input.isTurnSuspending?.() === true;
|
|
1486
1567
|
if (isTurnSuspending) {
|
|
@@ -1695,6 +1776,22 @@ async function maybeExecuteHostTool<TOOLS extends ToolSet>(input: {
|
|
|
1695
1776
|
await input.submitToolResult({
|
|
1696
1777
|
toolCallId: input.event.toolCallId,
|
|
1697
1778
|
output,
|
|
1779
|
+
...(tool.toModelOutput == null
|
|
1780
|
+
? {}
|
|
1781
|
+
: {
|
|
1782
|
+
toolResult: {
|
|
1783
|
+
type: 'tool-result' as const,
|
|
1784
|
+
toolCallId: input.event.toolCallId,
|
|
1785
|
+
toolName: input.event.toolName,
|
|
1786
|
+
output: await createToolModelOutput({
|
|
1787
|
+
toolCallId: input.event.toolCallId,
|
|
1788
|
+
input: input.parsedToolCall.input,
|
|
1789
|
+
output,
|
|
1790
|
+
tool,
|
|
1791
|
+
errorMode: 'none',
|
|
1792
|
+
}),
|
|
1793
|
+
},
|
|
1794
|
+
}),
|
|
1698
1795
|
});
|
|
1699
1796
|
return { executed: true, outcome: { ok: true, output } };
|
|
1700
1797
|
} catch (err) {
|