@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.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