@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.js CHANGED
@@ -184,6 +184,18 @@ var ConfigManager = class {
184
184
  setParams(params) {
185
185
  this.updateField("params", params);
186
186
  }
187
+ /**
188
+ * Get global dependencies
189
+ */
190
+ getDependencies() {
191
+ return this.config.dependencies;
192
+ }
193
+ /**
194
+ * Set global dependencies
195
+ */
196
+ setDependencies(dependencies) {
197
+ this.updateField("dependencies", dependencies);
198
+ }
187
199
  /**
188
200
  * Get current entity ID (agent or team based on mode)
189
201
  */
@@ -251,6 +263,29 @@ var ConfigManager = class {
251
263
  }
252
264
  return new URLSearchParams(params);
253
265
  }
266
+ /**
267
+ * Build dependencies by merging global dependencies and per-request dependencies.
268
+ * Merge order (lowest to highest precedence):
269
+ * 1. Global dependencies from config
270
+ * 2. Per-request dependencies (overrides global)
271
+ *
272
+ * @param perRequestDependencies - Optional dependencies for this specific request
273
+ * @returns Merged dependencies object, or undefined if no dependencies are configured
274
+ */
275
+ buildDependencies(perRequestDependencies) {
276
+ const dependencies = {};
277
+ const globalDependencies = this.getDependencies();
278
+ if (globalDependencies) {
279
+ Object.assign(dependencies, globalDependencies);
280
+ }
281
+ if (perRequestDependencies) {
282
+ Object.assign(dependencies, perRequestDependencies);
283
+ }
284
+ if (Object.keys(dependencies).length === 0) {
285
+ return void 0;
286
+ }
287
+ return dependencies;
288
+ }
254
289
  };
255
290
 
256
291
  // src/managers/session-manager.ts
@@ -337,79 +372,271 @@ var SessionManager = class {
337
372
  convertRunsToMessages(runs) {
338
373
  const messages = [];
339
374
  for (const run of runs) {
340
- const timestamp = run.created_at ? new Date(run.created_at).getTime() / 1e3 : Math.floor(Date.now() / 1e3);
341
- if (run.run_input) {
342
- messages.push({
343
- role: "user",
344
- content: run.run_input,
345
- created_at: timestamp
346
- });
347
- }
348
- const toolCalls = [];
349
- if (run.tools && Array.isArray(run.tools)) {
350
- for (const tool of run.tools) {
351
- const toolObj = tool;
352
- const toolCall = {
353
- role: "tool",
354
- content: toolObj.content ?? "",
355
- tool_call_id: toolObj.tool_call_id ?? "",
356
- tool_name: toolObj.tool_name ?? "",
357
- tool_args: toolObj.tool_args ?? {},
358
- tool_call_error: toolObj.tool_call_error ?? false,
359
- metrics: toolObj.metrics ?? { time: 0 },
360
- created_at: timestamp
361
- };
362
- toolCalls.push(toolCall);
363
- }
364
- }
365
- if (run.reasoning_messages && Array.isArray(run.reasoning_messages)) {
366
- for (const msg of run.reasoning_messages) {
367
- const reasoningMsg = msg;
368
- if (reasoningMsg.role === "tool") {
369
- toolCalls.push({
370
- role: "tool",
371
- content: reasoningMsg.content ?? "",
372
- tool_call_id: reasoningMsg.tool_call_id ?? "",
373
- tool_name: reasoningMsg.tool_name ?? "",
374
- tool_args: reasoningMsg.tool_args ?? {},
375
- tool_call_error: reasoningMsg.tool_call_error ?? false,
376
- metrics: reasoningMsg.metrics ?? {
377
- time: 0
378
- },
379
- created_at: reasoningMsg.created_at ?? timestamp
380
- });
381
- }
382
- }
383
- }
384
- let contentStr = "";
385
- if (typeof run.content === "string") {
386
- contentStr = run.content;
387
- } else if (run.content && typeof run.content === "object") {
388
- contentStr = JSON.stringify(run.content);
375
+ const timestamp = this.getRunTimestamp(run);
376
+ const inputMedia = this.mergeInputMedia(
377
+ this.extractInputMedia(run.input_media),
378
+ this.extractInputMediaFromMessages(run.messages)
379
+ );
380
+ if (run.run_input || this.hasAnyMedia(inputMedia)) {
381
+ messages.push(
382
+ this.buildUserMessage(run.run_input ?? "", timestamp, inputMedia)
383
+ );
389
384
  }
390
- const extraData = run.reasoning_messages || run.reasoning_steps || run.references ? {
391
- reasoning_messages: run.reasoning_messages,
392
- reasoning_steps: run.reasoning_steps,
393
- references: run.references
394
- } : void 0;
385
+ const toolCalls = this.extractToolCalls(run, timestamp);
395
386
  messages.push({
396
387
  role: "agent",
397
- content: contentStr,
388
+ content: this.normalizeRunContent(run.content),
398
389
  tool_calls: toolCalls.length > 0 ? toolCalls : void 0,
399
- extra_data: extraData,
400
- images: run.images,
401
- videos: run.videos,
402
- audio: run.audio,
403
- response_audio: run.response_audio,
390
+ extra_data: this.buildExtraData(run),
391
+ images: this.normalizeImages(run.images),
392
+ videos: this.normalizeVideos(run.videos),
393
+ audio: this.normalizeAudio(run.audio),
394
+ files: this.normalizeFiles(run.files),
395
+ response_audio: run.response_audio ?? void 0,
404
396
  created_at: timestamp + 1
405
397
  // Agent response is slightly after user message
406
398
  });
407
399
  }
408
400
  return messages;
409
401
  }
402
+ getRunTimestamp(run) {
403
+ return run.created_at ? new Date(run.created_at).getTime() / 1e3 : Math.floor(Date.now() / 1e3);
404
+ }
405
+ buildUserMessage(content, createdAt, media) {
406
+ return {
407
+ role: "user",
408
+ content,
409
+ images: media?.images,
410
+ videos: media?.videos,
411
+ audio: media?.audio,
412
+ files: media?.files,
413
+ created_at: createdAt
414
+ };
415
+ }
416
+ hasAnyMedia(media) {
417
+ return Boolean(
418
+ media.images && media.images.length > 0 || media.videos && media.videos.length > 0 || media.audio && media.audio.length > 0 || media.files && media.files.length > 0
419
+ );
420
+ }
421
+ isRecord(value) {
422
+ return typeof value === "object" && value !== null;
423
+ }
424
+ getStringValue(record, key) {
425
+ const value = record[key];
426
+ return typeof value === "string" && value.length > 0 ? value : void 0;
427
+ }
428
+ getNumberOrStringValue(record, key) {
429
+ const value = record[key];
430
+ return typeof value === "number" || typeof value === "string" ? value : void 0;
431
+ }
432
+ buildDataUrl(content, mimeType, fallbackMimeType) {
433
+ const safeMimeType = mimeType || fallbackMimeType;
434
+ return `data:${safeMimeType};base64,${content}`;
435
+ }
436
+ normalizeImage(item) {
437
+ if (!this.isRecord(item)) {
438
+ return void 0;
439
+ }
440
+ const mimeType = this.getStringValue(item, "mime_type");
441
+ const content = this.getStringValue(item, "content");
442
+ const url = this.getStringValue(item, "url") || (content ? this.buildDataUrl(content, mimeType, "image/png") : void 0);
443
+ if (!url) {
444
+ return void 0;
445
+ }
446
+ return {
447
+ url,
448
+ revised_prompt: this.getStringValue(item, "revised_prompt"),
449
+ original_prompt: this.getStringValue(item, "original_prompt"),
450
+ alt_text: this.getStringValue(item, "alt_text"),
451
+ id: this.getStringValue(item, "id"),
452
+ mime_type: mimeType,
453
+ format: this.getStringValue(item, "format")
454
+ };
455
+ }
456
+ normalizeVideo(item) {
457
+ if (!this.isRecord(item)) {
458
+ return void 0;
459
+ }
460
+ const mimeType = this.getStringValue(item, "mime_type");
461
+ const content = this.getStringValue(item, "content");
462
+ const url = this.getStringValue(item, "url") || (content ? this.buildDataUrl(content, mimeType, "video/mp4") : void 0);
463
+ const normalized = {
464
+ url,
465
+ id: this.getNumberOrStringValue(item, "id"),
466
+ eta: this.getNumberOrStringValue(item, "eta"),
467
+ mime_type: mimeType,
468
+ format: this.getStringValue(item, "format"),
469
+ original_prompt: this.getStringValue(item, "original_prompt"),
470
+ revised_prompt: this.getStringValue(item, "revised_prompt"),
471
+ width: typeof item.width === "number" ? item.width : void 0,
472
+ height: typeof item.height === "number" ? item.height : void 0,
473
+ fps: typeof item.fps === "number" ? item.fps : void 0,
474
+ duration: typeof item.duration === "number" ? item.duration : void 0
475
+ };
476
+ if (normalized.url || normalized.id !== void 0 || normalized.eta !== void 0) {
477
+ return normalized;
478
+ }
479
+ return void 0;
480
+ }
481
+ normalizeAudioEntry(item) {
482
+ if (!this.isRecord(item)) {
483
+ return void 0;
484
+ }
485
+ const mimeType = this.getStringValue(item, "mime_type");
486
+ const content = this.getStringValue(item, "content");
487
+ const url = this.getStringValue(item, "url") || (content ? this.buildDataUrl(content, mimeType, "audio/wav") : void 0);
488
+ const normalized = {
489
+ base64_audio: this.getStringValue(item, "base64_audio"),
490
+ mime_type: mimeType,
491
+ url,
492
+ id: this.getNumberOrStringValue(item, "id"),
493
+ content,
494
+ channels: typeof item.channels === "number" ? item.channels : void 0,
495
+ sample_rate: typeof item.sample_rate === "number" ? item.sample_rate : void 0,
496
+ format: this.getStringValue(item, "format"),
497
+ duration: typeof item.duration === "number" ? item.duration : void 0
498
+ };
499
+ if (normalized.url || normalized.base64_audio || normalized.content || normalized.id !== void 0) {
500
+ return normalized;
501
+ }
502
+ return void 0;
503
+ }
504
+ normalizeFileEntry(item) {
505
+ if (!this.isRecord(item)) {
506
+ return void 0;
507
+ }
508
+ const normalized = {
509
+ id: this.getStringValue(item, "id"),
510
+ url: this.getStringValue(item, "url"),
511
+ filename: this.getStringValue(item, "filename"),
512
+ name: this.getStringValue(item, "name"),
513
+ mime_type: this.getStringValue(item, "mime_type"),
514
+ format: this.getStringValue(item, "format"),
515
+ size: typeof item.size === "number" ? item.size : void 0
516
+ };
517
+ if (normalized.url || normalized.filename || normalized.name || normalized.id) {
518
+ return normalized;
519
+ }
520
+ return void 0;
521
+ }
522
+ normalizeArray(value, normalizer) {
523
+ if (!Array.isArray(value)) {
524
+ return void 0;
525
+ }
526
+ const items = value.map((item) => normalizer(item)).filter((item) => item !== void 0);
527
+ return items.length > 0 ? items : void 0;
528
+ }
529
+ normalizeImages(value) {
530
+ return this.normalizeArray(value, (item) => this.normalizeImage(item));
531
+ }
532
+ normalizeVideos(value) {
533
+ return this.normalizeArray(value, (item) => this.normalizeVideo(item));
534
+ }
535
+ normalizeAudio(value) {
536
+ return this.normalizeArray(value, (item) => this.normalizeAudioEntry(item));
537
+ }
538
+ normalizeFiles(value) {
539
+ return this.normalizeArray(value, (item) => this.normalizeFileEntry(item));
540
+ }
541
+ extractInputMedia(inputMedia) {
542
+ if (!this.isRecord(inputMedia)) {
543
+ return {};
544
+ }
545
+ return {
546
+ images: this.normalizeImages(inputMedia.images),
547
+ videos: this.normalizeVideos(inputMedia.videos),
548
+ audio: this.normalizeAudio(inputMedia.audios ?? inputMedia.audio),
549
+ files: this.normalizeFiles(inputMedia.files)
550
+ };
551
+ }
552
+ extractInputMediaFromMessages(messages) {
553
+ if (!Array.isArray(messages)) {
554
+ return {};
555
+ }
556
+ for (const message of messages) {
557
+ if (!this.isRecord(message)) {
558
+ continue;
559
+ }
560
+ if (this.getStringValue(message, "role") !== "user") {
561
+ continue;
562
+ }
563
+ const media = {
564
+ images: this.normalizeImages(message.images ?? message.image),
565
+ videos: this.normalizeVideos(message.videos ?? message.video),
566
+ audio: this.normalizeAudio(message.audios ?? message.audio),
567
+ files: this.normalizeFiles(message.files)
568
+ };
569
+ if (this.hasAnyMedia(media)) {
570
+ return media;
571
+ }
572
+ }
573
+ return {};
574
+ }
575
+ mergeInputMedia(primary, fallback) {
576
+ return {
577
+ images: primary.images ?? fallback.images,
578
+ videos: primary.videos ?? fallback.videos,
579
+ audio: primary.audio ?? fallback.audio,
580
+ files: primary.files ?? fallback.files
581
+ };
582
+ }
583
+ normalizeRunContent(content) {
584
+ if (typeof content === "string") {
585
+ return content;
586
+ }
587
+ if (content && typeof content === "object") {
588
+ return JSON.stringify(content);
589
+ }
590
+ return "";
591
+ }
592
+ buildToolCall(rawTool, fallbackTimestamp) {
593
+ return {
594
+ role: "tool",
595
+ content: rawTool.content ?? "",
596
+ tool_call_id: rawTool.tool_call_id ?? "",
597
+ tool_name: rawTool.tool_name ?? "",
598
+ tool_args: rawTool.tool_args ?? {},
599
+ tool_call_error: rawTool.tool_call_error ?? false,
600
+ metrics: rawTool.metrics ?? { time: 0 },
601
+ created_at: rawTool.created_at ?? fallbackTimestamp
602
+ };
603
+ }
604
+ extractToolCalls(run, timestamp) {
605
+ const toolCalls = [];
606
+ if (run.tools && Array.isArray(run.tools)) {
607
+ for (const tool of run.tools) {
608
+ toolCalls.push(this.buildToolCall(tool, timestamp));
609
+ }
610
+ }
611
+ if (run.reasoning_messages && Array.isArray(run.reasoning_messages)) {
612
+ for (const message of run.reasoning_messages) {
613
+ if (message.role === "tool") {
614
+ toolCalls.push(this.buildToolCall(message, timestamp));
615
+ }
616
+ }
617
+ }
618
+ return toolCalls;
619
+ }
620
+ buildExtraData(run) {
621
+ if (!(run.reasoning_messages || run.reasoning_steps || run.references)) {
622
+ return void 0;
623
+ }
624
+ return {
625
+ reasoning_messages: run.reasoning_messages,
626
+ reasoning_steps: run.reasoning_steps,
627
+ references: run.references
628
+ };
629
+ }
410
630
  };
411
631
 
412
632
  // src/parsers/stream-parser.ts
633
+ var StreamResponseHttpError = class extends Error {
634
+ constructor(status, message) {
635
+ super(message);
636
+ this.name = "StreamResponseHttpError";
637
+ this.status = status;
638
+ }
639
+ };
413
640
  function isLegacyFormat(data) {
414
641
  return typeof data === "object" && data !== null && "event" in data && !("data" in data) && typeof data.event === "string";
415
642
  }
@@ -433,72 +660,139 @@ function convertNewFormatToLegacy(newFormatData) {
433
660
  function processChunk(chunk, onChunk) {
434
661
  onChunk(chunk);
435
662
  }
436
- function parseBuffer(buffer, onChunk) {
437
- let currentIndex = 0;
438
- let jsonStartIndex = buffer.indexOf("{", currentIndex);
439
- while (jsonStartIndex !== -1 && jsonStartIndex < buffer.length) {
440
- let braceCount = 0;
441
- let inString = false;
442
- let escapeNext = false;
443
- let jsonEndIndex = -1;
444
- let i = jsonStartIndex;
445
- for (; i < buffer.length; i++) {
446
- const char = buffer[i];
447
- if (inString) {
448
- if (escapeNext) {
449
- escapeNext = false;
450
- } else if (char === "\\") {
451
- escapeNext = true;
452
- } else if (char === '"') {
453
- inString = false;
454
- }
455
- } else if (char === '"') {
456
- inString = true;
457
- } else if (char === "{") {
458
- braceCount++;
459
- } else if (char === "}") {
460
- braceCount--;
461
- if (braceCount === 0) {
462
- jsonEndIndex = i;
463
- break;
464
- }
465
- }
663
+ function updateJsonParserState(char, state) {
664
+ if (state.inString) {
665
+ if (state.escapeNext) {
666
+ state.escapeNext = false;
667
+ return false;
466
668
  }
467
- if (jsonEndIndex !== -1) {
468
- const jsonString = buffer.slice(jsonStartIndex, jsonEndIndex + 1);
469
- try {
470
- const parsed = JSON.parse(jsonString);
471
- if (isLegacyFormat(parsed)) {
472
- processChunk(parsed, onChunk);
473
- } else {
474
- const legacyChunk = convertNewFormatToLegacy(parsed);
475
- processChunk(legacyChunk, onChunk);
476
- }
477
- } catch (error) {
478
- if (typeof process !== "undefined" && process.env?.NODE_ENV === "development") {
479
- console.error("Failed to parse JSON chunk:", {
480
- error,
481
- chunk: jsonString.substring(0, 100) + (jsonString.length > 100 ? "..." : ""),
482
- position: jsonStartIndex
483
- });
484
- }
485
- if (jsonString.length > 1e4) {
486
- throw new Error(
487
- `Failed to parse large JSON chunk at position ${jsonStartIndex}`
488
- );
489
- }
490
- jsonStartIndex = buffer.indexOf("{", jsonStartIndex + 1);
491
- continue;
492
- }
493
- currentIndex = jsonEndIndex + 1;
494
- buffer = buffer.slice(currentIndex).trim();
495
- currentIndex = 0;
496
- jsonStartIndex = buffer.indexOf("{", currentIndex);
669
+ if (char === "\\") {
670
+ state.escapeNext = true;
671
+ return false;
672
+ }
673
+ if (char === '"') {
674
+ state.inString = false;
675
+ }
676
+ return false;
677
+ }
678
+ if (char === '"') {
679
+ state.inString = true;
680
+ return false;
681
+ }
682
+ if (char === "{") {
683
+ state.braceCount++;
684
+ return false;
685
+ }
686
+ if (char !== "}") {
687
+ return false;
688
+ }
689
+ state.braceCount--;
690
+ return state.braceCount === 0;
691
+ }
692
+ function findJsonSlice(buffer, fromIndex) {
693
+ const startIndex = buffer.indexOf("{", fromIndex);
694
+ if (startIndex === -1) {
695
+ return void 0;
696
+ }
697
+ const parserState = {
698
+ braceCount: 0,
699
+ inString: false,
700
+ escapeNext: false
701
+ };
702
+ for (let i = startIndex; i < buffer.length; i++) {
703
+ const isJsonComplete = updateJsonParserState(buffer[i], parserState);
704
+ if (isJsonComplete) {
705
+ return { startIndex, endIndex: i };
706
+ }
707
+ }
708
+ return void 0;
709
+ }
710
+ function logChunkParseError(error, jsonString, position) {
711
+ if (typeof process === "undefined" || process.env?.NODE_ENV !== "development") {
712
+ return;
713
+ }
714
+ console.error("Failed to parse JSON chunk:", {
715
+ error,
716
+ chunk: jsonString.substring(0, 100) + (jsonString.length > 100 ? "..." : ""),
717
+ position
718
+ });
719
+ }
720
+ function processJsonSlice(jsonString, startIndex, onChunk) {
721
+ try {
722
+ const parsed = JSON.parse(jsonString);
723
+ if (isLegacyFormat(parsed)) {
724
+ processChunk(parsed, onChunk);
497
725
  } else {
726
+ processChunk(convertNewFormatToLegacy(parsed), onChunk);
727
+ }
728
+ return true;
729
+ } catch (error) {
730
+ logChunkParseError(error, jsonString, startIndex);
731
+ if (jsonString.length > 1e4) {
732
+ throw new Error(
733
+ `Failed to parse large JSON chunk at position ${startIndex}`
734
+ );
735
+ }
736
+ return false;
737
+ }
738
+ }
739
+ function parseBuffer(buffer, onChunk) {
740
+ let searchIndex = 0;
741
+ let remainingBuffer = buffer;
742
+ while (true) {
743
+ const jsonSlice = findJsonSlice(remainingBuffer, searchIndex);
744
+ if (!jsonSlice) {
498
745
  break;
499
746
  }
747
+ const jsonString = remainingBuffer.slice(
748
+ jsonSlice.startIndex,
749
+ jsonSlice.endIndex + 1
750
+ );
751
+ const parsed = processJsonSlice(jsonString, jsonSlice.startIndex, onChunk);
752
+ if (!parsed) {
753
+ searchIndex = jsonSlice.startIndex + 1;
754
+ continue;
755
+ }
756
+ remainingBuffer = remainingBuffer.slice(jsonSlice.endIndex + 1).trim();
757
+ searchIndex = 0;
758
+ }
759
+ return remainingBuffer;
760
+ }
761
+ function buildRequestHeaders(headers, requestBody) {
762
+ return {
763
+ ...!(requestBody instanceof FormData) && {
764
+ "Content-Type": "application/json"
765
+ },
766
+ ...headers
767
+ };
768
+ }
769
+ async function buildErrorMessage(response) {
770
+ const defaultMessage = `HTTP ${response.status}: ${response.statusText}`;
771
+ const contentType = response.headers.get("content-type");
772
+ if (!contentType?.includes("application/json")) {
773
+ return defaultMessage;
774
+ }
775
+ try {
776
+ const errorData = await response.json();
777
+ return errorData.detail || errorData.message || defaultMessage;
778
+ } catch {
779
+ return defaultMessage;
780
+ }
781
+ }
782
+ async function consumeResponseStream(body, onChunk, onComplete) {
783
+ const reader = body.getReader();
784
+ const decoder = new TextDecoder();
785
+ let buffer = "";
786
+ while (true) {
787
+ const { done, value } = await reader.read();
788
+ if (done) {
789
+ parseBuffer(buffer, onChunk);
790
+ onComplete();
791
+ return;
792
+ }
793
+ buffer += decoder.decode(value, { stream: true });
794
+ buffer = parseBuffer(buffer, onChunk);
500
795
  }
501
- return buffer;
502
796
  }
503
797
  async function streamResponse(options) {
504
798
  const {
@@ -511,54 +805,30 @@ async function streamResponse(options) {
511
805
  onComplete,
512
806
  signal
513
807
  } = options;
514
- let buffer = "";
515
808
  const finalUrl = params?.toString() ? `${apiUrl}?${params.toString()}` : apiUrl;
516
809
  try {
517
810
  const response = await fetch(finalUrl, {
518
811
  method: "POST",
519
- headers: {
520
- ...!(requestBody instanceof FormData) && {
521
- "Content-Type": "application/json"
522
- },
523
- ...headers
524
- },
812
+ headers: buildRequestHeaders(headers, requestBody),
525
813
  body: requestBody instanceof FormData ? requestBody : JSON.stringify(requestBody),
526
814
  signal
527
815
  });
528
816
  if (!response.ok) {
529
- let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
530
- const contentType = response.headers.get("content-type");
531
- if (contentType?.includes("application/json")) {
532
- try {
533
- const errorData = await response.json();
534
- errorMessage = errorData.detail || errorData.message || errorMessage;
535
- } catch {
536
- }
537
- }
538
- throw new Error(errorMessage);
817
+ const errorMessage = await buildErrorMessage(response);
818
+ throw new StreamResponseHttpError(response.status, errorMessage);
539
819
  }
540
820
  if (!response.body) {
541
821
  throw new Error("No response body");
542
822
  }
543
- const reader = response.body.getReader();
544
- const decoder = new TextDecoder();
545
- const processStream = async () => {
546
- while (true) {
547
- const { done, value } = await reader.read();
548
- if (done) {
549
- buffer = parseBuffer(buffer, onChunk);
550
- onComplete();
551
- return;
552
- }
553
- buffer += decoder.decode(value, { stream: true });
554
- buffer = parseBuffer(buffer, onChunk);
555
- }
556
- };
557
- await processStream();
823
+ await consumeResponseStream(response.body, onChunk, onComplete);
558
824
  } catch (error) {
559
825
  if (error instanceof Error && error.name === "AbortError") {
560
826
  return;
561
827
  }
828
+ if (error instanceof Error) {
829
+ onError(error);
830
+ return;
831
+ }
562
832
  if (typeof error === "object" && error !== null && "detail" in error) {
563
833
  onError(new Error(String(error.detail)));
564
834
  } else {
@@ -590,10 +860,15 @@ function processToolCall(toolCall, prevToolCalls = []) {
590
860
  );
591
861
  if (existingToolCallIndex >= 0) {
592
862
  const updatedToolCalls = [...prevToolCalls];
593
- updatedToolCalls[existingToolCallIndex] = {
594
- ...updatedToolCalls[existingToolCallIndex],
595
- ...toolCall
596
- };
863
+ const existing = updatedToolCalls[existingToolCallIndex];
864
+ const merged = { ...existing, ...toolCall };
865
+ if (existing.external_execution && existing.result !== void 0) {
866
+ merged.result = existing.result;
867
+ }
868
+ if (existing.ui_component !== void 0 && !toolCall.ui_component) {
869
+ merged.ui_component = existing.ui_component;
870
+ }
871
+ updatedToolCalls[existingToolCallIndex] = merged;
597
872
  return updatedToolCalls;
598
873
  }
599
874
  return [...prevToolCalls, toolCall];
@@ -614,6 +889,105 @@ var EventProcessor = class {
614
889
  constructor() {
615
890
  this.lastContent = "";
616
891
  }
892
+ appendRunContent(chunk, updatedMessage) {
893
+ if (typeof chunk.content === "string") {
894
+ const uniqueContent = chunk.content.replace(this.lastContent, "");
895
+ updatedMessage.content = updatedMessage.content + uniqueContent;
896
+ this.lastContent = chunk.content;
897
+ return;
898
+ }
899
+ if (typeof chunk.content !== "string" && chunk.content !== null) {
900
+ const jsonBlock = getJsonMarkdown(chunk.content);
901
+ updatedMessage.content = updatedMessage.content + jsonBlock;
902
+ this.lastContent = jsonBlock;
903
+ }
904
+ }
905
+ applyRunContentFields(chunk, lastMessage, updatedMessage) {
906
+ this.appendRunContent(chunk, updatedMessage);
907
+ updatedMessage.tool_calls = processChunkToolCalls(
908
+ chunk,
909
+ lastMessage.tool_calls
910
+ );
911
+ if (chunk.extra_data?.reasoning_steps) {
912
+ updatedMessage.extra_data = {
913
+ ...updatedMessage.extra_data,
914
+ reasoning_steps: chunk.extra_data.reasoning_steps
915
+ };
916
+ }
917
+ if (chunk.extra_data?.references) {
918
+ updatedMessage.extra_data = {
919
+ ...updatedMessage.extra_data,
920
+ references: chunk.extra_data.references
921
+ };
922
+ }
923
+ updatedMessage.created_at = chunk.created_at ?? lastMessage.created_at;
924
+ if (chunk.images) {
925
+ updatedMessage.images = chunk.images;
926
+ }
927
+ if (chunk.image) {
928
+ const existingImages = updatedMessage.images ?? lastMessage.images ?? [];
929
+ const hasImage = existingImages.some((image) => {
930
+ if (image.id && chunk.image?.id) {
931
+ return image.id === chunk.image.id;
932
+ }
933
+ return image.url === chunk.image?.url;
934
+ });
935
+ if (!hasImage) {
936
+ updatedMessage.images = [...existingImages, chunk.image];
937
+ }
938
+ }
939
+ if (chunk.videos) {
940
+ updatedMessage.videos = chunk.videos;
941
+ }
942
+ if (chunk.audio) {
943
+ updatedMessage.audio = chunk.audio;
944
+ }
945
+ if (chunk.files) {
946
+ updatedMessage.files = chunk.files;
947
+ }
948
+ if (chunk.response_audio?.transcript && typeof chunk.response_audio.transcript === "string") {
949
+ updatedMessage.response_audio = {
950
+ ...updatedMessage.response_audio,
951
+ transcript: (updatedMessage.response_audio?.transcript || "") + chunk.response_audio.transcript
952
+ };
953
+ }
954
+ }
955
+ applyCompletedFields(chunk, lastMessage, updatedMessage) {
956
+ let updatedContent;
957
+ if (typeof chunk.content === "string") {
958
+ updatedContent = chunk.content;
959
+ } else {
960
+ try {
961
+ updatedContent = JSON.stringify(chunk.content);
962
+ } catch {
963
+ updatedContent = "Error parsing response";
964
+ }
965
+ }
966
+ updatedMessage.content = updatedContent;
967
+ updatedMessage.tool_calls = processChunkToolCalls(
968
+ chunk,
969
+ lastMessage.tool_calls
970
+ );
971
+ const completedImages = chunk.images ?? (chunk.image ? [chunk.image] : void 0);
972
+ updatedMessage.images = completedImages ?? lastMessage.images;
973
+ updatedMessage.videos = chunk.videos ?? lastMessage.videos;
974
+ updatedMessage.audio = chunk.audio ?? lastMessage.audio;
975
+ updatedMessage.files = chunk.files ?? lastMessage.files;
976
+ updatedMessage.response_audio = chunk.response_audio;
977
+ updatedMessage.created_at = chunk.created_at ?? lastMessage.created_at;
978
+ updatedMessage.extra_data = {
979
+ reasoning_steps: chunk.extra_data?.reasoning_steps ?? lastMessage.extra_data?.reasoning_steps,
980
+ references: chunk.extra_data?.references ?? lastMessage.extra_data?.references
981
+ };
982
+ }
983
+ appendReasoningSteps(chunk, lastMessage, updatedMessage) {
984
+ const existingSteps = lastMessage.extra_data?.reasoning_steps ?? [];
985
+ const incomingSteps = chunk.extra_data?.reasoning_steps ?? [];
986
+ updatedMessage.extra_data = {
987
+ ...updatedMessage.extra_data,
988
+ reasoning_steps: [...existingSteps, ...incomingSteps]
989
+ };
990
+ }
617
991
  /**
618
992
  * Process a chunk and update the last message
619
993
  */
@@ -640,57 +1014,11 @@ var EventProcessor = class {
640
1014
  break;
641
1015
  case import_agno_types.RunEvent.RunContent:
642
1016
  case import_agno_types.RunEvent.TeamRunContent:
643
- if (typeof chunk.content === "string") {
644
- const uniqueContent = chunk.content.replace(this.lastContent, "");
645
- updatedMessage.content = updatedMessage.content + uniqueContent;
646
- this.lastContent = chunk.content;
647
- } else if (typeof chunk.content !== "string" && chunk.content !== null) {
648
- const jsonBlock = getJsonMarkdown(chunk.content);
649
- updatedMessage.content = updatedMessage.content + jsonBlock;
650
- this.lastContent = jsonBlock;
651
- }
652
- updatedMessage.tool_calls = processChunkToolCalls(
653
- chunk,
654
- lastMessage.tool_calls
655
- );
656
- if (chunk.extra_data?.reasoning_steps) {
657
- updatedMessage.extra_data = {
658
- ...updatedMessage.extra_data,
659
- reasoning_steps: chunk.extra_data.reasoning_steps
660
- };
661
- }
662
- if (chunk.extra_data?.references) {
663
- updatedMessage.extra_data = {
664
- ...updatedMessage.extra_data,
665
- references: chunk.extra_data.references
666
- };
667
- }
668
- updatedMessage.created_at = chunk.created_at ?? lastMessage.created_at;
669
- if (chunk.images) {
670
- updatedMessage.images = chunk.images;
671
- }
672
- if (chunk.videos) {
673
- updatedMessage.videos = chunk.videos;
674
- }
675
- if (chunk.audio) {
676
- updatedMessage.audio = chunk.audio;
677
- }
678
- if (chunk.response_audio?.transcript && typeof chunk.response_audio.transcript === "string") {
679
- const transcript = chunk.response_audio.transcript;
680
- updatedMessage.response_audio = {
681
- ...updatedMessage.response_audio,
682
- transcript: (updatedMessage.response_audio?.transcript || "") + transcript
683
- };
684
- }
1017
+ this.applyRunContentFields(chunk, lastMessage, updatedMessage);
685
1018
  break;
686
1019
  case import_agno_types.RunEvent.ReasoningStep:
687
1020
  case import_agno_types.RunEvent.TeamReasoningStep: {
688
- const existingSteps = lastMessage.extra_data?.reasoning_steps ?? [];
689
- const incomingSteps = chunk.extra_data?.reasoning_steps ?? [];
690
- updatedMessage.extra_data = {
691
- ...updatedMessage.extra_data,
692
- reasoning_steps: [...existingSteps, ...incomingSteps]
693
- };
1021
+ this.appendReasoningSteps(chunk, lastMessage, updatedMessage);
694
1022
  break;
695
1023
  }
696
1024
  case import_agno_types.RunEvent.ReasoningCompleted:
@@ -704,29 +1032,7 @@ var EventProcessor = class {
704
1032
  break;
705
1033
  case import_agno_types.RunEvent.RunCompleted:
706
1034
  case import_agno_types.RunEvent.TeamRunCompleted: {
707
- let updatedContent;
708
- if (typeof chunk.content === "string") {
709
- updatedContent = chunk.content;
710
- } else {
711
- try {
712
- updatedContent = JSON.stringify(chunk.content);
713
- } catch {
714
- updatedContent = "Error parsing response";
715
- }
716
- }
717
- updatedMessage.content = updatedContent;
718
- updatedMessage.tool_calls = processChunkToolCalls(
719
- chunk,
720
- lastMessage.tool_calls
721
- );
722
- updatedMessage.images = chunk.images ?? lastMessage.images;
723
- updatedMessage.videos = chunk.videos ?? lastMessage.videos;
724
- updatedMessage.response_audio = chunk.response_audio;
725
- updatedMessage.created_at = chunk.created_at ?? lastMessage.created_at;
726
- updatedMessage.extra_data = {
727
- reasoning_steps: chunk.extra_data?.reasoning_steps ?? lastMessage.extra_data?.reasoning_steps,
728
- references: chunk.extra_data?.references ?? lastMessage.extra_data?.references
729
- };
1035
+ this.applyCompletedFields(chunk, lastMessage, updatedMessage);
730
1036
  break;
731
1037
  }
732
1038
  case import_agno_types.RunEvent.UpdatingMemory:
@@ -740,6 +1046,8 @@ var EventProcessor = class {
740
1046
  case import_agno_types.RunEvent.TeamRunCancelled:
741
1047
  updatedMessage.streamingError = true;
742
1048
  break;
1049
+ default:
1050
+ break;
743
1051
  }
744
1052
  return updatedMessage;
745
1053
  }
@@ -867,36 +1175,36 @@ function sanitizeObject(obj) {
867
1175
  function isDevelopment() {
868
1176
  return typeof process !== "undefined" && process.env?.NODE_ENV === "development";
869
1177
  }
870
- var Logger = class {
1178
+ var Logger = {
871
1179
  /**
872
1180
  * Log debug information (only in development)
873
1181
  */
874
- static debug(message, data) {
1182
+ debug(message, data) {
875
1183
  if (isDevelopment()) {
876
1184
  const sanitized = data ? sanitizeObject(data) : void 0;
877
1185
  console.debug(`[DEBUG] ${message}`, sanitized || "");
878
1186
  }
879
- }
1187
+ },
880
1188
  /**
881
1189
  * Log informational messages (only in development)
882
1190
  */
883
- static info(message, data) {
1191
+ info(message, data) {
884
1192
  if (isDevelopment()) {
885
1193
  const sanitized = data ? sanitizeObject(data) : void 0;
886
1194
  console.info(`[INFO] ${message}`, sanitized || "");
887
1195
  }
888
- }
1196
+ },
889
1197
  /**
890
1198
  * Log warnings (always logs)
891
1199
  */
892
- static warn(message, data) {
1200
+ warn(message, data) {
893
1201
  const sanitized = data ? sanitizeObject(data) : void 0;
894
1202
  console.warn(`[WARN] ${message}`, sanitized || "");
895
- }
1203
+ },
896
1204
  /**
897
1205
  * Log errors (always logs)
898
1206
  */
899
- static error(message, data) {
1207
+ error(message, data) {
900
1208
  const sanitized = data ? sanitizeObject(data) : void 0;
901
1209
  console.error(`[ERROR] ${message}`, sanitized || "");
902
1210
  }
@@ -914,16 +1222,100 @@ function toSafeISOString(timestamp) {
914
1222
  }
915
1223
  return new Date(ts).toISOString();
916
1224
  }
1225
+ function getFileName(file, index) {
1226
+ if ("name" in file && typeof file.name === "string" && file.name) {
1227
+ return file.name;
1228
+ }
1229
+ return `file-${index}`;
1230
+ }
1231
+ function getFileFormat(mimeType) {
1232
+ if (!mimeType) {
1233
+ return void 0;
1234
+ }
1235
+ const [, subtype] = mimeType.split("/");
1236
+ if (!subtype) {
1237
+ return void 0;
1238
+ }
1239
+ return subtype.split(";")[0]?.trim().toLowerCase() || void 0;
1240
+ }
1241
+ function createPreviewUrl(file) {
1242
+ if (typeof URL === "undefined" || typeof URL.createObjectURL !== "function") {
1243
+ return void 0;
1244
+ }
1245
+ try {
1246
+ return URL.createObjectURL(file);
1247
+ } catch {
1248
+ return void 0;
1249
+ }
1250
+ }
1251
+ function buildMessageMediaPayload(files) {
1252
+ const images = [];
1253
+ const videos = [];
1254
+ const audio = [];
1255
+ const fileAttachments = [];
1256
+ const objectUrls = [];
1257
+ files.forEach((file, index) => {
1258
+ const filename = getFileName(file, index);
1259
+ const mimeType = file.type || "application/octet-stream";
1260
+ const format = getFileFormat(mimeType);
1261
+ const previewUrl = createPreviewUrl(file);
1262
+ if (previewUrl) {
1263
+ objectUrls.push(previewUrl);
1264
+ }
1265
+ if (mimeType.startsWith("image/") && previewUrl) {
1266
+ images.push({
1267
+ url: previewUrl,
1268
+ mime_type: mimeType,
1269
+ format
1270
+ });
1271
+ return;
1272
+ }
1273
+ if (mimeType.startsWith("video/")) {
1274
+ videos.push({
1275
+ url: previewUrl,
1276
+ id: filename,
1277
+ mime_type: mimeType,
1278
+ format
1279
+ });
1280
+ return;
1281
+ }
1282
+ if (mimeType.startsWith("audio/")) {
1283
+ audio.push({
1284
+ url: previewUrl,
1285
+ id: filename,
1286
+ mime_type: mimeType,
1287
+ format
1288
+ });
1289
+ return;
1290
+ }
1291
+ fileAttachments.push({
1292
+ filename,
1293
+ name: filename,
1294
+ mime_type: mimeType,
1295
+ format,
1296
+ size: file.size,
1297
+ url: previewUrl
1298
+ });
1299
+ });
1300
+ return {
1301
+ images: images.length > 0 ? images : void 0,
1302
+ videos: videos.length > 0 ? videos : void 0,
1303
+ audio: audio.length > 0 ? audio : void 0,
1304
+ files: fileAttachments.length > 0 ? fileAttachments : void 0,
1305
+ objectUrls
1306
+ };
1307
+ }
917
1308
  var AgnoClient = class extends import_eventemitter3.default {
918
1309
  constructor(config) {
919
1310
  super();
920
- // toolCallId -> UIComponentSpec
921
1311
  this.runCompletedSuccessfully = false;
1312
+ this.currentAbortController = null;
922
1313
  this.messageStore = new MessageStore();
923
1314
  this.configManager = new ConfigManager(config);
924
1315
  this.sessionManager = new SessionManager();
925
1316
  this.eventProcessor = new EventProcessor();
926
1317
  this.pendingUISpecs = /* @__PURE__ */ new Map();
1318
+ this.localAttachmentUrls = /* @__PURE__ */ new Set();
927
1319
  this.state = {
928
1320
  isStreaming: false,
929
1321
  isRefreshing: false,
@@ -966,6 +1358,8 @@ var AgnoClient = class extends import_eventemitter3.default {
966
1358
  * Clear all messages
967
1359
  */
968
1360
  clearMessages() {
1361
+ this.revokeAttachmentUrlsFromMessages(this.messageStore.getMessages());
1362
+ this.localAttachmentUrls.clear();
969
1363
  this.messageStore.clear();
970
1364
  this.configManager.setSessionId(void 0);
971
1365
  this.pendingUISpecs.clear();
@@ -979,42 +1373,84 @@ var AgnoClient = class extends import_eventemitter3.default {
979
1373
  if (!(this.state.isStreaming || this.state.isPaused)) {
980
1374
  throw new Error("No active or paused run to cancel");
981
1375
  }
1376
+ const runId = this.state.pausedRunId || this.currentRunId;
1377
+ if (this.currentAbortController) {
1378
+ this.currentAbortController.abort();
1379
+ this.currentAbortController = null;
1380
+ }
1381
+ this.state.isCancelling = true;
1382
+ this.emit("state:change", this.getState());
1383
+ const cancelErrorMessage = runId ? await this.requestBackendCancel(runId) : this.logMissingRunIdForCancel();
1384
+ this.state.isStreaming = false;
1385
+ this.state.isPaused = false;
1386
+ this.state.isCancelling = false;
1387
+ this.state.pausedRunId = void 0;
1388
+ this.state.toolsAwaitingExecution = void 0;
1389
+ this.currentRunId = void 0;
1390
+ if (cancelErrorMessage) {
1391
+ this.state.errorMessage = cancelErrorMessage;
1392
+ this.emit("message:error", cancelErrorMessage);
1393
+ }
1394
+ this.emit("run:cancelled", { runId });
1395
+ this.emit("state:change", this.getState());
1396
+ }
1397
+ logMissingRunIdForCancel() {
1398
+ Logger.warn(
1399
+ "[AgnoClient] No run ID available, skipping backend cancel request"
1400
+ );
1401
+ return void 0;
1402
+ }
1403
+ async requestBackendCancel(runId) {
982
1404
  const runUrl = this.configManager.getRunUrl();
983
1405
  if (!runUrl) {
984
- throw new Error("No agent or team selected");
985
- }
986
- const runId = this.state.pausedRunId || this.currentRunId;
987
- if (!runId) {
988
- throw new Error("No run ID available to cancel");
1406
+ const message = "Run cancelled locally, but backend cancel could not be sent: no agent or team selected";
1407
+ Logger.warn(`[AgnoClient] ${message}`);
1408
+ return message;
989
1409
  }
990
1410
  const cancelUrl = `${runUrl}/${runId}/cancel`;
991
1411
  const headers = this.configManager.buildRequestHeaders();
992
- this.state.isCancelling = true;
993
- this.emit("state:change", this.getState());
994
1412
  try {
995
1413
  const response = await fetch(cancelUrl, {
996
1414
  method: "POST",
997
1415
  headers
998
1416
  });
999
- if (!response.ok) {
1000
- throw new Error("Failed to cancel run");
1417
+ if (response.ok) {
1418
+ return void 0;
1001
1419
  }
1002
- this.state.isStreaming = false;
1003
- this.state.isPaused = false;
1004
- this.state.isCancelling = false;
1005
- this.state.pausedRunId = void 0;
1006
- this.state.toolsAwaitingExecution = void 0;
1007
- this.currentRunId = void 0;
1008
- this.emit("run:cancelled", { runId });
1009
- this.emit("state:change", this.getState());
1010
- } catch (error) {
1011
- this.state.isCancelling = false;
1012
- this.emit("state:change", this.getState());
1013
- throw new Error(
1014
- `Error cancelling run: ${error instanceof Error ? error.message : String(error)}`
1420
+ if (response.status === 401 || response.status === 403) {
1421
+ const message = `Run cancelled locally, but backend cancel was rejected (${response.status})`;
1422
+ Logger.warn(`[AgnoClient] ${message}`);
1423
+ return message;
1424
+ }
1425
+ Logger.warn(
1426
+ `[AgnoClient] Backend cancel returned ${response.status} \u2014 run may have already completed`
1015
1427
  );
1428
+ return void 0;
1429
+ } catch (error) {
1430
+ const reason = error instanceof Error ? error.message : String(error);
1431
+ const message = `Run cancelled locally, but backend cancel failed: ${reason}`;
1432
+ Logger.warn(`[AgnoClient] ${message}`);
1433
+ return message;
1016
1434
  }
1017
1435
  }
1436
+ /**
1437
+ * Abort the active stream without calling the backend cancel endpoint.
1438
+ * Since streamResponse handles AbortError by returning silently
1439
+ * (no onComplete/onError called), state cleanup is done here.
1440
+ */
1441
+ abortStream() {
1442
+ if (!this.state.isStreaming) {
1443
+ return;
1444
+ }
1445
+ if (this.currentAbortController) {
1446
+ this.currentAbortController.abort();
1447
+ this.currentAbortController = null;
1448
+ }
1449
+ this.state.isStreaming = false;
1450
+ this.currentRunId = void 0;
1451
+ this.emit("stream:end");
1452
+ this.emit("state:change", this.getState());
1453
+ }
1018
1454
  /**
1019
1455
  * Send a message to the agent/team (streaming)
1020
1456
  */
@@ -1035,16 +1471,30 @@ var AgnoClient = class extends import_eventemitter3.default {
1035
1471
  if (typeof message === "string") {
1036
1472
  formData.append("message", message);
1037
1473
  }
1474
+ if (options?.files) {
1475
+ options.files.forEach((file, index) => {
1476
+ formData.append("files", file, getFileName(file, index));
1477
+ });
1478
+ }
1479
+ const requestFiles = formData.getAll("files").filter((entry) => typeof entry !== "string");
1480
+ const userMessageMedia = buildMessageMediaPayload(requestFiles);
1481
+ this.trackAttachmentUrls(userMessageMedia.objectUrls);
1482
+ const userMessageContent = String(formData.get("message") ?? "");
1038
1483
  const lastMessage = this.messageStore.getLastMessage();
1039
1484
  if (lastMessage?.streamingError) {
1040
1485
  const secondLast = this.messageStore.getMessages()[this.messageStore.getMessages().length - 2];
1041
1486
  if (secondLast?.role === "user") {
1487
+ this.revokeAttachmentUrlsFromMessages([secondLast, lastMessage]);
1042
1488
  this.messageStore.removeLastMessages(2);
1043
1489
  }
1044
1490
  }
1045
1491
  this.messageStore.addMessage({
1046
1492
  role: "user",
1047
- content: formData.get("message"),
1493
+ content: userMessageContent,
1494
+ images: userMessageMedia.images,
1495
+ videos: userMessageMedia.videos,
1496
+ audio: userMessageMedia.audio,
1497
+ files: userMessageMedia.files,
1048
1498
  created_at: Math.floor(Date.now() / 1e3)
1049
1499
  });
1050
1500
  this.messageStore.addMessage({
@@ -1058,34 +1508,40 @@ var AgnoClient = class extends import_eventemitter3.default {
1058
1508
  this.eventProcessor.reset();
1059
1509
  let newSessionId = this.configManager.getSessionId();
1060
1510
  try {
1061
- formData.append("stream", "true");
1062
- formData.append("session_id", newSessionId ?? "");
1511
+ formData.set("stream", "true");
1512
+ formData.set("session_id", newSessionId ?? "");
1063
1513
  const userId = this.configManager.getUserId();
1064
1514
  if (userId) {
1065
- formData.append("user_id", userId);
1515
+ formData.set("user_id", userId);
1516
+ }
1517
+ const dependencies = this.configManager.buildDependencies(
1518
+ options?.dependencies
1519
+ );
1520
+ if (dependencies) {
1521
+ formData.set("dependencies", JSON.stringify(dependencies));
1066
1522
  }
1067
1523
  const headers = this.configManager.buildRequestHeaders(options?.headers);
1068
1524
  const params = this.configManager.buildQueryString(options?.params);
1525
+ this.currentAbortController = new AbortController();
1069
1526
  await streamResponse({
1070
1527
  apiUrl: runUrl,
1071
1528
  headers,
1072
1529
  params,
1073
1530
  requestBody: formData,
1531
+ signal: this.currentAbortController.signal,
1074
1532
  onChunk: (chunk) => {
1075
- this.handleChunk(
1076
- chunk,
1077
- newSessionId,
1078
- formData.get("message")
1079
- );
1533
+ this.handleChunk(chunk, newSessionId, userMessageContent);
1080
1534
  if ((chunk.event === import_agno_types2.RunEvent.RunStarted || chunk.event === import_agno_types2.RunEvent.TeamRunStarted || chunk.event === import_agno_types2.RunEvent.ReasoningStarted || chunk.event === import_agno_types2.RunEvent.TeamReasoningStarted) && chunk.session_id) {
1081
1535
  newSessionId = chunk.session_id;
1082
1536
  this.configManager.setSessionId(chunk.session_id);
1083
1537
  }
1084
1538
  },
1085
1539
  onError: (error) => {
1540
+ this.currentAbortController = null;
1086
1541
  this.handleError(error, newSessionId);
1087
1542
  },
1088
1543
  onComplete: async () => {
1544
+ this.currentAbortController = null;
1089
1545
  this.state.isStreaming = false;
1090
1546
  this.currentRunId = void 0;
1091
1547
  this.emit("stream:end");
@@ -1098,6 +1554,7 @@ var AgnoClient = class extends import_eventemitter3.default {
1098
1554
  }
1099
1555
  });
1100
1556
  } catch (error) {
1557
+ this.currentAbortController = null;
1101
1558
  this.handleError(
1102
1559
  error instanceof Error ? error : new Error(String(error)),
1103
1560
  newSessionId
@@ -1183,6 +1640,73 @@ var AgnoClient = class extends import_eventemitter3.default {
1183
1640
  this.emit("stream:end");
1184
1641
  this.emit("state:change", this.getState());
1185
1642
  }
1643
+ trackAttachmentUrls(urls) {
1644
+ for (const url of urls) {
1645
+ this.localAttachmentUrls.add(url);
1646
+ }
1647
+ }
1648
+ collectAttachmentUrls(message) {
1649
+ const imageUrls = message.images?.map((image) => image.url) ?? [];
1650
+ const videoUrls = message.videos?.map((video) => video.url).filter((url) => Boolean(url)) ?? [];
1651
+ const audioUrls = message.audio?.map((audio) => audio.url).filter((url) => Boolean(url)) ?? [];
1652
+ const fileUrls = message.files?.map((file) => file.url).filter((url) => Boolean(url)) ?? [];
1653
+ return [...imageUrls, ...videoUrls, ...audioUrls, ...fileUrls];
1654
+ }
1655
+ revokeAttachmentUrls(urls) {
1656
+ if (typeof URL === "undefined" || typeof URL.revokeObjectURL !== "function") {
1657
+ return;
1658
+ }
1659
+ for (const url of urls) {
1660
+ if (!this.localAttachmentUrls.has(url)) {
1661
+ continue;
1662
+ }
1663
+ URL.revokeObjectURL(url);
1664
+ this.localAttachmentUrls.delete(url);
1665
+ }
1666
+ }
1667
+ revokeAttachmentUrlsFromMessages(messages) {
1668
+ const urls = [];
1669
+ for (const message of messages) {
1670
+ if (!message) {
1671
+ continue;
1672
+ }
1673
+ urls.push(...this.collectAttachmentUrls(message));
1674
+ }
1675
+ this.revokeAttachmentUrls(urls);
1676
+ }
1677
+ collectExistingUIComponents() {
1678
+ const existingUIComponents = /* @__PURE__ */ new Map();
1679
+ for (const message of this.messageStore.getMessages()) {
1680
+ if (!message.tool_calls) {
1681
+ continue;
1682
+ }
1683
+ for (const toolCall of message.tool_calls) {
1684
+ if (toolCall.ui_component) {
1685
+ existingUIComponents.set(
1686
+ toolCall.tool_call_id,
1687
+ toolCall.ui_component
1688
+ );
1689
+ }
1690
+ }
1691
+ }
1692
+ return existingUIComponents;
1693
+ }
1694
+ restoreUIComponents(messages, uiComponents) {
1695
+ if (uiComponents.size === 0) {
1696
+ return;
1697
+ }
1698
+ for (const message of messages) {
1699
+ if (!message.tool_calls) {
1700
+ continue;
1701
+ }
1702
+ for (const toolCall of message.tool_calls) {
1703
+ const uiComponent = uiComponents.get(toolCall.tool_call_id);
1704
+ if (uiComponent) {
1705
+ toolCall.ui_component = uiComponent;
1706
+ }
1707
+ }
1708
+ }
1709
+ }
1186
1710
  /**
1187
1711
  * Refresh messages from the session API after run completion.
1188
1712
  * Replaces streamed messages with authoritative session data.
@@ -1195,22 +1719,14 @@ var AgnoClient = class extends import_eventemitter3.default {
1195
1719
  Logger.debug("[AgnoClient] Cannot refresh: no session ID");
1196
1720
  return;
1197
1721
  }
1722
+ if (this.state.isStreaming) {
1723
+ Logger.debug("[AgnoClient] Skipping refresh: stream is active");
1724
+ return;
1725
+ }
1198
1726
  this.state.isRefreshing = true;
1199
1727
  this.emit("state:change", this.getState());
1200
1728
  try {
1201
- const existingUIComponents = /* @__PURE__ */ new Map();
1202
- for (const message of this.messageStore.getMessages()) {
1203
- if (message.tool_calls) {
1204
- for (const toolCall of message.tool_calls) {
1205
- if (toolCall.ui_component) {
1206
- existingUIComponents.set(
1207
- toolCall.tool_call_id,
1208
- toolCall.ui_component
1209
- );
1210
- }
1211
- }
1212
- }
1213
- }
1729
+ const existingUIComponents = this.collectExistingUIComponents();
1214
1730
  const config = this.configManager.getConfig();
1215
1731
  const entityType = this.configManager.getMode();
1216
1732
  const dbId = this.configManager.getDbId() || "";
@@ -1226,22 +1742,15 @@ var AgnoClient = class extends import_eventemitter3.default {
1226
1742
  userId,
1227
1743
  params
1228
1744
  );
1229
- const messages = this.sessionManager.convertSessionToMessages(response);
1230
- if (existingUIComponents.size > 0) {
1231
- for (const message of messages) {
1232
- if (message.tool_calls) {
1233
- for (let i = 0; i < message.tool_calls.length; i++) {
1234
- const toolCall = message.tool_calls[i];
1235
- const uiComponent = existingUIComponents.get(
1236
- toolCall.tool_call_id
1237
- );
1238
- if (uiComponent) {
1239
- message.tool_calls[i].ui_component = uiComponent;
1240
- }
1241
- }
1242
- }
1243
- }
1745
+ if (this.state.isStreaming) {
1746
+ Logger.debug(
1747
+ "[AgnoClient] Aborting refresh: stream started during fetch"
1748
+ );
1749
+ return;
1244
1750
  }
1751
+ const messages = this.sessionManager.convertSessionToMessages(response);
1752
+ this.restoreUIComponents(messages, existingUIComponents);
1753
+ this.revokeAttachmentUrlsFromMessages(this.messageStore.getMessages());
1245
1754
  this.messageStore.setMessages(messages);
1246
1755
  Logger.debug(
1247
1756
  "[AgnoClient] Session refreshed:",
@@ -1290,6 +1799,7 @@ var AgnoClient = class extends import_eventemitter3.default {
1290
1799
  "[AgnoClient] Setting messages to store:",
1291
1800
  `${messages.length} messages`
1292
1801
  );
1802
+ this.revokeAttachmentUrlsFromMessages(this.messageStore.getMessages());
1293
1803
  this.messageStore.setMessages(messages);
1294
1804
  this.configManager.setSessionId(sessionId);
1295
1805
  Logger.debug("[AgnoClient] Emitting events...");
@@ -1441,9 +1951,9 @@ var AgnoClient = class extends import_eventemitter3.default {
1441
1951
  }
1442
1952
  }
1443
1953
  if (updatedMessages.length > 0) {
1444
- updatedMessages.forEach(({ index, message }) => {
1954
+ for (const { index, message } of updatedMessages) {
1445
1955
  this.messageStore.updateMessage(index, () => message);
1446
- });
1956
+ }
1447
1957
  this.emit("message:update", this.messageStore.getMessages());
1448
1958
  }
1449
1959
  }
@@ -1471,15 +1981,48 @@ var AgnoClient = class extends import_eventemitter3.default {
1471
1981
  if (!runUrl) {
1472
1982
  throw new Error("No agent or team selected");
1473
1983
  }
1474
- const continueUrl = `${runUrl}/${this.state.pausedRunId}/continue`;
1475
- this.state.isPaused = false;
1984
+ const pausedRunId = this.state.pausedRunId;
1985
+ const continueUrl = `${runUrl}/${pausedRunId}/continue`;
1476
1986
  this.state.isStreaming = true;
1477
- this.emit("run:continued", { runId: this.state.pausedRunId });
1987
+ this.state.errorMessage = void 0;
1478
1988
  this.emit("state:change", this.getState());
1479
- const cleanedTools = tools.map((tool) => {
1480
- const { ui_component, ...backendTool } = tool;
1481
- return backendTool;
1482
- });
1989
+ let hasContinued = false;
1990
+ let streamError;
1991
+ const markRunContinued = (runId) => {
1992
+ if (hasContinued) {
1993
+ return;
1994
+ }
1995
+ hasContinued = true;
1996
+ this.state.isPaused = false;
1997
+ this.state.toolsAwaitingExecution = void 0;
1998
+ this.emit("run:continued", { runId: runId || pausedRunId });
1999
+ this.emit("state:change", this.getState());
2000
+ };
2001
+ const handleContinueError = (error) => {
2002
+ streamError = error;
2003
+ this.state.isStreaming = false;
2004
+ this.state.errorMessage = error.message;
2005
+ const isConflictError = error instanceof StreamResponseHttpError && error.status === 409;
2006
+ if (isConflictError || !hasContinued) {
2007
+ this.state.isPaused = true;
2008
+ } else {
2009
+ this.state.isPaused = false;
2010
+ this.state.pausedRunId = void 0;
2011
+ this.state.toolsAwaitingExecution = void 0;
2012
+ this.messageStore.updateLastMessage((msg) => ({
2013
+ ...msg,
2014
+ streamingError: true
2015
+ }));
2016
+ }
2017
+ this.emit("message:error", error.message);
2018
+ this.emit("stream:end");
2019
+ this.emit("state:change", this.getState());
2020
+ };
2021
+ const cleanedTools = tools.map(
2022
+ ({ ui_component: _uiComponent, ...tool }) => {
2023
+ return tool;
2024
+ }
2025
+ );
1483
2026
  const formData = new FormData();
1484
2027
  formData.append("tools", JSON.stringify(cleanedTools));
1485
2028
  formData.append("stream", "true");
@@ -1493,36 +2036,45 @@ var AgnoClient = class extends import_eventemitter3.default {
1493
2036
  }
1494
2037
  const headers = this.configManager.buildRequestHeaders(options?.headers);
1495
2038
  const params = this.configManager.buildQueryString(options?.params);
1496
- try {
1497
- await streamResponse({
1498
- apiUrl: continueUrl,
1499
- headers,
1500
- params,
1501
- requestBody: formData,
1502
- onChunk: (chunk) => {
1503
- this.handleChunk(chunk, currentSessionId, "");
1504
- },
1505
- onError: (error) => {
1506
- this.handleError(error, currentSessionId);
1507
- },
1508
- onComplete: async () => {
1509
- this.state.isStreaming = false;
2039
+ this.currentAbortController = new AbortController();
2040
+ await streamResponse({
2041
+ apiUrl: continueUrl,
2042
+ headers,
2043
+ params,
2044
+ requestBody: formData,
2045
+ signal: this.currentAbortController.signal,
2046
+ onChunk: (chunk) => {
2047
+ const event = chunk.event;
2048
+ if (!hasContinued && event !== import_agno_types2.RunEvent.RunPaused) {
2049
+ markRunContinued(chunk.run_id);
2050
+ }
2051
+ this.handleChunk(chunk, currentSessionId, "");
2052
+ },
2053
+ onError: (error) => {
2054
+ this.currentAbortController = null;
2055
+ handleContinueError(error);
2056
+ },
2057
+ onComplete: async () => {
2058
+ this.currentAbortController = null;
2059
+ if (!(hasContinued || this.state.isPaused)) {
2060
+ markRunContinued(pausedRunId);
2061
+ }
2062
+ this.state.isStreaming = false;
2063
+ if (!this.state.isPaused) {
1510
2064
  this.state.pausedRunId = void 0;
1511
2065
  this.state.toolsAwaitingExecution = void 0;
1512
- this.emit("stream:end");
1513
- this.emit("message:complete", this.messageStore.getMessages());
1514
- this.emit("state:change", this.getState());
1515
- if (this.runCompletedSuccessfully) {
1516
- this.runCompletedSuccessfully = false;
1517
- await this.refreshSessionMessages();
1518
- }
1519
2066
  }
1520
- });
1521
- } catch (error) {
1522
- this.handleError(
1523
- error instanceof Error ? error : new Error(String(error)),
1524
- currentSessionId
1525
- );
2067
+ this.emit("stream:end");
2068
+ this.emit("message:complete", this.messageStore.getMessages());
2069
+ this.emit("state:change", this.getState());
2070
+ if (this.runCompletedSuccessfully) {
2071
+ this.runCompletedSuccessfully = false;
2072
+ await this.refreshSessionMessages();
2073
+ }
2074
+ }
2075
+ });
2076
+ if (streamError) {
2077
+ throw streamError;
1526
2078
  }
1527
2079
  }
1528
2080
  /**
@@ -1634,7 +2186,13 @@ var AgnoClient = class extends import_eventemitter3.default {
1634
2186
  * After calling dispose(), the client instance should not be reused.
1635
2187
  */
1636
2188
  dispose() {
2189
+ if (this.currentAbortController) {
2190
+ this.currentAbortController.abort();
2191
+ this.currentAbortController = null;
2192
+ }
1637
2193
  this.removeAllListeners();
2194
+ this.revokeAttachmentUrlsFromMessages(this.messageStore.getMessages());
2195
+ this.localAttachmentUrls.clear();
1638
2196
  this.messageStore.clear();
1639
2197
  this.pendingUISpecs.clear();
1640
2198
  this.eventProcessor.reset();