@tanstack/ai-devtools-core 0.2.1 → 0.2.3

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.
Files changed (29) hide show
  1. package/README.md +1 -0
  2. package/dist/esm/components/ConversationDetails.js +74 -1
  3. package/dist/esm/components/ConversationDetails.js.map +1 -1
  4. package/dist/esm/components/conversation/ActivityEventsTab.d.ts +8 -0
  5. package/dist/esm/components/conversation/ActivityEventsTab.js +72 -0
  6. package/dist/esm/components/conversation/ActivityEventsTab.js.map +1 -0
  7. package/dist/esm/components/conversation/ConversationHeader.js +109 -66
  8. package/dist/esm/components/conversation/ConversationHeader.js.map +1 -1
  9. package/dist/esm/components/conversation/ConversationTabs.d.ts +1 -1
  10. package/dist/esm/components/conversation/ConversationTabs.js +107 -3
  11. package/dist/esm/components/conversation/ConversationTabs.js.map +1 -1
  12. package/dist/esm/components/conversation/MessageCard.js +83 -28
  13. package/dist/esm/components/conversation/MessageCard.js.map +1 -1
  14. package/dist/esm/components/conversation/index.d.ts +1 -0
  15. package/dist/esm/store/ai-context.d.ts +19 -2
  16. package/dist/esm/store/ai-context.js +528 -262
  17. package/dist/esm/store/ai-context.js.map +1 -1
  18. package/dist/esm/styles/use-styles.d.ts +7 -0
  19. package/dist/esm/styles/use-styles.js +65 -1
  20. package/dist/esm/styles/use-styles.js.map +1 -1
  21. package/package.json +2 -2
  22. package/src/components/ConversationDetails.tsx +70 -1
  23. package/src/components/conversation/ActivityEventsTab.tsx +57 -0
  24. package/src/components/conversation/ConversationHeader.tsx +52 -36
  25. package/src/components/conversation/ConversationTabs.tsx +121 -2
  26. package/src/components/conversation/MessageCard.tsx +45 -10
  27. package/src/components/conversation/index.ts +1 -0
  28. package/src/store/ai-context.tsx +783 -418
  29. package/src/styles/use-styles.ts +65 -1
@@ -129,6 +129,10 @@ const AIProvider = (props) => {
129
129
  label,
130
130
  messages: [],
131
131
  chunks: [],
132
+ imageEvents: [],
133
+ speechEvents: [],
134
+ transcriptionEvents: [],
135
+ videoEvents: [],
132
136
  status: "active",
133
137
  startedAt: Date.now()
134
138
  });
@@ -170,6 +174,7 @@ const AIProvider = (props) => {
170
174
  return;
171
175
  }
172
176
  }
177
+ addChunk(conversationId, chunk);
173
178
  }
174
179
  function updateMessageUsage(conversationId, messageId, cumulativeUsage) {
175
180
  const conv = state.conversations[conversationId];
@@ -240,6 +245,21 @@ const AIProvider = (props) => {
240
245
  if (!state.conversations[conversationId]) return;
241
246
  queueChunk(conversationId, chunk);
242
247
  }
248
+ function addActivityEvent(conversationId, activity, event) {
249
+ if (!state.conversations[conversationId]) return;
250
+ setState("conversations", conversationId, activity, produce((events) => {
251
+ if (!events) return [event];
252
+ events.push(event);
253
+ return events;
254
+ }));
255
+ const activityFlagMap = {
256
+ imageEvents: "hasImage",
257
+ speechEvents: "hasSpeech",
258
+ transcriptionEvents: "hasTranscription",
259
+ videoEvents: "hasVideo"
260
+ };
261
+ setState("conversations", conversationId, activityFlagMap[activity], true);
262
+ }
243
263
  function ensureMessageForChunk(conversationId, messageId, timestamp) {
244
264
  if (!messageId) return;
245
265
  const conv = state.conversations[conversationId];
@@ -264,46 +284,115 @@ const AIProvider = (props) => {
264
284
  provider: "Client"
265
285
  });
266
286
  }));
267
- cleanupFns.push(aiEventClient.on("client:message-sent", (e) => {
268
- const clientId = e.payload.clientId;
269
- if (!state.conversations[clientId]) {
270
- getOrCreateConversation(clientId, "client", `Client Chat (${clientId.substring(0, 8)})`);
287
+ cleanupFns.push(aiEventClient.on("text:message:created", (e) => {
288
+ const {
289
+ clientId,
290
+ streamId,
291
+ messageId,
292
+ role,
293
+ content,
294
+ timestamp
295
+ } = e.payload;
296
+ const conversationId = clientId || (streamId ? streamToConversation.get(streamId) : void 0);
297
+ if (!conversationId) return;
298
+ if (role === "tool" || role === "system") return;
299
+ if (!state.conversations[conversationId]) {
300
+ getOrCreateConversation(conversationId, clientId ? "client" : "server", clientId ? `Client Chat (${conversationId.substring(0, 8)})` : `Server Chat (${conversationId.substring(0, 8)})`);
271
301
  }
272
- addMessage(clientId, {
273
- id: e.payload.messageId,
274
- role: "user",
275
- content: e.payload.content,
276
- timestamp: e.payload.timestamp,
277
- source: "client"
302
+ const conv = state.conversations[conversationId];
303
+ if (!conv) return;
304
+ const existingIndex = conv.messages.findIndex((message) => message.id === messageId);
305
+ const parts = e.payload.parts?.map((part) => {
306
+ if (part.type === "text") {
307
+ return {
308
+ type: "text",
309
+ content: part.content
310
+ };
311
+ }
312
+ if (part.type === "tool-call") {
313
+ return {
314
+ type: "tool-call",
315
+ toolCallId: part.id,
316
+ toolName: part.name,
317
+ arguments: part.arguments,
318
+ state: part.state,
319
+ output: part.output,
320
+ content: part.approval ? JSON.stringify(part.approval) : void 0
321
+ };
322
+ }
323
+ if (part.type === "tool-result") {
324
+ return {
325
+ type: "tool-result",
326
+ toolCallId: part.toolCallId,
327
+ content: part.content,
328
+ state: part.state,
329
+ error: part.error
330
+ };
331
+ }
332
+ return {
333
+ type: "thinking",
334
+ content: part.content
335
+ };
278
336
  });
279
- updateConversation(clientId, {
280
- status: "active"
337
+ const toolCalls = e.payload.toolCalls?.map((toolCall) => ({
338
+ id: toolCall.id,
339
+ name: toolCall.function.name,
340
+ arguments: toolCall.function.arguments,
341
+ state: "input-complete"
342
+ }));
343
+ const source = e.payload.source ?? (clientId ? "client" : "server");
344
+ if (role === "user" && conv.type === "client" && source === "server") {
345
+ return;
346
+ }
347
+ if (role === "assistant" && source === "client" && !content && (!toolCalls || toolCalls.length === 0)) {
348
+ return;
349
+ }
350
+ const messagePayload = {
351
+ id: messageId,
352
+ role,
353
+ content,
354
+ timestamp,
355
+ parts,
356
+ toolCalls,
357
+ source
358
+ };
359
+ if (existingIndex >= 0) {
360
+ updateMessage(conversationId, existingIndex, messagePayload);
361
+ } else {
362
+ addMessage(conversationId, messagePayload);
363
+ }
364
+ updateConversation(conversationId, {
365
+ status: "active",
366
+ hasChat: true
281
367
  });
282
368
  }));
283
- cleanupFns.push(aiEventClient.on("client:message-appended", (e) => {
284
- const clientId = e.payload.clientId;
285
- const role = e.payload.role;
286
- if (role === "user") return;
287
- if (!state.conversations[clientId]) return;
288
- if (role === "assistant") {
289
- addMessage(clientId, {
290
- id: e.payload.messageId,
291
- role: "assistant",
292
- content: e.payload.contentPreview,
293
- timestamp: e.payload.timestamp,
294
- source: "client"
295
- });
296
- } else if (role === "tool") {
297
- addMessage(clientId, {
298
- id: e.payload.messageId,
299
- role: "tool",
300
- content: e.payload.contentPreview,
301
- timestamp: e.payload.timestamp,
302
- source: "client"
303
- });
369
+ cleanupFns.push(aiEventClient.on("text:message:user", (e) => {
370
+ const {
371
+ clientId,
372
+ streamId,
373
+ messageId,
374
+ content,
375
+ timestamp
376
+ } = e.payload;
377
+ const conversationId = clientId || (streamId ? streamToConversation.get(streamId) : void 0);
378
+ if (!conversationId) return;
379
+ const conv = state.conversations[conversationId];
380
+ if (!conv) return;
381
+ const existingIndex = conv.messages.findIndex((message) => message.id === messageId);
382
+ if (existingIndex >= 0) return;
383
+ const source = e.payload.source ?? (clientId ? "client" : "server");
384
+ if (conv.type === "client" && source === "server") {
385
+ return;
304
386
  }
387
+ addMessage(conversationId, {
388
+ id: messageId,
389
+ role: "user",
390
+ content,
391
+ timestamp,
392
+ source
393
+ });
305
394
  }));
306
- cleanupFns.push(aiEventClient.on("client:loading-changed", (e) => {
395
+ cleanupFns.push(aiEventClient.on("client:loading:changed", (e) => {
307
396
  const clientId = e.payload.clientId;
308
397
  if (state.conversations[clientId]) {
309
398
  updateConversation(clientId, {
@@ -320,7 +409,7 @@ const AIProvider = (props) => {
320
409
  });
321
410
  }
322
411
  }));
323
- cleanupFns.push(aiEventClient.on("client:messages-cleared", (e) => {
412
+ cleanupFns.push(aiEventClient.on("client:messages:cleared", (e) => {
324
413
  const clientId = e.payload.clientId;
325
414
  if (state.conversations[clientId]) {
326
415
  updateConversation(clientId, {
@@ -340,7 +429,7 @@ const AIProvider = (props) => {
340
429
  });
341
430
  }
342
431
  }));
343
- cleanupFns.push(aiEventClient.on("client:error-changed", (e) => {
432
+ cleanupFns.push(aiEventClient.on("client:error:changed", (e) => {
344
433
  const clientId = e.payload.clientId;
345
434
  if (state.conversations[clientId] && e.payload.error) {
346
435
  updateConversation(clientId, {
@@ -348,31 +437,79 @@ const AIProvider = (props) => {
348
437
  });
349
438
  }
350
439
  }));
351
- cleanupFns.push(aiEventClient.on("client:assistant-message-updated", (e) => {
352
- const clientId = e.payload.clientId;
353
- const messageId = e.payload.messageId;
354
- const content = e.payload.content;
440
+ cleanupFns.push(aiEventClient.on("tools:result:added", (e) => {
441
+ const {
442
+ clientId,
443
+ toolCallId,
444
+ toolName,
445
+ output,
446
+ state: resultState,
447
+ timestamp
448
+ } = e.payload;
449
+ if (!clientId) return;
355
450
  if (!state.conversations[clientId]) return;
356
451
  const conv = state.conversations[clientId];
357
- const messageIndex = conv.messages.findIndex((m) => m.id === messageId);
358
- if (messageIndex >= 0) {
359
- updateMessage(clientId, messageIndex, {
360
- content,
361
- model: conv.model
362
- });
452
+ const chunk = {
453
+ id: `chunk-tool-result-${toolCallId}-${Date.now()}`,
454
+ type: "tool_result",
455
+ toolCallId,
456
+ toolName,
457
+ result: output,
458
+ timestamp,
459
+ chunkCount: 1,
460
+ isClientTool: true
461
+ };
462
+ for (let messageIndex = conv.messages.length - 1; messageIndex >= 0; messageIndex--) {
463
+ const message = conv.messages[messageIndex];
464
+ if (!message?.toolCalls) continue;
465
+ const toolCallIndex = message.toolCalls.findIndex((t) => t.id === toolCallId);
466
+ if (toolCallIndex >= 0) {
467
+ updateToolCall(clientId, messageIndex, toolCallIndex, {
468
+ result: output,
469
+ state: resultState === "output-error" ? "error" : "complete"
470
+ });
471
+ chunk.messageId = message.id;
472
+ addChunkToMessage(clientId, chunk);
473
+ return;
474
+ }
475
+ }
476
+ addChunkToMessage(clientId, chunk);
477
+ }));
478
+ cleanupFns.push(aiEventClient.on("tools:approval:responded", (e) => {
479
+ const {
480
+ clientId,
481
+ toolCallId,
482
+ approved
483
+ } = e.payload;
484
+ if (!clientId) return;
485
+ if (!state.conversations[clientId]) return;
486
+ const conv = state.conversations[clientId];
487
+ for (let messageIndex = conv.messages.length - 1; messageIndex >= 0; messageIndex--) {
488
+ const message = conv.messages[messageIndex];
489
+ if (!message?.toolCalls) continue;
490
+ const toolCallIndex = message.toolCalls.findIndex((t) => t.id === toolCallId);
491
+ if (toolCallIndex >= 0) {
492
+ updateToolCall(clientId, messageIndex, toolCallIndex, {
493
+ state: approved ? "approved" : "denied",
494
+ approvalRequired: false
495
+ });
496
+ return;
497
+ }
363
498
  }
364
499
  }));
365
- cleanupFns.push(aiEventClient.on("client:tool-call-updated", (e) => {
500
+ cleanupFns.push(aiEventClient.on("tools:call:updated", (e) => {
366
501
  const {
367
502
  clientId,
503
+ streamId,
368
504
  messageId,
369
505
  toolCallId,
370
506
  toolName,
371
507
  state: toolCallState,
372
508
  arguments: args
373
509
  } = e.payload;
374
- if (!state.conversations[clientId]) return;
375
- const conv = state.conversations[clientId];
510
+ const conversationId = clientId || (streamId ? streamToConversation.get(streamId) : void 0);
511
+ if (!conversationId || !state.conversations[conversationId]) return;
512
+ const conv = state.conversations[conversationId];
376
513
  const messageIndex = conv.messages.findIndex((m) => m.id === messageId);
377
514
  if (messageIndex === -1) return;
378
515
  const message = conv.messages[messageIndex];
@@ -382,93 +519,98 @@ const AIProvider = (props) => {
382
519
  const toolCall = {
383
520
  id: toolCallId,
384
521
  name: toolName,
385
- arguments: JSON.stringify(args, null, 2),
522
+ arguments: args,
386
523
  state: toolCallState
387
524
  };
388
525
  if (existingToolIndex >= 0) {
389
- updateToolCall(clientId, messageIndex, existingToolIndex, toolCall);
526
+ updateToolCall(conversationId, messageIndex, existingToolIndex, toolCall);
390
527
  } else {
391
- setToolCalls(clientId, messageIndex, [...toolCalls, toolCall]);
528
+ setToolCalls(conversationId, messageIndex, [...toolCalls, toolCall]);
392
529
  }
393
530
  }));
394
- cleanupFns.push(aiEventClient.on("client:approval-requested", (e) => {
531
+ cleanupFns.push(aiEventClient.on("tools:input:available", (e) => {
395
532
  const {
396
- clientId,
533
+ streamId,
397
534
  messageId,
398
535
  toolCallId,
399
- approvalId
536
+ toolName,
537
+ input,
538
+ timestamp,
539
+ clientId
400
540
  } = e.payload;
401
- if (!state.conversations[clientId]) return;
402
- const conv = state.conversations[clientId];
403
- const messageIndex = conv.messages.findIndex((m) => m.id === messageId);
404
- if (messageIndex === -1) return;
405
- const message = conv.messages[messageIndex];
406
- if (!message?.toolCalls) return;
407
- const toolCallIndex = message.toolCalls.findIndex((t) => t.id === toolCallId);
408
- if (toolCallIndex === -1) return;
409
- updateToolCall(clientId, messageIndex, toolCallIndex, {
410
- approvalRequired: true,
411
- approvalId,
412
- state: "approval-requested"
413
- });
414
- }));
415
- cleanupFns.push(aiEventClient.on("tool:result-added", (e) => {
416
- const {
417
- clientId,
541
+ const conversationId = clientId || (streamId ? streamToConversation.get(streamId) : void 0);
542
+ if (!conversationId) return;
543
+ const chunk = {
544
+ id: `chunk-${Date.now()}-${Math.random()}`,
545
+ type: "tool_call",
546
+ messageId,
418
547
  toolCallId,
419
548
  toolName,
420
- output,
421
- state: resultState,
422
- timestamp
423
- } = e.payload;
424
- if (!state.conversations[clientId]) return;
425
- const conv = state.conversations[clientId];
426
- for (let messageIndex = conv.messages.length - 1; messageIndex >= 0; messageIndex--) {
427
- const message = conv.messages[messageIndex];
428
- if (!message?.toolCalls) continue;
429
- const toolCallIndex = message.toolCalls.findIndex((t) => t.id === toolCallId);
430
- if (toolCallIndex >= 0) {
431
- updateToolCall(clientId, messageIndex, toolCallIndex, {
432
- result: output,
433
- state: resultState === "output-error" ? "error" : "complete"
434
- });
435
- const chunk = {
436
- id: `chunk-tool-result-${toolCallId}-${Date.now()}`,
437
- type: "tool_result",
438
- messageId: message.id,
439
- toolCallId,
440
- toolName,
441
- result: output,
442
- timestamp,
443
- chunkCount: 1
444
- };
445
- addChunkToMessage(clientId, chunk);
446
- return;
447
- }
549
+ arguments: JSON.stringify(input),
550
+ timestamp,
551
+ chunkCount: 1
552
+ };
553
+ const conv = state.conversations[conversationId];
554
+ if (conv?.type === "client") {
555
+ addChunkToMessage(conversationId, chunk);
556
+ } else {
557
+ addChunk(conversationId, chunk);
448
558
  }
449
559
  }));
450
- cleanupFns.push(aiEventClient.on("tool:approval-responded", (e) => {
560
+ cleanupFns.push(aiEventClient.on("tools:call:completed", (e) => {
451
561
  const {
452
- clientId,
562
+ streamId,
453
563
  toolCallId,
454
- approved
564
+ toolName,
565
+ result,
566
+ duration,
567
+ messageId,
568
+ timestamp,
569
+ clientId
455
570
  } = e.payload;
456
- if (!state.conversations[clientId]) return;
457
- const conv = state.conversations[clientId];
458
- for (let messageIndex = conv.messages.length - 1; messageIndex >= 0; messageIndex--) {
459
- const message = conv.messages[messageIndex];
571
+ const conversationId = clientId || (streamId ? streamToConversation.get(streamId) : void 0);
572
+ if (!conversationId || !state.conversations[conversationId]) return;
573
+ const conv = state.conversations[conversationId];
574
+ const chunk = {
575
+ id: `chunk-tool-result-${toolCallId}-${Date.now()}`,
576
+ type: "tool_result",
577
+ messageId,
578
+ toolCallId,
579
+ toolName,
580
+ result,
581
+ duration,
582
+ timestamp,
583
+ chunkCount: 1
584
+ };
585
+ if (conv.type === "client" && messageId) {
586
+ const messageIndex = conv.messages.findIndex((m) => m.id === messageId);
587
+ if (messageIndex !== -1) {
588
+ queueMessageChunk(conversationId, messageIndex, chunk);
589
+ } else {
590
+ for (let i = conv.messages.length - 1; i >= 0; i--) {
591
+ if (conv.messages[i]?.role === "assistant") {
592
+ queueMessageChunk(conversationId, i, chunk);
593
+ break;
594
+ }
595
+ }
596
+ }
597
+ } else {
598
+ addChunk(conversationId, chunk);
599
+ }
600
+ for (let i = conv.messages.length - 1; i >= 0; i--) {
601
+ const message = conv.messages[i];
460
602
  if (!message?.toolCalls) continue;
461
603
  const toolCallIndex = message.toolCalls.findIndex((t) => t.id === toolCallId);
462
604
  if (toolCallIndex >= 0) {
463
- updateToolCall(clientId, messageIndex, toolCallIndex, {
464
- state: approved ? "approved" : "denied",
465
- approvalRequired: false
605
+ updateToolCall(conversationId, i, toolCallIndex, {
606
+ duration,
607
+ result
466
608
  });
467
609
  return;
468
610
  }
469
611
  }
470
612
  }));
471
- cleanupFns.push(aiEventClient.on("stream:chunk:content", (e) => {
613
+ cleanupFns.push(aiEventClient.on("text:chunk:content", (e) => {
472
614
  const streamId = e.payload.streamId;
473
615
  const conversationId = streamToConversation.get(streamId);
474
616
  if (!conversationId) return;
@@ -488,8 +630,16 @@ const AIProvider = (props) => {
488
630
  ensureMessageForChunk(conversationId, e.payload.messageId, e.payload.timestamp);
489
631
  addChunk(conversationId, chunk);
490
632
  }
633
+ if (e.payload.messageId) {
634
+ const messageIndex = conv?.messages.findIndex((msg) => msg.id === e.payload.messageId);
635
+ if (messageIndex !== void 0 && messageIndex >= 0) {
636
+ updateMessage(conversationId, messageIndex, {
637
+ content: e.payload.content
638
+ });
639
+ }
640
+ }
491
641
  }));
492
- cleanupFns.push(aiEventClient.on("stream:chunk:tool-call", (e) => {
642
+ cleanupFns.push(aiEventClient.on("text:chunk:tool-call", (e) => {
493
643
  const streamId = e.payload.streamId;
494
644
  const conversationId = streamToConversation.get(streamId);
495
645
  if (!conversationId) return;
@@ -510,8 +660,28 @@ const AIProvider = (props) => {
510
660
  ensureMessageForChunk(conversationId, e.payload.messageId, e.payload.timestamp);
511
661
  addChunk(conversationId, chunk);
512
662
  }
663
+ if (e.payload.messageId) {
664
+ const messageIndex = conv?.messages.findIndex((msg) => msg.id === e.payload.messageId);
665
+ if (messageIndex !== void 0 && messageIndex >= 0) {
666
+ const message = conv?.messages[messageIndex];
667
+ if (!message) return;
668
+ const toolCalls = message.toolCalls || [];
669
+ const existingToolIndex = toolCalls.findIndex((t) => t.id === e.payload.toolCallId);
670
+ const toolCall = {
671
+ id: e.payload.toolCallId,
672
+ name: e.payload.toolName,
673
+ arguments: e.payload.arguments,
674
+ state: "input-streaming"
675
+ };
676
+ if (existingToolIndex >= 0) {
677
+ updateToolCall(conversationId, messageIndex, existingToolIndex, toolCall);
678
+ } else {
679
+ setToolCalls(conversationId, messageIndex, [...toolCalls, toolCall]);
680
+ }
681
+ }
682
+ }
513
683
  }));
514
- cleanupFns.push(aiEventClient.on("stream:chunk:tool-result", (e) => {
684
+ cleanupFns.push(aiEventClient.on("text:chunk:tool-result", (e) => {
515
685
  const streamId = e.payload.streamId;
516
686
  const conversationId = streamToConversation.get(streamId);
517
687
  if (!conversationId) return;
@@ -520,7 +690,7 @@ const AIProvider = (props) => {
520
690
  type: "tool_result",
521
691
  messageId: e.payload.messageId,
522
692
  toolCallId: e.payload.toolCallId,
523
- content: e.payload.result,
693
+ result: e.payload.result,
524
694
  timestamp: e.payload.timestamp,
525
695
  chunkCount: 1
526
696
  };
@@ -546,7 +716,7 @@ const AIProvider = (props) => {
546
716
  }
547
717
  }
548
718
  }));
549
- cleanupFns.push(aiEventClient.on("stream:chunk:thinking", (e) => {
719
+ cleanupFns.push(aiEventClient.on("text:chunk:thinking", (e) => {
550
720
  const streamId = e.payload.streamId;
551
721
  const conversationId = streamToConversation.get(streamId);
552
722
  if (!conversationId) return;
@@ -575,7 +745,7 @@ const AIProvider = (props) => {
575
745
  addChunk(conversationId, chunk);
576
746
  }
577
747
  }));
578
- cleanupFns.push(aiEventClient.on("stream:chunk:done", (e) => {
748
+ cleanupFns.push(aiEventClient.on("text:chunk:done", (e) => {
579
749
  const streamId = e.payload.streamId;
580
750
  const conversationId = streamToConversation.get(streamId);
581
751
  if (!conversationId) return;
@@ -605,7 +775,7 @@ const AIProvider = (props) => {
605
775
  completedAt: e.payload.timestamp
606
776
  });
607
777
  }));
608
- cleanupFns.push(aiEventClient.on("stream:chunk:error", (e) => {
778
+ cleanupFns.push(aiEventClient.on("text:chunk:error", (e) => {
609
779
  const streamId = e.payload.streamId;
610
780
  const conversationId = streamToConversation.get(streamId);
611
781
  if (!conversationId) return;
@@ -629,16 +799,7 @@ const AIProvider = (props) => {
629
799
  completedAt: e.payload.timestamp
630
800
  });
631
801
  }));
632
- cleanupFns.push(aiEventClient.on("stream:ended", (e) => {
633
- const streamId = e.payload.streamId;
634
- const conversationId = streamToConversation.get(streamId);
635
- if (!conversationId) return;
636
- updateConversation(conversationId, {
637
- status: "completed",
638
- completedAt: e.payload.timestamp
639
- });
640
- }));
641
- cleanupFns.push(aiEventClient.on("stream:approval-requested", (e) => {
802
+ cleanupFns.push(aiEventClient.on("tools:approval:requested", (e) => {
642
803
  const {
643
804
  streamId,
644
805
  messageId,
@@ -646,9 +807,10 @@ const AIProvider = (props) => {
646
807
  toolName,
647
808
  input,
648
809
  approvalId,
649
- timestamp
810
+ timestamp,
811
+ clientId
650
812
  } = e.payload;
651
- const conversationId = streamToConversation.get(streamId);
813
+ const conversationId = clientId || streamToConversation.get(streamId) || "";
652
814
  if (!conversationId) return;
653
815
  const conv = state.conversations[conversationId];
654
816
  if (!conv) return;
@@ -684,104 +846,56 @@ const AIProvider = (props) => {
684
846
  }
685
847
  }
686
848
  }));
687
- cleanupFns.push(aiEventClient.on("processor:text-updated", (e) => {
688
- const streamId = e.payload.streamId;
689
- let conversationId = streamToConversation.get(streamId);
690
- if (!conversationId) {
691
- const activeClients = Object.values(state.conversations).filter((c) => c.type === "client" && c.status === "active").sort((a, b) => b.startedAt - a.startedAt);
692
- if (activeClients.length > 0 && activeClients[0]) {
693
- conversationId = activeClients[0].id;
694
- streamToConversation.set(streamId, conversationId);
695
- }
696
- }
697
- if (!conversationId) return;
698
- const conv = state.conversations[conversationId];
699
- if (!conv) return;
700
- const lastMessage = conv.messages[conv.messages.length - 1];
701
- if (lastMessage && lastMessage.role === "assistant") {
702
- updateMessage(conversationId, conv.messages.length - 1, {
703
- content: e.payload.content
704
- });
705
- }
706
- }));
707
- cleanupFns.push(aiEventClient.on("processor:tool-call-state-changed", (e) => {
708
- const streamId = e.payload.streamId;
709
- const conversationId = streamToConversation.get(streamId);
710
- if (!conversationId || !state.conversations[conversationId]) return;
711
- const conv = state.conversations[conversationId];
712
- const lastMessage = conv.messages[conv.messages.length - 1];
713
- if (lastMessage && lastMessage.role === "assistant") {
714
- const toolCalls = lastMessage.toolCalls || [];
715
- const existingToolIndex = toolCalls.findIndex((t) => t.id === e.payload.toolCallId);
716
- const toolCall = {
717
- id: e.payload.toolCallId,
718
- name: e.payload.toolName,
719
- arguments: JSON.stringify(e.payload.arguments, null, 2),
720
- state: e.payload.state
721
- };
722
- if (existingToolIndex >= 0) {
723
- updateToolCall(conversationId, conv.messages.length - 1, existingToolIndex, toolCall);
724
- } else {
725
- setToolCalls(conversationId, conv.messages.length - 1, [...toolCalls, toolCall]);
726
- }
727
- }
728
- }));
729
- cleanupFns.push(aiEventClient.on("processor:tool-result-state-changed", (e) => {
730
- const streamId = e.payload.streamId;
731
- const conversationId = streamToConversation.get(streamId);
732
- if (!conversationId || !state.conversations[conversationId]) return;
733
- const conv = state.conversations[conversationId];
734
- for (let i = conv.messages.length - 1; i >= 0; i--) {
735
- const message = conv.messages[i];
736
- if (!message?.toolCalls) continue;
737
- const toolCallIndex = message.toolCalls.findIndex((t) => t.id === e.payload.toolCallId);
738
- if (toolCallIndex >= 0) {
739
- updateToolCall(conversationId, i, toolCallIndex, {
740
- result: e.payload.content,
741
- state: e.payload.error ? "error" : e.payload.state
742
- });
743
- return;
744
- }
745
- }
746
- }));
747
- cleanupFns.push(aiEventClient.on("text:started", (e) => {
849
+ cleanupFns.push(aiEventClient.on("text:request:started", (e) => {
748
850
  const streamId = e.payload.streamId;
749
851
  const model = e.payload.model;
750
852
  e.payload.provider;
751
853
  const clientId = e.payload.clientId;
752
854
  if (clientId && state.conversations[clientId]) {
753
855
  streamToConversation.set(streamId, clientId);
856
+ requestToConversation.set(e.payload.requestId, clientId);
754
857
  updateConversation(clientId, {
755
858
  status: "active",
756
- ...e.payload
859
+ ...e.payload,
860
+ systemPrompts: e.payload.systemPrompts,
861
+ hasChat: true
757
862
  });
758
863
  return;
759
864
  }
760
865
  const activeClient = Object.values(state.conversations).find((c) => c.type === "client" && c.status === "active" && !c.model);
761
866
  if (activeClient) {
762
867
  streamToConversation.set(streamId, activeClient.id);
868
+ requestToConversation.set(e.payload.requestId, activeClient.id);
763
869
  updateConversation(activeClient.id, {
764
- ...e.payload
870
+ ...e.payload,
871
+ systemPrompts: e.payload.systemPrompts,
872
+ hasChat: true
765
873
  });
766
874
  } else {
767
875
  const existingServerConv = Object.values(state.conversations).find((c) => c.type === "server" && c.model === model);
768
876
  if (existingServerConv) {
769
877
  streamToConversation.set(streamId, existingServerConv.id);
878
+ requestToConversation.set(e.payload.requestId, existingServerConv.id);
770
879
  updateConversation(existingServerConv.id, {
771
880
  status: "active",
772
- ...e.payload
881
+ ...e.payload,
882
+ systemPrompts: e.payload.systemPrompts,
883
+ hasChat: true
773
884
  });
774
885
  } else {
775
886
  const serverId = `server-${model}`;
776
887
  getOrCreateConversation(serverId, "server", `${model} Server`);
777
888
  streamToConversation.set(streamId, serverId);
889
+ requestToConversation.set(e.payload.requestId, serverId);
778
890
  updateConversation(serverId, {
779
- ...e.payload
891
+ ...e.payload,
892
+ systemPrompts: e.payload.systemPrompts,
893
+ hasChat: true
780
894
  });
781
895
  }
782
896
  }
783
897
  }));
784
- cleanupFns.push(aiEventClient.on("text:completed", (e) => {
898
+ cleanupFns.push(aiEventClient.on("text:request:completed", (e) => {
785
899
  const {
786
900
  requestId,
787
901
  usage
@@ -801,19 +915,7 @@ const AIProvider = (props) => {
801
915
  }
802
916
  }
803
917
  }));
804
- cleanupFns.push(aiEventClient.on("text:iteration", (e) => {
805
- const {
806
- requestId,
807
- iterationNumber
808
- } = e.payload;
809
- const conversationId = requestToConversation.get(requestId);
810
- if (conversationId && state.conversations[conversationId]) {
811
- updateConversation(conversationId, {
812
- iterationCount: iterationNumber
813
- });
814
- }
815
- }));
816
- cleanupFns.push(aiEventClient.on("usage:tokens", (e) => {
918
+ cleanupFns.push(aiEventClient.on("text:usage", (e) => {
817
919
  const {
818
920
  requestId,
819
921
  usage,
@@ -827,59 +929,7 @@ const AIProvider = (props) => {
827
929
  updateMessageUsage(conversationId, messageId, usage);
828
930
  }
829
931
  }));
830
- cleanupFns.push(aiEventClient.on("tool:call-completed", (e) => {
831
- const {
832
- streamId,
833
- toolCallId,
834
- toolName,
835
- result,
836
- duration,
837
- messageId,
838
- timestamp
839
- } = e.payload;
840
- const conversationId = streamToConversation.get(streamId);
841
- if (!conversationId || !state.conversations[conversationId]) return;
842
- const conv = state.conversations[conversationId];
843
- const chunk = {
844
- id: `chunk-tool-result-${toolCallId}-${Date.now()}`,
845
- type: "tool_result",
846
- messageId,
847
- toolCallId,
848
- toolName,
849
- result,
850
- duration,
851
- timestamp,
852
- chunkCount: 1
853
- };
854
- if (conv.type === "client" && messageId) {
855
- const messageIndex = conv.messages.findIndex((m) => m.id === messageId);
856
- if (messageIndex !== -1) {
857
- queueMessageChunk(conversationId, messageIndex, chunk);
858
- } else {
859
- for (let i = conv.messages.length - 1; i >= 0; i--) {
860
- if (conv.messages[i]?.role === "assistant") {
861
- queueMessageChunk(conversationId, i, chunk);
862
- break;
863
- }
864
- }
865
- }
866
- } else {
867
- addChunk(conversationId, chunk);
868
- }
869
- for (let i = conv.messages.length - 1; i >= 0; i--) {
870
- const message = conv.messages[i];
871
- if (!message?.toolCalls) continue;
872
- const toolCallIndex = message.toolCalls.findIndex((t) => t.id === toolCallId);
873
- if (toolCallIndex >= 0) {
874
- updateToolCall(conversationId, i, toolCallIndex, {
875
- duration,
876
- result
877
- });
878
- return;
879
- }
880
- }
881
- }));
882
- cleanupFns.push(aiEventClient.on("summarize:started", (e) => {
932
+ cleanupFns.push(aiEventClient.on("summarize:request:started", (e) => {
883
933
  const {
884
934
  requestId,
885
935
  model,
@@ -915,7 +965,7 @@ const AIProvider = (props) => {
915
965
  setState("conversations", conversationId, "hasSummarize", true);
916
966
  }
917
967
  }));
918
- cleanupFns.push(aiEventClient.on("summarize:completed", (e) => {
968
+ cleanupFns.push(aiEventClient.on("summarize:request:completed", (e) => {
919
969
  const {
920
970
  requestId,
921
971
  outputLength,
@@ -934,6 +984,222 @@ const AIProvider = (props) => {
934
984
  }));
935
985
  }
936
986
  }));
987
+ cleanupFns.push(aiEventClient.on("image:request:started", (e) => {
988
+ const {
989
+ requestId,
990
+ clientId,
991
+ timestamp
992
+ } = e.payload;
993
+ let conversationId = clientId;
994
+ if (!conversationId || !state.conversations[conversationId]) {
995
+ conversationId = `image-${requestId}`;
996
+ getOrCreateConversation(conversationId, "server", `Image (${requestId.substring(0, 8)})`);
997
+ }
998
+ addActivityEvent(conversationId, "imageEvents", {
999
+ id: requestId,
1000
+ name: "image:request:started",
1001
+ timestamp,
1002
+ payload: e.payload
1003
+ });
1004
+ }));
1005
+ cleanupFns.push(aiEventClient.on("image:request:completed", (e) => {
1006
+ const {
1007
+ requestId,
1008
+ clientId,
1009
+ timestamp
1010
+ } = e.payload;
1011
+ let conversationId = clientId;
1012
+ if (!conversationId || !state.conversations[conversationId]) {
1013
+ conversationId = `image-${requestId}`;
1014
+ getOrCreateConversation(conversationId, "server", `Image (${requestId.substring(0, 8)})`);
1015
+ }
1016
+ addActivityEvent(conversationId, "imageEvents", {
1017
+ id: requestId,
1018
+ name: "image:request:completed",
1019
+ timestamp,
1020
+ payload: e.payload
1021
+ });
1022
+ }));
1023
+ cleanupFns.push(aiEventClient.on("image:usage", (e) => {
1024
+ const {
1025
+ requestId,
1026
+ clientId,
1027
+ timestamp
1028
+ } = e.payload;
1029
+ let conversationId = clientId;
1030
+ if (!conversationId || !state.conversations[conversationId]) {
1031
+ conversationId = `image-${requestId}`;
1032
+ getOrCreateConversation(conversationId, "server", `Image (${requestId.substring(0, 8)})`);
1033
+ }
1034
+ addActivityEvent(conversationId, "imageEvents", {
1035
+ id: requestId,
1036
+ name: "image:usage",
1037
+ timestamp,
1038
+ payload: e.payload
1039
+ });
1040
+ }));
1041
+ cleanupFns.push(aiEventClient.on("speech:request:started", (e) => {
1042
+ const {
1043
+ requestId,
1044
+ clientId,
1045
+ timestamp
1046
+ } = e.payload;
1047
+ let conversationId = clientId;
1048
+ if (!conversationId || !state.conversations[conversationId]) {
1049
+ conversationId = `speech-${requestId}`;
1050
+ getOrCreateConversation(conversationId, "server", `Speech (${requestId.substring(0, 8)})`);
1051
+ }
1052
+ addActivityEvent(conversationId, "speechEvents", {
1053
+ id: requestId,
1054
+ name: "speech:request:started",
1055
+ timestamp,
1056
+ payload: e.payload
1057
+ });
1058
+ }));
1059
+ cleanupFns.push(aiEventClient.on("speech:request:completed", (e) => {
1060
+ const {
1061
+ requestId,
1062
+ clientId,
1063
+ timestamp
1064
+ } = e.payload;
1065
+ let conversationId = clientId;
1066
+ if (!conversationId || !state.conversations[conversationId]) {
1067
+ conversationId = `speech-${requestId}`;
1068
+ getOrCreateConversation(conversationId, "server", `Speech (${requestId.substring(0, 8)})`);
1069
+ }
1070
+ addActivityEvent(conversationId, "speechEvents", {
1071
+ id: requestId,
1072
+ name: "speech:request:completed",
1073
+ timestamp,
1074
+ payload: e.payload
1075
+ });
1076
+ }));
1077
+ cleanupFns.push(aiEventClient.on("speech:usage", (e) => {
1078
+ const {
1079
+ requestId,
1080
+ clientId,
1081
+ timestamp
1082
+ } = e.payload;
1083
+ let conversationId = clientId;
1084
+ if (!conversationId || !state.conversations[conversationId]) {
1085
+ conversationId = `speech-${requestId}`;
1086
+ getOrCreateConversation(conversationId, "server", `Speech (${requestId.substring(0, 8)})`);
1087
+ }
1088
+ addActivityEvent(conversationId, "speechEvents", {
1089
+ id: requestId,
1090
+ name: "speech:usage",
1091
+ timestamp,
1092
+ payload: e.payload
1093
+ });
1094
+ }));
1095
+ cleanupFns.push(aiEventClient.on("transcription:request:started", (e) => {
1096
+ const {
1097
+ requestId,
1098
+ clientId,
1099
+ timestamp
1100
+ } = e.payload;
1101
+ let conversationId = clientId;
1102
+ if (!conversationId || !state.conversations[conversationId]) {
1103
+ conversationId = `transcription-${requestId}`;
1104
+ getOrCreateConversation(conversationId, "server", `Transcription (${requestId.substring(0, 8)})`);
1105
+ }
1106
+ addActivityEvent(conversationId, "transcriptionEvents", {
1107
+ id: requestId,
1108
+ name: "transcription:request:started",
1109
+ timestamp,
1110
+ payload: e.payload
1111
+ });
1112
+ }));
1113
+ cleanupFns.push(aiEventClient.on("transcription:request:completed", (e) => {
1114
+ const {
1115
+ requestId,
1116
+ clientId,
1117
+ timestamp
1118
+ } = e.payload;
1119
+ let conversationId = clientId;
1120
+ if (!conversationId || !state.conversations[conversationId]) {
1121
+ conversationId = `transcription-${requestId}`;
1122
+ getOrCreateConversation(conversationId, "server", `Transcription (${requestId.substring(0, 8)})`);
1123
+ }
1124
+ addActivityEvent(conversationId, "transcriptionEvents", {
1125
+ id: requestId,
1126
+ name: "transcription:request:completed",
1127
+ timestamp,
1128
+ payload: e.payload
1129
+ });
1130
+ }));
1131
+ cleanupFns.push(aiEventClient.on("transcription:usage", (e) => {
1132
+ const {
1133
+ requestId,
1134
+ clientId,
1135
+ timestamp
1136
+ } = e.payload;
1137
+ let conversationId = clientId;
1138
+ if (!conversationId || !state.conversations[conversationId]) {
1139
+ conversationId = `transcription-${requestId}`;
1140
+ getOrCreateConversation(conversationId, "server", `Transcription (${requestId.substring(0, 8)})`);
1141
+ }
1142
+ addActivityEvent(conversationId, "transcriptionEvents", {
1143
+ id: requestId,
1144
+ name: "transcription:usage",
1145
+ timestamp,
1146
+ payload: e.payload
1147
+ });
1148
+ }));
1149
+ cleanupFns.push(aiEventClient.on("video:request:started", (e) => {
1150
+ const {
1151
+ requestId,
1152
+ clientId,
1153
+ timestamp
1154
+ } = e.payload;
1155
+ let conversationId = clientId;
1156
+ if (!conversationId || !state.conversations[conversationId]) {
1157
+ conversationId = `video-${requestId}`;
1158
+ getOrCreateConversation(conversationId, "server", `Video (${requestId.substring(0, 8)})`);
1159
+ }
1160
+ addActivityEvent(conversationId, "videoEvents", {
1161
+ id: requestId,
1162
+ name: "video:request:started",
1163
+ timestamp,
1164
+ payload: e.payload
1165
+ });
1166
+ }));
1167
+ cleanupFns.push(aiEventClient.on("video:request:completed", (e) => {
1168
+ const {
1169
+ requestId,
1170
+ clientId,
1171
+ timestamp
1172
+ } = e.payload;
1173
+ let conversationId = clientId;
1174
+ if (!conversationId || !state.conversations[conversationId]) {
1175
+ conversationId = `video-${requestId}`;
1176
+ getOrCreateConversation(conversationId, "server", `Video (${requestId.substring(0, 8)})`);
1177
+ }
1178
+ addActivityEvent(conversationId, "videoEvents", {
1179
+ id: requestId,
1180
+ name: "video:request:completed",
1181
+ timestamp,
1182
+ payload: e.payload
1183
+ });
1184
+ }));
1185
+ cleanupFns.push(aiEventClient.on("video:usage", (e) => {
1186
+ const {
1187
+ requestId,
1188
+ clientId,
1189
+ timestamp
1190
+ } = e.payload;
1191
+ let conversationId = clientId;
1192
+ if (!conversationId || !state.conversations[conversationId]) {
1193
+ conversationId = `video-${requestId}`;
1194
+ getOrCreateConversation(conversationId, "server", `Video (${requestId.substring(0, 8)})`);
1195
+ }
1196
+ addActivityEvent(conversationId, "videoEvents", {
1197
+ id: requestId,
1198
+ name: "video:usage",
1199
+ timestamp,
1200
+ payload: e.payload
1201
+ });
1202
+ }));
937
1203
  onCleanup(() => {
938
1204
  for (const cleanup of cleanupFns) {
939
1205
  cleanup();