@antipopp/agno-client 0.2.0 → 0.4.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/README.md CHANGED
@@ -28,6 +28,7 @@ const client = new AgnoClient({
28
28
  mode: 'agent',
29
29
  agentId: 'your-agent-id',
30
30
  authToken: 'optional-auth-token',
31
+ userId: 'user-123', // Optional: Link sessions to a user
31
32
  });
32
33
 
33
34
  // Listen to message updates
@@ -66,6 +67,7 @@ new AgnoClient(config: AgnoClientConfig)
66
67
  - `teamId` (string, optional) - Team ID (required if mode is 'team')
67
68
  - `dbId` (string, optional) - Database ID
68
69
  - `sessionId` (string, optional) - Current session ID
70
+ - `userId` (string, optional) - User ID to link sessions to a specific user
69
71
 
70
72
  ### Methods
71
73
 
@@ -136,6 +138,7 @@ Update client configuration.
136
138
  client.updateConfig({
137
139
  agentId: 'new-agent-id',
138
140
  authToken: 'new-token',
141
+ userId: 'user-456', // Update user ID
139
142
  });
140
143
  ```
141
144
 
package/dist/index.d.mts CHANGED
@@ -12,6 +12,7 @@ declare class AgnoClient extends EventEmitter {
12
12
  private sessionManager;
13
13
  private eventProcessor;
14
14
  private state;
15
+ private pendingUISpecs;
15
16
  constructor(config: AgnoClientConfig);
16
17
  /**
17
18
  * Get current messages
@@ -63,6 +64,22 @@ declare class AgnoClient extends EventEmitter {
63
64
  * Delete a team session
64
65
  */
65
66
  deleteTeamSession(teamId: string, sessionId: string): Promise<void>;
67
+ /**
68
+ * Add tool calls to the last message
69
+ * Used by frontend execution to add tool calls that were executed locally
70
+ */
71
+ addToolCallsToLastMessage(toolCalls: ToolCall[]): void;
72
+ /**
73
+ * Hydrate a specific tool call with its UI component
74
+ * If tool call doesn't exist yet, stores UI spec as pending
75
+ */
76
+ hydrateToolCallUI(toolCallId: string, uiSpec: any): void;
77
+ /**
78
+ * Apply any pending UI specs to tool calls that have just been added
79
+ * Called after message updates to attach UI to newly arrived tool calls
80
+ * Batches all updates to emit only one message:update event
81
+ */
82
+ private applyPendingUISpecs;
66
83
  /**
67
84
  * Continue a paused run after executing external tools
68
85
  */
package/dist/index.d.ts CHANGED
@@ -12,6 +12,7 @@ declare class AgnoClient extends EventEmitter {
12
12
  private sessionManager;
13
13
  private eventProcessor;
14
14
  private state;
15
+ private pendingUISpecs;
15
16
  constructor(config: AgnoClientConfig);
16
17
  /**
17
18
  * Get current messages
@@ -63,6 +64,22 @@ declare class AgnoClient extends EventEmitter {
63
64
  * Delete a team session
64
65
  */
65
66
  deleteTeamSession(teamId: string, sessionId: string): Promise<void>;
67
+ /**
68
+ * Add tool calls to the last message
69
+ * Used by frontend execution to add tool calls that were executed locally
70
+ */
71
+ addToolCallsToLastMessage(toolCalls: ToolCall[]): void;
72
+ /**
73
+ * Hydrate a specific tool call with its UI component
74
+ * If tool call doesn't exist yet, stores UI spec as pending
75
+ */
76
+ hydrateToolCallUI(toolCallId: string, uiSpec: any): void;
77
+ /**
78
+ * Apply any pending UI specs to tool calls that have just been added
79
+ * Called after message updates to attach UI to newly arrived tool calls
80
+ * Batches all updates to emit only one message:update event
81
+ */
82
+ private applyPendingUISpecs;
66
83
  /**
67
84
  * Continue a paused run after executing external tools
68
85
  */
package/dist/index.js CHANGED
@@ -77,6 +77,21 @@ var MessageStore = class {
77
77
  ];
78
78
  return updatedMessage;
79
79
  }
80
+ /**
81
+ * Update a specific message by index
82
+ */
83
+ updateMessage(index, updater) {
84
+ if (index < 0 || index >= this.messages.length)
85
+ return void 0;
86
+ const message = this.messages[index];
87
+ const updatedMessage = updater(message);
88
+ this.messages = [
89
+ ...this.messages.slice(0, index),
90
+ updatedMessage,
91
+ ...this.messages.slice(index + 1)
92
+ ];
93
+ return updatedMessage;
94
+ }
80
95
  /**
81
96
  * Remove last N messages
82
97
  */
@@ -211,6 +226,18 @@ var ConfigManager = class {
211
226
  setSessionId(sessionId) {
212
227
  this.updateField("sessionId", sessionId);
213
228
  }
229
+ /**
230
+ * Get user ID
231
+ */
232
+ getUserId() {
233
+ return this.config.userId;
234
+ }
235
+ /**
236
+ * Set user ID
237
+ */
238
+ setUserId(userId) {
239
+ this.updateField("userId", userId);
240
+ }
214
241
  /**
215
242
  * Get current entity ID (agent or team based on mode)
216
243
  */
@@ -321,9 +348,7 @@ var SessionManager = class {
321
348
  * Convert session runs array to chat messages
322
349
  */
323
350
  convertSessionToMessages(runs) {
324
- console.log("[SessionManager] convertSessionToMessages received:", runs.length, "runs");
325
351
  const messages = this.convertRunsToMessages(runs);
326
- console.log("[SessionManager] Converted to messages:", messages.length, "messages");
327
352
  return messages;
328
353
  }
329
354
  /**
@@ -345,7 +370,7 @@ var SessionManager = class {
345
370
  if (run.tools && Array.isArray(run.tools)) {
346
371
  for (const tool of run.tools) {
347
372
  const toolObj = tool;
348
- toolCalls.push({
373
+ const toolCall = {
349
374
  role: "tool",
350
375
  content: toolObj.content ?? "",
351
376
  tool_call_id: toolObj.tool_call_id ?? "",
@@ -354,7 +379,8 @@ var SessionManager = class {
354
379
  tool_call_error: toolObj.tool_call_error ?? false,
355
380
  metrics: toolObj.metrics ?? { time: 0 },
356
381
  created_at: timestamp
357
- });
382
+ };
383
+ toolCalls.push(toolCall);
358
384
  }
359
385
  }
360
386
  if (run.reasoning_messages && Array.isArray(run.reasoning_messages)) {
@@ -501,6 +527,28 @@ var EventProcessor = class {
501
527
  references: chunk.extra_data.references
502
528
  };
503
529
  }
530
+ if (chunk.extra_data?.generated_ui) {
531
+ const existingUI = updatedMessage.extra_data?.generated_ui ?? [];
532
+ const incomingUI = chunk.extra_data.generated_ui;
533
+ const mergedUI = [...existingUI];
534
+ for (const uiData of incomingUI) {
535
+ const existingIndex = mergedUI.findIndex(
536
+ (ui) => ui.tool_call_id === uiData.tool_call_id
537
+ );
538
+ if (existingIndex >= 0) {
539
+ mergedUI[existingIndex] = {
540
+ ...mergedUI[existingIndex],
541
+ ...uiData
542
+ };
543
+ } else {
544
+ mergedUI.push(uiData);
545
+ }
546
+ }
547
+ updatedMessage.extra_data = {
548
+ ...updatedMessage.extra_data,
549
+ generated_ui: mergedUI
550
+ };
551
+ }
504
552
  updatedMessage.created_at = chunk.created_at ?? lastMessage.created_at;
505
553
  if (chunk.images) {
506
554
  updatedMessage.images = chunk.images;
@@ -560,7 +608,8 @@ var EventProcessor = class {
560
608
  updatedMessage.created_at = chunk.created_at ?? lastMessage.created_at;
561
609
  updatedMessage.extra_data = {
562
610
  reasoning_steps: chunk.extra_data?.reasoning_steps ?? lastMessage.extra_data?.reasoning_steps,
563
- references: chunk.extra_data?.references ?? lastMessage.extra_data?.references
611
+ references: chunk.extra_data?.references ?? lastMessage.extra_data?.references,
612
+ generated_ui: chunk.extra_data?.generated_ui ?? lastMessage.extra_data?.generated_ui
564
613
  };
565
614
  break;
566
615
  case import_agno_types.RunEvent.UpdatingMemory:
@@ -818,12 +867,14 @@ function toSafeISOString(timestamp) {
818
867
  return new Date(ts).toISOString();
819
868
  }
820
869
  var AgnoClient = class extends import_eventemitter3.default {
870
+ // toolCallId -> UIComponentSpec
821
871
  constructor(config) {
822
872
  super();
823
873
  this.messageStore = new MessageStore();
824
874
  this.configManager = new ConfigManager(config);
825
875
  this.sessionManager = new SessionManager();
826
876
  this.eventProcessor = new EventProcessor();
877
+ this.pendingUISpecs = /* @__PURE__ */ new Map();
827
878
  this.state = {
828
879
  isStreaming: false,
829
880
  isEndpointActive: false,
@@ -866,6 +917,7 @@ var AgnoClient = class extends import_eventemitter3.default {
866
917
  clearMessages() {
867
918
  this.messageStore.clear();
868
919
  this.configManager.setSessionId(void 0);
920
+ this.pendingUISpecs.clear();
869
921
  this.emit("message:update", this.messageStore.getMessages());
870
922
  this.emit("state:change", this.getState());
871
923
  }
@@ -913,6 +965,10 @@ var AgnoClient = class extends import_eventemitter3.default {
913
965
  try {
914
966
  formData.append("stream", "true");
915
967
  formData.append("session_id", newSessionId ?? "");
968
+ const userId = this.configManager.getUserId();
969
+ if (userId) {
970
+ formData.append("user_id", userId);
971
+ }
916
972
  const headers = { ...options?.headers };
917
973
  const authToken = this.configManager.getAuthToken();
918
974
  if (authToken) {
@@ -970,18 +1026,10 @@ var AgnoClient = class extends import_eventemitter3.default {
970
1026
  }
971
1027
  }
972
1028
  if (event === import_agno_types2.RunEvent.RunPaused) {
973
- console.log("[AgnoClient] RunPaused event detected");
974
- console.log("[AgnoClient] Chunk:", chunk);
975
- console.log("[AgnoClient] tools_awaiting_external_execution:", chunk.tools_awaiting_external_execution);
976
- console.log("[AgnoClient] tools_requiring_confirmation:", chunk.tools_requiring_confirmation);
977
- console.log("[AgnoClient] tools_requiring_user_input:", chunk.tools_requiring_user_input);
978
- console.log("[AgnoClient] tools:", chunk.tools);
979
1029
  this.state.isStreaming = false;
980
1030
  this.state.isPaused = true;
981
1031
  this.state.pausedRunId = chunk.run_id;
982
1032
  this.state.toolsAwaitingExecution = chunk.tools_awaiting_external_execution || chunk.tools_requiring_confirmation || chunk.tools_requiring_user_input || chunk.tools || [];
983
- console.log("[AgnoClient] toolsAwaitingExecution:", this.state.toolsAwaitingExecution);
984
- console.log("[AgnoClient] Emitting run:paused event");
985
1033
  this.emit("run:paused", {
986
1034
  runId: chunk.run_id,
987
1035
  sessionId: chunk.session_id,
@@ -1009,6 +1057,7 @@ var AgnoClient = class extends import_eventemitter3.default {
1009
1057
  const updated = this.eventProcessor.processChunk(chunk, lastMessage);
1010
1058
  return updated || lastMessage;
1011
1059
  });
1060
+ this.applyPendingUISpecs();
1012
1061
  this.emit("message:update", this.messageStore.getMessages());
1013
1062
  }
1014
1063
  /**
@@ -1118,6 +1167,103 @@ var AgnoClient = class extends import_eventemitter3.default {
1118
1167
  }
1119
1168
  this.emit("state:change", this.getState());
1120
1169
  }
1170
+ /**
1171
+ * Add tool calls to the last message
1172
+ * Used by frontend execution to add tool calls that were executed locally
1173
+ */
1174
+ addToolCallsToLastMessage(toolCalls) {
1175
+ const lastMessage = this.messageStore.getLastMessage();
1176
+ if (!lastMessage || lastMessage.role !== "agent") {
1177
+ return;
1178
+ }
1179
+ const existingToolCalls = lastMessage.tool_calls || [];
1180
+ const existingIds = new Set(existingToolCalls.map((t) => t.tool_call_id));
1181
+ const newToolCalls = toolCalls.filter((t) => !existingIds.has(t.tool_call_id));
1182
+ if (newToolCalls.length > 0) {
1183
+ this.messageStore.updateLastMessage((msg) => ({
1184
+ ...msg,
1185
+ tool_calls: [...existingToolCalls, ...newToolCalls]
1186
+ }));
1187
+ this.emit("message:update", this.messageStore.getMessages());
1188
+ }
1189
+ }
1190
+ /**
1191
+ * Hydrate a specific tool call with its UI component
1192
+ * If tool call doesn't exist yet, stores UI spec as pending
1193
+ */
1194
+ hydrateToolCallUI(toolCallId, uiSpec) {
1195
+ const messages = this.messageStore.getMessages();
1196
+ for (let i = messages.length - 1; i >= 0; i--) {
1197
+ const message = messages[i];
1198
+ if (message.tool_calls) {
1199
+ const toolIndex = message.tool_calls.findIndex(
1200
+ (t) => t.tool_call_id === toolCallId
1201
+ );
1202
+ if (toolIndex !== -1) {
1203
+ this.messageStore.updateMessage(i, (msg) => {
1204
+ const updatedToolCalls = [...msg.tool_calls || []];
1205
+ updatedToolCalls[toolIndex] = {
1206
+ ...updatedToolCalls[toolIndex],
1207
+ ui_component: uiSpec
1208
+ };
1209
+ return {
1210
+ ...msg,
1211
+ tool_calls: updatedToolCalls
1212
+ };
1213
+ });
1214
+ this.pendingUISpecs.delete(toolCallId);
1215
+ this.emit("message:update", this.messageStore.getMessages());
1216
+ return;
1217
+ }
1218
+ }
1219
+ }
1220
+ this.pendingUISpecs.set(toolCallId, uiSpec);
1221
+ }
1222
+ /**
1223
+ * Apply any pending UI specs to tool calls that have just been added
1224
+ * Called after message updates to attach UI to newly arrived tool calls
1225
+ * Batches all updates to emit only one message:update event
1226
+ */
1227
+ applyPendingUISpecs() {
1228
+ if (this.pendingUISpecs.size === 0)
1229
+ return;
1230
+ const messages = this.messageStore.getMessages();
1231
+ const updatedMessages = [];
1232
+ for (let i = messages.length - 1; i >= 0; i--) {
1233
+ const message = messages[i];
1234
+ if (message.tool_calls) {
1235
+ let messageUpdated = false;
1236
+ const updatedToolCalls = [...message.tool_calls];
1237
+ for (let j = 0; j < updatedToolCalls.length; j++) {
1238
+ const toolCall = updatedToolCalls[j];
1239
+ const pendingUI = this.pendingUISpecs.get(toolCall.tool_call_id);
1240
+ if (pendingUI && !toolCall.ui_component) {
1241
+ updatedToolCalls[j] = {
1242
+ ...updatedToolCalls[j],
1243
+ ui_component: pendingUI
1244
+ };
1245
+ this.pendingUISpecs.delete(toolCall.tool_call_id);
1246
+ messageUpdated = true;
1247
+ }
1248
+ }
1249
+ if (messageUpdated) {
1250
+ updatedMessages.push({
1251
+ index: i,
1252
+ message: {
1253
+ ...message,
1254
+ tool_calls: updatedToolCalls
1255
+ }
1256
+ });
1257
+ }
1258
+ }
1259
+ }
1260
+ if (updatedMessages.length > 0) {
1261
+ updatedMessages.forEach(({ index, message }) => {
1262
+ this.messageStore.updateMessage(index, () => message);
1263
+ });
1264
+ this.emit("message:update", this.messageStore.getMessages());
1265
+ }
1266
+ }
1121
1267
  /**
1122
1268
  * Continue a paused run after executing external tools
1123
1269
  */
@@ -1134,13 +1280,21 @@ var AgnoClient = class extends import_eventemitter3.default {
1134
1280
  this.state.isStreaming = true;
1135
1281
  this.emit("run:continued", { runId: this.state.pausedRunId });
1136
1282
  this.emit("state:change", this.getState());
1283
+ const cleanedTools = tools.map((tool) => {
1284
+ const { ui_component, ...backendTool } = tool;
1285
+ return backendTool;
1286
+ });
1137
1287
  const formData = new FormData();
1138
- formData.append("tools", JSON.stringify(tools));
1288
+ formData.append("tools", JSON.stringify(cleanedTools));
1139
1289
  formData.append("stream", "true");
1140
1290
  const currentSessionId = this.configManager.getSessionId();
1141
1291
  if (currentSessionId) {
1142
1292
  formData.append("session_id", currentSessionId);
1143
1293
  }
1294
+ const userId = this.configManager.getUserId();
1295
+ if (userId) {
1296
+ formData.append("user_id", userId);
1297
+ }
1144
1298
  const headers = { ...options?.headers };
1145
1299
  const authToken = this.configManager.getAuthToken();
1146
1300
  if (authToken) {
package/dist/index.mjs CHANGED
@@ -39,6 +39,21 @@ var MessageStore = class {
39
39
  ];
40
40
  return updatedMessage;
41
41
  }
42
+ /**
43
+ * Update a specific message by index
44
+ */
45
+ updateMessage(index, updater) {
46
+ if (index < 0 || index >= this.messages.length)
47
+ return void 0;
48
+ const message = this.messages[index];
49
+ const updatedMessage = updater(message);
50
+ this.messages = [
51
+ ...this.messages.slice(0, index),
52
+ updatedMessage,
53
+ ...this.messages.slice(index + 1)
54
+ ];
55
+ return updatedMessage;
56
+ }
42
57
  /**
43
58
  * Remove last N messages
44
59
  */
@@ -173,6 +188,18 @@ var ConfigManager = class {
173
188
  setSessionId(sessionId) {
174
189
  this.updateField("sessionId", sessionId);
175
190
  }
191
+ /**
192
+ * Get user ID
193
+ */
194
+ getUserId() {
195
+ return this.config.userId;
196
+ }
197
+ /**
198
+ * Set user ID
199
+ */
200
+ setUserId(userId) {
201
+ this.updateField("userId", userId);
202
+ }
176
203
  /**
177
204
  * Get current entity ID (agent or team based on mode)
178
205
  */
@@ -283,9 +310,7 @@ var SessionManager = class {
283
310
  * Convert session runs array to chat messages
284
311
  */
285
312
  convertSessionToMessages(runs) {
286
- console.log("[SessionManager] convertSessionToMessages received:", runs.length, "runs");
287
313
  const messages = this.convertRunsToMessages(runs);
288
- console.log("[SessionManager] Converted to messages:", messages.length, "messages");
289
314
  return messages;
290
315
  }
291
316
  /**
@@ -307,7 +332,7 @@ var SessionManager = class {
307
332
  if (run.tools && Array.isArray(run.tools)) {
308
333
  for (const tool of run.tools) {
309
334
  const toolObj = tool;
310
- toolCalls.push({
335
+ const toolCall = {
311
336
  role: "tool",
312
337
  content: toolObj.content ?? "",
313
338
  tool_call_id: toolObj.tool_call_id ?? "",
@@ -316,7 +341,8 @@ var SessionManager = class {
316
341
  tool_call_error: toolObj.tool_call_error ?? false,
317
342
  metrics: toolObj.metrics ?? { time: 0 },
318
343
  created_at: timestamp
319
- });
344
+ };
345
+ toolCalls.push(toolCall);
320
346
  }
321
347
  }
322
348
  if (run.reasoning_messages && Array.isArray(run.reasoning_messages)) {
@@ -463,6 +489,28 @@ var EventProcessor = class {
463
489
  references: chunk.extra_data.references
464
490
  };
465
491
  }
492
+ if (chunk.extra_data?.generated_ui) {
493
+ const existingUI = updatedMessage.extra_data?.generated_ui ?? [];
494
+ const incomingUI = chunk.extra_data.generated_ui;
495
+ const mergedUI = [...existingUI];
496
+ for (const uiData of incomingUI) {
497
+ const existingIndex = mergedUI.findIndex(
498
+ (ui) => ui.tool_call_id === uiData.tool_call_id
499
+ );
500
+ if (existingIndex >= 0) {
501
+ mergedUI[existingIndex] = {
502
+ ...mergedUI[existingIndex],
503
+ ...uiData
504
+ };
505
+ } else {
506
+ mergedUI.push(uiData);
507
+ }
508
+ }
509
+ updatedMessage.extra_data = {
510
+ ...updatedMessage.extra_data,
511
+ generated_ui: mergedUI
512
+ };
513
+ }
466
514
  updatedMessage.created_at = chunk.created_at ?? lastMessage.created_at;
467
515
  if (chunk.images) {
468
516
  updatedMessage.images = chunk.images;
@@ -522,7 +570,8 @@ var EventProcessor = class {
522
570
  updatedMessage.created_at = chunk.created_at ?? lastMessage.created_at;
523
571
  updatedMessage.extra_data = {
524
572
  reasoning_steps: chunk.extra_data?.reasoning_steps ?? lastMessage.extra_data?.reasoning_steps,
525
- references: chunk.extra_data?.references ?? lastMessage.extra_data?.references
573
+ references: chunk.extra_data?.references ?? lastMessage.extra_data?.references,
574
+ generated_ui: chunk.extra_data?.generated_ui ?? lastMessage.extra_data?.generated_ui
526
575
  };
527
576
  break;
528
577
  case RunEventEnum.UpdatingMemory:
@@ -780,12 +829,14 @@ function toSafeISOString(timestamp) {
780
829
  return new Date(ts).toISOString();
781
830
  }
782
831
  var AgnoClient = class extends EventEmitter {
832
+ // toolCallId -> UIComponentSpec
783
833
  constructor(config) {
784
834
  super();
785
835
  this.messageStore = new MessageStore();
786
836
  this.configManager = new ConfigManager(config);
787
837
  this.sessionManager = new SessionManager();
788
838
  this.eventProcessor = new EventProcessor();
839
+ this.pendingUISpecs = /* @__PURE__ */ new Map();
789
840
  this.state = {
790
841
  isStreaming: false,
791
842
  isEndpointActive: false,
@@ -828,6 +879,7 @@ var AgnoClient = class extends EventEmitter {
828
879
  clearMessages() {
829
880
  this.messageStore.clear();
830
881
  this.configManager.setSessionId(void 0);
882
+ this.pendingUISpecs.clear();
831
883
  this.emit("message:update", this.messageStore.getMessages());
832
884
  this.emit("state:change", this.getState());
833
885
  }
@@ -875,6 +927,10 @@ var AgnoClient = class extends EventEmitter {
875
927
  try {
876
928
  formData.append("stream", "true");
877
929
  formData.append("session_id", newSessionId ?? "");
930
+ const userId = this.configManager.getUserId();
931
+ if (userId) {
932
+ formData.append("user_id", userId);
933
+ }
878
934
  const headers = { ...options?.headers };
879
935
  const authToken = this.configManager.getAuthToken();
880
936
  if (authToken) {
@@ -932,18 +988,10 @@ var AgnoClient = class extends EventEmitter {
932
988
  }
933
989
  }
934
990
  if (event === RunEvent.RunPaused) {
935
- console.log("[AgnoClient] RunPaused event detected");
936
- console.log("[AgnoClient] Chunk:", chunk);
937
- console.log("[AgnoClient] tools_awaiting_external_execution:", chunk.tools_awaiting_external_execution);
938
- console.log("[AgnoClient] tools_requiring_confirmation:", chunk.tools_requiring_confirmation);
939
- console.log("[AgnoClient] tools_requiring_user_input:", chunk.tools_requiring_user_input);
940
- console.log("[AgnoClient] tools:", chunk.tools);
941
991
  this.state.isStreaming = false;
942
992
  this.state.isPaused = true;
943
993
  this.state.pausedRunId = chunk.run_id;
944
994
  this.state.toolsAwaitingExecution = chunk.tools_awaiting_external_execution || chunk.tools_requiring_confirmation || chunk.tools_requiring_user_input || chunk.tools || [];
945
- console.log("[AgnoClient] toolsAwaitingExecution:", this.state.toolsAwaitingExecution);
946
- console.log("[AgnoClient] Emitting run:paused event");
947
995
  this.emit("run:paused", {
948
996
  runId: chunk.run_id,
949
997
  sessionId: chunk.session_id,
@@ -971,6 +1019,7 @@ var AgnoClient = class extends EventEmitter {
971
1019
  const updated = this.eventProcessor.processChunk(chunk, lastMessage);
972
1020
  return updated || lastMessage;
973
1021
  });
1022
+ this.applyPendingUISpecs();
974
1023
  this.emit("message:update", this.messageStore.getMessages());
975
1024
  }
976
1025
  /**
@@ -1080,6 +1129,103 @@ var AgnoClient = class extends EventEmitter {
1080
1129
  }
1081
1130
  this.emit("state:change", this.getState());
1082
1131
  }
1132
+ /**
1133
+ * Add tool calls to the last message
1134
+ * Used by frontend execution to add tool calls that were executed locally
1135
+ */
1136
+ addToolCallsToLastMessage(toolCalls) {
1137
+ const lastMessage = this.messageStore.getLastMessage();
1138
+ if (!lastMessage || lastMessage.role !== "agent") {
1139
+ return;
1140
+ }
1141
+ const existingToolCalls = lastMessage.tool_calls || [];
1142
+ const existingIds = new Set(existingToolCalls.map((t) => t.tool_call_id));
1143
+ const newToolCalls = toolCalls.filter((t) => !existingIds.has(t.tool_call_id));
1144
+ if (newToolCalls.length > 0) {
1145
+ this.messageStore.updateLastMessage((msg) => ({
1146
+ ...msg,
1147
+ tool_calls: [...existingToolCalls, ...newToolCalls]
1148
+ }));
1149
+ this.emit("message:update", this.messageStore.getMessages());
1150
+ }
1151
+ }
1152
+ /**
1153
+ * Hydrate a specific tool call with its UI component
1154
+ * If tool call doesn't exist yet, stores UI spec as pending
1155
+ */
1156
+ hydrateToolCallUI(toolCallId, uiSpec) {
1157
+ const messages = this.messageStore.getMessages();
1158
+ for (let i = messages.length - 1; i >= 0; i--) {
1159
+ const message = messages[i];
1160
+ if (message.tool_calls) {
1161
+ const toolIndex = message.tool_calls.findIndex(
1162
+ (t) => t.tool_call_id === toolCallId
1163
+ );
1164
+ if (toolIndex !== -1) {
1165
+ this.messageStore.updateMessage(i, (msg) => {
1166
+ const updatedToolCalls = [...msg.tool_calls || []];
1167
+ updatedToolCalls[toolIndex] = {
1168
+ ...updatedToolCalls[toolIndex],
1169
+ ui_component: uiSpec
1170
+ };
1171
+ return {
1172
+ ...msg,
1173
+ tool_calls: updatedToolCalls
1174
+ };
1175
+ });
1176
+ this.pendingUISpecs.delete(toolCallId);
1177
+ this.emit("message:update", this.messageStore.getMessages());
1178
+ return;
1179
+ }
1180
+ }
1181
+ }
1182
+ this.pendingUISpecs.set(toolCallId, uiSpec);
1183
+ }
1184
+ /**
1185
+ * Apply any pending UI specs to tool calls that have just been added
1186
+ * Called after message updates to attach UI to newly arrived tool calls
1187
+ * Batches all updates to emit only one message:update event
1188
+ */
1189
+ applyPendingUISpecs() {
1190
+ if (this.pendingUISpecs.size === 0)
1191
+ return;
1192
+ const messages = this.messageStore.getMessages();
1193
+ const updatedMessages = [];
1194
+ for (let i = messages.length - 1; i >= 0; i--) {
1195
+ const message = messages[i];
1196
+ if (message.tool_calls) {
1197
+ let messageUpdated = false;
1198
+ const updatedToolCalls = [...message.tool_calls];
1199
+ for (let j = 0; j < updatedToolCalls.length; j++) {
1200
+ const toolCall = updatedToolCalls[j];
1201
+ const pendingUI = this.pendingUISpecs.get(toolCall.tool_call_id);
1202
+ if (pendingUI && !toolCall.ui_component) {
1203
+ updatedToolCalls[j] = {
1204
+ ...updatedToolCalls[j],
1205
+ ui_component: pendingUI
1206
+ };
1207
+ this.pendingUISpecs.delete(toolCall.tool_call_id);
1208
+ messageUpdated = true;
1209
+ }
1210
+ }
1211
+ if (messageUpdated) {
1212
+ updatedMessages.push({
1213
+ index: i,
1214
+ message: {
1215
+ ...message,
1216
+ tool_calls: updatedToolCalls
1217
+ }
1218
+ });
1219
+ }
1220
+ }
1221
+ }
1222
+ if (updatedMessages.length > 0) {
1223
+ updatedMessages.forEach(({ index, message }) => {
1224
+ this.messageStore.updateMessage(index, () => message);
1225
+ });
1226
+ this.emit("message:update", this.messageStore.getMessages());
1227
+ }
1228
+ }
1083
1229
  /**
1084
1230
  * Continue a paused run after executing external tools
1085
1231
  */
@@ -1096,13 +1242,21 @@ var AgnoClient = class extends EventEmitter {
1096
1242
  this.state.isStreaming = true;
1097
1243
  this.emit("run:continued", { runId: this.state.pausedRunId });
1098
1244
  this.emit("state:change", this.getState());
1245
+ const cleanedTools = tools.map((tool) => {
1246
+ const { ui_component, ...backendTool } = tool;
1247
+ return backendTool;
1248
+ });
1099
1249
  const formData = new FormData();
1100
- formData.append("tools", JSON.stringify(tools));
1250
+ formData.append("tools", JSON.stringify(cleanedTools));
1101
1251
  formData.append("stream", "true");
1102
1252
  const currentSessionId = this.configManager.getSessionId();
1103
1253
  if (currentSessionId) {
1104
1254
  formData.append("session_id", currentSessionId);
1105
1255
  }
1256
+ const userId = this.configManager.getUserId();
1257
+ if (userId) {
1258
+ formData.append("user_id", userId);
1259
+ }
1106
1260
  const headers = { ...options?.headers };
1107
1261
  const authToken = this.configManager.getAuthToken();
1108
1262
  if (authToken) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antipopp/agno-client",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Core client library for Agno agents with streaming support and HITL frontend tool execution",
5
5
  "author": "antipopp",
6
6
  "license": "MIT",
@@ -34,7 +34,7 @@
34
34
  ],
35
35
  "dependencies": {
36
36
  "eventemitter3": "^5.0.1",
37
- "@antipopp/agno-types": "0.2.0"
37
+ "@antipopp/agno-types": "0.4.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "tsup": "^8.0.1",