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