@ai-sdk/harness 1.0.92 → 1.0.93
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 +15 -0
- package/README.md +5 -1
- package/dist/agent/index.d.ts +155 -110
- package/dist/agent/index.js +292 -99
- 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 +100 -94
- package/dist/utils/index.d.ts +8 -2
- package/dist/utils/index.js +313 -33
- package/dist/utils/index.js.map +1 -1
- package/package.json +4 -4
- package/src/agent/harness-agent-session.ts +107 -7
- package/src/agent/harness-agent-settings.ts +86 -7
- package/src/agent/harness-agent.ts +322 -112
- package/src/agent/internal/lifecycle-state-validation.ts +3 -0
- package/src/agent/internal/run-prompt.ts +16 -6
- 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-lifecycle-state.ts +38 -0
- package/src/v1/harness-v1-session.ts +9 -40
- 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,53 @@ 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
|
+
skills: ReadonlyArray<HarnessAgentSkill>;
|
|
98
|
+
instructions: string | undefined;
|
|
99
|
+
tools: HarnessAllTools<THarness, TUserTools>;
|
|
100
|
+
activeTools: TUserTools;
|
|
101
|
+
toolSpecs: HarnessAgentToolSpec[];
|
|
102
|
+
builtinToolFiltering: HarnessV1BuiltinToolFiltering | undefined;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
type PreparedHarnessAgentPromptTurnInput<
|
|
106
|
+
THarness extends HarnessAgentAdapter<any>,
|
|
107
|
+
TUserTools extends ToolSet,
|
|
108
|
+
> = PreparedHarnessAgentTurnSettings<THarness, TUserTools> & {
|
|
109
|
+
prompt: HarnessAgentPrompt;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
type PreparedHarnessAgentContinueTurnInput<
|
|
113
|
+
THarness extends HarnessAgentAdapter<any>,
|
|
114
|
+
TUserTools extends ToolSet,
|
|
115
|
+
> = PreparedHarnessAgentTurnSettings<THarness, TUserTools> & {
|
|
116
|
+
toolApprovalContinuations: readonly HarnessAgentToolApprovalContinuation[];
|
|
117
|
+
toolResultContinuations: readonly HarnessAgentToolResultContinuation[];
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
type HarnessAgentTurnResult<
|
|
121
|
+
THarness extends HarnessAgentAdapter<any>,
|
|
122
|
+
TUserTools extends ToolSet,
|
|
123
|
+
RUNTIME_CONTEXT extends Context,
|
|
124
|
+
OUTPUT extends Output,
|
|
125
|
+
> = {
|
|
126
|
+
result: StreamTextResult<
|
|
127
|
+
HarnessAllTools<THarness, TUserTools>,
|
|
128
|
+
RUNTIME_CONTEXT,
|
|
129
|
+
OUTPUT
|
|
130
|
+
>;
|
|
131
|
+
done: Promise<void>;
|
|
132
|
+
ready: Promise<void>;
|
|
133
|
+
};
|
|
134
|
+
|
|
87
135
|
/**
|
|
88
136
|
* AI SDK `Agent` implementation that drives a third-party agent runtime
|
|
89
137
|
* through a harness adapter (Claude Code, Codex, …).
|
|
@@ -124,8 +172,9 @@ export class HarnessAgent<
|
|
|
124
172
|
TUserTools extends ToolSet = {},
|
|
125
173
|
RUNTIME_CONTEXT extends Context = Context,
|
|
126
174
|
OUTPUT extends Output = never,
|
|
175
|
+
CALL_OPTIONS = never,
|
|
127
176
|
> implements Agent<
|
|
128
|
-
|
|
177
|
+
CALL_OPTIONS,
|
|
129
178
|
HarnessAllTools<THarness, TUserTools>,
|
|
130
179
|
RUNTIME_CONTEXT,
|
|
131
180
|
OUTPUT
|
|
@@ -145,13 +194,13 @@ export class HarnessAgent<
|
|
|
145
194
|
THarness,
|
|
146
195
|
TUserTools,
|
|
147
196
|
RUNTIME_CONTEXT,
|
|
148
|
-
OUTPUT
|
|
197
|
+
OUTPUT,
|
|
198
|
+
CALL_OPTIONS
|
|
149
199
|
>;
|
|
150
200
|
private readonly stopConditions: Array<
|
|
151
201
|
StopCondition<HarnessAllTools<THarness, TUserTools>, RUNTIME_CONTEXT>
|
|
152
202
|
>;
|
|
153
203
|
private readonly sandboxConfig: HarnessAgentSandboxConfig;
|
|
154
|
-
private readonly activeUserTools: TUserTools;
|
|
155
204
|
private readonly builtinToolFiltering:
|
|
156
205
|
| HarnessV1BuiltinToolFiltering
|
|
157
206
|
| undefined;
|
|
@@ -162,7 +211,8 @@ export class HarnessAgent<
|
|
|
162
211
|
THarness,
|
|
163
212
|
TUserTools,
|
|
164
213
|
RUNTIME_CONTEXT,
|
|
165
|
-
OUTPUT
|
|
214
|
+
OUTPUT,
|
|
215
|
+
CALL_OPTIONS
|
|
166
216
|
>,
|
|
167
217
|
) {
|
|
168
218
|
const sandboxConfig = resolveSandboxConfig(settings);
|
|
@@ -187,7 +237,6 @@ export class HarnessAgent<
|
|
|
187
237
|
activeTools: settings.activeTools,
|
|
188
238
|
inactiveTools: settings.inactiveTools,
|
|
189
239
|
});
|
|
190
|
-
this.activeUserTools = toolFiltering.activeUserTools;
|
|
191
240
|
this.builtinToolFiltering = toolFiltering.builtinToolFiltering;
|
|
192
241
|
if (
|
|
193
242
|
Object.keys(settings.harness.builtinTools).length > 0 &&
|
|
@@ -329,6 +378,7 @@ export class HarnessAgent<
|
|
|
329
378
|
);
|
|
330
379
|
}
|
|
331
380
|
|
|
381
|
+
const recipe = await harness.getBootstrap?.({ abortSignal });
|
|
332
382
|
if (isResumedSession) {
|
|
333
383
|
if (sandboxProvider.resumeSession == null) {
|
|
334
384
|
throw new HarnessCapabilityUnsupportedError({
|
|
@@ -348,16 +398,34 @@ export class HarnessAgent<
|
|
|
348
398
|
sessionId,
|
|
349
399
|
workDir: this.sandboxConfig.workDir,
|
|
350
400
|
});
|
|
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
401
|
|
|
402
|
+
// Ensure the harness bootstrap recipe on resumed sessions too. The
|
|
403
|
+
// marker is keyed by recipe identity, so a resume whose bootstrap is
|
|
404
|
+
// already current costs one file read, while a resume into a sandbox
|
|
405
|
+
// bootstrapped by an older adapter build — a snapshot that outlived
|
|
406
|
+
// the harness version that made it — would otherwise keep running a
|
|
407
|
+
// stale bridge against a newer host, silently missing whatever the
|
|
408
|
+
// newer protocol added.
|
|
409
|
+
if (recipe != null) {
|
|
410
|
+
const recipeIdentity = await hashHarnessBootstrap(recipe);
|
|
411
|
+
try {
|
|
412
|
+
await applyBootstrapRecipe({
|
|
413
|
+
session: resumedSandboxSession.restricted(),
|
|
414
|
+
recipe,
|
|
415
|
+
identity: recipeIdentity,
|
|
416
|
+
defaultWorkingDirectory:
|
|
417
|
+
resumedSandboxSession.defaultWorkingDirectory,
|
|
418
|
+
abortSignal,
|
|
419
|
+
});
|
|
420
|
+
} catch (err) {
|
|
421
|
+
await cleanupAfterStartFailure({
|
|
422
|
+
sandboxSession,
|
|
423
|
+
ownsSandboxLifecycle,
|
|
424
|
+
});
|
|
425
|
+
throw err;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
} else {
|
|
361
429
|
// Defines the hashes based on both harness bootstrap recipe and
|
|
362
430
|
// consumer-defined onBootstrap callback.
|
|
363
431
|
const sandboxBootstrapPlan = await createSandboxBootstrapPlan({
|
|
@@ -431,8 +499,8 @@ export class HarnessAgent<
|
|
|
431
499
|
|
|
432
500
|
try {
|
|
433
501
|
const baseStartOptions = {
|
|
502
|
+
model: this.settings.model,
|
|
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,90 @@ 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
|
+
skills: input.turnSettings.skills,
|
|
750
|
+
instructions: input.turnSettings.instructions,
|
|
751
|
+
tools: input.turnSettings.tools,
|
|
752
|
+
activeTools: input.turnSettings.activeTools,
|
|
753
|
+
toolSpecs: input.turnSettings.toolSpecs,
|
|
754
|
+
builtinToolFiltering: input.turnSettings.builtinToolFiltering,
|
|
674
755
|
runtimeContext: input.runtimeContext,
|
|
675
756
|
abortSignal: input.abortSignal,
|
|
676
757
|
responseFormat: input.responseFormat,
|
|
677
758
|
output: this.settings.output,
|
|
678
759
|
telemetry: this.settings.telemetry,
|
|
679
760
|
stopConditions: this.stopConditions,
|
|
680
|
-
}
|
|
761
|
+
};
|
|
681
762
|
}
|
|
682
763
|
|
|
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: {
|
|
764
|
+
private _resolveContinueTurnInput(options: {
|
|
692
765
|
prompt?: string | ModelMessage[];
|
|
693
766
|
messages?: ModelMessage[];
|
|
694
|
-
}):
|
|
695
|
-
| { mode: 'prompt'; prompt: HarnessAgentPrompt }
|
|
696
|
-
| {
|
|
697
|
-
mode: 'continue';
|
|
698
|
-
toolApprovalContinuations: readonly HarnessAgentToolApprovalContinuation[];
|
|
699
|
-
toolResultContinuations: readonly HarnessAgentToolResultContinuation[];
|
|
700
|
-
} {
|
|
767
|
+
}): HarnessAgentContinueTurnInput | undefined {
|
|
701
768
|
if (typeof options.prompt === 'string') {
|
|
702
|
-
return
|
|
769
|
+
return undefined;
|
|
703
770
|
}
|
|
704
771
|
const messages = Array.isArray(options.prompt)
|
|
705
772
|
? options.prompt
|
|
@@ -714,15 +781,36 @@ export class HarnessAgent<
|
|
|
714
781
|
toolResultContinuations.length > 0
|
|
715
782
|
) {
|
|
716
783
|
return {
|
|
717
|
-
mode: 'continue',
|
|
718
784
|
toolApprovalContinuations,
|
|
719
785
|
toolResultContinuations,
|
|
720
786
|
};
|
|
721
787
|
}
|
|
788
|
+
}
|
|
789
|
+
return undefined;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/*
|
|
793
|
+
* Reduce AI SDK input to the single user message the harness should run
|
|
794
|
+
* for this turn. The harness session owns prior-turn state (system
|
|
795
|
+
* prompt, assistant turns, tool results) — we never replay it. A bare
|
|
796
|
+
* string is forwarded as-is; a message array is collapsed to its last
|
|
797
|
+
* `role: 'user'` entry. Inputs whose only messages are non-user (system,
|
|
798
|
+
* assistant, tool) have no fresh user input and are rejected.
|
|
799
|
+
*/
|
|
800
|
+
private _resolvePromptTurnInput(options: {
|
|
801
|
+
prompt?: string | ModelMessage[];
|
|
802
|
+
messages?: ModelMessage[];
|
|
803
|
+
}): HarnessAgentPrompt {
|
|
804
|
+
if (typeof options.prompt === 'string') {
|
|
805
|
+
return options.prompt;
|
|
806
|
+
}
|
|
807
|
+
const messages = Array.isArray(options.prompt)
|
|
808
|
+
? options.prompt
|
|
809
|
+
: options.messages;
|
|
810
|
+
if (Array.isArray(messages)) {
|
|
722
811
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
723
812
|
const message = messages[i];
|
|
724
|
-
if (message?.role === 'user')
|
|
725
|
-
return { mode: 'prompt', prompt: message };
|
|
813
|
+
if (message?.role === 'user') return message;
|
|
726
814
|
}
|
|
727
815
|
throw new Error(
|
|
728
816
|
'HarnessAgent: messages must contain at least one `role: "user"` entry.',
|
|
@@ -731,15 +819,137 @@ export class HarnessAgent<
|
|
|
731
819
|
throw new Error('HarnessAgent: either `prompt` or `messages` is required.');
|
|
732
820
|
}
|
|
733
821
|
|
|
822
|
+
private async _preparePromptTurnInput(
|
|
823
|
+
options: AgentCallParameters<
|
|
824
|
+
CALL_OPTIONS,
|
|
825
|
+
HarnessAllTools<THarness, TUserTools>,
|
|
826
|
+
RUNTIME_CONTEXT
|
|
827
|
+
> &
|
|
828
|
+
HarnessAgentCallExtensions,
|
|
829
|
+
): Promise<PreparedHarnessAgentPromptTurnInput<THarness, TUserTools>> {
|
|
830
|
+
let callOptions = options;
|
|
831
|
+
if (
|
|
832
|
+
this.settings.callOptionsSchema != null &&
|
|
833
|
+
options.options !== undefined
|
|
834
|
+
) {
|
|
835
|
+
const validatedOptions = await validateTypes({
|
|
836
|
+
value: options.options,
|
|
837
|
+
schema: this.settings.callOptionsSchema,
|
|
838
|
+
context: { field: 'options' },
|
|
839
|
+
});
|
|
840
|
+
callOptions = {
|
|
841
|
+
...options,
|
|
842
|
+
options: validatedOptions,
|
|
843
|
+
} as unknown as typeof options;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
const {
|
|
847
|
+
session: _session,
|
|
848
|
+
abortSignal: _abortSignal,
|
|
849
|
+
timeout: _timeout,
|
|
850
|
+
onStart: _onStart,
|
|
851
|
+
experimental_onStart: _experimentalOnStart,
|
|
852
|
+
onStepStart: _onStepStart,
|
|
853
|
+
experimental_onStepStart: _experimentalOnStepStart,
|
|
854
|
+
onToolExecutionStart: _onToolExecutionStart,
|
|
855
|
+
experimental_onToolCallStart: _experimentalOnToolCallStart,
|
|
856
|
+
onToolExecutionEnd: _onToolExecutionEnd,
|
|
857
|
+
experimental_onToolCallFinish: _experimentalOnToolCallFinish,
|
|
858
|
+
onStepEnd: _onStepEnd,
|
|
859
|
+
onStepFinish: _onStepFinish,
|
|
860
|
+
onEnd: _onEnd,
|
|
861
|
+
onFinish: _onFinish,
|
|
862
|
+
experimental_sandbox: _experimentalSandbox,
|
|
863
|
+
...promptOptions
|
|
864
|
+
} = callOptions;
|
|
865
|
+
|
|
866
|
+
const baseCallArgs = {
|
|
867
|
+
skills: this.settings.skills,
|
|
868
|
+
instructions: this.settings.instructions,
|
|
869
|
+
tools: this.settings.tools,
|
|
870
|
+
...promptOptions,
|
|
871
|
+
};
|
|
872
|
+
const preparedCallArgs =
|
|
873
|
+
(await this.settings.prepareCall?.(
|
|
874
|
+
baseCallArgs as Parameters<
|
|
875
|
+
NonNullable<
|
|
876
|
+
HarnessAgentSettings<
|
|
877
|
+
THarness,
|
|
878
|
+
TUserTools,
|
|
879
|
+
RUNTIME_CONTEXT,
|
|
880
|
+
OUTPUT,
|
|
881
|
+
CALL_OPTIONS
|
|
882
|
+
>['prepareCall']
|
|
883
|
+
>
|
|
884
|
+
>[0],
|
|
885
|
+
)) ?? baseCallArgs;
|
|
886
|
+
if (this._resolveContinueTurnInput(preparedCallArgs) != null) {
|
|
887
|
+
throw new Error(
|
|
888
|
+
'HarnessAgent.prepareCall must return a fresh user prompt, not a tool continuation.',
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
return {
|
|
893
|
+
prompt: this._resolvePromptTurnInput(preparedCallArgs),
|
|
894
|
+
...this._prepareTurnSettings({
|
|
895
|
+
skills: preparedCallArgs.skills,
|
|
896
|
+
instructions: preparedCallArgs.instructions,
|
|
897
|
+
tools: preparedCallArgs.tools,
|
|
898
|
+
}),
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
private _prepareContinueTurnInput(options: {
|
|
903
|
+
toolApprovalContinuations?: readonly HarnessAgentToolApprovalContinuation[];
|
|
904
|
+
toolResultContinuations?: readonly HarnessAgentToolResultContinuation[];
|
|
905
|
+
}): PreparedHarnessAgentContinueTurnInput<THarness, TUserTools> {
|
|
906
|
+
return {
|
|
907
|
+
...this._prepareTurnSettings({
|
|
908
|
+
skills: this.settings.skills,
|
|
909
|
+
instructions: this.settings.instructions,
|
|
910
|
+
tools: this.settings.tools,
|
|
911
|
+
}),
|
|
912
|
+
toolApprovalContinuations: options.toolApprovalContinuations ?? [],
|
|
913
|
+
toolResultContinuations: options.toolResultContinuations ?? [],
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
private _prepareTurnSettings(options: {
|
|
918
|
+
skills?: ReadonlyArray<HarnessAgentSkill>;
|
|
919
|
+
instructions?: string;
|
|
920
|
+
tools?: TUserTools;
|
|
921
|
+
}): PreparedHarnessAgentTurnSettings<THarness, TUserTools> {
|
|
922
|
+
const userTools = options.tools ?? ({} as TUserTools);
|
|
923
|
+
const tools = {
|
|
924
|
+
...this.settings.harness.builtinTools,
|
|
925
|
+
...userTools,
|
|
926
|
+
} as HarnessAllTools<THarness, TUserTools>;
|
|
927
|
+
const toolFiltering = resolveHarnessAgentToolFiltering({
|
|
928
|
+
harness: this.settings.harness,
|
|
929
|
+
userTools,
|
|
930
|
+
allTools: tools,
|
|
931
|
+
activeTools: this.settings.activeTools,
|
|
932
|
+
inactiveTools: this.settings.inactiveTools,
|
|
933
|
+
});
|
|
934
|
+
return {
|
|
935
|
+
skills: options.skills ?? [],
|
|
936
|
+
instructions: options.instructions,
|
|
937
|
+
tools,
|
|
938
|
+
activeTools: toolFiltering.activeUserTools,
|
|
939
|
+
toolSpecs: this._toToolSpecs(toolFiltering.activeUserTools),
|
|
940
|
+
builtinToolFiltering: toolFiltering.builtinToolFiltering,
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
|
|
734
944
|
/*
|
|
735
945
|
* Wire-format projection of user-defined tools only. Harness builtins are
|
|
736
946
|
* executed by the runtime and the bridge already knows about them — we
|
|
737
947
|
* never re-declare them over the wire.
|
|
738
948
|
*/
|
|
739
|
-
private _toToolSpecs(): HarnessAgentToolSpec[] {
|
|
949
|
+
private _toToolSpecs(activeUserTools: TUserTools): HarnessAgentToolSpec[] {
|
|
740
950
|
const specs: HarnessAgentToolSpec[] = [];
|
|
741
951
|
for (const [name, tool] of Object.entries(
|
|
742
|
-
|
|
952
|
+
activeUserTools as Record<string, unknown>,
|
|
743
953
|
)) {
|
|
744
954
|
const t = tool as {
|
|
745
955
|
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
|
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
type HarnessV1PromptControl,
|
|
10
10
|
type HarnessV1ResponseFormat,
|
|
11
11
|
type HarnessV1Session,
|
|
12
|
+
type HarnessV1Skill,
|
|
12
13
|
type HarnessV1StreamPart,
|
|
13
14
|
type HarnessV1ToolSpec,
|
|
14
15
|
} from '../../v1';
|
|
@@ -80,6 +81,7 @@ export function runPrompt<
|
|
|
80
81
|
mode?: 'prompt' | 'continue';
|
|
81
82
|
/** Required for `mode: 'prompt'`; absent for `mode: 'continue'`. */
|
|
82
83
|
prompt?: HarnessV1Prompt;
|
|
84
|
+
skills?: ReadonlyArray<HarnessV1Skill>;
|
|
83
85
|
instructions: string | undefined;
|
|
84
86
|
tools: TOOLS;
|
|
85
87
|
activeTools?: ToolSet;
|
|
@@ -179,6 +181,7 @@ export function runPrompt<
|
|
|
179
181
|
input.mode === 'continue'
|
|
180
182
|
? emit =>
|
|
181
183
|
input.session.doContinueTurn({
|
|
184
|
+
skills: input.skills ?? [],
|
|
182
185
|
responseFormat: input.responseFormat,
|
|
183
186
|
tools: input.toolSpecs,
|
|
184
187
|
instructions: input.instructions,
|
|
@@ -193,6 +196,7 @@ export function runPrompt<
|
|
|
193
196
|
}
|
|
194
197
|
return input.session.doPromptTurn({
|
|
195
198
|
prompt: input.prompt,
|
|
199
|
+
skills: input.skills ?? [],
|
|
196
200
|
responseFormat: input.responseFormat,
|
|
197
201
|
tools: input.toolSpecs,
|
|
198
202
|
instructions: input.instructions,
|
|
@@ -734,12 +738,18 @@ export function runPrompt<
|
|
|
734
738
|
// paths help debugging); the consumer-facing settle uses the
|
|
735
739
|
// workDir-stripped one, like every other forwarded part.
|
|
736
740
|
await telemetry.error(value.error);
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
741
|
+
// A turn the caller itself aborted ends with an error-shaped part by
|
|
742
|
+
// construction; diagnosing the caller's own signal to stderr reads
|
|
743
|
+
// as a malfunction. `settleFailure` below still reports it as an
|
|
744
|
+
// abort to the consumer.
|
|
745
|
+
if (!input.abortSignal?.aborted) {
|
|
746
|
+
logBridgeError({
|
|
747
|
+
harnessId: input.harness.harnessId,
|
|
748
|
+
sessionId: input.session.sessionId,
|
|
749
|
+
context: 'harness stream error',
|
|
750
|
+
error: value.error,
|
|
751
|
+
});
|
|
752
|
+
}
|
|
743
753
|
settleFailure(displayValue.error);
|
|
744
754
|
return;
|
|
745
755
|
}
|