@ai-sdk/harness 1.0.92 → 1.0.94
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 +29 -0
- package/README.md +5 -1
- package/dist/agent/index.d.ts +166 -118
- package/dist/agent/index.js +399 -102
- package/dist/agent/index.js.map +1 -1
- package/dist/bridge/index.d.ts +20 -1
- package/dist/bridge/index.js +29 -5
- package/dist/bridge/index.js.map +1 -1
- package/dist/index.d.ts +142 -102
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/index.d.ts +8 -2
- package/dist/utils/index.js +339 -33
- package/dist/utils/index.js.map +1 -1
- package/package.json +5 -5
- package/src/agent/harness-agent-session.ts +115 -7
- package/src/agent/harness-agent-settings.ts +87 -7
- package/src/agent/harness-agent.ts +328 -112
- package/src/agent/internal/lifecycle-state-validation.ts +3 -0
- package/src/agent/internal/run-prompt.ts +49 -11
- package/src/agent/internal/strip-work-dir.ts +102 -0
- package/src/agent/internal/translate-stream-part.ts +5 -0
- package/src/bridge/index.ts +73 -6
- package/src/utils/index.ts +1 -0
- package/src/utils/write-skills.ts +419 -32
- package/src/v1/harness-v1-bridge-protocol.ts +6 -0
- package/src/v1/harness-v1-lifecycle-state.ts +45 -0
- package/src/v1/harness-v1-session.ts +3 -48
- package/src/v1/harness-v1-stream-part.ts +31 -1
- package/src/v1/index.ts +1 -0
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { HarnessCapabilityUnsupportedError } from '../errors/harness-capability-unsupported-error';
|
|
2
2
|
import type {
|
|
3
|
-
HarnessV1Bootstrap,
|
|
4
3
|
HarnessV1BuiltinToolFiltering,
|
|
5
4
|
HarnessV1JSONSchema,
|
|
6
5
|
HarnessV1NetworkSandboxSession,
|
|
@@ -10,6 +9,7 @@ import {
|
|
|
10
9
|
asArray,
|
|
11
10
|
asSchema,
|
|
12
11
|
generateId,
|
|
12
|
+
validateTypes,
|
|
13
13
|
type Context,
|
|
14
14
|
type Experimental_SandboxSession as SandboxSession,
|
|
15
15
|
type ModelMessage,
|
|
@@ -38,6 +38,7 @@ import type {
|
|
|
38
38
|
HarnessAgentPermissionMode,
|
|
39
39
|
HarnessAgentPrompt,
|
|
40
40
|
HarnessAgentResumeSessionState,
|
|
41
|
+
HarnessAgentSkill,
|
|
41
42
|
HarnessAgentToolSpec,
|
|
42
43
|
} from './harness-agent-types';
|
|
43
44
|
import {
|
|
@@ -84,6 +85,54 @@ export interface HarnessAgentCallExtensions {
|
|
|
84
85
|
session: HarnessAgentSession;
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
type HarnessAgentContinueTurnInput = {
|
|
89
|
+
toolApprovalContinuations: readonly HarnessAgentToolApprovalContinuation[];
|
|
90
|
+
toolResultContinuations: readonly HarnessAgentToolResultContinuation[];
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
type PreparedHarnessAgentTurnSettings<
|
|
94
|
+
THarness extends HarnessAgentAdapter<any>,
|
|
95
|
+
TUserTools extends ToolSet,
|
|
96
|
+
> = {
|
|
97
|
+
model: string | undefined;
|
|
98
|
+
skills: ReadonlyArray<HarnessAgentSkill>;
|
|
99
|
+
instructions: string | undefined;
|
|
100
|
+
tools: HarnessAllTools<THarness, TUserTools>;
|
|
101
|
+
activeTools: TUserTools;
|
|
102
|
+
toolSpecs: HarnessAgentToolSpec[];
|
|
103
|
+
builtinToolFiltering: HarnessV1BuiltinToolFiltering | undefined;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
type PreparedHarnessAgentPromptTurnInput<
|
|
107
|
+
THarness extends HarnessAgentAdapter<any>,
|
|
108
|
+
TUserTools extends ToolSet,
|
|
109
|
+
> = PreparedHarnessAgentTurnSettings<THarness, TUserTools> & {
|
|
110
|
+
prompt: HarnessAgentPrompt;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
type PreparedHarnessAgentContinueTurnInput<
|
|
114
|
+
THarness extends HarnessAgentAdapter<any>,
|
|
115
|
+
TUserTools extends ToolSet,
|
|
116
|
+
> = PreparedHarnessAgentTurnSettings<THarness, TUserTools> & {
|
|
117
|
+
toolApprovalContinuations: readonly HarnessAgentToolApprovalContinuation[];
|
|
118
|
+
toolResultContinuations: readonly HarnessAgentToolResultContinuation[];
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
type HarnessAgentTurnResult<
|
|
122
|
+
THarness extends HarnessAgentAdapter<any>,
|
|
123
|
+
TUserTools extends ToolSet,
|
|
124
|
+
RUNTIME_CONTEXT extends Context,
|
|
125
|
+
OUTPUT extends Output,
|
|
126
|
+
> = {
|
|
127
|
+
result: StreamTextResult<
|
|
128
|
+
HarnessAllTools<THarness, TUserTools>,
|
|
129
|
+
RUNTIME_CONTEXT,
|
|
130
|
+
OUTPUT
|
|
131
|
+
>;
|
|
132
|
+
done: Promise<void>;
|
|
133
|
+
ready: Promise<void>;
|
|
134
|
+
};
|
|
135
|
+
|
|
87
136
|
/**
|
|
88
137
|
* AI SDK `Agent` implementation that drives a third-party agent runtime
|
|
89
138
|
* through a harness adapter (Claude Code, Codex, …).
|
|
@@ -124,8 +173,9 @@ export class HarnessAgent<
|
|
|
124
173
|
TUserTools extends ToolSet = {},
|
|
125
174
|
RUNTIME_CONTEXT extends Context = Context,
|
|
126
175
|
OUTPUT extends Output = never,
|
|
176
|
+
CALL_OPTIONS = never,
|
|
127
177
|
> implements Agent<
|
|
128
|
-
|
|
178
|
+
CALL_OPTIONS,
|
|
129
179
|
HarnessAllTools<THarness, TUserTools>,
|
|
130
180
|
RUNTIME_CONTEXT,
|
|
131
181
|
OUTPUT
|
|
@@ -145,13 +195,13 @@ export class HarnessAgent<
|
|
|
145
195
|
THarness,
|
|
146
196
|
TUserTools,
|
|
147
197
|
RUNTIME_CONTEXT,
|
|
148
|
-
OUTPUT
|
|
198
|
+
OUTPUT,
|
|
199
|
+
CALL_OPTIONS
|
|
149
200
|
>;
|
|
150
201
|
private readonly stopConditions: Array<
|
|
151
202
|
StopCondition<HarnessAllTools<THarness, TUserTools>, RUNTIME_CONTEXT>
|
|
152
203
|
>;
|
|
153
204
|
private readonly sandboxConfig: HarnessAgentSandboxConfig;
|
|
154
|
-
private readonly activeUserTools: TUserTools;
|
|
155
205
|
private readonly builtinToolFiltering:
|
|
156
206
|
| HarnessV1BuiltinToolFiltering
|
|
157
207
|
| undefined;
|
|
@@ -162,7 +212,8 @@ export class HarnessAgent<
|
|
|
162
212
|
THarness,
|
|
163
213
|
TUserTools,
|
|
164
214
|
RUNTIME_CONTEXT,
|
|
165
|
-
OUTPUT
|
|
215
|
+
OUTPUT,
|
|
216
|
+
CALL_OPTIONS
|
|
166
217
|
>,
|
|
167
218
|
) {
|
|
168
219
|
const sandboxConfig = resolveSandboxConfig(settings);
|
|
@@ -187,7 +238,6 @@ export class HarnessAgent<
|
|
|
187
238
|
activeTools: settings.activeTools,
|
|
188
239
|
inactiveTools: settings.inactiveTools,
|
|
189
240
|
});
|
|
190
|
-
this.activeUserTools = toolFiltering.activeUserTools;
|
|
191
241
|
this.builtinToolFiltering = toolFiltering.builtinToolFiltering;
|
|
192
242
|
if (
|
|
193
243
|
Object.keys(settings.harness.builtinTools).length > 0 &&
|
|
@@ -329,6 +379,7 @@ export class HarnessAgent<
|
|
|
329
379
|
);
|
|
330
380
|
}
|
|
331
381
|
|
|
382
|
+
const recipe = await harness.getBootstrap?.({ abortSignal });
|
|
332
383
|
if (isResumedSession) {
|
|
333
384
|
if (sandboxProvider.resumeSession == null) {
|
|
334
385
|
throw new HarnessCapabilityUnsupportedError({
|
|
@@ -348,16 +399,34 @@ export class HarnessAgent<
|
|
|
348
399
|
sessionId,
|
|
349
400
|
workDir: this.sandboxConfig.workDir,
|
|
350
401
|
});
|
|
351
|
-
} else {
|
|
352
|
-
// The logic in this clause applies the bootstrap plan, including both the harness
|
|
353
|
-
// bootstrap recipe and agent specific sandbox configuration.
|
|
354
|
-
// The logic matches largely what `prepareHarnessSandboxTemplate()` and
|
|
355
|
-
// `prepareSandboxForHarness()` do, so they will have to remain aligned.
|
|
356
|
-
let recipe: HarnessV1Bootstrap | undefined;
|
|
357
|
-
if (harness.getBootstrap != null) {
|
|
358
|
-
recipe = await harness.getBootstrap({ abortSignal });
|
|
359
|
-
}
|
|
360
402
|
|
|
403
|
+
// Ensure the harness bootstrap recipe on resumed sessions too. The
|
|
404
|
+
// marker is keyed by recipe identity, so a resume whose bootstrap is
|
|
405
|
+
// already current costs one file read, while a resume into a sandbox
|
|
406
|
+
// bootstrapped by an older adapter build — a snapshot that outlived
|
|
407
|
+
// the harness version that made it — would otherwise keep running a
|
|
408
|
+
// stale bridge against a newer host, silently missing whatever the
|
|
409
|
+
// newer protocol added.
|
|
410
|
+
if (recipe != null) {
|
|
411
|
+
const recipeIdentity = await hashHarnessBootstrap(recipe);
|
|
412
|
+
try {
|
|
413
|
+
await applyBootstrapRecipe({
|
|
414
|
+
session: resumedSandboxSession.restricted(),
|
|
415
|
+
recipe,
|
|
416
|
+
identity: recipeIdentity,
|
|
417
|
+
defaultWorkingDirectory:
|
|
418
|
+
resumedSandboxSession.defaultWorkingDirectory,
|
|
419
|
+
abortSignal,
|
|
420
|
+
});
|
|
421
|
+
} catch (err) {
|
|
422
|
+
await cleanupAfterStartFailure({
|
|
423
|
+
sandboxSession,
|
|
424
|
+
ownsSandboxLifecycle,
|
|
425
|
+
});
|
|
426
|
+
throw err;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
} else {
|
|
361
430
|
// Defines the hashes based on both harness bootstrap recipe and
|
|
362
431
|
// consumer-defined onBootstrap callback.
|
|
363
432
|
const sandboxBootstrapPlan = await createSandboxBootstrapPlan({
|
|
@@ -432,7 +501,6 @@ export class HarnessAgent<
|
|
|
432
501
|
try {
|
|
433
502
|
const baseStartOptions = {
|
|
434
503
|
sessionId,
|
|
435
|
-
skills: this.settings.skills,
|
|
436
504
|
resumeFrom: validatedResumeFrom,
|
|
437
505
|
continueFrom: effectiveContinueFrom,
|
|
438
506
|
permissionMode: this.permissionMode,
|
|
@@ -455,6 +523,7 @@ export class HarnessAgent<
|
|
|
455
523
|
toolApproval: this.settings.toolApproval,
|
|
456
524
|
pendingToolApprovals: effectiveContinueFrom?.pendingToolApprovals,
|
|
457
525
|
pendingToolResults: effectiveContinueFrom?.pendingToolResults,
|
|
526
|
+
turnSettings: effectiveContinueFrom?.turnSettings,
|
|
458
527
|
turnState:
|
|
459
528
|
effectiveContinueFrom == null
|
|
460
529
|
? 'idle'
|
|
@@ -477,7 +546,7 @@ export class HarnessAgent<
|
|
|
477
546
|
|
|
478
547
|
async generate(
|
|
479
548
|
options: AgentCallParameters<
|
|
480
|
-
|
|
549
|
+
CALL_OPTIONS,
|
|
481
550
|
HarnessAllTools<THarness, TUserTools>,
|
|
482
551
|
RUNTIME_CONTEXT
|
|
483
552
|
> &
|
|
@@ -489,23 +558,24 @@ export class HarnessAgent<
|
|
|
489
558
|
OUTPUT
|
|
490
559
|
>
|
|
491
560
|
> {
|
|
492
|
-
const
|
|
561
|
+
const continueTurnInput = this._resolveContinueTurnInput(options);
|
|
493
562
|
const runtimeContext = {} as RUNTIME_CONTEXT;
|
|
494
|
-
const
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
563
|
+
const { result, done } =
|
|
564
|
+
continueTurnInput == null
|
|
565
|
+
? await this._startPromptTurn({ options, runtimeContext })
|
|
566
|
+
: await this._startContinueTurn({
|
|
567
|
+
session: options.session,
|
|
568
|
+
turnInput: continueTurnInput,
|
|
569
|
+
runtimeContext,
|
|
570
|
+
abortSignal: options.abortSignal,
|
|
571
|
+
});
|
|
502
572
|
await done;
|
|
503
573
|
return this._toGenerateResult(result);
|
|
504
574
|
}
|
|
505
575
|
|
|
506
576
|
async stream(
|
|
507
577
|
options: AgentStreamParameters<
|
|
508
|
-
|
|
578
|
+
CALL_OPTIONS,
|
|
509
579
|
HarnessAllTools<THarness, TUserTools>,
|
|
510
580
|
RUNTIME_CONTEXT
|
|
511
581
|
> &
|
|
@@ -517,16 +587,18 @@ export class HarnessAgent<
|
|
|
517
587
|
OUTPUT
|
|
518
588
|
>
|
|
519
589
|
> {
|
|
520
|
-
const
|
|
590
|
+
const continueTurnInput = this._resolveContinueTurnInput(options);
|
|
521
591
|
const runtimeContext = {} as RUNTIME_CONTEXT;
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
592
|
+
const { result, ready } =
|
|
593
|
+
continueTurnInput == null
|
|
594
|
+
? await this._startPromptTurn({ options, runtimeContext })
|
|
595
|
+
: await this._startContinueTurn({
|
|
596
|
+
session: options.session,
|
|
597
|
+
turnInput: continueTurnInput,
|
|
598
|
+
runtimeContext,
|
|
599
|
+
abortSignal: options.abortSignal,
|
|
600
|
+
});
|
|
601
|
+
await ready;
|
|
530
602
|
return result;
|
|
531
603
|
}
|
|
532
604
|
|
|
@@ -548,18 +620,14 @@ export class HarnessAgent<
|
|
|
548
620
|
>
|
|
549
621
|
> {
|
|
550
622
|
const runtimeContext = {} as RUNTIME_CONTEXT;
|
|
551
|
-
const
|
|
552
|
-
|
|
553
|
-
const { result, done } = this._startTurn({
|
|
623
|
+
const { result, done } = await this._startContinueTurn({
|
|
554
624
|
session: options.session,
|
|
555
625
|
turnInput: {
|
|
556
|
-
mode: 'continue',
|
|
557
626
|
toolApprovalContinuations: options.toolApprovalContinuations ?? [],
|
|
558
627
|
toolResultContinuations: options.toolResultContinuations ?? [],
|
|
559
628
|
},
|
|
560
629
|
runtimeContext,
|
|
561
630
|
abortSignal: options.abortSignal,
|
|
562
|
-
responseFormat,
|
|
563
631
|
});
|
|
564
632
|
await done;
|
|
565
633
|
return this._toGenerateResult(result);
|
|
@@ -586,19 +654,16 @@ export class HarnessAgent<
|
|
|
586
654
|
>
|
|
587
655
|
> {
|
|
588
656
|
const runtimeContext = {} as RUNTIME_CONTEXT;
|
|
589
|
-
const
|
|
590
|
-
|
|
591
|
-
const { result } = this._startTurn({
|
|
657
|
+
const { result, ready } = await this._startContinueTurn({
|
|
592
658
|
session: options.session,
|
|
593
659
|
turnInput: {
|
|
594
|
-
mode: 'continue',
|
|
595
660
|
toolApprovalContinuations: options.toolApprovalContinuations ?? [],
|
|
596
661
|
toolResultContinuations: options.toolResultContinuations ?? [],
|
|
597
662
|
},
|
|
598
663
|
runtimeContext,
|
|
599
664
|
abortSignal: options.abortSignal,
|
|
600
|
-
responseFormat,
|
|
601
665
|
});
|
|
666
|
+
await ready;
|
|
602
667
|
return result;
|
|
603
668
|
}
|
|
604
669
|
|
|
@@ -618,88 +683,91 @@ export class HarnessAgent<
|
|
|
618
683
|
|
|
619
684
|
// ─── Internals ──────────────────────────────────────────────────────
|
|
620
685
|
|
|
621
|
-
private
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
toolResultContinuations: readonly HarnessAgentToolResultContinuation[];
|
|
629
|
-
};
|
|
686
|
+
private async _startPromptTurn(input: {
|
|
687
|
+
options: AgentCallParameters<
|
|
688
|
+
CALL_OPTIONS,
|
|
689
|
+
HarnessAllTools<THarness, TUserTools>,
|
|
690
|
+
RUNTIME_CONTEXT
|
|
691
|
+
> &
|
|
692
|
+
HarnessAgentCallExtensions;
|
|
630
693
|
runtimeContext: RUNTIME_CONTEXT;
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
694
|
+
}): Promise<
|
|
695
|
+
HarnessAgentTurnResult<THarness, TUserTools, RUNTIME_CONTEXT, OUTPUT>
|
|
696
|
+
> {
|
|
697
|
+
const turnInput = await this._preparePromptTurnInput(input.options);
|
|
698
|
+
const responseFormat = await this._resolveResponseFormat();
|
|
699
|
+
|
|
700
|
+
return input.options.session.promptTurn<
|
|
635
701
|
HarnessAllTools<THarness, TUserTools>,
|
|
636
702
|
RUNTIME_CONTEXT,
|
|
637
703
|
OUTPUT
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
if (input.turnInput.mode === 'continue') {
|
|
642
|
-
return input.session.continueTurn<
|
|
643
|
-
HarnessAllTools<THarness, TUserTools>,
|
|
644
|
-
RUNTIME_CONTEXT,
|
|
645
|
-
OUTPUT
|
|
646
|
-
>({
|
|
647
|
-
instructions: this.settings.instructions,
|
|
648
|
-
tools: this.tools,
|
|
649
|
-
activeTools: this.activeUserTools,
|
|
650
|
-
toolSpecs: this._toToolSpecs(),
|
|
651
|
-
builtinToolFiltering: this.builtinToolFiltering,
|
|
704
|
+
>({
|
|
705
|
+
...this._buildTurnOptions({
|
|
706
|
+
turnSettings: turnInput,
|
|
652
707
|
runtimeContext: input.runtimeContext,
|
|
653
|
-
abortSignal: input.abortSignal,
|
|
654
|
-
responseFormat
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
toolResultContinuations: input.turnInput.toolResultContinuations,
|
|
660
|
-
});
|
|
661
|
-
}
|
|
708
|
+
abortSignal: input.options.abortSignal,
|
|
709
|
+
responseFormat,
|
|
710
|
+
}),
|
|
711
|
+
prompt: turnInput.prompt,
|
|
712
|
+
});
|
|
713
|
+
}
|
|
662
714
|
|
|
663
|
-
|
|
715
|
+
private async _startContinueTurn(input: {
|
|
716
|
+
session: HarnessAgentSession;
|
|
717
|
+
turnInput: HarnessAgentContinueTurnInput;
|
|
718
|
+
runtimeContext: RUNTIME_CONTEXT;
|
|
719
|
+
abortSignal: AbortSignal | undefined;
|
|
720
|
+
}): Promise<
|
|
721
|
+
HarnessAgentTurnResult<THarness, TUserTools, RUNTIME_CONTEXT, OUTPUT>
|
|
722
|
+
> {
|
|
723
|
+
const turnInput = this._prepareContinueTurnInput(input.turnInput);
|
|
724
|
+
const responseFormat = await this._resolveResponseFormat();
|
|
725
|
+
|
|
726
|
+
return input.session.continueTurn<
|
|
664
727
|
HarnessAllTools<THarness, TUserTools>,
|
|
665
728
|
RUNTIME_CONTEXT,
|
|
666
729
|
OUTPUT
|
|
667
730
|
>({
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
731
|
+
...this._buildTurnOptions({
|
|
732
|
+
turnSettings: turnInput,
|
|
733
|
+
runtimeContext: input.runtimeContext,
|
|
734
|
+
abortSignal: input.abortSignal,
|
|
735
|
+
responseFormat,
|
|
736
|
+
}),
|
|
737
|
+
toolApprovalContinuations: turnInput.toolApprovalContinuations,
|
|
738
|
+
toolResultContinuations: turnInput.toolResultContinuations,
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
private _buildTurnOptions(input: {
|
|
743
|
+
turnSettings: PreparedHarnessAgentTurnSettings<THarness, TUserTools>;
|
|
744
|
+
runtimeContext: RUNTIME_CONTEXT;
|
|
745
|
+
abortSignal: AbortSignal | undefined;
|
|
746
|
+
responseFormat: HarnessV1ResponseFormat | undefined;
|
|
747
|
+
}) {
|
|
748
|
+
return {
|
|
749
|
+
model: input.turnSettings.model,
|
|
750
|
+
skills: input.turnSettings.skills,
|
|
751
|
+
instructions: input.turnSettings.instructions,
|
|
752
|
+
tools: input.turnSettings.tools,
|
|
753
|
+
activeTools: input.turnSettings.activeTools,
|
|
754
|
+
toolSpecs: input.turnSettings.toolSpecs,
|
|
755
|
+
builtinToolFiltering: input.turnSettings.builtinToolFiltering,
|
|
674
756
|
runtimeContext: input.runtimeContext,
|
|
675
757
|
abortSignal: input.abortSignal,
|
|
676
758
|
responseFormat: input.responseFormat,
|
|
677
759
|
output: this.settings.output,
|
|
678
760
|
telemetry: this.settings.telemetry,
|
|
679
761
|
stopConditions: this.stopConditions,
|
|
680
|
-
}
|
|
762
|
+
};
|
|
681
763
|
}
|
|
682
764
|
|
|
683
|
-
|
|
684
|
-
* Reduce AI SDK input to the single user message the harness should run
|
|
685
|
-
* for this turn. The harness session owns prior-turn state (system
|
|
686
|
-
* prompt, assistant turns, tool results) — we never replay it. A bare
|
|
687
|
-
* string is forwarded as-is; a message array is collapsed to its last
|
|
688
|
-
* `role: 'user'` entry. Inputs whose only messages are non-user (system,
|
|
689
|
-
* assistant, tool) have no fresh user input and are rejected.
|
|
690
|
-
*/
|
|
691
|
-
private _resolveTurnInput(options: {
|
|
765
|
+
private _resolveContinueTurnInput(options: {
|
|
692
766
|
prompt?: string | ModelMessage[];
|
|
693
767
|
messages?: ModelMessage[];
|
|
694
|
-
}):
|
|
695
|
-
| { mode: 'prompt'; prompt: HarnessAgentPrompt }
|
|
696
|
-
| {
|
|
697
|
-
mode: 'continue';
|
|
698
|
-
toolApprovalContinuations: readonly HarnessAgentToolApprovalContinuation[];
|
|
699
|
-
toolResultContinuations: readonly HarnessAgentToolResultContinuation[];
|
|
700
|
-
} {
|
|
768
|
+
}): HarnessAgentContinueTurnInput | undefined {
|
|
701
769
|
if (typeof options.prompt === 'string') {
|
|
702
|
-
return
|
|
770
|
+
return undefined;
|
|
703
771
|
}
|
|
704
772
|
const messages = Array.isArray(options.prompt)
|
|
705
773
|
? options.prompt
|
|
@@ -714,15 +782,36 @@ export class HarnessAgent<
|
|
|
714
782
|
toolResultContinuations.length > 0
|
|
715
783
|
) {
|
|
716
784
|
return {
|
|
717
|
-
mode: 'continue',
|
|
718
785
|
toolApprovalContinuations,
|
|
719
786
|
toolResultContinuations,
|
|
720
787
|
};
|
|
721
788
|
}
|
|
789
|
+
}
|
|
790
|
+
return undefined;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/*
|
|
794
|
+
* Reduce AI SDK input to the single user message the harness should run
|
|
795
|
+
* for this turn. The harness session owns prior-turn state (system
|
|
796
|
+
* prompt, assistant turns, tool results) — we never replay it. A bare
|
|
797
|
+
* string is forwarded as-is; a message array is collapsed to its last
|
|
798
|
+
* `role: 'user'` entry. Inputs whose only messages are non-user (system,
|
|
799
|
+
* assistant, tool) have no fresh user input and are rejected.
|
|
800
|
+
*/
|
|
801
|
+
private _resolvePromptTurnInput(options: {
|
|
802
|
+
prompt?: string | ModelMessage[];
|
|
803
|
+
messages?: ModelMessage[];
|
|
804
|
+
}): HarnessAgentPrompt {
|
|
805
|
+
if (typeof options.prompt === 'string') {
|
|
806
|
+
return options.prompt;
|
|
807
|
+
}
|
|
808
|
+
const messages = Array.isArray(options.prompt)
|
|
809
|
+
? options.prompt
|
|
810
|
+
: options.messages;
|
|
811
|
+
if (Array.isArray(messages)) {
|
|
722
812
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
723
813
|
const message = messages[i];
|
|
724
|
-
if (message?.role === 'user')
|
|
725
|
-
return { mode: 'prompt', prompt: message };
|
|
814
|
+
if (message?.role === 'user') return message;
|
|
726
815
|
}
|
|
727
816
|
throw new Error(
|
|
728
817
|
'HarnessAgent: messages must contain at least one `role: "user"` entry.',
|
|
@@ -731,15 +820,142 @@ export class HarnessAgent<
|
|
|
731
820
|
throw new Error('HarnessAgent: either `prompt` or `messages` is required.');
|
|
732
821
|
}
|
|
733
822
|
|
|
823
|
+
private async _preparePromptTurnInput(
|
|
824
|
+
options: AgentCallParameters<
|
|
825
|
+
CALL_OPTIONS,
|
|
826
|
+
HarnessAllTools<THarness, TUserTools>,
|
|
827
|
+
RUNTIME_CONTEXT
|
|
828
|
+
> &
|
|
829
|
+
HarnessAgentCallExtensions,
|
|
830
|
+
): Promise<PreparedHarnessAgentPromptTurnInput<THarness, TUserTools>> {
|
|
831
|
+
let callOptions = options;
|
|
832
|
+
if (
|
|
833
|
+
this.settings.callOptionsSchema != null &&
|
|
834
|
+
options.options !== undefined
|
|
835
|
+
) {
|
|
836
|
+
const validatedOptions = await validateTypes({
|
|
837
|
+
value: options.options,
|
|
838
|
+
schema: this.settings.callOptionsSchema,
|
|
839
|
+
context: { field: 'options' },
|
|
840
|
+
});
|
|
841
|
+
callOptions = {
|
|
842
|
+
...options,
|
|
843
|
+
options: validatedOptions,
|
|
844
|
+
} as unknown as typeof options;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
const {
|
|
848
|
+
session: _session,
|
|
849
|
+
abortSignal: _abortSignal,
|
|
850
|
+
timeout: _timeout,
|
|
851
|
+
onStart: _onStart,
|
|
852
|
+
experimental_onStart: _experimentalOnStart,
|
|
853
|
+
onStepStart: _onStepStart,
|
|
854
|
+
experimental_onStepStart: _experimentalOnStepStart,
|
|
855
|
+
onToolExecutionStart: _onToolExecutionStart,
|
|
856
|
+
experimental_onToolCallStart: _experimentalOnToolCallStart,
|
|
857
|
+
onToolExecutionEnd: _onToolExecutionEnd,
|
|
858
|
+
experimental_onToolCallFinish: _experimentalOnToolCallFinish,
|
|
859
|
+
onStepEnd: _onStepEnd,
|
|
860
|
+
onStepFinish: _onStepFinish,
|
|
861
|
+
onEnd: _onEnd,
|
|
862
|
+
onFinish: _onFinish,
|
|
863
|
+
experimental_sandbox: _experimentalSandbox,
|
|
864
|
+
...promptOptions
|
|
865
|
+
} = callOptions;
|
|
866
|
+
|
|
867
|
+
const baseCallArgs = {
|
|
868
|
+
model: this.settings.model,
|
|
869
|
+
skills: this.settings.skills,
|
|
870
|
+
instructions: this.settings.instructions,
|
|
871
|
+
tools: this.settings.tools,
|
|
872
|
+
...promptOptions,
|
|
873
|
+
};
|
|
874
|
+
const preparedCallArgs =
|
|
875
|
+
(await this.settings.prepareCall?.(
|
|
876
|
+
baseCallArgs as Parameters<
|
|
877
|
+
NonNullable<
|
|
878
|
+
HarnessAgentSettings<
|
|
879
|
+
THarness,
|
|
880
|
+
TUserTools,
|
|
881
|
+
RUNTIME_CONTEXT,
|
|
882
|
+
OUTPUT,
|
|
883
|
+
CALL_OPTIONS
|
|
884
|
+
>['prepareCall']
|
|
885
|
+
>
|
|
886
|
+
>[0],
|
|
887
|
+
)) ?? baseCallArgs;
|
|
888
|
+
if (this._resolveContinueTurnInput(preparedCallArgs) != null) {
|
|
889
|
+
throw new Error(
|
|
890
|
+
'HarnessAgent.prepareCall must return a fresh user prompt, not a tool continuation.',
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
return {
|
|
895
|
+
prompt: this._resolvePromptTurnInput(preparedCallArgs),
|
|
896
|
+
...this._prepareTurnSettings({
|
|
897
|
+
model: preparedCallArgs.model,
|
|
898
|
+
skills: preparedCallArgs.skills,
|
|
899
|
+
instructions: preparedCallArgs.instructions,
|
|
900
|
+
tools: preparedCallArgs.tools,
|
|
901
|
+
}),
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
private _prepareContinueTurnInput(options: {
|
|
906
|
+
toolApprovalContinuations?: readonly HarnessAgentToolApprovalContinuation[];
|
|
907
|
+
toolResultContinuations?: readonly HarnessAgentToolResultContinuation[];
|
|
908
|
+
}): PreparedHarnessAgentContinueTurnInput<THarness, TUserTools> {
|
|
909
|
+
return {
|
|
910
|
+
...this._prepareTurnSettings({
|
|
911
|
+
model: this.settings.model,
|
|
912
|
+
skills: this.settings.skills,
|
|
913
|
+
instructions: this.settings.instructions,
|
|
914
|
+
tools: this.settings.tools,
|
|
915
|
+
}),
|
|
916
|
+
toolApprovalContinuations: options.toolApprovalContinuations ?? [],
|
|
917
|
+
toolResultContinuations: options.toolResultContinuations ?? [],
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
private _prepareTurnSettings(options: {
|
|
922
|
+
model?: string;
|
|
923
|
+
skills?: ReadonlyArray<HarnessAgentSkill>;
|
|
924
|
+
instructions?: string;
|
|
925
|
+
tools?: TUserTools;
|
|
926
|
+
}): PreparedHarnessAgentTurnSettings<THarness, TUserTools> {
|
|
927
|
+
const userTools = options.tools ?? ({} as TUserTools);
|
|
928
|
+
const tools = {
|
|
929
|
+
...this.settings.harness.builtinTools,
|
|
930
|
+
...userTools,
|
|
931
|
+
} as HarnessAllTools<THarness, TUserTools>;
|
|
932
|
+
const toolFiltering = resolveHarnessAgentToolFiltering({
|
|
933
|
+
harness: this.settings.harness,
|
|
934
|
+
userTools,
|
|
935
|
+
allTools: tools,
|
|
936
|
+
activeTools: this.settings.activeTools,
|
|
937
|
+
inactiveTools: this.settings.inactiveTools,
|
|
938
|
+
});
|
|
939
|
+
return {
|
|
940
|
+
model: options.model,
|
|
941
|
+
skills: options.skills ?? [],
|
|
942
|
+
instructions: options.instructions,
|
|
943
|
+
tools,
|
|
944
|
+
activeTools: toolFiltering.activeUserTools,
|
|
945
|
+
toolSpecs: this._toToolSpecs(toolFiltering.activeUserTools),
|
|
946
|
+
builtinToolFiltering: toolFiltering.builtinToolFiltering,
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
|
|
734
950
|
/*
|
|
735
951
|
* Wire-format projection of user-defined tools only. Harness builtins are
|
|
736
952
|
* executed by the runtime and the bridge already knows about them — we
|
|
737
953
|
* never re-declare them over the wire.
|
|
738
954
|
*/
|
|
739
|
-
private _toToolSpecs(): HarnessAgentToolSpec[] {
|
|
955
|
+
private _toToolSpecs(activeUserTools: TUserTools): HarnessAgentToolSpec[] {
|
|
740
956
|
const specs: HarnessAgentToolSpec[] = [];
|
|
741
957
|
for (const [name, tool] of Object.entries(
|
|
742
|
-
|
|
958
|
+
activeUserTools as Record<string, unknown>,
|
|
743
959
|
)) {
|
|
744
960
|
const t = tool as {
|
|
745
961
|
description?: string;
|
|
@@ -104,5 +104,8 @@ export async function validateLifecycleStateData<
|
|
|
104
104
|
...(state.pendingToolResults !== undefined
|
|
105
105
|
? { pendingToolResults: state.pendingToolResults }
|
|
106
106
|
: {}),
|
|
107
|
+
...(state.turnSettings !== undefined
|
|
108
|
+
? { turnSettings: state.turnSettings }
|
|
109
|
+
: {}),
|
|
107
110
|
} as STATE;
|
|
108
111
|
}
|