@ai-sdk/harness 1.0.70 → 1.0.71
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 +10 -0
- package/dist/agent/index.js +64 -29
- package/dist/agent/index.js.map +1 -1
- package/package.json +3 -3
- package/src/agent/internal/run-prompt.ts +78 -40
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/harness",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.71",
|
|
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.7",
|
|
48
48
|
"@ai-sdk/provider-utils": "5.0.27",
|
|
49
|
-
"ai": "7.0.
|
|
49
|
+
"ai": "7.0.65"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"ws": "^8.21.0",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"typescript": "5.8.3",
|
|
66
66
|
"ws": "^8.21.0",
|
|
67
67
|
"zod": "3.25.76",
|
|
68
|
-
"@ai-sdk/otel": "1.0.
|
|
68
|
+
"@ai-sdk/otel": "1.0.65",
|
|
69
69
|
"@vercel/ai-tsconfig": "0.0.0"
|
|
70
70
|
},
|
|
71
71
|
"engines": {
|
|
@@ -248,6 +248,25 @@ export function runPrompt<
|
|
|
248
248
|
| Extract<HarnessV1StreamPart, { type: 'finish' }>
|
|
249
249
|
| undefined;
|
|
250
250
|
const completedSteps: Array<StepResult<TOOLS, RUNTIME_CONTEXT>> = [];
|
|
251
|
+
const outstandingHostToolExecutions: Promise<void>[] = [];
|
|
252
|
+
const startHostToolExecution = (execution: Promise<void>): void => {
|
|
253
|
+
outstandingHostToolExecutions.push(execution);
|
|
254
|
+
// The execution is joined at the next step boundary. Attach a rejection
|
|
255
|
+
// handler immediately so failures cannot become unhandled in the
|
|
256
|
+
// meantime; awaiting the original promise still propagates the failure.
|
|
257
|
+
void execution.catch(() => {});
|
|
258
|
+
};
|
|
259
|
+
const waitForOutstandingHostToolExecutions = async (): Promise<void> => {
|
|
260
|
+
if (outstandingHostToolExecutions.length === 0) return;
|
|
261
|
+
const executions = outstandingHostToolExecutions.splice(0);
|
|
262
|
+
const results = await Promise.allSettled(executions);
|
|
263
|
+
const failedExecution = results.find(
|
|
264
|
+
result => result.status === 'rejected',
|
|
265
|
+
);
|
|
266
|
+
if (failedExecution != null) {
|
|
267
|
+
throw failedExecution.reason;
|
|
268
|
+
}
|
|
269
|
+
};
|
|
251
270
|
const releasePendingStopBoundary = (): void => {
|
|
252
271
|
pendingStopBoundary?.releaseCheckpoint?.();
|
|
253
272
|
pendingStopBoundary = undefined;
|
|
@@ -311,6 +330,7 @@ export function runPrompt<
|
|
|
311
330
|
const finishForHostInputPause = async (options: {
|
|
312
331
|
completeCurrentStep: boolean;
|
|
313
332
|
}): Promise<void> => {
|
|
333
|
+
await waitForOutstandingHostToolExecutions();
|
|
314
334
|
if (options.completeCurrentStep) {
|
|
315
335
|
await completeStep({
|
|
316
336
|
finishReason: toolCallsFinishReason,
|
|
@@ -665,6 +685,7 @@ export function runPrompt<
|
|
|
665
685
|
* or a second `error` part from `fail`).
|
|
666
686
|
*/
|
|
667
687
|
if (value.type === 'error' && displayValue.type === 'error') {
|
|
688
|
+
await waitForOutstandingHostToolExecutions();
|
|
668
689
|
// Telemetry and stderr diagnostics keep the raw error (absolute
|
|
669
690
|
// paths help debugging); the consumer-facing settle uses the
|
|
670
691
|
// workDir-stripped one, like every other forwarded part.
|
|
@@ -784,6 +805,7 @@ export function runPrompt<
|
|
|
784
805
|
|
|
785
806
|
// Drive step boundaries.
|
|
786
807
|
if (value.type === 'finish-step') {
|
|
808
|
+
await waitForOutstandingHostToolExecutions();
|
|
787
809
|
await completeStep({
|
|
788
810
|
finishReason: value.finishReason,
|
|
789
811
|
usage: value.usage,
|
|
@@ -799,6 +821,7 @@ export function runPrompt<
|
|
|
799
821
|
}
|
|
800
822
|
|
|
801
823
|
if (value.type === 'finish') {
|
|
824
|
+
await waitForOutstandingHostToolExecutions();
|
|
802
825
|
finalFinish = value;
|
|
803
826
|
await telemetry.end({
|
|
804
827
|
finishReason: value.finishReason,
|
|
@@ -910,52 +933,62 @@ export function runPrompt<
|
|
|
910
933
|
await finishForHostInputPause({ completeCurrentStep: true });
|
|
911
934
|
return;
|
|
912
935
|
}
|
|
913
|
-
|
|
914
|
-
event: toolCall,
|
|
915
|
-
tools: activeTools,
|
|
916
|
-
wrappedExecuteTool: telemetry.executeTool,
|
|
917
|
-
sandboxSession: input.sandboxSession,
|
|
918
|
-
abortSignal: input.abortSignal,
|
|
919
|
-
control,
|
|
920
|
-
onPreliminaryResult: preliminaryOutput => {
|
|
921
|
-
/*
|
|
922
|
-
* Project a `yield`ed value as a preliminary AI SDK
|
|
923
|
-
* `tool-result` part. Unlike the final result — which is
|
|
924
|
-
* submitted to the runtime, echoed back as a `tool-result`
|
|
925
|
-
* event, and stripped on its way through the loop above —
|
|
926
|
-
* preliminary values never reach the runtime, so strip the
|
|
927
|
-
* working directory here to match the final result's projection.
|
|
928
|
-
*/
|
|
929
|
-
const stripped = stripWorkDir(
|
|
930
|
-
{
|
|
931
|
-
type: 'tool-result',
|
|
932
|
-
toolCallId: toolCall.toolCallId,
|
|
933
|
-
toolName: toolCall.toolName,
|
|
934
|
-
result: preliminaryOutput as Extract<
|
|
935
|
-
HarnessV1StreamPart,
|
|
936
|
-
{ type: 'tool-result' }
|
|
937
|
-
>['result'],
|
|
938
|
-
},
|
|
939
|
-
input.sessionWorkDir,
|
|
940
|
-
) as Extract<HarnessV1StreamPart, { type: 'tool-result' }>;
|
|
941
|
-
result.enqueue({
|
|
942
|
-
type: 'tool-result',
|
|
943
|
-
toolCallId: toolCall.toolCallId,
|
|
944
|
-
toolName: toolCall.toolName,
|
|
945
|
-
input: undefined,
|
|
946
|
-
output: stripped.result,
|
|
947
|
-
preliminary: true,
|
|
948
|
-
} as TextStreamPart<TOOLS>);
|
|
949
|
-
},
|
|
950
|
-
});
|
|
951
|
-
if (!execution.executed) {
|
|
936
|
+
if (!isExecutableTool(activeTools[toolCall.toolName])) {
|
|
952
937
|
recordPendingToolResult({ toolCall });
|
|
953
938
|
await finishForHostInputPause({ completeCurrentStep: true });
|
|
954
939
|
return;
|
|
955
940
|
}
|
|
956
|
-
|
|
941
|
+
startHostToolExecution(
|
|
942
|
+
(async () => {
|
|
943
|
+
const execution = await maybeExecuteHostTool({
|
|
944
|
+
event: toolCall,
|
|
945
|
+
tools: activeTools,
|
|
946
|
+
wrappedExecuteTool: telemetry.executeTool,
|
|
947
|
+
sandboxSession: input.sandboxSession,
|
|
948
|
+
abortSignal: input.abortSignal,
|
|
949
|
+
control,
|
|
950
|
+
onPreliminaryResult: preliminaryOutput => {
|
|
951
|
+
/*
|
|
952
|
+
* Project a `yield`ed value as a preliminary AI SDK
|
|
953
|
+
* `tool-result` part. Unlike the final result — which is
|
|
954
|
+
* submitted to the runtime, echoed back as a `tool-result`
|
|
955
|
+
* event, and stripped on its way through the loop above —
|
|
956
|
+
* preliminary values never reach the runtime, so strip the
|
|
957
|
+
* working directory here to match the final result's projection.
|
|
958
|
+
*/
|
|
959
|
+
const stripped = stripWorkDir(
|
|
960
|
+
{
|
|
961
|
+
type: 'tool-result',
|
|
962
|
+
toolCallId: toolCall.toolCallId,
|
|
963
|
+
toolName: toolCall.toolName,
|
|
964
|
+
result: preliminaryOutput as Extract<
|
|
965
|
+
HarnessV1StreamPart,
|
|
966
|
+
{ type: 'tool-result' }
|
|
967
|
+
>['result'],
|
|
968
|
+
},
|
|
969
|
+
input.sessionWorkDir,
|
|
970
|
+
) as Extract<HarnessV1StreamPart, { type: 'tool-result' }>;
|
|
971
|
+
result.enqueue({
|
|
972
|
+
type: 'tool-result',
|
|
973
|
+
toolCallId: toolCall.toolCallId,
|
|
974
|
+
toolName: toolCall.toolName,
|
|
975
|
+
input: undefined,
|
|
976
|
+
output: stripped.result,
|
|
977
|
+
preliminary: true,
|
|
978
|
+
} as TextStreamPart<TOOLS>);
|
|
979
|
+
},
|
|
980
|
+
});
|
|
981
|
+
if (!execution.executed) {
|
|
982
|
+
throw new Error(
|
|
983
|
+
`Harness '${input.harness.harnessId}' could not execute host tool '${toolCall.toolName}'.`,
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
await telemetry.toolEnd(toolCall.toolCallId, execution.outcome);
|
|
987
|
+
})(),
|
|
988
|
+
);
|
|
957
989
|
}
|
|
958
990
|
}
|
|
991
|
+
await waitForOutstandingHostToolExecutions();
|
|
959
992
|
const isTurnSuspending = input.isTurnSuspending?.() === true;
|
|
960
993
|
if (isTurnSuspending) {
|
|
961
994
|
if (finalFinish == null) {
|
|
@@ -982,6 +1015,11 @@ export function runPrompt<
|
|
|
982
1015
|
: undefined,
|
|
983
1016
|
);
|
|
984
1017
|
} catch (err) {
|
|
1018
|
+
try {
|
|
1019
|
+
await waitForOutstandingHostToolExecutions();
|
|
1020
|
+
} catch {
|
|
1021
|
+
// Preserve the error that stopped the reader loop.
|
|
1022
|
+
}
|
|
985
1023
|
await telemetry.error(err);
|
|
986
1024
|
logBridgeError({
|
|
987
1025
|
harnessId: input.harness.harnessId,
|