@ai-sdk/harness 0.0.0 → 1.0.0-canary.3
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 +24 -0
- package/LICENSE +13 -0
- package/README.md +142 -0
- package/agent/index.ts +17 -0
- package/bridge/index.ts +10 -0
- package/dist/agent/index.d.ts +1480 -0
- package/dist/agent/index.js +2554 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/bridge/index.d.ts +111 -0
- package/dist/bridge/index.js +414 -0
- package/dist/bridge/index.js.map +1 -0
- package/dist/index.d.ts +1510 -0
- package/dist/index.js +15834 -0
- package/dist/index.js.map +1 -0
- package/dist/observability/index.d.ts +97 -0
- package/dist/observability/index.js +225 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/utils/index.d.ts +196 -0
- package/dist/utils/index.js +327 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +104 -1
- package/src/agent/harness-agent-session.ts +352 -0
- package/src/agent/harness-agent-settings.ts +131 -0
- package/src/agent/harness-agent-tool-approval-continuation.ts +94 -0
- package/src/agent/harness-agent.ts +750 -0
- package/src/agent/harness-diagnostics.ts +88 -0
- package/src/agent/internal/bootstrap-recipe.ts +124 -0
- package/src/agent/internal/bridge-port-registry.ts +52 -0
- package/src/agent/internal/harness-stream-text-result.ts +720 -0
- package/src/agent/internal/permission-mode.ts +50 -0
- package/src/agent/internal/resolve-observability.ts +128 -0
- package/src/agent/internal/resume-state-validation.ts +51 -0
- package/src/agent/internal/run-prompt.ts +811 -0
- package/src/agent/internal/strip-work-dir.ts +68 -0
- package/src/agent/internal/to-harness-stream.ts +75 -0
- package/src/agent/internal/translate-stream-part.ts +221 -0
- package/src/agent/internal/turn-telemetry.ts +359 -0
- package/src/agent/prewarm.ts +46 -0
- package/src/bridge/index.ts +700 -0
- package/src/errors/harness-capability-unsupported-error.ts +41 -0
- package/src/errors/harness-error.ts +22 -0
- package/src/index.ts +3 -0
- package/src/observability/file-reporter.ts +209 -0
- package/src/observability/index.ts +13 -0
- package/src/observability/trace-tree-reporter.ts +122 -0
- package/src/utils/classify-disk-log.ts +43 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/sandbox-channel.ts +453 -0
- package/src/v1/harness-v1-bootstrap.ts +46 -0
- package/src/v1/harness-v1-bridge-protocol.ts +310 -0
- package/src/v1/harness-v1-builtin-tool.ts +138 -0
- package/src/v1/harness-v1-call-warning.ts +22 -0
- package/src/v1/harness-v1-diagnostic.ts +66 -0
- package/src/v1/harness-v1-metadata.ts +13 -0
- package/src/v1/harness-v1-network-sandbox-session.ts +123 -0
- package/src/v1/harness-v1-observability.ts +20 -0
- package/src/v1/harness-v1-permission-mode.ts +11 -0
- package/src/v1/harness-v1-prompt-control.ts +41 -0
- package/src/v1/harness-v1-prompt.ts +11 -0
- package/src/v1/harness-v1-resume-state.ts +46 -0
- package/src/v1/harness-v1-sandbox-provider.ts +76 -0
- package/src/v1/harness-v1-session.ts +268 -0
- package/src/v1/harness-v1-skill.ts +22 -0
- package/src/v1/harness-v1-stream-part.ts +363 -0
- package/src/v1/harness-v1-tool-spec.ts +31 -0
- package/src/v1/harness-v1.ts +83 -0
- package/src/v1/index.ts +93 -0
- package/utils/index.ts +1 -0
|
@@ -0,0 +1,2554 @@
|
|
|
1
|
+
// src/errors/harness-capability-unsupported-error.ts
|
|
2
|
+
import { AISDKError as AISDKError2 } from "@ai-sdk/provider";
|
|
3
|
+
|
|
4
|
+
// src/errors/harness-error.ts
|
|
5
|
+
import { AISDKError } from "@ai-sdk/provider";
|
|
6
|
+
var name = "AI_HarnessError";
|
|
7
|
+
var marker = `vercel.ai.error.${name}`;
|
|
8
|
+
var symbol = Symbol.for(marker);
|
|
9
|
+
var _a, _b;
|
|
10
|
+
var HarnessError = class extends (_b = AISDKError, _a = symbol, _b) {
|
|
11
|
+
constructor({ message, cause }) {
|
|
12
|
+
super({ name, message, cause });
|
|
13
|
+
this[_a] = true;
|
|
14
|
+
}
|
|
15
|
+
static isInstance(error) {
|
|
16
|
+
return AISDKError.hasMarker(error, marker);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// src/errors/harness-capability-unsupported-error.ts
|
|
21
|
+
var name2 = "AI_HarnessCapabilityUnsupportedError";
|
|
22
|
+
var marker2 = `vercel.ai.error.${name2}`;
|
|
23
|
+
var symbol2 = Symbol.for(marker2);
|
|
24
|
+
var _a2, _b2;
|
|
25
|
+
var HarnessCapabilityUnsupportedError = class extends (_b2 = HarnessError, _a2 = symbol2, _b2) {
|
|
26
|
+
constructor({
|
|
27
|
+
message,
|
|
28
|
+
harnessId,
|
|
29
|
+
cause
|
|
30
|
+
}) {
|
|
31
|
+
super({ message, cause });
|
|
32
|
+
this[_a2] = true;
|
|
33
|
+
Object.defineProperty(this, "name", { value: name2 });
|
|
34
|
+
this.harnessId = harnessId;
|
|
35
|
+
}
|
|
36
|
+
static isInstance(error) {
|
|
37
|
+
return AISDKError2.hasMarker(error, marker2);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// src/agent/harness-agent.ts
|
|
42
|
+
import {
|
|
43
|
+
asSchema,
|
|
44
|
+
generateId as generateId5
|
|
45
|
+
} from "@ai-sdk/provider-utils";
|
|
46
|
+
|
|
47
|
+
// src/agent/internal/bridge-port-registry.ts
|
|
48
|
+
var registries = /* @__PURE__ */ new WeakMap();
|
|
49
|
+
function acquireBridgePort(options) {
|
|
50
|
+
let entry = registries.get(options.poolKey);
|
|
51
|
+
if (entry == null) {
|
|
52
|
+
entry = { pool: options.pool, leases: /* @__PURE__ */ new Map() };
|
|
53
|
+
registries.set(options.poolKey, entry);
|
|
54
|
+
}
|
|
55
|
+
const existing = entry.leases.get(options.sessionId);
|
|
56
|
+
if (existing != null) return existing;
|
|
57
|
+
const leased = new Set(entry.leases.values());
|
|
58
|
+
for (const port of entry.pool) {
|
|
59
|
+
if (!leased.has(port)) {
|
|
60
|
+
entry.leases.set(options.sessionId, port);
|
|
61
|
+
return port;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
throw new Error(
|
|
65
|
+
`No available bridge port \u2014 pool of ${entry.pool.length} ports is fully leased.`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
function releaseBridgePort(options) {
|
|
69
|
+
const entry = registries.get(options.poolKey);
|
|
70
|
+
if (entry == null) return;
|
|
71
|
+
entry.leases.delete(options.sessionId);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/agent/internal/resume-state-validation.ts
|
|
75
|
+
import { safeValidateTypes } from "@ai-sdk/provider-utils";
|
|
76
|
+
async function validateResumeStateData(input) {
|
|
77
|
+
const { harness, state } = input;
|
|
78
|
+
if (state.specificationVersion !== "harness-v1") {
|
|
79
|
+
throw new HarnessError({
|
|
80
|
+
message: `Resume state has unexpected specificationVersion '${state.specificationVersion}'; expected 'harness-v1'.`
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
if (state.harnessId !== harness.harnessId) {
|
|
84
|
+
throw new HarnessError({
|
|
85
|
+
message: `Resume state was produced by harness '${state.harnessId}' but this agent uses '${harness.harnessId}'.`
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
if (harness.resumeStateSchema == null) {
|
|
89
|
+
return state;
|
|
90
|
+
}
|
|
91
|
+
const result = await safeValidateTypes({
|
|
92
|
+
value: state.data,
|
|
93
|
+
schema: harness.resumeStateSchema
|
|
94
|
+
});
|
|
95
|
+
if (!result.success) {
|
|
96
|
+
throw new HarnessError({
|
|
97
|
+
message: `Resume state failed schema validation for harness '${harness.harnessId}': ${result.error.message}`,
|
|
98
|
+
cause: result.error
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
harnessId: state.harnessId,
|
|
103
|
+
specificationVersion: state.specificationVersion,
|
|
104
|
+
data: result.value,
|
|
105
|
+
...state.pendingToolApprovals !== void 0 ? { pendingToolApprovals: state.pendingToolApprovals } : {}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/agent/internal/to-harness-stream.ts
|
|
110
|
+
async function toHarnessStream(options) {
|
|
111
|
+
let controller;
|
|
112
|
+
let closed = false;
|
|
113
|
+
const stream = new ReadableStream({
|
|
114
|
+
start(c) {
|
|
115
|
+
controller = c;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
const safeEnqueue = (part) => {
|
|
119
|
+
if (closed) return;
|
|
120
|
+
controller.enqueue(part);
|
|
121
|
+
};
|
|
122
|
+
const safeClose = () => {
|
|
123
|
+
if (closed) return;
|
|
124
|
+
closed = true;
|
|
125
|
+
controller.close();
|
|
126
|
+
};
|
|
127
|
+
const control = await options.invoke(safeEnqueue);
|
|
128
|
+
Promise.resolve(control.done).then(
|
|
129
|
+
() => safeClose(),
|
|
130
|
+
(err) => {
|
|
131
|
+
safeEnqueue({ type: "error", error: err });
|
|
132
|
+
safeClose();
|
|
133
|
+
}
|
|
134
|
+
).catch(() => {
|
|
135
|
+
});
|
|
136
|
+
return { stream, control };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/agent/internal/run-prompt.ts
|
|
140
|
+
import {
|
|
141
|
+
executeTool,
|
|
142
|
+
generateId as generateId4,
|
|
143
|
+
isExecutableTool,
|
|
144
|
+
safeParseJSON
|
|
145
|
+
} from "@ai-sdk/provider-utils";
|
|
146
|
+
import { parseToolCall } from "ai/internal";
|
|
147
|
+
|
|
148
|
+
// src/agent/internal/harness-stream-text-result.ts
|
|
149
|
+
import {
|
|
150
|
+
DelayedPromise,
|
|
151
|
+
generateId
|
|
152
|
+
} from "@ai-sdk/provider-utils";
|
|
153
|
+
import {
|
|
154
|
+
addLanguageModelUsage,
|
|
155
|
+
asLanguageModelUsage,
|
|
156
|
+
createAsyncIterableStream,
|
|
157
|
+
createNullLanguageModelUsage,
|
|
158
|
+
DefaultStepResult,
|
|
159
|
+
toResponseMessages
|
|
160
|
+
} from "ai/internal";
|
|
161
|
+
import {
|
|
162
|
+
createUIMessageStreamResponse,
|
|
163
|
+
toUIMessageStream as toUIMessageStreamHelper
|
|
164
|
+
} from "ai";
|
|
165
|
+
var HarnessStreamTextResult = class {
|
|
166
|
+
constructor(options) {
|
|
167
|
+
// Delayed promises backing every PromiseLike accessor. Each is typed
|
|
168
|
+
// against the corresponding `StreamTextResult` property so the public
|
|
169
|
+
// surface stays in lockstep with AI SDK's interface as it evolves.
|
|
170
|
+
this._content = new DelayedPromise();
|
|
171
|
+
this._text = new DelayedPromise();
|
|
172
|
+
this._reasoning = new DelayedPromise();
|
|
173
|
+
this._reasoningText = new DelayedPromise();
|
|
174
|
+
this._files = new DelayedPromise();
|
|
175
|
+
this._sources = new DelayedPromise();
|
|
176
|
+
this._toolCalls = new DelayedPromise();
|
|
177
|
+
this._staticToolCalls = new DelayedPromise();
|
|
178
|
+
this._dynamicToolCalls = new DelayedPromise();
|
|
179
|
+
this._toolResults = new DelayedPromise();
|
|
180
|
+
this._staticToolResults = new DelayedPromise();
|
|
181
|
+
this._dynamicToolResults = new DelayedPromise();
|
|
182
|
+
this._finishReason = new DelayedPromise();
|
|
183
|
+
this._rawFinishReason = new DelayedPromise();
|
|
184
|
+
this._usage = new DelayedPromise();
|
|
185
|
+
this._warnings = new DelayedPromise();
|
|
186
|
+
this._steps = new DelayedPromise();
|
|
187
|
+
this._finalStep = new DelayedPromise();
|
|
188
|
+
this._request = new DelayedPromise();
|
|
189
|
+
this._response = new DelayedPromise();
|
|
190
|
+
this._responseMessages = new DelayedPromise();
|
|
191
|
+
this._providerMetadata = new DelayedPromise();
|
|
192
|
+
this.stepsBuffer = [];
|
|
193
|
+
this.currentStepContent = [];
|
|
194
|
+
this.currentStepWarnings = [];
|
|
195
|
+
this.stepNumber = 0;
|
|
196
|
+
// Accumulators that span the whole turn.
|
|
197
|
+
this.accumulatedUsage = createNullLanguageModelUsage();
|
|
198
|
+
this.finalProviderMetadata = void 0;
|
|
199
|
+
this.finalFinishReason = "other";
|
|
200
|
+
this.finalRawFinishReason = void 0;
|
|
201
|
+
this.aggregateWarnings = [];
|
|
202
|
+
this.settled = false;
|
|
203
|
+
this.tools = options.tools;
|
|
204
|
+
this.runtimeContext = options.runtimeContext;
|
|
205
|
+
this.toolsContext = options.toolsContext;
|
|
206
|
+
this.providerName = `harness:${options.harnessId}`;
|
|
207
|
+
this.modelId = options.sessionId;
|
|
208
|
+
let controllerRef;
|
|
209
|
+
const baseStream = new ReadableStream({
|
|
210
|
+
start(c) {
|
|
211
|
+
controllerRef = c;
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
this.fullStreamController = controllerRef;
|
|
215
|
+
const [forFull, forText] = baseStream.tee();
|
|
216
|
+
this.stream = forFull;
|
|
217
|
+
this.fullStream = this.stream;
|
|
218
|
+
this.textStream = forText.pipeThrough(
|
|
219
|
+
new TransformStream({
|
|
220
|
+
transform(part, controller) {
|
|
221
|
+
if (part.type === "text-delta") {
|
|
222
|
+
controller.enqueue(part.text);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
// ─── Writer-side methods used by the driver ────────────────────────
|
|
229
|
+
/**
|
|
230
|
+
* Push a translated `TextStreamPart` into `fullStream` and accumulate it
|
|
231
|
+
* into the current step's content array where applicable.
|
|
232
|
+
*/
|
|
233
|
+
enqueue(part) {
|
|
234
|
+
this.fullStreamController.enqueue(part);
|
|
235
|
+
this.appendToCurrentStepContent(part);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Mark the end of a step. Builds a `StepResult` from the accumulated
|
|
239
|
+
* content and records it in the steps array. Accepts the V4-shaped
|
|
240
|
+
* finish reason / usage the harness emits and normalizes to AI SDK's
|
|
241
|
+
* flat shape internally.
|
|
242
|
+
*/
|
|
243
|
+
finishStep(input) {
|
|
244
|
+
const normalizedUsage = asLanguageModelUsage(input.usage);
|
|
245
|
+
const finishReason = input.finishReason.unified;
|
|
246
|
+
const rawFinishReason = input.finishReason.raw;
|
|
247
|
+
const step = new DefaultStepResult({
|
|
248
|
+
callId: generateId(),
|
|
249
|
+
stepNumber: this.stepNumber,
|
|
250
|
+
provider: this.providerName,
|
|
251
|
+
modelId: this.modelId,
|
|
252
|
+
runtimeContext: this.runtimeContext,
|
|
253
|
+
toolsContext: this.toolsContext,
|
|
254
|
+
content: this.currentStepContent,
|
|
255
|
+
finishReason,
|
|
256
|
+
rawFinishReason,
|
|
257
|
+
usage: normalizedUsage,
|
|
258
|
+
performance: createEmptyPerformance(),
|
|
259
|
+
warnings: input.warnings.length > 0 ? input.warnings : void 0,
|
|
260
|
+
request: {},
|
|
261
|
+
response: {
|
|
262
|
+
id: generateId(),
|
|
263
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
264
|
+
modelId: this.modelId,
|
|
265
|
+
messages: []
|
|
266
|
+
},
|
|
267
|
+
providerMetadata: input.providerMetadata
|
|
268
|
+
});
|
|
269
|
+
this.stepsBuffer.push(step);
|
|
270
|
+
this.fullStreamController.enqueue({
|
|
271
|
+
type: "finish-step",
|
|
272
|
+
finishReason,
|
|
273
|
+
rawFinishReason,
|
|
274
|
+
usage: normalizedUsage,
|
|
275
|
+
providerMetadata: input.providerMetadata,
|
|
276
|
+
response: step.response,
|
|
277
|
+
performance: createEmptyPerformance()
|
|
278
|
+
});
|
|
279
|
+
this.accumulatedUsage = addLanguageModelUsage(
|
|
280
|
+
this.accumulatedUsage,
|
|
281
|
+
normalizedUsage
|
|
282
|
+
);
|
|
283
|
+
this.finalFinishReason = finishReason;
|
|
284
|
+
this.finalRawFinishReason = rawFinishReason;
|
|
285
|
+
this.finalProviderMetadata = input.providerMetadata;
|
|
286
|
+
if (input.warnings.length > 0)
|
|
287
|
+
this.aggregateWarnings.push(...input.warnings);
|
|
288
|
+
this.stepNumber += 1;
|
|
289
|
+
this.currentStepContent = [];
|
|
290
|
+
this.currentStepWarnings = [];
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Resolve every delayed promise and close `fullStream`. Idempotent.
|
|
294
|
+
*/
|
|
295
|
+
async finish() {
|
|
296
|
+
if (this.settled) return;
|
|
297
|
+
this.settled = true;
|
|
298
|
+
if (this.currentStepContent.length > 0) {
|
|
299
|
+
const trailingStep = new DefaultStepResult({
|
|
300
|
+
callId: generateId(),
|
|
301
|
+
stepNumber: this.stepNumber,
|
|
302
|
+
provider: this.providerName,
|
|
303
|
+
modelId: this.modelId,
|
|
304
|
+
runtimeContext: this.runtimeContext,
|
|
305
|
+
toolsContext: this.toolsContext,
|
|
306
|
+
content: this.currentStepContent,
|
|
307
|
+
finishReason: this.finalFinishReason,
|
|
308
|
+
rawFinishReason: this.finalRawFinishReason,
|
|
309
|
+
usage: createNullLanguageModelUsage(),
|
|
310
|
+
performance: createEmptyPerformance(),
|
|
311
|
+
warnings: this.currentStepWarnings.length > 0 ? this.currentStepWarnings : void 0,
|
|
312
|
+
request: {},
|
|
313
|
+
response: {
|
|
314
|
+
id: generateId(),
|
|
315
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
316
|
+
modelId: this.modelId,
|
|
317
|
+
messages: []
|
|
318
|
+
},
|
|
319
|
+
providerMetadata: this.finalProviderMetadata
|
|
320
|
+
});
|
|
321
|
+
this.stepsBuffer.push(trailingStep);
|
|
322
|
+
this.currentStepContent = [];
|
|
323
|
+
this.currentStepWarnings = [];
|
|
324
|
+
}
|
|
325
|
+
const finalStep = this.stepsBuffer.length > 0 ? this.stepsBuffer[this.stepsBuffer.length - 1] : new DefaultStepResult({
|
|
326
|
+
callId: generateId(),
|
|
327
|
+
stepNumber: 0,
|
|
328
|
+
provider: this.providerName,
|
|
329
|
+
modelId: this.modelId,
|
|
330
|
+
runtimeContext: this.runtimeContext,
|
|
331
|
+
toolsContext: this.toolsContext,
|
|
332
|
+
content: [],
|
|
333
|
+
finishReason: this.finalFinishReason,
|
|
334
|
+
rawFinishReason: this.finalRawFinishReason,
|
|
335
|
+
usage: createNullLanguageModelUsage(),
|
|
336
|
+
performance: createEmptyPerformance(),
|
|
337
|
+
warnings: void 0,
|
|
338
|
+
request: {},
|
|
339
|
+
response: {
|
|
340
|
+
id: generateId(),
|
|
341
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
342
|
+
modelId: this.modelId,
|
|
343
|
+
messages: []
|
|
344
|
+
},
|
|
345
|
+
providerMetadata: void 0
|
|
346
|
+
});
|
|
347
|
+
const aggregatedContent = this.stepsBuffer.flatMap((s) => s.content);
|
|
348
|
+
this._content.resolve(
|
|
349
|
+
aggregatedContent
|
|
350
|
+
);
|
|
351
|
+
this._text.resolve(finalStep.text);
|
|
352
|
+
this._reasoning.resolve(
|
|
353
|
+
[]
|
|
354
|
+
);
|
|
355
|
+
this._reasoningText.resolve(void 0);
|
|
356
|
+
this._files.resolve(this.stepsBuffer.flatMap((s) => s.files));
|
|
357
|
+
this._sources.resolve(this.stepsBuffer.flatMap((s) => s.sources));
|
|
358
|
+
this._toolCalls.resolve(
|
|
359
|
+
this.stepsBuffer.flatMap((s) => s.toolCalls)
|
|
360
|
+
);
|
|
361
|
+
this._staticToolCalls.resolve(
|
|
362
|
+
this.stepsBuffer.flatMap((s) => s.staticToolCalls)
|
|
363
|
+
);
|
|
364
|
+
this._dynamicToolCalls.resolve(
|
|
365
|
+
this.stepsBuffer.flatMap((s) => s.dynamicToolCalls)
|
|
366
|
+
);
|
|
367
|
+
this._toolResults.resolve(
|
|
368
|
+
this.stepsBuffer.flatMap((s) => s.toolResults)
|
|
369
|
+
);
|
|
370
|
+
this._staticToolResults.resolve(
|
|
371
|
+
this.stepsBuffer.flatMap((s) => s.staticToolResults)
|
|
372
|
+
);
|
|
373
|
+
this._dynamicToolResults.resolve(
|
|
374
|
+
this.stepsBuffer.flatMap((s) => s.dynamicToolResults)
|
|
375
|
+
);
|
|
376
|
+
this._finishReason.resolve(this.finalFinishReason);
|
|
377
|
+
this._rawFinishReason.resolve(this.finalRawFinishReason);
|
|
378
|
+
this._usage.resolve(this.accumulatedUsage);
|
|
379
|
+
this._warnings.resolve(
|
|
380
|
+
this.aggregateWarnings.length > 0 ? this.aggregateWarnings : void 0
|
|
381
|
+
);
|
|
382
|
+
this._steps.resolve(this.stepsBuffer);
|
|
383
|
+
this._finalStep.resolve(finalStep);
|
|
384
|
+
this._request.resolve(finalStep.request);
|
|
385
|
+
this._response.resolve(finalStep.response);
|
|
386
|
+
this._providerMetadata.resolve(this.finalProviderMetadata);
|
|
387
|
+
const responseMessages = await toResponseMessages({
|
|
388
|
+
content: aggregatedContent,
|
|
389
|
+
tools: this.tools
|
|
390
|
+
});
|
|
391
|
+
this._responseMessages.resolve(responseMessages);
|
|
392
|
+
this.fullStreamController.enqueue({
|
|
393
|
+
type: "finish",
|
|
394
|
+
finishReason: this.finalFinishReason,
|
|
395
|
+
rawFinishReason: this.finalRawFinishReason,
|
|
396
|
+
totalUsage: this.accumulatedUsage,
|
|
397
|
+
providerMetadata: this.finalProviderMetadata
|
|
398
|
+
});
|
|
399
|
+
this.fullStreamController.close();
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Surface a fatal error as a stream `error` part + reject every delayed
|
|
403
|
+
* promise so awaiting consumers stop hanging. Idempotent.
|
|
404
|
+
*/
|
|
405
|
+
fail(error) {
|
|
406
|
+
if (this.settled) return;
|
|
407
|
+
this.settled = true;
|
|
408
|
+
this.fullStreamController.enqueue({
|
|
409
|
+
type: "error",
|
|
410
|
+
error
|
|
411
|
+
});
|
|
412
|
+
this.fullStreamController.close();
|
|
413
|
+
for (const dp of [
|
|
414
|
+
this._content,
|
|
415
|
+
this._text,
|
|
416
|
+
this._reasoning,
|
|
417
|
+
this._reasoningText,
|
|
418
|
+
this._files,
|
|
419
|
+
this._sources,
|
|
420
|
+
this._toolCalls,
|
|
421
|
+
this._staticToolCalls,
|
|
422
|
+
this._dynamicToolCalls,
|
|
423
|
+
this._toolResults,
|
|
424
|
+
this._staticToolResults,
|
|
425
|
+
this._dynamicToolResults,
|
|
426
|
+
this._finishReason,
|
|
427
|
+
this._rawFinishReason,
|
|
428
|
+
this._usage,
|
|
429
|
+
this._warnings,
|
|
430
|
+
this._steps,
|
|
431
|
+
this._finalStep,
|
|
432
|
+
this._request,
|
|
433
|
+
this._response,
|
|
434
|
+
this._responseMessages,
|
|
435
|
+
this._providerMetadata
|
|
436
|
+
]) {
|
|
437
|
+
try {
|
|
438
|
+
dp.reject(error);
|
|
439
|
+
} catch (e) {
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
// ─── Reader-side public surface (StreamTextResult contract) ────────
|
|
444
|
+
get content() {
|
|
445
|
+
return this._content.promise;
|
|
446
|
+
}
|
|
447
|
+
get text() {
|
|
448
|
+
return this._text.promise;
|
|
449
|
+
}
|
|
450
|
+
get reasoning() {
|
|
451
|
+
return this._reasoning.promise;
|
|
452
|
+
}
|
|
453
|
+
get reasoningText() {
|
|
454
|
+
return this._reasoningText.promise;
|
|
455
|
+
}
|
|
456
|
+
get files() {
|
|
457
|
+
return this._files.promise;
|
|
458
|
+
}
|
|
459
|
+
get sources() {
|
|
460
|
+
return this._sources.promise;
|
|
461
|
+
}
|
|
462
|
+
get toolCalls() {
|
|
463
|
+
return this._toolCalls.promise;
|
|
464
|
+
}
|
|
465
|
+
get staticToolCalls() {
|
|
466
|
+
return this._staticToolCalls.promise;
|
|
467
|
+
}
|
|
468
|
+
get dynamicToolCalls() {
|
|
469
|
+
return this._dynamicToolCalls.promise;
|
|
470
|
+
}
|
|
471
|
+
get toolResults() {
|
|
472
|
+
return this._toolResults.promise;
|
|
473
|
+
}
|
|
474
|
+
get staticToolResults() {
|
|
475
|
+
return this._staticToolResults.promise;
|
|
476
|
+
}
|
|
477
|
+
get dynamicToolResults() {
|
|
478
|
+
return this._dynamicToolResults.promise;
|
|
479
|
+
}
|
|
480
|
+
get finishReason() {
|
|
481
|
+
return this._finishReason.promise;
|
|
482
|
+
}
|
|
483
|
+
get rawFinishReason() {
|
|
484
|
+
return this._rawFinishReason.promise;
|
|
485
|
+
}
|
|
486
|
+
get usage() {
|
|
487
|
+
return this._usage.promise;
|
|
488
|
+
}
|
|
489
|
+
get totalUsage() {
|
|
490
|
+
return this._usage.promise;
|
|
491
|
+
}
|
|
492
|
+
get warnings() {
|
|
493
|
+
return this._warnings.promise;
|
|
494
|
+
}
|
|
495
|
+
get steps() {
|
|
496
|
+
return this._steps.promise;
|
|
497
|
+
}
|
|
498
|
+
get finalStep() {
|
|
499
|
+
return this._finalStep.promise;
|
|
500
|
+
}
|
|
501
|
+
get request() {
|
|
502
|
+
return this._request.promise;
|
|
503
|
+
}
|
|
504
|
+
get response() {
|
|
505
|
+
return this._response.promise;
|
|
506
|
+
}
|
|
507
|
+
get responseMessages() {
|
|
508
|
+
return this._responseMessages.promise;
|
|
509
|
+
}
|
|
510
|
+
get providerMetadata() {
|
|
511
|
+
return this._providerMetadata.promise;
|
|
512
|
+
}
|
|
513
|
+
// Output-specification surfaces are not yet supported.
|
|
514
|
+
get experimental_partialOutputStream() {
|
|
515
|
+
throw notSupportedYet("partial output stream");
|
|
516
|
+
}
|
|
517
|
+
get partialOutputStream() {
|
|
518
|
+
throw notSupportedYet("partial output stream");
|
|
519
|
+
}
|
|
520
|
+
get elementStream() {
|
|
521
|
+
throw notSupportedYet("element stream");
|
|
522
|
+
}
|
|
523
|
+
get output() {
|
|
524
|
+
throw notSupportedYet("structured output");
|
|
525
|
+
}
|
|
526
|
+
async consumeStream() {
|
|
527
|
+
const reader = this.fullStream.getReader();
|
|
528
|
+
try {
|
|
529
|
+
while (true) {
|
|
530
|
+
const { done } = await reader.read();
|
|
531
|
+
if (done) return;
|
|
532
|
+
}
|
|
533
|
+
} finally {
|
|
534
|
+
reader.releaseLock();
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
toUIMessageStream({
|
|
538
|
+
originalMessages,
|
|
539
|
+
generateMessageId,
|
|
540
|
+
onFinish,
|
|
541
|
+
messageMetadata,
|
|
542
|
+
sendReasoning,
|
|
543
|
+
sendSources,
|
|
544
|
+
sendStart,
|
|
545
|
+
sendFinish,
|
|
546
|
+
onError
|
|
547
|
+
} = {}) {
|
|
548
|
+
return createAsyncIterableStream(
|
|
549
|
+
toUIMessageStreamHelper({
|
|
550
|
+
stream: this.stream,
|
|
551
|
+
tools: this.tools,
|
|
552
|
+
originalMessages,
|
|
553
|
+
generateMessageId,
|
|
554
|
+
onFinish,
|
|
555
|
+
messageMetadata,
|
|
556
|
+
sendReasoning,
|
|
557
|
+
sendSources,
|
|
558
|
+
sendStart,
|
|
559
|
+
sendFinish,
|
|
560
|
+
onError
|
|
561
|
+
})
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
pipeUIMessageStreamToResponse() {
|
|
565
|
+
throw notSupportedYet("pipeUIMessageStreamToResponse");
|
|
566
|
+
}
|
|
567
|
+
pipeTextStreamToResponse() {
|
|
568
|
+
throw notSupportedYet("pipeTextStreamToResponse");
|
|
569
|
+
}
|
|
570
|
+
toUIMessageStreamResponse({
|
|
571
|
+
originalMessages,
|
|
572
|
+
generateMessageId,
|
|
573
|
+
onFinish,
|
|
574
|
+
messageMetadata,
|
|
575
|
+
sendReasoning,
|
|
576
|
+
sendSources,
|
|
577
|
+
sendStart,
|
|
578
|
+
sendFinish,
|
|
579
|
+
onError,
|
|
580
|
+
...init
|
|
581
|
+
} = {}) {
|
|
582
|
+
return createUIMessageStreamResponse({
|
|
583
|
+
stream: this.toUIMessageStream({
|
|
584
|
+
originalMessages,
|
|
585
|
+
generateMessageId,
|
|
586
|
+
onFinish,
|
|
587
|
+
messageMetadata,
|
|
588
|
+
sendReasoning,
|
|
589
|
+
sendSources,
|
|
590
|
+
sendStart,
|
|
591
|
+
sendFinish,
|
|
592
|
+
onError
|
|
593
|
+
}),
|
|
594
|
+
...init
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
toTextStreamResponse() {
|
|
598
|
+
throw notSupportedYet("toTextStreamResponse");
|
|
599
|
+
}
|
|
600
|
+
// ─── Helpers ────────────────────────────────────────────────────────
|
|
601
|
+
appendToCurrentStepContent(part) {
|
|
602
|
+
switch (part.type) {
|
|
603
|
+
case "text-delta": {
|
|
604
|
+
const last = this.currentStepContent[this.currentStepContent.length - 1];
|
|
605
|
+
if (last && last.type === "text") {
|
|
606
|
+
last.text += part.text;
|
|
607
|
+
} else {
|
|
608
|
+
this.currentStepContent.push({
|
|
609
|
+
type: "text",
|
|
610
|
+
text: part.text
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
case "tool-call":
|
|
616
|
+
this.currentStepContent.push({
|
|
617
|
+
...part
|
|
618
|
+
});
|
|
619
|
+
return;
|
|
620
|
+
case "tool-approval-request":
|
|
621
|
+
this.currentStepContent.push({
|
|
622
|
+
...part
|
|
623
|
+
});
|
|
624
|
+
return;
|
|
625
|
+
case "tool-approval-response":
|
|
626
|
+
this.currentStepContent.push({
|
|
627
|
+
...part
|
|
628
|
+
});
|
|
629
|
+
return;
|
|
630
|
+
case "tool-result":
|
|
631
|
+
this.currentStepContent.push({
|
|
632
|
+
...part
|
|
633
|
+
});
|
|
634
|
+
return;
|
|
635
|
+
default:
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
function createEmptyPerformance() {
|
|
641
|
+
return {
|
|
642
|
+
effectiveOutputTokensPerSecond: 0,
|
|
643
|
+
outputTokensPerSecond: void 0,
|
|
644
|
+
inputTokensPerSecond: void 0,
|
|
645
|
+
effectiveTotalTokensPerSecond: 0,
|
|
646
|
+
stepTimeMs: 0,
|
|
647
|
+
responseTimeMs: 0,
|
|
648
|
+
toolExecutionMs: {},
|
|
649
|
+
timeToFirstOutputMs: void 0
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
function notSupportedYet(feature) {
|
|
653
|
+
return new Error(
|
|
654
|
+
`HarnessAgent: ${feature} is not implemented yet. Track the foundation review for follow-up.`
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// src/agent/internal/translate-stream-part.ts
|
|
659
|
+
import { generateId as generateId2 } from "@ai-sdk/provider-utils";
|
|
660
|
+
function translateStreamPart(event) {
|
|
661
|
+
switch (event.type) {
|
|
662
|
+
case "stream-start":
|
|
663
|
+
return [];
|
|
664
|
+
case "text-start":
|
|
665
|
+
return [
|
|
666
|
+
{
|
|
667
|
+
type: "text-start",
|
|
668
|
+
id: event.id,
|
|
669
|
+
providerMetadata: event.harnessMetadata
|
|
670
|
+
}
|
|
671
|
+
];
|
|
672
|
+
case "text-delta":
|
|
673
|
+
return [
|
|
674
|
+
{
|
|
675
|
+
type: "text-delta",
|
|
676
|
+
id: event.id,
|
|
677
|
+
text: event.delta,
|
|
678
|
+
providerMetadata: event.harnessMetadata
|
|
679
|
+
}
|
|
680
|
+
];
|
|
681
|
+
case "text-end":
|
|
682
|
+
return [
|
|
683
|
+
{
|
|
684
|
+
type: "text-end",
|
|
685
|
+
id: event.id,
|
|
686
|
+
providerMetadata: event.harnessMetadata
|
|
687
|
+
}
|
|
688
|
+
];
|
|
689
|
+
case "reasoning-start":
|
|
690
|
+
return [
|
|
691
|
+
{
|
|
692
|
+
type: "reasoning-start",
|
|
693
|
+
id: event.id,
|
|
694
|
+
providerMetadata: event.harnessMetadata
|
|
695
|
+
}
|
|
696
|
+
];
|
|
697
|
+
case "reasoning-delta":
|
|
698
|
+
return [
|
|
699
|
+
{
|
|
700
|
+
type: "reasoning-delta",
|
|
701
|
+
id: event.id,
|
|
702
|
+
text: event.delta,
|
|
703
|
+
providerMetadata: event.harnessMetadata
|
|
704
|
+
}
|
|
705
|
+
];
|
|
706
|
+
case "reasoning-end":
|
|
707
|
+
return [
|
|
708
|
+
{
|
|
709
|
+
type: "reasoning-end",
|
|
710
|
+
id: event.id,
|
|
711
|
+
providerMetadata: event.harnessMetadata
|
|
712
|
+
}
|
|
713
|
+
];
|
|
714
|
+
case "tool-call":
|
|
715
|
+
return [];
|
|
716
|
+
case "tool-approval-request":
|
|
717
|
+
return [];
|
|
718
|
+
case "tool-result":
|
|
719
|
+
return [
|
|
720
|
+
{
|
|
721
|
+
type: "tool-result",
|
|
722
|
+
toolCallId: event.toolCallId,
|
|
723
|
+
toolName: event.toolName,
|
|
724
|
+
input: void 0,
|
|
725
|
+
output: event.result,
|
|
726
|
+
...event.preliminary !== void 0 ? { preliminary: event.preliminary } : {},
|
|
727
|
+
...event.providerMetadata !== void 0 ? { providerMetadata: event.providerMetadata } : {}
|
|
728
|
+
}
|
|
729
|
+
];
|
|
730
|
+
case "file-change": {
|
|
731
|
+
const toolCallId = `harness-file-change-${generateId2()}`;
|
|
732
|
+
const payload = { event: event.event, path: event.path };
|
|
733
|
+
return [
|
|
734
|
+
{
|
|
735
|
+
type: "tool-call",
|
|
736
|
+
toolCallId,
|
|
737
|
+
toolName: "fileChange",
|
|
738
|
+
input: payload,
|
|
739
|
+
dynamic: true,
|
|
740
|
+
providerExecuted: true,
|
|
741
|
+
...event.harnessMetadata !== void 0 ? { providerMetadata: event.harnessMetadata } : {}
|
|
742
|
+
},
|
|
743
|
+
{
|
|
744
|
+
type: "tool-result",
|
|
745
|
+
toolCallId,
|
|
746
|
+
toolName: "fileChange",
|
|
747
|
+
input: payload,
|
|
748
|
+
output: payload,
|
|
749
|
+
dynamic: true,
|
|
750
|
+
providerExecuted: true,
|
|
751
|
+
...event.harnessMetadata !== void 0 ? { providerMetadata: event.harnessMetadata } : {}
|
|
752
|
+
}
|
|
753
|
+
];
|
|
754
|
+
}
|
|
755
|
+
case "compaction": {
|
|
756
|
+
const toolCallId = `harness-compaction-${generateId2()}`;
|
|
757
|
+
const output = {
|
|
758
|
+
trigger: event.trigger,
|
|
759
|
+
summary: event.summary,
|
|
760
|
+
...event.tokensBefore !== void 0 ? { tokensBefore: event.tokensBefore } : {},
|
|
761
|
+
...event.tokensAfter !== void 0 ? { tokensAfter: event.tokensAfter } : {}
|
|
762
|
+
};
|
|
763
|
+
return [
|
|
764
|
+
{
|
|
765
|
+
type: "tool-call",
|
|
766
|
+
toolCallId,
|
|
767
|
+
toolName: "compaction",
|
|
768
|
+
input: {},
|
|
769
|
+
dynamic: true,
|
|
770
|
+
providerExecuted: true,
|
|
771
|
+
...event.harnessMetadata !== void 0 ? { providerMetadata: event.harnessMetadata } : {}
|
|
772
|
+
},
|
|
773
|
+
{
|
|
774
|
+
type: "tool-result",
|
|
775
|
+
toolCallId,
|
|
776
|
+
toolName: "compaction",
|
|
777
|
+
input: {},
|
|
778
|
+
output,
|
|
779
|
+
dynamic: true,
|
|
780
|
+
providerExecuted: true,
|
|
781
|
+
...event.harnessMetadata !== void 0 ? { providerMetadata: event.harnessMetadata } : {}
|
|
782
|
+
}
|
|
783
|
+
];
|
|
784
|
+
}
|
|
785
|
+
case "error":
|
|
786
|
+
return [{ type: "error", error: event.error }];
|
|
787
|
+
case "raw":
|
|
788
|
+
return [
|
|
789
|
+
{ type: "raw", rawValue: event.rawValue }
|
|
790
|
+
];
|
|
791
|
+
case "finish-step":
|
|
792
|
+
case "finish":
|
|
793
|
+
return [];
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// src/agent/internal/strip-work-dir.ts
|
|
798
|
+
function stripWorkDir(part, sessionWorkDir) {
|
|
799
|
+
if (sessionWorkDir.length === 0) return part;
|
|
800
|
+
switch (part.type) {
|
|
801
|
+
case "tool-call":
|
|
802
|
+
return { ...part, input: stripString(part.input, sessionWorkDir) };
|
|
803
|
+
case "tool-result":
|
|
804
|
+
return {
|
|
805
|
+
...part,
|
|
806
|
+
result: stripDeep(part.result, sessionWorkDir)
|
|
807
|
+
};
|
|
808
|
+
case "file-change":
|
|
809
|
+
return { ...part, path: stripString(part.path, sessionWorkDir) };
|
|
810
|
+
default:
|
|
811
|
+
return part;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
function stripString(value, workDir) {
|
|
815
|
+
return value.split(`${workDir}/`).join("").split(workDir).join(".");
|
|
816
|
+
}
|
|
817
|
+
function stripDeep(value, workDir) {
|
|
818
|
+
if (typeof value === "string") return stripString(value, workDir);
|
|
819
|
+
if (Array.isArray(value)) return value.map((item) => stripDeep(item, workDir));
|
|
820
|
+
if (value !== null && typeof value === "object") {
|
|
821
|
+
const out = {};
|
|
822
|
+
for (const [key, val] of Object.entries(value)) {
|
|
823
|
+
out[key] = stripDeep(val, workDir);
|
|
824
|
+
}
|
|
825
|
+
return out;
|
|
826
|
+
}
|
|
827
|
+
return value;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// src/agent/internal/turn-telemetry.ts
|
|
831
|
+
import { generateId as generateId3 } from "@ai-sdk/provider-utils";
|
|
832
|
+
import { createTelemetryDispatcher } from "ai/internal";
|
|
833
|
+
var NOOP = {
|
|
834
|
+
start() {
|
|
835
|
+
},
|
|
836
|
+
ensureStepOpen() {
|
|
837
|
+
},
|
|
838
|
+
stepFinish() {
|
|
839
|
+
},
|
|
840
|
+
toolStart() {
|
|
841
|
+
},
|
|
842
|
+
toolEnd() {
|
|
843
|
+
},
|
|
844
|
+
end() {
|
|
845
|
+
},
|
|
846
|
+
error() {
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
function createTurnTelemetry(opts) {
|
|
850
|
+
var _a3;
|
|
851
|
+
if (opts.telemetry == null) return NOOP;
|
|
852
|
+
const dispatcher = createTelemetryDispatcher({ telemetry: opts.telemetry });
|
|
853
|
+
const callId = generateId3();
|
|
854
|
+
const provider = opts.harnessId;
|
|
855
|
+
let modelId = (_a3 = opts.modelId) != null ? _a3 : "";
|
|
856
|
+
const runtimeContext = opts.runtimeContext;
|
|
857
|
+
const inputMessages = [
|
|
858
|
+
{ role: "user", content: opts.promptText }
|
|
859
|
+
];
|
|
860
|
+
let started = false;
|
|
861
|
+
let stepOpen = false;
|
|
862
|
+
let stepNumber = 0;
|
|
863
|
+
let ended = false;
|
|
864
|
+
const openTools = /* @__PURE__ */ new Map();
|
|
865
|
+
const cast = (event) => event;
|
|
866
|
+
const fireStart = () => {
|
|
867
|
+
var _a4;
|
|
868
|
+
if (started) return;
|
|
869
|
+
started = true;
|
|
870
|
+
(_a4 = dispatcher.onStart) == null ? void 0 : _a4.call(
|
|
871
|
+
dispatcher,
|
|
872
|
+
cast({
|
|
873
|
+
callId,
|
|
874
|
+
operationId: "ai.harness",
|
|
875
|
+
provider,
|
|
876
|
+
modelId,
|
|
877
|
+
tools: void 0,
|
|
878
|
+
toolChoice: void 0,
|
|
879
|
+
activeTools: void 0,
|
|
880
|
+
maxRetries: 0,
|
|
881
|
+
timeout: void 0,
|
|
882
|
+
headers: void 0,
|
|
883
|
+
providerOptions: void 0,
|
|
884
|
+
output: void 0,
|
|
885
|
+
toolsContext: void 0,
|
|
886
|
+
runtimeContext,
|
|
887
|
+
instructions: opts.instructions,
|
|
888
|
+
messages: inputMessages
|
|
889
|
+
})
|
|
890
|
+
);
|
|
891
|
+
};
|
|
892
|
+
const start = (overrideModelId) => {
|
|
893
|
+
if (started) return;
|
|
894
|
+
if (overrideModelId) modelId = overrideModelId;
|
|
895
|
+
fireStart();
|
|
896
|
+
};
|
|
897
|
+
const ensureStepOpen = () => {
|
|
898
|
+
var _a4, _b3;
|
|
899
|
+
if (!started) fireStart();
|
|
900
|
+
if (stepOpen || ended) return;
|
|
901
|
+
stepOpen = true;
|
|
902
|
+
(_a4 = dispatcher.onStepStart) == null ? void 0 : _a4.call(
|
|
903
|
+
dispatcher,
|
|
904
|
+
cast({
|
|
905
|
+
callId,
|
|
906
|
+
provider,
|
|
907
|
+
modelId,
|
|
908
|
+
stepNumber,
|
|
909
|
+
tools: void 0,
|
|
910
|
+
toolChoice: void 0,
|
|
911
|
+
activeTools: void 0,
|
|
912
|
+
steps: new Array(stepNumber),
|
|
913
|
+
providerOptions: void 0,
|
|
914
|
+
output: void 0,
|
|
915
|
+
runtimeContext,
|
|
916
|
+
messages: inputMessages
|
|
917
|
+
})
|
|
918
|
+
);
|
|
919
|
+
(_b3 = dispatcher.onLanguageModelCallStart) == null ? void 0 : _b3.call(
|
|
920
|
+
dispatcher,
|
|
921
|
+
cast({
|
|
922
|
+
callId,
|
|
923
|
+
provider,
|
|
924
|
+
modelId,
|
|
925
|
+
messages: inputMessages,
|
|
926
|
+
tools: void 0
|
|
927
|
+
})
|
|
928
|
+
);
|
|
929
|
+
};
|
|
930
|
+
const inferenceEnd = (info) => {
|
|
931
|
+
var _a4;
|
|
932
|
+
(_a4 = dispatcher.onLanguageModelCallEnd) == null ? void 0 : _a4.call(
|
|
933
|
+
dispatcher,
|
|
934
|
+
cast({
|
|
935
|
+
callId,
|
|
936
|
+
finishReason: info.finishReason,
|
|
937
|
+
responseId: callId,
|
|
938
|
+
usage: info.usage,
|
|
939
|
+
content: info.content
|
|
940
|
+
})
|
|
941
|
+
);
|
|
942
|
+
};
|
|
943
|
+
const closeOpenTools = () => {
|
|
944
|
+
var _a4;
|
|
945
|
+
for (const call of openTools.values()) {
|
|
946
|
+
(_a4 = dispatcher.onToolExecutionEnd) == null ? void 0 : _a4.call(
|
|
947
|
+
dispatcher,
|
|
948
|
+
cast({
|
|
949
|
+
callId,
|
|
950
|
+
toolExecutionMs: 0,
|
|
951
|
+
messages: [],
|
|
952
|
+
toolCall: {
|
|
953
|
+
type: "tool-call",
|
|
954
|
+
toolCallId: call.toolCallId,
|
|
955
|
+
toolName: call.toolName,
|
|
956
|
+
input: call.input,
|
|
957
|
+
dynamic: true
|
|
958
|
+
},
|
|
959
|
+
toolContext: void 0,
|
|
960
|
+
toolOutput: { type: "error", error: new Error("tool span unclosed") }
|
|
961
|
+
})
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
openTools.clear();
|
|
965
|
+
};
|
|
966
|
+
return {
|
|
967
|
+
start,
|
|
968
|
+
ensureStepOpen,
|
|
969
|
+
stepFinish(info) {
|
|
970
|
+
var _a4, _b3;
|
|
971
|
+
if (!stepOpen) return;
|
|
972
|
+
const content = (_a4 = info.content) != null ? _a4 : [];
|
|
973
|
+
closeOpenTools();
|
|
974
|
+
inferenceEnd({
|
|
975
|
+
finishReason: info.finishReason,
|
|
976
|
+
usage: info.usage,
|
|
977
|
+
content
|
|
978
|
+
});
|
|
979
|
+
(_b3 = dispatcher.onStepEnd) == null ? void 0 : _b3.call(
|
|
980
|
+
dispatcher,
|
|
981
|
+
cast({
|
|
982
|
+
callId,
|
|
983
|
+
finishReason: info.finishReason,
|
|
984
|
+
usage: info.usage,
|
|
985
|
+
providerMetadata: info.providerMetadata,
|
|
986
|
+
content,
|
|
987
|
+
response: {
|
|
988
|
+
id: callId,
|
|
989
|
+
modelId,
|
|
990
|
+
timestamp: /* @__PURE__ */ new Date(0),
|
|
991
|
+
messages: []
|
|
992
|
+
}
|
|
993
|
+
})
|
|
994
|
+
);
|
|
995
|
+
stepOpen = false;
|
|
996
|
+
stepNumber += 1;
|
|
997
|
+
},
|
|
998
|
+
toolStart(call) {
|
|
999
|
+
var _a4;
|
|
1000
|
+
ensureStepOpen();
|
|
1001
|
+
openTools.set(call.toolCallId, call);
|
|
1002
|
+
(_a4 = dispatcher.onToolExecutionStart) == null ? void 0 : _a4.call(
|
|
1003
|
+
dispatcher,
|
|
1004
|
+
cast({
|
|
1005
|
+
callId,
|
|
1006
|
+
messages: [],
|
|
1007
|
+
toolCall: {
|
|
1008
|
+
type: "tool-call",
|
|
1009
|
+
toolCallId: call.toolCallId,
|
|
1010
|
+
toolName: call.toolName,
|
|
1011
|
+
input: call.input,
|
|
1012
|
+
dynamic: true
|
|
1013
|
+
},
|
|
1014
|
+
toolContext: void 0
|
|
1015
|
+
})
|
|
1016
|
+
);
|
|
1017
|
+
},
|
|
1018
|
+
toolEnd(toolCallId, output) {
|
|
1019
|
+
var _a4;
|
|
1020
|
+
const call = openTools.get(toolCallId);
|
|
1021
|
+
if (call == null) return;
|
|
1022
|
+
openTools.delete(toolCallId);
|
|
1023
|
+
(_a4 = dispatcher.onToolExecutionEnd) == null ? void 0 : _a4.call(
|
|
1024
|
+
dispatcher,
|
|
1025
|
+
cast({
|
|
1026
|
+
callId,
|
|
1027
|
+
toolExecutionMs: 0,
|
|
1028
|
+
messages: [],
|
|
1029
|
+
toolCall: {
|
|
1030
|
+
type: "tool-call",
|
|
1031
|
+
toolCallId: call.toolCallId,
|
|
1032
|
+
toolName: call.toolName,
|
|
1033
|
+
input: call.input,
|
|
1034
|
+
dynamic: true
|
|
1035
|
+
},
|
|
1036
|
+
toolContext: void 0,
|
|
1037
|
+
toolOutput: output.ok ? { type: "tool-result", output: output.output } : { type: "error", error: output.error }
|
|
1038
|
+
})
|
|
1039
|
+
);
|
|
1040
|
+
},
|
|
1041
|
+
end(info) {
|
|
1042
|
+
var _a4, _b3;
|
|
1043
|
+
if (ended) return;
|
|
1044
|
+
if (!started) fireStart();
|
|
1045
|
+
if (stepOpen) {
|
|
1046
|
+
closeOpenTools();
|
|
1047
|
+
inferenceEnd({
|
|
1048
|
+
finishReason: info.finishReason,
|
|
1049
|
+
usage: info.usage,
|
|
1050
|
+
content: []
|
|
1051
|
+
});
|
|
1052
|
+
(_a4 = dispatcher.onStepEnd) == null ? void 0 : _a4.call(
|
|
1053
|
+
dispatcher,
|
|
1054
|
+
cast({
|
|
1055
|
+
callId,
|
|
1056
|
+
finishReason: info.finishReason,
|
|
1057
|
+
usage: info.usage,
|
|
1058
|
+
providerMetadata: void 0,
|
|
1059
|
+
content: [],
|
|
1060
|
+
response: {
|
|
1061
|
+
id: callId,
|
|
1062
|
+
modelId,
|
|
1063
|
+
timestamp: /* @__PURE__ */ new Date(0),
|
|
1064
|
+
messages: []
|
|
1065
|
+
}
|
|
1066
|
+
})
|
|
1067
|
+
);
|
|
1068
|
+
stepOpen = false;
|
|
1069
|
+
}
|
|
1070
|
+
ended = true;
|
|
1071
|
+
(_b3 = dispatcher.onEnd) == null ? void 0 : _b3.call(
|
|
1072
|
+
dispatcher,
|
|
1073
|
+
cast({
|
|
1074
|
+
callId,
|
|
1075
|
+
operationId: "ai.harness",
|
|
1076
|
+
finishReason: info.finishReason,
|
|
1077
|
+
usage: info.usage,
|
|
1078
|
+
totalUsage: info.usage,
|
|
1079
|
+
content: [],
|
|
1080
|
+
steps: new Array(stepNumber),
|
|
1081
|
+
response: {
|
|
1082
|
+
id: callId,
|
|
1083
|
+
modelId,
|
|
1084
|
+
timestamp: /* @__PURE__ */ new Date(0),
|
|
1085
|
+
messages: []
|
|
1086
|
+
},
|
|
1087
|
+
runtimeContext
|
|
1088
|
+
})
|
|
1089
|
+
);
|
|
1090
|
+
},
|
|
1091
|
+
error(err) {
|
|
1092
|
+
var _a4;
|
|
1093
|
+
if (ended) return;
|
|
1094
|
+
if (!started) fireStart();
|
|
1095
|
+
closeOpenTools();
|
|
1096
|
+
ended = true;
|
|
1097
|
+
(_a4 = dispatcher.onError) == null ? void 0 : _a4.call(dispatcher, err);
|
|
1098
|
+
}
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// src/agent/internal/permission-mode.ts
|
|
1103
|
+
var DEFAULT_PERMISSION_MODE = "allow-all";
|
|
1104
|
+
function resolvePermissionMode(input) {
|
|
1105
|
+
var _a3;
|
|
1106
|
+
return (_a3 = input.permissionMode) != null ? _a3 : DEFAULT_PERMISSION_MODE;
|
|
1107
|
+
}
|
|
1108
|
+
function permissionModeNeedsBuiltinSupport(input) {
|
|
1109
|
+
return input.permissionMode !== "allow-all";
|
|
1110
|
+
}
|
|
1111
|
+
function resolveCustomToolApproval(input) {
|
|
1112
|
+
var _a3;
|
|
1113
|
+
const status = normalizeToolApprovalStatus({
|
|
1114
|
+
status: (_a3 = input.toolApproval) == null ? void 0 : _a3[input.toolName]
|
|
1115
|
+
});
|
|
1116
|
+
switch (status.type) {
|
|
1117
|
+
case "not-applicable":
|
|
1118
|
+
case "approved":
|
|
1119
|
+
return { type: "allow", reason: status.reason };
|
|
1120
|
+
case "denied":
|
|
1121
|
+
return { type: "deny", reason: status.reason };
|
|
1122
|
+
case "user-approval":
|
|
1123
|
+
return { type: "request" };
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
function normalizeToolApprovalStatus(input) {
|
|
1127
|
+
if (input.status === void 0) return { type: "not-applicable" };
|
|
1128
|
+
if (typeof input.status === "string") return { type: input.status };
|
|
1129
|
+
return input.status;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// src/agent/internal/run-prompt.ts
|
|
1133
|
+
function runPrompt(input) {
|
|
1134
|
+
var _a3, _b3, _c;
|
|
1135
|
+
const result = new HarnessStreamTextResult({
|
|
1136
|
+
tools: input.tools,
|
|
1137
|
+
runtimeContext: input.runtimeContext,
|
|
1138
|
+
// toolsContext is not configurable for harnesses; pass undefined cast.
|
|
1139
|
+
toolsContext: void 0,
|
|
1140
|
+
harnessId: input.harness.harnessId,
|
|
1141
|
+
sessionId: input.session.sessionId
|
|
1142
|
+
});
|
|
1143
|
+
const pendingToolApprovals = (_a3 = input.pendingToolApprovals) != null ? _a3 : [];
|
|
1144
|
+
const onPendingToolApproval = (_b3 = input.onPendingToolApproval) != null ? _b3 : (() => {
|
|
1145
|
+
});
|
|
1146
|
+
const onToolApprovalSettled = (_c = input.onToolApprovalSettled) != null ? _c : (() => {
|
|
1147
|
+
});
|
|
1148
|
+
const telemetry = createTurnTelemetry({
|
|
1149
|
+
telemetry: input.telemetry,
|
|
1150
|
+
harnessId: input.harness.harnessId,
|
|
1151
|
+
modelId: input.session.modelId,
|
|
1152
|
+
instructions: input.instructions,
|
|
1153
|
+
promptText: input.prompt != null ? promptToText(input.prompt) : "",
|
|
1154
|
+
runtimeContext: input.runtimeContext
|
|
1155
|
+
});
|
|
1156
|
+
const done = (async () => {
|
|
1157
|
+
var _a4, _b4, _c2, _d, _e, _f;
|
|
1158
|
+
let bridge;
|
|
1159
|
+
try {
|
|
1160
|
+
bridge = await toHarnessStream({
|
|
1161
|
+
invoke: input.mode === "continue" ? (emit) => input.session.doContinueTurn({
|
|
1162
|
+
tools: input.toolSpecs,
|
|
1163
|
+
abortSignal: input.abortSignal,
|
|
1164
|
+
emit
|
|
1165
|
+
}) : (emit) => {
|
|
1166
|
+
if (input.prompt == null) {
|
|
1167
|
+
throw new Error(
|
|
1168
|
+
'runPrompt: `prompt` is required for mode "prompt".'
|
|
1169
|
+
);
|
|
1170
|
+
}
|
|
1171
|
+
return input.session.doPromptTurn({
|
|
1172
|
+
prompt: input.prompt,
|
|
1173
|
+
tools: input.toolSpecs,
|
|
1174
|
+
instructions: input.instructions,
|
|
1175
|
+
abortSignal: input.abortSignal,
|
|
1176
|
+
emit
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
});
|
|
1180
|
+
} catch (err) {
|
|
1181
|
+
telemetry.error(err);
|
|
1182
|
+
result.fail(err);
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
const { stream, control } = bridge;
|
|
1186
|
+
const reader = stream.getReader();
|
|
1187
|
+
const toolCallsByToolCallId = /* @__PURE__ */ new Map();
|
|
1188
|
+
const rawToolCallsByToolCallId = /* @__PURE__ */ new Map();
|
|
1189
|
+
const pendingApprovalsByApprovalId = new Map(
|
|
1190
|
+
pendingToolApprovals.map((approval) => [approval.approvalId, approval])
|
|
1191
|
+
);
|
|
1192
|
+
const pendingApprovalsByToolCallId = new Map(
|
|
1193
|
+
pendingToolApprovals.map((approval) => [approval.toolCallId, approval])
|
|
1194
|
+
);
|
|
1195
|
+
const continuationsByApprovalId = new Map(
|
|
1196
|
+
((_a4 = input.toolApprovalContinuations) != null ? _a4 : []).map((continuation) => [
|
|
1197
|
+
continuation.approvalResponse.approvalId,
|
|
1198
|
+
continuation
|
|
1199
|
+
])
|
|
1200
|
+
);
|
|
1201
|
+
const settledApprovalToolCallIds = /* @__PURE__ */ new Set();
|
|
1202
|
+
let stepText = "";
|
|
1203
|
+
let stepReasoning = "";
|
|
1204
|
+
let stepToolCalls = [];
|
|
1205
|
+
const buildStepContent = () => {
|
|
1206
|
+
const parts = [];
|
|
1207
|
+
if (stepText) parts.push({ type: "text", text: stepText });
|
|
1208
|
+
if (stepReasoning) parts.push({ type: "reasoning", text: stepReasoning });
|
|
1209
|
+
parts.push(...stepToolCalls);
|
|
1210
|
+
return parts;
|
|
1211
|
+
};
|
|
1212
|
+
const resetStepContent = () => {
|
|
1213
|
+
stepText = "";
|
|
1214
|
+
stepReasoning = "";
|
|
1215
|
+
stepToolCalls = [];
|
|
1216
|
+
};
|
|
1217
|
+
const zeroUsage = {
|
|
1218
|
+
inputTokens: {
|
|
1219
|
+
total: void 0,
|
|
1220
|
+
noCache: void 0,
|
|
1221
|
+
cacheRead: void 0,
|
|
1222
|
+
cacheWrite: void 0
|
|
1223
|
+
},
|
|
1224
|
+
outputTokens: {
|
|
1225
|
+
total: void 0,
|
|
1226
|
+
text: void 0,
|
|
1227
|
+
reasoning: void 0
|
|
1228
|
+
}
|
|
1229
|
+
};
|
|
1230
|
+
const toolCallsFinishReason = {
|
|
1231
|
+
unified: "tool-calls",
|
|
1232
|
+
raw: void 0
|
|
1233
|
+
};
|
|
1234
|
+
const finishForToolApprovalPause = async () => {
|
|
1235
|
+
telemetry.stepFinish({
|
|
1236
|
+
finishReason: toolCallsFinishReason,
|
|
1237
|
+
usage: zeroUsage,
|
|
1238
|
+
content: buildStepContent()
|
|
1239
|
+
});
|
|
1240
|
+
resetStepContent();
|
|
1241
|
+
result.finishStep({
|
|
1242
|
+
finishReason: toolCallsFinishReason,
|
|
1243
|
+
usage: zeroUsage,
|
|
1244
|
+
providerMetadata: void 0,
|
|
1245
|
+
warnings: []
|
|
1246
|
+
});
|
|
1247
|
+
telemetry.end({
|
|
1248
|
+
finishReason: toolCallsFinishReason,
|
|
1249
|
+
usage: zeroUsage
|
|
1250
|
+
});
|
|
1251
|
+
await result.finish();
|
|
1252
|
+
};
|
|
1253
|
+
const enqueueApprovalRequest = (approval) => {
|
|
1254
|
+
result.enqueue({
|
|
1255
|
+
type: "tool-approval-request",
|
|
1256
|
+
approvalId: approval.approvalId,
|
|
1257
|
+
toolCall: approval.toolCall,
|
|
1258
|
+
...approval.isAutomatic !== void 0 ? { isAutomatic: approval.isAutomatic } : {}
|
|
1259
|
+
});
|
|
1260
|
+
};
|
|
1261
|
+
const enqueueAutomaticApprovalResponse = (input2) => {
|
|
1262
|
+
result.enqueue({
|
|
1263
|
+
type: "tool-approval-response",
|
|
1264
|
+
approvalId: input2.approvalId,
|
|
1265
|
+
toolCall: input2.toolCall,
|
|
1266
|
+
approved: input2.approved,
|
|
1267
|
+
...input2.reason !== void 0 ? { reason: input2.reason } : {},
|
|
1268
|
+
...input2.providerExecuted !== void 0 ? { providerExecuted: input2.providerExecuted } : {}
|
|
1269
|
+
});
|
|
1270
|
+
};
|
|
1271
|
+
const enqueueApprovalResponse = (approval, continuation) => {
|
|
1272
|
+
result.enqueue({
|
|
1273
|
+
type: "tool-approval-response",
|
|
1274
|
+
approvalId: approval.approvalId,
|
|
1275
|
+
toolCall: continuation.toolCall,
|
|
1276
|
+
approved: continuation.approvalResponse.approved,
|
|
1277
|
+
...continuation.approvalResponse.reason !== void 0 ? { reason: continuation.approvalResponse.reason } : {},
|
|
1278
|
+
...approval.providerExecuted !== void 0 ? { providerExecuted: approval.providerExecuted } : {}
|
|
1279
|
+
});
|
|
1280
|
+
};
|
|
1281
|
+
const processPendingApprovalContinuation = async (approval, continuation) => {
|
|
1282
|
+
var _a5;
|
|
1283
|
+
enqueueApprovalResponse(approval, continuation);
|
|
1284
|
+
onToolApprovalSettled(approval.approvalId);
|
|
1285
|
+
pendingApprovalsByApprovalId.delete(approval.approvalId);
|
|
1286
|
+
pendingApprovalsByToolCallId.delete(approval.toolCallId);
|
|
1287
|
+
settledApprovalToolCallIds.add(approval.toolCallId);
|
|
1288
|
+
if (approval.kind === "builtin") {
|
|
1289
|
+
if (control.submitToolApproval == null) {
|
|
1290
|
+
throw new Error(
|
|
1291
|
+
`Harness '${input.harness.harnessId}' emitted a built-in tool approval request but does not support approval responses.`
|
|
1292
|
+
);
|
|
1293
|
+
}
|
|
1294
|
+
await control.submitToolApproval({
|
|
1295
|
+
approvalId: approval.approvalId,
|
|
1296
|
+
approved: continuation.approvalResponse.approved,
|
|
1297
|
+
reason: continuation.approvalResponse.reason
|
|
1298
|
+
});
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
if (!continuation.approvalResponse.approved) {
|
|
1302
|
+
await control.submitToolResult({
|
|
1303
|
+
toolCallId: approval.toolCallId,
|
|
1304
|
+
output: {
|
|
1305
|
+
type: "execution-denied",
|
|
1306
|
+
reason: continuation.approvalResponse.reason
|
|
1307
|
+
}
|
|
1308
|
+
});
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
const rawToolCall = (_a5 = rawToolCallsByToolCallId.get(approval.toolCallId)) != null ? _a5 : {
|
|
1312
|
+
type: "tool-call",
|
|
1313
|
+
toolCallId: approval.toolCallId,
|
|
1314
|
+
toolName: approval.toolName,
|
|
1315
|
+
input: approval.input
|
|
1316
|
+
};
|
|
1317
|
+
const outcome = await maybeExecuteHostTool({
|
|
1318
|
+
event: rawToolCall,
|
|
1319
|
+
tools: input.tools,
|
|
1320
|
+
sandboxSession: input.sandboxSession,
|
|
1321
|
+
abortSignal: input.abortSignal,
|
|
1322
|
+
control,
|
|
1323
|
+
onPreliminaryResult: (preliminaryOutput) => {
|
|
1324
|
+
const stripped = stripWorkDir(
|
|
1325
|
+
{
|
|
1326
|
+
type: "tool-result",
|
|
1327
|
+
toolCallId: rawToolCall.toolCallId,
|
|
1328
|
+
toolName: rawToolCall.toolName,
|
|
1329
|
+
result: preliminaryOutput
|
|
1330
|
+
},
|
|
1331
|
+
input.sessionWorkDir
|
|
1332
|
+
);
|
|
1333
|
+
result.enqueue({
|
|
1334
|
+
type: "tool-result",
|
|
1335
|
+
toolCallId: rawToolCall.toolCallId,
|
|
1336
|
+
toolName: rawToolCall.toolName,
|
|
1337
|
+
input: void 0,
|
|
1338
|
+
output: stripped.result,
|
|
1339
|
+
preliminary: true
|
|
1340
|
+
});
|
|
1341
|
+
}
|
|
1342
|
+
});
|
|
1343
|
+
telemetry.toolEnd(rawToolCall.toolCallId, outcome);
|
|
1344
|
+
};
|
|
1345
|
+
try {
|
|
1346
|
+
for (const approval of pendingToolApprovals) {
|
|
1347
|
+
const continuation = continuationsByApprovalId.get(approval.approvalId);
|
|
1348
|
+
if (continuation != null) {
|
|
1349
|
+
await processPendingApprovalContinuation(approval, continuation);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
while (true) {
|
|
1353
|
+
const { value, done: done2 } = await reader.read();
|
|
1354
|
+
if (done2) break;
|
|
1355
|
+
if (value == null) continue;
|
|
1356
|
+
if (value.type === "stream-start") {
|
|
1357
|
+
telemetry.start((_b4 = value.modelId) != null ? _b4 : input.session.modelId);
|
|
1358
|
+
}
|
|
1359
|
+
if (value.type !== "stream-start" && value.type !== "finish-step" && value.type !== "finish" && value.type !== "error") {
|
|
1360
|
+
telemetry.ensureStepOpen();
|
|
1361
|
+
}
|
|
1362
|
+
const displayValue = stripWorkDir(value, input.sessionWorkDir);
|
|
1363
|
+
const settledApprovalToolCallReplay = displayValue.type === "tool-call" && !displayValue.providerExecuted && settledApprovalToolCallIds.has(displayValue.toolCallId);
|
|
1364
|
+
if (settledApprovalToolCallReplay) {
|
|
1365
|
+
continue;
|
|
1366
|
+
}
|
|
1367
|
+
for (const part of translateStreamPart(displayValue)) {
|
|
1368
|
+
result.enqueue(part);
|
|
1369
|
+
}
|
|
1370
|
+
if (displayValue.type === "tool-call") {
|
|
1371
|
+
const parsed = await validateToolCall({
|
|
1372
|
+
event: displayValue,
|
|
1373
|
+
tools: input.tools
|
|
1374
|
+
});
|
|
1375
|
+
const parsedToolCall = asToolCallTextStreamPart({ part: parsed });
|
|
1376
|
+
rawToolCallsByToolCallId.set(displayValue.toolCallId, displayValue);
|
|
1377
|
+
toolCallsByToolCallId.set(displayValue.toolCallId, parsedToolCall);
|
|
1378
|
+
result.enqueue(parsed);
|
|
1379
|
+
}
|
|
1380
|
+
if (value.type === "text-delta") {
|
|
1381
|
+
stepText += value.delta;
|
|
1382
|
+
} else if (value.type === "reasoning-delta") {
|
|
1383
|
+
stepReasoning += value.delta;
|
|
1384
|
+
}
|
|
1385
|
+
if (value.type === "tool-call") {
|
|
1386
|
+
stepToolCalls.push({
|
|
1387
|
+
type: "tool-call",
|
|
1388
|
+
toolCallId: value.toolCallId,
|
|
1389
|
+
toolName: value.toolName,
|
|
1390
|
+
input: value.input
|
|
1391
|
+
});
|
|
1392
|
+
telemetry.toolStart({
|
|
1393
|
+
toolCallId: value.toolCallId,
|
|
1394
|
+
toolName: value.toolName,
|
|
1395
|
+
input: value.input
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1398
|
+
if (value.type === "tool-result") {
|
|
1399
|
+
telemetry.toolEnd(
|
|
1400
|
+
value.toolCallId,
|
|
1401
|
+
value.isError ? { ok: false, error: value.result } : { ok: true, output: value.result }
|
|
1402
|
+
);
|
|
1403
|
+
}
|
|
1404
|
+
if (value.type === "tool-approval-request") {
|
|
1405
|
+
const toolCall = toolCallsByToolCallId.get(value.toolCallId);
|
|
1406
|
+
if (toolCall == null) {
|
|
1407
|
+
throw new Error(
|
|
1408
|
+
`Harness '${input.harness.harnessId}' emitted approval request '${value.approvalId}' for unknown tool call '${value.toolCallId}'.`
|
|
1409
|
+
);
|
|
1410
|
+
}
|
|
1411
|
+
const rawToolCall = rawToolCallsByToolCallId.get(value.toolCallId);
|
|
1412
|
+
const pendingApproval = (_e = pendingApprovalsByApprovalId.get(value.approvalId)) != null ? _e : {
|
|
1413
|
+
approvalId: value.approvalId,
|
|
1414
|
+
toolCallId: value.toolCallId,
|
|
1415
|
+
toolName: toolCall.toolName,
|
|
1416
|
+
input: (_c2 = rawToolCall == null ? void 0 : rawToolCall.input) != null ? _c2 : JSON.stringify(toolCall.input),
|
|
1417
|
+
kind: "builtin",
|
|
1418
|
+
providerExecuted: (_d = rawToolCall == null ? void 0 : rawToolCall.providerExecuted) != null ? _d : true,
|
|
1419
|
+
...(rawToolCall == null ? void 0 : rawToolCall.nativeName) !== void 0 ? { nativeName: rawToolCall.nativeName } : {}
|
|
1420
|
+
};
|
|
1421
|
+
pendingApprovalsByApprovalId.set(
|
|
1422
|
+
pendingApproval.approvalId,
|
|
1423
|
+
pendingApproval
|
|
1424
|
+
);
|
|
1425
|
+
pendingApprovalsByToolCallId.set(
|
|
1426
|
+
pendingApproval.toolCallId,
|
|
1427
|
+
pendingApproval
|
|
1428
|
+
);
|
|
1429
|
+
const continuation = continuationsByApprovalId.get(
|
|
1430
|
+
pendingApproval.approvalId
|
|
1431
|
+
);
|
|
1432
|
+
if (continuation != null) {
|
|
1433
|
+
await processPendingApprovalContinuation(
|
|
1434
|
+
pendingApproval,
|
|
1435
|
+
continuation
|
|
1436
|
+
);
|
|
1437
|
+
continue;
|
|
1438
|
+
}
|
|
1439
|
+
onPendingToolApproval(pendingApproval);
|
|
1440
|
+
enqueueApprovalRequest({
|
|
1441
|
+
approvalId: pendingApproval.approvalId,
|
|
1442
|
+
toolCall
|
|
1443
|
+
});
|
|
1444
|
+
await finishForToolApprovalPause();
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
if (value.type === "finish-step") {
|
|
1448
|
+
telemetry.stepFinish({
|
|
1449
|
+
finishReason: value.finishReason,
|
|
1450
|
+
usage: value.usage,
|
|
1451
|
+
providerMetadata: value.harnessMetadata,
|
|
1452
|
+
content: buildStepContent()
|
|
1453
|
+
});
|
|
1454
|
+
resetStepContent();
|
|
1455
|
+
result.finishStep({
|
|
1456
|
+
finishReason: value.finishReason,
|
|
1457
|
+
usage: value.usage,
|
|
1458
|
+
providerMetadata: value.harnessMetadata,
|
|
1459
|
+
warnings: []
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1462
|
+
if (value.type === "finish") {
|
|
1463
|
+
telemetry.end({
|
|
1464
|
+
finishReason: value.finishReason,
|
|
1465
|
+
usage: value.totalUsage
|
|
1466
|
+
});
|
|
1467
|
+
}
|
|
1468
|
+
if (value.type === "tool-call" && !value.providerExecuted) {
|
|
1469
|
+
const toolCall = value;
|
|
1470
|
+
const parsedToolCall = toolCallsByToolCallId.get(toolCall.toolCallId);
|
|
1471
|
+
if (parsedToolCall == null) {
|
|
1472
|
+
throw new Error(
|
|
1473
|
+
`Harness '${input.harness.harnessId}' could not find parsed tool call '${toolCall.toolCallId}' for custom tool approval.`
|
|
1474
|
+
);
|
|
1475
|
+
}
|
|
1476
|
+
const customToolApprovalDecision = resolveCustomToolApproval({
|
|
1477
|
+
toolName: toolCall.toolName,
|
|
1478
|
+
toolApproval: input.toolApproval
|
|
1479
|
+
});
|
|
1480
|
+
if (customToolApprovalDecision.type === "deny") {
|
|
1481
|
+
const approvalId = generateId4();
|
|
1482
|
+
enqueueApprovalRequest({
|
|
1483
|
+
approvalId,
|
|
1484
|
+
toolCall: parsedToolCall,
|
|
1485
|
+
isAutomatic: true
|
|
1486
|
+
});
|
|
1487
|
+
enqueueAutomaticApprovalResponse({
|
|
1488
|
+
approvalId,
|
|
1489
|
+
toolCall: parsedToolCall,
|
|
1490
|
+
approved: false,
|
|
1491
|
+
reason: customToolApprovalDecision.reason,
|
|
1492
|
+
providerExecuted: false
|
|
1493
|
+
});
|
|
1494
|
+
const output = {
|
|
1495
|
+
type: "execution-denied",
|
|
1496
|
+
reason: customToolApprovalDecision.reason
|
|
1497
|
+
};
|
|
1498
|
+
await control.submitToolResult({
|
|
1499
|
+
toolCallId: toolCall.toolCallId,
|
|
1500
|
+
output
|
|
1501
|
+
});
|
|
1502
|
+
telemetry.toolEnd(toolCall.toolCallId, { ok: true, output });
|
|
1503
|
+
continue;
|
|
1504
|
+
}
|
|
1505
|
+
const pendingApproval = (_f = pendingApprovalsByToolCallId.get(toolCall.toolCallId)) != null ? _f : customToolApprovalDecision.type === "request" ? {
|
|
1506
|
+
approvalId: generateId4(),
|
|
1507
|
+
toolCallId: toolCall.toolCallId,
|
|
1508
|
+
toolName: toolCall.toolName,
|
|
1509
|
+
input: toolCall.input,
|
|
1510
|
+
kind: "custom",
|
|
1511
|
+
providerExecuted: false,
|
|
1512
|
+
...toolCall.nativeName !== void 0 ? { nativeName: toolCall.nativeName } : {}
|
|
1513
|
+
} : void 0;
|
|
1514
|
+
if (pendingApproval != null) {
|
|
1515
|
+
pendingApprovalsByApprovalId.set(
|
|
1516
|
+
pendingApproval.approvalId,
|
|
1517
|
+
pendingApproval
|
|
1518
|
+
);
|
|
1519
|
+
pendingApprovalsByToolCallId.set(
|
|
1520
|
+
pendingApproval.toolCallId,
|
|
1521
|
+
pendingApproval
|
|
1522
|
+
);
|
|
1523
|
+
const continuation = continuationsByApprovalId.get(
|
|
1524
|
+
pendingApproval.approvalId
|
|
1525
|
+
);
|
|
1526
|
+
if (continuation != null) {
|
|
1527
|
+
await processPendingApprovalContinuation(
|
|
1528
|
+
pendingApproval,
|
|
1529
|
+
continuation
|
|
1530
|
+
);
|
|
1531
|
+
continue;
|
|
1532
|
+
}
|
|
1533
|
+
const pendingParsedToolCall = toolCallsByToolCallId.get(
|
|
1534
|
+
pendingApproval.toolCallId
|
|
1535
|
+
);
|
|
1536
|
+
if (pendingParsedToolCall == null) {
|
|
1537
|
+
throw new Error(
|
|
1538
|
+
`Harness '${input.harness.harnessId}' could not find parsed tool call '${pendingApproval.toolCallId}' for approval request '${pendingApproval.approvalId}'.`
|
|
1539
|
+
);
|
|
1540
|
+
}
|
|
1541
|
+
onPendingToolApproval(pendingApproval);
|
|
1542
|
+
enqueueApprovalRequest({
|
|
1543
|
+
approvalId: pendingApproval.approvalId,
|
|
1544
|
+
toolCall: pendingParsedToolCall
|
|
1545
|
+
});
|
|
1546
|
+
await finishForToolApprovalPause();
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
const outcome = await maybeExecuteHostTool({
|
|
1550
|
+
event: toolCall,
|
|
1551
|
+
tools: input.tools,
|
|
1552
|
+
sandboxSession: input.sandboxSession,
|
|
1553
|
+
abortSignal: input.abortSignal,
|
|
1554
|
+
control,
|
|
1555
|
+
onPreliminaryResult: (preliminaryOutput) => {
|
|
1556
|
+
const stripped = stripWorkDir(
|
|
1557
|
+
{
|
|
1558
|
+
type: "tool-result",
|
|
1559
|
+
toolCallId: toolCall.toolCallId,
|
|
1560
|
+
toolName: toolCall.toolName,
|
|
1561
|
+
result: preliminaryOutput
|
|
1562
|
+
},
|
|
1563
|
+
input.sessionWorkDir
|
|
1564
|
+
);
|
|
1565
|
+
result.enqueue({
|
|
1566
|
+
type: "tool-result",
|
|
1567
|
+
toolCallId: toolCall.toolCallId,
|
|
1568
|
+
toolName: toolCall.toolName,
|
|
1569
|
+
input: void 0,
|
|
1570
|
+
output: stripped.result,
|
|
1571
|
+
preliminary: true
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
});
|
|
1575
|
+
telemetry.toolEnd(toolCall.toolCallId, outcome);
|
|
1576
|
+
}
|
|
1577
|
+
if (value.type === "error") {
|
|
1578
|
+
telemetry.error(value.error);
|
|
1579
|
+
result.fail(value.error);
|
|
1580
|
+
return;
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
await result.finish();
|
|
1584
|
+
} catch (err) {
|
|
1585
|
+
telemetry.error(err);
|
|
1586
|
+
result.fail(err);
|
|
1587
|
+
} finally {
|
|
1588
|
+
reader.releaseLock();
|
|
1589
|
+
}
|
|
1590
|
+
})();
|
|
1591
|
+
done.catch(() => {
|
|
1592
|
+
});
|
|
1593
|
+
return { result, done };
|
|
1594
|
+
}
|
|
1595
|
+
function asToolCallTextStreamPart(input) {
|
|
1596
|
+
if (input.part.type !== "tool-call") {
|
|
1597
|
+
throw new Error(
|
|
1598
|
+
`Expected parsed tool-call stream part, got '${input.part.type}'.`
|
|
1599
|
+
);
|
|
1600
|
+
}
|
|
1601
|
+
return input.part;
|
|
1602
|
+
}
|
|
1603
|
+
async function maybeExecuteHostTool(input) {
|
|
1604
|
+
const tool = input.tools[input.event.toolName];
|
|
1605
|
+
if (!isExecutableTool(tool)) return { ok: true, output: void 0 };
|
|
1606
|
+
const parsed = await safeParseJSON({ text: input.event.input });
|
|
1607
|
+
const args = parsed.success ? parsed.value : input.event.input;
|
|
1608
|
+
try {
|
|
1609
|
+
let output;
|
|
1610
|
+
const stream = executeTool({
|
|
1611
|
+
tool,
|
|
1612
|
+
input: args,
|
|
1613
|
+
options: {
|
|
1614
|
+
toolCallId: input.event.toolCallId,
|
|
1615
|
+
messages: [],
|
|
1616
|
+
abortSignal: input.abortSignal,
|
|
1617
|
+
context: void 0,
|
|
1618
|
+
experimental_sandbox: input.sandboxSession
|
|
1619
|
+
}
|
|
1620
|
+
});
|
|
1621
|
+
for await (const part of stream) {
|
|
1622
|
+
if (part.type === "preliminary") {
|
|
1623
|
+
input.onPreliminaryResult(part.output);
|
|
1624
|
+
} else {
|
|
1625
|
+
output = part.output;
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
await input.control.submitToolResult({
|
|
1629
|
+
toolCallId: input.event.toolCallId,
|
|
1630
|
+
output
|
|
1631
|
+
});
|
|
1632
|
+
return { ok: true, output };
|
|
1633
|
+
} catch (err) {
|
|
1634
|
+
await input.control.submitToolResult({
|
|
1635
|
+
toolCallId: input.event.toolCallId,
|
|
1636
|
+
output: { error: String(err) },
|
|
1637
|
+
isError: true
|
|
1638
|
+
});
|
|
1639
|
+
return { ok: false, error: err };
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
async function validateToolCall(args) {
|
|
1643
|
+
const { event, tools } = args;
|
|
1644
|
+
const toolCall = {
|
|
1645
|
+
type: "tool-call",
|
|
1646
|
+
toolCallId: event.toolCallId,
|
|
1647
|
+
toolName: event.toolName,
|
|
1648
|
+
input: event.input,
|
|
1649
|
+
...event.providerExecuted !== void 0 ? { providerExecuted: event.providerExecuted } : {},
|
|
1650
|
+
...event.providerMetadata !== void 0 ? { providerMetadata: event.providerMetadata } : {}
|
|
1651
|
+
};
|
|
1652
|
+
const parsed = await parseToolCall({
|
|
1653
|
+
toolCall,
|
|
1654
|
+
tools,
|
|
1655
|
+
repairToolCall: void 0,
|
|
1656
|
+
refineToolInput: void 0,
|
|
1657
|
+
instructions: void 0,
|
|
1658
|
+
messages: []
|
|
1659
|
+
});
|
|
1660
|
+
return parsed;
|
|
1661
|
+
}
|
|
1662
|
+
function promptToText(prompt) {
|
|
1663
|
+
if (typeof prompt === "string") return prompt;
|
|
1664
|
+
const content = prompt.content;
|
|
1665
|
+
if (typeof content === "string") return content;
|
|
1666
|
+
if (Array.isArray(content)) {
|
|
1667
|
+
return content.filter(
|
|
1668
|
+
(part) => typeof part === "object" && part != null && part.type === "text"
|
|
1669
|
+
).map((part) => part.text).join("");
|
|
1670
|
+
}
|
|
1671
|
+
return "";
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
// src/agent/harness-agent-session.ts
|
|
1675
|
+
var HarnessAgentSession = class {
|
|
1676
|
+
constructor(options) {
|
|
1677
|
+
this.pendingToolApprovals = /* @__PURE__ */ new Map();
|
|
1678
|
+
this.stopped = false;
|
|
1679
|
+
var _a3;
|
|
1680
|
+
this.sessionId = options.sessionId;
|
|
1681
|
+
this.harness = options.harness;
|
|
1682
|
+
this.underlyingSession = options.underlyingSession;
|
|
1683
|
+
this.sandboxSession = options.sandboxSession;
|
|
1684
|
+
this.sandboxProvider = options.sandboxProvider;
|
|
1685
|
+
this.leasedBridgePort = options.leasedBridgePort;
|
|
1686
|
+
this.sessionWorkDir = options.sessionWorkDir;
|
|
1687
|
+
this.toolApproval = options.toolApproval;
|
|
1688
|
+
for (const approval of (_a3 = options.pendingToolApprovals) != null ? _a3 : []) {
|
|
1689
|
+
this.pendingToolApprovals.set(approval.approvalId, approval);
|
|
1690
|
+
}
|
|
1691
|
+
this.isResume = options.underlyingSession.isResume;
|
|
1692
|
+
}
|
|
1693
|
+
/**
|
|
1694
|
+
* Active network sandbox session.
|
|
1695
|
+
*
|
|
1696
|
+
* @internal — accessed by session turn and lifecycle drivers.
|
|
1697
|
+
*/
|
|
1698
|
+
getSandboxSession() {
|
|
1699
|
+
if (this.stopped || this.sandboxSession == null) {
|
|
1700
|
+
throw new Error(
|
|
1701
|
+
`Harness session ${this.sessionId} has ended and cannot be reused.`
|
|
1702
|
+
);
|
|
1703
|
+
}
|
|
1704
|
+
return this.sandboxSession;
|
|
1705
|
+
}
|
|
1706
|
+
/**
|
|
1707
|
+
* Working directory the agent runs in for this session. Used to strip the
|
|
1708
|
+
* prefix from absolute paths in stream events before they reach consumers.
|
|
1709
|
+
*
|
|
1710
|
+
* @internal — accessed by session turn drivers.
|
|
1711
|
+
*/
|
|
1712
|
+
getSessionWorkDir() {
|
|
1713
|
+
return this.sessionWorkDir;
|
|
1714
|
+
}
|
|
1715
|
+
promptTurn(options) {
|
|
1716
|
+
const session = this.requireReusableSession();
|
|
1717
|
+
if (this.pendingToolApprovals.size > 0) {
|
|
1718
|
+
throw new Error(
|
|
1719
|
+
`Harness session ${this.sessionId} has pending tool approvals and must be continued with approval responses before accepting a new prompt.`
|
|
1720
|
+
);
|
|
1721
|
+
}
|
|
1722
|
+
const sandboxSession = this.getSandboxSession();
|
|
1723
|
+
return runPrompt({
|
|
1724
|
+
harness: this.harness,
|
|
1725
|
+
session,
|
|
1726
|
+
prompt: options.prompt,
|
|
1727
|
+
instructions: options.instructions,
|
|
1728
|
+
tools: options.tools,
|
|
1729
|
+
toolSpecs: options.toolSpecs,
|
|
1730
|
+
sandboxSession: sandboxSession.restricted(),
|
|
1731
|
+
sessionWorkDir: this.sessionWorkDir,
|
|
1732
|
+
runtimeContext: options.runtimeContext,
|
|
1733
|
+
abortSignal: options.abortSignal,
|
|
1734
|
+
telemetry: options.telemetry,
|
|
1735
|
+
toolApproval: this.toolApproval,
|
|
1736
|
+
pendingToolApprovals: this.getPendingToolApprovals(),
|
|
1737
|
+
onPendingToolApproval: (approval) => {
|
|
1738
|
+
this.pendingToolApprovals.set(approval.approvalId, approval);
|
|
1739
|
+
},
|
|
1740
|
+
onToolApprovalSettled: (approvalId) => {
|
|
1741
|
+
this.pendingToolApprovals.delete(approvalId);
|
|
1742
|
+
}
|
|
1743
|
+
});
|
|
1744
|
+
}
|
|
1745
|
+
continueTurn(options) {
|
|
1746
|
+
const session = this.requireReusableSession();
|
|
1747
|
+
const sandboxSession = this.getSandboxSession();
|
|
1748
|
+
return runPrompt({
|
|
1749
|
+
harness: this.harness,
|
|
1750
|
+
session,
|
|
1751
|
+
mode: "continue",
|
|
1752
|
+
instructions: options.instructions,
|
|
1753
|
+
tools: options.tools,
|
|
1754
|
+
toolSpecs: options.toolSpecs,
|
|
1755
|
+
sandboxSession: sandboxSession.restricted(),
|
|
1756
|
+
sessionWorkDir: this.sessionWorkDir,
|
|
1757
|
+
runtimeContext: options.runtimeContext,
|
|
1758
|
+
abortSignal: options.abortSignal,
|
|
1759
|
+
telemetry: options.telemetry,
|
|
1760
|
+
toolApproval: this.toolApproval,
|
|
1761
|
+
pendingToolApprovals: this.getPendingToolApprovals(),
|
|
1762
|
+
toolApprovalContinuations: options.toolApprovalContinuations,
|
|
1763
|
+
onPendingToolApproval: (approval) => {
|
|
1764
|
+
this.pendingToolApprovals.set(approval.approvalId, approval);
|
|
1765
|
+
},
|
|
1766
|
+
onToolApprovalSettled: (approvalId) => {
|
|
1767
|
+
this.pendingToolApprovals.delete(approvalId);
|
|
1768
|
+
}
|
|
1769
|
+
});
|
|
1770
|
+
}
|
|
1771
|
+
/**
|
|
1772
|
+
* Ask the underlying runtime to compact its context. The runtime performs
|
|
1773
|
+
* the compaction itself; when it completes, a `compaction` part appears on
|
|
1774
|
+
* the active (or next) turn's stream. Safe to call between turns for
|
|
1775
|
+
* runtimes whose compaction is session-scoped (e.g. Pi).
|
|
1776
|
+
*
|
|
1777
|
+
* Throws `HarnessCapabilityUnsupportedError` for harnesses that cannot
|
|
1778
|
+
* trigger compaction manually (e.g. Codex, which still auto-compacts under
|
|
1779
|
+
* the hood). Throws if the session has ended.
|
|
1780
|
+
*/
|
|
1781
|
+
async compact(customInstructions) {
|
|
1782
|
+
await this.requireReusableSession().doCompact(customInstructions);
|
|
1783
|
+
}
|
|
1784
|
+
/**
|
|
1785
|
+
* Park the session, returning a payload the caller can persist and later
|
|
1786
|
+
* pass to `agent.createSession({ sessionId, resumeFrom })` to reconnect.
|
|
1787
|
+
* The runtime and sandbox keep running; this local session handle becomes
|
|
1788
|
+
* unusable.
|
|
1789
|
+
*/
|
|
1790
|
+
async detach() {
|
|
1791
|
+
if (this.stopped || this.underlyingSession == null) {
|
|
1792
|
+
throw new Error(
|
|
1793
|
+
`Harness session ${this.sessionId} is not active and cannot be detached.`
|
|
1794
|
+
);
|
|
1795
|
+
}
|
|
1796
|
+
const session = this.underlyingSession;
|
|
1797
|
+
try {
|
|
1798
|
+
const raw = await session.doDetach();
|
|
1799
|
+
const validated = await validateResumeStateData({
|
|
1800
|
+
harness: this.harness,
|
|
1801
|
+
state: raw
|
|
1802
|
+
});
|
|
1803
|
+
return this.withPendingToolApprovals(validated);
|
|
1804
|
+
} finally {
|
|
1805
|
+
this.endLocalHandle({ releasePortLease: false });
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
/**
|
|
1809
|
+
* Persist enough state to resume later, then stop the runtime and sandbox.
|
|
1810
|
+
* Returns the resume state for a future
|
|
1811
|
+
* `agent.createSession({ sessionId, resumeFrom })` call.
|
|
1812
|
+
*/
|
|
1813
|
+
async stop() {
|
|
1814
|
+
if (this.stopped || this.underlyingSession == null) {
|
|
1815
|
+
throw new Error(
|
|
1816
|
+
`Harness session ${this.sessionId} is not active and cannot be stopped.`
|
|
1817
|
+
);
|
|
1818
|
+
}
|
|
1819
|
+
const session = this.underlyingSession;
|
|
1820
|
+
const sandboxSession = this.getSandboxSession();
|
|
1821
|
+
try {
|
|
1822
|
+
const raw = await session.doStop();
|
|
1823
|
+
const validated = await validateResumeStateData({
|
|
1824
|
+
harness: this.harness,
|
|
1825
|
+
state: raw
|
|
1826
|
+
});
|
|
1827
|
+
return this.withPendingToolApprovals(validated);
|
|
1828
|
+
} finally {
|
|
1829
|
+
this.endLocalHandle({ releasePortLease: true });
|
|
1830
|
+
await Promise.resolve(sandboxSession.stop()).catch(() => {
|
|
1831
|
+
});
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
/**
|
|
1835
|
+
* Stop the runtime and discard resumability. The sandbox is destroyed when
|
|
1836
|
+
* the provider supports destruction; otherwise it is stopped.
|
|
1837
|
+
*/
|
|
1838
|
+
async destroy() {
|
|
1839
|
+
var _a3, _b3;
|
|
1840
|
+
if (this.stopped) return;
|
|
1841
|
+
const session = this.underlyingSession;
|
|
1842
|
+
const sandboxSession = this.getSandboxSession();
|
|
1843
|
+
this.endLocalHandle({ releasePortLease: true });
|
|
1844
|
+
if (session != null) {
|
|
1845
|
+
await Promise.resolve(session.doDestroy()).catch(() => {
|
|
1846
|
+
});
|
|
1847
|
+
}
|
|
1848
|
+
await Promise.resolve(
|
|
1849
|
+
(_b3 = (_a3 = sandboxSession.destroy) == null ? void 0 : _a3.call(sandboxSession)) != null ? _b3 : sandboxSession.stop()
|
|
1850
|
+
).catch(() => {
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1853
|
+
/**
|
|
1854
|
+
* Gracefully freeze the active turn at the slice boundary and return the
|
|
1855
|
+
* resume payload, **leaving the sandbox/runtime running** so the next process
|
|
1856
|
+
* can resume. Resolves once the in-flight `stream()`/`continueTurn()`
|
|
1857
|
+
* has cleanly wound down at a precise cursor (see
|
|
1858
|
+
* {@link HarnessV1Session.doSuspendTurn}).
|
|
1859
|
+
*
|
|
1860
|
+
* After this call the session is marked stopped. This in-process handle no
|
|
1861
|
+
* longer drives turns; a future slice creates a fresh session from the
|
|
1862
|
+
* returned state. The sandbox is **not** stopped and no port lease is
|
|
1863
|
+
* released, because bridge-backed adapters may still have a live bridge on
|
|
1864
|
+
* that port.
|
|
1865
|
+
*/
|
|
1866
|
+
async suspendTurn() {
|
|
1867
|
+
if (this.stopped || this.underlyingSession == null) {
|
|
1868
|
+
throw new Error(
|
|
1869
|
+
`Harness session ${this.sessionId} is not active and cannot be suspended.`
|
|
1870
|
+
);
|
|
1871
|
+
}
|
|
1872
|
+
const session = this.underlyingSession;
|
|
1873
|
+
const raw = await session.doSuspendTurn();
|
|
1874
|
+
const validated = await validateResumeStateData({
|
|
1875
|
+
harness: this.harness,
|
|
1876
|
+
state: raw
|
|
1877
|
+
});
|
|
1878
|
+
this.stopped = true;
|
|
1879
|
+
this.underlyingSession = void 0;
|
|
1880
|
+
this.sandboxSession = void 0;
|
|
1881
|
+
return this.withPendingToolApprovals(validated);
|
|
1882
|
+
}
|
|
1883
|
+
getPendingToolApprovals() {
|
|
1884
|
+
return Array.from(this.pendingToolApprovals.values());
|
|
1885
|
+
}
|
|
1886
|
+
withPendingToolApprovals(state) {
|
|
1887
|
+
const pendingToolApprovals = this.getPendingToolApprovals();
|
|
1888
|
+
if (pendingToolApprovals.length === 0) {
|
|
1889
|
+
return {
|
|
1890
|
+
harnessId: state.harnessId,
|
|
1891
|
+
specificationVersion: state.specificationVersion,
|
|
1892
|
+
data: state.data
|
|
1893
|
+
};
|
|
1894
|
+
}
|
|
1895
|
+
return {
|
|
1896
|
+
...state,
|
|
1897
|
+
pendingToolApprovals
|
|
1898
|
+
};
|
|
1899
|
+
}
|
|
1900
|
+
endLocalHandle(options) {
|
|
1901
|
+
this.stopped = true;
|
|
1902
|
+
this.underlyingSession = void 0;
|
|
1903
|
+
this.sandboxSession = void 0;
|
|
1904
|
+
if (options.releasePortLease) {
|
|
1905
|
+
this.releasePortLease();
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
releasePortLease() {
|
|
1909
|
+
if (this.leasedBridgePort == null) return;
|
|
1910
|
+
releaseBridgePort({
|
|
1911
|
+
poolKey: this.sandboxProvider,
|
|
1912
|
+
sessionId: this.sessionId
|
|
1913
|
+
});
|
|
1914
|
+
this.leasedBridgePort = void 0;
|
|
1915
|
+
}
|
|
1916
|
+
requireReusableSession() {
|
|
1917
|
+
if (this.stopped || this.underlyingSession == null) {
|
|
1918
|
+
throw new Error(
|
|
1919
|
+
`Harness session ${this.sessionId} has ended and cannot be reused.`
|
|
1920
|
+
);
|
|
1921
|
+
}
|
|
1922
|
+
return this.underlyingSession;
|
|
1923
|
+
}
|
|
1924
|
+
};
|
|
1925
|
+
|
|
1926
|
+
// src/agent/harness-agent-tool-approval-continuation.ts
|
|
1927
|
+
function collectHarnessAgentToolApprovalContinuations(input) {
|
|
1928
|
+
const lastMessage = input.messages.at(-1);
|
|
1929
|
+
if ((lastMessage == null ? void 0 : lastMessage.role) !== "tool") return [];
|
|
1930
|
+
const toolCallsByToolCallId = /* @__PURE__ */ new Map();
|
|
1931
|
+
const approvalRequestsByApprovalId = /* @__PURE__ */ new Map();
|
|
1932
|
+
for (const message of input.messages) {
|
|
1933
|
+
if (message.role !== "assistant" || typeof message.content === "string") {
|
|
1934
|
+
continue;
|
|
1935
|
+
}
|
|
1936
|
+
for (const part of message.content) {
|
|
1937
|
+
if (part.type === "tool-call") {
|
|
1938
|
+
toolCallsByToolCallId.set(part.toolCallId, {
|
|
1939
|
+
type: "tool-call",
|
|
1940
|
+
toolCallId: part.toolCallId,
|
|
1941
|
+
toolName: part.toolName,
|
|
1942
|
+
input: part.input,
|
|
1943
|
+
...part.providerExecuted !== void 0 ? { providerExecuted: part.providerExecuted } : {}
|
|
1944
|
+
});
|
|
1945
|
+
} else if (part.type === "tool-approval-request") {
|
|
1946
|
+
approvalRequestsByApprovalId.set(part.approvalId, part);
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
const toolResultIds = /* @__PURE__ */ new Set();
|
|
1951
|
+
for (const part of lastMessage.content) {
|
|
1952
|
+
if (part.type === "tool-result") {
|
|
1953
|
+
toolResultIds.add(part.toolCallId);
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
const continuations = [];
|
|
1957
|
+
for (const part of lastMessage.content) {
|
|
1958
|
+
if (part.type !== "tool-approval-response") continue;
|
|
1959
|
+
const approvalRequest = approvalRequestsByApprovalId.get(part.approvalId);
|
|
1960
|
+
if (approvalRequest == null) {
|
|
1961
|
+
throw new HarnessError({
|
|
1962
|
+
message: `Tool approval response '${part.approvalId}' does not match a prior tool approval request.`
|
|
1963
|
+
});
|
|
1964
|
+
}
|
|
1965
|
+
if (toolResultIds.has(approvalRequest.toolCallId)) continue;
|
|
1966
|
+
const toolCall = toolCallsByToolCallId.get(approvalRequest.toolCallId);
|
|
1967
|
+
if (toolCall == null) {
|
|
1968
|
+
throw new HarnessError({
|
|
1969
|
+
message: `Tool approval request '${approvalRequest.approvalId}' references unknown tool call '${approvalRequest.toolCallId}'.`
|
|
1970
|
+
});
|
|
1971
|
+
}
|
|
1972
|
+
continuations.push({
|
|
1973
|
+
approvalResponse: part,
|
|
1974
|
+
toolCall
|
|
1975
|
+
});
|
|
1976
|
+
}
|
|
1977
|
+
return continuations;
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
// src/agent/internal/bootstrap-recipe.ts
|
|
1981
|
+
var BOOTSTRAP_SCHEMA_VERSION = 1;
|
|
1982
|
+
async function hashBootstrap(recipe) {
|
|
1983
|
+
const encoder = new TextEncoder();
|
|
1984
|
+
const chunks = [];
|
|
1985
|
+
const pushString = (value) => {
|
|
1986
|
+
chunks.push(encoder.encode(value));
|
|
1987
|
+
chunks.push(encoder.encode("\0"));
|
|
1988
|
+
};
|
|
1989
|
+
pushString(recipe.harnessId);
|
|
1990
|
+
pushString(recipe.bootstrapDir);
|
|
1991
|
+
const sortedFiles = [...recipe.files].sort(
|
|
1992
|
+
(a, b) => a.path.localeCompare(b.path)
|
|
1993
|
+
);
|
|
1994
|
+
for (const file of sortedFiles) {
|
|
1995
|
+
pushString(file.path);
|
|
1996
|
+
pushString(file.content);
|
|
1997
|
+
}
|
|
1998
|
+
pushString(JSON.stringify(recipe.commands));
|
|
1999
|
+
pushString(String(BOOTSTRAP_SCHEMA_VERSION));
|
|
2000
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
2001
|
+
const buffer = new Uint8Array(totalLength);
|
|
2002
|
+
let offset = 0;
|
|
2003
|
+
for (const chunk of chunks) {
|
|
2004
|
+
buffer.set(chunk, offset);
|
|
2005
|
+
offset += chunk.length;
|
|
2006
|
+
}
|
|
2007
|
+
const digest = await crypto.subtle.digest("SHA-256", buffer);
|
|
2008
|
+
const bytes = new Uint8Array(digest);
|
|
2009
|
+
let hex = "";
|
|
2010
|
+
for (let i = 0; i < 8; i++) {
|
|
2011
|
+
hex += bytes[i].toString(16).padStart(2, "0");
|
|
2012
|
+
}
|
|
2013
|
+
return hex;
|
|
2014
|
+
}
|
|
2015
|
+
function bootstrapMarkerPath(recipe, identity) {
|
|
2016
|
+
return `${recipe.bootstrapDir}/.bootstrap-${identity}.ok`;
|
|
2017
|
+
}
|
|
2018
|
+
async function applyBootstrapRecipe(session, recipe, identity, options) {
|
|
2019
|
+
const markerPath = bootstrapMarkerPath(recipe, identity);
|
|
2020
|
+
const existingMarker = await session.readTextFile({
|
|
2021
|
+
path: markerPath,
|
|
2022
|
+
abortSignal: options == null ? void 0 : options.abortSignal
|
|
2023
|
+
});
|
|
2024
|
+
if (existingMarker !== null) {
|
|
2025
|
+
return;
|
|
2026
|
+
}
|
|
2027
|
+
for (const file of recipe.files) {
|
|
2028
|
+
await session.writeTextFile({
|
|
2029
|
+
path: file.path,
|
|
2030
|
+
content: file.content,
|
|
2031
|
+
abortSignal: options == null ? void 0 : options.abortSignal
|
|
2032
|
+
});
|
|
2033
|
+
}
|
|
2034
|
+
for (const cmd of recipe.commands) {
|
|
2035
|
+
const result = await session.run({
|
|
2036
|
+
command: cmd.command,
|
|
2037
|
+
workingDirectory: cmd.workingDirectory,
|
|
2038
|
+
abortSignal: options == null ? void 0 : options.abortSignal
|
|
2039
|
+
});
|
|
2040
|
+
if (result.exitCode !== 0) {
|
|
2041
|
+
throw new Error(
|
|
2042
|
+
`Bootstrap command failed for harness '${recipe.harnessId}' (exit ${result.exitCode}): ${cmd.command}
|
|
2043
|
+
${result.stderr || result.stdout}`
|
|
2044
|
+
);
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
await session.writeTextFile({
|
|
2048
|
+
path: markerPath,
|
|
2049
|
+
content: "",
|
|
2050
|
+
abortSignal: options == null ? void 0 : options.abortSignal
|
|
2051
|
+
});
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
// src/agent/internal/resolve-observability.ts
|
|
2055
|
+
import { asArray } from "@ai-sdk/provider-utils";
|
|
2056
|
+
var ENV_TRUTHY = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
|
|
2057
|
+
function parseList(value) {
|
|
2058
|
+
if (!value) return void 0;
|
|
2059
|
+
const items = value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
2060
|
+
return items.length > 0 ? items : void 0;
|
|
2061
|
+
}
|
|
2062
|
+
function resolveDebugConfig(debug, env = process.env) {
|
|
2063
|
+
var _a3, _b3, _c, _d;
|
|
2064
|
+
const enabled = (_b3 = debug == null ? void 0 : debug.enabled) != null ? _b3 : ENV_TRUTHY.has(((_a3 = env.HARNESS_DEBUG) != null ? _a3 : "").toLowerCase());
|
|
2065
|
+
const level = (_d = (_c = debug == null ? void 0 : debug.level) != null ? _c : env.HARNESS_DEBUG_LEVEL) != null ? _d : "debug";
|
|
2066
|
+
const subsystems = (debug == null ? void 0 : debug.subsystems) ? [...debug.subsystems] : parseList(env.HARNESS_DEBUG_SUBSYSTEMS);
|
|
2067
|
+
return { enabled, level, subsystems };
|
|
2068
|
+
}
|
|
2069
|
+
function toHarnessDiagnostic(d) {
|
|
2070
|
+
return {
|
|
2071
|
+
level: d.level,
|
|
2072
|
+
message: d.message,
|
|
2073
|
+
subsystem: d.subsystem,
|
|
2074
|
+
kind: d.kind,
|
|
2075
|
+
source: d.source,
|
|
2076
|
+
stream: d.stream,
|
|
2077
|
+
attrs: d.attrs,
|
|
2078
|
+
error: d.error,
|
|
2079
|
+
sessionId: d.sessionId,
|
|
2080
|
+
timestamp: d.timestamp
|
|
2081
|
+
};
|
|
2082
|
+
}
|
|
2083
|
+
function formatForStderr(d) {
|
|
2084
|
+
var _a3;
|
|
2085
|
+
const parts = [`[harness:${d.level}]`, d.subsystem, d.message].filter(
|
|
2086
|
+
Boolean
|
|
2087
|
+
);
|
|
2088
|
+
let line = parts.join(" ");
|
|
2089
|
+
if (d.error) {
|
|
2090
|
+
line += ` (${(_a3 = d.error.name) != null ? _a3 : "Error"}: ${d.error.message})`;
|
|
2091
|
+
}
|
|
2092
|
+
return `${line}
|
|
2093
|
+
`;
|
|
2094
|
+
}
|
|
2095
|
+
function buildObservability(options) {
|
|
2096
|
+
var _a3;
|
|
2097
|
+
const resolved = resolveDebugConfig(options.settings.debug);
|
|
2098
|
+
if (!resolved.enabled) return void 0;
|
|
2099
|
+
const onLog = options.settings.onLog;
|
|
2100
|
+
const integrations = ((_a3 = options.settings.telemetry) == null ? void 0 : _a3.integrations) ? asArray(options.settings.telemetry.integrations) : [];
|
|
2101
|
+
const diagnosticConsumers = integrations.filter(
|
|
2102
|
+
(integration) => typeof integration.ingestDiagnostic === "function"
|
|
2103
|
+
);
|
|
2104
|
+
const report = (emitted) => {
|
|
2105
|
+
var _a4;
|
|
2106
|
+
const diagnostic = toHarnessDiagnostic(emitted);
|
|
2107
|
+
try {
|
|
2108
|
+
process.stderr.write(formatForStderr(diagnostic));
|
|
2109
|
+
} catch (e) {
|
|
2110
|
+
}
|
|
2111
|
+
onLog == null ? void 0 : onLog(diagnostic);
|
|
2112
|
+
for (const consumer of diagnosticConsumers) {
|
|
2113
|
+
(_a4 = consumer.ingestDiagnostic) == null ? void 0 : _a4.call(consumer, diagnostic);
|
|
2114
|
+
}
|
|
2115
|
+
};
|
|
2116
|
+
return {
|
|
2117
|
+
debug: {
|
|
2118
|
+
enabled: true,
|
|
2119
|
+
level: resolved.level,
|
|
2120
|
+
...resolved.subsystems ? { subsystems: resolved.subsystems } : {}
|
|
2121
|
+
},
|
|
2122
|
+
report
|
|
2123
|
+
};
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
// src/agent/harness-agent.ts
|
|
2127
|
+
var HarnessAgent = class {
|
|
2128
|
+
constructor(settings) {
|
|
2129
|
+
this.version = "agent-v1";
|
|
2130
|
+
var _a3;
|
|
2131
|
+
this.settings = settings;
|
|
2132
|
+
this.id = settings.id;
|
|
2133
|
+
this.userTools = (_a3 = settings.tools) != null ? _a3 : {};
|
|
2134
|
+
this.permissionMode = resolvePermissionMode({
|
|
2135
|
+
permissionMode: settings.permissionMode
|
|
2136
|
+
});
|
|
2137
|
+
if (Object.keys(settings.harness.builtinTools).length > 0 && permissionModeNeedsBuiltinSupport({
|
|
2138
|
+
permissionMode: this.permissionMode
|
|
2139
|
+
}) && settings.harness.supportsBuiltinToolApprovals !== true) {
|
|
2140
|
+
throw new HarnessCapabilityUnsupportedError({
|
|
2141
|
+
message: `Harness '${settings.harness.harnessId}' does not support built-in tool approval requests; use permissionMode: 'allow-all'.`,
|
|
2142
|
+
harnessId: settings.harness.harnessId
|
|
2143
|
+
});
|
|
2144
|
+
}
|
|
2145
|
+
this.tools = {
|
|
2146
|
+
...settings.harness.builtinTools,
|
|
2147
|
+
...this.userTools
|
|
2148
|
+
};
|
|
2149
|
+
}
|
|
2150
|
+
/** Identifier of the harness backing this agent. */
|
|
2151
|
+
get harnessId() {
|
|
2152
|
+
return this.settings.harness.harnessId;
|
|
2153
|
+
}
|
|
2154
|
+
/**
|
|
2155
|
+
* Start a fresh session, or resume from state previously returned by
|
|
2156
|
+
* `session.detach()` or `session.stop()`. The returned
|
|
2157
|
+
* `HarnessAgentSession` must be passed to subsequent `generate` / `stream`
|
|
2158
|
+
* calls; end it with `session.detach()`, `session.stop()`, or
|
|
2159
|
+
* `session.destroy()`.
|
|
2160
|
+
*/
|
|
2161
|
+
async createSession(options) {
|
|
2162
|
+
var _a3;
|
|
2163
|
+
const sessionId = (_a3 = options == null ? void 0 : options.sessionId) != null ? _a3 : generateId5();
|
|
2164
|
+
const resumeFrom = options == null ? void 0 : options.resumeFrom;
|
|
2165
|
+
const abortSignal = options == null ? void 0 : options.abortSignal;
|
|
2166
|
+
const harness = this.settings.harness;
|
|
2167
|
+
const sandboxProvider = this.settings.sandbox;
|
|
2168
|
+
let validatedResumeFrom;
|
|
2169
|
+
if (resumeFrom != null) {
|
|
2170
|
+
validatedResumeFrom = await validateResumeStateData({
|
|
2171
|
+
harness,
|
|
2172
|
+
state: resumeFrom
|
|
2173
|
+
});
|
|
2174
|
+
}
|
|
2175
|
+
let recipe;
|
|
2176
|
+
let identity;
|
|
2177
|
+
if (harness.getBootstrap != null) {
|
|
2178
|
+
recipe = await harness.getBootstrap({ abortSignal });
|
|
2179
|
+
identity = await hashBootstrap(recipe);
|
|
2180
|
+
}
|
|
2181
|
+
const acquiredSandboxSession = await this._acquireSandbox({
|
|
2182
|
+
sandboxProvider,
|
|
2183
|
+
sessionId,
|
|
2184
|
+
isResume: validatedResumeFrom != null,
|
|
2185
|
+
recipe,
|
|
2186
|
+
identity,
|
|
2187
|
+
abortSignal
|
|
2188
|
+
});
|
|
2189
|
+
const leased = applyPortLease({
|
|
2190
|
+
provider: sandboxProvider,
|
|
2191
|
+
sandboxSession: acquiredSandboxSession,
|
|
2192
|
+
sessionId
|
|
2193
|
+
});
|
|
2194
|
+
const sandboxSession = leased.sandboxSession;
|
|
2195
|
+
const leasedBridgePort = leased.port;
|
|
2196
|
+
const sessionWorkDir = `${sandboxSession.defaultWorkingDirectory}/${harness.harnessId}-${sessionId}`;
|
|
2197
|
+
try {
|
|
2198
|
+
if (validatedResumeFrom == null && recipe != null && identity != null) {
|
|
2199
|
+
await applyBootstrapRecipe(
|
|
2200
|
+
sandboxSession.restricted(),
|
|
2201
|
+
recipe,
|
|
2202
|
+
identity,
|
|
2203
|
+
{ abortSignal }
|
|
2204
|
+
);
|
|
2205
|
+
}
|
|
2206
|
+
await sandboxSession.run({
|
|
2207
|
+
command: `mkdir -p ${sessionWorkDir}`,
|
|
2208
|
+
abortSignal
|
|
2209
|
+
});
|
|
2210
|
+
if (this.settings.onSandboxSession != null) {
|
|
2211
|
+
await this.settings.onSandboxSession({
|
|
2212
|
+
session: sandboxSession.restricted(),
|
|
2213
|
+
sessionWorkDir,
|
|
2214
|
+
abortSignal
|
|
2215
|
+
});
|
|
2216
|
+
}
|
|
2217
|
+
} catch (err) {
|
|
2218
|
+
await cleanupAfterStartFailure({
|
|
2219
|
+
sandboxProvider,
|
|
2220
|
+
sandboxSession,
|
|
2221
|
+
sessionId,
|
|
2222
|
+
leasedBridgePort
|
|
2223
|
+
});
|
|
2224
|
+
throw err;
|
|
2225
|
+
}
|
|
2226
|
+
try {
|
|
2227
|
+
const baseStartOptions = {
|
|
2228
|
+
sessionId,
|
|
2229
|
+
skills: this.settings.skills,
|
|
2230
|
+
resumeFrom: validatedResumeFrom,
|
|
2231
|
+
permissionMode: this.permissionMode,
|
|
2232
|
+
abortSignal,
|
|
2233
|
+
observability: buildObservability({ settings: this.settings })
|
|
2234
|
+
};
|
|
2235
|
+
const underlyingSession = await harness.doStart({
|
|
2236
|
+
...baseStartOptions,
|
|
2237
|
+
sandboxSession,
|
|
2238
|
+
sessionWorkDir
|
|
2239
|
+
});
|
|
2240
|
+
return new HarnessAgentSession({
|
|
2241
|
+
sessionId,
|
|
2242
|
+
harness,
|
|
2243
|
+
underlyingSession,
|
|
2244
|
+
sandboxSession,
|
|
2245
|
+
sandboxProvider,
|
|
2246
|
+
leasedBridgePort,
|
|
2247
|
+
sessionWorkDir,
|
|
2248
|
+
toolApproval: this.settings.toolApproval,
|
|
2249
|
+
pendingToolApprovals: validatedResumeFrom == null ? void 0 : validatedResumeFrom.pendingToolApprovals
|
|
2250
|
+
});
|
|
2251
|
+
} catch (error) {
|
|
2252
|
+
await cleanupAfterStartFailure({
|
|
2253
|
+
sandboxProvider,
|
|
2254
|
+
sandboxSession,
|
|
2255
|
+
sessionId,
|
|
2256
|
+
leasedBridgePort
|
|
2257
|
+
});
|
|
2258
|
+
throw error;
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
async generate(options) {
|
|
2262
|
+
const turnInput = this._resolveTurnInput(options);
|
|
2263
|
+
const runtimeContext = {};
|
|
2264
|
+
const { result, done } = this._startTurn({
|
|
2265
|
+
session: options.session,
|
|
2266
|
+
turnInput,
|
|
2267
|
+
runtimeContext,
|
|
2268
|
+
abortSignal: options.abortSignal
|
|
2269
|
+
});
|
|
2270
|
+
await done;
|
|
2271
|
+
return this._toGenerateResult(result);
|
|
2272
|
+
}
|
|
2273
|
+
async stream(options) {
|
|
2274
|
+
const turnInput = this._resolveTurnInput(options);
|
|
2275
|
+
const runtimeContext = {};
|
|
2276
|
+
const { result } = this._startTurn({
|
|
2277
|
+
session: options.session,
|
|
2278
|
+
turnInput,
|
|
2279
|
+
runtimeContext,
|
|
2280
|
+
abortSignal: options.abortSignal
|
|
2281
|
+
});
|
|
2282
|
+
return result;
|
|
2283
|
+
}
|
|
2284
|
+
/**
|
|
2285
|
+
* Continue the in-flight turn **without a new prompt**, streaming its events
|
|
2286
|
+
* like {@link stream}. Used to keep consuming a turn that is still running
|
|
2287
|
+
* (or finished) in the runtime after a process boundary — the workflow slice
|
|
2288
|
+
* loop calls this on every slice after the first. Routes through the adapter's
|
|
2289
|
+
* `doContinueTurn`; what it can guarantee (lossless attach vs. lossy rerun)
|
|
2290
|
+
* follows from how the adapter resumed the session.
|
|
2291
|
+
*/
|
|
2292
|
+
async continueTurn(options) {
|
|
2293
|
+
const runtimeContext = {};
|
|
2294
|
+
const { result } = options.session.continueTurn({
|
|
2295
|
+
instructions: this.settings.instructions,
|
|
2296
|
+
tools: this.tools,
|
|
2297
|
+
toolSpecs: this._toToolSpecs(),
|
|
2298
|
+
runtimeContext,
|
|
2299
|
+
abortSignal: options.abortSignal,
|
|
2300
|
+
telemetry: this.settings.telemetry,
|
|
2301
|
+
toolApprovalContinuations: options.toolApprovalContinuations
|
|
2302
|
+
});
|
|
2303
|
+
return result;
|
|
2304
|
+
}
|
|
2305
|
+
// ─── Internals ──────────────────────────────────────────────────────
|
|
2306
|
+
_startTurn(input) {
|
|
2307
|
+
if (input.turnInput.mode === "continue") {
|
|
2308
|
+
return input.session.continueTurn({
|
|
2309
|
+
instructions: this.settings.instructions,
|
|
2310
|
+
tools: this.tools,
|
|
2311
|
+
toolSpecs: this._toToolSpecs(),
|
|
2312
|
+
runtimeContext: input.runtimeContext,
|
|
2313
|
+
abortSignal: input.abortSignal,
|
|
2314
|
+
telemetry: this.settings.telemetry,
|
|
2315
|
+
toolApprovalContinuations: input.turnInput.toolApprovalContinuations
|
|
2316
|
+
});
|
|
2317
|
+
}
|
|
2318
|
+
return input.session.promptTurn({
|
|
2319
|
+
prompt: input.turnInput.prompt,
|
|
2320
|
+
instructions: this.settings.instructions,
|
|
2321
|
+
tools: this.tools,
|
|
2322
|
+
toolSpecs: this._toToolSpecs(),
|
|
2323
|
+
runtimeContext: input.runtimeContext,
|
|
2324
|
+
abortSignal: input.abortSignal,
|
|
2325
|
+
telemetry: this.settings.telemetry
|
|
2326
|
+
});
|
|
2327
|
+
}
|
|
2328
|
+
async _acquireSandbox(input) {
|
|
2329
|
+
const { sandboxProvider } = input;
|
|
2330
|
+
if (input.isResume) {
|
|
2331
|
+
if (sandboxProvider.resumeSession == null) {
|
|
2332
|
+
throw new HarnessCapabilityUnsupportedError({
|
|
2333
|
+
message: `Sandbox provider '${sandboxProvider.providerId}' does not support resume.`,
|
|
2334
|
+
harnessId: this.settings.harness.harnessId
|
|
2335
|
+
});
|
|
2336
|
+
}
|
|
2337
|
+
return sandboxProvider.resumeSession({
|
|
2338
|
+
sessionId: input.sessionId,
|
|
2339
|
+
abortSignal: input.abortSignal
|
|
2340
|
+
});
|
|
2341
|
+
}
|
|
2342
|
+
return sandboxProvider.createSession({
|
|
2343
|
+
sessionId: input.sessionId,
|
|
2344
|
+
abortSignal: input.abortSignal,
|
|
2345
|
+
identity: input.identity,
|
|
2346
|
+
onFirstCreate: input.recipe != null && input.identity != null ? (session, opts) => applyBootstrapRecipe(
|
|
2347
|
+
session,
|
|
2348
|
+
input.recipe,
|
|
2349
|
+
input.identity,
|
|
2350
|
+
opts
|
|
2351
|
+
) : void 0
|
|
2352
|
+
});
|
|
2353
|
+
}
|
|
2354
|
+
/*
|
|
2355
|
+
* Reduce AI SDK input to the single user message the harness should run
|
|
2356
|
+
* for this turn. The harness session owns prior-turn state (system
|
|
2357
|
+
* prompt, assistant turns, tool results) — we never replay it. A bare
|
|
2358
|
+
* string is forwarded as-is; a message array is collapsed to its last
|
|
2359
|
+
* `role: 'user'` entry. Inputs whose only messages are non-user (system,
|
|
2360
|
+
* assistant, tool) have no fresh user input and are rejected.
|
|
2361
|
+
*/
|
|
2362
|
+
_resolveTurnInput(options) {
|
|
2363
|
+
if (typeof options.prompt === "string") {
|
|
2364
|
+
return { mode: "prompt", prompt: options.prompt };
|
|
2365
|
+
}
|
|
2366
|
+
const messages = Array.isArray(options.prompt) ? options.prompt : options.messages;
|
|
2367
|
+
if (Array.isArray(messages)) {
|
|
2368
|
+
const toolApprovalContinuations = collectHarnessAgentToolApprovalContinuations({ messages });
|
|
2369
|
+
if (toolApprovalContinuations.length > 0) {
|
|
2370
|
+
return {
|
|
2371
|
+
mode: "continue",
|
|
2372
|
+
toolApprovalContinuations
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
2376
|
+
const message = messages[i];
|
|
2377
|
+
if ((message == null ? void 0 : message.role) === "user")
|
|
2378
|
+
return { mode: "prompt", prompt: message };
|
|
2379
|
+
}
|
|
2380
|
+
throw new Error(
|
|
2381
|
+
'HarnessAgent: messages must contain at least one `role: "user"` entry.'
|
|
2382
|
+
);
|
|
2383
|
+
}
|
|
2384
|
+
throw new Error("HarnessAgent: either `prompt` or `messages` is required.");
|
|
2385
|
+
}
|
|
2386
|
+
/*
|
|
2387
|
+
* Wire-format projection of user-defined tools only. Harness builtins are
|
|
2388
|
+
* executed by the runtime and the bridge already knows about them — we
|
|
2389
|
+
* never re-declare them over the wire.
|
|
2390
|
+
*/
|
|
2391
|
+
_toToolSpecs() {
|
|
2392
|
+
const specs = [];
|
|
2393
|
+
for (const [name3, tool] of Object.entries(
|
|
2394
|
+
this.userTools
|
|
2395
|
+
)) {
|
|
2396
|
+
const t = tool;
|
|
2397
|
+
let inputSchema;
|
|
2398
|
+
if (t.inputSchema != null) {
|
|
2399
|
+
try {
|
|
2400
|
+
inputSchema = asSchema(
|
|
2401
|
+
t.inputSchema
|
|
2402
|
+
).jsonSchema;
|
|
2403
|
+
} catch (e) {
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
specs.push({ name: name3, description: t.description, inputSchema });
|
|
2407
|
+
}
|
|
2408
|
+
return specs;
|
|
2409
|
+
}
|
|
2410
|
+
async _toGenerateResult(streamResult) {
|
|
2411
|
+
const [steps, usage, responseMessages] = await Promise.all([
|
|
2412
|
+
streamResult.steps,
|
|
2413
|
+
streamResult.usage,
|
|
2414
|
+
streamResult.responseMessages
|
|
2415
|
+
]);
|
|
2416
|
+
return new HarnessGenerateTextResult({ steps, usage, responseMessages });
|
|
2417
|
+
}
|
|
2418
|
+
};
|
|
2419
|
+
var HarnessGenerateTextResult = class {
|
|
2420
|
+
constructor(options) {
|
|
2421
|
+
this.output = void 0;
|
|
2422
|
+
this.steps = options.steps;
|
|
2423
|
+
this.usage = options.usage;
|
|
2424
|
+
this.responseMessages = options.responseMessages;
|
|
2425
|
+
}
|
|
2426
|
+
get finalStep() {
|
|
2427
|
+
return this.steps.at(-1);
|
|
2428
|
+
}
|
|
2429
|
+
get content() {
|
|
2430
|
+
return this.steps.flatMap((step) => step.content);
|
|
2431
|
+
}
|
|
2432
|
+
get text() {
|
|
2433
|
+
return this.finalStep.text;
|
|
2434
|
+
}
|
|
2435
|
+
get files() {
|
|
2436
|
+
return this.steps.flatMap((step) => step.files);
|
|
2437
|
+
}
|
|
2438
|
+
get sources() {
|
|
2439
|
+
return this.steps.flatMap((step) => step.sources);
|
|
2440
|
+
}
|
|
2441
|
+
get toolCalls() {
|
|
2442
|
+
return this.steps.flatMap((step) => step.toolCalls);
|
|
2443
|
+
}
|
|
2444
|
+
get staticToolCalls() {
|
|
2445
|
+
return this.steps.flatMap((step) => step.staticToolCalls);
|
|
2446
|
+
}
|
|
2447
|
+
get dynamicToolCalls() {
|
|
2448
|
+
return this.steps.flatMap((step) => step.dynamicToolCalls);
|
|
2449
|
+
}
|
|
2450
|
+
get toolResults() {
|
|
2451
|
+
return this.steps.flatMap((step) => step.toolResults);
|
|
2452
|
+
}
|
|
2453
|
+
get staticToolResults() {
|
|
2454
|
+
return this.steps.flatMap((step) => step.staticToolResults);
|
|
2455
|
+
}
|
|
2456
|
+
get dynamicToolResults() {
|
|
2457
|
+
return this.steps.flatMap((step) => step.dynamicToolResults);
|
|
2458
|
+
}
|
|
2459
|
+
get finishReason() {
|
|
2460
|
+
return this.finalStep.finishReason;
|
|
2461
|
+
}
|
|
2462
|
+
get rawFinishReason() {
|
|
2463
|
+
return this.finalStep.rawFinishReason;
|
|
2464
|
+
}
|
|
2465
|
+
get warnings() {
|
|
2466
|
+
return this.steps.flatMap((step) => {
|
|
2467
|
+
var _a3;
|
|
2468
|
+
return (_a3 = step.warnings) != null ? _a3 : [];
|
|
2469
|
+
});
|
|
2470
|
+
}
|
|
2471
|
+
get reasoning() {
|
|
2472
|
+
return this.finalStep.content.filter(
|
|
2473
|
+
(part) => part.type === "reasoning" || part.type === "reasoning-file"
|
|
2474
|
+
);
|
|
2475
|
+
}
|
|
2476
|
+
get reasoningText() {
|
|
2477
|
+
return this.finalStep.reasoningText;
|
|
2478
|
+
}
|
|
2479
|
+
get totalUsage() {
|
|
2480
|
+
return this.usage;
|
|
2481
|
+
}
|
|
2482
|
+
get request() {
|
|
2483
|
+
return this.finalStep.request;
|
|
2484
|
+
}
|
|
2485
|
+
get response() {
|
|
2486
|
+
return this.finalStep.response;
|
|
2487
|
+
}
|
|
2488
|
+
get providerMetadata() {
|
|
2489
|
+
return this.finalStep.providerMetadata;
|
|
2490
|
+
}
|
|
2491
|
+
};
|
|
2492
|
+
function applyPortLease(input) {
|
|
2493
|
+
const pool = input.provider.bridgePorts;
|
|
2494
|
+
if (pool == null || pool.length === 0) {
|
|
2495
|
+
return { sandboxSession: input.sandboxSession, port: void 0 };
|
|
2496
|
+
}
|
|
2497
|
+
const port = acquireBridgePort({
|
|
2498
|
+
poolKey: input.provider,
|
|
2499
|
+
pool,
|
|
2500
|
+
sessionId: input.sessionId
|
|
2501
|
+
});
|
|
2502
|
+
return {
|
|
2503
|
+
sandboxSession: narrowNetworkSessionPorts(input.sandboxSession, port),
|
|
2504
|
+
port
|
|
2505
|
+
};
|
|
2506
|
+
}
|
|
2507
|
+
function narrowNetworkSessionPorts(sandboxSession, leasedPort) {
|
|
2508
|
+
return Object.create(sandboxSession, {
|
|
2509
|
+
ports: {
|
|
2510
|
+
value: [leasedPort],
|
|
2511
|
+
enumerable: true
|
|
2512
|
+
}
|
|
2513
|
+
});
|
|
2514
|
+
}
|
|
2515
|
+
async function cleanupAfterStartFailure(input) {
|
|
2516
|
+
await Promise.resolve(input.sandboxSession.stop()).catch(() => {
|
|
2517
|
+
});
|
|
2518
|
+
if (input.leasedBridgePort != null) {
|
|
2519
|
+
releaseBridgePort({
|
|
2520
|
+
poolKey: input.sandboxProvider,
|
|
2521
|
+
sessionId: input.sessionId
|
|
2522
|
+
});
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
|
|
2526
|
+
// src/agent/prewarm.ts
|
|
2527
|
+
async function prewarmHarness(options) {
|
|
2528
|
+
var _a3, _b3;
|
|
2529
|
+
const recipe = await ((_b3 = (_a3 = options.harness).getBootstrap) == null ? void 0 : _b3.call(_a3, {
|
|
2530
|
+
abortSignal: options.abortSignal
|
|
2531
|
+
}));
|
|
2532
|
+
if (recipe == null) return;
|
|
2533
|
+
const identity = await hashBootstrap(recipe);
|
|
2534
|
+
const sandboxSession = await options.sandboxProvider.createSession({
|
|
2535
|
+
abortSignal: options.abortSignal,
|
|
2536
|
+
identity,
|
|
2537
|
+
onFirstCreate: (session, opts) => applyBootstrapRecipe(session, recipe, identity, opts)
|
|
2538
|
+
});
|
|
2539
|
+
try {
|
|
2540
|
+
await applyBootstrapRecipe(sandboxSession.restricted(), recipe, identity, {
|
|
2541
|
+
abortSignal: options.abortSignal
|
|
2542
|
+
});
|
|
2543
|
+
} finally {
|
|
2544
|
+
await Promise.resolve(sandboxSession.stop()).catch(() => {
|
|
2545
|
+
});
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
export {
|
|
2549
|
+
HarnessAgent,
|
|
2550
|
+
HarnessAgentSession,
|
|
2551
|
+
collectHarnessAgentToolApprovalContinuations,
|
|
2552
|
+
prewarmHarness
|
|
2553
|
+
};
|
|
2554
|
+
//# sourceMappingURL=index.js.map
|