@automattic/agenttic-client 0.1.9 → 0.1.11

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
@@ -113,8 +113,8 @@ function createTextPart(text) {
113
113
  text
114
114
  };
115
115
  }
116
- function createSendMessageRequest(params, method = "message/send") {
117
- return {
116
+ function createSendMessageRequest(params, method = "message/send", tokenStreaming = false) {
117
+ const request = {
118
118
  jsonrpc: "2.0",
119
119
  id: createRequestId(),
120
120
  method,
@@ -123,6 +123,10 @@ function createSendMessageRequest(params, method = "message/send") {
123
123
  ...params
124
124
  }
125
125
  };
126
+ if (tokenStreaming && method === "message/stream") {
127
+ request.tokenStreaming = true;
128
+ }
129
+ return request;
126
130
  }
127
131
  function extractTextFromMessage(message) {
128
132
  if (!message || !message.parts || !Array.isArray(message.parts)) {
@@ -218,6 +222,9 @@ function createToolResultMessage(toolResults, historyDataParts = []) {
218
222
  }
219
223
  };
220
224
  }
225
+ function createAbortController() {
226
+ return new AbortController();
227
+ }
221
228
 
222
229
  // src/client/utils/internal/messages.ts
223
230
  async function enhanceMessageWithTools(message, toolProvider) {
@@ -312,10 +319,14 @@ function parseStreamChunk(chunk, buffer = "") {
312
319
  const nextBuffer = currentStreamData.substring(lastCompleteEventEnd);
313
320
  return { events, nextBuffer };
314
321
  }
315
- async function* parseSSEStream(stream) {
322
+ async function* parseSSEStream(stream, options = {}) {
323
+ const { supportDeltas = false } = options;
316
324
  const reader = stream.getReader();
317
325
  const decoder = new TextDecoder();
318
326
  let buffer = "";
327
+ const accumulator = new DeltaAccumulator();
328
+ let currentTaskId = null;
329
+ let lastStatus = null;
319
330
  try {
320
331
  while (true) {
321
332
  const { done, value } = await reader.read();
@@ -325,13 +336,50 @@ async function* parseSSEStream(stream) {
325
336
  const chunk = decoder.decode(value, { stream: true });
326
337
  const { events, nextBuffer } = parseStreamChunk(chunk, buffer);
327
338
  if (events && Array.isArray(events)) {
328
- for (const event of events) {
339
+ for (let i = 0; i < events.length; i++) {
340
+ const event = events[i];
341
+ if (i > 0 && event.method === "message/delta" && typeof requestAnimationFrame !== "undefined") {
342
+ await new Promise((resolve) => {
343
+ requestAnimationFrame(() => resolve(void 0));
344
+ });
345
+ }
329
346
  if (event.error) {
330
347
  throw new Error(
331
348
  `Streaming error: ${event.error.message}`
332
349
  );
333
350
  }
334
- if (event.result && event.result.status) {
351
+ if (supportDeltas && event.method === "message/delta" && event.params?.delta) {
352
+ const delta = event.params.delta;
353
+ try {
354
+ if (delta.deltaType === "content") {
355
+ accumulator.processContentDelta(
356
+ delta.content
357
+ );
358
+ if (!currentTaskId && event.params.id) {
359
+ currentTaskId = event.params.id;
360
+ }
361
+ if (currentTaskId) {
362
+ const currentMessage = accumulator.getCurrentMessage();
363
+ yield {
364
+ id: currentTaskId,
365
+ status: {
366
+ state: "working",
367
+ message: currentMessage
368
+ },
369
+ final: false,
370
+ text: accumulator.getTextContent()
371
+ };
372
+ }
373
+ }
374
+ } catch (error) {
375
+ logger("Failed to process delta: %o", error);
376
+ }
377
+ } else if (event.result && event.result.status) {
378
+ currentTaskId = event.result.id;
379
+ lastStatus = event.result.status;
380
+ if (accumulator.getTextContent() || accumulator.getCurrentMessage().parts.length > 0) {
381
+ accumulator.reset();
382
+ }
335
383
  const update = {
336
384
  id: event.result.id,
337
385
  status: event.result.status,
@@ -344,6 +392,22 @@ async function* parseSSEStream(stream) {
344
392
  )
345
393
  };
346
394
  yield update;
395
+ } else if (event.id && event.result) {
396
+ currentTaskId = event.result.id;
397
+ if (event.result.status) {
398
+ const update = {
399
+ id: event.result.id,
400
+ status: event.result.status,
401
+ final: event.result.status.state === "completed" || event.result.status.state === "failed" || event.result.status.state === "canceled",
402
+ text: extractTextFromMessage(
403
+ event.result.status?.message || {
404
+ role: "agent",
405
+ parts: []
406
+ }
407
+ )
408
+ };
409
+ yield update;
410
+ }
347
411
  }
348
412
  }
349
413
  }
@@ -353,6 +417,103 @@ async function* parseSSEStream(stream) {
353
417
  reader.releaseLock();
354
418
  }
355
419
  }
420
+ var DeltaAccumulator = class {
421
+ textContent = "";
422
+ toolCalls = /* @__PURE__ */ new Map();
423
+ /**
424
+ * Process a simple content delta (server's actual format)
425
+ * @param content - The text content to append
426
+ */
427
+ processContentDelta(content) {
428
+ this.textContent += content;
429
+ }
430
+ /**
431
+ * Process a delta message and accumulate the content (original format)
432
+ * @param delta - The delta message to process
433
+ */
434
+ processDelta(delta) {
435
+ switch (delta.type) {
436
+ case "content":
437
+ this.textContent += delta.content;
438
+ break;
439
+ case "tool_name":
440
+ if (!this.toolCalls.has(delta.toolCallIndex)) {
441
+ this.toolCalls.set(delta.toolCallIndex, {
442
+ toolCallId: delta.toolCallId,
443
+ toolName: "",
444
+ argumentFragments: []
445
+ });
446
+ }
447
+ const toolCall = this.toolCalls.get(delta.toolCallIndex);
448
+ toolCall.toolName += delta.content;
449
+ break;
450
+ case "tool_argument":
451
+ if (!this.toolCalls.has(delta.toolCallIndex)) {
452
+ this.toolCalls.set(delta.toolCallIndex, {
453
+ toolCallId: delta.toolCallId,
454
+ toolName: "",
455
+ argumentFragments: []
456
+ });
457
+ }
458
+ const call = this.toolCalls.get(delta.toolCallIndex);
459
+ call.argumentFragments.push(delta.content);
460
+ break;
461
+ }
462
+ }
463
+ /**
464
+ * Get the accumulated text content
465
+ */
466
+ getTextContent() {
467
+ return this.textContent;
468
+ }
469
+ /**
470
+ * Get the current accumulated message
471
+ * @param role - The role for the message (default: 'agent')
472
+ */
473
+ getCurrentMessage(role = "agent") {
474
+ const parts = [];
475
+ if (this.textContent) {
476
+ parts.push({
477
+ type: "text",
478
+ text: this.textContent
479
+ });
480
+ }
481
+ for (const [index, toolCall] of this.toolCalls) {
482
+ if (toolCall.toolName) {
483
+ const argumentsStr = toolCall.argumentFragments.join("");
484
+ let args = {};
485
+ if (argumentsStr) {
486
+ try {
487
+ args = JSON.parse(argumentsStr);
488
+ } catch {
489
+ args = { _raw: argumentsStr };
490
+ }
491
+ }
492
+ parts.push({
493
+ type: "data",
494
+ data: {
495
+ toolCallId: toolCall.toolCallId,
496
+ toolId: toolCall.toolName,
497
+ arguments: args
498
+ }
499
+ });
500
+ }
501
+ }
502
+ return {
503
+ role,
504
+ parts,
505
+ kind: "message",
506
+ messageId: generateMessageId()
507
+ };
508
+ }
509
+ /**
510
+ * Reset the accumulator
511
+ */
512
+ reset() {
513
+ this.textContent = "";
514
+ this.toolCalls.clear();
515
+ }
516
+ };
356
517
 
357
518
  // src/client/utils/internal/errors.ts
358
519
  function handleRequestError(error, timeoutId, operation = "request") {
@@ -417,6 +578,32 @@ async function getHeaders(authProvider, isStreaming = false) {
417
578
  }
418
579
  return baseHeaders;
419
580
  }
581
+ function combineSignals(signal1, signal2) {
582
+ if (!signal2) {
583
+ return signal1;
584
+ }
585
+ const controller = new AbortController();
586
+ const handleAbort = (signal) => {
587
+ if (!controller.signal.aborted) {
588
+ controller.abort(signal.reason);
589
+ }
590
+ };
591
+ if (signal1.aborted) {
592
+ controller.abort(signal1.reason);
593
+ } else {
594
+ signal1.addEventListener("abort", () => handleAbort(signal1), {
595
+ once: true
596
+ });
597
+ }
598
+ if (signal2.aborted) {
599
+ controller.abort(signal2.reason);
600
+ } else {
601
+ signal2.addEventListener("abort", () => handleAbort(signal2), {
602
+ once: true
603
+ });
604
+ }
605
+ return controller.signal;
606
+ }
420
607
  function createFetchOptions(headers, body, signal) {
421
608
  return {
422
609
  method: "POST",
@@ -428,7 +615,7 @@ function createFetchOptions(headers, body, signal) {
428
615
  async function prepareRequest(params, config, options, toolProvider, contextProvider, defaultSessionId) {
429
616
  const { message, sessionId, taskId, metadata } = params;
430
617
  const { agentId, agentUrl, authProvider, proxy } = config;
431
- const { isStreaming = false } = options;
618
+ const { isStreaming = false, enableTokenStreaming = false } = options;
432
619
  const effectiveSessionId = sessionId || defaultSessionId;
433
620
  const fullAgentUrl = constructAgentUrl(agentUrl, agentId);
434
621
  const enhancedMessage = await enhanceMessage(
@@ -443,7 +630,9 @@ async function prepareRequest(params, config, options, toolProvider, contextProv
443
630
  message: enhancedMessage,
444
631
  metadata
445
632
  },
446
- isStreaming ? "message/stream" : "message/send"
633
+ isStreaming ? "message/stream" : "message/send",
634
+ enableTokenStreaming && isStreaming
635
+ // Only enable token streaming if using SSE
447
636
  );
448
637
  const headers = await getHeaders(authProvider, isStreaming);
449
638
  logRequest("POST", fullAgentUrl, headers, request);
@@ -455,24 +644,26 @@ async function prepareRequest(params, config, options, toolProvider, contextProv
455
644
  fullAgentUrl
456
645
  };
457
646
  }
458
- async function executeRequest(preparedRequest, config) {
647
+ async function executeRequest(preparedRequest, config, options = {}) {
459
648
  const { request, headers, fullAgentUrl } = preparedRequest;
460
649
  const { timeout } = config;
461
- const { timeoutId, controller } = createTimeoutHandler(
650
+ const { abortSignal: externalSignal } = options;
651
+ const { timeoutId, controller: timeoutController } = createTimeoutHandler(
462
652
  timeout,
463
653
  "request"
464
654
  );
655
+ const signal = externalSignal ? combineSignals(timeoutController.signal, externalSignal) : timeoutController.signal;
465
656
  try {
466
- const options = createFetchOptions(
657
+ const fetchOptions = createFetchOptions(
467
658
  headers,
468
659
  JSON.stringify(request),
469
- controller.signal
660
+ signal
470
661
  );
471
662
  logger("Making request to %s with options: %O", fullAgentUrl, {
472
- method: options.method,
473
- headers: options.headers
663
+ method: fetchOptions.method,
664
+ headers: fetchOptions.headers
474
665
  });
475
- const response = await fetch(fullAgentUrl, options);
666
+ const response = await fetch(fullAgentUrl, fetchOptions);
476
667
  clearTimeout(timeoutId);
477
668
  validateHttpResponse(response, "request");
478
669
  const data = await response.json();
@@ -490,21 +681,31 @@ async function executeRequest(preparedRequest, config) {
490
681
  async function* executeStreamingRequest(preparedRequest, config, options) {
491
682
  const { request, headers, fullAgentUrl } = preparedRequest;
492
683
  const {} = config;
493
- const { streamingTimeout = 6e4 } = options;
684
+ const {
685
+ streamingTimeout = 6e4,
686
+ abortSignal: externalSignal,
687
+ enableTokenStreaming = false
688
+ } = options;
494
689
  const { timeoutId, controller } = createTimeoutHandler(
495
690
  streamingTimeout,
496
691
  "streaming request"
497
692
  );
693
+ const signal = externalSignal ? combineSignals(controller.signal, externalSignal) : controller.signal;
498
694
  try {
499
- const fetchOptions = createFetchOptions(
500
- headers,
501
- JSON.stringify(request),
502
- controller.signal
503
- );
695
+ const requestBody = JSON.stringify(request);
696
+ const fetchOptions = createFetchOptions(headers, requestBody, signal);
504
697
  const response = await fetch(fullAgentUrl, fetchOptions);
505
698
  clearTimeout(timeoutId);
506
699
  validateStreamingResponse(response, "streaming request");
507
- yield* parseSSEStream(response.body);
700
+ if (!response.body) {
701
+ throw new Error(
702
+ "Response body is null - server may not support streaming"
703
+ );
704
+ }
705
+ const supportDeltas = enableTokenStreaming && request.tokenStreaming === true;
706
+ yield* parseSSEStream(response.body, {
707
+ supportDeltas
708
+ });
508
709
  } catch (error) {
509
710
  handleRequestError(error, timeoutId, "streaming request");
510
711
  }
@@ -512,6 +713,7 @@ async function* executeStreamingRequest(preparedRequest, config, options) {
512
713
 
513
714
  // src/client/index.ts
514
715
  var DEFAULT_TIMEOUT = 12e4;
716
+ var toolResultPromises = /* @__PURE__ */ new Map();
515
717
  async function hasMatchingToolCallbacks(toolProvider, message) {
516
718
  if (!toolProvider || !message || !toolProvider.getAvailableTools) {
517
719
  return false;
@@ -535,6 +737,32 @@ async function hasMatchingToolCallbacks(toolProvider, message) {
535
737
  }
536
738
  return false;
537
739
  }
740
+ function clearToolResultPromises() {
741
+ toolResultPromises.clear();
742
+ }
743
+ function updateToolResultsWithResolvedPromises(toolResults) {
744
+ return toolResults.map((toolResult) => {
745
+ const toolCallId = toolResult.data.toolCallId;
746
+ const promiseEntry = toolResultPromises.get(toolCallId);
747
+ if (promiseEntry && promiseEntry.resolvedValue !== null) {
748
+ const resolvedValue = promiseEntry.resolvedValue;
749
+ if (resolvedValue.error) {
750
+ return createToolResultDataPart(
751
+ toolCallId,
752
+ toolResult.data.toolId,
753
+ void 0,
754
+ resolvedValue.error
755
+ );
756
+ }
757
+ return createToolResultDataPart(
758
+ toolCallId,
759
+ toolResult.data.toolId,
760
+ resolvedValue
761
+ );
762
+ }
763
+ return toolResult;
764
+ });
765
+ }
538
766
  async function executeToolCallBatch(toolCalls, toolProvider, messageId) {
539
767
  const results = [];
540
768
  const agentMessages = [];
@@ -595,7 +823,7 @@ function conversationHistoryToDataParts(conversationHistory) {
595
823
  }
596
824
  return historyParts;
597
825
  }
598
- async function continueTask(taskId, message, requestConfig, toolProvider, contextProvider, sessionId) {
826
+ async function continueTask(taskId, message, requestConfig, toolProvider, contextProvider, sessionId, abortSignal) {
599
827
  const continueParams = {
600
828
  message,
601
829
  taskId,
@@ -605,30 +833,37 @@ async function continueTask(taskId, message, requestConfig, toolProvider, contex
605
833
  const preparedRequest = await prepareRequest(
606
834
  continueParams,
607
835
  requestConfig,
608
- { isStreaming: false },
836
+ { isStreaming: false, abortSignal },
609
837
  toolProvider,
610
838
  contextProvider,
611
839
  sessionId
612
840
  );
613
- return await executeRequest(preparedRequest, requestConfig);
841
+ return await executeRequest(preparedRequest, requestConfig, {
842
+ abortSignal
843
+ });
614
844
  }
615
- async function continueTaskStreamed(taskId, message, requestConfig, toolProvider, contextProvider, sessionId) {
845
+ async function continueTaskStreamed(taskId, message, requestConfig, toolProvider, contextProvider, sessionId, abortSignal, requestOptions) {
616
846
  const continueParams = {
617
847
  message,
618
848
  taskId,
619
849
  sessionId: void 0
620
850
  // Use task's session
621
851
  };
852
+ const options = requestOptions || { isStreaming: true };
622
853
  const preparedRequest = await prepareRequest(
623
854
  continueParams,
624
855
  requestConfig,
625
- { isStreaming: true },
856
+ {
857
+ ...options,
858
+ abortSignal
859
+ },
626
860
  toolProvider,
627
861
  contextProvider,
628
862
  sessionId
629
863
  );
630
864
  const stream = executeStreamingRequest(preparedRequest, requestConfig, {
631
- isStreaming: true
865
+ ...options,
866
+ abortSignal
632
867
  });
633
868
  return processAgentResponseStream(
634
869
  stream,
@@ -638,11 +873,14 @@ async function continueTaskStreamed(taskId, message, requestConfig, toolProvider
638
873
  sessionId,
639
874
  true,
640
875
  // withHistory
641
- []
876
+ [],
642
877
  // newConversationParts - empty for continueTask
878
+ abortSignal,
879
+ options
880
+ // Pass through the same request options
643
881
  );
644
882
  }
645
- async function* processAgentResponseStream(stream, toolProvider, contextProvider, requestConfig, sessionId, withHistory = true, newConversationParts = []) {
883
+ async function* processAgentResponseStream(stream, toolProvider, contextProvider, requestConfig, sessionId, withHistory = true, newConversationParts = [], abortSignal, requestOptions) {
646
884
  for await (const update of stream) {
647
885
  yield update;
648
886
  if (update.status.state === "running" && update.status.message && toolProvider && await hasMatchingToolCallbacks(
@@ -712,6 +950,28 @@ async function* processAgentResponseStream(stream, toolProvider, contextProvider
712
950
  createAgentTextMessage(agentMessage)
713
951
  );
714
952
  }
953
+ if (result.result instanceof Promise) {
954
+ const promise = result.result;
955
+ const promiseEntry = {
956
+ promise,
957
+ resolvedValue: null
958
+ };
959
+ toolResultPromises.set(
960
+ toolCallId,
961
+ promiseEntry
962
+ );
963
+ promise.then((resolvedValue) => {
964
+ promiseEntry.resolvedValue = resolvedValue;
965
+ }).catch((error) => {
966
+ console.error(
967
+ `Promise rejected for tool call ${toolCallId}:`,
968
+ error
969
+ );
970
+ promiseEntry.resolvedValue = {
971
+ error: error instanceof Error ? error.message : String(error)
972
+ };
973
+ });
974
+ }
715
975
  const toolResult = createToolResultDataPart(
716
976
  toolCallId,
717
977
  toolId,
@@ -752,7 +1012,9 @@ async function* processAgentResponseStream(stream, toolProvider, contextProvider
752
1012
  requestConfig,
753
1013
  toolProvider,
754
1014
  contextProvider,
755
- sessionId
1015
+ sessionId,
1016
+ abortSignal,
1017
+ requestOptions
756
1018
  );
757
1019
  let continuedTaskUpdate = null;
758
1020
  for await (const streamUpdate of continuedTaskStream) {
@@ -837,7 +1099,9 @@ async function* processAgentResponseStream(stream, toolProvider, contextProvider
837
1099
  requestConfig,
838
1100
  toolProvider,
839
1101
  contextProvider,
840
- sessionId
1102
+ sessionId,
1103
+ abortSignal,
1104
+ requestOptions
841
1105
  );
842
1106
  let moreFinalTask = null;
843
1107
  for await (const streamUpdate of moreTaskStream) {
@@ -930,7 +1194,8 @@ function createClient(config) {
930
1194
  defaultSessionId,
931
1195
  timeout = DEFAULT_TIMEOUT,
932
1196
  toolProvider,
933
- contextProvider
1197
+ contextProvider,
1198
+ enableStreaming = false
934
1199
  } = config;
935
1200
  const requestConfig = {
936
1201
  agentId,
@@ -940,21 +1205,22 @@ function createClient(config) {
940
1205
  };
941
1206
  return {
942
1207
  async sendMessage(params) {
943
- const { withHistory = true } = params;
1208
+ const { withHistory = true, abortSignal } = params;
944
1209
  const sessionId = params.sessionId || defaultSessionId || void 0;
945
1210
  const newConversationParts = [];
946
1211
  newConversationParts.push(params.message);
947
1212
  const preparedRequest = await prepareRequest(
948
1213
  params,
949
1214
  requestConfig,
950
- { isStreaming: false },
1215
+ { isStreaming: false, abortSignal },
951
1216
  toolProvider,
952
1217
  contextProvider,
953
1218
  sessionId
954
1219
  );
955
1220
  let currentTask = await executeRequest(
956
1221
  preparedRequest,
957
- requestConfig
1222
+ requestConfig,
1223
+ { abortSignal }
958
1224
  );
959
1225
  const allToolParts = [];
960
1226
  const agentMessages = [];
@@ -1015,7 +1281,8 @@ function createClient(config) {
1015
1281
  requestConfig,
1016
1282
  toolProvider,
1017
1283
  contextProvider,
1018
- sessionId
1284
+ sessionId,
1285
+ abortSignal
1019
1286
  );
1020
1287
  } else {
1021
1288
  break;
@@ -1061,14 +1328,26 @@ function createClient(config) {
1061
1328
  };
1062
1329
  },
1063
1330
  async *sendMessageStream(params) {
1064
- const { withHistory = true } = params;
1331
+ const {
1332
+ withHistory = true,
1333
+ abortSignal,
1334
+ enableStreaming: requestStreaming
1335
+ } = params;
1065
1336
  const sessionId = params.sessionId || defaultSessionId || void 0;
1337
+ const useTokenStreaming = requestStreaming ?? enableStreaming;
1066
1338
  const newConversationParts = [];
1067
1339
  newConversationParts.push(params.message);
1068
1340
  const preparedRequest = await prepareRequest(
1069
1341
  params,
1070
1342
  requestConfig,
1071
- { isStreaming: true, streamingTimeout: timeout },
1343
+ {
1344
+ isStreaming: true,
1345
+ // Always use message/stream endpoint for SSE
1346
+ enableTokenStreaming: useTokenStreaming,
1347
+ // Optional token-by-token streaming
1348
+ streamingTimeout: timeout,
1349
+ abortSignal
1350
+ },
1072
1351
  toolProvider,
1073
1352
  contextProvider,
1074
1353
  sessionId
@@ -1078,7 +1357,10 @@ function createClient(config) {
1078
1357
  requestConfig,
1079
1358
  {
1080
1359
  isStreaming: true,
1081
- streamingTimeout: timeout
1360
+ enableTokenStreaming: useTokenStreaming,
1361
+ // Token streaming is optional
1362
+ streamingTimeout: timeout,
1363
+ abortSignal
1082
1364
  }
1083
1365
  );
1084
1366
  yield* processAgentResponseStream(
@@ -1088,7 +1370,13 @@ function createClient(config) {
1088
1370
  requestConfig,
1089
1371
  sessionId,
1090
1372
  withHistory,
1091
- newConversationParts
1373
+ newConversationParts,
1374
+ abortSignal,
1375
+ {
1376
+ isStreaming: true,
1377
+ enableTokenStreaming: useTokenStreaming,
1378
+ streamingTimeout: timeout
1379
+ }
1092
1380
  );
1093
1381
  },
1094
1382
  async continueTask(taskId, userInput, sessionId) {
@@ -1378,6 +1666,31 @@ function extractToolResultsFromMessage(message) {
1378
1666
  }
1379
1667
 
1380
1668
  // src/react/agentManager.ts
1669
+ async function resolvePromisesInConversationHistory(conversationHistory) {
1670
+ const resolvedHistory = [];
1671
+ for (const message of conversationHistory) {
1672
+ if (message.parts && Array.isArray(message.parts)) {
1673
+ const hasToolResults = message.parts.some(
1674
+ (part) => part.type === "data" && "toolCallId" in part.data && "result" in part.data
1675
+ );
1676
+ if (hasToolResults) {
1677
+ const updatedParts = updateToolResultsWithResolvedPromises(
1678
+ message.parts
1679
+ );
1680
+ resolvedHistory.push({
1681
+ ...message,
1682
+ parts: updatedParts
1683
+ });
1684
+ } else {
1685
+ resolvedHistory.push(message);
1686
+ }
1687
+ } else {
1688
+ resolvedHistory.push(message);
1689
+ }
1690
+ }
1691
+ clearToolResultPromises();
1692
+ return resolvedHistory;
1693
+ }
1381
1694
  function createAgentManager() {
1382
1695
  const agents = /* @__PURE__ */ new Map();
1383
1696
  async function persistConversationHistory(key, messages) {
@@ -1506,9 +1819,20 @@ function createAgentManager() {
1506
1819
  ...managedAgent.conversationHistory
1507
1820
  ];
1508
1821
  let currentToolCallIds = [];
1822
+ const resolvedConversationHistory = await resolvePromisesInConversationHistory(
1823
+ currentConversationHistory
1824
+ );
1825
+ managedAgent.conversationHistory = resolvedConversationHistory;
1826
+ currentConversationHistory = resolvedConversationHistory;
1827
+ if (withHistory) {
1828
+ await persistConversationHistory(
1829
+ key,
1830
+ resolvedConversationHistory
1831
+ );
1832
+ }
1509
1833
  const messageObj = options.message || createTextMessageWithHistory(
1510
1834
  message,
1511
- currentConversationHistory
1835
+ resolvedConversationHistory
1512
1836
  );
1513
1837
  const userMessage = createTextMessage(message);
1514
1838
  currentConversationHistory = [
@@ -1632,7 +1956,7 @@ function getAgentManager() {
1632
1956
  }
1633
1957
 
1634
1958
  // src/utils/createMessageRenderer.tsx
1635
- import Markdown from "react-markdown";
1959
+ import Markdown from "streamdown";
1636
1960
 
1637
1961
  // src/markdown-extensions/index.ts
1638
1962
  import remarkGfm from "remark-gfm";
@@ -1699,30 +2023,40 @@ var createFeedbackActions = (config) => {
1699
2023
  }
1700
2024
  const feedbackState = feedbackByMessageId[message.id];
1701
2025
  const actions = [];
1702
- if (feedbackState !== "down") {
1703
- actions.push({
1704
- id: "feedback-up",
1705
- icon: config.icons.up,
1706
- label: "Good response",
1707
- onClick: async () => {
2026
+ actions.push({
2027
+ id: "feedback-up",
2028
+ icon: config.icons.up,
2029
+ label: "Good response",
2030
+ onClick: async () => {
2031
+ if (feedbackState === "up") {
2032
+ delete feedbackByMessageId[message.id];
2033
+ } else {
1708
2034
  await handleFeedback(message.id, "up");
1709
- },
1710
- tooltip: "This response was helpful",
1711
- disabled: feedbackState === "up"
1712
- });
1713
- }
1714
- if (feedbackState !== "up") {
1715
- actions.push({
1716
- id: "feedback-down",
1717
- icon: config.icons.down,
1718
- label: "Bad response",
1719
- onClick: async () => {
2035
+ }
2036
+ notifyListeners();
2037
+ },
2038
+ tooltip: "This response was helpful",
2039
+ pressed: feedbackState === "up",
2040
+ disabled: feedbackState === "down"
2041
+ // Disable if other is selected
2042
+ });
2043
+ actions.push({
2044
+ id: "feedback-down",
2045
+ icon: config.icons.down,
2046
+ label: "Bad response",
2047
+ onClick: async () => {
2048
+ if (feedbackState === "down") {
2049
+ delete feedbackByMessageId[message.id];
2050
+ } else {
1720
2051
  await handleFeedback(message.id, "down");
1721
- },
1722
- tooltip: "This response was not helpful",
1723
- disabled: feedbackState === "down"
1724
- });
1725
- }
2052
+ }
2053
+ notifyListeners();
2054
+ },
2055
+ tooltip: "This response was not helpful",
2056
+ pressed: feedbackState === "down",
2057
+ disabled: feedbackState === "up"
2058
+ // Disable if other is selected
2059
+ });
1726
2060
  return actions;
1727
2061
  },
1728
2062
  clearFeedback: (messageId) => {
@@ -1800,7 +2134,9 @@ function resolveActionsForMessage(message, registrations) {
1800
2134
  icon: action.icon,
1801
2135
  onClick: action.onClick,
1802
2136
  tooltip: action.tooltip,
1803
- disabled: action.disabled || false
2137
+ disabled: action.disabled || false,
2138
+ pressed: action.pressed,
2139
+ showLabel: action.showLabel
1804
2140
  }));
1805
2141
  return filteredActions;
1806
2142
  }
@@ -1904,7 +2240,8 @@ function useAgentChat(config) {
1904
2240
  error: isValidConfig ? null : "Invalid agent configuration",
1905
2241
  suggestions: [],
1906
2242
  markdownComponents: {},
1907
- markdownExtensions: {}
2243
+ markdownExtensions: {},
2244
+ currentAbortController: null
1908
2245
  });
1909
2246
  const {
1910
2247
  registerMessageActions,
@@ -1932,7 +2269,8 @@ function useAgentChat(config) {
1932
2269
  sessionId: agentConfig.sessionId,
1933
2270
  contextProvider: config.contextProvider || createNoOpContextProvider(),
1934
2271
  toolProvider: config.toolProvider || createNoOpToolProvider(),
1935
- authProvider: config.authProvider
2272
+ authProvider: config.authProvider,
2273
+ enableStreaming: config.enableStreaming
1936
2274
  });
1937
2275
  const clientHistory = agentManager.getConversationHistory(agentKey);
1938
2276
  setState((prev) => {
@@ -1961,6 +2299,7 @@ function useAgentChat(config) {
1961
2299
  config.contextProvider,
1962
2300
  config.toolProvider,
1963
2301
  config.authProvider,
2302
+ config.enableStreaming,
1964
2303
  isValidConfig
1965
2304
  ]);
1966
2305
  const onSubmit = useCallback3(
@@ -1979,51 +2318,143 @@ function useAgentChat(config) {
1979
2318
  archived: false,
1980
2319
  showIcon: false
1981
2320
  };
2321
+ const abortController = new AbortController();
1982
2322
  setState((prev) => ({
1983
2323
  ...prev,
1984
2324
  uiMessages: [...prev.uiMessages, userMessage],
1985
2325
  isProcessing: true,
1986
- error: null
2326
+ error: null,
2327
+ currentAbortController: abortController
1987
2328
  }));
1988
2329
  try {
1989
- let lastUpdate = null;
2330
+ let streamingMessageId = null;
2331
+ let finalMessageAdded = false;
1990
2332
  for await (const update of agentManager.sendMessageStream(
1991
2333
  agentKey,
1992
- message
2334
+ message,
2335
+ {
2336
+ abortSignal: abortController.signal
2337
+ }
1993
2338
  )) {
1994
- lastUpdate = update;
1995
- }
1996
- const updatedClientHistory = agentManager.getConversationHistory(agentKey);
1997
- setState((prev) => {
1998
- const transformedClientMessages = updatedClientHistory.map(
1999
- (msg) => transformClientMessageToUI(
2000
- msg,
2339
+ if (!update.final && update.text) {
2340
+ if (!streamingMessageId) {
2341
+ streamingMessageId = `agent-streaming-${Date.now()}`;
2342
+ const streamingMessage = {
2343
+ id: streamingMessageId,
2344
+ role: "agent",
2345
+ content: [
2346
+ { type: "text", text: update.text }
2347
+ ],
2348
+ timestamp: Date.now(),
2349
+ archived: false,
2350
+ showIcon: true,
2351
+ icon: "assistant"
2352
+ };
2353
+ setState((prev) => ({
2354
+ ...prev,
2355
+ uiMessages: [
2356
+ ...prev.uiMessages,
2357
+ streamingMessage
2358
+ ]
2359
+ }));
2360
+ } else {
2361
+ setState((prev) => ({
2362
+ ...prev,
2363
+ uiMessages: prev.uiMessages.map(
2364
+ (msg) => msg.id === streamingMessageId ? {
2365
+ ...msg,
2366
+ content: [
2367
+ {
2368
+ type: "text",
2369
+ text: update.text
2370
+ }
2371
+ ]
2372
+ } : msg
2373
+ )
2374
+ }));
2375
+ }
2376
+ }
2377
+ if (update.final && update.status?.message && streamingMessageId) {
2378
+ finalMessageAdded = true;
2379
+ const currentStreamingId = streamingMessageId;
2380
+ const finalMessage = transformClientMessageToUI(
2381
+ update.status.message,
2001
2382
  registrationsRef.current
2002
- )
2003
- ).filter((msg) => msg !== null);
2004
- const clientMessageIds = new Set(
2005
- updatedClientHistory.map((msg) => msg.messageId)
2006
- );
2007
- const uiOnlyMessages = prev.uiMessages.filter(
2008
- (msg) => !clientMessageIds.has(msg.id) && msg.content[0]?.type === "component"
2009
- );
2010
- const mergedUIMessages = sortUIMessagesByTime([
2011
- ...transformedClientMessages,
2012
- ...uiOnlyMessages
2013
- ]);
2014
- return {
2015
- ...prev,
2016
- clientMessages: updatedClientHistory,
2017
- uiMessages: mergedUIMessages,
2018
- isProcessing: false
2019
- };
2020
- });
2383
+ );
2384
+ if (finalMessage) {
2385
+ setState((prev) => {
2386
+ const updatedMessages = prev.uiMessages.map(
2387
+ (msg) => msg.id === currentStreamingId ? finalMessage : msg
2388
+ );
2389
+ const updatedClientHistory = agentManager.getConversationHistory(
2390
+ agentKey
2391
+ );
2392
+ return {
2393
+ ...prev,
2394
+ clientMessages: updatedClientHistory,
2395
+ uiMessages: updatedMessages,
2396
+ isProcessing: false,
2397
+ currentAbortController: null
2398
+ };
2399
+ });
2400
+ }
2401
+ streamingMessageId = null;
2402
+ }
2403
+ }
2404
+ if (!finalMessageAdded) {
2405
+ const updatedClientHistory = agentManager.getConversationHistory(agentKey);
2406
+ setState((prev) => {
2407
+ let filteredMessages = prev.uiMessages;
2408
+ if (streamingMessageId) {
2409
+ filteredMessages = prev.uiMessages.filter(
2410
+ (msg) => msg.id !== streamingMessageId
2411
+ );
2412
+ }
2413
+ const transformedClientMessages = updatedClientHistory.map(
2414
+ (msg) => transformClientMessageToUI(
2415
+ msg,
2416
+ registrationsRef.current
2417
+ )
2418
+ ).filter(
2419
+ (msg) => msg !== null
2420
+ );
2421
+ const clientMessageIds = new Set(
2422
+ updatedClientHistory.map((msg) => msg.messageId)
2423
+ );
2424
+ const uiOnlyMessages = filteredMessages.filter(
2425
+ (msg) => !clientMessageIds.has(msg.id) && msg.content[0]?.type === "component"
2426
+ );
2427
+ const mergedUIMessages = sortUIMessagesByTime([
2428
+ ...transformedClientMessages,
2429
+ ...uiOnlyMessages
2430
+ ]);
2431
+ return {
2432
+ ...prev,
2433
+ clientMessages: updatedClientHistory,
2434
+ uiMessages: mergedUIMessages,
2435
+ isProcessing: false,
2436
+ currentAbortController: null
2437
+ };
2438
+ });
2439
+ }
2021
2440
  } catch (error) {
2441
+ if (error instanceof Error && error.name === "AbortError") {
2442
+ console.log("Request was aborted by user");
2443
+ setState((prev) => ({
2444
+ ...prev,
2445
+ isProcessing: false,
2446
+ error: null,
2447
+ // Don't show error for user-initiated abort
2448
+ currentAbortController: null
2449
+ }));
2450
+ return;
2451
+ }
2022
2452
  const errorMessage = error instanceof Error ? error.message : "Failed to send message";
2023
2453
  setState((prev) => ({
2024
2454
  ...prev,
2025
2455
  isProcessing: false,
2026
- error: errorMessage
2456
+ error: errorMessage,
2457
+ currentAbortController: null
2027
2458
  }));
2028
2459
  throw error;
2029
2460
  }
@@ -2142,6 +2573,14 @@ function useAgentChat(config) {
2142
2573
  }),
2143
2574
  [state.markdownComponents, state.markdownExtensions]
2144
2575
  );
2576
+ const abortCurrentRequest = useCallback3(() => {
2577
+ setState((currentState) => {
2578
+ if (currentState.currentAbortController) {
2579
+ currentState.currentAbortController.abort();
2580
+ }
2581
+ return currentState;
2582
+ });
2583
+ }, []);
2145
2584
  return {
2146
2585
  // AgentUI props
2147
2586
  messages: state.uiMessages,
@@ -2161,7 +2600,9 @@ function useAgentChat(config) {
2161
2600
  clearAllMessageActions,
2162
2601
  createFeedbackActions: createFeedbackActions2,
2163
2602
  // Tool integration
2164
- addMessage
2603
+ addMessage,
2604
+ // Abort control
2605
+ abortCurrentRequest
2165
2606
  };
2166
2607
  }
2167
2608
 
@@ -2266,6 +2707,7 @@ export {
2266
2707
  BarChart,
2267
2708
  ChartBlock,
2268
2709
  LineChart,
2710
+ createAbortController,
2269
2711
  createClient,
2270
2712
  createFeedbackActions,
2271
2713
  createJetpackAuthProvider,