@ai-sdk/harness 1.0.99 → 1.0.101
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 +19 -0
- package/agent/index.ts +2 -8
- package/dist/agent/index.d.ts +65 -46
- package/dist/agent/index.js +93 -71
- package/dist/agent/index.js.map +1 -1
- package/dist/bridge/index.d.ts +11 -1
- package/dist/bridge/index.js +37 -7
- package/dist/bridge/index.js.map +1 -1
- package/dist/index.d.ts +80 -2
- package/dist/index.js +286 -229
- package/dist/index.js.map +1 -1
- package/dist/utils/index.d.ts +9 -1
- package/dist/utils/index.js +17 -2
- package/dist/utils/index.js.map +1 -1
- package/package.json +3 -3
- package/src/agent/harness-agent-session.ts +4 -8
- package/src/agent/harness-agent-tool-approval-continuation.ts +6 -32
- package/src/agent/harness-agent-tool-result-continuation.ts +4 -53
- package/src/agent/harness-agent.ts +40 -18
- package/src/agent/internal/run-prompt.ts +100 -35
- package/src/bridge/index.ts +82 -11
- package/src/utils/sandbox-credential-brokering.ts +32 -1
- package/src/v1/harness-v1-bridge-protocol.ts +1 -0
- package/src/v1/harness-v1-builtin-tool.ts +2 -0
- package/src/v1/harness-v1-lifecycle-state.ts +2 -0
- package/src/v1/harness-v1-prompt-control.ts +3 -0
- package/src/v1/harness-v1-questions-tool.ts +72 -0
- package/src/v1/index.ts +9 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/harness",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.101",
|
|
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.10",
|
|
48
48
|
"@ai-sdk/provider-utils": "5.0.36",
|
|
49
|
-
"ai": "7.0.
|
|
49
|
+
"ai": "7.0.92"
|
|
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.92",
|
|
62
62
|
"@opentelemetry/sdk-trace-base": "2.7.1",
|
|
63
63
|
"@types/node": "22.19.19",
|
|
64
64
|
"@types/ws": "^8.5.13",
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
Context,
|
|
3
3
|
Experimental_SandboxSession as SandboxSession,
|
|
4
|
+
ToolApprovalResponse,
|
|
5
|
+
ToolResultPart,
|
|
4
6
|
ToolSet,
|
|
5
7
|
} from '@ai-sdk/provider-utils';
|
|
6
8
|
import type {
|
|
@@ -29,8 +31,6 @@ import type {
|
|
|
29
31
|
HarnessAgentResumeSessionState,
|
|
30
32
|
HarnessAgentToolSpec,
|
|
31
33
|
} from './harness-agent-types';
|
|
32
|
-
import type { HarnessAgentToolApprovalContinuation } from './harness-agent-tool-approval-continuation';
|
|
33
|
-
import type { HarnessAgentToolResultContinuation } from './harness-agent-tool-result-continuation';
|
|
34
34
|
import { validateLifecycleStateData } from './internal/lifecycle-state-validation';
|
|
35
35
|
import { runPrompt } from './internal/run-prompt';
|
|
36
36
|
import { getRestrictedSandboxSession } from '../utils/get-restricted-sandbox-session';
|
|
@@ -306,12 +306,8 @@ export class HarnessAgentSession {
|
|
|
306
306
|
output: OUTPUT | undefined;
|
|
307
307
|
telemetry: TelemetryOptions | undefined;
|
|
308
308
|
stopConditions: ReadonlyArray<StopCondition<TOOLS, RUNTIME_CONTEXT>>;
|
|
309
|
-
toolApprovalContinuations?:
|
|
310
|
-
|
|
311
|
-
| undefined;
|
|
312
|
-
toolResultContinuations?:
|
|
313
|
-
| readonly HarnessAgentToolResultContinuation[]
|
|
314
|
-
| undefined;
|
|
309
|
+
toolApprovalContinuations?: readonly ToolApprovalResponse[] | undefined;
|
|
310
|
+
toolResultContinuations?: readonly ToolResultPart[] | undefined;
|
|
315
311
|
}): HarnessAgentTurnResult<TOOLS, RUNTIME_CONTEXT, OUTPUT> {
|
|
316
312
|
const session = this.requireReusableSession();
|
|
317
313
|
this.requireContinuableTurn();
|
|
@@ -5,17 +5,6 @@ import type {
|
|
|
5
5
|
ToolApprovalResponse,
|
|
6
6
|
} from '@ai-sdk/provider-utils';
|
|
7
7
|
|
|
8
|
-
export type HarnessAgentToolApprovalContinuation = {
|
|
9
|
-
readonly approvalResponse: ToolApprovalResponse;
|
|
10
|
-
readonly toolCall: {
|
|
11
|
-
readonly type: 'tool-call';
|
|
12
|
-
readonly toolCallId: string;
|
|
13
|
-
readonly toolName: string;
|
|
14
|
-
readonly input: unknown;
|
|
15
|
-
readonly providerExecuted?: boolean;
|
|
16
|
-
};
|
|
17
|
-
};
|
|
18
|
-
|
|
19
8
|
/**
|
|
20
9
|
* Extract approval decisions that should continue a suspended harness turn.
|
|
21
10
|
*
|
|
@@ -28,14 +17,11 @@ export type HarnessAgentToolApprovalContinuation = {
|
|
|
28
17
|
*/
|
|
29
18
|
export function collectHarnessAgentToolApprovalContinuations(input: {
|
|
30
19
|
messages: readonly ModelMessage[];
|
|
31
|
-
}): readonly
|
|
20
|
+
}): readonly ToolApprovalResponse[] {
|
|
32
21
|
const lastMessage = input.messages.at(-1);
|
|
33
22
|
if (lastMessage?.role !== 'tool') return [];
|
|
34
23
|
|
|
35
|
-
const
|
|
36
|
-
string,
|
|
37
|
-
HarnessAgentToolApprovalContinuation['toolCall']
|
|
38
|
-
>();
|
|
24
|
+
const toolCallIds = new Set<string>();
|
|
39
25
|
const approvalRequestsByApprovalId = new Map<string, ToolApprovalRequest>();
|
|
40
26
|
for (const message of input.messages) {
|
|
41
27
|
if (message.role !== 'assistant' || typeof message.content === 'string') {
|
|
@@ -43,15 +29,7 @@ export function collectHarnessAgentToolApprovalContinuations(input: {
|
|
|
43
29
|
}
|
|
44
30
|
for (const part of message.content) {
|
|
45
31
|
if (part.type === 'tool-call') {
|
|
46
|
-
|
|
47
|
-
type: 'tool-call',
|
|
48
|
-
toolCallId: part.toolCallId,
|
|
49
|
-
toolName: part.toolName,
|
|
50
|
-
input: part.input,
|
|
51
|
-
...(part.providerExecuted !== undefined
|
|
52
|
-
? { providerExecuted: part.providerExecuted }
|
|
53
|
-
: {}),
|
|
54
|
-
});
|
|
32
|
+
toolCallIds.add(part.toolCallId);
|
|
55
33
|
} else if (part.type === 'tool-approval-request') {
|
|
56
34
|
approvalRequestsByApprovalId.set(part.approvalId, part);
|
|
57
35
|
}
|
|
@@ -65,7 +43,7 @@ export function collectHarnessAgentToolApprovalContinuations(input: {
|
|
|
65
43
|
}
|
|
66
44
|
}
|
|
67
45
|
|
|
68
|
-
const continuations:
|
|
46
|
+
const continuations: ToolApprovalResponse[] = [];
|
|
69
47
|
for (const part of lastMessage.content) {
|
|
70
48
|
if (part.type !== 'tool-approval-response') continue;
|
|
71
49
|
|
|
@@ -77,17 +55,13 @@ export function collectHarnessAgentToolApprovalContinuations(input: {
|
|
|
77
55
|
}
|
|
78
56
|
if (toolResultIds.has(approvalRequest.toolCallId)) continue;
|
|
79
57
|
|
|
80
|
-
|
|
81
|
-
if (toolCall == null) {
|
|
58
|
+
if (!toolCallIds.has(approvalRequest.toolCallId)) {
|
|
82
59
|
throw new HarnessError({
|
|
83
60
|
message: `Tool approval request '${approvalRequest.approvalId}' references unknown tool call '${approvalRequest.toolCallId}'.`,
|
|
84
61
|
});
|
|
85
62
|
}
|
|
86
63
|
|
|
87
|
-
continuations.push(
|
|
88
|
-
approvalResponse: part,
|
|
89
|
-
toolCall,
|
|
90
|
-
});
|
|
64
|
+
continuations.push(part);
|
|
91
65
|
}
|
|
92
66
|
|
|
93
67
|
return continuations;
|
|
@@ -1,62 +1,13 @@
|
|
|
1
|
-
import type { ModelMessage,
|
|
2
|
-
|
|
3
|
-
export type HarnessAgentToolResultContinuation = {
|
|
4
|
-
readonly toolCallId: string;
|
|
5
|
-
readonly output: unknown;
|
|
6
|
-
readonly isError?: boolean;
|
|
7
|
-
};
|
|
1
|
+
import type { ModelMessage, ToolResultPart } from '@ai-sdk/provider-utils';
|
|
8
2
|
|
|
9
3
|
/**
|
|
10
|
-
* Extract client-provided tool results from the trailing tool message
|
|
11
|
-
* convert AI SDK model-output wrappers back into values a harness runtime can
|
|
12
|
-
* return from its pending host-tool invocation.
|
|
4
|
+
* Extract client-provided tool results from the trailing tool message.
|
|
13
5
|
*/
|
|
14
6
|
export function collectHarnessAgentToolResultContinuations(input: {
|
|
15
7
|
messages: readonly ModelMessage[];
|
|
16
|
-
}): readonly
|
|
8
|
+
}): readonly ToolResultPart[] {
|
|
17
9
|
const lastMessage = input.messages.at(-1);
|
|
18
10
|
if (lastMessage?.role !== 'tool') return [];
|
|
19
11
|
|
|
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
|
-
}
|
|
12
|
+
return lastMessage.content.filter(part => part.type === 'tool-result');
|
|
62
13
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HarnessCapabilityUnsupportedError } from '../errors/harness-capability-unsupported-error';
|
|
2
2
|
import type {
|
|
3
|
+
HarnessV1,
|
|
3
4
|
HarnessV1BuiltinToolFiltering,
|
|
4
5
|
HarnessV1JSONSchema,
|
|
5
6
|
HarnessV1NetworkSandboxSession,
|
|
@@ -13,6 +14,8 @@ import {
|
|
|
13
14
|
type Context,
|
|
14
15
|
type Experimental_SandboxSession as SandboxSession,
|
|
15
16
|
type ModelMessage,
|
|
17
|
+
type ToolApprovalResponse,
|
|
18
|
+
type ToolResultPart,
|
|
16
19
|
type ToolSet,
|
|
17
20
|
} from '@ai-sdk/provider-utils';
|
|
18
21
|
import type {
|
|
@@ -41,14 +44,8 @@ import type {
|
|
|
41
44
|
HarnessAgentSkill,
|
|
42
45
|
HarnessAgentToolSpec,
|
|
43
46
|
} from './harness-agent-types';
|
|
44
|
-
import {
|
|
45
|
-
|
|
46
|
-
type HarnessAgentToolApprovalContinuation,
|
|
47
|
-
} from './harness-agent-tool-approval-continuation';
|
|
48
|
-
import {
|
|
49
|
-
collectHarnessAgentToolResultContinuations,
|
|
50
|
-
type HarnessAgentToolResultContinuation,
|
|
51
|
-
} from './harness-agent-tool-result-continuation';
|
|
47
|
+
import { collectHarnessAgentToolApprovalContinuations } from './harness-agent-tool-approval-continuation';
|
|
48
|
+
import { collectHarnessAgentToolResultContinuations } from './harness-agent-tool-result-continuation';
|
|
52
49
|
import {
|
|
53
50
|
applyBootstrapRecipe,
|
|
54
51
|
hashHarnessBootstrap,
|
|
@@ -86,8 +83,8 @@ export interface HarnessAgentCallExtensions {
|
|
|
86
83
|
}
|
|
87
84
|
|
|
88
85
|
type HarnessAgentContinueTurnInput = {
|
|
89
|
-
toolApprovalContinuations: readonly
|
|
90
|
-
toolResultContinuations: readonly
|
|
86
|
+
toolApprovalContinuations: readonly ToolApprovalResponse[];
|
|
87
|
+
toolResultContinuations: readonly ToolResultPart[];
|
|
91
88
|
};
|
|
92
89
|
|
|
93
90
|
type PreparedHarnessAgentTurnSettings<
|
|
@@ -114,8 +111,8 @@ type PreparedHarnessAgentContinueTurnInput<
|
|
|
114
111
|
THarness extends HarnessAgentAdapter<any>,
|
|
115
112
|
TUserTools extends ToolSet,
|
|
116
113
|
> = PreparedHarnessAgentTurnSettings<THarness, TUserTools> & {
|
|
117
|
-
toolApprovalContinuations: readonly
|
|
118
|
-
toolResultContinuations: readonly
|
|
114
|
+
toolApprovalContinuations: readonly ToolApprovalResponse[];
|
|
115
|
+
toolResultContinuations: readonly ToolResultPart[];
|
|
119
116
|
};
|
|
120
117
|
|
|
121
118
|
type HarnessAgentTurnResult<
|
|
@@ -224,6 +221,10 @@ export class HarnessAgent<
|
|
|
224
221
|
this.sandboxConfig = sandboxConfig;
|
|
225
222
|
this.id = settings.id;
|
|
226
223
|
const userTools = settings.tools ?? ({} as TUserTools);
|
|
224
|
+
assertNoReservedQuestionTool({
|
|
225
|
+
harness: settings.harness,
|
|
226
|
+
userTools,
|
|
227
|
+
});
|
|
227
228
|
this.permissionMode = resolvePermissionMode({
|
|
228
229
|
permissionMode: settings.permissionMode,
|
|
229
230
|
});
|
|
@@ -609,8 +610,8 @@ export class HarnessAgent<
|
|
|
609
610
|
*/
|
|
610
611
|
async continueGenerate(options: {
|
|
611
612
|
session: HarnessAgentSession;
|
|
612
|
-
toolApprovalContinuations?: readonly
|
|
613
|
-
toolResultContinuations?: readonly
|
|
613
|
+
toolApprovalContinuations?: readonly ToolApprovalResponse[];
|
|
614
|
+
toolResultContinuations?: readonly ToolResultPart[];
|
|
614
615
|
abortSignal?: AbortSignal;
|
|
615
616
|
}): Promise<
|
|
616
617
|
GenerateTextResult<
|
|
@@ -643,8 +644,8 @@ export class HarnessAgent<
|
|
|
643
644
|
*/
|
|
644
645
|
async continueStream(options: {
|
|
645
646
|
session: HarnessAgentSession;
|
|
646
|
-
toolApprovalContinuations?: readonly
|
|
647
|
-
toolResultContinuations?: readonly
|
|
647
|
+
toolApprovalContinuations?: readonly ToolApprovalResponse[];
|
|
648
|
+
toolResultContinuations?: readonly ToolResultPart[];
|
|
648
649
|
abortSignal?: AbortSignal;
|
|
649
650
|
}): Promise<
|
|
650
651
|
StreamTextResult<
|
|
@@ -903,8 +904,8 @@ export class HarnessAgent<
|
|
|
903
904
|
}
|
|
904
905
|
|
|
905
906
|
private _prepareContinueTurnInput(options: {
|
|
906
|
-
toolApprovalContinuations?: readonly
|
|
907
|
-
toolResultContinuations?: readonly
|
|
907
|
+
toolApprovalContinuations?: readonly ToolApprovalResponse[];
|
|
908
|
+
toolResultContinuations?: readonly ToolResultPart[];
|
|
908
909
|
}): PreparedHarnessAgentContinueTurnInput<THarness, TUserTools> {
|
|
909
910
|
return {
|
|
910
911
|
...this._prepareTurnSettings({
|
|
@@ -925,6 +926,10 @@ export class HarnessAgent<
|
|
|
925
926
|
tools?: TUserTools;
|
|
926
927
|
}): PreparedHarnessAgentTurnSettings<THarness, TUserTools> {
|
|
927
928
|
const userTools = options.tools ?? ({} as TUserTools);
|
|
929
|
+
assertNoReservedQuestionTool({
|
|
930
|
+
harness: this.settings.harness,
|
|
931
|
+
userTools,
|
|
932
|
+
});
|
|
928
933
|
const tools = {
|
|
929
934
|
...this.settings.harness.builtinTools,
|
|
930
935
|
...userTools,
|
|
@@ -1028,6 +1033,23 @@ export class HarnessAgent<
|
|
|
1028
1033
|
}
|
|
1029
1034
|
}
|
|
1030
1035
|
|
|
1036
|
+
function assertNoReservedQuestionTool(input: {
|
|
1037
|
+
harness: HarnessV1;
|
|
1038
|
+
userTools: ToolSet;
|
|
1039
|
+
}): void {
|
|
1040
|
+
if (
|
|
1041
|
+
Object.prototype.hasOwnProperty.call(
|
|
1042
|
+
input.harness.builtinTools,
|
|
1043
|
+
'askUserQuestions',
|
|
1044
|
+
) &&
|
|
1045
|
+
Object.prototype.hasOwnProperty.call(input.userTools, 'askUserQuestions')
|
|
1046
|
+
) {
|
|
1047
|
+
throw new Error(
|
|
1048
|
+
"HarnessAgent tool name 'askUserQuestions' is reserved for harness question requests.",
|
|
1049
|
+
);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1031
1053
|
/*
|
|
1032
1054
|
* `GenerateTextResult` view over a drained `streamText` run. Non-deprecated
|
|
1033
1055
|
* members derive from `steps` (the single source of truth), and the deprecated
|
|
@@ -21,6 +21,8 @@ import {
|
|
|
21
21
|
safeParseJSON,
|
|
22
22
|
type Context,
|
|
23
23
|
type Experimental_SandboxSession as SandboxSession,
|
|
24
|
+
type ToolApprovalResponse,
|
|
25
|
+
type ToolResultPart,
|
|
24
26
|
type ToolSet,
|
|
25
27
|
} from '@ai-sdk/provider-utils';
|
|
26
28
|
import {
|
|
@@ -39,8 +41,6 @@ import type {
|
|
|
39
41
|
TelemetryOptions,
|
|
40
42
|
TextStreamPart,
|
|
41
43
|
} from 'ai';
|
|
42
|
-
import type { HarnessAgentToolApprovalContinuation } from '../harness-agent-tool-approval-continuation';
|
|
43
|
-
import type { HarnessAgentToolResultContinuation } from '../harness-agent-tool-result-continuation';
|
|
44
44
|
import type { HarnessAgentToolApprovalConfiguration } from '../harness-agent-settings';
|
|
45
45
|
import { HarnessStreamTextResult } from './harness-stream-text-result';
|
|
46
46
|
import { translateStreamPart } from './translate-stream-part';
|
|
@@ -54,6 +54,29 @@ import { resolveCustomToolApproval } from './permission-mode';
|
|
|
54
54
|
import { logBridgeError } from '../../utils/bridge-diagnostics';
|
|
55
55
|
import { pinSandboxChannelEventCheckpoint } from '../../utils/sandbox-channel';
|
|
56
56
|
|
|
57
|
+
function unwrapToolResultOutput(toolResult: ToolResultPart): {
|
|
58
|
+
output: unknown;
|
|
59
|
+
isError?: boolean;
|
|
60
|
+
} {
|
|
61
|
+
switch (toolResult.output.type) {
|
|
62
|
+
case 'text':
|
|
63
|
+
case 'json':
|
|
64
|
+
return { output: toolResult.output.value };
|
|
65
|
+
case 'error-text':
|
|
66
|
+
case 'error-json':
|
|
67
|
+
return { output: toolResult.output.value, isError: true };
|
|
68
|
+
case 'execution-denied':
|
|
69
|
+
return {
|
|
70
|
+
output: {
|
|
71
|
+
type: toolResult.output.type,
|
|
72
|
+
reason: toolResult.output.reason,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
case 'content':
|
|
76
|
+
return { output: toolResult.output };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
57
80
|
/**
|
|
58
81
|
* Drive one prompt turn end-to-end:
|
|
59
82
|
* - call `session.doPromptTurn` via `toHarnessStream`
|
|
@@ -99,12 +122,8 @@ export function runPrompt<
|
|
|
99
122
|
toolApproval?: HarnessAgentToolApprovalConfiguration | undefined;
|
|
100
123
|
pendingToolApprovals?: readonly HarnessV1PendingToolApproval[];
|
|
101
124
|
pendingToolResults?: readonly HarnessV1PendingToolResult[];
|
|
102
|
-
toolApprovalContinuations?:
|
|
103
|
-
|
|
104
|
-
| undefined;
|
|
105
|
-
toolResultContinuations?:
|
|
106
|
-
| readonly HarnessAgentToolResultContinuation[]
|
|
107
|
-
| undefined;
|
|
125
|
+
toolApprovalContinuations?: readonly ToolApprovalResponse[] | undefined;
|
|
126
|
+
toolResultContinuations?: readonly ToolResultPart[] | undefined;
|
|
108
127
|
onPendingToolApproval?: (approval: HarnessV1PendingToolApproval) => void;
|
|
109
128
|
onToolApprovalSettled?: (approvalId: string) => void;
|
|
110
129
|
onPendingToolResult?: (pendingResult: HarnessV1PendingToolResult) => void;
|
|
@@ -267,7 +286,7 @@ export function runPrompt<
|
|
|
267
286
|
);
|
|
268
287
|
const continuationsByApprovalId = new Map(
|
|
269
288
|
(input.toolApprovalContinuations ?? []).map(continuation => [
|
|
270
|
-
continuation.
|
|
289
|
+
continuation.approvalId,
|
|
271
290
|
continuation,
|
|
272
291
|
]),
|
|
273
292
|
);
|
|
@@ -433,15 +452,16 @@ export function runPrompt<
|
|
|
433
452
|
};
|
|
434
453
|
const enqueueApprovalResponse = (
|
|
435
454
|
approval: HarnessV1PendingToolApproval,
|
|
436
|
-
continuation:
|
|
455
|
+
continuation: ToolApprovalResponse,
|
|
456
|
+
toolCall: ToolCallTextStreamPart,
|
|
437
457
|
): void => {
|
|
438
458
|
result.enqueueContinuation({
|
|
439
459
|
type: 'tool-approval-response',
|
|
440
460
|
approvalId: approval.approvalId,
|
|
441
|
-
toolCall
|
|
442
|
-
approved: continuation.
|
|
443
|
-
...(continuation.
|
|
444
|
-
? { reason: continuation.
|
|
461
|
+
toolCall,
|
|
462
|
+
approved: continuation.approved,
|
|
463
|
+
...(continuation.reason !== undefined
|
|
464
|
+
? { reason: continuation.reason }
|
|
445
465
|
: {}),
|
|
446
466
|
...(approval.providerExecuted !== undefined
|
|
447
467
|
? { providerExecuted: approval.providerExecuted }
|
|
@@ -457,6 +477,9 @@ export function runPrompt<
|
|
|
457
477
|
toolCallId: options.toolCall.toolCallId,
|
|
458
478
|
toolName: options.toolCall.toolName,
|
|
459
479
|
input: options.toolCall.input,
|
|
480
|
+
...(options.toolCall.providerMetadata !== undefined
|
|
481
|
+
? { providerOptions: options.toolCall.providerMetadata }
|
|
482
|
+
: {}),
|
|
460
483
|
} satisfies HarnessV1PendingToolResult);
|
|
461
484
|
pendingResultsByToolCallId.set(pendingResult.toolCallId, pendingResult);
|
|
462
485
|
onPendingToolResult(pendingResult);
|
|
@@ -464,19 +487,28 @@ export function runPrompt<
|
|
|
464
487
|
};
|
|
465
488
|
const processPendingToolResultContinuation = async (
|
|
466
489
|
pendingResult: HarnessV1PendingToolResult,
|
|
467
|
-
continuation:
|
|
490
|
+
continuation: ToolResultPart,
|
|
468
491
|
): Promise<void> => {
|
|
492
|
+
const result = unwrapToolResultOutput(continuation);
|
|
469
493
|
onToolResultSettled(pendingResult.toolCallId);
|
|
470
494
|
pendingResultsByToolCallId.delete(pendingResult.toolCallId);
|
|
471
495
|
settledHostToolCallIds.add(pendingResult.toolCallId);
|
|
472
496
|
await control.submitToolResult({
|
|
473
497
|
toolCallId: pendingResult.toolCallId,
|
|
474
|
-
output:
|
|
475
|
-
isError:
|
|
498
|
+
output: result.output,
|
|
499
|
+
isError: result.isError,
|
|
500
|
+
toolResult: {
|
|
501
|
+
...continuation,
|
|
502
|
+
toolName: pendingResult.toolName,
|
|
503
|
+
...(continuation.providerOptions == null &&
|
|
504
|
+
pendingResult.providerOptions != null
|
|
505
|
+
? { providerOptions: pendingResult.providerOptions }
|
|
506
|
+
: {}),
|
|
507
|
+
},
|
|
476
508
|
});
|
|
477
509
|
};
|
|
478
510
|
const enqueueHostToolOutcome = (options: {
|
|
479
|
-
toolCall:
|
|
511
|
+
toolCall: ToolCallTextStreamPart;
|
|
480
512
|
outcome: HostToolOutcome;
|
|
481
513
|
}): void => {
|
|
482
514
|
if (options.outcome.ok) {
|
|
@@ -512,9 +544,30 @@ export function runPrompt<
|
|
|
512
544
|
};
|
|
513
545
|
const processPendingApprovalContinuation = async (
|
|
514
546
|
approval: HarnessV1PendingToolApproval,
|
|
515
|
-
continuation:
|
|
547
|
+
continuation: ToolApprovalResponse,
|
|
516
548
|
): Promise<'continued' | 'awaiting-tool-result'> => {
|
|
517
|
-
|
|
549
|
+
const rawToolCall =
|
|
550
|
+
rawToolCallsByToolCallId.get(approval.toolCallId) ??
|
|
551
|
+
({
|
|
552
|
+
type: 'tool-call',
|
|
553
|
+
toolCallId: approval.toolCallId,
|
|
554
|
+
toolName: approval.toolName,
|
|
555
|
+
input: approval.input,
|
|
556
|
+
providerExecuted: approval.providerExecuted,
|
|
557
|
+
nativeName: approval.nativeName,
|
|
558
|
+
} satisfies Extract<HarnessV1StreamPart, { type: 'tool-call' }>);
|
|
559
|
+
const parsedInput = await safeParseJSON({ text: rawToolCall.input });
|
|
560
|
+
const toolCall: ToolCallTextStreamPart = {
|
|
561
|
+
type: 'tool-call',
|
|
562
|
+
toolCallId: rawToolCall.toolCallId,
|
|
563
|
+
toolName: rawToolCall.toolName,
|
|
564
|
+
input: parsedInput.success ? parsedInput.value : rawToolCall.input,
|
|
565
|
+
...(rawToolCall.providerExecuted !== undefined
|
|
566
|
+
? { providerExecuted: rawToolCall.providerExecuted }
|
|
567
|
+
: {}),
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
enqueueApprovalResponse(approval, continuation, toolCall);
|
|
518
571
|
onToolApprovalSettled(approval.approvalId);
|
|
519
572
|
pendingApprovalsByApprovalId.delete(approval.approvalId);
|
|
520
573
|
pendingApprovalsByToolCallId.delete(approval.toolCallId);
|
|
@@ -528,33 +581,24 @@ export function runPrompt<
|
|
|
528
581
|
}
|
|
529
582
|
await control.submitToolApproval({
|
|
530
583
|
approvalId: approval.approvalId,
|
|
531
|
-
approved: continuation.
|
|
532
|
-
reason: continuation.
|
|
584
|
+
approved: continuation.approved,
|
|
585
|
+
reason: continuation.reason,
|
|
533
586
|
});
|
|
534
587
|
return 'continued';
|
|
535
588
|
}
|
|
536
589
|
|
|
537
590
|
settledHostToolCallIds.add(approval.toolCallId);
|
|
538
|
-
if (!continuation.
|
|
591
|
+
if (!continuation.approved) {
|
|
539
592
|
await control.submitToolResult({
|
|
540
593
|
toolCallId: approval.toolCallId,
|
|
541
594
|
output: {
|
|
542
595
|
type: 'execution-denied',
|
|
543
|
-
reason: continuation.
|
|
596
|
+
reason: continuation.reason,
|
|
544
597
|
},
|
|
545
598
|
});
|
|
546
599
|
return 'continued';
|
|
547
600
|
}
|
|
548
601
|
|
|
549
|
-
const rawToolCall =
|
|
550
|
-
rawToolCallsByToolCallId.get(approval.toolCallId) ??
|
|
551
|
-
({
|
|
552
|
-
type: 'tool-call',
|
|
553
|
-
toolCallId: approval.toolCallId,
|
|
554
|
-
toolName: approval.toolName,
|
|
555
|
-
input: approval.input,
|
|
556
|
-
} satisfies Extract<HarnessV1StreamPart, { type: 'tool-call' }>);
|
|
557
|
-
|
|
558
602
|
await telemetry.start(input.model);
|
|
559
603
|
await telemetry.toolStart({
|
|
560
604
|
toolCallId: rawToolCall.toolCallId,
|
|
@@ -597,7 +641,7 @@ export function runPrompt<
|
|
|
597
641
|
return 'awaiting-tool-result';
|
|
598
642
|
}
|
|
599
643
|
enqueueHostToolOutcome({
|
|
600
|
-
toolCall
|
|
644
|
+
toolCall,
|
|
601
645
|
outcome: execution.outcome,
|
|
602
646
|
});
|
|
603
647
|
await telemetry.toolEnd(rawToolCall.toolCallId, execution.outcome);
|
|
@@ -940,7 +984,16 @@ export function runPrompt<
|
|
|
940
984
|
`Harness '${input.harness.harnessId}' could not find parsed tool call '${toolCall.toolCallId}' for custom tool approval.`,
|
|
941
985
|
);
|
|
942
986
|
}
|
|
943
|
-
|
|
987
|
+
const isClientExecutedBuiltin =
|
|
988
|
+
toolCall.toolName === 'askUserQuestions' &&
|
|
989
|
+
Object.prototype.hasOwnProperty.call(
|
|
990
|
+
input.harness.builtinTools,
|
|
991
|
+
toolCall.toolName,
|
|
992
|
+
);
|
|
993
|
+
if (
|
|
994
|
+
!isClientExecutedBuiltin &&
|
|
995
|
+
!hasTool({ tools: activeTools, toolName: toolCall.toolName })
|
|
996
|
+
) {
|
|
944
997
|
const output = {
|
|
945
998
|
type: 'execution-denied',
|
|
946
999
|
reason: getHarnessV1BuiltinToolFilteringDenialReason({
|
|
@@ -954,6 +1007,18 @@ export function runPrompt<
|
|
|
954
1007
|
await telemetry.toolEnd(toolCall.toolCallId, { ok: true, output });
|
|
955
1008
|
continue;
|
|
956
1009
|
}
|
|
1010
|
+
if (isClientExecutedBuiltin) {
|
|
1011
|
+
recordPendingToolResult({ toolCall });
|
|
1012
|
+
if (
|
|
1013
|
+
expectedStepToolCallCount != null &&
|
|
1014
|
+
observedStepToolCallCount < expectedStepToolCallCount
|
|
1015
|
+
) {
|
|
1016
|
+
pauseAfterStepToolCalls = true;
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
await finishForHostInputPause({ completeCurrentStep: true });
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
957
1022
|
const customToolApprovalDecision = resolveCustomToolApproval({
|
|
958
1023
|
toolName: toolCall.toolName,
|
|
959
1024
|
toolApproval: input.toolApproval,
|