@antipopp/agno-client 0.10.0 → 0.11.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 CHANGED
@@ -148,6 +148,18 @@ var ConfigManager = class {
148
148
  setParams(params) {
149
149
  this.updateField("params", params);
150
150
  }
151
+ /**
152
+ * Get global dependencies
153
+ */
154
+ getDependencies() {
155
+ return this.config.dependencies;
156
+ }
157
+ /**
158
+ * Set global dependencies
159
+ */
160
+ setDependencies(dependencies) {
161
+ this.updateField("dependencies", dependencies);
162
+ }
151
163
  /**
152
164
  * Get current entity ID (agent or team based on mode)
153
165
  */
@@ -215,6 +227,29 @@ var ConfigManager = class {
215
227
  }
216
228
  return new URLSearchParams(params);
217
229
  }
230
+ /**
231
+ * Build dependencies by merging global dependencies and per-request dependencies.
232
+ * Merge order (lowest to highest precedence):
233
+ * 1. Global dependencies from config
234
+ * 2. Per-request dependencies (overrides global)
235
+ *
236
+ * @param perRequestDependencies - Optional dependencies for this specific request
237
+ * @returns Merged dependencies object, or undefined if no dependencies are configured
238
+ */
239
+ buildDependencies(perRequestDependencies) {
240
+ const dependencies = {};
241
+ const globalDependencies = this.getDependencies();
242
+ if (globalDependencies) {
243
+ Object.assign(dependencies, globalDependencies);
244
+ }
245
+ if (perRequestDependencies) {
246
+ Object.assign(dependencies, perRequestDependencies);
247
+ }
248
+ if (Object.keys(dependencies).length === 0) {
249
+ return void 0;
250
+ }
251
+ return dependencies;
252
+ }
218
253
  };
219
254
 
220
255
  // src/managers/session-manager.ts
@@ -301,79 +336,271 @@ var SessionManager = class {
301
336
  convertRunsToMessages(runs) {
302
337
  const messages = [];
303
338
  for (const run of runs) {
304
- const timestamp = run.created_at ? new Date(run.created_at).getTime() / 1e3 : Math.floor(Date.now() / 1e3);
305
- if (run.run_input) {
306
- messages.push({
307
- role: "user",
308
- content: run.run_input,
309
- created_at: timestamp
310
- });
311
- }
312
- const toolCalls = [];
313
- if (run.tools && Array.isArray(run.tools)) {
314
- for (const tool of run.tools) {
315
- const toolObj = tool;
316
- const toolCall = {
317
- role: "tool",
318
- content: toolObj.content ?? "",
319
- tool_call_id: toolObj.tool_call_id ?? "",
320
- tool_name: toolObj.tool_name ?? "",
321
- tool_args: toolObj.tool_args ?? {},
322
- tool_call_error: toolObj.tool_call_error ?? false,
323
- metrics: toolObj.metrics ?? { time: 0 },
324
- created_at: timestamp
325
- };
326
- toolCalls.push(toolCall);
327
- }
328
- }
329
- if (run.reasoning_messages && Array.isArray(run.reasoning_messages)) {
330
- for (const msg of run.reasoning_messages) {
331
- const reasoningMsg = msg;
332
- if (reasoningMsg.role === "tool") {
333
- toolCalls.push({
334
- role: "tool",
335
- content: reasoningMsg.content ?? "",
336
- tool_call_id: reasoningMsg.tool_call_id ?? "",
337
- tool_name: reasoningMsg.tool_name ?? "",
338
- tool_args: reasoningMsg.tool_args ?? {},
339
- tool_call_error: reasoningMsg.tool_call_error ?? false,
340
- metrics: reasoningMsg.metrics ?? {
341
- time: 0
342
- },
343
- created_at: reasoningMsg.created_at ?? timestamp
344
- });
345
- }
346
- }
347
- }
348
- let contentStr = "";
349
- if (typeof run.content === "string") {
350
- contentStr = run.content;
351
- } else if (run.content && typeof run.content === "object") {
352
- contentStr = JSON.stringify(run.content);
339
+ const timestamp = this.getRunTimestamp(run);
340
+ const inputMedia = this.mergeInputMedia(
341
+ this.extractInputMedia(run.input_media),
342
+ this.extractInputMediaFromMessages(run.messages)
343
+ );
344
+ if (run.run_input || this.hasAnyMedia(inputMedia)) {
345
+ messages.push(
346
+ this.buildUserMessage(run.run_input ?? "", timestamp, inputMedia)
347
+ );
353
348
  }
354
- const extraData = run.reasoning_messages || run.reasoning_steps || run.references ? {
355
- reasoning_messages: run.reasoning_messages,
356
- reasoning_steps: run.reasoning_steps,
357
- references: run.references
358
- } : void 0;
349
+ const toolCalls = this.extractToolCalls(run, timestamp);
359
350
  messages.push({
360
351
  role: "agent",
361
- content: contentStr,
352
+ content: this.normalizeRunContent(run.content),
362
353
  tool_calls: toolCalls.length > 0 ? toolCalls : void 0,
363
- extra_data: extraData,
364
- images: run.images,
365
- videos: run.videos,
366
- audio: run.audio,
367
- response_audio: run.response_audio,
354
+ extra_data: this.buildExtraData(run),
355
+ images: this.normalizeImages(run.images),
356
+ videos: this.normalizeVideos(run.videos),
357
+ audio: this.normalizeAudio(run.audio),
358
+ files: this.normalizeFiles(run.files),
359
+ response_audio: run.response_audio ?? void 0,
368
360
  created_at: timestamp + 1
369
361
  // Agent response is slightly after user message
370
362
  });
371
363
  }
372
364
  return messages;
373
365
  }
366
+ getRunTimestamp(run) {
367
+ return run.created_at ? new Date(run.created_at).getTime() / 1e3 : Math.floor(Date.now() / 1e3);
368
+ }
369
+ buildUserMessage(content, createdAt, media) {
370
+ return {
371
+ role: "user",
372
+ content,
373
+ images: media?.images,
374
+ videos: media?.videos,
375
+ audio: media?.audio,
376
+ files: media?.files,
377
+ created_at: createdAt
378
+ };
379
+ }
380
+ hasAnyMedia(media) {
381
+ return Boolean(
382
+ media.images && media.images.length > 0 || media.videos && media.videos.length > 0 || media.audio && media.audio.length > 0 || media.files && media.files.length > 0
383
+ );
384
+ }
385
+ isRecord(value) {
386
+ return typeof value === "object" && value !== null;
387
+ }
388
+ getStringValue(record, key) {
389
+ const value = record[key];
390
+ return typeof value === "string" && value.length > 0 ? value : void 0;
391
+ }
392
+ getNumberOrStringValue(record, key) {
393
+ const value = record[key];
394
+ return typeof value === "number" || typeof value === "string" ? value : void 0;
395
+ }
396
+ buildDataUrl(content, mimeType, fallbackMimeType) {
397
+ const safeMimeType = mimeType || fallbackMimeType;
398
+ return `data:${safeMimeType};base64,${content}`;
399
+ }
400
+ normalizeImage(item) {
401
+ if (!this.isRecord(item)) {
402
+ return void 0;
403
+ }
404
+ const mimeType = this.getStringValue(item, "mime_type");
405
+ const content = this.getStringValue(item, "content");
406
+ const url = this.getStringValue(item, "url") || (content ? this.buildDataUrl(content, mimeType, "image/png") : void 0);
407
+ if (!url) {
408
+ return void 0;
409
+ }
410
+ return {
411
+ url,
412
+ revised_prompt: this.getStringValue(item, "revised_prompt"),
413
+ original_prompt: this.getStringValue(item, "original_prompt"),
414
+ alt_text: this.getStringValue(item, "alt_text"),
415
+ id: this.getStringValue(item, "id"),
416
+ mime_type: mimeType,
417
+ format: this.getStringValue(item, "format")
418
+ };
419
+ }
420
+ normalizeVideo(item) {
421
+ if (!this.isRecord(item)) {
422
+ return void 0;
423
+ }
424
+ const mimeType = this.getStringValue(item, "mime_type");
425
+ const content = this.getStringValue(item, "content");
426
+ const url = this.getStringValue(item, "url") || (content ? this.buildDataUrl(content, mimeType, "video/mp4") : void 0);
427
+ const normalized = {
428
+ url,
429
+ id: this.getNumberOrStringValue(item, "id"),
430
+ eta: this.getNumberOrStringValue(item, "eta"),
431
+ mime_type: mimeType,
432
+ format: this.getStringValue(item, "format"),
433
+ original_prompt: this.getStringValue(item, "original_prompt"),
434
+ revised_prompt: this.getStringValue(item, "revised_prompt"),
435
+ width: typeof item.width === "number" ? item.width : void 0,
436
+ height: typeof item.height === "number" ? item.height : void 0,
437
+ fps: typeof item.fps === "number" ? item.fps : void 0,
438
+ duration: typeof item.duration === "number" ? item.duration : void 0
439
+ };
440
+ if (normalized.url || normalized.id !== void 0 || normalized.eta !== void 0) {
441
+ return normalized;
442
+ }
443
+ return void 0;
444
+ }
445
+ normalizeAudioEntry(item) {
446
+ if (!this.isRecord(item)) {
447
+ return void 0;
448
+ }
449
+ const mimeType = this.getStringValue(item, "mime_type");
450
+ const content = this.getStringValue(item, "content");
451
+ const url = this.getStringValue(item, "url") || (content ? this.buildDataUrl(content, mimeType, "audio/wav") : void 0);
452
+ const normalized = {
453
+ base64_audio: this.getStringValue(item, "base64_audio"),
454
+ mime_type: mimeType,
455
+ url,
456
+ id: this.getNumberOrStringValue(item, "id"),
457
+ content,
458
+ channels: typeof item.channels === "number" ? item.channels : void 0,
459
+ sample_rate: typeof item.sample_rate === "number" ? item.sample_rate : void 0,
460
+ format: this.getStringValue(item, "format"),
461
+ duration: typeof item.duration === "number" ? item.duration : void 0
462
+ };
463
+ if (normalized.url || normalized.base64_audio || normalized.content || normalized.id !== void 0) {
464
+ return normalized;
465
+ }
466
+ return void 0;
467
+ }
468
+ normalizeFileEntry(item) {
469
+ if (!this.isRecord(item)) {
470
+ return void 0;
471
+ }
472
+ const normalized = {
473
+ id: this.getStringValue(item, "id"),
474
+ url: this.getStringValue(item, "url"),
475
+ filename: this.getStringValue(item, "filename"),
476
+ name: this.getStringValue(item, "name"),
477
+ mime_type: this.getStringValue(item, "mime_type"),
478
+ format: this.getStringValue(item, "format"),
479
+ size: typeof item.size === "number" ? item.size : void 0
480
+ };
481
+ if (normalized.url || normalized.filename || normalized.name || normalized.id) {
482
+ return normalized;
483
+ }
484
+ return void 0;
485
+ }
486
+ normalizeArray(value, normalizer) {
487
+ if (!Array.isArray(value)) {
488
+ return void 0;
489
+ }
490
+ const items = value.map((item) => normalizer(item)).filter((item) => item !== void 0);
491
+ return items.length > 0 ? items : void 0;
492
+ }
493
+ normalizeImages(value) {
494
+ return this.normalizeArray(value, (item) => this.normalizeImage(item));
495
+ }
496
+ normalizeVideos(value) {
497
+ return this.normalizeArray(value, (item) => this.normalizeVideo(item));
498
+ }
499
+ normalizeAudio(value) {
500
+ return this.normalizeArray(value, (item) => this.normalizeAudioEntry(item));
501
+ }
502
+ normalizeFiles(value) {
503
+ return this.normalizeArray(value, (item) => this.normalizeFileEntry(item));
504
+ }
505
+ extractInputMedia(inputMedia) {
506
+ if (!this.isRecord(inputMedia)) {
507
+ return {};
508
+ }
509
+ return {
510
+ images: this.normalizeImages(inputMedia.images),
511
+ videos: this.normalizeVideos(inputMedia.videos),
512
+ audio: this.normalizeAudio(inputMedia.audios ?? inputMedia.audio),
513
+ files: this.normalizeFiles(inputMedia.files)
514
+ };
515
+ }
516
+ extractInputMediaFromMessages(messages) {
517
+ if (!Array.isArray(messages)) {
518
+ return {};
519
+ }
520
+ for (const message of messages) {
521
+ if (!this.isRecord(message)) {
522
+ continue;
523
+ }
524
+ if (this.getStringValue(message, "role") !== "user") {
525
+ continue;
526
+ }
527
+ const media = {
528
+ images: this.normalizeImages(message.images ?? message.image),
529
+ videos: this.normalizeVideos(message.videos ?? message.video),
530
+ audio: this.normalizeAudio(message.audios ?? message.audio),
531
+ files: this.normalizeFiles(message.files)
532
+ };
533
+ if (this.hasAnyMedia(media)) {
534
+ return media;
535
+ }
536
+ }
537
+ return {};
538
+ }
539
+ mergeInputMedia(primary, fallback) {
540
+ return {
541
+ images: primary.images ?? fallback.images,
542
+ videos: primary.videos ?? fallback.videos,
543
+ audio: primary.audio ?? fallback.audio,
544
+ files: primary.files ?? fallback.files
545
+ };
546
+ }
547
+ normalizeRunContent(content) {
548
+ if (typeof content === "string") {
549
+ return content;
550
+ }
551
+ if (content && typeof content === "object") {
552
+ return JSON.stringify(content);
553
+ }
554
+ return "";
555
+ }
556
+ buildToolCall(rawTool, fallbackTimestamp) {
557
+ return {
558
+ role: "tool",
559
+ content: rawTool.content ?? "",
560
+ tool_call_id: rawTool.tool_call_id ?? "",
561
+ tool_name: rawTool.tool_name ?? "",
562
+ tool_args: rawTool.tool_args ?? {},
563
+ tool_call_error: rawTool.tool_call_error ?? false,
564
+ metrics: rawTool.metrics ?? { time: 0 },
565
+ created_at: rawTool.created_at ?? fallbackTimestamp
566
+ };
567
+ }
568
+ extractToolCalls(run, timestamp) {
569
+ const toolCalls = [];
570
+ if (run.tools && Array.isArray(run.tools)) {
571
+ for (const tool of run.tools) {
572
+ toolCalls.push(this.buildToolCall(tool, timestamp));
573
+ }
574
+ }
575
+ if (run.reasoning_messages && Array.isArray(run.reasoning_messages)) {
576
+ for (const message of run.reasoning_messages) {
577
+ if (message.role === "tool") {
578
+ toolCalls.push(this.buildToolCall(message, timestamp));
579
+ }
580
+ }
581
+ }
582
+ return toolCalls;
583
+ }
584
+ buildExtraData(run) {
585
+ if (!(run.reasoning_messages || run.reasoning_steps || run.references)) {
586
+ return void 0;
587
+ }
588
+ return {
589
+ reasoning_messages: run.reasoning_messages,
590
+ reasoning_steps: run.reasoning_steps,
591
+ references: run.references
592
+ };
593
+ }
374
594
  };
375
595
 
376
596
  // src/parsers/stream-parser.ts
597
+ var StreamResponseHttpError = class extends Error {
598
+ constructor(status, message) {
599
+ super(message);
600
+ this.name = "StreamResponseHttpError";
601
+ this.status = status;
602
+ }
603
+ };
377
604
  function isLegacyFormat(data) {
378
605
  return typeof data === "object" && data !== null && "event" in data && !("data" in data) && typeof data.event === "string";
379
606
  }
@@ -397,72 +624,139 @@ function convertNewFormatToLegacy(newFormatData) {
397
624
  function processChunk(chunk, onChunk) {
398
625
  onChunk(chunk);
399
626
  }
400
- function parseBuffer(buffer, onChunk) {
401
- let currentIndex = 0;
402
- let jsonStartIndex = buffer.indexOf("{", currentIndex);
403
- while (jsonStartIndex !== -1 && jsonStartIndex < buffer.length) {
404
- let braceCount = 0;
405
- let inString = false;
406
- let escapeNext = false;
407
- let jsonEndIndex = -1;
408
- let i = jsonStartIndex;
409
- for (; i < buffer.length; i++) {
410
- const char = buffer[i];
411
- if (inString) {
412
- if (escapeNext) {
413
- escapeNext = false;
414
- } else if (char === "\\") {
415
- escapeNext = true;
416
- } else if (char === '"') {
417
- inString = false;
418
- }
419
- } else if (char === '"') {
420
- inString = true;
421
- } else if (char === "{") {
422
- braceCount++;
423
- } else if (char === "}") {
424
- braceCount--;
425
- if (braceCount === 0) {
426
- jsonEndIndex = i;
427
- break;
428
- }
429
- }
627
+ function updateJsonParserState(char, state) {
628
+ if (state.inString) {
629
+ if (state.escapeNext) {
630
+ state.escapeNext = false;
631
+ return false;
430
632
  }
431
- if (jsonEndIndex !== -1) {
432
- const jsonString = buffer.slice(jsonStartIndex, jsonEndIndex + 1);
433
- try {
434
- const parsed = JSON.parse(jsonString);
435
- if (isLegacyFormat(parsed)) {
436
- processChunk(parsed, onChunk);
437
- } else {
438
- const legacyChunk = convertNewFormatToLegacy(parsed);
439
- processChunk(legacyChunk, onChunk);
440
- }
441
- } catch (error) {
442
- if (typeof process !== "undefined" && process.env?.NODE_ENV === "development") {
443
- console.error("Failed to parse JSON chunk:", {
444
- error,
445
- chunk: jsonString.substring(0, 100) + (jsonString.length > 100 ? "..." : ""),
446
- position: jsonStartIndex
447
- });
448
- }
449
- if (jsonString.length > 1e4) {
450
- throw new Error(
451
- `Failed to parse large JSON chunk at position ${jsonStartIndex}`
452
- );
453
- }
454
- jsonStartIndex = buffer.indexOf("{", jsonStartIndex + 1);
455
- continue;
456
- }
457
- currentIndex = jsonEndIndex + 1;
458
- buffer = buffer.slice(currentIndex).trim();
459
- currentIndex = 0;
460
- jsonStartIndex = buffer.indexOf("{", currentIndex);
633
+ if (char === "\\") {
634
+ state.escapeNext = true;
635
+ return false;
636
+ }
637
+ if (char === '"') {
638
+ state.inString = false;
639
+ }
640
+ return false;
641
+ }
642
+ if (char === '"') {
643
+ state.inString = true;
644
+ return false;
645
+ }
646
+ if (char === "{") {
647
+ state.braceCount++;
648
+ return false;
649
+ }
650
+ if (char !== "}") {
651
+ return false;
652
+ }
653
+ state.braceCount--;
654
+ return state.braceCount === 0;
655
+ }
656
+ function findJsonSlice(buffer, fromIndex) {
657
+ const startIndex = buffer.indexOf("{", fromIndex);
658
+ if (startIndex === -1) {
659
+ return void 0;
660
+ }
661
+ const parserState = {
662
+ braceCount: 0,
663
+ inString: false,
664
+ escapeNext: false
665
+ };
666
+ for (let i = startIndex; i < buffer.length; i++) {
667
+ const isJsonComplete = updateJsonParserState(buffer[i], parserState);
668
+ if (isJsonComplete) {
669
+ return { startIndex, endIndex: i };
670
+ }
671
+ }
672
+ return void 0;
673
+ }
674
+ function logChunkParseError(error, jsonString, position) {
675
+ if (typeof process === "undefined" || process.env?.NODE_ENV !== "development") {
676
+ return;
677
+ }
678
+ console.error("Failed to parse JSON chunk:", {
679
+ error,
680
+ chunk: jsonString.substring(0, 100) + (jsonString.length > 100 ? "..." : ""),
681
+ position
682
+ });
683
+ }
684
+ function processJsonSlice(jsonString, startIndex, onChunk) {
685
+ try {
686
+ const parsed = JSON.parse(jsonString);
687
+ if (isLegacyFormat(parsed)) {
688
+ processChunk(parsed, onChunk);
461
689
  } else {
690
+ processChunk(convertNewFormatToLegacy(parsed), onChunk);
691
+ }
692
+ return true;
693
+ } catch (error) {
694
+ logChunkParseError(error, jsonString, startIndex);
695
+ if (jsonString.length > 1e4) {
696
+ throw new Error(
697
+ `Failed to parse large JSON chunk at position ${startIndex}`
698
+ );
699
+ }
700
+ return false;
701
+ }
702
+ }
703
+ function parseBuffer(buffer, onChunk) {
704
+ let searchIndex = 0;
705
+ let remainingBuffer = buffer;
706
+ while (true) {
707
+ const jsonSlice = findJsonSlice(remainingBuffer, searchIndex);
708
+ if (!jsonSlice) {
462
709
  break;
463
710
  }
711
+ const jsonString = remainingBuffer.slice(
712
+ jsonSlice.startIndex,
713
+ jsonSlice.endIndex + 1
714
+ );
715
+ const parsed = processJsonSlice(jsonString, jsonSlice.startIndex, onChunk);
716
+ if (!parsed) {
717
+ searchIndex = jsonSlice.startIndex + 1;
718
+ continue;
719
+ }
720
+ remainingBuffer = remainingBuffer.slice(jsonSlice.endIndex + 1).trim();
721
+ searchIndex = 0;
722
+ }
723
+ return remainingBuffer;
724
+ }
725
+ function buildRequestHeaders(headers, requestBody) {
726
+ return {
727
+ ...!(requestBody instanceof FormData) && {
728
+ "Content-Type": "application/json"
729
+ },
730
+ ...headers
731
+ };
732
+ }
733
+ async function buildErrorMessage(response) {
734
+ const defaultMessage = `HTTP ${response.status}: ${response.statusText}`;
735
+ const contentType = response.headers.get("content-type");
736
+ if (!contentType?.includes("application/json")) {
737
+ return defaultMessage;
738
+ }
739
+ try {
740
+ const errorData = await response.json();
741
+ return errorData.detail || errorData.message || defaultMessage;
742
+ } catch {
743
+ return defaultMessage;
744
+ }
745
+ }
746
+ async function consumeResponseStream(body, onChunk, onComplete) {
747
+ const reader = body.getReader();
748
+ const decoder = new TextDecoder();
749
+ let buffer = "";
750
+ while (true) {
751
+ const { done, value } = await reader.read();
752
+ if (done) {
753
+ parseBuffer(buffer, onChunk);
754
+ onComplete();
755
+ return;
756
+ }
757
+ buffer += decoder.decode(value, { stream: true });
758
+ buffer = parseBuffer(buffer, onChunk);
464
759
  }
465
- return buffer;
466
760
  }
467
761
  async function streamResponse(options) {
468
762
  const {
@@ -475,54 +769,30 @@ async function streamResponse(options) {
475
769
  onComplete,
476
770
  signal
477
771
  } = options;
478
- let buffer = "";
479
772
  const finalUrl = params?.toString() ? `${apiUrl}?${params.toString()}` : apiUrl;
480
773
  try {
481
774
  const response = await fetch(finalUrl, {
482
775
  method: "POST",
483
- headers: {
484
- ...!(requestBody instanceof FormData) && {
485
- "Content-Type": "application/json"
486
- },
487
- ...headers
488
- },
776
+ headers: buildRequestHeaders(headers, requestBody),
489
777
  body: requestBody instanceof FormData ? requestBody : JSON.stringify(requestBody),
490
778
  signal
491
779
  });
492
780
  if (!response.ok) {
493
- let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
494
- const contentType = response.headers.get("content-type");
495
- if (contentType?.includes("application/json")) {
496
- try {
497
- const errorData = await response.json();
498
- errorMessage = errorData.detail || errorData.message || errorMessage;
499
- } catch {
500
- }
501
- }
502
- throw new Error(errorMessage);
781
+ const errorMessage = await buildErrorMessage(response);
782
+ throw new StreamResponseHttpError(response.status, errorMessage);
503
783
  }
504
784
  if (!response.body) {
505
785
  throw new Error("No response body");
506
786
  }
507
- const reader = response.body.getReader();
508
- const decoder = new TextDecoder();
509
- const processStream = async () => {
510
- while (true) {
511
- const { done, value } = await reader.read();
512
- if (done) {
513
- buffer = parseBuffer(buffer, onChunk);
514
- onComplete();
515
- return;
516
- }
517
- buffer += decoder.decode(value, { stream: true });
518
- buffer = parseBuffer(buffer, onChunk);
519
- }
520
- };
521
- await processStream();
787
+ await consumeResponseStream(response.body, onChunk, onComplete);
522
788
  } catch (error) {
523
789
  if (error instanceof Error && error.name === "AbortError") {
524
790
  return;
525
791
  }
792
+ if (error instanceof Error) {
793
+ onError(error);
794
+ return;
795
+ }
526
796
  if (typeof error === "object" && error !== null && "detail" in error) {
527
797
  onError(new Error(String(error.detail)));
528
798
  } else {
@@ -554,10 +824,15 @@ function processToolCall(toolCall, prevToolCalls = []) {
554
824
  );
555
825
  if (existingToolCallIndex >= 0) {
556
826
  const updatedToolCalls = [...prevToolCalls];
557
- updatedToolCalls[existingToolCallIndex] = {
558
- ...updatedToolCalls[existingToolCallIndex],
559
- ...toolCall
560
- };
827
+ const existing = updatedToolCalls[existingToolCallIndex];
828
+ const merged = { ...existing, ...toolCall };
829
+ if (existing.external_execution && existing.result !== void 0) {
830
+ merged.result = existing.result;
831
+ }
832
+ if (existing.ui_component !== void 0 && !toolCall.ui_component) {
833
+ merged.ui_component = existing.ui_component;
834
+ }
835
+ updatedToolCalls[existingToolCallIndex] = merged;
561
836
  return updatedToolCalls;
562
837
  }
563
838
  return [...prevToolCalls, toolCall];
@@ -578,6 +853,105 @@ var EventProcessor = class {
578
853
  constructor() {
579
854
  this.lastContent = "";
580
855
  }
856
+ appendRunContent(chunk, updatedMessage) {
857
+ if (typeof chunk.content === "string") {
858
+ const uniqueContent = chunk.content.replace(this.lastContent, "");
859
+ updatedMessage.content = updatedMessage.content + uniqueContent;
860
+ this.lastContent = chunk.content;
861
+ return;
862
+ }
863
+ if (typeof chunk.content !== "string" && chunk.content !== null) {
864
+ const jsonBlock = getJsonMarkdown(chunk.content);
865
+ updatedMessage.content = updatedMessage.content + jsonBlock;
866
+ this.lastContent = jsonBlock;
867
+ }
868
+ }
869
+ applyRunContentFields(chunk, lastMessage, updatedMessage) {
870
+ this.appendRunContent(chunk, updatedMessage);
871
+ updatedMessage.tool_calls = processChunkToolCalls(
872
+ chunk,
873
+ lastMessage.tool_calls
874
+ );
875
+ if (chunk.extra_data?.reasoning_steps) {
876
+ updatedMessage.extra_data = {
877
+ ...updatedMessage.extra_data,
878
+ reasoning_steps: chunk.extra_data.reasoning_steps
879
+ };
880
+ }
881
+ if (chunk.extra_data?.references) {
882
+ updatedMessage.extra_data = {
883
+ ...updatedMessage.extra_data,
884
+ references: chunk.extra_data.references
885
+ };
886
+ }
887
+ updatedMessage.created_at = chunk.created_at ?? lastMessage.created_at;
888
+ if (chunk.images) {
889
+ updatedMessage.images = chunk.images;
890
+ }
891
+ if (chunk.image) {
892
+ const existingImages = updatedMessage.images ?? lastMessage.images ?? [];
893
+ const hasImage = existingImages.some((image) => {
894
+ if (image.id && chunk.image?.id) {
895
+ return image.id === chunk.image.id;
896
+ }
897
+ return image.url === chunk.image?.url;
898
+ });
899
+ if (!hasImage) {
900
+ updatedMessage.images = [...existingImages, chunk.image];
901
+ }
902
+ }
903
+ if (chunk.videos) {
904
+ updatedMessage.videos = chunk.videos;
905
+ }
906
+ if (chunk.audio) {
907
+ updatedMessage.audio = chunk.audio;
908
+ }
909
+ if (chunk.files) {
910
+ updatedMessage.files = chunk.files;
911
+ }
912
+ if (chunk.response_audio?.transcript && typeof chunk.response_audio.transcript === "string") {
913
+ updatedMessage.response_audio = {
914
+ ...updatedMessage.response_audio,
915
+ transcript: (updatedMessage.response_audio?.transcript || "") + chunk.response_audio.transcript
916
+ };
917
+ }
918
+ }
919
+ applyCompletedFields(chunk, lastMessage, updatedMessage) {
920
+ let updatedContent;
921
+ if (typeof chunk.content === "string") {
922
+ updatedContent = chunk.content;
923
+ } else {
924
+ try {
925
+ updatedContent = JSON.stringify(chunk.content);
926
+ } catch {
927
+ updatedContent = "Error parsing response";
928
+ }
929
+ }
930
+ updatedMessage.content = updatedContent;
931
+ updatedMessage.tool_calls = processChunkToolCalls(
932
+ chunk,
933
+ lastMessage.tool_calls
934
+ );
935
+ const completedImages = chunk.images ?? (chunk.image ? [chunk.image] : void 0);
936
+ updatedMessage.images = completedImages ?? lastMessage.images;
937
+ updatedMessage.videos = chunk.videos ?? lastMessage.videos;
938
+ updatedMessage.audio = chunk.audio ?? lastMessage.audio;
939
+ updatedMessage.files = chunk.files ?? lastMessage.files;
940
+ updatedMessage.response_audio = chunk.response_audio;
941
+ updatedMessage.created_at = chunk.created_at ?? lastMessage.created_at;
942
+ updatedMessage.extra_data = {
943
+ reasoning_steps: chunk.extra_data?.reasoning_steps ?? lastMessage.extra_data?.reasoning_steps,
944
+ references: chunk.extra_data?.references ?? lastMessage.extra_data?.references
945
+ };
946
+ }
947
+ appendReasoningSteps(chunk, lastMessage, updatedMessage) {
948
+ const existingSteps = lastMessage.extra_data?.reasoning_steps ?? [];
949
+ const incomingSteps = chunk.extra_data?.reasoning_steps ?? [];
950
+ updatedMessage.extra_data = {
951
+ ...updatedMessage.extra_data,
952
+ reasoning_steps: [...existingSteps, ...incomingSteps]
953
+ };
954
+ }
581
955
  /**
582
956
  * Process a chunk and update the last message
583
957
  */
@@ -604,57 +978,11 @@ var EventProcessor = class {
604
978
  break;
605
979
  case RunEventEnum.RunContent:
606
980
  case RunEventEnum.TeamRunContent:
607
- if (typeof chunk.content === "string") {
608
- const uniqueContent = chunk.content.replace(this.lastContent, "");
609
- updatedMessage.content = updatedMessage.content + uniqueContent;
610
- this.lastContent = chunk.content;
611
- } else if (typeof chunk.content !== "string" && chunk.content !== null) {
612
- const jsonBlock = getJsonMarkdown(chunk.content);
613
- updatedMessage.content = updatedMessage.content + jsonBlock;
614
- this.lastContent = jsonBlock;
615
- }
616
- updatedMessage.tool_calls = processChunkToolCalls(
617
- chunk,
618
- lastMessage.tool_calls
619
- );
620
- if (chunk.extra_data?.reasoning_steps) {
621
- updatedMessage.extra_data = {
622
- ...updatedMessage.extra_data,
623
- reasoning_steps: chunk.extra_data.reasoning_steps
624
- };
625
- }
626
- if (chunk.extra_data?.references) {
627
- updatedMessage.extra_data = {
628
- ...updatedMessage.extra_data,
629
- references: chunk.extra_data.references
630
- };
631
- }
632
- updatedMessage.created_at = chunk.created_at ?? lastMessage.created_at;
633
- if (chunk.images) {
634
- updatedMessage.images = chunk.images;
635
- }
636
- if (chunk.videos) {
637
- updatedMessage.videos = chunk.videos;
638
- }
639
- if (chunk.audio) {
640
- updatedMessage.audio = chunk.audio;
641
- }
642
- if (chunk.response_audio?.transcript && typeof chunk.response_audio.transcript === "string") {
643
- const transcript = chunk.response_audio.transcript;
644
- updatedMessage.response_audio = {
645
- ...updatedMessage.response_audio,
646
- transcript: (updatedMessage.response_audio?.transcript || "") + transcript
647
- };
648
- }
981
+ this.applyRunContentFields(chunk, lastMessage, updatedMessage);
649
982
  break;
650
983
  case RunEventEnum.ReasoningStep:
651
984
  case RunEventEnum.TeamReasoningStep: {
652
- const existingSteps = lastMessage.extra_data?.reasoning_steps ?? [];
653
- const incomingSteps = chunk.extra_data?.reasoning_steps ?? [];
654
- updatedMessage.extra_data = {
655
- ...updatedMessage.extra_data,
656
- reasoning_steps: [...existingSteps, ...incomingSteps]
657
- };
985
+ this.appendReasoningSteps(chunk, lastMessage, updatedMessage);
658
986
  break;
659
987
  }
660
988
  case RunEventEnum.ReasoningCompleted:
@@ -668,29 +996,7 @@ var EventProcessor = class {
668
996
  break;
669
997
  case RunEventEnum.RunCompleted:
670
998
  case RunEventEnum.TeamRunCompleted: {
671
- let updatedContent;
672
- if (typeof chunk.content === "string") {
673
- updatedContent = chunk.content;
674
- } else {
675
- try {
676
- updatedContent = JSON.stringify(chunk.content);
677
- } catch {
678
- updatedContent = "Error parsing response";
679
- }
680
- }
681
- updatedMessage.content = updatedContent;
682
- updatedMessage.tool_calls = processChunkToolCalls(
683
- chunk,
684
- lastMessage.tool_calls
685
- );
686
- updatedMessage.images = chunk.images ?? lastMessage.images;
687
- updatedMessage.videos = chunk.videos ?? lastMessage.videos;
688
- updatedMessage.response_audio = chunk.response_audio;
689
- updatedMessage.created_at = chunk.created_at ?? lastMessage.created_at;
690
- updatedMessage.extra_data = {
691
- reasoning_steps: chunk.extra_data?.reasoning_steps ?? lastMessage.extra_data?.reasoning_steps,
692
- references: chunk.extra_data?.references ?? lastMessage.extra_data?.references
693
- };
999
+ this.applyCompletedFields(chunk, lastMessage, updatedMessage);
694
1000
  break;
695
1001
  }
696
1002
  case RunEventEnum.UpdatingMemory:
@@ -704,6 +1010,8 @@ var EventProcessor = class {
704
1010
  case RunEventEnum.TeamRunCancelled:
705
1011
  updatedMessage.streamingError = true;
706
1012
  break;
1013
+ default:
1014
+ break;
707
1015
  }
708
1016
  return updatedMessage;
709
1017
  }
@@ -831,36 +1139,36 @@ function sanitizeObject(obj) {
831
1139
  function isDevelopment() {
832
1140
  return typeof process !== "undefined" && process.env?.NODE_ENV === "development";
833
1141
  }
834
- var Logger = class {
1142
+ var Logger = {
835
1143
  /**
836
1144
  * Log debug information (only in development)
837
1145
  */
838
- static debug(message, data) {
1146
+ debug(message, data) {
839
1147
  if (isDevelopment()) {
840
1148
  const sanitized = data ? sanitizeObject(data) : void 0;
841
1149
  console.debug(`[DEBUG] ${message}`, sanitized || "");
842
1150
  }
843
- }
1151
+ },
844
1152
  /**
845
1153
  * Log informational messages (only in development)
846
1154
  */
847
- static info(message, data) {
1155
+ info(message, data) {
848
1156
  if (isDevelopment()) {
849
1157
  const sanitized = data ? sanitizeObject(data) : void 0;
850
1158
  console.info(`[INFO] ${message}`, sanitized || "");
851
1159
  }
852
- }
1160
+ },
853
1161
  /**
854
1162
  * Log warnings (always logs)
855
1163
  */
856
- static warn(message, data) {
1164
+ warn(message, data) {
857
1165
  const sanitized = data ? sanitizeObject(data) : void 0;
858
1166
  console.warn(`[WARN] ${message}`, sanitized || "");
859
- }
1167
+ },
860
1168
  /**
861
1169
  * Log errors (always logs)
862
1170
  */
863
- static error(message, data) {
1171
+ error(message, data) {
864
1172
  const sanitized = data ? sanitizeObject(data) : void 0;
865
1173
  console.error(`[ERROR] ${message}`, sanitized || "");
866
1174
  }
@@ -878,16 +1186,100 @@ function toSafeISOString(timestamp) {
878
1186
  }
879
1187
  return new Date(ts).toISOString();
880
1188
  }
1189
+ function getFileName(file, index) {
1190
+ if ("name" in file && typeof file.name === "string" && file.name) {
1191
+ return file.name;
1192
+ }
1193
+ return `file-${index}`;
1194
+ }
1195
+ function getFileFormat(mimeType) {
1196
+ if (!mimeType) {
1197
+ return void 0;
1198
+ }
1199
+ const [, subtype] = mimeType.split("/");
1200
+ if (!subtype) {
1201
+ return void 0;
1202
+ }
1203
+ return subtype.split(";")[0]?.trim().toLowerCase() || void 0;
1204
+ }
1205
+ function createPreviewUrl(file) {
1206
+ if (typeof URL === "undefined" || typeof URL.createObjectURL !== "function") {
1207
+ return void 0;
1208
+ }
1209
+ try {
1210
+ return URL.createObjectURL(file);
1211
+ } catch {
1212
+ return void 0;
1213
+ }
1214
+ }
1215
+ function buildMessageMediaPayload(files) {
1216
+ const images = [];
1217
+ const videos = [];
1218
+ const audio = [];
1219
+ const fileAttachments = [];
1220
+ const objectUrls = [];
1221
+ files.forEach((file, index) => {
1222
+ const filename = getFileName(file, index);
1223
+ const mimeType = file.type || "application/octet-stream";
1224
+ const format = getFileFormat(mimeType);
1225
+ const previewUrl = createPreviewUrl(file);
1226
+ if (previewUrl) {
1227
+ objectUrls.push(previewUrl);
1228
+ }
1229
+ if (mimeType.startsWith("image/") && previewUrl) {
1230
+ images.push({
1231
+ url: previewUrl,
1232
+ mime_type: mimeType,
1233
+ format
1234
+ });
1235
+ return;
1236
+ }
1237
+ if (mimeType.startsWith("video/")) {
1238
+ videos.push({
1239
+ url: previewUrl,
1240
+ id: filename,
1241
+ mime_type: mimeType,
1242
+ format
1243
+ });
1244
+ return;
1245
+ }
1246
+ if (mimeType.startsWith("audio/")) {
1247
+ audio.push({
1248
+ url: previewUrl,
1249
+ id: filename,
1250
+ mime_type: mimeType,
1251
+ format
1252
+ });
1253
+ return;
1254
+ }
1255
+ fileAttachments.push({
1256
+ filename,
1257
+ name: filename,
1258
+ mime_type: mimeType,
1259
+ format,
1260
+ size: file.size,
1261
+ url: previewUrl
1262
+ });
1263
+ });
1264
+ return {
1265
+ images: images.length > 0 ? images : void 0,
1266
+ videos: videos.length > 0 ? videos : void 0,
1267
+ audio: audio.length > 0 ? audio : void 0,
1268
+ files: fileAttachments.length > 0 ? fileAttachments : void 0,
1269
+ objectUrls
1270
+ };
1271
+ }
881
1272
  var AgnoClient = class extends EventEmitter {
882
1273
  constructor(config) {
883
1274
  super();
884
- // toolCallId -> UIComponentSpec
885
1275
  this.runCompletedSuccessfully = false;
1276
+ this.currentAbortController = null;
886
1277
  this.messageStore = new MessageStore();
887
1278
  this.configManager = new ConfigManager(config);
888
1279
  this.sessionManager = new SessionManager();
889
1280
  this.eventProcessor = new EventProcessor();
890
1281
  this.pendingUISpecs = /* @__PURE__ */ new Map();
1282
+ this.localAttachmentUrls = /* @__PURE__ */ new Set();
891
1283
  this.state = {
892
1284
  isStreaming: false,
893
1285
  isRefreshing: false,
@@ -930,6 +1322,8 @@ var AgnoClient = class extends EventEmitter {
930
1322
  * Clear all messages
931
1323
  */
932
1324
  clearMessages() {
1325
+ this.revokeAttachmentUrlsFromMessages(this.messageStore.getMessages());
1326
+ this.localAttachmentUrls.clear();
933
1327
  this.messageStore.clear();
934
1328
  this.configManager.setSessionId(void 0);
935
1329
  this.pendingUISpecs.clear();
@@ -943,42 +1337,84 @@ var AgnoClient = class extends EventEmitter {
943
1337
  if (!(this.state.isStreaming || this.state.isPaused)) {
944
1338
  throw new Error("No active or paused run to cancel");
945
1339
  }
1340
+ const runId = this.state.pausedRunId || this.currentRunId;
1341
+ if (this.currentAbortController) {
1342
+ this.currentAbortController.abort();
1343
+ this.currentAbortController = null;
1344
+ }
1345
+ this.state.isCancelling = true;
1346
+ this.emit("state:change", this.getState());
1347
+ const cancelErrorMessage = runId ? await this.requestBackendCancel(runId) : this.logMissingRunIdForCancel();
1348
+ this.state.isStreaming = false;
1349
+ this.state.isPaused = false;
1350
+ this.state.isCancelling = false;
1351
+ this.state.pausedRunId = void 0;
1352
+ this.state.toolsAwaitingExecution = void 0;
1353
+ this.currentRunId = void 0;
1354
+ if (cancelErrorMessage) {
1355
+ this.state.errorMessage = cancelErrorMessage;
1356
+ this.emit("message:error", cancelErrorMessage);
1357
+ }
1358
+ this.emit("run:cancelled", { runId });
1359
+ this.emit("state:change", this.getState());
1360
+ }
1361
+ logMissingRunIdForCancel() {
1362
+ Logger.warn(
1363
+ "[AgnoClient] No run ID available, skipping backend cancel request"
1364
+ );
1365
+ return void 0;
1366
+ }
1367
+ async requestBackendCancel(runId) {
946
1368
  const runUrl = this.configManager.getRunUrl();
947
1369
  if (!runUrl) {
948
- throw new Error("No agent or team selected");
949
- }
950
- const runId = this.state.pausedRunId || this.currentRunId;
951
- if (!runId) {
952
- throw new Error("No run ID available to cancel");
1370
+ const message = "Run cancelled locally, but backend cancel could not be sent: no agent or team selected";
1371
+ Logger.warn(`[AgnoClient] ${message}`);
1372
+ return message;
953
1373
  }
954
1374
  const cancelUrl = `${runUrl}/${runId}/cancel`;
955
1375
  const headers = this.configManager.buildRequestHeaders();
956
- this.state.isCancelling = true;
957
- this.emit("state:change", this.getState());
958
1376
  try {
959
1377
  const response = await fetch(cancelUrl, {
960
1378
  method: "POST",
961
1379
  headers
962
1380
  });
963
- if (!response.ok) {
964
- throw new Error("Failed to cancel run");
1381
+ if (response.ok) {
1382
+ return void 0;
965
1383
  }
966
- this.state.isStreaming = false;
967
- this.state.isPaused = false;
968
- this.state.isCancelling = false;
969
- this.state.pausedRunId = void 0;
970
- this.state.toolsAwaitingExecution = void 0;
971
- this.currentRunId = void 0;
972
- this.emit("run:cancelled", { runId });
973
- this.emit("state:change", this.getState());
974
- } catch (error) {
975
- this.state.isCancelling = false;
976
- this.emit("state:change", this.getState());
977
- throw new Error(
978
- `Error cancelling run: ${error instanceof Error ? error.message : String(error)}`
1384
+ if (response.status === 401 || response.status === 403) {
1385
+ const message = `Run cancelled locally, but backend cancel was rejected (${response.status})`;
1386
+ Logger.warn(`[AgnoClient] ${message}`);
1387
+ return message;
1388
+ }
1389
+ Logger.warn(
1390
+ `[AgnoClient] Backend cancel returned ${response.status} \u2014 run may have already completed`
979
1391
  );
1392
+ return void 0;
1393
+ } catch (error) {
1394
+ const reason = error instanceof Error ? error.message : String(error);
1395
+ const message = `Run cancelled locally, but backend cancel failed: ${reason}`;
1396
+ Logger.warn(`[AgnoClient] ${message}`);
1397
+ return message;
980
1398
  }
981
1399
  }
1400
+ /**
1401
+ * Abort the active stream without calling the backend cancel endpoint.
1402
+ * Since streamResponse handles AbortError by returning silently
1403
+ * (no onComplete/onError called), state cleanup is done here.
1404
+ */
1405
+ abortStream() {
1406
+ if (!this.state.isStreaming) {
1407
+ return;
1408
+ }
1409
+ if (this.currentAbortController) {
1410
+ this.currentAbortController.abort();
1411
+ this.currentAbortController = null;
1412
+ }
1413
+ this.state.isStreaming = false;
1414
+ this.currentRunId = void 0;
1415
+ this.emit("stream:end");
1416
+ this.emit("state:change", this.getState());
1417
+ }
982
1418
  /**
983
1419
  * Send a message to the agent/team (streaming)
984
1420
  */
@@ -999,16 +1435,30 @@ var AgnoClient = class extends EventEmitter {
999
1435
  if (typeof message === "string") {
1000
1436
  formData.append("message", message);
1001
1437
  }
1438
+ if (options?.files) {
1439
+ options.files.forEach((file, index) => {
1440
+ formData.append("files", file, getFileName(file, index));
1441
+ });
1442
+ }
1443
+ const requestFiles = formData.getAll("files").filter((entry) => typeof entry !== "string");
1444
+ const userMessageMedia = buildMessageMediaPayload(requestFiles);
1445
+ this.trackAttachmentUrls(userMessageMedia.objectUrls);
1446
+ const userMessageContent = String(formData.get("message") ?? "");
1002
1447
  const lastMessage = this.messageStore.getLastMessage();
1003
1448
  if (lastMessage?.streamingError) {
1004
1449
  const secondLast = this.messageStore.getMessages()[this.messageStore.getMessages().length - 2];
1005
1450
  if (secondLast?.role === "user") {
1451
+ this.revokeAttachmentUrlsFromMessages([secondLast, lastMessage]);
1006
1452
  this.messageStore.removeLastMessages(2);
1007
1453
  }
1008
1454
  }
1009
1455
  this.messageStore.addMessage({
1010
1456
  role: "user",
1011
- content: formData.get("message"),
1457
+ content: userMessageContent,
1458
+ images: userMessageMedia.images,
1459
+ videos: userMessageMedia.videos,
1460
+ audio: userMessageMedia.audio,
1461
+ files: userMessageMedia.files,
1012
1462
  created_at: Math.floor(Date.now() / 1e3)
1013
1463
  });
1014
1464
  this.messageStore.addMessage({
@@ -1022,34 +1472,40 @@ var AgnoClient = class extends EventEmitter {
1022
1472
  this.eventProcessor.reset();
1023
1473
  let newSessionId = this.configManager.getSessionId();
1024
1474
  try {
1025
- formData.append("stream", "true");
1026
- formData.append("session_id", newSessionId ?? "");
1475
+ formData.set("stream", "true");
1476
+ formData.set("session_id", newSessionId ?? "");
1027
1477
  const userId = this.configManager.getUserId();
1028
1478
  if (userId) {
1029
- formData.append("user_id", userId);
1479
+ formData.set("user_id", userId);
1480
+ }
1481
+ const dependencies = this.configManager.buildDependencies(
1482
+ options?.dependencies
1483
+ );
1484
+ if (dependencies) {
1485
+ formData.set("dependencies", JSON.stringify(dependencies));
1030
1486
  }
1031
1487
  const headers = this.configManager.buildRequestHeaders(options?.headers);
1032
1488
  const params = this.configManager.buildQueryString(options?.params);
1489
+ this.currentAbortController = new AbortController();
1033
1490
  await streamResponse({
1034
1491
  apiUrl: runUrl,
1035
1492
  headers,
1036
1493
  params,
1037
1494
  requestBody: formData,
1495
+ signal: this.currentAbortController.signal,
1038
1496
  onChunk: (chunk) => {
1039
- this.handleChunk(
1040
- chunk,
1041
- newSessionId,
1042
- formData.get("message")
1043
- );
1497
+ this.handleChunk(chunk, newSessionId, userMessageContent);
1044
1498
  if ((chunk.event === RunEvent.RunStarted || chunk.event === RunEvent.TeamRunStarted || chunk.event === RunEvent.ReasoningStarted || chunk.event === RunEvent.TeamReasoningStarted) && chunk.session_id) {
1045
1499
  newSessionId = chunk.session_id;
1046
1500
  this.configManager.setSessionId(chunk.session_id);
1047
1501
  }
1048
1502
  },
1049
1503
  onError: (error) => {
1504
+ this.currentAbortController = null;
1050
1505
  this.handleError(error, newSessionId);
1051
1506
  },
1052
1507
  onComplete: async () => {
1508
+ this.currentAbortController = null;
1053
1509
  this.state.isStreaming = false;
1054
1510
  this.currentRunId = void 0;
1055
1511
  this.emit("stream:end");
@@ -1062,6 +1518,7 @@ var AgnoClient = class extends EventEmitter {
1062
1518
  }
1063
1519
  });
1064
1520
  } catch (error) {
1521
+ this.currentAbortController = null;
1065
1522
  this.handleError(
1066
1523
  error instanceof Error ? error : new Error(String(error)),
1067
1524
  newSessionId
@@ -1147,6 +1604,73 @@ var AgnoClient = class extends EventEmitter {
1147
1604
  this.emit("stream:end");
1148
1605
  this.emit("state:change", this.getState());
1149
1606
  }
1607
+ trackAttachmentUrls(urls) {
1608
+ for (const url of urls) {
1609
+ this.localAttachmentUrls.add(url);
1610
+ }
1611
+ }
1612
+ collectAttachmentUrls(message) {
1613
+ const imageUrls = message.images?.map((image) => image.url) ?? [];
1614
+ const videoUrls = message.videos?.map((video) => video.url).filter((url) => Boolean(url)) ?? [];
1615
+ const audioUrls = message.audio?.map((audio) => audio.url).filter((url) => Boolean(url)) ?? [];
1616
+ const fileUrls = message.files?.map((file) => file.url).filter((url) => Boolean(url)) ?? [];
1617
+ return [...imageUrls, ...videoUrls, ...audioUrls, ...fileUrls];
1618
+ }
1619
+ revokeAttachmentUrls(urls) {
1620
+ if (typeof URL === "undefined" || typeof URL.revokeObjectURL !== "function") {
1621
+ return;
1622
+ }
1623
+ for (const url of urls) {
1624
+ if (!this.localAttachmentUrls.has(url)) {
1625
+ continue;
1626
+ }
1627
+ URL.revokeObjectURL(url);
1628
+ this.localAttachmentUrls.delete(url);
1629
+ }
1630
+ }
1631
+ revokeAttachmentUrlsFromMessages(messages) {
1632
+ const urls = [];
1633
+ for (const message of messages) {
1634
+ if (!message) {
1635
+ continue;
1636
+ }
1637
+ urls.push(...this.collectAttachmentUrls(message));
1638
+ }
1639
+ this.revokeAttachmentUrls(urls);
1640
+ }
1641
+ collectExistingUIComponents() {
1642
+ const existingUIComponents = /* @__PURE__ */ new Map();
1643
+ for (const message of this.messageStore.getMessages()) {
1644
+ if (!message.tool_calls) {
1645
+ continue;
1646
+ }
1647
+ for (const toolCall of message.tool_calls) {
1648
+ if (toolCall.ui_component) {
1649
+ existingUIComponents.set(
1650
+ toolCall.tool_call_id,
1651
+ toolCall.ui_component
1652
+ );
1653
+ }
1654
+ }
1655
+ }
1656
+ return existingUIComponents;
1657
+ }
1658
+ restoreUIComponents(messages, uiComponents) {
1659
+ if (uiComponents.size === 0) {
1660
+ return;
1661
+ }
1662
+ for (const message of messages) {
1663
+ if (!message.tool_calls) {
1664
+ continue;
1665
+ }
1666
+ for (const toolCall of message.tool_calls) {
1667
+ const uiComponent = uiComponents.get(toolCall.tool_call_id);
1668
+ if (uiComponent) {
1669
+ toolCall.ui_component = uiComponent;
1670
+ }
1671
+ }
1672
+ }
1673
+ }
1150
1674
  /**
1151
1675
  * Refresh messages from the session API after run completion.
1152
1676
  * Replaces streamed messages with authoritative session data.
@@ -1159,22 +1683,14 @@ var AgnoClient = class extends EventEmitter {
1159
1683
  Logger.debug("[AgnoClient] Cannot refresh: no session ID");
1160
1684
  return;
1161
1685
  }
1686
+ if (this.state.isStreaming) {
1687
+ Logger.debug("[AgnoClient] Skipping refresh: stream is active");
1688
+ return;
1689
+ }
1162
1690
  this.state.isRefreshing = true;
1163
1691
  this.emit("state:change", this.getState());
1164
1692
  try {
1165
- const existingUIComponents = /* @__PURE__ */ new Map();
1166
- for (const message of this.messageStore.getMessages()) {
1167
- if (message.tool_calls) {
1168
- for (const toolCall of message.tool_calls) {
1169
- if (toolCall.ui_component) {
1170
- existingUIComponents.set(
1171
- toolCall.tool_call_id,
1172
- toolCall.ui_component
1173
- );
1174
- }
1175
- }
1176
- }
1177
- }
1693
+ const existingUIComponents = this.collectExistingUIComponents();
1178
1694
  const config = this.configManager.getConfig();
1179
1695
  const entityType = this.configManager.getMode();
1180
1696
  const dbId = this.configManager.getDbId() || "";
@@ -1190,22 +1706,15 @@ var AgnoClient = class extends EventEmitter {
1190
1706
  userId,
1191
1707
  params
1192
1708
  );
1193
- const messages = this.sessionManager.convertSessionToMessages(response);
1194
- if (existingUIComponents.size > 0) {
1195
- for (const message of messages) {
1196
- if (message.tool_calls) {
1197
- for (let i = 0; i < message.tool_calls.length; i++) {
1198
- const toolCall = message.tool_calls[i];
1199
- const uiComponent = existingUIComponents.get(
1200
- toolCall.tool_call_id
1201
- );
1202
- if (uiComponent) {
1203
- message.tool_calls[i].ui_component = uiComponent;
1204
- }
1205
- }
1206
- }
1207
- }
1709
+ if (this.state.isStreaming) {
1710
+ Logger.debug(
1711
+ "[AgnoClient] Aborting refresh: stream started during fetch"
1712
+ );
1713
+ return;
1208
1714
  }
1715
+ const messages = this.sessionManager.convertSessionToMessages(response);
1716
+ this.restoreUIComponents(messages, existingUIComponents);
1717
+ this.revokeAttachmentUrlsFromMessages(this.messageStore.getMessages());
1209
1718
  this.messageStore.setMessages(messages);
1210
1719
  Logger.debug(
1211
1720
  "[AgnoClient] Session refreshed:",
@@ -1254,6 +1763,7 @@ var AgnoClient = class extends EventEmitter {
1254
1763
  "[AgnoClient] Setting messages to store:",
1255
1764
  `${messages.length} messages`
1256
1765
  );
1766
+ this.revokeAttachmentUrlsFromMessages(this.messageStore.getMessages());
1257
1767
  this.messageStore.setMessages(messages);
1258
1768
  this.configManager.setSessionId(sessionId);
1259
1769
  Logger.debug("[AgnoClient] Emitting events...");
@@ -1405,9 +1915,9 @@ var AgnoClient = class extends EventEmitter {
1405
1915
  }
1406
1916
  }
1407
1917
  if (updatedMessages.length > 0) {
1408
- updatedMessages.forEach(({ index, message }) => {
1918
+ for (const { index, message } of updatedMessages) {
1409
1919
  this.messageStore.updateMessage(index, () => message);
1410
- });
1920
+ }
1411
1921
  this.emit("message:update", this.messageStore.getMessages());
1412
1922
  }
1413
1923
  }
@@ -1435,15 +1945,48 @@ var AgnoClient = class extends EventEmitter {
1435
1945
  if (!runUrl) {
1436
1946
  throw new Error("No agent or team selected");
1437
1947
  }
1438
- const continueUrl = `${runUrl}/${this.state.pausedRunId}/continue`;
1439
- this.state.isPaused = false;
1948
+ const pausedRunId = this.state.pausedRunId;
1949
+ const continueUrl = `${runUrl}/${pausedRunId}/continue`;
1440
1950
  this.state.isStreaming = true;
1441
- this.emit("run:continued", { runId: this.state.pausedRunId });
1951
+ this.state.errorMessage = void 0;
1442
1952
  this.emit("state:change", this.getState());
1443
- const cleanedTools = tools.map((tool) => {
1444
- const { ui_component, ...backendTool } = tool;
1445
- return backendTool;
1446
- });
1953
+ let hasContinued = false;
1954
+ let streamError;
1955
+ const markRunContinued = (runId) => {
1956
+ if (hasContinued) {
1957
+ return;
1958
+ }
1959
+ hasContinued = true;
1960
+ this.state.isPaused = false;
1961
+ this.state.toolsAwaitingExecution = void 0;
1962
+ this.emit("run:continued", { runId: runId || pausedRunId });
1963
+ this.emit("state:change", this.getState());
1964
+ };
1965
+ const handleContinueError = (error) => {
1966
+ streamError = error;
1967
+ this.state.isStreaming = false;
1968
+ this.state.errorMessage = error.message;
1969
+ const isConflictError = error instanceof StreamResponseHttpError && error.status === 409;
1970
+ if (isConflictError || !hasContinued) {
1971
+ this.state.isPaused = true;
1972
+ } else {
1973
+ this.state.isPaused = false;
1974
+ this.state.pausedRunId = void 0;
1975
+ this.state.toolsAwaitingExecution = void 0;
1976
+ this.messageStore.updateLastMessage((msg) => ({
1977
+ ...msg,
1978
+ streamingError: true
1979
+ }));
1980
+ }
1981
+ this.emit("message:error", error.message);
1982
+ this.emit("stream:end");
1983
+ this.emit("state:change", this.getState());
1984
+ };
1985
+ const cleanedTools = tools.map(
1986
+ ({ ui_component: _uiComponent, ...tool }) => {
1987
+ return tool;
1988
+ }
1989
+ );
1447
1990
  const formData = new FormData();
1448
1991
  formData.append("tools", JSON.stringify(cleanedTools));
1449
1992
  formData.append("stream", "true");
@@ -1457,36 +2000,45 @@ var AgnoClient = class extends EventEmitter {
1457
2000
  }
1458
2001
  const headers = this.configManager.buildRequestHeaders(options?.headers);
1459
2002
  const params = this.configManager.buildQueryString(options?.params);
1460
- try {
1461
- await streamResponse({
1462
- apiUrl: continueUrl,
1463
- headers,
1464
- params,
1465
- requestBody: formData,
1466
- onChunk: (chunk) => {
1467
- this.handleChunk(chunk, currentSessionId, "");
1468
- },
1469
- onError: (error) => {
1470
- this.handleError(error, currentSessionId);
1471
- },
1472
- onComplete: async () => {
1473
- this.state.isStreaming = false;
2003
+ this.currentAbortController = new AbortController();
2004
+ await streamResponse({
2005
+ apiUrl: continueUrl,
2006
+ headers,
2007
+ params,
2008
+ requestBody: formData,
2009
+ signal: this.currentAbortController.signal,
2010
+ onChunk: (chunk) => {
2011
+ const event = chunk.event;
2012
+ if (!hasContinued && event !== RunEvent.RunPaused) {
2013
+ markRunContinued(chunk.run_id);
2014
+ }
2015
+ this.handleChunk(chunk, currentSessionId, "");
2016
+ },
2017
+ onError: (error) => {
2018
+ this.currentAbortController = null;
2019
+ handleContinueError(error);
2020
+ },
2021
+ onComplete: async () => {
2022
+ this.currentAbortController = null;
2023
+ if (!(hasContinued || this.state.isPaused)) {
2024
+ markRunContinued(pausedRunId);
2025
+ }
2026
+ this.state.isStreaming = false;
2027
+ if (!this.state.isPaused) {
1474
2028
  this.state.pausedRunId = void 0;
1475
2029
  this.state.toolsAwaitingExecution = void 0;
1476
- this.emit("stream:end");
1477
- this.emit("message:complete", this.messageStore.getMessages());
1478
- this.emit("state:change", this.getState());
1479
- if (this.runCompletedSuccessfully) {
1480
- this.runCompletedSuccessfully = false;
1481
- await this.refreshSessionMessages();
1482
- }
1483
2030
  }
1484
- });
1485
- } catch (error) {
1486
- this.handleError(
1487
- error instanceof Error ? error : new Error(String(error)),
1488
- currentSessionId
1489
- );
2031
+ this.emit("stream:end");
2032
+ this.emit("message:complete", this.messageStore.getMessages());
2033
+ this.emit("state:change", this.getState());
2034
+ if (this.runCompletedSuccessfully) {
2035
+ this.runCompletedSuccessfully = false;
2036
+ await this.refreshSessionMessages();
2037
+ }
2038
+ }
2039
+ });
2040
+ if (streamError) {
2041
+ throw streamError;
1490
2042
  }
1491
2043
  }
1492
2044
  /**
@@ -1598,7 +2150,13 @@ var AgnoClient = class extends EventEmitter {
1598
2150
  * After calling dispose(), the client instance should not be reused.
1599
2151
  */
1600
2152
  dispose() {
2153
+ if (this.currentAbortController) {
2154
+ this.currentAbortController.abort();
2155
+ this.currentAbortController = null;
2156
+ }
1601
2157
  this.removeAllListeners();
2158
+ this.revokeAttachmentUrlsFromMessages(this.messageStore.getMessages());
2159
+ this.localAttachmentUrls.clear();
1602
2160
  this.messageStore.clear();
1603
2161
  this.pendingUISpecs.clear();
1604
2162
  this.eventProcessor.reset();