@cydm/pie 1.0.14 → 1.0.15

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.
@@ -0,0 +1,1029 @@
1
+ import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
+ import {
3
+ EventStream,
4
+ streamSimple,
5
+ validateToolArguments
6
+ } from "./chunk-5DA2D3K2.js";
7
+
8
+ // ../../packages/agent-core/src/agent-loop.ts
9
+ function createLoopErrorMessage(error, config, signal) {
10
+ const message = error instanceof Error ? error.message : String(error);
11
+ return {
12
+ role: "assistant",
13
+ content: [{ type: "text", text: "" }],
14
+ api: config.model.api,
15
+ provider: config.model.provider,
16
+ model: config.model.id,
17
+ usage: {
18
+ input: 0,
19
+ output: 0,
20
+ cacheRead: 0,
21
+ cacheWrite: 0,
22
+ totalTokens: 0,
23
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
24
+ },
25
+ stopReason: signal?.aborted ? "aborted" : "error",
26
+ errorMessage: message,
27
+ timestamp: Date.now()
28
+ };
29
+ }
30
+ async function notifyOnError(config, error, phase) {
31
+ try {
32
+ await config.hooks?.onError?.(error instanceof Error ? error : new Error(String(error)), { phase });
33
+ } catch (hookError) {
34
+ console.warn(`[AgentLoop] onError hook failed during ${phase}:`, hookError);
35
+ }
36
+ }
37
+ function endStreamWithError(stream, newMessages, config, signal, error) {
38
+ const errorMessage = createLoopErrorMessage(error, config, signal);
39
+ newMessages.push(errorMessage);
40
+ stream.push({ type: "message_start", message: errorMessage });
41
+ stream.push({ type: "message_end", message: errorMessage });
42
+ stream.push({ type: "turn_end", message: errorMessage, toolResults: [] });
43
+ stream.push({ type: "agent_end", messages: newMessages });
44
+ stream.end(newMessages);
45
+ }
46
+ function agentLoop(prompts, context, config, signal, streamFn) {
47
+ const stream = createAgentStream();
48
+ (async () => {
49
+ const newMessages = [...prompts];
50
+ try {
51
+ try {
52
+ await config.hooks?.beforeAgentStart?.();
53
+ } catch (e) {
54
+ console.warn("[AgentLoop] beforeAgentStart hook failed:", e);
55
+ }
56
+ const currentContext = {
57
+ ...context,
58
+ messages: [...context.messages, ...prompts]
59
+ };
60
+ stream.push({ type: "agent_start" });
61
+ stream.push({ type: "turn_start" });
62
+ for (const prompt of prompts) {
63
+ stream.push({ type: "message_start", message: prompt });
64
+ stream.push({ type: "message_end", message: prompt });
65
+ }
66
+ try {
67
+ await runLoop(currentContext, newMessages, config, signal, stream, streamFn);
68
+ } catch (error) {
69
+ await notifyOnError(config, error, "runLoop");
70
+ endStreamWithError(stream, newMessages, config, signal, error);
71
+ return;
72
+ }
73
+ try {
74
+ await config.hooks?.afterAgentEnd?.(newMessages);
75
+ } catch (e) {
76
+ console.warn("[AgentLoop] afterAgentEnd hook failed:", e);
77
+ }
78
+ } catch (error) {
79
+ await notifyOnError(config, error, "agentLoop");
80
+ endStreamWithError(stream, newMessages, config, signal, error);
81
+ }
82
+ })();
83
+ return stream;
84
+ }
85
+ function agentLoopContinue(context, config, signal, streamFn) {
86
+ if (context.messages.length === 0) {
87
+ throw new Error("Cannot continue: no messages in context");
88
+ }
89
+ if (context.messages[context.messages.length - 1].role === "assistant") {
90
+ throw new Error("Cannot continue from message role: assistant");
91
+ }
92
+ const stream = createAgentStream();
93
+ (async () => {
94
+ const newMessages = [];
95
+ try {
96
+ try {
97
+ await config.hooks?.beforeAgentStart?.();
98
+ } catch (e) {
99
+ console.warn("[AgentLoop] beforeAgentStart hook failed:", e);
100
+ }
101
+ const currentContext = { ...context };
102
+ stream.push({ type: "agent_start" });
103
+ stream.push({ type: "turn_start" });
104
+ try {
105
+ await runLoop(currentContext, newMessages, config, signal, stream, streamFn);
106
+ } catch (error) {
107
+ await notifyOnError(config, error, "runLoop");
108
+ endStreamWithError(stream, newMessages, config, signal, error);
109
+ return;
110
+ }
111
+ try {
112
+ await config.hooks?.afterAgentEnd?.(newMessages);
113
+ } catch (e) {
114
+ console.warn("[AgentLoop] afterAgentEnd hook failed:", e);
115
+ }
116
+ } catch (error) {
117
+ await notifyOnError(config, error, "agentLoopContinue");
118
+ endStreamWithError(stream, newMessages, config, signal, error);
119
+ }
120
+ })();
121
+ return stream;
122
+ }
123
+ function createAgentStream() {
124
+ return new EventStream(
125
+ (event) => event.type === "agent_end",
126
+ (event) => event.type === "agent_end" ? event.messages : []
127
+ );
128
+ }
129
+ async function runLoop(currentContext, newMessages, config, signal, stream, streamFn) {
130
+ let firstTurn = true;
131
+ let pendingMessages = await config.getSteeringMessages?.() || [];
132
+ while (true) {
133
+ let hasMoreToolCalls = true;
134
+ let steeringAfterTools = null;
135
+ while (hasMoreToolCalls || pendingMessages.length > 0) {
136
+ if (!firstTurn) {
137
+ stream.push({ type: "turn_start" });
138
+ } else {
139
+ firstTurn = false;
140
+ }
141
+ if (pendingMessages.length > 0) {
142
+ for (const message2 of pendingMessages) {
143
+ stream.push({ type: "message_start", message: message2 });
144
+ stream.push({ type: "message_end", message: message2 });
145
+ currentContext.messages.push(message2);
146
+ newMessages.push(message2);
147
+ }
148
+ pendingMessages = [];
149
+ }
150
+ let message;
151
+ try {
152
+ message = await streamAssistantResponse(currentContext, config, signal, stream, streamFn);
153
+ } catch (error) {
154
+ await notifyOnError(config, error, "streamAssistantResponse");
155
+ throw error;
156
+ }
157
+ newMessages.push(message);
158
+ if (message.stopReason === "error" || message.stopReason === "aborted") {
159
+ stream.push({ type: "turn_end", message, toolResults: [] });
160
+ stream.push({ type: "agent_end", messages: newMessages });
161
+ stream.end(newMessages);
162
+ return;
163
+ }
164
+ const toolCalls = message.content.filter((c) => c.type === "toolCall");
165
+ hasMoreToolCalls = toolCalls.length > 0;
166
+ const toolResults = [];
167
+ if (hasMoreToolCalls) {
168
+ const toolExecution = await executeToolCalls(
169
+ currentContext.tools,
170
+ message,
171
+ signal,
172
+ stream,
173
+ config.getSteeringMessages,
174
+ config.hooks
175
+ );
176
+ toolResults.push(...toolExecution.toolResults);
177
+ steeringAfterTools = toolExecution.steeringMessages ?? null;
178
+ for (const result of toolResults) {
179
+ currentContext.messages.push(result);
180
+ newMessages.push(result);
181
+ }
182
+ }
183
+ stream.push({ type: "turn_end", message, toolResults });
184
+ if (steeringAfterTools && steeringAfterTools.length > 0) {
185
+ pendingMessages = steeringAfterTools;
186
+ steeringAfterTools = null;
187
+ } else {
188
+ pendingMessages = await config.getSteeringMessages?.() || [];
189
+ }
190
+ }
191
+ const followUpMessages = await config.getFollowUpMessages?.() || [];
192
+ if (followUpMessages.length > 0) {
193
+ pendingMessages = followUpMessages;
194
+ continue;
195
+ }
196
+ break;
197
+ }
198
+ stream.push({ type: "agent_end", messages: newMessages });
199
+ stream.end(newMessages);
200
+ }
201
+ async function streamAssistantResponse(context, config, signal, stream, streamFn) {
202
+ let messages = context.messages;
203
+ if (config.transformContext) {
204
+ messages = await config.transformContext(messages, signal);
205
+ }
206
+ const llmMessages = await config.convertToLlm(messages);
207
+ const llmContext = {
208
+ systemPrompt: context.systemPrompt,
209
+ messages: llmMessages,
210
+ tools: context.tools
211
+ };
212
+ const streamFunction = streamFn || streamSimple;
213
+ const resolvedApiKey = (config.getApiKey ? await config.getApiKey(config.model.provider) : void 0) || config.apiKey;
214
+ const response = await streamFunction(config.model, llmContext, {
215
+ ...config,
216
+ apiKey: resolvedApiKey,
217
+ signal
218
+ });
219
+ let partialMessage = null;
220
+ let addedPartial = false;
221
+ for await (const event of response) {
222
+ switch (event.type) {
223
+ case "start":
224
+ partialMessage = event.partial;
225
+ context.messages.push(partialMessage);
226
+ addedPartial = true;
227
+ stream.push({ type: "message_start", message: { ...partialMessage } });
228
+ break;
229
+ case "text_start":
230
+ case "text_delta":
231
+ case "text_end":
232
+ case "thinking_start":
233
+ case "thinking_delta":
234
+ case "thinking_end":
235
+ case "toolcall_start":
236
+ case "toolcall_delta":
237
+ case "toolcall_end":
238
+ if (partialMessage) {
239
+ partialMessage = event.partial;
240
+ context.messages[context.messages.length - 1] = partialMessage;
241
+ stream.push({
242
+ type: "message_update",
243
+ assistantMessageEvent: event,
244
+ message: { ...partialMessage }
245
+ });
246
+ }
247
+ break;
248
+ case "done":
249
+ case "error": {
250
+ const finalMessage = await response.result();
251
+ if (addedPartial) {
252
+ context.messages[context.messages.length - 1] = finalMessage;
253
+ } else {
254
+ context.messages.push(finalMessage);
255
+ }
256
+ if (!addedPartial) {
257
+ stream.push({ type: "message_start", message: { ...finalMessage } });
258
+ }
259
+ stream.push({ type: "message_end", message: finalMessage });
260
+ return finalMessage;
261
+ }
262
+ }
263
+ }
264
+ return await response.result();
265
+ }
266
+ async function executeToolCalls(tools, assistantMessage, signal, stream, getSteeringMessages, hooks) {
267
+ const toolCalls = assistantMessage.content.filter((c) => c.type === "toolCall");
268
+ const results = [];
269
+ let steeringMessages;
270
+ for (let index = 0; index < toolCalls.length; index++) {
271
+ const toolCall = toolCalls[index];
272
+ const tool = tools?.find((t) => t.name === toolCall.name);
273
+ let blocked = false;
274
+ let blockReason;
275
+ try {
276
+ const hookResult = await hooks?.beforeToolCall?.(toolCall.name, toolCall.arguments);
277
+ if (hookResult && hookResult.block) {
278
+ blocked = true;
279
+ blockReason = hookResult.reason || "Blocked by hook";
280
+ }
281
+ } catch (e) {
282
+ console.warn(`[AgentLoop] beforeToolCall hook failed for ${toolCall.name}:`, e);
283
+ }
284
+ let result;
285
+ let isError = false;
286
+ let executionArgs = toolCall.arguments;
287
+ let started = false;
288
+ const pushToolStart = () => {
289
+ if (started) return;
290
+ started = true;
291
+ stream.push({
292
+ type: "tool_execution_start",
293
+ toolCallId: toolCall.id,
294
+ toolName: toolCall.name,
295
+ args: executionArgs
296
+ });
297
+ };
298
+ if (blocked) {
299
+ pushToolStart();
300
+ result = {
301
+ content: [{ type: "text", text: `Tool execution blocked: ${blockReason}` }],
302
+ details: { blocked: true, reason: blockReason }
303
+ };
304
+ isError = true;
305
+ } else {
306
+ try {
307
+ if (!tool) {
308
+ throw new Error(`Tool ${toolCall.name} not found`);
309
+ }
310
+ const validatedArgs = validateToolArguments(tool, toolCall);
311
+ executionArgs = validatedArgs;
312
+ pushToolStart();
313
+ result = await tool.execute(toolCall.id, validatedArgs, signal, (partialResult) => {
314
+ stream.push({
315
+ type: "tool_execution_update",
316
+ toolCallId: toolCall.id,
317
+ toolName: toolCall.name,
318
+ args: executionArgs,
319
+ partialResult
320
+ });
321
+ });
322
+ isError = isError || !!result.isError;
323
+ } catch (e) {
324
+ pushToolStart();
325
+ result = {
326
+ content: [{ type: "text", text: e instanceof Error ? e.message : String(e) }],
327
+ details: {}
328
+ };
329
+ isError = true;
330
+ try {
331
+ await hooks?.onError?.(e instanceof Error ? e : new Error(String(e)), {
332
+ toolName: toolCall.name,
333
+ phase: "toolExecution"
334
+ });
335
+ } catch (hookError) {
336
+ console.warn(`[AgentLoop] onError hook failed during toolExecution for ${toolCall.name}:`, hookError);
337
+ }
338
+ }
339
+ }
340
+ try {
341
+ await hooks?.afterToolCall?.(toolCall.name, toolCall.arguments, result, isError);
342
+ } catch (e) {
343
+ console.warn(`[AgentLoop] afterToolCall hook failed for ${toolCall.name}:`, e);
344
+ }
345
+ stream.push({
346
+ type: "tool_execution_end",
347
+ toolCallId: toolCall.id,
348
+ toolName: toolCall.name,
349
+ args: executionArgs,
350
+ result,
351
+ isError
352
+ });
353
+ const toolResultMessage = {
354
+ role: "toolResult",
355
+ toolCallId: toolCall.id,
356
+ toolName: toolCall.name,
357
+ content: result.content,
358
+ details: result.details,
359
+ isError,
360
+ timestamp: Date.now()
361
+ };
362
+ results.push(toolResultMessage);
363
+ stream.push({ type: "message_start", message: toolResultMessage });
364
+ stream.push({ type: "message_end", message: toolResultMessage });
365
+ if (getSteeringMessages) {
366
+ const steering = await getSteeringMessages();
367
+ if (steering.length > 0) {
368
+ steeringMessages = steering;
369
+ const remainingCalls = toolCalls.slice(index + 1);
370
+ for (const skipped of remainingCalls) {
371
+ results.push(skipToolCall(skipped, stream));
372
+ }
373
+ break;
374
+ }
375
+ }
376
+ }
377
+ return { toolResults: results, steeringMessages };
378
+ }
379
+ function skipToolCall(toolCall, stream) {
380
+ const result = {
381
+ content: [{ type: "text", text: "Skipped due to queued user message." }],
382
+ details: {}
383
+ };
384
+ stream.push({
385
+ type: "tool_execution_start",
386
+ toolCallId: toolCall.id,
387
+ toolName: toolCall.name,
388
+ args: toolCall.arguments
389
+ });
390
+ stream.push({
391
+ type: "tool_execution_end",
392
+ toolCallId: toolCall.id,
393
+ toolName: toolCall.name,
394
+ args: toolCall.arguments,
395
+ result,
396
+ isError: true
397
+ });
398
+ const toolResultMessage = {
399
+ role: "toolResult",
400
+ toolCallId: toolCall.id,
401
+ toolName: toolCall.name,
402
+ content: result.content,
403
+ details: {},
404
+ isError: true,
405
+ timestamp: Date.now()
406
+ };
407
+ stream.push({ type: "message_start", message: toolResultMessage });
408
+ stream.push({ type: "message_end", message: toolResultMessage });
409
+ return toolResultMessage;
410
+ }
411
+
412
+ // ../../packages/agent-core/src/agent.ts
413
+ function defaultConvertToLlm(messages) {
414
+ return messages.filter((m) => m.role === "user" || m.role === "assistant" || m.role === "toolResult");
415
+ }
416
+ function isAgentMessage(value) {
417
+ return !!value && typeof value === "object" && "role" in value;
418
+ }
419
+ function isUserContentBlock(value) {
420
+ if (!value || typeof value !== "object" || !("type" in value)) {
421
+ return false;
422
+ }
423
+ const type = value.type;
424
+ return type === "text" || type === "image" || type === "audio" || type === "video" || type === "fileRef";
425
+ }
426
+ var Agent = class {
427
+ _state;
428
+ listeners = /* @__PURE__ */ new Set();
429
+ semanticListeners = /* @__PURE__ */ new Set();
430
+ abortController;
431
+ convertToLlm;
432
+ transformContext;
433
+ steeringQueue = [];
434
+ followUpQueue = [];
435
+ steeringMode;
436
+ followUpMode;
437
+ streamFn;
438
+ _sessionId;
439
+ _apiKey;
440
+ getApiKey;
441
+ runningPrompt;
442
+ resolveRunningPrompt;
443
+ _thinkingBudgets;
444
+ _maxRetryDelayMs;
445
+ hooks;
446
+ statusSnapshot = {
447
+ phase: "idle",
448
+ turnState: "completed",
449
+ hasPendingToolContinuation: false
450
+ };
451
+ constructor(opts = {}) {
452
+ this._state = {
453
+ systemPrompt: "",
454
+ model: null,
455
+ // Must be set before use
456
+ thinkingLevel: "off",
457
+ tools: [],
458
+ messages: [],
459
+ isStreaming: false,
460
+ streamMessage: null,
461
+ pendingToolCalls: /* @__PURE__ */ new Set(),
462
+ error: void 0,
463
+ ...opts.initialState
464
+ };
465
+ this.convertToLlm = opts.convertToLlm || defaultConvertToLlm;
466
+ this.transformContext = opts.transformContext;
467
+ this.steeringMode = opts.steeringMode || "one-at-a-time";
468
+ this.followUpMode = opts.followUpMode || "one-at-a-time";
469
+ this.streamFn = opts.streamFn || streamSimple;
470
+ this._sessionId = opts.sessionId;
471
+ this._apiKey = opts.apiKey;
472
+ this.getApiKey = opts.getApiKey;
473
+ this._thinkingBudgets = opts.thinkingBudgets;
474
+ this._maxRetryDelayMs = opts.maxRetryDelayMs;
475
+ this.hooks = opts.initialState?.hooks;
476
+ }
477
+ get sessionId() {
478
+ return this._sessionId;
479
+ }
480
+ set sessionId(value) {
481
+ this._sessionId = value;
482
+ }
483
+ get apiKey() {
484
+ return this._apiKey;
485
+ }
486
+ set apiKey(value) {
487
+ this._apiKey = value;
488
+ }
489
+ get thinkingBudgets() {
490
+ return this._thinkingBudgets;
491
+ }
492
+ set thinkingBudgets(value) {
493
+ this._thinkingBudgets = value;
494
+ }
495
+ get maxRetryDelayMs() {
496
+ return this._maxRetryDelayMs;
497
+ }
498
+ set maxRetryDelayMs(value) {
499
+ this._maxRetryDelayMs = value;
500
+ }
501
+ get state() {
502
+ return this._state;
503
+ }
504
+ subscribe(fn) {
505
+ this.listeners.add(fn);
506
+ return () => this.listeners.delete(fn);
507
+ }
508
+ subscribeSemantic(fn) {
509
+ this.semanticListeners.add(fn);
510
+ return () => this.semanticListeners.delete(fn);
511
+ }
512
+ getStatusSnapshot() {
513
+ return { ...this.statusSnapshot };
514
+ }
515
+ setSystemPrompt(v) {
516
+ this._state.systemPrompt = v;
517
+ }
518
+ setModel(m) {
519
+ this._state.model = m;
520
+ }
521
+ setThinkingLevel(l) {
522
+ this._state.thinkingLevel = l;
523
+ }
524
+ setTools(t) {
525
+ this._state.tools = t;
526
+ }
527
+ setHooks(hooks) {
528
+ this.hooks = hooks;
529
+ }
530
+ replaceMessages(ms) {
531
+ this._state.messages = ms.slice();
532
+ }
533
+ appendMessage(m) {
534
+ this._state.messages = [...this._state.messages, m];
535
+ }
536
+ clearMessages() {
537
+ this._state.messages = [];
538
+ }
539
+ steer(m) {
540
+ this.steeringQueue.push(m);
541
+ }
542
+ followUp(m) {
543
+ this.followUpQueue.push(m);
544
+ }
545
+ clearSteeringQueue() {
546
+ this.steeringQueue = [];
547
+ }
548
+ clearFollowUpQueue() {
549
+ this.followUpQueue = [];
550
+ }
551
+ clearAllQueues() {
552
+ this.steeringQueue = [];
553
+ this.followUpQueue = [];
554
+ }
555
+ hasQueuedMessages() {
556
+ return this.steeringQueue.length > 0 || this.followUpQueue.length > 0;
557
+ }
558
+ setSteeringMode(mode) {
559
+ this.steeringMode = mode;
560
+ }
561
+ getSteeringMode() {
562
+ return this.steeringMode;
563
+ }
564
+ setFollowUpMode(mode) {
565
+ this.followUpMode = mode;
566
+ }
567
+ getFollowUpMode() {
568
+ return this.followUpMode;
569
+ }
570
+ dequeueSteeringMessages() {
571
+ if (this.steeringMode === "one-at-a-time") {
572
+ if (this.steeringQueue.length > 0) {
573
+ const first = this.steeringQueue[0];
574
+ this.steeringQueue = this.steeringQueue.slice(1);
575
+ return [first];
576
+ }
577
+ return [];
578
+ }
579
+ const steering = this.steeringQueue.slice();
580
+ this.steeringQueue = [];
581
+ return steering;
582
+ }
583
+ dequeueFollowUpMessages() {
584
+ if (this.followUpMode === "one-at-a-time") {
585
+ if (this.followUpQueue.length > 0) {
586
+ const first = this.followUpQueue[0];
587
+ this.followUpQueue = this.followUpQueue.slice(1);
588
+ return [first];
589
+ }
590
+ return [];
591
+ }
592
+ const followUp = this.followUpQueue.slice();
593
+ this.followUpQueue = [];
594
+ return followUp;
595
+ }
596
+ abort() {
597
+ this.abortController?.abort();
598
+ }
599
+ waitForIdle() {
600
+ return this.runningPrompt ?? Promise.resolve();
601
+ }
602
+ reset() {
603
+ this._state.messages = [];
604
+ this._state.isStreaming = false;
605
+ this._state.streamMessage = null;
606
+ this._state.pendingToolCalls = /* @__PURE__ */ new Set();
607
+ this._state.error = void 0;
608
+ this.steeringQueue = [];
609
+ this.followUpQueue = [];
610
+ this.runningPrompt = void 0;
611
+ this.resolveRunningPrompt = void 0;
612
+ this.updateStatusSnapshot({
613
+ phase: "idle",
614
+ turnState: "completed",
615
+ activeToolName: void 0,
616
+ lastStopReason: void 0,
617
+ hasPendingToolContinuation: false
618
+ });
619
+ }
620
+ /**
621
+ * Reset only the processing state without clearing messages.
622
+ * Useful when resuming a session that was saved mid-processing.
623
+ */
624
+ resetProcessingState() {
625
+ this._state.isStreaming = false;
626
+ this._state.streamMessage = null;
627
+ this._state.pendingToolCalls = /* @__PURE__ */ new Set();
628
+ this._state.error = void 0;
629
+ this.steeringQueue = [];
630
+ this.followUpQueue = [];
631
+ this.runningPrompt = void 0;
632
+ this.resolveRunningPrompt = void 0;
633
+ this.abortController = void 0;
634
+ this.updateStatusSnapshot({
635
+ phase: "idle",
636
+ turnState: "completed",
637
+ activeToolName: void 0,
638
+ lastStopReason: void 0,
639
+ hasPendingToolContinuation: false
640
+ });
641
+ }
642
+ async prompt(input, images) {
643
+ if (this.runningPrompt) {
644
+ throw new Error("Agent is already processing a prompt.");
645
+ }
646
+ if (this._state.isStreaming) {
647
+ throw new Error("Agent is already processing a prompt.");
648
+ }
649
+ const promise = new Promise((resolve) => {
650
+ this.resolveRunningPrompt = resolve;
651
+ });
652
+ this.runningPrompt = promise;
653
+ try {
654
+ const model = this._state.model;
655
+ if (!model) throw new Error("No model configured");
656
+ let msgs;
657
+ if (Array.isArray(input)) {
658
+ if (input.length > 0 && input.every((item) => isUserContentBlock(item))) {
659
+ msgs = [{ role: "user", content: input, timestamp: Date.now() }];
660
+ } else if (input.every((item) => isAgentMessage(item))) {
661
+ msgs = input;
662
+ } else {
663
+ throw new Error("Invalid prompt array: expected user content blocks or agent messages.");
664
+ }
665
+ } else if (typeof input === "string") {
666
+ const content = [{ type: "text", text: input }];
667
+ if (images && images.length > 0) {
668
+ content.push(...images);
669
+ }
670
+ msgs = [{ role: "user", content, timestamp: Date.now() }];
671
+ } else {
672
+ msgs = [input];
673
+ }
674
+ await this._runLoop(msgs);
675
+ } finally {
676
+ if (this.runningPrompt === promise) {
677
+ this.resolveRunningPrompt?.();
678
+ this.runningPrompt = void 0;
679
+ this.resolveRunningPrompt = void 0;
680
+ }
681
+ }
682
+ }
683
+ async continue() {
684
+ if (this.runningPrompt) {
685
+ throw new Error("Agent is already processing.");
686
+ }
687
+ if (this._state.isStreaming) {
688
+ throw new Error("Agent is already processing.");
689
+ }
690
+ const promise = new Promise((resolve) => {
691
+ this.resolveRunningPrompt = resolve;
692
+ });
693
+ this.runningPrompt = promise;
694
+ try {
695
+ const messages = this._state.messages;
696
+ if (messages.length === 0) {
697
+ throw new Error("No messages to continue from");
698
+ }
699
+ if (messages[messages.length - 1].role === "assistant") {
700
+ const queuedSteering = this.dequeueSteeringMessages();
701
+ if (queuedSteering.length > 0) {
702
+ await this._runLoop(queuedSteering, {
703
+ skipInitialSteeringPoll: true,
704
+ suppressSteeringPolls: true,
705
+ suppressFollowUpPolls: true
706
+ });
707
+ return;
708
+ }
709
+ const queuedFollowUp = this.dequeueFollowUpMessages();
710
+ if (queuedFollowUp.length > 0) {
711
+ await this._runLoop(queuedFollowUp, {
712
+ suppressSteeringPolls: true,
713
+ suppressFollowUpPolls: true
714
+ });
715
+ return;
716
+ }
717
+ throw new Error("Cannot continue from message role: assistant");
718
+ }
719
+ await this._runLoop(void 0);
720
+ } finally {
721
+ if (this.runningPrompt === promise) {
722
+ this.resolveRunningPrompt?.();
723
+ this.runningPrompt = void 0;
724
+ this.resolveRunningPrompt = void 0;
725
+ }
726
+ }
727
+ }
728
+ async _runLoop(messages, options) {
729
+ const model = this._state.model;
730
+ if (!model) throw new Error("No model configured");
731
+ this.abortController = new AbortController();
732
+ this._state.isStreaming = true;
733
+ this._state.streamMessage = null;
734
+ this._state.error = void 0;
735
+ const reasoning = this._state.thinkingLevel === "off" ? void 0 : this._state.thinkingLevel;
736
+ const context = {
737
+ systemPrompt: this._state.systemPrompt,
738
+ messages: this._state.messages.slice(),
739
+ tools: this._state.tools
740
+ };
741
+ let skipInitialSteeringPoll = options?.skipInitialSteeringPoll === true;
742
+ const config = {
743
+ model,
744
+ reasoning,
745
+ apiKey: this._apiKey,
746
+ sessionId: this._sessionId,
747
+ thinkingBudgets: this._thinkingBudgets,
748
+ maxRetryDelayMs: this._maxRetryDelayMs,
749
+ convertToLlm: this.convertToLlm,
750
+ transformContext: this.transformContext,
751
+ getApiKey: this.getApiKey,
752
+ hooks: this.hooks,
753
+ getSteeringMessages: async () => {
754
+ if (options?.suppressSteeringPolls) {
755
+ return [];
756
+ }
757
+ if (skipInitialSteeringPoll) {
758
+ skipInitialSteeringPoll = false;
759
+ return [];
760
+ }
761
+ return this.dequeueSteeringMessages();
762
+ },
763
+ getFollowUpMessages: async () => {
764
+ if (options?.suppressFollowUpPolls) {
765
+ return [];
766
+ }
767
+ return this.dequeueFollowUpMessages();
768
+ }
769
+ };
770
+ let partial = null;
771
+ try {
772
+ const stream = messages ? agentLoop(messages, context, config, this.abortController.signal, this.streamFn) : agentLoopContinue(context, config, this.abortController.signal, this.streamFn);
773
+ for await (const event of stream) {
774
+ switch (event.type) {
775
+ case "message_start":
776
+ partial = event.message;
777
+ this._state.streamMessage = event.message;
778
+ break;
779
+ case "message_update":
780
+ partial = event.message;
781
+ this._state.streamMessage = event.message;
782
+ break;
783
+ case "message_end":
784
+ partial = null;
785
+ this._state.streamMessage = null;
786
+ this.appendMessage(event.message);
787
+ break;
788
+ case "tool_execution_start": {
789
+ const s = new Set(this._state.pendingToolCalls);
790
+ s.add(event.toolCallId);
791
+ this._state.pendingToolCalls = s;
792
+ break;
793
+ }
794
+ case "tool_execution_end": {
795
+ const s = new Set(this._state.pendingToolCalls);
796
+ s.delete(event.toolCallId);
797
+ this._state.pendingToolCalls = s;
798
+ break;
799
+ }
800
+ case "turn_end":
801
+ if (event.message.role === "assistant" && event.message.errorMessage) {
802
+ this._state.error = event.message.errorMessage;
803
+ }
804
+ break;
805
+ case "agent_end":
806
+ this._state.isStreaming = false;
807
+ this._state.streamMessage = null;
808
+ break;
809
+ }
810
+ this.emit(event);
811
+ }
812
+ if (partial && partial.role === "assistant" && partial.content?.length > 0) {
813
+ const onlyEmpty = !partial.content.some(
814
+ (c) => c.type === "thinking" && c.thinking?.trim().length > 0 || c.type === "text" && c.text?.trim().length > 0 || c.type === "toolCall" && c.name?.trim().length > 0
815
+ );
816
+ if (!onlyEmpty) {
817
+ this.appendMessage(partial);
818
+ }
819
+ }
820
+ } catch (err) {
821
+ const errorMsg = {
822
+ role: "assistant",
823
+ content: [{ type: "text", text: "" }],
824
+ api: model.api,
825
+ provider: model.provider,
826
+ model: model.id,
827
+ usage: {
828
+ input: 0,
829
+ output: 0,
830
+ cacheRead: 0,
831
+ cacheWrite: 0,
832
+ totalTokens: 0,
833
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
834
+ },
835
+ stopReason: this.abortController?.signal.aborted ? "aborted" : "error",
836
+ errorMessage: err?.message || String(err),
837
+ timestamp: Date.now()
838
+ };
839
+ this.appendMessage(errorMsg);
840
+ this._state.error = err?.message || String(err);
841
+ this.emit({ type: "agent_end", messages: [errorMsg] });
842
+ } finally {
843
+ this._state.isStreaming = false;
844
+ this._state.streamMessage = null;
845
+ this._state.pendingToolCalls = /* @__PURE__ */ new Set();
846
+ this.abortController = void 0;
847
+ }
848
+ }
849
+ reportObserverError(kind, eventType, error) {
850
+ console.warn(`[Agent] ${kind} failed while handling ${eventType}:`, error);
851
+ }
852
+ emit(e) {
853
+ for (const listener of this.listeners) {
854
+ try {
855
+ listener(e);
856
+ } catch (error) {
857
+ this.reportObserverError("agent event listener", e.type, error);
858
+ }
859
+ }
860
+ try {
861
+ this.emitDerivedSemanticEvents(e);
862
+ } catch (error) {
863
+ this.reportObserverError("derived semantic event", e.type, error);
864
+ }
865
+ }
866
+ emitSemantic(e) {
867
+ for (const listener of this.semanticListeners) {
868
+ try {
869
+ listener(e);
870
+ } catch (error) {
871
+ this.reportObserverError("semantic event listener", e.type, error);
872
+ }
873
+ }
874
+ }
875
+ updateStatusSnapshot(next) {
876
+ const snapshot = {
877
+ ...this.statusSnapshot,
878
+ ...next
879
+ };
880
+ if (snapshot.phase === this.statusSnapshot.phase && snapshot.turnState === this.statusSnapshot.turnState && snapshot.activeToolName === this.statusSnapshot.activeToolName && snapshot.lastStopReason === this.statusSnapshot.lastStopReason && snapshot.hasPendingToolContinuation === this.statusSnapshot.hasPendingToolContinuation) {
881
+ return;
882
+ }
883
+ this.statusSnapshot = snapshot;
884
+ this.emitSemantic({ type: "status_snapshot_changed", snapshot: { ...snapshot } });
885
+ }
886
+ emitDerivedSemanticEvents(event) {
887
+ switch (event.type) {
888
+ case "message_start":
889
+ if (event.message.role === "assistant") {
890
+ this.updateStatusSnapshot({
891
+ phase: "responding",
892
+ turnState: "continuing",
893
+ activeToolName: void 0,
894
+ lastStopReason: void 0,
895
+ hasPendingToolContinuation: false
896
+ });
897
+ this.emitSemantic({ type: "assistant_response_started", message: event.message });
898
+ }
899
+ break;
900
+ case "message_update":
901
+ if (event.message.role === "assistant" && event.assistantMessageEvent.type === "text_delta") {
902
+ this.updateStatusSnapshot({ phase: "responding" });
903
+ this.emitSemantic({
904
+ type: "assistant_response_delta",
905
+ message: event.message,
906
+ delta: event.assistantMessageEvent.delta
907
+ });
908
+ }
909
+ break;
910
+ case "message_end":
911
+ if (event.message.role === "assistant") {
912
+ this.emitSemantic({ type: "assistant_response_finished", message: event.message });
913
+ }
914
+ break;
915
+ case "tool_execution_start":
916
+ this.updateStatusSnapshot({
917
+ phase: "running_tool",
918
+ turnState: "continuing",
919
+ activeToolName: event.toolName,
920
+ lastStopReason: void 0,
921
+ hasPendingToolContinuation: false
922
+ });
923
+ this.emitSemantic({
924
+ type: "tool_started",
925
+ toolCallId: event.toolCallId,
926
+ toolName: event.toolName,
927
+ args: event.args
928
+ });
929
+ break;
930
+ case "tool_execution_update":
931
+ this.emitSemantic({
932
+ type: "tool_progressed",
933
+ toolCallId: event.toolCallId,
934
+ toolName: event.toolName,
935
+ args: event.args,
936
+ partialResult: event.partialResult
937
+ });
938
+ break;
939
+ case "tool_execution_end":
940
+ this.updateStatusSnapshot({
941
+ phase: this._state.isStreaming ? "responding" : "completed",
942
+ activeToolName: void 0
943
+ });
944
+ this.emitSemantic({
945
+ type: "tool_finished",
946
+ toolCallId: event.toolCallId,
947
+ toolName: event.toolName,
948
+ args: event.args,
949
+ result: event.result,
950
+ isError: event.isError
951
+ });
952
+ break;
953
+ case "turn_end": {
954
+ const stopReason = event.message.role === "assistant" ? event.message.stopReason : void 0;
955
+ const hasToolCalls = event.message.role === "assistant" && Array.isArray(event.message.content) && event.message.content.some((block) => block.type === "toolCall");
956
+ if (stopReason === "error" || stopReason === "aborted") {
957
+ this.updateStatusSnapshot({
958
+ phase: "failed",
959
+ turnState: "failed",
960
+ lastStopReason: stopReason,
961
+ hasPendingToolContinuation: false,
962
+ activeToolName: void 0
963
+ });
964
+ this.emitSemantic({
965
+ type: "turn_failed",
966
+ message: event.message,
967
+ toolResults: event.toolResults
968
+ });
969
+ } else if (hasToolCalls || stopReason === "toolUse") {
970
+ this.updateStatusSnapshot({
971
+ phase: "responding",
972
+ turnState: "continuing",
973
+ lastStopReason: stopReason,
974
+ hasPendingToolContinuation: true,
975
+ activeToolName: void 0
976
+ });
977
+ this.emitSemantic({
978
+ type: "turn_continues",
979
+ message: event.message,
980
+ toolResults: event.toolResults
981
+ });
982
+ } else {
983
+ this.updateStatusSnapshot({
984
+ phase: "completed",
985
+ turnState: "completed",
986
+ lastStopReason: stopReason,
987
+ hasPendingToolContinuation: false,
988
+ activeToolName: void 0
989
+ });
990
+ this.emitSemantic({
991
+ type: "turn_completed",
992
+ message: event.message,
993
+ toolResults: event.toolResults
994
+ });
995
+ }
996
+ break;
997
+ }
998
+ case "agent_end": {
999
+ const lastMessage = event.messages[event.messages.length - 1];
1000
+ if (lastMessage?.role === "assistant" && (lastMessage.stopReason === "error" || lastMessage.stopReason === "aborted")) {
1001
+ this.updateStatusSnapshot({
1002
+ phase: "failed",
1003
+ turnState: "failed",
1004
+ lastStopReason: lastMessage.stopReason,
1005
+ hasPendingToolContinuation: false,
1006
+ activeToolName: void 0
1007
+ });
1008
+ this.emitSemantic({
1009
+ type: "turn_failed",
1010
+ message: lastMessage,
1011
+ toolResults: []
1012
+ });
1013
+ }
1014
+ this.updateStatusSnapshot({
1015
+ phase: "idle",
1016
+ activeToolName: void 0,
1017
+ hasPendingToolContinuation: false
1018
+ });
1019
+ break;
1020
+ }
1021
+ }
1022
+ }
1023
+ };
1024
+
1025
+ export {
1026
+ agentLoop,
1027
+ agentLoopContinue,
1028
+ Agent
1029
+ };