@ai-sdk/harness 1.0.100 → 1.0.102
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 +30 -0
- package/agent/index.ts +2 -8
- package/dist/agent/index.d.ts +128 -48
- package/dist/agent/index.js +586 -512
- 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 +84 -2
- package/dist/index.js +286 -229
- package/dist/index.js.map +1 -1
- package/dist/utils/index.d.ts +17 -3
- package/dist/utils/index.js +323 -19
- package/dist/utils/index.js.map +1 -1
- package/package.json +3 -3
- package/src/agent/harness-agent-session.ts +9 -8
- package/src/agent/harness-agent-settings.ts +82 -3
- 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 +107 -18
- package/src/agent/internal/harness-stream-text-result.ts +33 -13
- package/src/agent/internal/run-prompt.ts +309 -122
- package/src/agent/internal/turn-telemetry.ts +293 -444
- package/src/bridge/index.ts +82 -11
- 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-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/harness-v1-session.ts +5 -0
- package/src/v1/index.ts +9 -0
|
@@ -17,10 +17,13 @@ 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,
|
|
23
24
|
type Experimental_SandboxSession as SandboxSession,
|
|
25
|
+
type ToolApprovalResponse,
|
|
26
|
+
type ToolResultPart,
|
|
24
27
|
type ToolSet,
|
|
25
28
|
} from '@ai-sdk/provider-utils';
|
|
26
29
|
import {
|
|
@@ -29,7 +32,7 @@ import {
|
|
|
29
32
|
type LanguageModelV4ToolCall,
|
|
30
33
|
type LanguageModelV4Usage,
|
|
31
34
|
} from '@ai-sdk/provider';
|
|
32
|
-
import { parseToolCall } from 'ai/internal';
|
|
35
|
+
import { asLanguageModelUsage, parseToolCall } from 'ai/internal';
|
|
33
36
|
import type {
|
|
34
37
|
ContentPart,
|
|
35
38
|
OutputInterface as Output,
|
|
@@ -38,22 +41,46 @@ import type {
|
|
|
38
41
|
StopCondition,
|
|
39
42
|
TelemetryOptions,
|
|
40
43
|
TextStreamPart,
|
|
44
|
+
TypedToolCall,
|
|
45
|
+
TypedToolError,
|
|
46
|
+
TypedToolResult,
|
|
41
47
|
} from 'ai';
|
|
42
|
-
import type { HarnessAgentToolApprovalContinuation } from '../harness-agent-tool-approval-continuation';
|
|
43
|
-
import type { HarnessAgentToolResultContinuation } from '../harness-agent-tool-result-continuation';
|
|
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';
|
|
55
59
|
import { pinSandboxChannelEventCheckpoint } from '../../utils/sandbox-channel';
|
|
56
60
|
|
|
61
|
+
function unwrapToolResultOutput(toolResult: ToolResultPart): {
|
|
62
|
+
output: unknown;
|
|
63
|
+
isError?: boolean;
|
|
64
|
+
} {
|
|
65
|
+
switch (toolResult.output.type) {
|
|
66
|
+
case 'text':
|
|
67
|
+
case 'json':
|
|
68
|
+
return { output: toolResult.output.value };
|
|
69
|
+
case 'error-text':
|
|
70
|
+
case 'error-json':
|
|
71
|
+
return { output: toolResult.output.value, isError: true };
|
|
72
|
+
case 'execution-denied':
|
|
73
|
+
return {
|
|
74
|
+
output: {
|
|
75
|
+
type: toolResult.output.type,
|
|
76
|
+
reason: toolResult.output.reason,
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
case 'content':
|
|
80
|
+
return { output: toolResult.output };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
57
84
|
/**
|
|
58
85
|
* Drive one prompt turn end-to-end:
|
|
59
86
|
* - call `session.doPromptTurn` via `toHarnessStream`
|
|
@@ -95,16 +122,13 @@ export function runPrompt<
|
|
|
95
122
|
responseFormat?: HarnessV1ResponseFormat | undefined;
|
|
96
123
|
output?: OUTPUT | undefined;
|
|
97
124
|
telemetry?: TelemetryOptions | undefined;
|
|
125
|
+
callbacks?: HarnessAgentLifecycleCallbacks<TOOLS, RUNTIME_CONTEXT, OUTPUT>;
|
|
98
126
|
stopConditions?: ReadonlyArray<StopCondition<TOOLS, RUNTIME_CONTEXT>>;
|
|
99
127
|
toolApproval?: HarnessAgentToolApprovalConfiguration | undefined;
|
|
100
128
|
pendingToolApprovals?: readonly HarnessV1PendingToolApproval[];
|
|
101
129
|
pendingToolResults?: readonly HarnessV1PendingToolResult[];
|
|
102
|
-
toolApprovalContinuations?:
|
|
103
|
-
|
|
104
|
-
| undefined;
|
|
105
|
-
toolResultContinuations?:
|
|
106
|
-
| readonly HarnessAgentToolResultContinuation[]
|
|
107
|
-
| undefined;
|
|
130
|
+
toolApprovalContinuations?: readonly ToolApprovalResponse[] | undefined;
|
|
131
|
+
toolResultContinuations?: readonly ToolResultPart[] | undefined;
|
|
108
132
|
onPendingToolApproval?: (approval: HarnessV1PendingToolApproval) => void;
|
|
109
133
|
onToolApprovalSettled?: (approvalId: string) => void;
|
|
110
134
|
onPendingToolResult?: (pendingResult: HarnessV1PendingToolResult) => void;
|
|
@@ -122,13 +146,15 @@ export function runPrompt<
|
|
|
122
146
|
result: HarnessStreamTextResult<TOOLS, RUNTIME_CONTEXT, OUTPUT>;
|
|
123
147
|
done: Promise<void>;
|
|
124
148
|
} {
|
|
149
|
+
const callId = generateId();
|
|
150
|
+
const toolsContext = {} as InferToolSetContext<TOOLS>;
|
|
125
151
|
const result = new HarnessStreamTextResult<TOOLS, RUNTIME_CONTEXT, OUTPUT>({
|
|
126
152
|
tools: input.tools,
|
|
127
153
|
runtimeContext: input.runtimeContext,
|
|
128
|
-
|
|
129
|
-
toolsContext: undefined as never,
|
|
154
|
+
toolsContext,
|
|
130
155
|
harnessId: input.harness.harnessId,
|
|
131
|
-
|
|
156
|
+
callId,
|
|
157
|
+
modelId: input.model ?? '',
|
|
132
158
|
output: input.output,
|
|
133
159
|
});
|
|
134
160
|
const pendingToolApprovals = input.pendingToolApprovals ?? [];
|
|
@@ -151,16 +177,23 @@ export function runPrompt<
|
|
|
151
177
|
})),
|
|
152
178
|
);
|
|
153
179
|
|
|
154
|
-
const
|
|
180
|
+
const lifecycle = createTurnLifecycle({
|
|
181
|
+
callId,
|
|
155
182
|
telemetry: input.telemetry,
|
|
183
|
+
callbacks: input.callbacks ?? {},
|
|
156
184
|
harnessId: input.harness.harnessId,
|
|
157
185
|
modelId: input.model,
|
|
158
186
|
instructions: input.instructions,
|
|
159
187
|
tools: input.tools,
|
|
160
188
|
activeToolNames,
|
|
161
189
|
toolSpecs: input.toolSpecs,
|
|
162
|
-
|
|
190
|
+
messages:
|
|
191
|
+
input.prompt == null
|
|
192
|
+
? []
|
|
193
|
+
: [{ role: 'user', content: promptToText(input.prompt) }],
|
|
163
194
|
runtimeContext: input.runtimeContext,
|
|
195
|
+
toolsContext,
|
|
196
|
+
output: input.output,
|
|
164
197
|
});
|
|
165
198
|
|
|
166
199
|
/*
|
|
@@ -224,7 +257,7 @@ export function runPrompt<
|
|
|
224
257
|
},
|
|
225
258
|
});
|
|
226
259
|
} catch (err) {
|
|
227
|
-
await
|
|
260
|
+
await lifecycle.error(err);
|
|
228
261
|
logBridgeError({
|
|
229
262
|
harnessId: input.harness.harnessId,
|
|
230
263
|
sessionId: input.session.sessionId,
|
|
@@ -267,7 +300,7 @@ export function runPrompt<
|
|
|
267
300
|
);
|
|
268
301
|
const continuationsByApprovalId = new Map(
|
|
269
302
|
(input.toolApprovalContinuations ?? []).map(continuation => [
|
|
270
|
-
continuation.
|
|
303
|
+
continuation.approvalId,
|
|
271
304
|
continuation,
|
|
272
305
|
]),
|
|
273
306
|
);
|
|
@@ -321,25 +354,59 @@ export function runPrompt<
|
|
|
321
354
|
pendingStopBoundary = undefined;
|
|
322
355
|
};
|
|
323
356
|
|
|
324
|
-
// Accumulate the model
|
|
325
|
-
//
|
|
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.
|
|
326
360
|
let stepText = '';
|
|
327
361
|
let stepReasoning = '';
|
|
328
|
-
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
|
+
};
|
|
329
390
|
let expectedStepToolCallCount: number | undefined;
|
|
330
391
|
let observedStepToolCallCount = 0;
|
|
331
392
|
let pauseAfterStepToolCalls = false;
|
|
332
|
-
const
|
|
333
|
-
const parts:
|
|
393
|
+
const buildModelCallContent = (): ContentPart<TOOLS>[] => {
|
|
394
|
+
const parts: ContentPart<TOOLS>[] = [];
|
|
334
395
|
if (stepText) parts.push({ type: 'text', text: stepText });
|
|
335
396
|
if (stepReasoning) parts.push({ type: 'reasoning', text: stepReasoning });
|
|
336
397
|
parts.push(...stepToolCalls);
|
|
398
|
+
parts.push(...stepApprovalRequests);
|
|
399
|
+
parts.push(...stepProviderToolResults);
|
|
337
400
|
return parts;
|
|
338
401
|
};
|
|
339
402
|
const resetStepContent = (): void => {
|
|
340
403
|
stepText = '';
|
|
341
404
|
stepReasoning = '';
|
|
342
405
|
stepToolCalls = [];
|
|
406
|
+
stepProviderToolResults = [];
|
|
407
|
+
stepApprovalRequests = [];
|
|
408
|
+
bufferedToolOutcomes = [];
|
|
409
|
+
toolExecutions.clear();
|
|
343
410
|
expectedStepToolCallCount = undefined;
|
|
344
411
|
observedStepToolCallCount = 0;
|
|
345
412
|
pauseAfterStepToolCalls = false;
|
|
@@ -366,13 +433,13 @@ export function runPrompt<
|
|
|
366
433
|
usage: LanguageModelV4Usage;
|
|
367
434
|
providerMetadata: ProviderMetadata | undefined;
|
|
368
435
|
}): Promise<StepResult<TOOLS, RUNTIME_CONTEXT>> => {
|
|
369
|
-
await
|
|
370
|
-
finishReason: input.finishReason,
|
|
371
|
-
usage: input.usage,
|
|
436
|
+
await lifecycle.languageModelCallEnd({
|
|
437
|
+
finishReason: input.finishReason.unified,
|
|
438
|
+
usage: asLanguageModelUsage(input.usage),
|
|
372
439
|
providerMetadata: input.providerMetadata,
|
|
373
|
-
content:
|
|
440
|
+
content: buildModelCallContent(),
|
|
374
441
|
});
|
|
375
|
-
|
|
442
|
+
await publishToolExecutions();
|
|
376
443
|
const step = result.finishStep({
|
|
377
444
|
finishReason: input.finishReason,
|
|
378
445
|
usage: input.usage,
|
|
@@ -380,6 +447,8 @@ export function runPrompt<
|
|
|
380
447
|
warnings: [],
|
|
381
448
|
});
|
|
382
449
|
completedSteps.push(step);
|
|
450
|
+
await lifecycle.stepEnd(step);
|
|
451
|
+
resetStepContent();
|
|
383
452
|
return step;
|
|
384
453
|
};
|
|
385
454
|
const finishForHostInputPause = async (options: {
|
|
@@ -392,10 +461,12 @@ export function runPrompt<
|
|
|
392
461
|
usage: zeroUsage,
|
|
393
462
|
providerMetadata: undefined,
|
|
394
463
|
});
|
|
464
|
+
} else {
|
|
465
|
+
await publishToolExecutions();
|
|
395
466
|
}
|
|
396
|
-
await
|
|
397
|
-
|
|
398
|
-
usage: zeroUsage,
|
|
467
|
+
await lifecycle.end({
|
|
468
|
+
steps: completedSteps,
|
|
469
|
+
usage: asLanguageModelUsage(zeroUsage),
|
|
399
470
|
});
|
|
400
471
|
await result.finish();
|
|
401
472
|
};
|
|
@@ -404,14 +475,16 @@ export function runPrompt<
|
|
|
404
475
|
toolCall: ToolCallTextStreamPart;
|
|
405
476
|
isAutomatic?: boolean;
|
|
406
477
|
}): void => {
|
|
407
|
-
|
|
478
|
+
const part = {
|
|
408
479
|
type: 'tool-approval-request',
|
|
409
480
|
approvalId: approval.approvalId,
|
|
410
481
|
toolCall: approval.toolCall,
|
|
411
482
|
...(approval.isAutomatic !== undefined
|
|
412
483
|
? { isAutomatic: approval.isAutomatic }
|
|
413
484
|
: {}),
|
|
414
|
-
} as TextStreamPart<TOOLS
|
|
485
|
+
} as TextStreamPart<TOOLS>;
|
|
486
|
+
result.enqueue(part);
|
|
487
|
+
stepApprovalRequests.push(part as ContentPart<TOOLS>);
|
|
415
488
|
};
|
|
416
489
|
const enqueueAutomaticApprovalResponse = (input: {
|
|
417
490
|
approvalId: string;
|
|
@@ -433,15 +506,16 @@ export function runPrompt<
|
|
|
433
506
|
};
|
|
434
507
|
const enqueueApprovalResponse = (
|
|
435
508
|
approval: HarnessV1PendingToolApproval,
|
|
436
|
-
continuation:
|
|
509
|
+
continuation: ToolApprovalResponse,
|
|
510
|
+
toolCall: ToolCallTextStreamPart,
|
|
437
511
|
): void => {
|
|
438
512
|
result.enqueueContinuation({
|
|
439
513
|
type: 'tool-approval-response',
|
|
440
514
|
approvalId: approval.approvalId,
|
|
441
|
-
toolCall
|
|
442
|
-
approved: continuation.
|
|
443
|
-
...(continuation.
|
|
444
|
-
? { reason: continuation.
|
|
515
|
+
toolCall,
|
|
516
|
+
approved: continuation.approved,
|
|
517
|
+
...(continuation.reason !== undefined
|
|
518
|
+
? { reason: continuation.reason }
|
|
445
519
|
: {}),
|
|
446
520
|
...(approval.providerExecuted !== undefined
|
|
447
521
|
? { providerExecuted: approval.providerExecuted }
|
|
@@ -457,6 +531,9 @@ export function runPrompt<
|
|
|
457
531
|
toolCallId: options.toolCall.toolCallId,
|
|
458
532
|
toolName: options.toolCall.toolName,
|
|
459
533
|
input: options.toolCall.input,
|
|
534
|
+
...(options.toolCall.providerMetadata !== undefined
|
|
535
|
+
? { providerOptions: options.toolCall.providerMetadata }
|
|
536
|
+
: {}),
|
|
460
537
|
} satisfies HarnessV1PendingToolResult);
|
|
461
538
|
pendingResultsByToolCallId.set(pendingResult.toolCallId, pendingResult);
|
|
462
539
|
onPendingToolResult(pendingResult);
|
|
@@ -464,19 +541,28 @@ export function runPrompt<
|
|
|
464
541
|
};
|
|
465
542
|
const processPendingToolResultContinuation = async (
|
|
466
543
|
pendingResult: HarnessV1PendingToolResult,
|
|
467
|
-
continuation:
|
|
544
|
+
continuation: ToolResultPart,
|
|
468
545
|
): Promise<void> => {
|
|
546
|
+
const result = unwrapToolResultOutput(continuation);
|
|
469
547
|
onToolResultSettled(pendingResult.toolCallId);
|
|
470
548
|
pendingResultsByToolCallId.delete(pendingResult.toolCallId);
|
|
471
549
|
settledHostToolCallIds.add(pendingResult.toolCallId);
|
|
472
550
|
await control.submitToolResult({
|
|
473
551
|
toolCallId: pendingResult.toolCallId,
|
|
474
|
-
output:
|
|
475
|
-
isError:
|
|
552
|
+
output: result.output,
|
|
553
|
+
isError: result.isError,
|
|
554
|
+
toolResult: {
|
|
555
|
+
...continuation,
|
|
556
|
+
toolName: pendingResult.toolName,
|
|
557
|
+
...(continuation.providerOptions == null &&
|
|
558
|
+
pendingResult.providerOptions != null
|
|
559
|
+
? { providerOptions: pendingResult.providerOptions }
|
|
560
|
+
: {}),
|
|
561
|
+
},
|
|
476
562
|
});
|
|
477
563
|
};
|
|
478
564
|
const enqueueHostToolOutcome = (options: {
|
|
479
|
-
toolCall:
|
|
565
|
+
toolCall: ToolCallTextStreamPart;
|
|
480
566
|
outcome: HostToolOutcome;
|
|
481
567
|
}): void => {
|
|
482
568
|
if (options.outcome.ok) {
|
|
@@ -512,9 +598,30 @@ export function runPrompt<
|
|
|
512
598
|
};
|
|
513
599
|
const processPendingApprovalContinuation = async (
|
|
514
600
|
approval: HarnessV1PendingToolApproval,
|
|
515
|
-
continuation:
|
|
601
|
+
continuation: ToolApprovalResponse,
|
|
516
602
|
): Promise<'continued' | 'awaiting-tool-result'> => {
|
|
517
|
-
|
|
603
|
+
const rawToolCall =
|
|
604
|
+
rawToolCallsByToolCallId.get(approval.toolCallId) ??
|
|
605
|
+
({
|
|
606
|
+
type: 'tool-call',
|
|
607
|
+
toolCallId: approval.toolCallId,
|
|
608
|
+
toolName: approval.toolName,
|
|
609
|
+
input: approval.input,
|
|
610
|
+
providerExecuted: approval.providerExecuted,
|
|
611
|
+
nativeName: approval.nativeName,
|
|
612
|
+
} satisfies Extract<HarnessV1StreamPart, { type: 'tool-call' }>);
|
|
613
|
+
const parsedInput = await safeParseJSON({ text: rawToolCall.input });
|
|
614
|
+
const toolCall: ToolCallTextStreamPart = {
|
|
615
|
+
type: 'tool-call',
|
|
616
|
+
toolCallId: rawToolCall.toolCallId,
|
|
617
|
+
toolName: rawToolCall.toolName,
|
|
618
|
+
input: parsedInput.success ? parsedInput.value : rawToolCall.input,
|
|
619
|
+
...(rawToolCall.providerExecuted !== undefined
|
|
620
|
+
? { providerExecuted: rawToolCall.providerExecuted }
|
|
621
|
+
: {}),
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
enqueueApprovalResponse(approval, continuation, toolCall);
|
|
518
625
|
onToolApprovalSettled(approval.approvalId);
|
|
519
626
|
pendingApprovalsByApprovalId.delete(approval.approvalId);
|
|
520
627
|
pendingApprovalsByToolCallId.delete(approval.toolCallId);
|
|
@@ -528,43 +635,33 @@ export function runPrompt<
|
|
|
528
635
|
}
|
|
529
636
|
await control.submitToolApproval({
|
|
530
637
|
approvalId: approval.approvalId,
|
|
531
|
-
approved: continuation.
|
|
532
|
-
reason: continuation.
|
|
638
|
+
approved: continuation.approved,
|
|
639
|
+
reason: continuation.reason,
|
|
533
640
|
});
|
|
534
641
|
return 'continued';
|
|
535
642
|
}
|
|
536
643
|
|
|
537
644
|
settledHostToolCallIds.add(approval.toolCallId);
|
|
538
|
-
if (!continuation.
|
|
645
|
+
if (!continuation.approved) {
|
|
539
646
|
await control.submitToolResult({
|
|
540
647
|
toolCallId: approval.toolCallId,
|
|
541
648
|
output: {
|
|
542
649
|
type: 'execution-denied',
|
|
543
|
-
reason: continuation.
|
|
650
|
+
reason: continuation.reason,
|
|
544
651
|
},
|
|
545
652
|
});
|
|
546
653
|
return 'continued';
|
|
547
654
|
}
|
|
548
655
|
|
|
549
|
-
|
|
550
|
-
|
|
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
|
-
await telemetry.start(input.model);
|
|
559
|
-
await telemetry.toolStart({
|
|
560
|
-
toolCallId: rawToolCall.toolCallId,
|
|
561
|
-
toolName: rawToolCall.toolName,
|
|
562
|
-
input: rawToolCall.input,
|
|
656
|
+
await lifecycle.start(input.model);
|
|
657
|
+
toolExecutions.set(rawToolCall.toolCallId, {
|
|
658
|
+
toolCall: toolCall as TypedToolCall<TOOLS>,
|
|
563
659
|
});
|
|
660
|
+
const executionStartedAt = Date.now();
|
|
564
661
|
const execution = await maybeExecuteHostTool({
|
|
565
662
|
event: rawToolCall,
|
|
566
663
|
tools: activeTools,
|
|
567
|
-
wrappedExecuteTool:
|
|
664
|
+
wrappedExecuteTool: lifecycle.executeTool,
|
|
568
665
|
sandboxSession: input.sandboxSession,
|
|
569
666
|
abortSignal: input.abortSignal,
|
|
570
667
|
control,
|
|
@@ -581,14 +678,16 @@ export function runPrompt<
|
|
|
581
678
|
},
|
|
582
679
|
input.sessionWorkDir,
|
|
583
680
|
) as Extract<HarnessV1StreamPart, { type: 'tool-result' }>;
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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
|
+
});
|
|
592
691
|
},
|
|
593
692
|
});
|
|
594
693
|
if (!execution.executed) {
|
|
@@ -596,11 +695,19 @@ export function runPrompt<
|
|
|
596
695
|
await finishForHostInputPause({ completeCurrentStep: false });
|
|
597
696
|
return 'awaiting-tool-result';
|
|
598
697
|
}
|
|
599
|
-
|
|
600
|
-
|
|
698
|
+
const toolExecution = toolExecutions.get(rawToolCall.toolCallId)!;
|
|
699
|
+
toolExecution.toolOutput = toToolOutput({
|
|
700
|
+
toolCall,
|
|
601
701
|
outcome: execution.outcome,
|
|
602
702
|
});
|
|
603
|
-
|
|
703
|
+
toolExecution.toolExecutionMs = Date.now() - executionStartedAt;
|
|
704
|
+
bufferedToolOutcomes.push(() => {
|
|
705
|
+
enqueueHostToolOutcome({
|
|
706
|
+
toolCall,
|
|
707
|
+
outcome: execution.outcome,
|
|
708
|
+
});
|
|
709
|
+
});
|
|
710
|
+
await publishToolExecutions();
|
|
604
711
|
return 'continued';
|
|
605
712
|
};
|
|
606
713
|
|
|
@@ -659,9 +766,12 @@ export function runPrompt<
|
|
|
659
766
|
).some(Boolean)
|
|
660
767
|
) {
|
|
661
768
|
await input.onStopConditionMet?.();
|
|
662
|
-
const {
|
|
769
|
+
const { usage } = pendingStopBoundary;
|
|
663
770
|
releasePendingStopBoundary();
|
|
664
|
-
await
|
|
771
|
+
await lifecycle.end({
|
|
772
|
+
steps: completedSteps,
|
|
773
|
+
usage: asLanguageModelUsage(usage),
|
|
774
|
+
});
|
|
665
775
|
await result.finish();
|
|
666
776
|
return;
|
|
667
777
|
} else {
|
|
@@ -672,7 +782,9 @@ export function runPrompt<
|
|
|
672
782
|
// Begin the operation span on stream-start, using the runtime-resolved
|
|
673
783
|
// model the adapter reports (falling back to the requested turn model).
|
|
674
784
|
if (value.type === 'stream-start') {
|
|
675
|
-
|
|
785
|
+
const modelId = value.modelId ?? input.model;
|
|
786
|
+
if (modelId != null) result.setModelId(modelId);
|
|
787
|
+
await lifecycle.start(modelId);
|
|
676
788
|
}
|
|
677
789
|
|
|
678
790
|
// Open a step span lazily before the first content of each step.
|
|
@@ -682,7 +794,7 @@ export function runPrompt<
|
|
|
682
794
|
value.type !== 'finish' &&
|
|
683
795
|
value.type !== 'error'
|
|
684
796
|
) {
|
|
685
|
-
await
|
|
797
|
+
await lifecycle.ensureStepOpen();
|
|
686
798
|
}
|
|
687
799
|
|
|
688
800
|
if (
|
|
@@ -730,6 +842,7 @@ export function runPrompt<
|
|
|
730
842
|
|
|
731
843
|
if (displayValue.type === 'finish-step' && closingResumedStep) {
|
|
732
844
|
closingResumedStep = false;
|
|
845
|
+
await publishToolExecutions();
|
|
733
846
|
resetStepContent();
|
|
734
847
|
result.discardCurrentStepContent();
|
|
735
848
|
continue;
|
|
@@ -780,7 +893,7 @@ export function runPrompt<
|
|
|
780
893
|
// Telemetry and stderr diagnostics keep the raw error (absolute
|
|
781
894
|
// paths help debugging); the consumer-facing settle uses the
|
|
782
895
|
// workDir-stripped one, like every other forwarded part.
|
|
783
|
-
await
|
|
896
|
+
await lifecycle.error(value.error);
|
|
784
897
|
// A turn the caller itself aborted ends with an error-shaped part by
|
|
785
898
|
// construction; diagnosing the caller's own signal to stderr reads
|
|
786
899
|
// as a malfunction. `settleFailure` below still reports it as an
|
|
@@ -797,12 +910,16 @@ export function runPrompt<
|
|
|
797
910
|
return;
|
|
798
911
|
}
|
|
799
912
|
|
|
800
|
-
|
|
801
|
-
for (const part of translateStreamPart<TOOLS>(
|
|
913
|
+
const translatedParts = translateStreamPart<TOOLS>(
|
|
802
914
|
displayValue,
|
|
803
915
|
translateOptions,
|
|
804
|
-
)
|
|
805
|
-
|
|
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);
|
|
806
923
|
}
|
|
807
924
|
|
|
808
925
|
// Tool-call validation lives here (not in translateStreamPart) because
|
|
@@ -818,38 +935,48 @@ export function runPrompt<
|
|
|
818
935
|
result.enqueue(parsed);
|
|
819
936
|
}
|
|
820
937
|
|
|
821
|
-
// Accumulate output content for telemetry / reporters.
|
|
822
938
|
if (value.type === 'text-delta') {
|
|
823
939
|
stepText += value.delta;
|
|
824
940
|
} else if (value.type === 'reasoning-delta') {
|
|
825
941
|
stepReasoning += value.delta;
|
|
826
942
|
}
|
|
827
943
|
|
|
828
|
-
// Telemetry: a tool execution begins on its `tool-call`.
|
|
829
944
|
if (value.type === 'tool-call') {
|
|
830
945
|
observedStepToolCallCount += 1;
|
|
831
946
|
expectedStepToolCallCount ??= value.stepToolCallCount;
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
toolCallId: value.toolCallId,
|
|
840
|
-
toolName: value.toolName,
|
|
841
|
-
input: value.input,
|
|
842
|
-
});
|
|
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
|
+
}
|
|
843
954
|
}
|
|
844
955
|
|
|
845
|
-
// Telemetry: close a tool span when its provider-executed result lands.
|
|
846
956
|
if (value.type === 'tool-result') {
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
value.isError
|
|
850
|
-
? {
|
|
851
|
-
|
|
852
|
-
|
|
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
|
+
}
|
|
853
980
|
}
|
|
854
981
|
|
|
855
982
|
if (value.type === 'tool-approval-request') {
|
|
@@ -925,9 +1052,9 @@ export function runPrompt<
|
|
|
925
1052
|
if (value.type === 'finish') {
|
|
926
1053
|
await waitForOutstandingHostToolExecutions();
|
|
927
1054
|
finalFinish = value;
|
|
928
|
-
await
|
|
929
|
-
|
|
930
|
-
usage: value.totalUsage,
|
|
1055
|
+
await lifecycle.end({
|
|
1056
|
+
steps: completedSteps,
|
|
1057
|
+
usage: asLanguageModelUsage(value.totalUsage),
|
|
931
1058
|
});
|
|
932
1059
|
}
|
|
933
1060
|
|
|
@@ -940,7 +1067,16 @@ export function runPrompt<
|
|
|
940
1067
|
`Harness '${input.harness.harnessId}' could not find parsed tool call '${toolCall.toolCallId}' for custom tool approval.`,
|
|
941
1068
|
);
|
|
942
1069
|
}
|
|
943
|
-
|
|
1070
|
+
const isClientExecutedBuiltin =
|
|
1071
|
+
toolCall.toolName === 'askUserQuestions' &&
|
|
1072
|
+
Object.prototype.hasOwnProperty.call(
|
|
1073
|
+
input.harness.builtinTools,
|
|
1074
|
+
toolCall.toolName,
|
|
1075
|
+
);
|
|
1076
|
+
if (
|
|
1077
|
+
!isClientExecutedBuiltin &&
|
|
1078
|
+
!hasTool({ tools: activeTools, toolName: toolCall.toolName })
|
|
1079
|
+
) {
|
|
944
1080
|
const output = {
|
|
945
1081
|
type: 'execution-denied',
|
|
946
1082
|
reason: getHarnessV1BuiltinToolFilteringDenialReason({
|
|
@@ -951,9 +1087,27 @@ export function runPrompt<
|
|
|
951
1087
|
toolCallId: toolCall.toolCallId,
|
|
952
1088
|
output,
|
|
953
1089
|
});
|
|
954
|
-
|
|
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
|
+
}
|
|
955
1097
|
continue;
|
|
956
1098
|
}
|
|
1099
|
+
if (isClientExecutedBuiltin) {
|
|
1100
|
+
recordPendingToolResult({ toolCall });
|
|
1101
|
+
if (
|
|
1102
|
+
expectedStepToolCallCount != null &&
|
|
1103
|
+
observedStepToolCallCount < expectedStepToolCallCount
|
|
1104
|
+
) {
|
|
1105
|
+
pauseAfterStepToolCalls = true;
|
|
1106
|
+
continue;
|
|
1107
|
+
}
|
|
1108
|
+
await finishForHostInputPause({ completeCurrentStep: true });
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
957
1111
|
const customToolApprovalDecision = resolveCustomToolApproval({
|
|
958
1112
|
toolName: toolCall.toolName,
|
|
959
1113
|
toolApproval: input.toolApproval,
|
|
@@ -980,7 +1134,13 @@ export function runPrompt<
|
|
|
980
1134
|
toolCallId: toolCall.toolCallId,
|
|
981
1135
|
output,
|
|
982
1136
|
});
|
|
983
|
-
|
|
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
|
+
}
|
|
984
1144
|
continue;
|
|
985
1145
|
}
|
|
986
1146
|
const pendingApproval =
|
|
@@ -1056,10 +1216,11 @@ export function runPrompt<
|
|
|
1056
1216
|
}
|
|
1057
1217
|
startHostToolExecution(
|
|
1058
1218
|
(async () => {
|
|
1219
|
+
const executionStartedAt = Date.now();
|
|
1059
1220
|
const execution = await maybeExecuteHostTool({
|
|
1060
1221
|
event: toolCall,
|
|
1061
1222
|
tools: activeTools,
|
|
1062
|
-
wrappedExecuteTool:
|
|
1223
|
+
wrappedExecuteTool: lifecycle.executeTool,
|
|
1063
1224
|
sandboxSession: input.sandboxSession,
|
|
1064
1225
|
abortSignal: input.abortSignal,
|
|
1065
1226
|
control,
|
|
@@ -1084,14 +1245,16 @@ export function runPrompt<
|
|
|
1084
1245
|
},
|
|
1085
1246
|
input.sessionWorkDir,
|
|
1086
1247
|
) as Extract<HarnessV1StreamPart, { type: 'tool-result' }>;
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
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
|
+
});
|
|
1095
1258
|
},
|
|
1096
1259
|
});
|
|
1097
1260
|
if (!execution.executed) {
|
|
@@ -1099,7 +1262,14 @@ export function runPrompt<
|
|
|
1099
1262
|
`Harness '${input.harness.harnessId}' could not execute host tool '${toolCall.toolName}'.`,
|
|
1100
1263
|
);
|
|
1101
1264
|
}
|
|
1102
|
-
|
|
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
|
+
}
|
|
1103
1273
|
})(),
|
|
1104
1274
|
);
|
|
1105
1275
|
}
|
|
@@ -1136,7 +1306,7 @@ export function runPrompt<
|
|
|
1136
1306
|
} catch {
|
|
1137
1307
|
// Preserve the error that stopped the reader loop.
|
|
1138
1308
|
}
|
|
1139
|
-
await
|
|
1309
|
+
await lifecycle.error(err);
|
|
1140
1310
|
logBridgeError({
|
|
1141
1311
|
harnessId: input.harness.harnessId,
|
|
1142
1312
|
sessionId: input.session.sessionId,
|
|
@@ -1167,6 +1337,23 @@ type HostToolExecution =
|
|
|
1167
1337
|
| { executed: false }
|
|
1168
1338
|
| { executed: true; outcome: HostToolOutcome };
|
|
1169
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
|
+
|
|
1170
1357
|
function asToolCallTextStreamPart<TOOLS extends ToolSet>(input: {
|
|
1171
1358
|
part: TextStreamPart<TOOLS>;
|
|
1172
1359
|
}): ToolCallTextStreamPart {
|
|
@@ -1198,7 +1385,7 @@ function hasTool(input: { tools: ToolSet; toolName: string }): boolean {
|
|
|
1198
1385
|
async function maybeExecuteHostTool<TOOLS extends ToolSet>(input: {
|
|
1199
1386
|
event: { toolCallId: string; toolName: string; input: string };
|
|
1200
1387
|
tools: TOOLS;
|
|
1201
|
-
wrappedExecuteTool:
|
|
1388
|
+
wrappedExecuteTool: TurnLifecycle<ToolSet, Context>['executeTool'];
|
|
1202
1389
|
sandboxSession: SandboxSession;
|
|
1203
1390
|
abortSignal: AbortSignal | undefined;
|
|
1204
1391
|
control: HarnessV1PromptControl;
|