@ai-sdk/harness 1.0.101 → 1.0.103
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 +32 -0
- package/dist/agent/index.d.ts +65 -4
- package/dist/agent/index.js +493 -441
- package/dist/agent/index.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/utils/index.d.ts +17 -3
- package/dist/utils/index.js +321 -18
- package/dist/utils/index.js.map +1 -1
- package/package.json +5 -5
- package/src/agent/harness-agent-session.ts +5 -0
- package/src/agent/harness-agent-settings.ts +82 -3
- package/src/agent/harness-agent.ts +67 -0
- package/src/agent/internal/harness-stream-text-result.ts +33 -13
- package/src/agent/internal/run-prompt.ts +209 -87
- package/src/agent/internal/turn-telemetry.ts +293 -444
- package/src/utils/index.ts +5 -0
- package/src/utils/write-instructions.ts +373 -0
- package/src/utils/write-skills.ts +59 -2
- package/src/v1/harness-v1-session.ts +5 -0
|
@@ -17,6 +17,7 @@ import { toHarnessStream } from './to-harness-stream';
|
|
|
17
17
|
import {
|
|
18
18
|
executeTool,
|
|
19
19
|
generateId,
|
|
20
|
+
type InferToolSetContext,
|
|
20
21
|
isExecutableTool,
|
|
21
22
|
safeParseJSON,
|
|
22
23
|
type Context,
|
|
@@ -31,7 +32,7 @@ import {
|
|
|
31
32
|
type LanguageModelV4ToolCall,
|
|
32
33
|
type LanguageModelV4Usage,
|
|
33
34
|
} from '@ai-sdk/provider';
|
|
34
|
-
import { parseToolCall } from 'ai/internal';
|
|
35
|
+
import { asLanguageModelUsage, parseToolCall } from 'ai/internal';
|
|
35
36
|
import type {
|
|
36
37
|
ContentPart,
|
|
37
38
|
OutputInterface as Output,
|
|
@@ -40,15 +41,18 @@ import type {
|
|
|
40
41
|
StopCondition,
|
|
41
42
|
TelemetryOptions,
|
|
42
43
|
TextStreamPart,
|
|
44
|
+
TypedToolCall,
|
|
45
|
+
TypedToolError,
|
|
46
|
+
TypedToolResult,
|
|
43
47
|
} from 'ai';
|
|
44
48
|
import type { HarnessAgentToolApprovalConfiguration } from '../harness-agent-settings';
|
|
45
49
|
import { HarnessStreamTextResult } from './harness-stream-text-result';
|
|
46
50
|
import { translateStreamPart } from './translate-stream-part';
|
|
47
51
|
import { createToolInputWorkDirStripper, stripWorkDir } from './strip-work-dir';
|
|
48
52
|
import {
|
|
49
|
-
|
|
50
|
-
type
|
|
51
|
-
type
|
|
53
|
+
createTurnLifecycle,
|
|
54
|
+
type HarnessAgentLifecycleCallbacks,
|
|
55
|
+
type TurnLifecycle,
|
|
52
56
|
} from './turn-telemetry';
|
|
53
57
|
import { resolveCustomToolApproval } from './permission-mode';
|
|
54
58
|
import { logBridgeError } from '../../utils/bridge-diagnostics';
|
|
@@ -118,6 +122,7 @@ export function runPrompt<
|
|
|
118
122
|
responseFormat?: HarnessV1ResponseFormat | undefined;
|
|
119
123
|
output?: OUTPUT | undefined;
|
|
120
124
|
telemetry?: TelemetryOptions | undefined;
|
|
125
|
+
callbacks?: HarnessAgentLifecycleCallbacks<TOOLS, RUNTIME_CONTEXT, OUTPUT>;
|
|
121
126
|
stopConditions?: ReadonlyArray<StopCondition<TOOLS, RUNTIME_CONTEXT>>;
|
|
122
127
|
toolApproval?: HarnessAgentToolApprovalConfiguration | undefined;
|
|
123
128
|
pendingToolApprovals?: readonly HarnessV1PendingToolApproval[];
|
|
@@ -141,13 +146,15 @@ export function runPrompt<
|
|
|
141
146
|
result: HarnessStreamTextResult<TOOLS, RUNTIME_CONTEXT, OUTPUT>;
|
|
142
147
|
done: Promise<void>;
|
|
143
148
|
} {
|
|
149
|
+
const callId = generateId();
|
|
150
|
+
const toolsContext = {} as InferToolSetContext<TOOLS>;
|
|
144
151
|
const result = new HarnessStreamTextResult<TOOLS, RUNTIME_CONTEXT, OUTPUT>({
|
|
145
152
|
tools: input.tools,
|
|
146
153
|
runtimeContext: input.runtimeContext,
|
|
147
|
-
|
|
148
|
-
toolsContext: undefined as never,
|
|
154
|
+
toolsContext,
|
|
149
155
|
harnessId: input.harness.harnessId,
|
|
150
|
-
|
|
156
|
+
callId,
|
|
157
|
+
modelId: input.model ?? '',
|
|
151
158
|
output: input.output,
|
|
152
159
|
});
|
|
153
160
|
const pendingToolApprovals = input.pendingToolApprovals ?? [];
|
|
@@ -170,16 +177,23 @@ export function runPrompt<
|
|
|
170
177
|
})),
|
|
171
178
|
);
|
|
172
179
|
|
|
173
|
-
const
|
|
180
|
+
const lifecycle = createTurnLifecycle({
|
|
181
|
+
callId,
|
|
174
182
|
telemetry: input.telemetry,
|
|
183
|
+
callbacks: input.callbacks ?? {},
|
|
175
184
|
harnessId: input.harness.harnessId,
|
|
176
185
|
modelId: input.model,
|
|
177
186
|
instructions: input.instructions,
|
|
178
187
|
tools: input.tools,
|
|
179
188
|
activeToolNames,
|
|
180
189
|
toolSpecs: input.toolSpecs,
|
|
181
|
-
|
|
190
|
+
messages:
|
|
191
|
+
input.prompt == null
|
|
192
|
+
? []
|
|
193
|
+
: [{ role: 'user', content: promptToText(input.prompt) }],
|
|
182
194
|
runtimeContext: input.runtimeContext,
|
|
195
|
+
toolsContext,
|
|
196
|
+
output: input.output,
|
|
183
197
|
});
|
|
184
198
|
|
|
185
199
|
/*
|
|
@@ -243,7 +257,7 @@ export function runPrompt<
|
|
|
243
257
|
},
|
|
244
258
|
});
|
|
245
259
|
} catch (err) {
|
|
246
|
-
await
|
|
260
|
+
await lifecycle.error(err);
|
|
247
261
|
logBridgeError({
|
|
248
262
|
harnessId: input.harness.harnessId,
|
|
249
263
|
sessionId: input.session.sessionId,
|
|
@@ -340,25 +354,59 @@ export function runPrompt<
|
|
|
340
354
|
pendingStopBoundary = undefined;
|
|
341
355
|
};
|
|
342
356
|
|
|
343
|
-
// Accumulate the model
|
|
344
|
-
//
|
|
357
|
+
// Accumulate the model response until its step boundary. Harness runtimes
|
|
358
|
+
// may execute tools before emitting `finish-step`, so tool lifecycle
|
|
359
|
+
// notifications and consumer-visible tool outcomes are held until then.
|
|
345
360
|
let stepText = '';
|
|
346
361
|
let stepReasoning = '';
|
|
347
|
-
let stepToolCalls:
|
|
362
|
+
let stepToolCalls: ContentPart<TOOLS>[] = [];
|
|
363
|
+
let stepProviderToolResults: ContentPart<TOOLS>[] = [];
|
|
364
|
+
let stepApprovalRequests: ContentPart<TOOLS>[] = [];
|
|
365
|
+
let bufferedToolOutcomes: Array<() => void> = [];
|
|
366
|
+
const toolExecutions = new Map<
|
|
367
|
+
string,
|
|
368
|
+
{
|
|
369
|
+
toolCall: TypedToolCall<TOOLS>;
|
|
370
|
+
toolOutput?: TypedToolResult<TOOLS> | TypedToolError<TOOLS>;
|
|
371
|
+
toolExecutionMs?: number;
|
|
372
|
+
}
|
|
373
|
+
>();
|
|
374
|
+
const publishToolExecutions = async (): Promise<void> => {
|
|
375
|
+
for (const execution of toolExecutions.values()) {
|
|
376
|
+
if (execution.toolOutput == null) continue;
|
|
377
|
+
await lifecycle.toolExecutionStart({
|
|
378
|
+
toolCall: execution.toolCall,
|
|
379
|
+
});
|
|
380
|
+
await lifecycle.toolExecutionEnd({
|
|
381
|
+
toolCall: execution.toolCall,
|
|
382
|
+
toolOutput: execution.toolOutput,
|
|
383
|
+
toolExecutionMs: execution.toolExecutionMs ?? 0,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
for (const publish of bufferedToolOutcomes) publish();
|
|
387
|
+
toolExecutions.clear();
|
|
388
|
+
bufferedToolOutcomes = [];
|
|
389
|
+
};
|
|
348
390
|
let expectedStepToolCallCount: number | undefined;
|
|
349
391
|
let observedStepToolCallCount = 0;
|
|
350
392
|
let pauseAfterStepToolCalls = false;
|
|
351
|
-
const
|
|
352
|
-
const parts:
|
|
393
|
+
const buildModelCallContent = (): ContentPart<TOOLS>[] => {
|
|
394
|
+
const parts: ContentPart<TOOLS>[] = [];
|
|
353
395
|
if (stepText) parts.push({ type: 'text', text: stepText });
|
|
354
396
|
if (stepReasoning) parts.push({ type: 'reasoning', text: stepReasoning });
|
|
355
397
|
parts.push(...stepToolCalls);
|
|
398
|
+
parts.push(...stepApprovalRequests);
|
|
399
|
+
parts.push(...stepProviderToolResults);
|
|
356
400
|
return parts;
|
|
357
401
|
};
|
|
358
402
|
const resetStepContent = (): void => {
|
|
359
403
|
stepText = '';
|
|
360
404
|
stepReasoning = '';
|
|
361
405
|
stepToolCalls = [];
|
|
406
|
+
stepProviderToolResults = [];
|
|
407
|
+
stepApprovalRequests = [];
|
|
408
|
+
bufferedToolOutcomes = [];
|
|
409
|
+
toolExecutions.clear();
|
|
362
410
|
expectedStepToolCallCount = undefined;
|
|
363
411
|
observedStepToolCallCount = 0;
|
|
364
412
|
pauseAfterStepToolCalls = false;
|
|
@@ -385,13 +433,13 @@ export function runPrompt<
|
|
|
385
433
|
usage: LanguageModelV4Usage;
|
|
386
434
|
providerMetadata: ProviderMetadata | undefined;
|
|
387
435
|
}): Promise<StepResult<TOOLS, RUNTIME_CONTEXT>> => {
|
|
388
|
-
await
|
|
389
|
-
finishReason: input.finishReason,
|
|
390
|
-
usage: input.usage,
|
|
436
|
+
await lifecycle.languageModelCallEnd({
|
|
437
|
+
finishReason: input.finishReason.unified,
|
|
438
|
+
usage: asLanguageModelUsage(input.usage),
|
|
391
439
|
providerMetadata: input.providerMetadata,
|
|
392
|
-
content:
|
|
440
|
+
content: buildModelCallContent(),
|
|
393
441
|
});
|
|
394
|
-
|
|
442
|
+
await publishToolExecutions();
|
|
395
443
|
const step = result.finishStep({
|
|
396
444
|
finishReason: input.finishReason,
|
|
397
445
|
usage: input.usage,
|
|
@@ -399,6 +447,8 @@ export function runPrompt<
|
|
|
399
447
|
warnings: [],
|
|
400
448
|
});
|
|
401
449
|
completedSteps.push(step);
|
|
450
|
+
await lifecycle.stepEnd(step);
|
|
451
|
+
resetStepContent();
|
|
402
452
|
return step;
|
|
403
453
|
};
|
|
404
454
|
const finishForHostInputPause = async (options: {
|
|
@@ -411,10 +461,12 @@ export function runPrompt<
|
|
|
411
461
|
usage: zeroUsage,
|
|
412
462
|
providerMetadata: undefined,
|
|
413
463
|
});
|
|
464
|
+
} else {
|
|
465
|
+
await publishToolExecutions();
|
|
414
466
|
}
|
|
415
|
-
await
|
|
416
|
-
|
|
417
|
-
usage: zeroUsage,
|
|
467
|
+
await lifecycle.end({
|
|
468
|
+
steps: completedSteps,
|
|
469
|
+
usage: asLanguageModelUsage(zeroUsage),
|
|
418
470
|
});
|
|
419
471
|
await result.finish();
|
|
420
472
|
};
|
|
@@ -423,14 +475,16 @@ export function runPrompt<
|
|
|
423
475
|
toolCall: ToolCallTextStreamPart;
|
|
424
476
|
isAutomatic?: boolean;
|
|
425
477
|
}): void => {
|
|
426
|
-
|
|
478
|
+
const part = {
|
|
427
479
|
type: 'tool-approval-request',
|
|
428
480
|
approvalId: approval.approvalId,
|
|
429
481
|
toolCall: approval.toolCall,
|
|
430
482
|
...(approval.isAutomatic !== undefined
|
|
431
483
|
? { isAutomatic: approval.isAutomatic }
|
|
432
484
|
: {}),
|
|
433
|
-
} as TextStreamPart<TOOLS
|
|
485
|
+
} as TextStreamPart<TOOLS>;
|
|
486
|
+
result.enqueue(part);
|
|
487
|
+
stepApprovalRequests.push(part as ContentPart<TOOLS>);
|
|
434
488
|
};
|
|
435
489
|
const enqueueAutomaticApprovalResponse = (input: {
|
|
436
490
|
approvalId: string;
|
|
@@ -599,16 +653,15 @@ export function runPrompt<
|
|
|
599
653
|
return 'continued';
|
|
600
654
|
}
|
|
601
655
|
|
|
602
|
-
await
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
toolName: rawToolCall.toolName,
|
|
606
|
-
input: rawToolCall.input,
|
|
656
|
+
await lifecycle.start(input.model);
|
|
657
|
+
toolExecutions.set(rawToolCall.toolCallId, {
|
|
658
|
+
toolCall: toolCall as TypedToolCall<TOOLS>,
|
|
607
659
|
});
|
|
660
|
+
const executionStartedAt = Date.now();
|
|
608
661
|
const execution = await maybeExecuteHostTool({
|
|
609
662
|
event: rawToolCall,
|
|
610
663
|
tools: activeTools,
|
|
611
|
-
wrappedExecuteTool:
|
|
664
|
+
wrappedExecuteTool: lifecycle.executeTool,
|
|
612
665
|
sandboxSession: input.sandboxSession,
|
|
613
666
|
abortSignal: input.abortSignal,
|
|
614
667
|
control,
|
|
@@ -625,14 +678,16 @@ export function runPrompt<
|
|
|
625
678
|
},
|
|
626
679
|
input.sessionWorkDir,
|
|
627
680
|
) as Extract<HarnessV1StreamPart, { type: 'tool-result' }>;
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
681
|
+
bufferedToolOutcomes.push(() => {
|
|
682
|
+
result.enqueue({
|
|
683
|
+
type: 'tool-result',
|
|
684
|
+
toolCallId: rawToolCall.toolCallId,
|
|
685
|
+
toolName: rawToolCall.toolName,
|
|
686
|
+
input: undefined,
|
|
687
|
+
output: stripped.result,
|
|
688
|
+
preliminary: true,
|
|
689
|
+
} as TextStreamPart<TOOLS>);
|
|
690
|
+
});
|
|
636
691
|
},
|
|
637
692
|
});
|
|
638
693
|
if (!execution.executed) {
|
|
@@ -640,11 +695,19 @@ export function runPrompt<
|
|
|
640
695
|
await finishForHostInputPause({ completeCurrentStep: false });
|
|
641
696
|
return 'awaiting-tool-result';
|
|
642
697
|
}
|
|
643
|
-
|
|
698
|
+
const toolExecution = toolExecutions.get(rawToolCall.toolCallId)!;
|
|
699
|
+
toolExecution.toolOutput = toToolOutput({
|
|
644
700
|
toolCall,
|
|
645
701
|
outcome: execution.outcome,
|
|
646
702
|
});
|
|
647
|
-
|
|
703
|
+
toolExecution.toolExecutionMs = Date.now() - executionStartedAt;
|
|
704
|
+
bufferedToolOutcomes.push(() => {
|
|
705
|
+
enqueueHostToolOutcome({
|
|
706
|
+
toolCall,
|
|
707
|
+
outcome: execution.outcome,
|
|
708
|
+
});
|
|
709
|
+
});
|
|
710
|
+
await publishToolExecutions();
|
|
648
711
|
return 'continued';
|
|
649
712
|
};
|
|
650
713
|
|
|
@@ -703,9 +766,12 @@ export function runPrompt<
|
|
|
703
766
|
).some(Boolean)
|
|
704
767
|
) {
|
|
705
768
|
await input.onStopConditionMet?.();
|
|
706
|
-
const {
|
|
769
|
+
const { usage } = pendingStopBoundary;
|
|
707
770
|
releasePendingStopBoundary();
|
|
708
|
-
await
|
|
771
|
+
await lifecycle.end({
|
|
772
|
+
steps: completedSteps,
|
|
773
|
+
usage: asLanguageModelUsage(usage),
|
|
774
|
+
});
|
|
709
775
|
await result.finish();
|
|
710
776
|
return;
|
|
711
777
|
} else {
|
|
@@ -716,7 +782,9 @@ export function runPrompt<
|
|
|
716
782
|
// Begin the operation span on stream-start, using the runtime-resolved
|
|
717
783
|
// model the adapter reports (falling back to the requested turn model).
|
|
718
784
|
if (value.type === 'stream-start') {
|
|
719
|
-
|
|
785
|
+
const modelId = value.modelId ?? input.model;
|
|
786
|
+
if (modelId != null) result.setModelId(modelId);
|
|
787
|
+
await lifecycle.start(modelId);
|
|
720
788
|
}
|
|
721
789
|
|
|
722
790
|
// Open a step span lazily before the first content of each step.
|
|
@@ -726,7 +794,7 @@ export function runPrompt<
|
|
|
726
794
|
value.type !== 'finish' &&
|
|
727
795
|
value.type !== 'error'
|
|
728
796
|
) {
|
|
729
|
-
await
|
|
797
|
+
await lifecycle.ensureStepOpen();
|
|
730
798
|
}
|
|
731
799
|
|
|
732
800
|
if (
|
|
@@ -774,6 +842,7 @@ export function runPrompt<
|
|
|
774
842
|
|
|
775
843
|
if (displayValue.type === 'finish-step' && closingResumedStep) {
|
|
776
844
|
closingResumedStep = false;
|
|
845
|
+
await publishToolExecutions();
|
|
777
846
|
resetStepContent();
|
|
778
847
|
result.discardCurrentStepContent();
|
|
779
848
|
continue;
|
|
@@ -824,7 +893,7 @@ export function runPrompt<
|
|
|
824
893
|
// Telemetry and stderr diagnostics keep the raw error (absolute
|
|
825
894
|
// paths help debugging); the consumer-facing settle uses the
|
|
826
895
|
// workDir-stripped one, like every other forwarded part.
|
|
827
|
-
await
|
|
896
|
+
await lifecycle.error(value.error);
|
|
828
897
|
// A turn the caller itself aborted ends with an error-shaped part by
|
|
829
898
|
// construction; diagnosing the caller's own signal to stderr reads
|
|
830
899
|
// as a malfunction. `settleFailure` below still reports it as an
|
|
@@ -841,12 +910,16 @@ export function runPrompt<
|
|
|
841
910
|
return;
|
|
842
911
|
}
|
|
843
912
|
|
|
844
|
-
|
|
845
|
-
for (const part of translateStreamPart<TOOLS>(
|
|
913
|
+
const translatedParts = translateStreamPart<TOOLS>(
|
|
846
914
|
displayValue,
|
|
847
915
|
translateOptions,
|
|
848
|
-
)
|
|
849
|
-
|
|
916
|
+
);
|
|
917
|
+
if (value.type === 'tool-result') {
|
|
918
|
+
bufferedToolOutcomes.push(() => {
|
|
919
|
+
for (const part of translatedParts) result.enqueue(part);
|
|
920
|
+
});
|
|
921
|
+
} else {
|
|
922
|
+
for (const part of translatedParts) result.enqueue(part);
|
|
850
923
|
}
|
|
851
924
|
|
|
852
925
|
// Tool-call validation lives here (not in translateStreamPart) because
|
|
@@ -862,38 +935,48 @@ export function runPrompt<
|
|
|
862
935
|
result.enqueue(parsed);
|
|
863
936
|
}
|
|
864
937
|
|
|
865
|
-
// Accumulate output content for telemetry / reporters.
|
|
866
938
|
if (value.type === 'text-delta') {
|
|
867
939
|
stepText += value.delta;
|
|
868
940
|
} else if (value.type === 'reasoning-delta') {
|
|
869
941
|
stepReasoning += value.delta;
|
|
870
942
|
}
|
|
871
943
|
|
|
872
|
-
// Telemetry: a tool execution begins on its `tool-call`.
|
|
873
944
|
if (value.type === 'tool-call') {
|
|
874
945
|
observedStepToolCallCount += 1;
|
|
875
946
|
expectedStepToolCallCount ??= value.stepToolCallCount;
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
toolCallId: value.toolCallId,
|
|
884
|
-
toolName: value.toolName,
|
|
885
|
-
input: value.input,
|
|
886
|
-
});
|
|
947
|
+
const toolCall = toolCallsByToolCallId.get(value.toolCallId);
|
|
948
|
+
if (toolCall != null) {
|
|
949
|
+
stepToolCalls.push(toolCall as ContentPart<TOOLS>);
|
|
950
|
+
toolExecutions.set(value.toolCallId, {
|
|
951
|
+
toolCall: toolCall as TypedToolCall<TOOLS>,
|
|
952
|
+
});
|
|
953
|
+
}
|
|
887
954
|
}
|
|
888
955
|
|
|
889
|
-
// Telemetry: close a tool span when its provider-executed result lands.
|
|
890
956
|
if (value.type === 'tool-result') {
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
value.isError
|
|
894
|
-
? {
|
|
895
|
-
|
|
896
|
-
|
|
957
|
+
const execution = toolExecutions.get(value.toolCallId);
|
|
958
|
+
if (execution != null && execution.toolOutput == null) {
|
|
959
|
+
execution.toolOutput = value.isError
|
|
960
|
+
? ({
|
|
961
|
+
...execution.toolCall,
|
|
962
|
+
type: 'tool-error',
|
|
963
|
+
error: value.result,
|
|
964
|
+
} as TypedToolError<TOOLS>)
|
|
965
|
+
: ({
|
|
966
|
+
...execution.toolCall,
|
|
967
|
+
type: 'tool-result',
|
|
968
|
+
output: value.result,
|
|
969
|
+
} as TypedToolResult<TOOLS>);
|
|
970
|
+
}
|
|
971
|
+
if (
|
|
972
|
+
rawToolCallsByToolCallId.get(value.toolCallId)?.providerExecuted ===
|
|
973
|
+
true &&
|
|
974
|
+
execution?.toolOutput != null
|
|
975
|
+
) {
|
|
976
|
+
stepProviderToolResults.push(
|
|
977
|
+
execution.toolOutput as ContentPart<TOOLS>,
|
|
978
|
+
);
|
|
979
|
+
}
|
|
897
980
|
}
|
|
898
981
|
|
|
899
982
|
if (value.type === 'tool-approval-request') {
|
|
@@ -969,9 +1052,9 @@ export function runPrompt<
|
|
|
969
1052
|
if (value.type === 'finish') {
|
|
970
1053
|
await waitForOutstandingHostToolExecutions();
|
|
971
1054
|
finalFinish = value;
|
|
972
|
-
await
|
|
973
|
-
|
|
974
|
-
usage: value.totalUsage,
|
|
1055
|
+
await lifecycle.end({
|
|
1056
|
+
steps: completedSteps,
|
|
1057
|
+
usage: asLanguageModelUsage(value.totalUsage),
|
|
975
1058
|
});
|
|
976
1059
|
}
|
|
977
1060
|
|
|
@@ -1004,7 +1087,13 @@ export function runPrompt<
|
|
|
1004
1087
|
toolCallId: toolCall.toolCallId,
|
|
1005
1088
|
output,
|
|
1006
1089
|
});
|
|
1007
|
-
|
|
1090
|
+
const execution = toolExecutions.get(toolCall.toolCallId);
|
|
1091
|
+
if (execution != null) {
|
|
1092
|
+
execution.toolOutput = toToolOutput({
|
|
1093
|
+
toolCall: parsedToolCall,
|
|
1094
|
+
outcome: { ok: true, output },
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1008
1097
|
continue;
|
|
1009
1098
|
}
|
|
1010
1099
|
if (isClientExecutedBuiltin) {
|
|
@@ -1045,7 +1134,13 @@ export function runPrompt<
|
|
|
1045
1134
|
toolCallId: toolCall.toolCallId,
|
|
1046
1135
|
output,
|
|
1047
1136
|
});
|
|
1048
|
-
|
|
1137
|
+
const execution = toolExecutions.get(toolCall.toolCallId);
|
|
1138
|
+
if (execution != null) {
|
|
1139
|
+
execution.toolOutput = toToolOutput({
|
|
1140
|
+
toolCall: parsedToolCall,
|
|
1141
|
+
outcome: { ok: true, output },
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1049
1144
|
continue;
|
|
1050
1145
|
}
|
|
1051
1146
|
const pendingApproval =
|
|
@@ -1121,10 +1216,11 @@ export function runPrompt<
|
|
|
1121
1216
|
}
|
|
1122
1217
|
startHostToolExecution(
|
|
1123
1218
|
(async () => {
|
|
1219
|
+
const executionStartedAt = Date.now();
|
|
1124
1220
|
const execution = await maybeExecuteHostTool({
|
|
1125
1221
|
event: toolCall,
|
|
1126
1222
|
tools: activeTools,
|
|
1127
|
-
wrappedExecuteTool:
|
|
1223
|
+
wrappedExecuteTool: lifecycle.executeTool,
|
|
1128
1224
|
sandboxSession: input.sandboxSession,
|
|
1129
1225
|
abortSignal: input.abortSignal,
|
|
1130
1226
|
control,
|
|
@@ -1149,14 +1245,16 @@ export function runPrompt<
|
|
|
1149
1245
|
},
|
|
1150
1246
|
input.sessionWorkDir,
|
|
1151
1247
|
) as Extract<HarnessV1StreamPart, { type: 'tool-result' }>;
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1248
|
+
bufferedToolOutcomes.push(() => {
|
|
1249
|
+
result.enqueue({
|
|
1250
|
+
type: 'tool-result',
|
|
1251
|
+
toolCallId: toolCall.toolCallId,
|
|
1252
|
+
toolName: toolCall.toolName,
|
|
1253
|
+
input: undefined,
|
|
1254
|
+
output: stripped.result,
|
|
1255
|
+
preliminary: true,
|
|
1256
|
+
} as TextStreamPart<TOOLS>);
|
|
1257
|
+
});
|
|
1160
1258
|
},
|
|
1161
1259
|
});
|
|
1162
1260
|
if (!execution.executed) {
|
|
@@ -1164,7 +1262,14 @@ export function runPrompt<
|
|
|
1164
1262
|
`Harness '${input.harness.harnessId}' could not execute host tool '${toolCall.toolName}'.`,
|
|
1165
1263
|
);
|
|
1166
1264
|
}
|
|
1167
|
-
|
|
1265
|
+
const toolExecution = toolExecutions.get(toolCall.toolCallId);
|
|
1266
|
+
if (toolExecution != null) {
|
|
1267
|
+
toolExecution.toolOutput = toToolOutput({
|
|
1268
|
+
toolCall: parsedToolCall,
|
|
1269
|
+
outcome: execution.outcome,
|
|
1270
|
+
});
|
|
1271
|
+
toolExecution.toolExecutionMs = Date.now() - executionStartedAt;
|
|
1272
|
+
}
|
|
1168
1273
|
})(),
|
|
1169
1274
|
);
|
|
1170
1275
|
}
|
|
@@ -1201,7 +1306,7 @@ export function runPrompt<
|
|
|
1201
1306
|
} catch {
|
|
1202
1307
|
// Preserve the error that stopped the reader loop.
|
|
1203
1308
|
}
|
|
1204
|
-
await
|
|
1309
|
+
await lifecycle.error(err);
|
|
1205
1310
|
logBridgeError({
|
|
1206
1311
|
harnessId: input.harness.harnessId,
|
|
1207
1312
|
sessionId: input.session.sessionId,
|
|
@@ -1232,6 +1337,23 @@ type HostToolExecution =
|
|
|
1232
1337
|
| { executed: false }
|
|
1233
1338
|
| { executed: true; outcome: HostToolOutcome };
|
|
1234
1339
|
|
|
1340
|
+
function toToolOutput<TOOLS extends ToolSet>(input: {
|
|
1341
|
+
toolCall: ToolCallTextStreamPart;
|
|
1342
|
+
outcome: HostToolOutcome;
|
|
1343
|
+
}): TypedToolResult<TOOLS> | TypedToolError<TOOLS> {
|
|
1344
|
+
return input.outcome.ok
|
|
1345
|
+
? ({
|
|
1346
|
+
...input.toolCall,
|
|
1347
|
+
type: 'tool-result',
|
|
1348
|
+
output: input.outcome.output,
|
|
1349
|
+
} as TypedToolResult<TOOLS>)
|
|
1350
|
+
: ({
|
|
1351
|
+
...input.toolCall,
|
|
1352
|
+
type: 'tool-error',
|
|
1353
|
+
error: input.outcome.error,
|
|
1354
|
+
} as TypedToolError<TOOLS>);
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1235
1357
|
function asToolCallTextStreamPart<TOOLS extends ToolSet>(input: {
|
|
1236
1358
|
part: TextStreamPart<TOOLS>;
|
|
1237
1359
|
}): ToolCallTextStreamPart {
|
|
@@ -1263,7 +1385,7 @@ function hasTool(input: { tools: ToolSet; toolName: string }): boolean {
|
|
|
1263
1385
|
async function maybeExecuteHostTool<TOOLS extends ToolSet>(input: {
|
|
1264
1386
|
event: { toolCallId: string; toolName: string; input: string };
|
|
1265
1387
|
tools: TOOLS;
|
|
1266
|
-
wrappedExecuteTool:
|
|
1388
|
+
wrappedExecuteTool: TurnLifecycle<ToolSet, Context>['executeTool'];
|
|
1267
1389
|
sandboxSession: SandboxSession;
|
|
1268
1390
|
abortSignal: AbortSignal | undefined;
|
|
1269
1391
|
control: HarnessV1PromptControl;
|