@ai-sdk/harness 1.0.93 → 1.0.95
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 +23 -0
- package/dist/agent/index.d.ts +22 -19
- package/dist/agent/index.js +138 -10
- package/dist/agent/index.js.map +1 -1
- package/dist/index.d.ts +48 -14
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/index.d.ts +7 -1
- package/dist/utils/index.js +42 -0
- package/dist/utils/index.js.map +1 -1
- package/package.json +5 -5
- package/src/agent/harness-agent-session.ts +8 -0
- package/src/agent/harness-agent-settings.ts +7 -6
- package/src/agent/harness-agent.ts +7 -1
- package/src/agent/internal/run-prompt.ts +48 -5
- package/src/agent/internal/strip-work-dir.ts +102 -0
- package/src/agent/internal/translate-stream-part.ts +5 -0
- package/src/agent/internal/turn-telemetry.ts +34 -8
- package/src/utils/bridge-token.ts +18 -0
- package/src/utils/index.ts +1 -0
- package/src/v1/harness-v1-bridge-protocol.ts +6 -0
- package/src/v1/harness-v1-lifecycle-state.ts +7 -0
- package/src/v1/harness-v1-session.ts +0 -14
- package/src/v1/harness-v1-stream-part.ts +31 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/harness",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.95",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -44,9 +44,9 @@
|
|
|
44
44
|
}
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@ai-sdk/provider": "4.0.
|
|
48
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
49
|
-
"ai": "7.0.
|
|
47
|
+
"@ai-sdk/provider": "4.0.9",
|
|
48
|
+
"@ai-sdk/provider-utils": "5.0.34",
|
|
49
|
+
"ai": "7.0.86"
|
|
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.86",
|
|
62
62
|
"@opentelemetry/sdk-trace-base": "2.7.1",
|
|
63
63
|
"@types/node": "22.19.19",
|
|
64
64
|
"@types/ws": "^8.5.13",
|
|
@@ -195,6 +195,7 @@ export class HarnessAgentSession {
|
|
|
195
195
|
OUTPUT extends Output,
|
|
196
196
|
>(options: {
|
|
197
197
|
prompt: HarnessAgentPrompt;
|
|
198
|
+
model: string | undefined;
|
|
198
199
|
skills: ReadonlyArray<HarnessV1Skill>;
|
|
199
200
|
instructions: string | undefined;
|
|
200
201
|
tools: TOOLS;
|
|
@@ -211,6 +212,7 @@ export class HarnessAgentSession {
|
|
|
211
212
|
const session = this.requireReusableSession();
|
|
212
213
|
this.requirePromptableTurn();
|
|
213
214
|
this.persistedTurnSettings = {
|
|
215
|
+
...(options.model == null ? {} : { model: options.model }),
|
|
214
216
|
skills: options.skills,
|
|
215
217
|
...(options.instructions == null
|
|
216
218
|
? {}
|
|
@@ -230,6 +232,7 @@ export class HarnessAgentSession {
|
|
|
230
232
|
harness: this.harness,
|
|
231
233
|
session,
|
|
232
234
|
prompt: options.prompt,
|
|
235
|
+
model: options.model,
|
|
233
236
|
skills: options.skills,
|
|
234
237
|
instructions: options.instructions,
|
|
235
238
|
tools: options.tools,
|
|
@@ -290,6 +293,7 @@ export class HarnessAgentSession {
|
|
|
290
293
|
RUNTIME_CONTEXT extends Context,
|
|
291
294
|
OUTPUT extends Output,
|
|
292
295
|
>(options: {
|
|
296
|
+
model: string | undefined;
|
|
293
297
|
skills: ReadonlyArray<HarnessV1Skill>;
|
|
294
298
|
instructions: string | undefined;
|
|
295
299
|
tools: TOOLS;
|
|
@@ -312,6 +316,7 @@ export class HarnessAgentSession {
|
|
|
312
316
|
const session = this.requireReusableSession();
|
|
313
317
|
this.requireContinuableTurn();
|
|
314
318
|
const turnSettings = this.resolveActiveTurnSettings({
|
|
319
|
+
model: options.model,
|
|
315
320
|
skills: options.skills,
|
|
316
321
|
instructions: options.instructions,
|
|
317
322
|
tools: options.tools,
|
|
@@ -326,6 +331,7 @@ export class HarnessAgentSession {
|
|
|
326
331
|
harness: this.harness,
|
|
327
332
|
session,
|
|
328
333
|
mode: 'continue',
|
|
334
|
+
model: turnSettings.persisted.model,
|
|
329
335
|
skills: turnSettings.persisted.skills,
|
|
330
336
|
instructions: turnSettings.persisted.instructions,
|
|
331
337
|
tools: turnSettings.tools as TOOLS,
|
|
@@ -753,6 +759,7 @@ export class HarnessAgentSession {
|
|
|
753
759
|
}
|
|
754
760
|
|
|
755
761
|
private resolveActiveTurnSettings(options: {
|
|
762
|
+
model: string | undefined;
|
|
756
763
|
skills: ReadonlyArray<HarnessV1Skill>;
|
|
757
764
|
instructions: string | undefined;
|
|
758
765
|
tools: ToolSet;
|
|
@@ -766,6 +773,7 @@ export class HarnessAgentSession {
|
|
|
766
773
|
const persisted =
|
|
767
774
|
this.persistedTurnSettings ??
|
|
768
775
|
({
|
|
776
|
+
...(options.model == null ? {} : { model: options.model }),
|
|
769
777
|
skills: options.skills,
|
|
770
778
|
...(options.instructions == null
|
|
771
779
|
? {}
|
|
@@ -80,8 +80,8 @@ type HarnessTools<TOOLS extends ToolSet> = ActiveTools<NoInfer<TOOLS>>;
|
|
|
80
80
|
*
|
|
81
81
|
* Prompt, abortSignal, callbacks, and custom call options belong on the
|
|
82
82
|
* `AgentCallParameters` / `AgentStreamParameters` passed to `generate` /
|
|
83
|
-
* `stream`. `prepareCall` can derive turn-scoped skills, instructions,
|
|
84
|
-
* tools from those custom call options.
|
|
83
|
+
* `stream`. `prepareCall` can derive turn-scoped model, skills, instructions,
|
|
84
|
+
* and tools from those custom call options.
|
|
85
85
|
*/
|
|
86
86
|
type HarnessAgentToolFilteringSettings<TOOLS extends ToolSet> =
|
|
87
87
|
| {
|
|
@@ -122,8 +122,9 @@ export type HarnessAgentSettings<
|
|
|
122
122
|
readonly id?: string;
|
|
123
123
|
|
|
124
124
|
/**
|
|
125
|
-
* Model identifier
|
|
126
|
-
*
|
|
125
|
+
* Model identifier used by the harness adapter. Supported values are
|
|
126
|
+
* defined by the selected harness. `prepareCall` can replace it between
|
|
127
|
+
* completed turns.
|
|
127
128
|
*/
|
|
128
129
|
readonly model?: string;
|
|
129
130
|
|
|
@@ -203,7 +204,7 @@ export type HarnessAgentSettings<
|
|
|
203
204
|
NoInfer<OUTPUT>,
|
|
204
205
|
CALL_OPTIONS
|
|
205
206
|
>,
|
|
206
|
-
'skills' | 'instructions' | 'tools'
|
|
207
|
+
'model' | 'skills' | 'instructions' | 'tools'
|
|
207
208
|
>,
|
|
208
209
|
) => MaybePromiseLike<
|
|
209
210
|
Pick<
|
|
@@ -214,7 +215,7 @@ export type HarnessAgentSettings<
|
|
|
214
215
|
NoInfer<OUTPUT>,
|
|
215
216
|
CALL_OPTIONS
|
|
216
217
|
>,
|
|
217
|
-
'skills' | 'instructions' | 'tools'
|
|
218
|
+
'model' | 'skills' | 'instructions' | 'tools'
|
|
218
219
|
> &
|
|
219
220
|
Omit<Prompt, 'system' | 'instructions' | 'allowSystemInMessages'>
|
|
220
221
|
>;
|
|
@@ -94,6 +94,7 @@ type PreparedHarnessAgentTurnSettings<
|
|
|
94
94
|
THarness extends HarnessAgentAdapter<any>,
|
|
95
95
|
TUserTools extends ToolSet,
|
|
96
96
|
> = {
|
|
97
|
+
model: string | undefined;
|
|
97
98
|
skills: ReadonlyArray<HarnessAgentSkill>;
|
|
98
99
|
instructions: string | undefined;
|
|
99
100
|
tools: HarnessAllTools<THarness, TUserTools>;
|
|
@@ -499,7 +500,6 @@ export class HarnessAgent<
|
|
|
499
500
|
|
|
500
501
|
try {
|
|
501
502
|
const baseStartOptions = {
|
|
502
|
-
model: this.settings.model,
|
|
503
503
|
sessionId,
|
|
504
504
|
resumeFrom: validatedResumeFrom,
|
|
505
505
|
continueFrom: effectiveContinueFrom,
|
|
@@ -746,6 +746,7 @@ export class HarnessAgent<
|
|
|
746
746
|
responseFormat: HarnessV1ResponseFormat | undefined;
|
|
747
747
|
}) {
|
|
748
748
|
return {
|
|
749
|
+
model: input.turnSettings.model,
|
|
749
750
|
skills: input.turnSettings.skills,
|
|
750
751
|
instructions: input.turnSettings.instructions,
|
|
751
752
|
tools: input.turnSettings.tools,
|
|
@@ -864,6 +865,7 @@ export class HarnessAgent<
|
|
|
864
865
|
} = callOptions;
|
|
865
866
|
|
|
866
867
|
const baseCallArgs = {
|
|
868
|
+
model: this.settings.model,
|
|
867
869
|
skills: this.settings.skills,
|
|
868
870
|
instructions: this.settings.instructions,
|
|
869
871
|
tools: this.settings.tools,
|
|
@@ -892,6 +894,7 @@ export class HarnessAgent<
|
|
|
892
894
|
return {
|
|
893
895
|
prompt: this._resolvePromptTurnInput(preparedCallArgs),
|
|
894
896
|
...this._prepareTurnSettings({
|
|
897
|
+
model: preparedCallArgs.model,
|
|
895
898
|
skills: preparedCallArgs.skills,
|
|
896
899
|
instructions: preparedCallArgs.instructions,
|
|
897
900
|
tools: preparedCallArgs.tools,
|
|
@@ -905,6 +908,7 @@ export class HarnessAgent<
|
|
|
905
908
|
}): PreparedHarnessAgentContinueTurnInput<THarness, TUserTools> {
|
|
906
909
|
return {
|
|
907
910
|
...this._prepareTurnSettings({
|
|
911
|
+
model: this.settings.model,
|
|
908
912
|
skills: this.settings.skills,
|
|
909
913
|
instructions: this.settings.instructions,
|
|
910
914
|
tools: this.settings.tools,
|
|
@@ -915,6 +919,7 @@ export class HarnessAgent<
|
|
|
915
919
|
}
|
|
916
920
|
|
|
917
921
|
private _prepareTurnSettings(options: {
|
|
922
|
+
model?: string;
|
|
918
923
|
skills?: ReadonlyArray<HarnessAgentSkill>;
|
|
919
924
|
instructions?: string;
|
|
920
925
|
tools?: TUserTools;
|
|
@@ -932,6 +937,7 @@ export class HarnessAgent<
|
|
|
932
937
|
inactiveTools: this.settings.inactiveTools,
|
|
933
938
|
});
|
|
934
939
|
return {
|
|
940
|
+
model: options.model,
|
|
935
941
|
skills: options.skills ?? [],
|
|
936
942
|
instructions: options.instructions,
|
|
937
943
|
tools,
|
|
@@ -44,7 +44,7 @@ import type { HarnessAgentToolResultContinuation } from '../harness-agent-tool-r
|
|
|
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';
|
|
47
|
-
import { stripWorkDir } from './strip-work-dir';
|
|
47
|
+
import { createToolInputWorkDirStripper, stripWorkDir } from './strip-work-dir';
|
|
48
48
|
import {
|
|
49
49
|
createTurnTelemetry,
|
|
50
50
|
type TurnContentPart,
|
|
@@ -81,6 +81,7 @@ export function runPrompt<
|
|
|
81
81
|
mode?: 'prompt' | 'continue';
|
|
82
82
|
/** Required for `mode: 'prompt'`; absent for `mode: 'continue'`. */
|
|
83
83
|
prompt?: HarnessV1Prompt;
|
|
84
|
+
model?: string;
|
|
84
85
|
skills?: ReadonlyArray<HarnessV1Skill>;
|
|
85
86
|
instructions: string | undefined;
|
|
86
87
|
tools: TOOLS;
|
|
@@ -137,12 +138,27 @@ export function runPrompt<
|
|
|
137
138
|
const onPendingToolResult = input.onPendingToolResult ?? (() => {});
|
|
138
139
|
const onToolResultSettled = input.onToolResultSettled ?? (() => {});
|
|
139
140
|
const activeTools = input.activeTools ?? input.tools;
|
|
141
|
+
const activeToolNames = Object.keys(input.tools).filter(
|
|
142
|
+
toolName =>
|
|
143
|
+
Object.prototype.hasOwnProperty.call(activeTools, toolName) ||
|
|
144
|
+
(Object.prototype.hasOwnProperty.call(
|
|
145
|
+
input.harness.builtinTools,
|
|
146
|
+
toolName,
|
|
147
|
+
) &&
|
|
148
|
+
isHarnessV1BuiltinToolIncluded({
|
|
149
|
+
toolName,
|
|
150
|
+
toolFiltering: input.builtinToolFiltering,
|
|
151
|
+
})),
|
|
152
|
+
);
|
|
140
153
|
|
|
141
154
|
const telemetry = createTurnTelemetry({
|
|
142
155
|
telemetry: input.telemetry,
|
|
143
156
|
harnessId: input.harness.harnessId,
|
|
144
|
-
modelId: input.
|
|
157
|
+
modelId: input.model,
|
|
145
158
|
instructions: input.instructions,
|
|
159
|
+
tools: input.tools,
|
|
160
|
+
activeToolNames,
|
|
161
|
+
toolSpecs: input.toolSpecs,
|
|
146
162
|
promptText: input.prompt != null ? promptToText(input.prompt) : '',
|
|
147
163
|
runtimeContext: input.runtimeContext,
|
|
148
164
|
});
|
|
@@ -181,6 +197,7 @@ export function runPrompt<
|
|
|
181
197
|
input.mode === 'continue'
|
|
182
198
|
? emit =>
|
|
183
199
|
input.session.doContinueTurn({
|
|
200
|
+
model: input.model,
|
|
184
201
|
skills: input.skills ?? [],
|
|
185
202
|
responseFormat: input.responseFormat,
|
|
186
203
|
tools: input.toolSpecs,
|
|
@@ -196,6 +213,7 @@ export function runPrompt<
|
|
|
196
213
|
}
|
|
197
214
|
return input.session.doPromptTurn({
|
|
198
215
|
prompt: input.prompt,
|
|
216
|
+
model: input.model,
|
|
199
217
|
skills: input.skills ?? [],
|
|
200
218
|
responseFormat: input.responseFormat,
|
|
201
219
|
tools: input.toolSpecs,
|
|
@@ -220,6 +238,9 @@ export function runPrompt<
|
|
|
220
238
|
const { stream, control } = bridge;
|
|
221
239
|
input.onPromptControlAvailable?.(control);
|
|
222
240
|
const reader = stream.getReader();
|
|
241
|
+
const stripToolInputWorkDir = createToolInputWorkDirStripper({
|
|
242
|
+
sessionWorkDir: input.sessionWorkDir,
|
|
243
|
+
});
|
|
223
244
|
const toolCallsByToolCallId = new Map<string, ToolCallTextStreamPart>();
|
|
224
245
|
const rawToolCallsByToolCallId = new Map<
|
|
225
246
|
string,
|
|
@@ -534,7 +555,7 @@ export function runPrompt<
|
|
|
534
555
|
input: approval.input,
|
|
535
556
|
} satisfies Extract<HarnessV1StreamPart, { type: 'tool-call' }>);
|
|
536
557
|
|
|
537
|
-
await telemetry.start(input.
|
|
558
|
+
await telemetry.start(input.model);
|
|
538
559
|
await telemetry.toolStart({
|
|
539
560
|
toolCallId: rawToolCall.toolCallId,
|
|
540
561
|
toolName: rawToolCall.toolName,
|
|
@@ -649,9 +670,9 @@ export function runPrompt<
|
|
|
649
670
|
}
|
|
650
671
|
|
|
651
672
|
// Begin the operation span on stream-start, using the runtime-resolved
|
|
652
|
-
// model the adapter reports (falling back to the
|
|
673
|
+
// model the adapter reports (falling back to the requested turn model).
|
|
653
674
|
if (value.type === 'stream-start') {
|
|
654
|
-
await telemetry.start(value.modelId ?? input.
|
|
675
|
+
await telemetry.start(value.modelId ?? input.model);
|
|
655
676
|
}
|
|
656
677
|
|
|
657
678
|
// Open a step span lazily before the first content of each step.
|
|
@@ -664,6 +685,28 @@ export function runPrompt<
|
|
|
664
685
|
await telemetry.ensureStepOpen();
|
|
665
686
|
}
|
|
666
687
|
|
|
688
|
+
if (
|
|
689
|
+
value.type === 'tool-input-start' ||
|
|
690
|
+
value.type === 'tool-input-delta' ||
|
|
691
|
+
value.type === 'tool-input-end'
|
|
692
|
+
) {
|
|
693
|
+
if (
|
|
694
|
+
settledHostToolCallIds.has(value.id) ||
|
|
695
|
+
settledBuiltinApprovalToolCallIds.has(value.id)
|
|
696
|
+
) {
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
for (const displayValue of stripToolInputWorkDir(value)) {
|
|
700
|
+
for (const part of translateStreamPart<TOOLS>(
|
|
701
|
+
displayValue,
|
|
702
|
+
translateOptions,
|
|
703
|
+
)) {
|
|
704
|
+
result.enqueue(part);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
|
|
667
710
|
/*
|
|
668
711
|
* Strip the session working-directory prefix for everything the
|
|
669
712
|
* consumer sees. The original `value` is kept intact for host tool
|
|
@@ -1,5 +1,57 @@
|
|
|
1
1
|
import type { HarnessV1StreamPart } from '../../v1';
|
|
2
2
|
|
|
3
|
+
type ToolInputStreamPart = Extract<
|
|
4
|
+
HarnessV1StreamPart,
|
|
5
|
+
{
|
|
6
|
+
type: 'tool-input-start' | 'tool-input-delta' | 'tool-input-end';
|
|
7
|
+
}
|
|
8
|
+
>;
|
|
9
|
+
|
|
10
|
+
export function createToolInputWorkDirStripper({
|
|
11
|
+
sessionWorkDir,
|
|
12
|
+
}: {
|
|
13
|
+
sessionWorkDir: string;
|
|
14
|
+
}): (part: ToolInputStreamPart) => ToolInputStreamPart[] {
|
|
15
|
+
const pendingByToolCallId = new Map<string, string>();
|
|
16
|
+
|
|
17
|
+
return part => {
|
|
18
|
+
if (sessionWorkDir.length === 0) return [part];
|
|
19
|
+
|
|
20
|
+
if (part.type === 'tool-input-start') {
|
|
21
|
+
pendingByToolCallId.set(part.id, '');
|
|
22
|
+
return [part];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (part.type === 'tool-input-delta') {
|
|
26
|
+
const stripped = stripStreamingString({
|
|
27
|
+
value: (pendingByToolCallId.get(part.id) ?? '') + part.delta,
|
|
28
|
+
workDir: sessionWorkDir,
|
|
29
|
+
final: false,
|
|
30
|
+
});
|
|
31
|
+
pendingByToolCallId.set(part.id, stripped.pending);
|
|
32
|
+
return stripped.output.length === 0
|
|
33
|
+
? []
|
|
34
|
+
: [{ ...part, delta: stripped.output }];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const pending = pendingByToolCallId.get(part.id);
|
|
38
|
+
pendingByToolCallId.delete(part.id);
|
|
39
|
+
if (pending == null || pending.length === 0) return [part];
|
|
40
|
+
|
|
41
|
+
const stripped = stripStreamingString({
|
|
42
|
+
value: pending,
|
|
43
|
+
workDir: sessionWorkDir,
|
|
44
|
+
final: true,
|
|
45
|
+
});
|
|
46
|
+
return stripped.output.length === 0
|
|
47
|
+
? [part]
|
|
48
|
+
: [
|
|
49
|
+
{ type: 'tool-input-delta', id: part.id, delta: stripped.output },
|
|
50
|
+
part,
|
|
51
|
+
];
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
3
55
|
/**
|
|
4
56
|
* Remove the session working-directory prefix from path-bearing fields of a
|
|
5
57
|
* stream event, returning a new event for display to consumers.
|
|
@@ -23,6 +75,8 @@ export function stripWorkDir(
|
|
|
23
75
|
if (sessionWorkDir.length === 0) return part;
|
|
24
76
|
|
|
25
77
|
switch (part.type) {
|
|
78
|
+
case 'tool-input-delta':
|
|
79
|
+
return { ...part, delta: stripString(part.delta, sessionWorkDir) };
|
|
26
80
|
case 'tool-call':
|
|
27
81
|
return { ...part, input: stripString(part.input, sessionWorkDir) };
|
|
28
82
|
case 'tool-result':
|
|
@@ -50,6 +104,54 @@ function stripString(value: string, workDir: string): string {
|
|
|
50
104
|
return value.split(`${workDir}/`).join('').split(workDir).join('.');
|
|
51
105
|
}
|
|
52
106
|
|
|
107
|
+
function stripStreamingString({
|
|
108
|
+
value,
|
|
109
|
+
workDir,
|
|
110
|
+
final,
|
|
111
|
+
}: {
|
|
112
|
+
value: string;
|
|
113
|
+
workDir: string;
|
|
114
|
+
final: boolean;
|
|
115
|
+
}): { output: string; pending: string } {
|
|
116
|
+
let remaining = value;
|
|
117
|
+
let output = '';
|
|
118
|
+
|
|
119
|
+
while (remaining.length > 0) {
|
|
120
|
+
const matchIndex = remaining.indexOf(workDir);
|
|
121
|
+
if (matchIndex >= 0) {
|
|
122
|
+
output += remaining.slice(0, matchIndex);
|
|
123
|
+
const followingIndex = matchIndex + workDir.length;
|
|
124
|
+
if (followingIndex === remaining.length && !final) {
|
|
125
|
+
return { output, pending: remaining.slice(matchIndex) };
|
|
126
|
+
}
|
|
127
|
+
if (remaining[followingIndex] === '/') {
|
|
128
|
+
remaining = remaining.slice(followingIndex + 1);
|
|
129
|
+
} else {
|
|
130
|
+
output += '.';
|
|
131
|
+
remaining = remaining.slice(followingIndex);
|
|
132
|
+
}
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (final) return { output: output + remaining, pending: '' };
|
|
137
|
+
|
|
138
|
+
let pendingLength = Math.min(remaining.length, workDir.length - 1);
|
|
139
|
+
while (
|
|
140
|
+
pendingLength > 0 &&
|
|
141
|
+
!workDir.startsWith(remaining.slice(-pendingLength))
|
|
142
|
+
) {
|
|
143
|
+
pendingLength -= 1;
|
|
144
|
+
}
|
|
145
|
+
const outputLength = remaining.length - pendingLength;
|
|
146
|
+
return {
|
|
147
|
+
output: output + remaining.slice(0, outputLength),
|
|
148
|
+
pending: remaining.slice(outputLength),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { output, pending: '' };
|
|
153
|
+
}
|
|
154
|
+
|
|
53
155
|
/**
|
|
54
156
|
* Recursively strip the working directory from every string nested in an
|
|
55
157
|
* arbitrary JSON-like value. Non-string leaves are returned unchanged.
|
|
@@ -103,6 +103,11 @@ export function translateStreamPart<TOOLS extends ToolSet>(
|
|
|
103
103
|
} as TextStreamPart<TOOLS>,
|
|
104
104
|
];
|
|
105
105
|
|
|
106
|
+
case 'tool-input-start':
|
|
107
|
+
case 'tool-input-delta':
|
|
108
|
+
case 'tool-input-end':
|
|
109
|
+
return [event as TextStreamPart<TOOLS>];
|
|
110
|
+
|
|
106
111
|
case 'tool-call':
|
|
107
112
|
// Tool-call validation is async (it parses input against the tool's
|
|
108
113
|
// schema) and lives in `run-prompt.ts` where the merged tool set is in
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
generateId,
|
|
3
|
+
type ModelMessage,
|
|
4
|
+
type ToolSet,
|
|
5
|
+
} from '@ai-sdk/provider-utils';
|
|
2
6
|
import { createTelemetryDispatcher } from 'ai/internal';
|
|
3
7
|
import type { LanguageModelUsage, TelemetryOptions } from 'ai';
|
|
8
|
+
import type { HarnessV1ToolSpec } from '../../v1';
|
|
4
9
|
|
|
5
10
|
/*
|
|
6
11
|
* Drives AI SDK's pluggable `Telemetry` lifecycle from a harness turn.
|
|
@@ -13,8 +18,9 @@ import type { LanguageModelUsage, TelemetryOptions } from 'ai';
|
|
|
13
18
|
* step boundary, tool-calls = tool executions, `finish` = operation end. The
|
|
14
19
|
* model-call-only event fields the harness has no value for (sampling params,
|
|
15
20
|
* standardized prompt) are left `undefined` / cast; the fields the integrations
|
|
16
|
-
* actually read (`callId`, `operationId`, `provider`, `modelId`,
|
|
17
|
-
* `toolCall`, `usage`, `finishReason`)
|
|
21
|
+
* actually read (`callId`, `operationId`, `provider`, `modelId`,
|
|
22
|
+
* `instructions`, `messages`, `tools`, `toolCall`, `usage`, `finishReason`)
|
|
23
|
+
* carry real values.
|
|
18
24
|
*
|
|
19
25
|
* Telemetry is opt-in: the framework only drives it when `settings.telemetry`
|
|
20
26
|
* is set (the dispatcher then also honours globally-registered integrations).
|
|
@@ -160,6 +166,9 @@ export function createTurnTelemetry(opts: {
|
|
|
160
166
|
harnessId: string;
|
|
161
167
|
modelId: string | undefined;
|
|
162
168
|
instructions: string | undefined;
|
|
169
|
+
tools: ToolSet;
|
|
170
|
+
activeToolNames: string[];
|
|
171
|
+
toolSpecs: HarnessV1ToolSpec[];
|
|
163
172
|
promptText: string;
|
|
164
173
|
runtimeContext: unknown;
|
|
165
174
|
}): TurnTelemetry {
|
|
@@ -177,6 +186,19 @@ export function createTurnTelemetry(opts: {
|
|
|
177
186
|
const inputMessages: ModelMessage[] = [
|
|
178
187
|
{ role: 'user', content: opts.promptText },
|
|
179
188
|
];
|
|
189
|
+
const languageModelTools =
|
|
190
|
+
opts.toolSpecs.length === 0
|
|
191
|
+
? undefined
|
|
192
|
+
: opts.toolSpecs.map(tool => ({
|
|
193
|
+
type: 'function' as const,
|
|
194
|
+
name: tool.name,
|
|
195
|
+
...(tool.description != null
|
|
196
|
+
? { description: tool.description }
|
|
197
|
+
: {}),
|
|
198
|
+
...(tool.inputSchema != null
|
|
199
|
+
? { inputSchema: tool.inputSchema }
|
|
200
|
+
: {}),
|
|
201
|
+
}));
|
|
180
202
|
|
|
181
203
|
let started = false;
|
|
182
204
|
let stepOpen = false;
|
|
@@ -211,9 +233,9 @@ export function createTurnTelemetry(opts: {
|
|
|
211
233
|
operationId: 'ai.harness',
|
|
212
234
|
provider,
|
|
213
235
|
modelId,
|
|
214
|
-
tools:
|
|
236
|
+
tools: opts.tools,
|
|
215
237
|
toolChoice: undefined,
|
|
216
|
-
activeTools:
|
|
238
|
+
activeTools: opts.activeToolNames,
|
|
217
239
|
maxRetries: 0,
|
|
218
240
|
timeout: undefined,
|
|
219
241
|
headers: undefined,
|
|
@@ -243,13 +265,14 @@ export function createTurnTelemetry(opts: {
|
|
|
243
265
|
provider,
|
|
244
266
|
modelId,
|
|
245
267
|
stepNumber,
|
|
246
|
-
tools:
|
|
268
|
+
tools: opts.tools,
|
|
247
269
|
toolChoice: undefined,
|
|
248
|
-
activeTools:
|
|
270
|
+
activeTools: opts.activeToolNames,
|
|
249
271
|
steps: new Array(stepNumber),
|
|
250
272
|
providerOptions: undefined,
|
|
251
273
|
output: undefined,
|
|
252
274
|
runtimeContext,
|
|
275
|
+
instructions: opts.instructions,
|
|
253
276
|
messages: inputMessages,
|
|
254
277
|
}),
|
|
255
278
|
);
|
|
@@ -260,8 +283,9 @@ export function createTurnTelemetry(opts: {
|
|
|
260
283
|
callId,
|
|
261
284
|
provider,
|
|
262
285
|
modelId,
|
|
286
|
+
instructions: opts.instructions,
|
|
263
287
|
messages: inputMessages,
|
|
264
|
-
tools:
|
|
288
|
+
tools: languageModelTools,
|
|
265
289
|
}),
|
|
266
290
|
);
|
|
267
291
|
};
|
|
@@ -279,6 +303,8 @@ export function createTurnTelemetry(opts: {
|
|
|
279
303
|
await dispatcher.onLanguageModelCallEnd?.(
|
|
280
304
|
cast<'onLanguageModelCallEnd'>({
|
|
281
305
|
callId,
|
|
306
|
+
provider,
|
|
307
|
+
modelId,
|
|
282
308
|
finishReason,
|
|
283
309
|
responseId: callId,
|
|
284
310
|
usage,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import type { HarnessV1PortEndpoint } from '../v1';
|
|
3
|
+
|
|
4
|
+
export function createBridgeToken(): string {
|
|
5
|
+
return randomBytes(32).toString('hex');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function withBridgeToken({
|
|
9
|
+
endpoint,
|
|
10
|
+
token,
|
|
11
|
+
}: {
|
|
12
|
+
endpoint: HarnessV1PortEndpoint;
|
|
13
|
+
token: string;
|
|
14
|
+
}): HarnessV1PortEndpoint {
|
|
15
|
+
const bridgeUrl = new URL(endpoint.url);
|
|
16
|
+
bridgeUrl.searchParams.set('agent_bridge_token', token);
|
|
17
|
+
return { ...endpoint, url: bridgeUrl.toString() };
|
|
18
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -21,6 +21,9 @@ import {
|
|
|
21
21
|
harnessV1TextStartPartSchema,
|
|
22
22
|
harnessV1ToolApprovalRequestPartSchema,
|
|
23
23
|
harnessV1ToolCallPartSchema,
|
|
24
|
+
harnessV1ToolInputDeltaPartSchema,
|
|
25
|
+
harnessV1ToolInputEndPartSchema,
|
|
26
|
+
harnessV1ToolInputStartPartSchema,
|
|
24
27
|
harnessV1ToolResultPartSchema,
|
|
25
28
|
} from './harness-v1-stream-part';
|
|
26
29
|
|
|
@@ -213,6 +216,9 @@ export const harnessV1BridgeOutboundMessageSchema = z.discriminatedUnion(
|
|
|
213
216
|
harnessV1ReasoningStartPartSchema,
|
|
214
217
|
harnessV1ReasoningDeltaPartSchema,
|
|
215
218
|
harnessV1ReasoningEndPartSchema,
|
|
219
|
+
harnessV1ToolInputStartPartSchema,
|
|
220
|
+
harnessV1ToolInputDeltaPartSchema,
|
|
221
|
+
harnessV1ToolInputEndPartSchema,
|
|
216
222
|
harnessV1ToolCallPartSchema,
|
|
217
223
|
harnessV1ToolApprovalRequestPartSchema,
|
|
218
224
|
harnessV1ToolResultPartSchema,
|
|
@@ -24,6 +24,13 @@ export type HarnessV1PendingToolResult = {
|
|
|
24
24
|
* so a resumed continuation cannot pick up configuration from a later turn.
|
|
25
25
|
*/
|
|
26
26
|
export type HarnessV1TurnSettings = {
|
|
27
|
+
/**
|
|
28
|
+
* Model identifier selected for this turn. Adapters interpret this value
|
|
29
|
+
* according to the underlying harness runtime. Rerun-based continuations
|
|
30
|
+
* reuse it when reconstructing the turn.
|
|
31
|
+
*/
|
|
32
|
+
readonly model?: string;
|
|
33
|
+
|
|
27
34
|
/**
|
|
28
35
|
* Skills made available to the underlying runtime for this turn. Adapters
|
|
29
36
|
* must replace skills from the preceding completed turn before starting a
|
|
@@ -21,12 +21,6 @@ import type { HarnessV1BuiltinToolFiltering } from './harness-v1-tool-filtering'
|
|
|
21
21
|
* calling the adapter, so adapters never need to derive provider-specific paths.
|
|
22
22
|
*/
|
|
23
23
|
export type HarnessV1StartOptions = {
|
|
24
|
-
/**
|
|
25
|
-
* Model identifier selected by the consumer. Adapters interpret this value
|
|
26
|
-
* according to the underlying harness runtime.
|
|
27
|
-
*/
|
|
28
|
-
readonly model?: string;
|
|
29
|
-
|
|
30
24
|
/**
|
|
31
25
|
* Stable identifier for this harness session. Used as the underlying
|
|
32
26
|
* resource name where the adapter has a notion of a named session
|
|
@@ -171,14 +165,6 @@ export type HarnessV1Session = {
|
|
|
171
165
|
*/
|
|
172
166
|
readonly isResume: boolean;
|
|
173
167
|
|
|
174
|
-
/**
|
|
175
|
-
* The model id the underlying runtime is configured to use, if the adapter
|
|
176
|
-
* knows it (e.g. from its settings). Surfaced into telemetry as
|
|
177
|
-
* `gen_ai.request.model` and the trace span labels. Omitted when the adapter
|
|
178
|
-
* defers to the runtime's own default and has no concrete id.
|
|
179
|
-
*/
|
|
180
|
-
readonly modelId?: string;
|
|
181
|
-
|
|
182
168
|
/**
|
|
183
169
|
* Run one prompt turn. Returns a control handle the host uses to feed
|
|
184
170
|
* tool results, approvals, and user messages back into the turn while it
|