@agentloop/core 0.1.0
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/README.md +287 -0
- package/dist/index.cjs +1168 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +825 -0
- package/dist/index.d.mts +825 -0
- package/dist/index.mjs +1152 -0
- package/dist/index.mjs.map +1 -0
- package/dist/test.cjs +7 -0
- package/dist/test.cjs.map +1 -0
- package/dist/test.d.cts +5 -0
- package/dist/test.d.mts +5 -0
- package/dist/test.mjs +6 -0
- package/dist/test.mjs.map +1 -0
- package/package.json +58 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1152 @@
|
|
|
1
|
+
//#region src/content.ts
|
|
2
|
+
/** Create a {@link TextPart}. */
|
|
3
|
+
function text(value) {
|
|
4
|
+
return {
|
|
5
|
+
type: "text",
|
|
6
|
+
text: value
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
/** Create a {@link JsonPart}. */
|
|
10
|
+
function json(value) {
|
|
11
|
+
return {
|
|
12
|
+
type: "json",
|
|
13
|
+
json: value
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/** Create a {@link BlobPart}. */
|
|
17
|
+
function blob(data, mediaType) {
|
|
18
|
+
return {
|
|
19
|
+
type: "blob",
|
|
20
|
+
data,
|
|
21
|
+
mediaType
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** Create a {@link URLPart}. */
|
|
25
|
+
function url(value, mediaType) {
|
|
26
|
+
return {
|
|
27
|
+
type: "url",
|
|
28
|
+
url: value,
|
|
29
|
+
...mediaType != null && { mediaType }
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/message.ts
|
|
34
|
+
/** Create a {@link SystemMessage}. Accepts a string or content array. */
|
|
35
|
+
function system(content) {
|
|
36
|
+
return {
|
|
37
|
+
role: "system",
|
|
38
|
+
content: typeof content === "string" ? [text(content)] : content
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** Create a {@link UserMessage}. Accepts a string or content array. */
|
|
42
|
+
function user(content) {
|
|
43
|
+
return {
|
|
44
|
+
role: "user",
|
|
45
|
+
content: typeof content === "string" ? [text(content)] : content
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** Create an {@link AssistantMessage}. Accepts a string or content array. */
|
|
49
|
+
function assistant(content) {
|
|
50
|
+
return {
|
|
51
|
+
role: "assistant",
|
|
52
|
+
content: typeof content === "string" ? [text(content)] : content
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** Normalize a {@link Prompt} into a {@link Message} array. */
|
|
56
|
+
function normalizePrompt(prompt) {
|
|
57
|
+
if (typeof prompt === "string") return [user(prompt)];
|
|
58
|
+
if (Array.isArray(prompt)) return prompt;
|
|
59
|
+
return [prompt];
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/schema.ts
|
|
63
|
+
/**
|
|
64
|
+
* Extract a JSON Schema object from a
|
|
65
|
+
* [Standard Schema v1](https://github.com/standard-schema/standard-schema)
|
|
66
|
+
* compatible validator.
|
|
67
|
+
*
|
|
68
|
+
* Throws if the schema does not expose `~standard.jsonSchema.input()`.
|
|
69
|
+
*/
|
|
70
|
+
function schemaToJsonSchema(schema) {
|
|
71
|
+
const ss = schema?.["~standard"];
|
|
72
|
+
if (ss?.jsonSchema?.input) return ss.jsonSchema.input();
|
|
73
|
+
throw new Error("Cannot convert schema to JSON Schema. Provide a Standard Schema v1-compatible validator.");
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region src/model.ts
|
|
77
|
+
/** Create an empty {@link Usage}. */
|
|
78
|
+
function emptyUsage() {
|
|
79
|
+
return {
|
|
80
|
+
inputTokens: 0,
|
|
81
|
+
outputTokens: 0,
|
|
82
|
+
totalTokens: 0
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/** Add two {@link Usage} objects together. */
|
|
86
|
+
function addUsage(a, b) {
|
|
87
|
+
return {
|
|
88
|
+
inputTokens: a.inputTokens + b.inputTokens,
|
|
89
|
+
outputTokens: a.outputTokens + b.outputTokens,
|
|
90
|
+
totalTokens: a.totalTokens + b.totalTokens,
|
|
91
|
+
cacheReadTokens: (a.cacheReadTokens ?? 0) + (b.cacheReadTokens ?? 0) || void 0,
|
|
92
|
+
cacheWriteTokens: (a.cacheWriteTokens ?? 0) + (b.cacheWriteTokens ?? 0) || void 0
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/tool.ts
|
|
97
|
+
/** Create a type-safe {@link Tool}. Schema infers the argument type. */
|
|
98
|
+
function defineTool(config) {
|
|
99
|
+
return config;
|
|
100
|
+
}
|
|
101
|
+
/** Normalize a {@link ToolReturn} into a {@link Content} array. */
|
|
102
|
+
function normalizeToolReturn(value) {
|
|
103
|
+
if (typeof value === "string") return [text(value)];
|
|
104
|
+
if (Array.isArray(value)) return value;
|
|
105
|
+
return [json(value)];
|
|
106
|
+
}
|
|
107
|
+
//#endregion
|
|
108
|
+
//#region src/policy.ts
|
|
109
|
+
/** Create a {@link Policy}. */
|
|
110
|
+
function definePolicy(config) {
|
|
111
|
+
return config;
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region src/observer.ts
|
|
115
|
+
/** Create an {@link Observer}. */
|
|
116
|
+
function defineObserver(config) {
|
|
117
|
+
return config;
|
|
118
|
+
}
|
|
119
|
+
//#endregion
|
|
120
|
+
//#region src/channel.ts
|
|
121
|
+
/** Create an {@link EventChannel}. */
|
|
122
|
+
function createEventChannel() {
|
|
123
|
+
const buffer = [];
|
|
124
|
+
let closed = false;
|
|
125
|
+
let waiter = null;
|
|
126
|
+
let resolveResult;
|
|
127
|
+
let rejectResult;
|
|
128
|
+
const result = new Promise((resolve, reject) => {
|
|
129
|
+
resolveResult = resolve;
|
|
130
|
+
rejectResult = reject;
|
|
131
|
+
});
|
|
132
|
+
function releaseWaiter() {
|
|
133
|
+
if (waiter !== null) {
|
|
134
|
+
const resolve = waiter;
|
|
135
|
+
waiter = null;
|
|
136
|
+
resolve({
|
|
137
|
+
value: void 0,
|
|
138
|
+
done: true
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function push(value) {
|
|
143
|
+
if (closed) return;
|
|
144
|
+
if (waiter !== null) {
|
|
145
|
+
const resolve = waiter;
|
|
146
|
+
waiter = null;
|
|
147
|
+
resolve({
|
|
148
|
+
value,
|
|
149
|
+
done: false
|
|
150
|
+
});
|
|
151
|
+
} else buffer.push(value);
|
|
152
|
+
}
|
|
153
|
+
function close(value) {
|
|
154
|
+
if (closed) return;
|
|
155
|
+
closed = true;
|
|
156
|
+
resolveResult(value);
|
|
157
|
+
releaseWaiter();
|
|
158
|
+
}
|
|
159
|
+
function fail(error) {
|
|
160
|
+
if (closed) return;
|
|
161
|
+
closed = true;
|
|
162
|
+
rejectResult(error);
|
|
163
|
+
releaseWaiter();
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
push,
|
|
167
|
+
close,
|
|
168
|
+
fail,
|
|
169
|
+
stream: { [Symbol.asyncIterator]() {
|
|
170
|
+
return {
|
|
171
|
+
next() {
|
|
172
|
+
if (buffer.length > 0) return Promise.resolve({
|
|
173
|
+
value: buffer.shift(),
|
|
174
|
+
done: false
|
|
175
|
+
});
|
|
176
|
+
if (closed) return Promise.resolve({
|
|
177
|
+
value: void 0,
|
|
178
|
+
done: true
|
|
179
|
+
});
|
|
180
|
+
return new Promise((resolve) => {
|
|
181
|
+
waiter = resolve;
|
|
182
|
+
});
|
|
183
|
+
},
|
|
184
|
+
return() {
|
|
185
|
+
releaseWaiter();
|
|
186
|
+
return Promise.resolve({
|
|
187
|
+
value: void 0,
|
|
188
|
+
done: true
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
} },
|
|
193
|
+
result
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
//#endregion
|
|
197
|
+
//#region src/emit.ts
|
|
198
|
+
/**
|
|
199
|
+
* Create an {@link EmitFn} that delivers events to observers and optionally
|
|
200
|
+
* pushes them into an external consumer (e.g. an {@link EventChannel}).
|
|
201
|
+
*
|
|
202
|
+
* Observer handlers are invoked synchronously; async rejections are caught.
|
|
203
|
+
* No observer can block or crash the agent loop.
|
|
204
|
+
*/
|
|
205
|
+
function createEmitFn(observers, push) {
|
|
206
|
+
return (event) => {
|
|
207
|
+
if (push !== void 0) push(event);
|
|
208
|
+
notifyObservers(observers, event, push);
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
/** Dispatch an event to all observers, reporting observer failures to the others. */
|
|
212
|
+
function notifyObservers(observers, event, push) {
|
|
213
|
+
const isObserverError = event.type === "error" && event.source === "observer";
|
|
214
|
+
for (const observer of observers) invokeObserver(observer, event, (err) => {
|
|
215
|
+
if (isObserverError) return;
|
|
216
|
+
const errorEvent = {
|
|
217
|
+
type: "error",
|
|
218
|
+
runId: event.runId,
|
|
219
|
+
step: event.step,
|
|
220
|
+
agent: event.agent,
|
|
221
|
+
timestamp: Date.now(),
|
|
222
|
+
error: err instanceof Error ? err : new Error(String(err)),
|
|
223
|
+
source: "observer",
|
|
224
|
+
observer: observer.name
|
|
225
|
+
};
|
|
226
|
+
if (push !== void 0) push(errorEvent);
|
|
227
|
+
for (const other of observers) if (other !== observer) invokeObserver(other, errorEvent, () => {});
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
/** Call an observer's typed handler and catch-all handler, independently. */
|
|
231
|
+
function invokeObserver(observer, event, onError) {
|
|
232
|
+
const typed = observer[`on${event.type.charAt(0).toUpperCase()}${event.type.slice(1)}`];
|
|
233
|
+
if (typeof typed === "function") try {
|
|
234
|
+
const result = typed.call(observer, event);
|
|
235
|
+
if (result != null) Promise.resolve(result).catch(onError);
|
|
236
|
+
} catch (err) {
|
|
237
|
+
onError(err);
|
|
238
|
+
}
|
|
239
|
+
if (observer.handler !== void 0) try {
|
|
240
|
+
const result = observer.handler(event);
|
|
241
|
+
if (result != null) Promise.resolve(result).catch(onError);
|
|
242
|
+
} catch (err) {
|
|
243
|
+
onError(err);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
//#endregion
|
|
247
|
+
//#region src/pipeline.ts
|
|
248
|
+
/**
|
|
249
|
+
* Evaluate policies in pipeline order.
|
|
250
|
+
*
|
|
251
|
+
* The first policy to return a non-void action wins and short-circuits the
|
|
252
|
+
* remaining policies. If a policy throws, the error is caught and treated as
|
|
253
|
+
* a `{ action: "stop" }` directive — the error is preserved on the result
|
|
254
|
+
* so the caller can emit an appropriate {@link ErrorEvent}.
|
|
255
|
+
*/
|
|
256
|
+
async function runPolicies(policies, invoke) {
|
|
257
|
+
for (const policy of policies) try {
|
|
258
|
+
const result = await invoke(policy);
|
|
259
|
+
if (result != null) return {
|
|
260
|
+
action: result,
|
|
261
|
+
policy: policy.name
|
|
262
|
+
};
|
|
263
|
+
} catch (err) {
|
|
264
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
265
|
+
return {
|
|
266
|
+
action: {
|
|
267
|
+
action: "stop",
|
|
268
|
+
reason: error.message
|
|
269
|
+
},
|
|
270
|
+
policy: policy.name,
|
|
271
|
+
error
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
//#endregion
|
|
276
|
+
//#region src/execute.ts
|
|
277
|
+
/**
|
|
278
|
+
* Execute all tool calls from a model response in parallel.
|
|
279
|
+
*
|
|
280
|
+
* Returns assembled {@link ToolResultPart} entries (one per call, preserving
|
|
281
|
+
* call order) and an optional `stopRun` policy name if any tool or policy
|
|
282
|
+
* signaled a stop.
|
|
283
|
+
*/
|
|
284
|
+
async function executeToolCalls(calls, toolMap, policies, ctx, emit) {
|
|
285
|
+
const promises = calls.map((call) => {
|
|
286
|
+
const tool = toolMap.get(call.name);
|
|
287
|
+
if (tool === void 0) {
|
|
288
|
+
emit({
|
|
289
|
+
type: "error",
|
|
290
|
+
...base(ctx),
|
|
291
|
+
error: /* @__PURE__ */ new Error(`Tool "${call.name}" is not available`),
|
|
292
|
+
source: "tool",
|
|
293
|
+
toolCall: call
|
|
294
|
+
});
|
|
295
|
+
return Promise.resolve({
|
|
296
|
+
output: [text(`Tool "${call.name}" is not available. Available tools: ${[...toolMap.keys()].join(", ")}`)],
|
|
297
|
+
isError: true
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
return executeTool(call, tool, policies, ctx, emit);
|
|
301
|
+
});
|
|
302
|
+
const settled = await Promise.all(promises);
|
|
303
|
+
let stopRun;
|
|
304
|
+
const results = [];
|
|
305
|
+
for (let i = 0; i < calls.length; i++) {
|
|
306
|
+
const call = calls[i];
|
|
307
|
+
const exec = settled[i];
|
|
308
|
+
results.push({
|
|
309
|
+
type: "tool_result",
|
|
310
|
+
id: call.id,
|
|
311
|
+
name: call.name,
|
|
312
|
+
output: exec.output,
|
|
313
|
+
isError: exec.isError ? true : void 0
|
|
314
|
+
});
|
|
315
|
+
if (exec.stopRun !== void 0 && stopRun === void 0) stopRun = exec.stopRun;
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
results,
|
|
319
|
+
stopRun
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
async function executeTool(call, tool, policies, ctx, emit) {
|
|
323
|
+
const callInfo = {
|
|
324
|
+
id: call.id,
|
|
325
|
+
name: call.name,
|
|
326
|
+
arguments: { ...call.arguments }
|
|
327
|
+
};
|
|
328
|
+
const beforePol = await runPolicies(policies, (p) => p.beforeToolRun?.(ctx, callInfo));
|
|
329
|
+
if (beforePol != null) {
|
|
330
|
+
if (beforePol.error) emitError(emit, ctx, beforePol.error, "policy", call, beforePol.policy);
|
|
331
|
+
emitSkip(emit, ctx, call, beforePol.action.action, "policy", beforePol.action.reason);
|
|
332
|
+
return {
|
|
333
|
+
output: beforePol.action.output ?? [],
|
|
334
|
+
isError: false,
|
|
335
|
+
stopRun: beforePol.action.action === "stop" ? beforePol.policy : void 0
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
const parsed = tool.schema.safeParse(callInfo.arguments);
|
|
339
|
+
if (!parsed.success) {
|
|
340
|
+
const msg = `Invalid arguments for tool "${tool.name}": ${String(parsed.error)}`;
|
|
341
|
+
emitError(emit, ctx, new Error(msg), "validation", call);
|
|
342
|
+
return {
|
|
343
|
+
output: [text(msg)],
|
|
344
|
+
isError: true
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
try {
|
|
348
|
+
const beforeAction = await tool.before?.(parsed.data, ctx);
|
|
349
|
+
if (beforeAction != null) {
|
|
350
|
+
emitSkip(emit, ctx, call, beforeAction.action, "tool", beforeAction.reason);
|
|
351
|
+
return {
|
|
352
|
+
output: beforeAction.output ?? [],
|
|
353
|
+
isError: false,
|
|
354
|
+
stopRun: beforeAction.action === "stop" ? tool.name : void 0
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
} catch (err) {
|
|
358
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
359
|
+
emitError(emit, ctx, error, "tool", call);
|
|
360
|
+
return {
|
|
361
|
+
output: [text(`Error in "${tool.name}" before hook: ${error.message}`)],
|
|
362
|
+
isError: true
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
const execStart = Date.now();
|
|
366
|
+
emit({
|
|
367
|
+
type: "toolRunStart",
|
|
368
|
+
...base(ctx),
|
|
369
|
+
id: call.id,
|
|
370
|
+
name: call.name,
|
|
371
|
+
arguments: callInfo.arguments
|
|
372
|
+
});
|
|
373
|
+
let output = [];
|
|
374
|
+
let error;
|
|
375
|
+
let stopRun;
|
|
376
|
+
let currentArgs = parsed.data;
|
|
377
|
+
for (;;) {
|
|
378
|
+
const toolSignal = tool.timeout !== void 0 ? AbortSignal.any([ctx.signal, AbortSignal.timeout(tool.timeout)]) : ctx.signal;
|
|
379
|
+
const toolCtx = {
|
|
380
|
+
...ctx,
|
|
381
|
+
signal: toolSignal,
|
|
382
|
+
id: call.id,
|
|
383
|
+
update(data) {
|
|
384
|
+
emit({
|
|
385
|
+
type: "toolRunUpdate",
|
|
386
|
+
...base(ctx),
|
|
387
|
+
id: call.id,
|
|
388
|
+
name: call.name,
|
|
389
|
+
data
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
try {
|
|
394
|
+
output = normalizeToolReturn(await raceAbort((async () => tool.execute(currentArgs, toolCtx))(), toolSignal));
|
|
395
|
+
error = void 0;
|
|
396
|
+
} catch (err) {
|
|
397
|
+
error = err instanceof Error ? err : new Error(String(err));
|
|
398
|
+
output = [text(`Error in tool "${tool.name}": ${error.message}`)];
|
|
399
|
+
emitError(emit, ctx, error, "tool", call);
|
|
400
|
+
}
|
|
401
|
+
let shouldRetry = false;
|
|
402
|
+
if (tool.after !== void 0) try {
|
|
403
|
+
const action = await tool.after(currentArgs, output, ctx);
|
|
404
|
+
if (action != null) {
|
|
405
|
+
const r = applyAfterAction(action, tool, currentArgs);
|
|
406
|
+
if (r.output !== void 0) {
|
|
407
|
+
output = r.output;
|
|
408
|
+
error = void 0;
|
|
409
|
+
}
|
|
410
|
+
if (r.retry) {
|
|
411
|
+
currentArgs = r.nextArgs;
|
|
412
|
+
stopRun = void 0;
|
|
413
|
+
shouldRetry = true;
|
|
414
|
+
}
|
|
415
|
+
if (r.stop) stopRun = tool.name;
|
|
416
|
+
if (r.validationError) {
|
|
417
|
+
error = r.validationError;
|
|
418
|
+
output = [text(error.message)];
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
} catch (err) {
|
|
423
|
+
emitError(emit, ctx, err instanceof Error ? err : new Error(String(err)), "tool", call);
|
|
424
|
+
}
|
|
425
|
+
if (shouldRetry) continue;
|
|
426
|
+
const resultInfo = {
|
|
427
|
+
id: call.id,
|
|
428
|
+
name: call.name,
|
|
429
|
+
arguments: callInfo.arguments,
|
|
430
|
+
output,
|
|
431
|
+
isError: error !== void 0
|
|
432
|
+
};
|
|
433
|
+
const afterPol = await runPolicies(policies, (p) => p.afterToolRun?.(ctx, resultInfo));
|
|
434
|
+
if (afterPol != null) {
|
|
435
|
+
if (afterPol.error) emitError(emit, ctx, afterPol.error, "policy", call, afterPol.policy);
|
|
436
|
+
const r = applyAfterAction(afterPol.action, tool, currentArgs);
|
|
437
|
+
if (r.output !== void 0) {
|
|
438
|
+
output = r.output;
|
|
439
|
+
error = void 0;
|
|
440
|
+
}
|
|
441
|
+
if (r.retry) {
|
|
442
|
+
currentArgs = r.nextArgs;
|
|
443
|
+
stopRun = void 0;
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
if (r.stop) stopRun = afterPol.policy;
|
|
447
|
+
if (r.validationError) {
|
|
448
|
+
error = r.validationError;
|
|
449
|
+
output = [text(error.message)];
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
break;
|
|
454
|
+
}
|
|
455
|
+
emit({
|
|
456
|
+
type: "toolRunEnd",
|
|
457
|
+
...base(ctx),
|
|
458
|
+
id: call.id,
|
|
459
|
+
name: call.name,
|
|
460
|
+
output,
|
|
461
|
+
error,
|
|
462
|
+
duration: Date.now() - execStart
|
|
463
|
+
});
|
|
464
|
+
return {
|
|
465
|
+
output,
|
|
466
|
+
isError: error !== void 0,
|
|
467
|
+
stopRun
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Interpret an {@link AfterToolRunAction} from either `tool.after` or
|
|
472
|
+
* `policy.afterToolRun`. Handles rewrite, retry (with optional re-validation),
|
|
473
|
+
* and stop uniformly so the two call sites stay DRY.
|
|
474
|
+
*/
|
|
475
|
+
function applyAfterAction(action, tool, currentArgs) {
|
|
476
|
+
switch (action.action) {
|
|
477
|
+
case "rewrite": return {
|
|
478
|
+
output: action.output,
|
|
479
|
+
retry: false,
|
|
480
|
+
nextArgs: currentArgs,
|
|
481
|
+
stop: false
|
|
482
|
+
};
|
|
483
|
+
case "retry":
|
|
484
|
+
if (action.arguments !== void 0) {
|
|
485
|
+
const reparsed = tool.schema.safeParse(action.arguments);
|
|
486
|
+
if (!reparsed.success) return {
|
|
487
|
+
retry: false,
|
|
488
|
+
nextArgs: currentArgs,
|
|
489
|
+
stop: false,
|
|
490
|
+
validationError: /* @__PURE__ */ new Error(`Invalid retry arguments for "${tool.name}": ${String(reparsed.error)}`)
|
|
491
|
+
};
|
|
492
|
+
return {
|
|
493
|
+
retry: true,
|
|
494
|
+
nextArgs: reparsed.data,
|
|
495
|
+
stop: false
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
return {
|
|
499
|
+
retry: true,
|
|
500
|
+
nextArgs: currentArgs,
|
|
501
|
+
stop: false
|
|
502
|
+
};
|
|
503
|
+
case "stop": return {
|
|
504
|
+
retry: false,
|
|
505
|
+
nextArgs: currentArgs,
|
|
506
|
+
stop: true
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
/** Create the base fields shared by all observer events. */
|
|
511
|
+
function base(ctx) {
|
|
512
|
+
return {
|
|
513
|
+
runId: ctx.runId,
|
|
514
|
+
step: ctx.step,
|
|
515
|
+
agent: ctx.agent,
|
|
516
|
+
timestamp: Date.now()
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
/** Emit an {@link ErrorEvent}. */
|
|
520
|
+
function emitError(emit, ctx, error, source, toolCall, policy) {
|
|
521
|
+
emit({
|
|
522
|
+
type: "error",
|
|
523
|
+
...base(ctx),
|
|
524
|
+
error,
|
|
525
|
+
source,
|
|
526
|
+
toolCall,
|
|
527
|
+
policy
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
/** Emit a {@link ToolSkipEvent}. */
|
|
531
|
+
function emitSkip(emit, ctx, call, action, source, reason) {
|
|
532
|
+
emit({
|
|
533
|
+
type: "toolSkip",
|
|
534
|
+
...base(ctx),
|
|
535
|
+
id: call.id,
|
|
536
|
+
name: call.name,
|
|
537
|
+
arguments: call.arguments,
|
|
538
|
+
action,
|
|
539
|
+
source,
|
|
540
|
+
reason
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Race a promise against an {@link AbortSignal}, with proper listener cleanup.
|
|
545
|
+
*
|
|
546
|
+
* If the signal is already aborted, rejects immediately. Otherwise, whichever
|
|
547
|
+
* settles first wins, and the abort listener is removed to prevent leaks.
|
|
548
|
+
*/
|
|
549
|
+
function raceAbort(promise, signal) {
|
|
550
|
+
if (signal.aborted) return Promise.reject(signal.reason ?? /* @__PURE__ */ new Error("Aborted"));
|
|
551
|
+
return new Promise((resolve, reject) => {
|
|
552
|
+
const onAbort = () => reject(signal.reason ?? /* @__PURE__ */ new Error("Aborted"));
|
|
553
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
554
|
+
promise.then((v) => {
|
|
555
|
+
signal.removeEventListener("abort", onAbort);
|
|
556
|
+
resolve(v);
|
|
557
|
+
}, (e) => {
|
|
558
|
+
signal.removeEventListener("abort", onAbort);
|
|
559
|
+
reject(e);
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
//#endregion
|
|
564
|
+
//#region src/loop.ts
|
|
565
|
+
/** Create base observer event fields from current run state. */
|
|
566
|
+
function baseFields(runId, step, agent) {
|
|
567
|
+
return {
|
|
568
|
+
runId,
|
|
569
|
+
step,
|
|
570
|
+
agent,
|
|
571
|
+
timestamp: Date.now()
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
/** Ensure system instructions appear as the first message in a plan's message list. */
|
|
575
|
+
function prepareMessages(plan) {
|
|
576
|
+
const messages = plan.messages;
|
|
577
|
+
if (plan.instructions !== void 0) if (messages.length > 0 && messages[0].role === "system") messages[0] = system(plan.instructions);
|
|
578
|
+
else messages.unshift(system(plan.instructions));
|
|
579
|
+
return messages;
|
|
580
|
+
}
|
|
581
|
+
/** Extract {@link ToolDefinition} metadata from tools (strips execute/hooks). */
|
|
582
|
+
function deriveToolDefs(tools) {
|
|
583
|
+
return tools.map((t) => ({
|
|
584
|
+
name: t.name,
|
|
585
|
+
description: t.description,
|
|
586
|
+
schema: t.schema,
|
|
587
|
+
...t.timeout !== void 0 && { timeout: t.timeout }
|
|
588
|
+
}));
|
|
589
|
+
}
|
|
590
|
+
/** Extract the {@link ModelConfig} subset from a {@link StepPlan}. */
|
|
591
|
+
function extractModelConfig(plan) {
|
|
592
|
+
const config = {};
|
|
593
|
+
if (plan.maxTokens !== void 0) config.maxTokens = plan.maxTokens;
|
|
594
|
+
if (plan.temperature !== void 0) config.temperature = plan.temperature;
|
|
595
|
+
if (plan.topP !== void 0) config.topP = plan.topP;
|
|
596
|
+
if (plan.topK !== void 0) config.topK = plan.topK;
|
|
597
|
+
if (plan.stopSequences !== void 0) config.stopSequences = plan.stopSequences;
|
|
598
|
+
return config;
|
|
599
|
+
}
|
|
600
|
+
/** Extract all {@link ToolCallPart} entries from an assistant message. */
|
|
601
|
+
function extractToolCalls(message) {
|
|
602
|
+
const calls = [];
|
|
603
|
+
for (const part of message.content) if (part.type === "tool_call") calls.push(part);
|
|
604
|
+
return calls;
|
|
605
|
+
}
|
|
606
|
+
/** Join all text content parts from an assistant message. */
|
|
607
|
+
function extractText(message) {
|
|
608
|
+
let result = "";
|
|
609
|
+
for (const part of message.content) if (part.type === "text") {
|
|
610
|
+
if (result) result += "\n";
|
|
611
|
+
result += part.text;
|
|
612
|
+
}
|
|
613
|
+
return result;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Parse structured output from the last text part of a response.
|
|
617
|
+
*
|
|
618
|
+
* Returns the validated object on success, `null` on failure. Emits an
|
|
619
|
+
* {@link ErrorEvent} with `source: "validation"` on parse or validation failure.
|
|
620
|
+
*/
|
|
621
|
+
function parseStructuredOutput(message, schema, emit, base) {
|
|
622
|
+
let lastText;
|
|
623
|
+
for (const part of message.content) if (part.type === "text") lastText = part.text;
|
|
624
|
+
if (lastText === void 0) return null;
|
|
625
|
+
let parsed;
|
|
626
|
+
try {
|
|
627
|
+
parsed = JSON.parse(lastText);
|
|
628
|
+
} catch {
|
|
629
|
+
emit({
|
|
630
|
+
type: "error",
|
|
631
|
+
...base,
|
|
632
|
+
error: /* @__PURE__ */ new Error("Structured output is not valid JSON"),
|
|
633
|
+
source: "validation"
|
|
634
|
+
});
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
const result = schema.safeParse(parsed);
|
|
638
|
+
if (result.success) return result.data;
|
|
639
|
+
emit({
|
|
640
|
+
type: "error",
|
|
641
|
+
...base,
|
|
642
|
+
error: /* @__PURE__ */ new Error(`Structured output validation failed: ${String(result.error)}`),
|
|
643
|
+
source: "validation"
|
|
644
|
+
});
|
|
645
|
+
return null;
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Consume a model's {@link StreamPart} iterable, assembling the final
|
|
649
|
+
* {@link AssistantMessage} while emitting observer events for each part.
|
|
650
|
+
*
|
|
651
|
+
* Single pass, single piece of state. Throws on stream errors — the caller
|
|
652
|
+
* catches and converts to an error result.
|
|
653
|
+
*/
|
|
654
|
+
async function consumeStream(stream, emit, base) {
|
|
655
|
+
const content = [];
|
|
656
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
657
|
+
let textBuffer = "";
|
|
658
|
+
let thinkingBuffer = "";
|
|
659
|
+
let thinkingSignature;
|
|
660
|
+
let thinkingRedacted = false;
|
|
661
|
+
let usage = emptyUsage();
|
|
662
|
+
let finishReason = "unknown";
|
|
663
|
+
function flushText() {
|
|
664
|
+
if (textBuffer) {
|
|
665
|
+
content.push({
|
|
666
|
+
type: "text",
|
|
667
|
+
text: textBuffer
|
|
668
|
+
});
|
|
669
|
+
textBuffer = "";
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
function flushThinking() {
|
|
673
|
+
if (thinkingBuffer || thinkingRedacted) {
|
|
674
|
+
content.push({
|
|
675
|
+
type: "thinking",
|
|
676
|
+
thinking: thinkingBuffer,
|
|
677
|
+
signature: thinkingSignature,
|
|
678
|
+
redacted: thinkingRedacted
|
|
679
|
+
});
|
|
680
|
+
thinkingBuffer = "";
|
|
681
|
+
thinkingSignature = void 0;
|
|
682
|
+
thinkingRedacted = false;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
for await (const part of stream) switch (part.type) {
|
|
686
|
+
case "text_start":
|
|
687
|
+
flushText();
|
|
688
|
+
flushThinking();
|
|
689
|
+
textBuffer = "";
|
|
690
|
+
emit({
|
|
691
|
+
type: "textStart",
|
|
692
|
+
...base()
|
|
693
|
+
});
|
|
694
|
+
break;
|
|
695
|
+
case "text_delta":
|
|
696
|
+
textBuffer += part.text;
|
|
697
|
+
emit({
|
|
698
|
+
type: "textDelta",
|
|
699
|
+
...base(),
|
|
700
|
+
text: part.text
|
|
701
|
+
});
|
|
702
|
+
break;
|
|
703
|
+
case "text_end":
|
|
704
|
+
flushText();
|
|
705
|
+
emit({
|
|
706
|
+
type: "textStop",
|
|
707
|
+
...base()
|
|
708
|
+
});
|
|
709
|
+
break;
|
|
710
|
+
case "thinking_start":
|
|
711
|
+
flushText();
|
|
712
|
+
flushThinking();
|
|
713
|
+
thinkingBuffer = "";
|
|
714
|
+
thinkingSignature = void 0;
|
|
715
|
+
thinkingRedacted = part.redacted ?? false;
|
|
716
|
+
emit({
|
|
717
|
+
type: "thinkingStart",
|
|
718
|
+
...base(),
|
|
719
|
+
redacted: part.redacted
|
|
720
|
+
});
|
|
721
|
+
break;
|
|
722
|
+
case "thinking_delta":
|
|
723
|
+
thinkingBuffer += part.thinking;
|
|
724
|
+
emit({
|
|
725
|
+
type: "thinkingDelta",
|
|
726
|
+
...base(),
|
|
727
|
+
text: part.thinking
|
|
728
|
+
});
|
|
729
|
+
break;
|
|
730
|
+
case "thinking_end":
|
|
731
|
+
thinkingSignature = part.signature;
|
|
732
|
+
flushThinking();
|
|
733
|
+
emit({
|
|
734
|
+
type: "thinkingStop",
|
|
735
|
+
...base()
|
|
736
|
+
});
|
|
737
|
+
break;
|
|
738
|
+
case "tool_call_start":
|
|
739
|
+
flushText();
|
|
740
|
+
flushThinking();
|
|
741
|
+
toolCalls.set(part.id, {
|
|
742
|
+
id: part.id,
|
|
743
|
+
name: part.name,
|
|
744
|
+
args: "",
|
|
745
|
+
signature: part.signature
|
|
746
|
+
});
|
|
747
|
+
emit({
|
|
748
|
+
type: "toolCallStart",
|
|
749
|
+
...base(),
|
|
750
|
+
id: part.id,
|
|
751
|
+
name: part.name
|
|
752
|
+
});
|
|
753
|
+
break;
|
|
754
|
+
case "tool_call_delta": {
|
|
755
|
+
const tc = toolCalls.get(part.id);
|
|
756
|
+
if (tc !== void 0) {
|
|
757
|
+
tc.args += part.args;
|
|
758
|
+
emit({
|
|
759
|
+
type: "toolCallDelta",
|
|
760
|
+
...base(),
|
|
761
|
+
id: part.id,
|
|
762
|
+
name: tc.name,
|
|
763
|
+
partialArguments: tc.args
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
break;
|
|
767
|
+
}
|
|
768
|
+
case "tool_call_end": {
|
|
769
|
+
const tc = toolCalls.get(part.id);
|
|
770
|
+
if (tc !== void 0) {
|
|
771
|
+
let args;
|
|
772
|
+
try {
|
|
773
|
+
args = JSON.parse(tc.args || "{}");
|
|
774
|
+
} catch (e) {
|
|
775
|
+
throw new Error(`Invalid JSON in tool call arguments for ${tc.name} (${tc.id})`, { cause: e });
|
|
776
|
+
}
|
|
777
|
+
content.push({
|
|
778
|
+
type: "tool_call",
|
|
779
|
+
id: tc.id,
|
|
780
|
+
name: tc.name,
|
|
781
|
+
arguments: args,
|
|
782
|
+
...tc.signature !== void 0 && { signature: tc.signature }
|
|
783
|
+
});
|
|
784
|
+
toolCalls.delete(tc.id);
|
|
785
|
+
emit({
|
|
786
|
+
type: "toolCallStop",
|
|
787
|
+
...base(),
|
|
788
|
+
id: tc.id,
|
|
789
|
+
name: tc.name,
|
|
790
|
+
arguments: args
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
break;
|
|
794
|
+
}
|
|
795
|
+
case "finish":
|
|
796
|
+
usage = part.usage;
|
|
797
|
+
finishReason = part.finishReason;
|
|
798
|
+
break;
|
|
799
|
+
case "error": throw part.error;
|
|
800
|
+
}
|
|
801
|
+
flushThinking();
|
|
802
|
+
flushText();
|
|
803
|
+
return {
|
|
804
|
+
message: {
|
|
805
|
+
role: "assistant",
|
|
806
|
+
content
|
|
807
|
+
},
|
|
808
|
+
usage,
|
|
809
|
+
finishReason
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Execute an agent run to completion.
|
|
814
|
+
*
|
|
815
|
+
* This function never throws. All errors are caught and reflected in the
|
|
816
|
+
* returned {@link AgentResult}. The caller provides an {@link EmitFn} that
|
|
817
|
+
* receives every observer event as it occurs.
|
|
818
|
+
*/
|
|
819
|
+
async function executeRun(config, prompt, options, emit) {
|
|
820
|
+
const runStart = Date.now();
|
|
821
|
+
const runId = crypto.randomUUID();
|
|
822
|
+
const policies = [...config.policies ?? [], ...options.policies ?? []];
|
|
823
|
+
const tools = config.tools ?? [];
|
|
824
|
+
const transcript = [...options.transcript ?? [], ...prompt !== void 0 ? normalizePrompt(prompt) : []];
|
|
825
|
+
const modelConfig = {};
|
|
826
|
+
if (options.maxTokens !== void 0) modelConfig.maxTokens = options.maxTokens;
|
|
827
|
+
if (options.temperature !== void 0) modelConfig.temperature = options.temperature;
|
|
828
|
+
if (options.topP !== void 0) modelConfig.topP = options.topP;
|
|
829
|
+
if (options.topK !== void 0) modelConfig.topK = options.topK;
|
|
830
|
+
if (options.stopSequences !== void 0) modelConfig.stopSequences = options.stopSequences;
|
|
831
|
+
const signal = options.signal ?? new AbortController().signal;
|
|
832
|
+
const state = options.state ?? {};
|
|
833
|
+
let totalUsage = emptyUsage();
|
|
834
|
+
let step = 0;
|
|
835
|
+
let retries = 0;
|
|
836
|
+
let finishReason = "unknown";
|
|
837
|
+
let stoppedBy;
|
|
838
|
+
let runError;
|
|
839
|
+
let parsedObject;
|
|
840
|
+
let lastResponse = {
|
|
841
|
+
role: "assistant",
|
|
842
|
+
content: []
|
|
843
|
+
};
|
|
844
|
+
/** Snapshot the current {@link RunContext}. */
|
|
845
|
+
function ctx() {
|
|
846
|
+
return {
|
|
847
|
+
runId,
|
|
848
|
+
step,
|
|
849
|
+
agent: config.name,
|
|
850
|
+
state,
|
|
851
|
+
retries,
|
|
852
|
+
signal
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
/** Create {@link BaseFields} from current run state. */
|
|
856
|
+
function base() {
|
|
857
|
+
return baseFields(runId, step, config.name);
|
|
858
|
+
}
|
|
859
|
+
try {
|
|
860
|
+
emit({
|
|
861
|
+
type: "runStart",
|
|
862
|
+
...base(),
|
|
863
|
+
model: config.model.name,
|
|
864
|
+
instructions: config.instructions,
|
|
865
|
+
prompt: prompt ?? void 0,
|
|
866
|
+
tools: tools.map((t) => t.name)
|
|
867
|
+
});
|
|
868
|
+
for (;;) {
|
|
869
|
+
if (signal.aborted) {
|
|
870
|
+
finishReason = "cancelled";
|
|
871
|
+
emit({
|
|
872
|
+
type: "abort",
|
|
873
|
+
...base(),
|
|
874
|
+
reason: String(signal.reason ?? "Aborted")
|
|
875
|
+
});
|
|
876
|
+
break;
|
|
877
|
+
}
|
|
878
|
+
const plan = {
|
|
879
|
+
model: config.model,
|
|
880
|
+
instructions: config.instructions,
|
|
881
|
+
messages: [...transcript],
|
|
882
|
+
tools: [...tools],
|
|
883
|
+
...modelConfig
|
|
884
|
+
};
|
|
885
|
+
const beforeStep = await runPolicies(policies, (p) => p.beforeStep?.(ctx(), plan));
|
|
886
|
+
if (beforeStep != null) {
|
|
887
|
+
if (beforeStep.error) emit({
|
|
888
|
+
type: "error",
|
|
889
|
+
...base(),
|
|
890
|
+
error: beforeStep.error,
|
|
891
|
+
source: "policy",
|
|
892
|
+
policy: beforeStep.policy
|
|
893
|
+
});
|
|
894
|
+
finishReason = "stop";
|
|
895
|
+
stoppedBy = beforeStep.policy;
|
|
896
|
+
break;
|
|
897
|
+
}
|
|
898
|
+
const toolMap = new Map(plan.tools.map((t) => [t.name, t]));
|
|
899
|
+
const stepStart = Date.now();
|
|
900
|
+
emit({
|
|
901
|
+
type: "stepStart",
|
|
902
|
+
...base(),
|
|
903
|
+
model: plan.model.name
|
|
904
|
+
});
|
|
905
|
+
const callMessages = prepareMessages(plan);
|
|
906
|
+
const toolDefs = deriveToolDefs(plan.tools);
|
|
907
|
+
let message;
|
|
908
|
+
let stepUsage;
|
|
909
|
+
let stepFinishReason;
|
|
910
|
+
try {
|
|
911
|
+
const result = await consumeStream(plan.model.stream({
|
|
912
|
+
messages: callMessages,
|
|
913
|
+
tools: toolDefs.length > 0 ? toolDefs : void 0,
|
|
914
|
+
config: extractModelConfig(plan),
|
|
915
|
+
output: options.output,
|
|
916
|
+
signal
|
|
917
|
+
}), emit, base);
|
|
918
|
+
message = result.message;
|
|
919
|
+
stepUsage = result.usage;
|
|
920
|
+
stepFinishReason = result.finishReason;
|
|
921
|
+
} catch (err) {
|
|
922
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
923
|
+
emit({
|
|
924
|
+
type: "error",
|
|
925
|
+
...base(),
|
|
926
|
+
error,
|
|
927
|
+
source: "provider"
|
|
928
|
+
});
|
|
929
|
+
message = {
|
|
930
|
+
role: "assistant",
|
|
931
|
+
content: []
|
|
932
|
+
};
|
|
933
|
+
stepUsage = emptyUsage();
|
|
934
|
+
stepFinishReason = "error";
|
|
935
|
+
runError = error;
|
|
936
|
+
}
|
|
937
|
+
const responseEnd = Date.now();
|
|
938
|
+
totalUsage = addUsage(totalUsage, stepUsage);
|
|
939
|
+
finishReason = stepFinishReason;
|
|
940
|
+
lastResponse = message;
|
|
941
|
+
parsedObject = options.output !== void 0 ? parseStructuredOutput(message, options.output, emit, base()) : void 0;
|
|
942
|
+
emit({
|
|
943
|
+
type: "responseFinish",
|
|
944
|
+
...base(),
|
|
945
|
+
message,
|
|
946
|
+
usage: stepUsage,
|
|
947
|
+
totalUsage,
|
|
948
|
+
finishReason: stepFinishReason,
|
|
949
|
+
error: runError,
|
|
950
|
+
duration: responseEnd - stepStart
|
|
951
|
+
});
|
|
952
|
+
if (runError !== void 0) break;
|
|
953
|
+
transcript.push(message);
|
|
954
|
+
const responseInfo = {
|
|
955
|
+
message,
|
|
956
|
+
messages: transcript,
|
|
957
|
+
usage: stepUsage,
|
|
958
|
+
totalUsage,
|
|
959
|
+
finishReason: stepFinishReason,
|
|
960
|
+
object: parsedObject
|
|
961
|
+
};
|
|
962
|
+
const afterResponse = await runPolicies(policies, (p) => p.afterResponse?.(ctx(), responseInfo));
|
|
963
|
+
if (afterResponse != null) {
|
|
964
|
+
if (afterResponse.error) emit({
|
|
965
|
+
type: "error",
|
|
966
|
+
...base(),
|
|
967
|
+
error: afterResponse.error,
|
|
968
|
+
source: "policy",
|
|
969
|
+
policy: afterResponse.policy
|
|
970
|
+
});
|
|
971
|
+
const a = afterResponse.action;
|
|
972
|
+
if (a.action === "stop") {
|
|
973
|
+
finishReason = "stop";
|
|
974
|
+
stoppedBy = afterResponse.policy;
|
|
975
|
+
break;
|
|
976
|
+
}
|
|
977
|
+
if (a.action === "replace") {
|
|
978
|
+
transcript[transcript.length - 1] = a.message;
|
|
979
|
+
lastResponse = a.message;
|
|
980
|
+
message = a.message;
|
|
981
|
+
}
|
|
982
|
+
if (a.action === "retry") {
|
|
983
|
+
transcript.pop();
|
|
984
|
+
if (a.messages) transcript.push(...a.messages);
|
|
985
|
+
retries++;
|
|
986
|
+
emit({
|
|
987
|
+
type: "stepRetry",
|
|
988
|
+
...base(),
|
|
989
|
+
reason: a.reason,
|
|
990
|
+
retries
|
|
991
|
+
});
|
|
992
|
+
continue;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
const toolCalls = extractToolCalls(message);
|
|
996
|
+
if (toolCalls.length > 0) {
|
|
997
|
+
const { results, stopRun } = await executeToolCalls(toolCalls, toolMap, policies, ctx(), emit);
|
|
998
|
+
transcript.push({
|
|
999
|
+
role: "tool",
|
|
1000
|
+
content: results
|
|
1001
|
+
});
|
|
1002
|
+
if (stopRun !== void 0) {
|
|
1003
|
+
stoppedBy = stopRun;
|
|
1004
|
+
finishReason = "stop";
|
|
1005
|
+
break;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
const stepMessages = [message];
|
|
1009
|
+
if (toolCalls.length > 0) stepMessages.push(transcript[transcript.length - 1]);
|
|
1010
|
+
const stepInfo = {
|
|
1011
|
+
messages: stepMessages,
|
|
1012
|
+
transcript,
|
|
1013
|
+
usage: stepUsage,
|
|
1014
|
+
totalUsage,
|
|
1015
|
+
finishReason: stepFinishReason,
|
|
1016
|
+
object: parsedObject
|
|
1017
|
+
};
|
|
1018
|
+
const afterStep = await runPolicies(policies, (p) => p.afterStep?.(ctx(), stepInfo));
|
|
1019
|
+
let injected = false;
|
|
1020
|
+
if (afterStep != null) {
|
|
1021
|
+
if (afterStep.error) emit({
|
|
1022
|
+
type: "error",
|
|
1023
|
+
...base(),
|
|
1024
|
+
error: afterStep.error,
|
|
1025
|
+
source: "policy",
|
|
1026
|
+
policy: afterStep.policy
|
|
1027
|
+
});
|
|
1028
|
+
const a = afterStep.action;
|
|
1029
|
+
if (a.action === "stop") {
|
|
1030
|
+
finishReason = "stop";
|
|
1031
|
+
stoppedBy = afterStep.policy;
|
|
1032
|
+
emit({
|
|
1033
|
+
type: "stepFinish",
|
|
1034
|
+
...base(),
|
|
1035
|
+
messages: stepMessages,
|
|
1036
|
+
object: parsedObject,
|
|
1037
|
+
usage: stepUsage,
|
|
1038
|
+
totalUsage,
|
|
1039
|
+
finishReason,
|
|
1040
|
+
duration: Date.now() - stepStart
|
|
1041
|
+
});
|
|
1042
|
+
break;
|
|
1043
|
+
}
|
|
1044
|
+
if (a.action === "retry") {
|
|
1045
|
+
transcript.splice(transcript.length - stepMessages.length, stepMessages.length);
|
|
1046
|
+
if (a.messages) transcript.push(...a.messages);
|
|
1047
|
+
retries++;
|
|
1048
|
+
emit({
|
|
1049
|
+
type: "stepRetry",
|
|
1050
|
+
...base(),
|
|
1051
|
+
reason: a.reason,
|
|
1052
|
+
retries
|
|
1053
|
+
});
|
|
1054
|
+
continue;
|
|
1055
|
+
}
|
|
1056
|
+
if (a.action === "inject") {
|
|
1057
|
+
transcript.push(...a.messages);
|
|
1058
|
+
injected = true;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
emit({
|
|
1062
|
+
type: "stepFinish",
|
|
1063
|
+
...base(),
|
|
1064
|
+
messages: stepMessages,
|
|
1065
|
+
object: parsedObject,
|
|
1066
|
+
usage: stepUsage,
|
|
1067
|
+
totalUsage,
|
|
1068
|
+
finishReason: stepFinishReason,
|
|
1069
|
+
duration: Date.now() - stepStart
|
|
1070
|
+
});
|
|
1071
|
+
if (toolCalls.length === 0 && !injected) break;
|
|
1072
|
+
step++;
|
|
1073
|
+
retries = 0;
|
|
1074
|
+
}
|
|
1075
|
+
} catch (err) {
|
|
1076
|
+
runError = err instanceof Error ? err : new Error(String(err));
|
|
1077
|
+
finishReason = "error";
|
|
1078
|
+
emit({
|
|
1079
|
+
type: "error",
|
|
1080
|
+
...base(),
|
|
1081
|
+
error: runError,
|
|
1082
|
+
source: "provider"
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
const result = {
|
|
1086
|
+
text: extractText(lastResponse),
|
|
1087
|
+
response: lastResponse,
|
|
1088
|
+
transcript,
|
|
1089
|
+
steps: step + 1,
|
|
1090
|
+
usage: totalUsage,
|
|
1091
|
+
finishReason,
|
|
1092
|
+
stoppedBy,
|
|
1093
|
+
error: runError,
|
|
1094
|
+
duration: Date.now() - runStart,
|
|
1095
|
+
object: parsedObject
|
|
1096
|
+
};
|
|
1097
|
+
emit({
|
|
1098
|
+
type: "runFinish",
|
|
1099
|
+
...base(),
|
|
1100
|
+
text: result.text,
|
|
1101
|
+
response: result.response,
|
|
1102
|
+
transcript: result.transcript,
|
|
1103
|
+
object: result.object,
|
|
1104
|
+
steps: result.steps,
|
|
1105
|
+
usage: result.usage,
|
|
1106
|
+
finishReason: result.finishReason,
|
|
1107
|
+
stoppedBy: result.stoppedBy,
|
|
1108
|
+
error: result.error,
|
|
1109
|
+
duration: result.duration
|
|
1110
|
+
});
|
|
1111
|
+
return result;
|
|
1112
|
+
}
|
|
1113
|
+
//#endregion
|
|
1114
|
+
//#region src/agent.ts
|
|
1115
|
+
/** Create an {@link Agent} from the given configuration. */
|
|
1116
|
+
function defineAgent(config) {
|
|
1117
|
+
const agent = {
|
|
1118
|
+
run(prompt, options) {
|
|
1119
|
+
const opts = options ?? {};
|
|
1120
|
+
return executeRun(config, prompt, opts, createEmitFn([...config.observers ?? [], ...opts.observers ?? []]));
|
|
1121
|
+
},
|
|
1122
|
+
stream(prompt, options) {
|
|
1123
|
+
const opts = options ?? {};
|
|
1124
|
+
const observers = [...config.observers ?? [], ...opts.observers ?? []];
|
|
1125
|
+
const channel = createEventChannel();
|
|
1126
|
+
executeRun(config, prompt, opts, createEmitFn(observers, channel.push)).then((result) => channel.close(result), (err) => channel.fail(err instanceof Error ? err : new Error(String(err))));
|
|
1127
|
+
return {
|
|
1128
|
+
result: channel.result,
|
|
1129
|
+
[Symbol.asyncIterator]() {
|
|
1130
|
+
return channel.stream[Symbol.asyncIterator]();
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
1133
|
+
},
|
|
1134
|
+
asTool(toolConfig) {
|
|
1135
|
+
return {
|
|
1136
|
+
name: toolConfig.name,
|
|
1137
|
+
description: toolConfig.description,
|
|
1138
|
+
schema: toolConfig.schema,
|
|
1139
|
+
async execute(args, ctx) {
|
|
1140
|
+
const result = await agent.run(toolConfig.prompt(args), { signal: ctx.signal });
|
|
1141
|
+
if (toolConfig.output) return toolConfig.output(result);
|
|
1142
|
+
return result.text;
|
|
1143
|
+
}
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
return agent;
|
|
1148
|
+
}
|
|
1149
|
+
//#endregion
|
|
1150
|
+
export { addUsage, assistant, blob, defineAgent, defineObserver, definePolicy, defineTool, emptyUsage, json, normalizePrompt, normalizeToolReturn, schemaToJsonSchema, system, text, url, user };
|
|
1151
|
+
|
|
1152
|
+
//# sourceMappingURL=index.mjs.map
|