@yourgpt/copilot-sdk 2.1.5-alpha.3 → 2.1.5-alpha.5

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 (106) hide show
  1. package/dist/{MessageTree-CoIt_4nB.d.cts → MessageTree-Clhiv_k2.d.ts} +5 -4
  2. package/dist/{MessageTree-CzaN9Eul.d.ts → MessageTree-Dt9qfJ55.d.cts} +5 -4
  3. package/dist/{ThreadManager-BEAECB7Y.d.ts → ThreadManager-D7KwT2FJ.d.ts} +3 -1
  4. package/dist/{ThreadManager-Cw5fwyCN.d.cts → ThreadManager-DK46fVl3.d.cts} +3 -1
  5. package/dist/{chunk-NUXLAZOE.cjs → chunk-3ZDRX7J2.cjs} +2 -2
  6. package/dist/{chunk-NUXLAZOE.cjs.map → chunk-3ZDRX7J2.cjs.map} +1 -1
  7. package/dist/{chunk-RKGRQRZU.js → chunk-533K2Z7C.js} +4 -4
  8. package/dist/{chunk-RKGRQRZU.js.map → chunk-533K2Z7C.js.map} +1 -1
  9. package/dist/chunk-5EGBIQYS.cjs +292 -0
  10. package/dist/chunk-5EGBIQYS.cjs.map +1 -0
  11. package/dist/chunk-5UGWLGFS.cjs +2039 -0
  12. package/dist/chunk-5UGWLGFS.cjs.map +1 -0
  13. package/dist/{chunk-3AONOZLY.js → chunk-AIVXGTWS.js} +2 -2
  14. package/dist/chunk-AIVXGTWS.js.map +1 -0
  15. package/dist/{chunk-LLM7AHMO.js → chunk-DDZLRCVX.js} +2 -2
  16. package/dist/{chunk-LLM7AHMO.js.map → chunk-DDZLRCVX.js.map} +1 -1
  17. package/dist/{chunk-PT2TOHG5.js → chunk-DH6EO6NW.js} +1337 -3049
  18. package/dist/chunk-DH6EO6NW.js.map +1 -0
  19. package/dist/{chunk-WIXFZUEZ.cjs → chunk-KGYDGK3U.cjs} +84 -30
  20. package/dist/chunk-KGYDGK3U.cjs.map +1 -0
  21. package/dist/{chunk-TCPAT3WG.cjs → chunk-LHLVTGIP.cjs} +1339 -3101
  22. package/dist/chunk-LHLVTGIP.cjs.map +1 -0
  23. package/dist/{chunk-TPB7XED6.cjs → chunk-TPDMBDQX.cjs} +2 -2
  24. package/dist/chunk-TPDMBDQX.cjs.map +1 -0
  25. package/dist/chunk-TXQ37MAO.js +287 -0
  26. package/dist/chunk-TXQ37MAO.js.map +1 -0
  27. package/dist/{chunk-MDS23G2S.cjs → chunk-Y2A6AMGO.cjs} +10 -10
  28. package/dist/{chunk-MDS23G2S.cjs.map → chunk-Y2A6AMGO.cjs.map} +1 -1
  29. package/dist/{chunk-WZ2TOZ7M.js → chunk-YLZCTR4O.js} +65 -11
  30. package/dist/chunk-YLZCTR4O.js.map +1 -0
  31. package/dist/chunk-ZAOTYA5L.js +1983 -0
  32. package/dist/chunk-ZAOTYA5L.js.map +1 -0
  33. package/dist/core/index.cjs +93 -93
  34. package/dist/core/index.d.cts +7 -7
  35. package/dist/core/index.d.ts +7 -7
  36. package/dist/core/index.js +5 -5
  37. package/dist/experimental/index.cjs +644 -0
  38. package/dist/experimental/index.cjs.map +1 -0
  39. package/dist/experimental/index.d.cts +924 -0
  40. package/dist/experimental/index.d.ts +924 -0
  41. package/dist/experimental/index.js +611 -0
  42. package/dist/experimental/index.js.map +1 -0
  43. package/dist/{index-D7169xuR.d.ts → index-D8zza1Q8.d.ts} +1 -1
  44. package/dist/{index-CzJB8Ddo.d.cts → index-DCVjTdIZ.d.cts} +1 -1
  45. package/dist/mcp/index.d.cts +3 -3
  46. package/dist/mcp/index.d.ts +3 -3
  47. package/dist/react/index.cjs +136 -123
  48. package/dist/react/index.d.cts +178 -12
  49. package/dist/react/index.d.ts +178 -12
  50. package/dist/react/index.js +7 -6
  51. package/dist/styles.css +45 -0
  52. package/dist/tools/anthropic/index.cjs +3 -3
  53. package/dist/tools/anthropic/index.d.cts +1 -1
  54. package/dist/tools/anthropic/index.d.ts +1 -1
  55. package/dist/tools/anthropic/index.js +2 -2
  56. package/dist/tools/brave/index.cjs +6 -6
  57. package/dist/tools/brave/index.d.cts +1 -1
  58. package/dist/tools/brave/index.d.ts +1 -1
  59. package/dist/tools/brave/index.js +3 -3
  60. package/dist/tools/exa/index.cjs +6 -6
  61. package/dist/tools/exa/index.d.cts +1 -1
  62. package/dist/tools/exa/index.d.ts +1 -1
  63. package/dist/tools/exa/index.js +3 -3
  64. package/dist/tools/google/index.cjs +6 -6
  65. package/dist/tools/google/index.d.cts +1 -1
  66. package/dist/tools/google/index.d.ts +1 -1
  67. package/dist/tools/google/index.js +4 -4
  68. package/dist/tools/openai/index.cjs +6 -6
  69. package/dist/tools/openai/index.d.cts +1 -1
  70. package/dist/tools/openai/index.d.ts +1 -1
  71. package/dist/tools/openai/index.js +3 -3
  72. package/dist/tools/searxng/index.cjs +6 -6
  73. package/dist/tools/searxng/index.d.cts +1 -1
  74. package/dist/tools/searxng/index.d.ts +1 -1
  75. package/dist/tools/searxng/index.js +3 -3
  76. package/dist/tools/serper/index.cjs +6 -6
  77. package/dist/tools/serper/index.d.cts +1 -1
  78. package/dist/tools/serper/index.d.ts +1 -1
  79. package/dist/tools/serper/index.js +3 -3
  80. package/dist/tools/tavily/index.cjs +6 -6
  81. package/dist/tools/tavily/index.d.cts +1 -1
  82. package/dist/tools/tavily/index.d.ts +1 -1
  83. package/dist/tools/tavily/index.js +3 -3
  84. package/dist/tools/web-search/index.cjs +7 -7
  85. package/dist/tools/web-search/index.d.cts +2 -2
  86. package/dist/tools/web-search/index.d.ts +2 -2
  87. package/dist/tools/web-search/index.js +4 -4
  88. package/dist/{tools-tmksfhUo.d.cts → tools-DcS6Aeao.d.cts} +7 -3
  89. package/dist/{tools-tmksfhUo.d.ts → tools-DcS6Aeao.d.ts} +7 -3
  90. package/dist/{types-BqwW3Baj.d.ts → types-BUYni9B8.d.ts} +1 -1
  91. package/dist/{types-BLw7mxtW.d.cts → types-Cvg4DUoc.d.cts} +1 -1
  92. package/dist/{types-BeFBBZ5i.d.ts → types-waEqyE4K.d.cts} +5 -0
  93. package/dist/{types-BeFBBZ5i.d.cts → types-waEqyE4K.d.ts} +5 -0
  94. package/dist/ui/index.cjs +354 -524
  95. package/dist/ui/index.cjs.map +1 -1
  96. package/dist/ui/index.d.cts +22 -4
  97. package/dist/ui/index.d.ts +22 -4
  98. package/dist/ui/index.js +197 -372
  99. package/dist/ui/index.js.map +1 -1
  100. package/package.json +6 -1
  101. package/dist/chunk-3AONOZLY.js.map +0 -1
  102. package/dist/chunk-PT2TOHG5.js.map +0 -1
  103. package/dist/chunk-TCPAT3WG.cjs.map +0 -1
  104. package/dist/chunk-TPB7XED6.cjs.map +0 -1
  105. package/dist/chunk-WIXFZUEZ.cjs.map +0 -1
  106. package/dist/chunk-WZ2TOZ7M.js.map +0 -1
@@ -1,9 +1,8 @@
1
- import { ThreadManager, createLogger, zodToJsonSchema, isConsoleCaptureActive, startConsoleCapture, isNetworkCaptureActive, startNetworkCapture, stopConsoleCapture, stopNetworkCapture, isScreenshotSupported, captureScreenshot, getConsoleLogs, getNetworkRequests, clearConsoleLogs, clearNetworkRequests, formatLogsForAI, formatRequestsForAI, detectIntent, streamSSE, zodObjectToInputSchema } from './chunk-WZ2TOZ7M.js';
1
+ import { createLogger, zodToJsonSchema } from './chunk-YLZCTR4O.js';
2
2
  import { createMCPClient, MCPToolAdapter } from './chunk-EWVQWTNV.js';
3
3
  import { SkillRegistry } from './chunk-VNLLW3ZI.js';
4
- import React2, { createContext, useRef, useState, useCallback, useEffect, useMemo, useContext, useSyncExternalStore } from 'react';
4
+ import React2, { createContext, useRef, useState, useCallback, useEffect, useContext, useMemo, useSyncExternalStore } from 'react';
5
5
  import { jsxs, jsx } from 'react/jsx-runtime';
6
- import * as z from 'zod';
7
6
 
8
7
  // src/chat/types/tool.ts
9
8
  var initialAgentLoopState = {
@@ -14,88 +13,6 @@ var initialAgentLoopState = {
14
13
  isProcessing: false
15
14
  };
16
15
 
17
- // src/chat/interfaces/ChatState.ts
18
- var SimpleChatState = class {
19
- constructor() {
20
- this._messages = [];
21
- this._status = "ready";
22
- this._error = void 0;
23
- this.callbacks = /* @__PURE__ */ new Set();
24
- }
25
- get messages() {
26
- return this._messages;
27
- }
28
- set messages(value) {
29
- this._messages = value;
30
- this.notify();
31
- }
32
- get status() {
33
- return this._status;
34
- }
35
- set status(value) {
36
- this._status = value;
37
- this.notify();
38
- }
39
- get error() {
40
- return this._error;
41
- }
42
- set error(value) {
43
- this._error = value;
44
- this.notify();
45
- }
46
- pushMessage(message) {
47
- this._messages = [...this._messages, message];
48
- this.notify();
49
- }
50
- popMessage() {
51
- this._messages = this._messages.slice(0, -1);
52
- this.notify();
53
- }
54
- replaceMessage(index, message) {
55
- this._messages = [
56
- ...this._messages.slice(0, index),
57
- message,
58
- ...this._messages.slice(index + 1)
59
- ];
60
- this.notify();
61
- }
62
- updateLastMessage(updater) {
63
- if (this._messages.length === 0) return;
64
- const lastIndex = this._messages.length - 1;
65
- this.replaceMessage(lastIndex, updater(this._messages[lastIndex]));
66
- }
67
- updateMessageById(id, updater) {
68
- const index = this._messages.findIndex((m) => m.id === id);
69
- if (index === -1) return false;
70
- this.replaceMessage(index, updater(this._messages[index]));
71
- return true;
72
- }
73
- setMessages(messages) {
74
- this._messages = messages;
75
- this.notify();
76
- }
77
- clearMessages() {
78
- this._messages = [];
79
- this.notify();
80
- }
81
- subscribe(callback) {
82
- this.callbacks.add(callback);
83
- return () => this.callbacks.delete(callback);
84
- }
85
- getMessagesSnapshot() {
86
- return this._messages;
87
- }
88
- getStatusSnapshot() {
89
- return this._status;
90
- }
91
- getErrorSnapshot() {
92
- return this._error;
93
- }
94
- notify() {
95
- this.callbacks.forEach((cb) => cb());
96
- }
97
- };
98
-
99
16
  // src/chat/functions/stream/parseSSE.ts
100
17
  function parseSSELine(line) {
101
18
  if (!line || line.trim() === "") {
@@ -516,6 +433,88 @@ var HttpTransport = class {
516
433
  }
517
434
  };
518
435
 
436
+ // src/chat/interfaces/ChatState.ts
437
+ var SimpleChatState = class {
438
+ constructor() {
439
+ this._messages = [];
440
+ this._status = "ready";
441
+ this._error = void 0;
442
+ this.callbacks = /* @__PURE__ */ new Set();
443
+ }
444
+ get messages() {
445
+ return this._messages;
446
+ }
447
+ set messages(value) {
448
+ this._messages = value;
449
+ this.notify();
450
+ }
451
+ get status() {
452
+ return this._status;
453
+ }
454
+ set status(value) {
455
+ this._status = value;
456
+ this.notify();
457
+ }
458
+ get error() {
459
+ return this._error;
460
+ }
461
+ set error(value) {
462
+ this._error = value;
463
+ this.notify();
464
+ }
465
+ pushMessage(message) {
466
+ this._messages = [...this._messages, message];
467
+ this.notify();
468
+ }
469
+ popMessage() {
470
+ this._messages = this._messages.slice(0, -1);
471
+ this.notify();
472
+ }
473
+ replaceMessage(index, message) {
474
+ this._messages = [
475
+ ...this._messages.slice(0, index),
476
+ message,
477
+ ...this._messages.slice(index + 1)
478
+ ];
479
+ this.notify();
480
+ }
481
+ updateLastMessage(updater) {
482
+ if (this._messages.length === 0) return;
483
+ const lastIndex = this._messages.length - 1;
484
+ this.replaceMessage(lastIndex, updater(this._messages[lastIndex]));
485
+ }
486
+ updateMessageById(id, updater) {
487
+ const index = this._messages.findIndex((m) => m.id === id);
488
+ if (index === -1) return false;
489
+ this.replaceMessage(index, updater(this._messages[index]));
490
+ return true;
491
+ }
492
+ setMessages(messages) {
493
+ this._messages = messages;
494
+ this.notify();
495
+ }
496
+ clearMessages() {
497
+ this._messages = [];
498
+ this.notify();
499
+ }
500
+ subscribe(callback) {
501
+ this.callbacks.add(callback);
502
+ return () => this.callbacks.delete(callback);
503
+ }
504
+ getMessagesSnapshot() {
505
+ return this._messages;
506
+ }
507
+ getStatusSnapshot() {
508
+ return this._status;
509
+ }
510
+ getErrorSnapshot() {
511
+ return this._error;
512
+ }
513
+ notify() {
514
+ this.callbacks.forEach((cb) => cb());
515
+ }
516
+ };
517
+
519
518
  // src/chat/optimizations.ts
520
519
  var DEFAULT_CHARS_PER_TOKEN = 4;
521
520
  var DEFAULT_SAFETY_MARGIN = 1.2;
@@ -637,15 +636,15 @@ ${attachments}`,
637
636
  charsPerToken
638
637
  );
639
638
  }
640
- function estimateToolTokens(tool2, charsPerToken = DEFAULT_CHARS_PER_TOKEN) {
641
- return estimateTokens(JSON.stringify(tool2), charsPerToken);
639
+ function estimateToolTokens(tool, charsPerToken = DEFAULT_CHARS_PER_TOKEN) {
640
+ return estimateTokens(JSON.stringify(tool), charsPerToken);
642
641
  }
643
642
  function buildToolQuery(messages) {
644
643
  return messages.filter(
645
644
  (message) => message.role === "user" || message.role === "assistant"
646
645
  ).slice(-3).map((message) => message.content).filter(Boolean).join(" ");
647
646
  }
648
- function matchesSelector(tool2, selector, activeProfile) {
647
+ function matchesSelector(tool, selector, activeProfile) {
649
648
  const normalized = selector.trim().toLowerCase();
650
649
  if (!normalized) {
651
650
  return false;
@@ -653,40 +652,40 @@ function matchesSelector(tool2, selector, activeProfile) {
653
652
  if (normalized === "*" || normalized === "all") {
654
653
  return true;
655
654
  }
656
- if (normalized === tool2.name.toLowerCase()) {
655
+ if (normalized === tool.name.toLowerCase()) {
657
656
  return true;
658
657
  }
659
658
  if (normalized.startsWith("group:")) {
660
- return (tool2.group ?? "").toLowerCase() === normalized.slice(6);
659
+ return (tool.group ?? "").toLowerCase() === normalized.slice(6);
661
660
  }
662
661
  if (normalized.startsWith("category:")) {
663
- return (tool2.category ?? "").toLowerCase() === normalized.slice(9);
662
+ return (tool.category ?? "").toLowerCase() === normalized.slice(9);
664
663
  }
665
664
  if (normalized.startsWith("profile:")) {
666
- return (tool2.profiles ?? []).map((value) => value.toLowerCase()).includes(normalized.slice(8));
665
+ return (tool.profiles ?? []).map((value) => value.toLowerCase()).includes(normalized.slice(8));
667
666
  }
668
667
  if (activeProfile && normalized === activeProfile.toLowerCase()) {
669
- return (tool2.profiles ?? []).map((value) => value.toLowerCase()).includes(normalized);
668
+ return (tool.profiles ?? []).map((value) => value.toLowerCase()).includes(normalized);
670
669
  }
671
670
  return false;
672
671
  }
673
- function scoreTool(tool2, queryTokens, activeProfile) {
672
+ function scoreTool(tool, queryTokens, activeProfile) {
674
673
  const haystack = [
675
- tool2.name,
676
- tool2.description,
677
- tool2.category,
678
- tool2.group,
679
- ...tool2.profiles ?? [],
680
- ...tool2.searchKeywords ?? []
674
+ tool.name,
675
+ tool.description,
676
+ tool.category,
677
+ tool.group,
678
+ ...tool.profiles ?? [],
679
+ ...tool.searchKeywords ?? []
681
680
  ].filter(Boolean).join(" ").toLowerCase();
682
- let score = tool2.deferLoading ? 0 : 2;
683
- if (activeProfile && tool2.profiles?.includes(activeProfile)) {
681
+ let score = tool.deferLoading ? 0 : 2;
682
+ if (activeProfile && tool.profiles?.includes(activeProfile)) {
684
683
  score += 2;
685
684
  }
686
685
  for (const token of queryTokens) {
687
- if (tool2.name.toLowerCase() === token) {
686
+ if (tool.name.toLowerCase() === token) {
688
687
  score += 6;
689
- } else if (tool2.name.toLowerCase().includes(token)) {
688
+ } else if (tool.name.toLowerCase().includes(token)) {
690
689
  score += 4;
691
690
  } else if (haystack.includes(token)) {
692
691
  score += 2;
@@ -900,15 +899,15 @@ function buildToolDefinitions(selectedTools) {
900
899
  if (selectedTools.length === 0) {
901
900
  return void 0;
902
901
  }
903
- return selectedTools.map((tool2) => ({
904
- name: tool2.name,
905
- description: tool2.description,
906
- category: tool2.category,
907
- group: tool2.group,
908
- deferLoading: tool2.deferLoading,
909
- profiles: tool2.profiles,
910
- searchKeywords: tool2.searchKeywords,
911
- inputSchema: tool2.inputSchema
902
+ return selectedTools.map((tool) => ({
903
+ name: tool.name,
904
+ description: tool.description,
905
+ category: tool.category,
906
+ group: tool.group,
907
+ deferLoading: tool.deferLoading,
908
+ profiles: tool.profiles,
909
+ searchKeywords: tool.searchKeywords,
910
+ inputSchema: tool.inputSchema
912
911
  }));
913
912
  }
914
913
  function resolveTruncationConfig(params) {
@@ -938,21 +937,21 @@ function resolveTruncationConfig(params) {
938
937
  preserveErrors: merged.preserveErrors ?? true
939
938
  };
940
939
  }
941
- function buildToolResultContent(result, tool2, args) {
940
+ function buildToolResultContent(result, tool, args) {
942
941
  if (typeof result === "string") {
943
942
  return result;
944
943
  }
945
944
  const typedResult = result ?? null;
946
- const responseMode = typedResult?._aiResponseMode ?? tool2?.aiResponseMode ?? "full";
945
+ const responseMode = typedResult?._aiResponseMode ?? tool?.aiResponseMode ?? "full";
947
946
  if (typedResult?._aiContent) {
948
947
  return JSON.stringify(typedResult._aiContent);
949
948
  }
950
949
  let aiContext = typedResult?._aiContext;
951
- if (!aiContext && tool2?.aiContext) {
952
- aiContext = typeof tool2.aiContext === "function" ? tool2.aiContext(
950
+ if (!aiContext && tool?.aiContext) {
951
+ aiContext = typeof tool.aiContext === "function" ? tool.aiContext(
953
952
  typedResult ?? { success: true },
954
953
  args ?? {}
955
- ) : tool2.aiContext;
954
+ ) : tool.aiContext;
956
955
  }
957
956
  switch (responseMode) {
958
957
  case "none":
@@ -981,9 +980,9 @@ Full data: ${JSON.stringify(dataOnly)}`;
981
980
  }
982
981
  }
983
982
  }
984
- function buildToolResultContentForPrompt(result, tool2, args, config) {
985
- const text = buildToolResultContent(result, tool2, args);
986
- const truncation = resolveTruncationConfig({ tool: tool2, config });
983
+ function buildToolResultContentForPrompt(result, tool, args, config) {
984
+ const text = buildToolResultContent(result, tool, args);
985
+ const truncation = resolveTruncationConfig({ tool, config });
987
986
  if (!truncation.enabled) {
988
987
  return text;
989
988
  }
@@ -1089,7 +1088,7 @@ function fitToolsToBudget(params) {
1089
1088
  return tools;
1090
1089
  }
1091
1090
  const getToolTokens = () => tools.reduce(
1092
- (sum, tool2) => sum + estimateToolTokens(tool2, params.charsPerToken),
1091
+ (sum, tool) => sum + estimateToolTokens(tool, params.charsPerToken),
1093
1092
  0
1094
1093
  );
1095
1094
  while (tools.length > 0 && getToolTokens() > params.maxTokens) {
@@ -1127,7 +1126,7 @@ function calculateBuckets(params) {
1127
1126
  0
1128
1127
  );
1129
1128
  const toolDefinitionTokens = (params.requestTools ?? []).reduce(
1130
- (sum, tool2) => sum + estimateToolTokens(tool2, params.charsPerToken),
1129
+ (sum, tool) => sum + estimateToolTokens(tool, params.charsPerToken),
1131
1130
  0
1132
1131
  );
1133
1132
  const total = systemPromptTokens + historyTokens + toolResultsTokens + toolDefinitionTokens;
@@ -1366,7 +1365,7 @@ var ChatContextOptimizer = class {
1366
1365
  if (!tools.length) {
1367
1366
  return [];
1368
1367
  }
1369
- const available = tools.filter((tool2) => tool2.available !== false);
1368
+ const available = tools.filter((tool) => tool.available !== false);
1370
1369
  const profileConfig = this.config?.toolProfiles;
1371
1370
  if (!profileConfig?.enabled) {
1372
1371
  return available;
@@ -1377,22 +1376,22 @@ var ChatContextOptimizer = class {
1377
1376
  let filtered = available;
1378
1377
  if (profile?.include?.length) {
1379
1378
  filtered = filtered.filter(
1380
- (tool2) => profile.include.some(
1381
- (selector) => matchesSelector(tool2, selector, activeProfile)
1382
- ) || !!activeProfile && tool2.profiles?.includes(activeProfile)
1379
+ (tool) => profile.include.some(
1380
+ (selector) => matchesSelector(tool, selector, activeProfile)
1381
+ ) || !!activeProfile && tool.profiles?.includes(activeProfile)
1383
1382
  );
1384
1383
  } else if (activeProfile) {
1385
- filtered = filtered.filter((tool2) => {
1386
- if (tool2.profiles?.length) {
1387
- return tool2.profiles.includes(activeProfile);
1384
+ filtered = filtered.filter((tool) => {
1385
+ if (tool.profiles?.length) {
1386
+ return tool.profiles.includes(activeProfile);
1388
1387
  }
1389
1388
  return includeUnprofiled;
1390
1389
  });
1391
1390
  }
1392
1391
  if (profile?.exclude?.length) {
1393
1392
  filtered = filtered.filter(
1394
- (tool2) => !profile.exclude.some(
1395
- (selector) => matchesSelector(tool2, selector, activeProfile)
1393
+ (tool) => !profile.exclude.some(
1394
+ (selector) => matchesSelector(tool, selector, activeProfile)
1396
1395
  )
1397
1396
  );
1398
1397
  }
@@ -1436,7 +1435,7 @@ var ChatContextOptimizer = class {
1436
1435
  }
1437
1436
  }
1438
1437
  const toolDefMap = new Map(
1439
- allTools.map((tool2) => [tool2.name, tool2])
1438
+ allTools.map((tool) => [tool.name, tool])
1440
1439
  );
1441
1440
  return messages.map((message) => {
1442
1441
  if (message.role !== "tool") {
@@ -1449,20 +1448,20 @@ var ChatContextOptimizer = class {
1449
1448
  };
1450
1449
  }
1451
1450
  const toolCall = message.toolCallId ? toolCallMap.get(message.toolCallId) : void 0;
1452
- const tool2 = toolCall ? toolDefMap.get(toolCall.toolName) : void 0;
1451
+ const tool = toolCall ? toolDefMap.get(toolCall.toolName) : void 0;
1453
1452
  let content = message.content;
1454
1453
  try {
1455
1454
  const parsed = JSON.parse(message.content);
1456
1455
  content = buildToolResultContentForPrompt(
1457
1456
  parsed,
1458
- tool2,
1457
+ tool,
1459
1458
  toolCall?.args ?? {},
1460
1459
  this.config
1461
1460
  );
1462
1461
  } catch {
1463
1462
  content = buildToolResultContentForPrompt(
1464
1463
  message.content,
1465
- tool2,
1464
+ tool,
1466
1465
  toolCall?.args ?? {},
1467
1466
  this.config
1468
1467
  );
@@ -1585,10 +1584,20 @@ var AbstractChat = class {
1585
1584
  this.state.pushMessage(userMessage);
1586
1585
  this.state.status = "submitted";
1587
1586
  this.state.error = void 0;
1587
+ let preCreatedMessageId;
1588
+ if (this.config.streaming !== false) {
1589
+ const visibleMessages2 = this.state.messages;
1590
+ const currentLeafId = visibleMessages2.length > 0 ? visibleMessages2[visibleMessages2.length - 1].id : void 0;
1591
+ const preMsg = createEmptyAssistantMessage(void 0, {
1592
+ parentId: currentLeafId
1593
+ });
1594
+ this.state.pushMessage(preMsg);
1595
+ preCreatedMessageId = preMsg.id;
1596
+ }
1588
1597
  this.callbacks.onMessagesChange?.(this._allMessages());
1589
1598
  this.callbacks.onStatusChange?.("submitted");
1590
1599
  await Promise.resolve();
1591
- await this.processRequest();
1600
+ await this.processRequest({ preCreatedMessageId });
1592
1601
  return true;
1593
1602
  } catch (error) {
1594
1603
  this.handleError(error);
@@ -1708,6 +1717,44 @@ var AbstractChat = class {
1708
1717
  this.handleError(error);
1709
1718
  }
1710
1719
  }
1720
+ /**
1721
+ * Add tool result messages to history and stop — does NOT trigger a new LLM request.
1722
+ *
1723
+ * Use this instead of continueWithToolResults when you want to close out pending
1724
+ * tool_use blocks (so the history stays valid) without letting the AI continue.
1725
+ * Optionally appends a final assistant message (e.g. an iteration-limit notice).
1726
+ */
1727
+ async addToolResultMessages(toolResults, finalAssistantContent) {
1728
+ const visibleMessages = this.state.messages;
1729
+ let chainParentId = visibleMessages.length > 0 ? visibleMessages[visibleMessages.length - 1].id : void 0;
1730
+ for (const { toolCallId, result } of toolResults) {
1731
+ const messageContent = typeof result === "string" ? result : JSON.stringify(result);
1732
+ const toolMessageId = generateMessageId();
1733
+ const toolMessage = {
1734
+ id: toolMessageId,
1735
+ role: "tool",
1736
+ content: messageContent,
1737
+ toolCallId,
1738
+ createdAt: /* @__PURE__ */ new Date(),
1739
+ ...chainParentId !== void 0 ? { parentId: chainParentId } : {}
1740
+ };
1741
+ this.state.pushMessage(toolMessage);
1742
+ chainParentId = toolMessageId;
1743
+ }
1744
+ if (finalAssistantContent) {
1745
+ const assistantMsg = {
1746
+ id: generateMessageId(),
1747
+ role: "assistant",
1748
+ content: finalAssistantContent,
1749
+ createdAt: /* @__PURE__ */ new Date(),
1750
+ ...chainParentId !== void 0 ? { parentId: chainParentId } : {}
1751
+ };
1752
+ this.state.pushMessage(assistantMsg);
1753
+ }
1754
+ this.callbacks.onMessagesChange?.(this._allMessages());
1755
+ this.state.status = "ready";
1756
+ this.callbacks.onStatusChange?.("ready");
1757
+ }
1711
1758
  /**
1712
1759
  * Stop generation
1713
1760
  */
@@ -1812,10 +1859,10 @@ var AbstractChat = class {
1812
1859
  /**
1813
1860
  * Process a chat request
1814
1861
  */
1815
- async processRequest() {
1862
+ async processRequest(options) {
1816
1863
  const request = this.buildRequest();
1817
- let preCreatedMessageId;
1818
- if (this.config.streaming !== false) {
1864
+ let preCreatedMessageId = options?.preCreatedMessageId;
1865
+ if (this.config.streaming !== false && !preCreatedMessageId) {
1819
1866
  const visibleMessages = this.state.messages;
1820
1867
  const currentLeafId = visibleMessages.length > 0 ? visibleMessages[visibleMessages.length - 1].id : void 0;
1821
1868
  const preMsg = createEmptyAssistantMessage(void 0, {
@@ -1948,15 +1995,15 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt;
1948
1995
  threadId: this.config.threadId,
1949
1996
  systemPrompt,
1950
1997
  llm: this.config.llm,
1951
- tools: this.config.tools?.length ? this.config.tools.map((tool2) => ({
1952
- name: tool2.name,
1953
- description: tool2.description,
1954
- category: tool2.category,
1955
- group: tool2.group,
1956
- deferLoading: tool2.deferLoading,
1957
- profiles: tool2.profiles,
1958
- searchKeywords: tool2.searchKeywords,
1959
- inputSchema: tool2.inputSchema
1998
+ tools: this.config.tools?.length ? this.config.tools.map((tool) => ({
1999
+ name: tool.name,
2000
+ description: tool.description,
2001
+ category: tool.category,
2002
+ group: tool.group,
2003
+ deferLoading: tool.deferLoading,
2004
+ profiles: tool.profiles,
2005
+ searchKeywords: tool.searchKeywords,
2006
+ inputSchema: tool.inputSchema
1960
2007
  })) : void 0,
1961
2008
  __skills: this.inlineSkills.length ? this.inlineSkills : void 0
1962
2009
  };
@@ -2033,7 +2080,11 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt;
2033
2080
  }
2034
2081
  if (chunk.type === "message:start" && this.streamState === null) {
2035
2082
  this.debug("message:start after mid-stream end - creating new message");
2036
- const newMessage = createEmptyAssistantMessage();
2083
+ const currentLeaf = this.state.messages;
2084
+ const currentLeafId = currentLeaf.length > 0 ? currentLeaf[currentLeaf.length - 1].id : void 0;
2085
+ const newMessage = createEmptyAssistantMessage(void 0, {
2086
+ parentId: currentLeafId
2087
+ });
2037
2088
  this.state.pushMessage(newMessage);
2038
2089
  this.streamState = createStreamState(newMessage.id);
2039
2090
  this.callbacks.onMessageStart?.(newMessage.id);
@@ -2103,10 +2154,19 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt;
2103
2154
  break;
2104
2155
  }
2105
2156
  }
2157
+ const insertParentId = insertIdx > 0 ? currentMessages[insertIdx - 1].id : void 0;
2158
+ const linkedToInsert = messagesToInsert.map((msg, i) => ({
2159
+ ...msg,
2160
+ parentId: i === 0 ? insertParentId : messagesToInsert[i - 1].id
2161
+ }));
2162
+ const lastInsertedId = linkedToInsert[linkedToInsert.length - 1].id;
2163
+ const updatedCurrent = currentMessages.map(
2164
+ (m, idx) => idx === insertIdx ? { ...m, parentId: lastInsertedId } : m
2165
+ );
2106
2166
  this.state.setMessages([
2107
- ...currentMessages.slice(0, insertIdx),
2108
- ...messagesToInsert,
2109
- ...currentMessages.slice(insertIdx)
2167
+ ...updatedCurrent.slice(0, insertIdx),
2168
+ ...linkedToInsert,
2169
+ ...updatedCurrent.slice(insertIdx)
2110
2170
  ]);
2111
2171
  }
2112
2172
  }
@@ -2171,6 +2231,10 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt;
2171
2231
  ...existing.childrenIds !== void 0 ? { childrenIds: existing.childrenIds } : {}
2172
2232
  })
2173
2233
  );
2234
+ this.callbacks.onStreamChunk?.({
2235
+ ...chunk,
2236
+ messageId: assistantMessage.id
2237
+ });
2174
2238
  if (chunk.type === "message:delta") {
2175
2239
  this.callbacks.onMessageDelta?.(assistantMessage.id, chunk.content);
2176
2240
  }
@@ -2232,12 +2296,26 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt;
2232
2296
  (message) => message.id === this.streamState.messageId
2233
2297
  ) : -1;
2234
2298
  if (currentStreamIndex === -1) {
2235
- this.state.setMessages([...currentMessages, ...messagesToInsert]);
2299
+ const appendParentId = currentMessages.length > 0 ? currentMessages[currentMessages.length - 1].id : void 0;
2300
+ const linkedToInsert = messagesToInsert.map((msg, i) => ({
2301
+ ...msg,
2302
+ parentId: i === 0 ? appendParentId : messagesToInsert[i - 1].id
2303
+ }));
2304
+ this.state.setMessages([...currentMessages, ...linkedToInsert]);
2236
2305
  } else {
2306
+ const insertParentId = currentStreamIndex > 0 ? currentMessages[currentStreamIndex - 1].id : void 0;
2307
+ const linkedToInsert = messagesToInsert.map((msg, i) => ({
2308
+ ...msg,
2309
+ parentId: i === 0 ? insertParentId : messagesToInsert[i - 1].id
2310
+ }));
2311
+ const lastInsertedId = linkedToInsert[linkedToInsert.length - 1].id;
2312
+ const updatedCurrent = currentMessages.map(
2313
+ (m, idx) => idx === currentStreamIndex ? { ...m, parentId: lastInsertedId } : m
2314
+ );
2237
2315
  this.state.setMessages([
2238
- ...currentMessages.slice(0, currentStreamIndex),
2239
- ...messagesToInsert,
2240
- ...currentMessages.slice(currentStreamIndex)
2316
+ ...updatedCurrent.slice(0, currentStreamIndex),
2317
+ ...linkedToInsert,
2318
+ ...updatedCurrent.slice(currentStreamIndex)
2241
2319
  ]);
2242
2320
  }
2243
2321
  }
@@ -2477,8 +2555,8 @@ var AbstractAgentLoop = class {
2477
2555
  this._maxIterations = config.maxIterations ?? 20;
2478
2556
  this._maxExecutionHistory = config.maxExecutionHistory ?? 100;
2479
2557
  if (config.tools) {
2480
- for (const tool2 of config.tools) {
2481
- this.registerTool(tool2);
2558
+ for (const tool of config.tools) {
2559
+ this.registerTool(tool);
2482
2560
  }
2483
2561
  }
2484
2562
  }
@@ -2567,15 +2645,15 @@ var AbstractAgentLoop = class {
2567
2645
  * Register a tool (reference counted for React StrictMode compatibility)
2568
2646
  * Tools are never fully removed - ref counting ensures StrictMode works correctly
2569
2647
  */
2570
- registerTool(tool2) {
2571
- const existing = this.registeredTools.get(tool2.name);
2648
+ registerTool(tool) {
2649
+ const existing = this.registeredTools.get(tool.name);
2572
2650
  if (existing) {
2573
- existing.tool = tool2;
2651
+ existing.tool = tool;
2574
2652
  existing.refCount++;
2575
2653
  existing.active = true;
2576
2654
  } else {
2577
- this.registeredTools.set(tool2.name, {
2578
- tool: tool2,
2655
+ this.registeredTools.set(tool.name, {
2656
+ tool,
2579
2657
  refCount: 1,
2580
2658
  active: true
2581
2659
  });
@@ -2640,7 +2718,7 @@ var AbstractAgentLoop = class {
2640
2718
  * Execute a single tool
2641
2719
  */
2642
2720
  async executeSingleTool(toolCall) {
2643
- const tool2 = this.getTool(toolCall.name);
2721
+ const tool = this.getTool(toolCall.name);
2644
2722
  const execution = {
2645
2723
  id: toolCall.id,
2646
2724
  toolCallId: toolCall.id,
@@ -2649,11 +2727,11 @@ var AbstractAgentLoop = class {
2649
2727
  status: "pending",
2650
2728
  approvalStatus: "none",
2651
2729
  startedAt: /* @__PURE__ */ new Date(),
2652
- hidden: tool2?.hidden
2730
+ hidden: tool?.hidden
2653
2731
  };
2654
2732
  this.addToolExecution(execution);
2655
2733
  this.callbacks.onToolStart?.(execution);
2656
- if (!tool2) {
2734
+ if (!tool) {
2657
2735
  const errorResult = {
2658
2736
  toolCallId: toolCall.id,
2659
2737
  success: false,
@@ -2667,9 +2745,9 @@ var AbstractAgentLoop = class {
2667
2745
  return errorResult;
2668
2746
  }
2669
2747
  let approvalData;
2670
- if (tool2.needsApproval && !this.config.autoApprove) {
2671
- const approvalTitle = typeof tool2.approvalTitle === "function" ? tool2.approvalTitle(toolCall.args) : tool2.approvalTitle;
2672
- const approvalMessage = typeof tool2.approvalMessage === "function" ? tool2.approvalMessage(toolCall.args) : tool2.approvalMessage;
2748
+ if (tool.needsApproval && !this.config.autoApprove) {
2749
+ const approvalTitle = typeof tool.approvalTitle === "function" ? tool.approvalTitle(toolCall.args) : tool.approvalTitle;
2750
+ const approvalMessage = typeof tool.approvalMessage === "function" ? tool.approvalMessage(toolCall.args) : tool.approvalMessage;
2673
2751
  execution.approvalStatus = "required";
2674
2752
  execution.approvalTitle = approvalTitle;
2675
2753
  execution.approvalMessage = approvalMessage;
@@ -2698,13 +2776,13 @@ var AbstractAgentLoop = class {
2698
2776
  }
2699
2777
  this.updateToolExecution(toolCall.id, { status: "executing" });
2700
2778
  try {
2701
- if (!tool2.handler) {
2779
+ if (!tool.handler) {
2702
2780
  throw new Error(`Tool "${toolCall.name}" has no handler`);
2703
2781
  }
2704
2782
  if (this._isCancelled || this.abortController?.signal.aborted) {
2705
2783
  throw new Error("Tool execution cancelled");
2706
2784
  }
2707
- const result = await tool2.handler(toolCall.args, {
2785
+ const result = await tool.handler(toolCall.args, {
2708
2786
  signal: this.abortController?.signal,
2709
2787
  data: { toolCallId: toolCall.id },
2710
2788
  approvalData
@@ -2927,738 +3005,747 @@ var AbstractAgentLoop = class {
2927
3005
  }
2928
3006
  };
2929
3007
 
2930
- // src/chat/ChatWithTools.ts
2931
- var ChatWithTools = class {
2932
- constructor(config, callbacks = {}) {
2933
- this.config = config;
2934
- this.callbacks = callbacks;
2935
- this.agentLoop = new AbstractAgentLoop(
2936
- {
2937
- maxIterations: config.maxIterations ?? 20,
2938
- tools: config.tools
2939
- },
2940
- {
2941
- onExecutionsChange: (executions) => {
2942
- callbacks.onToolExecutionsChange?.(executions);
2943
- },
2944
- onApprovalRequired: (execution) => {
2945
- callbacks.onApprovalRequired?.(execution);
2946
- }
3008
+ // src/chat/branching/MessageTree.ts
3009
+ var _MessageTree = class _MessageTree {
3010
+ constructor(messages) {
3011
+ /** All messages by ID */
3012
+ this.nodeMap = /* @__PURE__ */ new Map();
3013
+ /** parentKey → ordered list of child IDs (insertion order = oldest-first) */
3014
+ this.childrenOf = /* @__PURE__ */ new Map();
3015
+ /** parentKey currently-active child ID */
3016
+ this.activeChildMap = /* @__PURE__ */ new Map();
3017
+ /** Current leaf message ID (tip of the active path) */
3018
+ this._currentLeafId = null;
3019
+ /** Cached visible messages — invalidated on every mutation */
3020
+ this._visibleCache = null;
3021
+ if (messages?.length) {
3022
+ this._buildFromMessages(messages);
3023
+ }
3024
+ }
3025
+ // ============================================
3026
+ // Static Migration Helpers
3027
+ // ============================================
3028
+ /**
3029
+ * Convert a legacy flat array (no parentId) to a tree-linked array.
3030
+ *
3031
+ * Rules:
3032
+ * - Tool messages get parentId = the owning assistant message's id
3033
+ * (matched via toolCallId → toolCall.id), chained in sequence.
3034
+ * - All other messages get parentId = the previous message in the linear
3035
+ * chain (including tool messages, so the next non-tool after tool results
3036
+ * is a child of the last tool result, not a sibling).
3037
+ *
3038
+ * Returns a new array with parentId/childrenIds filled in.
3039
+ * Does NOT mutate the original messages.
3040
+ */
3041
+ static fromFlatArray(messages) {
3042
+ if (messages.length === 0) return messages;
3043
+ const alreadyLinked = messages.some((m) => m.parentId !== void 0);
3044
+ if (alreadyLinked) return messages;
3045
+ const result = [];
3046
+ let prevId = null;
3047
+ const assistantById = /* @__PURE__ */ new Map();
3048
+ for (const msg of messages) {
3049
+ if (msg.role === "assistant") {
3050
+ assistantById.set(msg.id, msg);
2947
3051
  }
2948
- );
2949
- this.chat = new AbstractChat({
2950
- runtimeUrl: config.runtimeUrl,
2951
- llm: config.llm,
2952
- systemPrompt: config.systemPrompt,
2953
- streaming: config.streaming,
2954
- headers: config.headers,
2955
- body: config.body,
2956
- optimization: config.optimization,
2957
- threadId: config.threadId,
2958
- debug: config.debug,
2959
- initialMessages: config.initialMessages,
2960
- state: config.state,
2961
- transport: config.transport,
2962
- callbacks: {
2963
- onMessagesChange: callbacks.onMessagesChange,
2964
- onStatusChange: callbacks.onStatusChange,
2965
- onError: callbacks.onError,
2966
- onMessageStart: callbacks.onMessageStart,
2967
- onMessageDelta: callbacks.onMessageDelta,
2968
- onMessageFinish: callbacks.onMessageFinish,
2969
- onToolCalls: callbacks.onToolCalls,
2970
- onFinish: callbacks.onFinish,
2971
- onContextUsageChange: callbacks.onContextUsageChange,
2972
- // Server-side tool callbacks - track in agentLoop for UI display
2973
- // IMPORTANT: Only track tools that are NOT registered client-side
2974
- // Client-side tools are tracked via executeToolCalls() path
2975
- onServerToolStart: (info) => {
2976
- const existingExecution = this.agentLoop.toolExecutions.find(
2977
- (e) => e.id === info.id
2978
- );
2979
- if (existingExecution) {
2980
- if (info.hidden !== void 0 && existingExecution.hidden !== info.hidden) {
2981
- this.debug(
2982
- "Updating hidden flag for existing execution:",
2983
- info.name,
2984
- info.hidden
2985
- );
2986
- this.agentLoop.updateToolExecution(info.id, {
2987
- hidden: info.hidden
2988
- });
2989
- }
2990
- return;
2991
- }
2992
- const isClientTool = this.agentLoop.tools.some(
2993
- (t) => t.name === info.name && t.location === "client"
2994
- );
2995
- if (isClientTool) {
2996
- this.debug("Skipping server tracking for client tool:", info.name);
2997
- return;
2998
- }
2999
- this.debug("Server tool started:", info.name, {
3000
- hidden: info.hidden,
3001
- id: info.id
3002
- });
3003
- this.agentLoop.addServerToolExecution(info);
3004
- },
3005
- onServerToolArgs: (info) => {
3006
- const isClientTool = this.agentLoop.tools.some(
3007
- (t) => t.name === info.name && t.location === "client"
3008
- );
3009
- if (isClientTool) return;
3010
- this.debug("Server tool args:", info.name, info.args);
3011
- this.agentLoop.updateServerToolArgs(info.id, info.args ?? {});
3012
- },
3013
- onServerToolEnd: (info) => {
3014
- const isClientTool = this.agentLoop.tools.some(
3015
- (t) => t.name === info.name && t.location === "client"
3016
- );
3017
- if (isClientTool) return;
3018
- this.debug("Server tool ended:", info.name, {
3019
- error: info.error,
3020
- hasResult: !!info.result
3021
- });
3022
- this.agentLoop.completeServerToolExecution(info);
3023
- }
3024
- }
3025
- });
3026
- this.wireEvents();
3027
- }
3028
- /**
3029
- * Wire up internal events between chat and agent loop
3030
- */
3031
- wireEvents() {
3032
- this.debug("Wiring up toolCalls event handler");
3033
- this.chat.on("toolCalls", async (event) => {
3034
- const toolCalls = event.toolCalls;
3035
- if (!toolCalls?.length) {
3036
- return;
3037
- }
3038
- this.debug("Tool calls received:", toolCalls.length);
3039
- const toolCallInfos = toolCalls.map((tc) => {
3040
- const tcAny = tc;
3041
- const name = tcAny.function?.name ?? tcAny.name ?? "";
3042
- let args = {};
3043
- if (tcAny.function?.arguments) {
3044
- try {
3045
- args = JSON.parse(tcAny.function.arguments);
3046
- } catch {
3047
- args = {};
3052
+ }
3053
+ const lastToolIdByAssistant = /* @__PURE__ */ new Map();
3054
+ for (const msg of messages) {
3055
+ if (msg.role === "tool" && msg.toolCallId) {
3056
+ let ownerAssistantId = null;
3057
+ for (const [, assistant] of assistantById) {
3058
+ if (assistant.toolCalls?.some((tc) => tc.id === msg.toolCallId)) {
3059
+ ownerAssistantId = assistant.id;
3060
+ break;
3048
3061
  }
3049
- } else if (tcAny.args) {
3050
- args = tcAny.args;
3051
3062
  }
3052
- return { id: tc.id, name, args };
3053
- });
3054
- try {
3055
- const results = await this.agentLoop.executeToolCalls(toolCallInfos);
3056
- this.debug("Tool results:", results);
3057
- if (results.length > 0) {
3058
- const toolResults = results.map((r) => ({
3059
- toolCallId: r.toolCallId,
3060
- result: r.success ? r.result : { success: false, error: r.error }
3061
- }));
3062
- await this.chat.continueWithToolResults(toolResults);
3063
- } else if (this.agentLoop.maxIterationsReached && toolCallInfos.length > 0) {
3064
- this.debug("Max iterations reached, adding blocked tool results");
3065
- const errorMessage = this.config.maxIterationsMessage || "Tool execution paused: iteration limit reached. User can say 'continue' to resume.";
3066
- const blockedResults = toolCallInfos.map((tc) => ({
3067
- toolCallId: tc.id,
3068
- result: {
3069
- success: false,
3070
- error: errorMessage
3071
- }
3072
- }));
3073
- await this.chat.continueWithToolResults(blockedResults);
3063
+ const chainParent = ownerAssistantId ? lastToolIdByAssistant.get(ownerAssistantId) ?? ownerAssistantId : prevId ?? null;
3064
+ result.push({
3065
+ ...msg,
3066
+ parentId: chainParent,
3067
+ childrenIds: []
3068
+ });
3069
+ if (ownerAssistantId) {
3070
+ lastToolIdByAssistant.set(ownerAssistantId, msg.id);
3074
3071
  }
3075
- } catch (error) {
3076
- this.debug("Error executing tools:", error);
3077
- console.error("[ChatWithTools] Tool execution error:", error);
3072
+ prevId = msg.id;
3073
+ } else {
3074
+ result.push({
3075
+ ...msg,
3076
+ parentId: prevId,
3077
+ childrenIds: []
3078
+ });
3079
+ prevId = msg.id;
3078
3080
  }
3079
- });
3081
+ }
3082
+ const childrenMap = /* @__PURE__ */ new Map();
3083
+ for (const msg of result) {
3084
+ const parentKey = msg.parentId == null ? _MessageTree.ROOT_KEY : msg.parentId;
3085
+ if (!childrenMap.has(parentKey)) {
3086
+ childrenMap.set(parentKey, []);
3087
+ }
3088
+ childrenMap.get(parentKey).push(msg.id);
3089
+ }
3090
+ return result.map((msg) => ({
3091
+ ...msg,
3092
+ childrenIds: childrenMap.get(msg.id) ?? []
3093
+ }));
3080
3094
  }
3081
3095
  // ============================================
3082
- // Chat Getters
3096
+ // Core Queries
3083
3097
  // ============================================
3084
- get messages() {
3085
- return this.chat.messages;
3086
- }
3087
- get status() {
3088
- return this.chat.status;
3089
- }
3090
- get error() {
3091
- return this.chat.error;
3098
+ /**
3099
+ * Returns the visible path (root → current leaf) — what the UI renders
3100
+ * and what gets sent to the API.
3101
+ *
3102
+ * Backward-compat: if NO message has parentId set (all undefined),
3103
+ * falls back to insertion order (legacy linear mode).
3104
+ */
3105
+ getVisibleMessages() {
3106
+ if (this._visibleCache !== null) return this._visibleCache;
3107
+ if (this.nodeMap.size === 0) {
3108
+ this._visibleCache = [];
3109
+ return this._visibleCache;
3110
+ }
3111
+ const hasTreeStructure = Array.from(this.nodeMap.values()).some(
3112
+ (m) => m.parentId !== void 0
3113
+ );
3114
+ this._visibleCache = hasTreeStructure ? this._getActivePath().map((id) => this.nodeMap.get(id)) : Array.from(this.nodeMap.values());
3115
+ return this._visibleCache;
3092
3116
  }
3093
- get isStreaming() {
3094
- return this.chat.isStreaming;
3117
+ _invalidateCache() {
3118
+ this._visibleCache = null;
3095
3119
  }
3096
3120
  /**
3097
- * Whether any operation is in progress (chat or tools)
3098
- * Use this to show loading indicators and disable send button
3121
+ * Returns ALL messages across every branch (for persistence / ThreadManager).
3099
3122
  */
3100
- get isLoading() {
3101
- const chatBusy = this.status === "submitted" || this.status === "streaming";
3102
- const toolsBusy = this.agentLoop.isProcessing;
3103
- const hasPendingApprovals = this.agentLoop.pendingApprovalExecutions.length > 0;
3104
- return chatBusy || toolsBusy || hasPendingApprovals;
3123
+ getAllMessages() {
3124
+ return Array.from(this.nodeMap.values());
3105
3125
  }
3106
3126
  /**
3107
- * Check if a request is currently in progress (excludes pending approvals)
3108
- * Use this to prevent sending new messages
3127
+ * Branch navigation info for the UI navigator.
3128
+ * Returns null if the message has no siblings (only child).
3109
3129
  */
3110
- get isBusy() {
3111
- const chatBusy = this.status === "submitted" || this.status === "streaming";
3112
- const toolsBusy = this.agentLoop.isProcessing;
3113
- return chatBusy || toolsBusy;
3114
- }
3115
- // ============================================
3116
- // Tool Execution Getters
3117
- // ============================================
3118
- get toolExecutions() {
3119
- return this.agentLoop.toolExecutions;
3120
- }
3121
- get tools() {
3122
- return this.agentLoop.tools;
3123
- }
3124
- get iteration() {
3125
- return this.agentLoop.iteration;
3130
+ getBranchInfo(messageId) {
3131
+ const msg = this.nodeMap.get(messageId);
3132
+ if (!msg) return null;
3133
+ const parentKey = this._parentKey(msg.parentId);
3134
+ const siblings = this.childrenOf.get(parentKey) ?? [];
3135
+ if (siblings.length <= 1) return null;
3136
+ const siblingIndex = siblings.indexOf(messageId);
3137
+ return {
3138
+ siblingIndex,
3139
+ totalSiblings: siblings.length,
3140
+ siblingIds: [...siblings],
3141
+ hasPrevious: siblingIndex > 0,
3142
+ hasNext: siblingIndex < siblings.length - 1
3143
+ };
3126
3144
  }
3127
- get maxIterations() {
3128
- return this.agentLoop.maxIterations;
3145
+ get currentLeafId() {
3146
+ return this._currentLeafId;
3129
3147
  }
3130
- get isProcessing() {
3131
- return this.agentLoop.isProcessing;
3148
+ get hasBranches() {
3149
+ for (const children of this.childrenOf.values()) {
3150
+ if (children.length > 1) return true;
3151
+ }
3152
+ return false;
3132
3153
  }
3133
3154
  // ============================================
3134
- // Chat Actions
3155
+ // Mutations
3135
3156
  // ============================================
3136
3157
  /**
3137
- * Send a message
3138
- * Returns false if a request is already in progress
3139
- *
3140
- * @param options.editMessageId - Edit flow: new message branches from the
3141
- * same parent as this message ID
3158
+ * Insert a new message.
3159
+ * - Updates childrenOf and nodeMap.
3160
+ * - New branch becomes active (activeChildMap updated).
3161
+ * - Updates current leaf.
3142
3162
  */
3143
- async sendMessage(content, attachments, options) {
3144
- if (this.isLoading) {
3145
- this.debug("sendMessage blocked - request already in progress");
3146
- return false;
3163
+ addMessage(message) {
3164
+ this.nodeMap.set(message.id, message);
3165
+ const parentKey = this._parentKey(message.parentId);
3166
+ if (!this.childrenOf.has(parentKey)) {
3167
+ this.childrenOf.set(parentKey, []);
3147
3168
  }
3148
- this.agentLoop.resetIterations();
3149
- return await this.chat.sendMessage(content, attachments, options);
3150
- }
3151
- /**
3152
- * Stop generation and cancel any running tools
3153
- */
3154
- stop() {
3155
- this.agentLoop.cancel();
3156
- this.chat.stop();
3157
- this.debug("Stopped - cancelled tools and aborted stream");
3158
- }
3159
- /**
3160
- * Clear all messages
3161
- */
3162
- clearMessages() {
3163
- this.chat.clearMessages();
3164
- this.agentLoop.clearToolExecutions();
3165
- }
3166
- /**
3167
- * Set messages directly
3168
- */
3169
- setMessages(messages) {
3170
- this.chat.setMessages(messages);
3171
- }
3172
- /**
3173
- * Regenerate last response
3174
- */
3175
- async regenerate(messageId) {
3176
- await this.chat.regenerate(messageId);
3177
- }
3178
- /**
3179
- * Set tools available for the LLM
3180
- */
3181
- setTools(tools) {
3182
- this.chat.setTools(tools);
3183
- }
3184
- /**
3185
- * Update prompt/tool optimization controls.
3186
- */
3187
- setOptimizationConfig(config) {
3188
- this.config.optimization = config;
3189
- this.chat.setOptimizationConfig(config);
3190
- }
3191
- /**
3192
- * Set the active tool profile used for request-time tool selection.
3193
- */
3194
- setToolProfile(profile) {
3195
- this.chat.setToolProfile(profile);
3196
- }
3197
- /**
3198
- * Get the most recent prompt context usage snapshot.
3199
- */
3200
- getContextUsage() {
3201
- return this.chat.getContextUsage();
3202
- }
3203
- /**
3204
- * Set dynamic context (from useAIContext hook)
3205
- */
3206
- setContext(context) {
3207
- this.chat.setContext(context);
3208
- }
3209
- /**
3210
- * Set system prompt dynamically
3211
- */
3212
- setSystemPrompt(prompt) {
3213
- this.chat.setSystemPrompt(prompt);
3214
- }
3215
- /**
3216
- * Set headers configuration
3217
- * Can be static headers or a getter function for dynamic resolution
3218
- */
3219
- setHeaders(headers) {
3220
- this.chat.setHeaders(headers);
3221
- }
3222
- /**
3223
- * Set URL configuration
3224
- * Can be static URL or a getter function for dynamic resolution
3225
- */
3226
- setUrl(url) {
3227
- this.chat.setUrl(url);
3228
- }
3229
- /**
3230
- * Set body configuration
3231
- * Additional properties merged into every request body
3232
- */
3233
- setBody(body) {
3234
- this.chat.setBody(body);
3235
- }
3236
- setRequestMessageTransform(fn) {
3237
- this.chat.setRequestMessageTransform(fn);
3238
- }
3239
- /**
3240
- * Set inline skills (forwarded to underlying chat instance)
3241
- */
3242
- setInlineSkills(skills) {
3243
- this.chat.setInlineSkills(skills);
3244
- }
3245
- // ============================================
3246
- // Tool Registration
3247
- // ============================================
3248
- /**
3249
- * Register a tool
3250
- */
3251
- registerTool(tool2) {
3252
- this.agentLoop.registerTool(tool2);
3253
- this.chat.setTools(this.agentLoop.tools);
3169
+ const siblings = this.childrenOf.get(parentKey);
3170
+ if (!siblings.includes(message.id)) {
3171
+ siblings.push(message.id);
3172
+ }
3173
+ this.activeChildMap.set(parentKey, message.id);
3174
+ this._currentLeafId = this._walkToLeaf(message.id);
3175
+ this._invalidateCache();
3176
+ return message;
3254
3177
  }
3255
3178
  /**
3256
- * Unregister a tool
3179
+ * Navigate: make messageId the active child at its parent fork,
3180
+ * then walk to its leaf and update currentLeafId.
3257
3181
  */
3258
- unregisterTool(name) {
3259
- this.agentLoop.unregisterTool(name);
3260
- this.chat.setTools(this.agentLoop.tools);
3182
+ switchBranch(messageId) {
3183
+ const msg = this.nodeMap.get(messageId);
3184
+ if (!msg) return;
3185
+ const parentKey = this._parentKey(msg.parentId);
3186
+ this.activeChildMap.set(parentKey, messageId);
3187
+ this._currentLeafId = this._walkToLeaf(messageId);
3188
+ this._invalidateCache();
3261
3189
  }
3262
- // ============================================
3263
- // Tool Approval
3264
- // ============================================
3265
3190
  /**
3266
- * Approve a tool execution with optional extra data
3191
+ * Update message content in-place (streaming updates).
3192
+ * No tree structure change.
3267
3193
  */
3268
- approveToolExecution(id, extraData, permissionLevel) {
3269
- this.agentLoop.approveToolExecution(id, extraData, permissionLevel);
3194
+ updateMessage(id, updater) {
3195
+ const existing = this.nodeMap.get(id);
3196
+ if (!existing) return false;
3197
+ this.nodeMap.set(id, updater(existing));
3198
+ this._invalidateCache();
3199
+ return true;
3270
3200
  }
3271
3201
  /**
3272
- * Reject a tool execution
3202
+ * Set current leaf explicitly.
3203
+ * Used by regenerate() to rewind the active path before pushing a new message.
3273
3204
  */
3274
- rejectToolExecution(id, reason, permissionLevel) {
3275
- this.agentLoop.rejectToolExecution(id, reason, permissionLevel);
3205
+ setCurrentLeaf(leafId) {
3206
+ this._currentLeafId = leafId;
3207
+ if (leafId === null) return;
3208
+ const msg = this.nodeMap.get(leafId);
3209
+ if (!msg) return;
3210
+ let current = msg;
3211
+ while (current) {
3212
+ const parentKey = this._parentKey(current.parentId);
3213
+ this.activeChildMap.set(parentKey, current.id);
3214
+ if (current.parentId == null || current.parentId === void 0) break;
3215
+ current = this.nodeMap.get(current.parentId);
3216
+ }
3217
+ this._invalidateCache();
3276
3218
  }
3277
3219
  /**
3278
- * Clear tool executions
3220
+ * Rebuild entire tree from a message array.
3221
+ * Used by setMessages().
3279
3222
  */
3280
- clearToolExecutions() {
3281
- this.agentLoop.clearToolExecutions();
3223
+ reset(messages) {
3224
+ this.nodeMap.clear();
3225
+ this.childrenOf.clear();
3226
+ this.activeChildMap.clear();
3227
+ this._currentLeafId = null;
3228
+ this._invalidateCache();
3229
+ if (messages.length > 0) {
3230
+ this._buildFromMessages(messages);
3231
+ }
3282
3232
  }
3283
3233
  // ============================================
3284
- // Event Subscriptions (for framework adapters)
3234
+ // Private Helpers
3285
3235
  // ============================================
3286
- /**
3287
- * Subscribe to chat events
3288
- */
3289
- on(event, handler) {
3290
- return this.chat.on(event, handler);
3236
+ _buildFromMessages(messages) {
3237
+ const linked = messages.some((m) => m.parentId !== void 0) ? messages : _MessageTree.fromFlatArray(messages);
3238
+ for (const msg of linked) {
3239
+ this.nodeMap.set(msg.id, msg);
3240
+ const parentKey = this._parentKey(msg.parentId);
3241
+ if (!this.childrenOf.has(parentKey)) {
3242
+ this.childrenOf.set(parentKey, []);
3243
+ }
3244
+ const siblings = this.childrenOf.get(parentKey);
3245
+ if (!siblings.includes(msg.id)) {
3246
+ siblings.push(msg.id);
3247
+ }
3248
+ }
3249
+ for (const [parentKey, children] of this.childrenOf) {
3250
+ if (children.length > 0) {
3251
+ this.activeChildMap.set(parentKey, children[children.length - 1]);
3252
+ }
3253
+ }
3254
+ const path = this._getActivePath();
3255
+ this._currentLeafId = path.length > 0 ? path[path.length - 1] : null;
3291
3256
  }
3292
- // ============================================
3293
- // Cleanup
3294
- // ============================================
3295
- /**
3296
- * Whether this instance has been disposed
3297
- */
3298
- get disposed() {
3299
- return this.chat.disposed;
3257
+ _parentKey(parentId) {
3258
+ if (parentId == null || parentId === void 0) {
3259
+ return _MessageTree.ROOT_KEY;
3260
+ }
3261
+ return parentId;
3300
3262
  }
3301
3263
  /**
3302
- * Revive a disposed instance (for React StrictMode compatibility)
3303
- * This allows reusing an instance after dispose() was called,
3304
- * preserving registered tools and state
3264
+ * Walk forward from a message along active children to find the leaf.
3305
3265
  */
3306
- revive() {
3307
- this.chat.revive();
3308
- this.agentLoop.revive();
3266
+ _walkToLeaf(fromId) {
3267
+ let current = fromId;
3268
+ while (true) {
3269
+ const children = this.childrenOf.get(current);
3270
+ if (!children || children.length === 0) break;
3271
+ const activeChild = this.activeChildMap.get(current);
3272
+ if (!activeChild) break;
3273
+ if (!this.nodeMap.has(activeChild)) break;
3274
+ current = activeChild;
3275
+ }
3276
+ return current;
3309
3277
  }
3310
3278
  /**
3311
- * Dispose and cleanup
3279
+ * Walk the active path from root to the current leaf.
3312
3280
  */
3313
- dispose() {
3314
- this.chat.dispose();
3315
- this.agentLoop.dispose();
3316
- }
3317
- // ============================================
3318
- // Private
3319
- // ============================================
3320
- debug(message, ...args) {
3321
- createLogger("tools", () => this.config.debug ?? false)(
3322
- message,
3323
- args.length === 1 ? args[0] : args.length > 1 ? args : void 0
3324
- );
3281
+ _getActivePath() {
3282
+ const path = [];
3283
+ const visited = /* @__PURE__ */ new Set();
3284
+ const rootChildren = this.childrenOf.get(_MessageTree.ROOT_KEY) ?? [];
3285
+ if (rootChildren.length === 0) return path;
3286
+ let activeId = this.activeChildMap.get(_MessageTree.ROOT_KEY);
3287
+ if (!activeId) {
3288
+ activeId = rootChildren[rootChildren.length - 1];
3289
+ }
3290
+ let current = activeId;
3291
+ while (current && !visited.has(current)) {
3292
+ if (!this.nodeMap.has(current)) break;
3293
+ visited.add(current);
3294
+ path.push(current);
3295
+ const activeChild = this.activeChildMap.get(current);
3296
+ if (!activeChild || !this.nodeMap.has(activeChild)) break;
3297
+ current = activeChild;
3298
+ }
3299
+ return path;
3325
3300
  }
3326
3301
  };
3302
+ /** Sentinel key used for root-level messages (parentId === null) */
3303
+ _MessageTree.ROOT_KEY = "__root__";
3304
+ var MessageTree = _MessageTree;
3327
3305
 
3328
- // src/chat/branching/MessageTree.ts
3329
- var _MessageTree = class _MessageTree {
3330
- constructor(messages) {
3331
- /** All messages by ID */
3332
- this.nodeMap = /* @__PURE__ */ new Map();
3333
- /** parentKey ordered list of child IDs (insertion order = oldest-first) */
3334
- this.childrenOf = /* @__PURE__ */ new Map();
3335
- /** parentKey currently-active child ID */
3336
- this.activeChildMap = /* @__PURE__ */ new Map();
3337
- /** Current leaf message ID (tip of the active path) */
3338
- this._currentLeafId = null;
3339
- /** Cached visible messages — invalidated on every mutation */
3340
- this._visibleCache = null;
3341
- if (messages?.length) {
3342
- this._buildFromMessages(messages);
3343
- }
3306
+ // src/chat/ChatWithTools.ts
3307
+ var ChatWithTools = class {
3308
+ constructor(config, callbacks = {}) {
3309
+ this.config = config;
3310
+ this.callbacks = callbacks;
3311
+ this.agentLoop = new AbstractAgentLoop(
3312
+ {
3313
+ maxIterations: config.maxIterations ?? 20,
3314
+ tools: config.tools
3315
+ },
3316
+ {
3317
+ onExecutionsChange: (executions) => {
3318
+ callbacks.onToolExecutionsChange?.(executions);
3319
+ },
3320
+ onApprovalRequired: (execution) => {
3321
+ callbacks.onApprovalRequired?.(execution);
3322
+ }
3323
+ }
3324
+ );
3325
+ this.chat = new AbstractChat({
3326
+ runtimeUrl: config.runtimeUrl,
3327
+ llm: config.llm,
3328
+ systemPrompt: config.systemPrompt,
3329
+ streaming: config.streaming,
3330
+ headers: config.headers,
3331
+ body: config.body,
3332
+ optimization: config.optimization,
3333
+ threadId: config.threadId,
3334
+ debug: config.debug,
3335
+ initialMessages: config.initialMessages,
3336
+ state: config.state,
3337
+ transport: config.transport,
3338
+ callbacks: {
3339
+ onMessagesChange: callbacks.onMessagesChange,
3340
+ onStatusChange: callbacks.onStatusChange,
3341
+ onError: callbacks.onError,
3342
+ onMessageStart: callbacks.onMessageStart,
3343
+ onMessageDelta: callbacks.onMessageDelta,
3344
+ onMessageFinish: callbacks.onMessageFinish,
3345
+ onToolCalls: callbacks.onToolCalls,
3346
+ onFinish: callbacks.onFinish,
3347
+ onContextUsageChange: callbacks.onContextUsageChange,
3348
+ // Server-side tool callbacks - track in agentLoop for UI display
3349
+ // IMPORTANT: Only track tools that are NOT registered client-side
3350
+ // Client-side tools are tracked via executeToolCalls() path
3351
+ onServerToolStart: (info) => {
3352
+ const existingExecution = this.agentLoop.toolExecutions.find(
3353
+ (e) => e.id === info.id
3354
+ );
3355
+ if (existingExecution) {
3356
+ if (info.hidden !== void 0 && existingExecution.hidden !== info.hidden) {
3357
+ this.debug(
3358
+ "Updating hidden flag for existing execution:",
3359
+ info.name,
3360
+ info.hidden
3361
+ );
3362
+ this.agentLoop.updateToolExecution(info.id, {
3363
+ hidden: info.hidden
3364
+ });
3365
+ }
3366
+ return;
3367
+ }
3368
+ const isClientTool = this.agentLoop.tools.some(
3369
+ (t) => t.name === info.name && t.location === "client"
3370
+ );
3371
+ if (isClientTool) {
3372
+ this.debug("Skipping server tracking for client tool:", info.name);
3373
+ return;
3374
+ }
3375
+ this.debug("Server tool started:", info.name, {
3376
+ hidden: info.hidden,
3377
+ id: info.id
3378
+ });
3379
+ this.agentLoop.addServerToolExecution(info);
3380
+ },
3381
+ onServerToolArgs: (info) => {
3382
+ const isClientTool = this.agentLoop.tools.some(
3383
+ (t) => t.name === info.name && t.location === "client"
3384
+ );
3385
+ if (isClientTool) return;
3386
+ this.debug("Server tool args:", info.name, info.args);
3387
+ this.agentLoop.updateServerToolArgs(info.id, info.args ?? {});
3388
+ },
3389
+ onServerToolEnd: (info) => {
3390
+ const isClientTool = this.agentLoop.tools.some(
3391
+ (t) => t.name === info.name && t.location === "client"
3392
+ );
3393
+ if (isClientTool) return;
3394
+ this.debug("Server tool ended:", info.name, {
3395
+ error: info.error,
3396
+ hasResult: !!info.result
3397
+ });
3398
+ this.agentLoop.completeServerToolExecution(info);
3399
+ }
3400
+ }
3401
+ });
3402
+ this.wireEvents();
3344
3403
  }
3345
- // ============================================
3346
- // Static Migration Helpers
3347
- // ============================================
3348
3404
  /**
3349
- * Convert a legacy flat array (no parentId) to a tree-linked array.
3350
- *
3351
- * Rules:
3352
- * - Tool messages get parentId = the owning assistant message's id
3353
- * (matched via toolCallId → toolCall.id).
3354
- * - All other messages get parentId of the previous non-tool message
3355
- * (or null for the first message).
3356
- *
3357
- * Returns a new array with parentId/childrenIds filled in.
3358
- * Does NOT mutate the original messages.
3405
+ * Wire up internal events between chat and agent loop
3359
3406
  */
3360
- static fromFlatArray(messages) {
3361
- if (messages.length === 0) return messages;
3362
- const alreadyLinked = messages.some((m) => m.parentId !== void 0);
3363
- if (alreadyLinked) return messages;
3364
- const result = [];
3365
- let prevNonToolId = null;
3366
- const assistantById = /* @__PURE__ */ new Map();
3367
- for (const msg of messages) {
3368
- if (msg.role === "assistant") {
3369
- assistantById.set(msg.id, msg);
3407
+ wireEvents() {
3408
+ this.debug("Wiring up toolCalls event handler");
3409
+ this.chat.on("toolCalls", async (event) => {
3410
+ const toolCalls = event.toolCalls;
3411
+ if (!toolCalls?.length) {
3412
+ return;
3370
3413
  }
3371
- }
3372
- for (const msg of messages) {
3373
- if (msg.role === "tool" && msg.toolCallId) {
3374
- let ownerAssistantId = null;
3375
- for (const [, assistant] of assistantById) {
3376
- if (assistant.toolCalls?.some((tc) => tc.id === msg.toolCallId)) {
3377
- ownerAssistantId = assistant.id;
3378
- break;
3414
+ this.debug("Tool calls received:", toolCalls.length);
3415
+ const toolCallInfos = toolCalls.map((tc) => {
3416
+ const tcAny = tc;
3417
+ const name = tcAny.function?.name ?? tcAny.name ?? "";
3418
+ let args = {};
3419
+ if (tcAny.function?.arguments) {
3420
+ try {
3421
+ args = JSON.parse(tcAny.function.arguments);
3422
+ } catch {
3423
+ args = {};
3379
3424
  }
3425
+ } else if (tcAny.args) {
3426
+ args = tcAny.args;
3380
3427
  }
3381
- result.push({
3382
- ...msg,
3383
- parentId: ownerAssistantId ?? prevNonToolId,
3384
- childrenIds: []
3385
- });
3386
- } else {
3387
- result.push({
3388
- ...msg,
3389
- parentId: prevNonToolId,
3390
- childrenIds: []
3391
- });
3392
- prevNonToolId = msg.id;
3393
- }
3394
- }
3395
- const childrenMap = /* @__PURE__ */ new Map();
3396
- for (const msg of result) {
3397
- const parentKey = msg.parentId == null ? _MessageTree.ROOT_KEY : msg.parentId;
3398
- if (!childrenMap.has(parentKey)) {
3399
- childrenMap.set(parentKey, []);
3428
+ return { id: tc.id, name, args };
3429
+ });
3430
+ try {
3431
+ const results = await this.agentLoop.executeToolCalls(toolCallInfos);
3432
+ this.debug("Tool results:", results);
3433
+ if (results.length > 0) {
3434
+ const toolResults = results.map((r) => ({
3435
+ toolCallId: r.toolCallId,
3436
+ result: r.success ? r.result : { success: false, error: r.error }
3437
+ }));
3438
+ await this.chat.continueWithToolResults(toolResults);
3439
+ } else if (this.agentLoop.maxIterationsReached && toolCallInfos.length > 0) {
3440
+ this.debug(
3441
+ "Max iterations reached, stopping loop without new LLM request"
3442
+ );
3443
+ const errorMessage = this.config.maxIterationsMessage || "Tool execution paused: iteration limit reached. User can say 'continue' to resume.";
3444
+ const blockedResults = toolCallInfos.map((tc) => ({
3445
+ toolCallId: tc.id,
3446
+ result: {
3447
+ success: false,
3448
+ error: errorMessage
3449
+ }
3450
+ }));
3451
+ await this.chat.addToolResultMessages(blockedResults, errorMessage);
3452
+ }
3453
+ } catch (error) {
3454
+ this.debug("Error executing tools:", error);
3455
+ console.error("[ChatWithTools] Tool execution error:", error);
3400
3456
  }
3401
- childrenMap.get(parentKey).push(msg.id);
3402
- }
3403
- return result.map((msg) => ({
3404
- ...msg,
3405
- childrenIds: childrenMap.get(msg.id) ?? []
3406
- }));
3457
+ });
3407
3458
  }
3408
3459
  // ============================================
3409
- // Core Queries
3460
+ // Chat Getters
3410
3461
  // ============================================
3411
- /**
3412
- * Returns the visible path (root → current leaf) — what the UI renders
3413
- * and what gets sent to the API.
3414
- *
3415
- * Backward-compat: if NO message has parentId set (all undefined),
3416
- * falls back to insertion order (legacy linear mode).
3417
- */
3418
- getVisibleMessages() {
3419
- if (this._visibleCache !== null) return this._visibleCache;
3420
- if (this.nodeMap.size === 0) {
3421
- this._visibleCache = [];
3422
- return this._visibleCache;
3423
- }
3424
- const hasTreeStructure = Array.from(this.nodeMap.values()).some(
3425
- (m) => m.parentId !== void 0
3426
- );
3427
- this._visibleCache = hasTreeStructure ? this._getActivePath().map((id) => this.nodeMap.get(id)) : Array.from(this.nodeMap.values());
3428
- return this._visibleCache;
3462
+ get messages() {
3463
+ return this.chat.messages;
3429
3464
  }
3430
- _invalidateCache() {
3431
- this._visibleCache = null;
3465
+ get status() {
3466
+ return this.chat.status;
3467
+ }
3468
+ get error() {
3469
+ return this.chat.error;
3470
+ }
3471
+ get isStreaming() {
3472
+ return this.chat.isStreaming;
3432
3473
  }
3433
3474
  /**
3434
- * Returns ALL messages across every branch (for persistence / ThreadManager).
3475
+ * Whether any operation is in progress (chat or tools)
3476
+ * Use this to show loading indicators and disable send button
3435
3477
  */
3436
- getAllMessages() {
3437
- return Array.from(this.nodeMap.values());
3478
+ get isLoading() {
3479
+ const chatBusy = this.status === "submitted" || this.status === "streaming";
3480
+ const toolsBusy = this.agentLoop.isProcessing;
3481
+ const hasPendingApprovals = this.agentLoop.pendingApprovalExecutions.length > 0;
3482
+ return chatBusy || toolsBusy || hasPendingApprovals;
3438
3483
  }
3439
3484
  /**
3440
- * Branch navigation info for the UI navigator.
3441
- * Returns null if the message has no siblings (only child).
3485
+ * Check if a request is currently in progress (excludes pending approvals)
3486
+ * Use this to prevent sending new messages
3442
3487
  */
3443
- getBranchInfo(messageId) {
3444
- const msg = this.nodeMap.get(messageId);
3445
- if (!msg) return null;
3446
- const parentKey = this._parentKey(msg.parentId);
3447
- const siblings = this.childrenOf.get(parentKey) ?? [];
3448
- if (siblings.length <= 1) return null;
3449
- const siblingIndex = siblings.indexOf(messageId);
3450
- return {
3451
- siblingIndex,
3452
- totalSiblings: siblings.length,
3453
- siblingIds: [...siblings],
3454
- hasPrevious: siblingIndex > 0,
3455
- hasNext: siblingIndex < siblings.length - 1
3456
- };
3488
+ get isBusy() {
3489
+ const chatBusy = this.status === "submitted" || this.status === "streaming";
3490
+ const toolsBusy = this.agentLoop.isProcessing;
3491
+ return chatBusy || toolsBusy;
3457
3492
  }
3458
- get currentLeafId() {
3459
- return this._currentLeafId;
3493
+ // ============================================
3494
+ // Tool Execution Getters
3495
+ // ============================================
3496
+ get toolExecutions() {
3497
+ return this.agentLoop.toolExecutions;
3460
3498
  }
3461
- get hasBranches() {
3462
- for (const children of this.childrenOf.values()) {
3463
- if (children.length > 1) return true;
3464
- }
3465
- return false;
3499
+ get tools() {
3500
+ return this.agentLoop.tools;
3501
+ }
3502
+ get iteration() {
3503
+ return this.agentLoop.iteration;
3504
+ }
3505
+ get maxIterations() {
3506
+ return this.agentLoop.maxIterations;
3507
+ }
3508
+ get isProcessing() {
3509
+ return this.agentLoop.isProcessing;
3466
3510
  }
3467
3511
  // ============================================
3468
- // Mutations
3512
+ // Chat Actions
3469
3513
  // ============================================
3470
3514
  /**
3471
- * Insert a new message.
3472
- * - Updates childrenOf and nodeMap.
3473
- * - New branch becomes active (activeChildMap updated).
3474
- * - Updates current leaf.
3515
+ * Send a message
3516
+ * Returns false if a request is already in progress
3517
+ *
3518
+ * @param options.editMessageId - Edit flow: new message branches from the
3519
+ * same parent as this message ID
3475
3520
  */
3476
- addMessage(message) {
3477
- this.nodeMap.set(message.id, message);
3478
- const parentKey = this._parentKey(message.parentId);
3479
- if (!this.childrenOf.has(parentKey)) {
3480
- this.childrenOf.set(parentKey, []);
3481
- }
3482
- const siblings = this.childrenOf.get(parentKey);
3483
- if (!siblings.includes(message.id)) {
3484
- siblings.push(message.id);
3521
+ async sendMessage(content, attachments, options) {
3522
+ if (this.isLoading) {
3523
+ this.debug("sendMessage blocked - request already in progress");
3524
+ return false;
3485
3525
  }
3486
- this.activeChildMap.set(parentKey, message.id);
3487
- this._currentLeafId = this._walkToLeaf(message.id);
3488
- this._invalidateCache();
3489
- return message;
3526
+ this.agentLoop.resetIterations();
3527
+ return await this.chat.sendMessage(content, attachments, options);
3490
3528
  }
3491
3529
  /**
3492
- * Navigate: make messageId the active child at its parent fork,
3493
- * then walk to its leaf and update currentLeafId.
3530
+ * Stop generation and cancel any running tools
3494
3531
  */
3495
- switchBranch(messageId) {
3496
- const msg = this.nodeMap.get(messageId);
3497
- if (!msg) return;
3498
- const parentKey = this._parentKey(msg.parentId);
3499
- this.activeChildMap.set(parentKey, messageId);
3500
- this._currentLeafId = this._walkToLeaf(messageId);
3501
- this._invalidateCache();
3532
+ stop() {
3533
+ this.agentLoop.cancel();
3534
+ this.chat.stop();
3535
+ this.debug("Stopped - cancelled tools and aborted stream");
3502
3536
  }
3503
3537
  /**
3504
- * Update message content in-place (streaming updates).
3505
- * No tree structure change.
3538
+ * Clear all messages
3506
3539
  */
3507
- updateMessage(id, updater) {
3508
- const existing = this.nodeMap.get(id);
3509
- if (!existing) return false;
3510
- this.nodeMap.set(id, updater(existing));
3511
- this._invalidateCache();
3512
- return true;
3540
+ clearMessages() {
3541
+ this.chat.clearMessages();
3542
+ this.agentLoop.clearToolExecutions();
3513
3543
  }
3514
3544
  /**
3515
- * Set current leaf explicitly.
3516
- * Used by regenerate() to rewind the active path before pushing a new message.
3545
+ * Set messages directly
3517
3546
  */
3518
- setCurrentLeaf(leafId) {
3519
- this._currentLeafId = leafId;
3520
- if (leafId === null) return;
3521
- const msg = this.nodeMap.get(leafId);
3522
- if (!msg) return;
3523
- let current = msg;
3524
- while (current) {
3525
- const parentKey = this._parentKey(current.parentId);
3526
- this.activeChildMap.set(parentKey, current.id);
3527
- if (current.parentId == null || current.parentId === void 0) break;
3528
- current = this.nodeMap.get(current.parentId);
3529
- }
3530
- this._invalidateCache();
3547
+ setMessages(messages) {
3548
+ this.chat.setMessages(messages);
3531
3549
  }
3532
3550
  /**
3533
- * Rebuild entire tree from a message array.
3534
- * Used by setMessages().
3551
+ * Regenerate last response
3535
3552
  */
3536
- reset(messages) {
3537
- this.nodeMap.clear();
3538
- this.childrenOf.clear();
3539
- this.activeChildMap.clear();
3540
- this._currentLeafId = null;
3541
- this._invalidateCache();
3542
- if (messages.length > 0) {
3543
- this._buildFromMessages(messages);
3544
- }
3553
+ async regenerate(messageId) {
3554
+ await this.chat.regenerate(messageId);
3545
3555
  }
3546
- // ============================================
3547
- // Private Helpers
3548
- // ============================================
3549
- _buildFromMessages(messages) {
3550
- const linked = messages.some((m) => m.parentId !== void 0) ? messages : _MessageTree.fromFlatArray(messages);
3551
- for (const msg of linked) {
3552
- this.nodeMap.set(msg.id, msg);
3553
- const parentKey = this._parentKey(msg.parentId);
3554
- if (!this.childrenOf.has(parentKey)) {
3555
- this.childrenOf.set(parentKey, []);
3556
- }
3557
- const siblings = this.childrenOf.get(parentKey);
3558
- if (!siblings.includes(msg.id)) {
3559
- siblings.push(msg.id);
3560
- }
3561
- }
3562
- for (const [parentKey, children] of this.childrenOf) {
3563
- if (children.length > 0) {
3564
- this.activeChildMap.set(parentKey, children[children.length - 1]);
3565
- }
3566
- }
3567
- const path = this._getActivePath();
3568
- this._currentLeafId = path.length > 0 ? path[path.length - 1] : null;
3556
+ /**
3557
+ * Set tools available for the LLM
3558
+ */
3559
+ setTools(tools) {
3560
+ this.chat.setTools(tools);
3569
3561
  }
3570
- _parentKey(parentId) {
3571
- if (parentId == null || parentId === void 0) {
3572
- return _MessageTree.ROOT_KEY;
3573
- }
3574
- return parentId;
3562
+ /**
3563
+ * Update prompt/tool optimization controls.
3564
+ */
3565
+ setOptimizationConfig(config) {
3566
+ this.config.optimization = config;
3567
+ this.chat.setOptimizationConfig(config);
3575
3568
  }
3576
3569
  /**
3577
- * Walk forward from a message along active children to find the leaf.
3570
+ * Set the active tool profile used for request-time tool selection.
3578
3571
  */
3579
- _walkToLeaf(fromId) {
3580
- let current = fromId;
3581
- while (true) {
3582
- const children = this.childrenOf.get(current);
3583
- if (!children || children.length === 0) break;
3584
- const activeChild = this.activeChildMap.get(current);
3585
- if (!activeChild) break;
3586
- if (!this.nodeMap.has(activeChild)) break;
3587
- current = activeChild;
3588
- }
3589
- return current;
3572
+ setToolProfile(profile) {
3573
+ this.chat.setToolProfile(profile);
3590
3574
  }
3591
3575
  /**
3592
- * Walk the active path from root to the current leaf.
3576
+ * Get the most recent prompt context usage snapshot.
3593
3577
  */
3594
- _getActivePath() {
3595
- const path = [];
3596
- const visited = /* @__PURE__ */ new Set();
3597
- const rootChildren = this.childrenOf.get(_MessageTree.ROOT_KEY) ?? [];
3598
- if (rootChildren.length === 0) return path;
3599
- let activeId = this.activeChildMap.get(_MessageTree.ROOT_KEY);
3600
- if (!activeId) {
3601
- activeId = rootChildren[rootChildren.length - 1];
3602
- }
3603
- let current = activeId;
3604
- while (current && !visited.has(current)) {
3605
- if (!this.nodeMap.has(current)) break;
3606
- visited.add(current);
3607
- path.push(current);
3608
- const activeChild = this.activeChildMap.get(current);
3609
- if (!activeChild || !this.nodeMap.has(activeChild)) break;
3610
- current = activeChild;
3611
- }
3612
- return path;
3578
+ getContextUsage() {
3579
+ return this.chat.getContextUsage();
3613
3580
  }
3614
- };
3615
- /** Sentinel key used for root-level messages (parentId === null) */
3616
- _MessageTree.ROOT_KEY = "__root__";
3617
- var MessageTree = _MessageTree;
3618
-
3619
- // src/react/internal/ReactChatState.ts
3620
- var ReactChatState = class {
3621
- constructor(initialMessages) {
3622
- this._status = "ready";
3623
- this._error = void 0;
3624
- // Callbacks for React subscriptions (useSyncExternalStore)
3625
- this.subscribers = /* @__PURE__ */ new Set();
3626
- // ============================================
3627
- // Subscription (for useSyncExternalStore)
3628
- // ============================================
3629
- /**
3630
- * Subscribe to state changes.
3631
- * Returns an unsubscribe function.
3632
- *
3633
- * @example
3634
- * ```tsx
3635
- * const messages = useSyncExternalStore(
3636
- * state.subscribe,
3637
- * () => state.messages
3638
- * );
3639
- * ```
3640
- */
3641
- this.subscribe = (callback) => {
3642
- this.subscribers.add(callback);
3643
- return () => {
3644
- this.subscribers.delete(callback);
3645
- };
3646
- };
3647
- this.tree = new MessageTree(initialMessages);
3581
+ /**
3582
+ * Set dynamic context (from useAIContext hook)
3583
+ */
3584
+ setContext(context) {
3585
+ this.chat.setContext(context);
3648
3586
  }
3649
- // ============================================
3650
- // Getters — visible path only
3651
- // ============================================
3652
3587
  /**
3653
- * Returns the VISIBLE PATH (active branch) — what the UI renders
3654
- * and what gets sent to the API.
3655
- *
3656
- * For all messages across all branches, use getAllMessages().
3588
+ * Set system prompt dynamically
3657
3589
  */
3658
- get messages() {
3659
- return this.tree.getVisibleMessages();
3590
+ setSystemPrompt(prompt) {
3591
+ this.chat.setSystemPrompt(prompt);
3660
3592
  }
3661
- get status() {
3593
+ /**
3594
+ * Set headers configuration
3595
+ * Can be static headers or a getter function for dynamic resolution
3596
+ */
3597
+ setHeaders(headers) {
3598
+ this.chat.setHeaders(headers);
3599
+ }
3600
+ /**
3601
+ * Set URL configuration
3602
+ * Can be static URL or a getter function for dynamic resolution
3603
+ */
3604
+ setUrl(url) {
3605
+ this.chat.setUrl(url);
3606
+ }
3607
+ /**
3608
+ * Set body configuration
3609
+ * Additional properties merged into every request body
3610
+ */
3611
+ setBody(body) {
3612
+ this.chat.setBody(body);
3613
+ }
3614
+ setRequestMessageTransform(fn) {
3615
+ this.chat.setRequestMessageTransform(fn);
3616
+ }
3617
+ /**
3618
+ * Set inline skills (forwarded to underlying chat instance)
3619
+ */
3620
+ setInlineSkills(skills) {
3621
+ this.chat.setInlineSkills(skills);
3622
+ }
3623
+ // ============================================
3624
+ // Tool Registration
3625
+ // ============================================
3626
+ /**
3627
+ * Register a tool
3628
+ */
3629
+ registerTool(tool) {
3630
+ this.agentLoop.registerTool(tool);
3631
+ this.chat.setTools(this.agentLoop.tools);
3632
+ }
3633
+ /**
3634
+ * Unregister a tool
3635
+ */
3636
+ unregisterTool(name) {
3637
+ this.agentLoop.unregisterTool(name);
3638
+ this.chat.setTools(this.agentLoop.tools);
3639
+ }
3640
+ // ============================================
3641
+ // Tool Approval
3642
+ // ============================================
3643
+ /**
3644
+ * Approve a tool execution with optional extra data
3645
+ */
3646
+ approveToolExecution(id, extraData, permissionLevel) {
3647
+ this.agentLoop.approveToolExecution(id, extraData, permissionLevel);
3648
+ }
3649
+ /**
3650
+ * Reject a tool execution
3651
+ */
3652
+ rejectToolExecution(id, reason, permissionLevel) {
3653
+ this.agentLoop.rejectToolExecution(id, reason, permissionLevel);
3654
+ }
3655
+ /**
3656
+ * Clear tool executions
3657
+ */
3658
+ clearToolExecutions() {
3659
+ this.agentLoop.clearToolExecutions();
3660
+ }
3661
+ // ============================================
3662
+ // Event Subscriptions (for framework adapters)
3663
+ // ============================================
3664
+ /**
3665
+ * Subscribe to chat events
3666
+ */
3667
+ on(event, handler) {
3668
+ return this.chat.on(event, handler);
3669
+ }
3670
+ // ============================================
3671
+ // Cleanup
3672
+ // ============================================
3673
+ /**
3674
+ * Whether this instance has been disposed
3675
+ */
3676
+ get disposed() {
3677
+ return this.chat.disposed;
3678
+ }
3679
+ /**
3680
+ * Revive a disposed instance (for React StrictMode compatibility)
3681
+ * This allows reusing an instance after dispose() was called,
3682
+ * preserving registered tools and state
3683
+ */
3684
+ revive() {
3685
+ this.chat.revive();
3686
+ this.agentLoop.revive();
3687
+ }
3688
+ /**
3689
+ * Dispose and cleanup
3690
+ */
3691
+ dispose() {
3692
+ this.chat.dispose();
3693
+ this.agentLoop.dispose();
3694
+ }
3695
+ // ============================================
3696
+ // Private
3697
+ // ============================================
3698
+ debug(message, ...args) {
3699
+ createLogger("tools", () => this.config.debug ?? false)(
3700
+ message,
3701
+ args.length === 1 ? args[0] : args.length > 1 ? args : void 0
3702
+ );
3703
+ }
3704
+ };
3705
+
3706
+ // src/react/internal/ReactChatState.ts
3707
+ var ReactChatState = class {
3708
+ constructor(initialMessages) {
3709
+ this._status = "ready";
3710
+ this._error = void 0;
3711
+ // Callbacks for React subscriptions (useSyncExternalStore)
3712
+ this.subscribers = /* @__PURE__ */ new Set();
3713
+ // ============================================
3714
+ // Subscription (for useSyncExternalStore)
3715
+ // ============================================
3716
+ /**
3717
+ * Subscribe to state changes.
3718
+ * Returns an unsubscribe function.
3719
+ *
3720
+ * @example
3721
+ * ```tsx
3722
+ * const messages = useSyncExternalStore(
3723
+ * state.subscribe,
3724
+ * () => state.messages
3725
+ * );
3726
+ * ```
3727
+ */
3728
+ this.subscribe = (callback) => {
3729
+ this.subscribers.add(callback);
3730
+ return () => {
3731
+ this.subscribers.delete(callback);
3732
+ };
3733
+ };
3734
+ this.tree = new MessageTree(initialMessages);
3735
+ }
3736
+ // ============================================
3737
+ // Getters — visible path only
3738
+ // ============================================
3739
+ /**
3740
+ * Returns the VISIBLE PATH (active branch) — what the UI renders
3741
+ * and what gets sent to the API.
3742
+ *
3743
+ * For all messages across all branches, use getAllMessages().
3744
+ */
3745
+ get messages() {
3746
+ return this.tree.getVisibleMessages();
3747
+ }
3748
+ get status() {
3662
3749
  return this._status;
3663
3750
  }
3664
3751
  get error() {
@@ -3782,149 +3869,21 @@ var ReactChatState = class {
3782
3869
  function createReactChatState(initialMessages) {
3783
3870
  return new ReactChatState(initialMessages);
3784
3871
  }
3785
-
3786
- // src/react/internal/ReactChatWithTools.ts
3787
- var ReactChatWithTools = class extends ChatWithTools {
3788
- constructor(config, callbacks = {}) {
3789
- const reactState = new ReactChatState(config.initialMessages);
3790
- super({ ...config, state: reactState }, callbacks);
3791
- /**
3792
- * Subscribe to state changes (for useSyncExternalStore)
3793
- */
3794
- this.subscribe = (callback) => {
3795
- return this.reactState.subscribe(callback);
3796
- };
3797
- this.reactState = reactState;
3798
- }
3799
- // ============================================
3800
- // Branching API — pass-throughs to ReactChatState
3801
- // ============================================
3802
- /**
3803
- * Navigate to a sibling branch.
3804
- */
3805
- switchBranch(messageId) {
3806
- this.reactState.switchBranch(messageId);
3807
- }
3808
- /**
3809
- * Get branch navigation info for a message.
3810
- */
3811
- getBranchInfo(messageId) {
3812
- return this.reactState.getBranchInfo(messageId);
3813
- }
3814
- /**
3815
- * Get all messages across all branches (for persistence).
3816
- */
3817
- getAllMessages() {
3818
- return this.reactState.getAllMessages();
3819
- }
3820
- /**
3821
- * Whether any message has siblings (branching has occurred).
3822
- */
3823
- get hasBranches() {
3824
- return this.reactState.hasBranches;
3825
- }
3826
- /**
3827
- * Dispose and cleanup
3828
- */
3829
- dispose() {
3830
- super.dispose();
3831
- this.reactState.dispose();
3832
- }
3833
- /**
3834
- * Revive a disposed instance (for React StrictMode compatibility)
3835
- */
3836
- revive() {
3837
- super.revive();
3838
- this.reactState.revive();
3839
- }
3840
- };
3841
-
3842
- // src/react/utils/context-tree.ts
3843
- function addNode(tree, node, parentId) {
3844
- const newNode = {
3845
- ...node,
3846
- children: node.children || []
3847
- };
3848
- if (!parentId) {
3849
- return [...tree, newNode];
3850
- }
3851
- return tree.map((n) => {
3852
- if (n.id === parentId) {
3853
- return { ...n, children: [...n.children, newNode] };
3854
- }
3855
- if (n.children.length > 0) {
3856
- return { ...n, children: addNode(n.children, node, parentId) };
3857
- }
3858
- return n;
3859
- });
3860
- }
3861
- function removeNode(tree, id) {
3862
- return tree.reduce((result, node) => {
3863
- if (node.id !== id) {
3864
- const newNode = { ...node, children: removeNode(node.children, id) };
3865
- result.push(newNode);
3866
- }
3867
- return result;
3868
- }, []);
3869
- }
3870
- function getIndentPrefix(index, level) {
3871
- if (level === 0) {
3872
- return `${index + 1}.`;
3873
- } else if (level === 1) {
3874
- return `${String.fromCharCode(65 + index)}.`;
3875
- } else if (level === 2) {
3876
- return `${String.fromCharCode(97 + index)}.`;
3877
- } else {
3878
- return "-";
3879
- }
3880
- }
3881
- function printNode(node, prefix = "", indentLevel = 0) {
3882
- const indent = " ".repeat(indentLevel);
3883
- const prefixLength = prefix.length + indent.length;
3884
- const subsequentIndent = " ".repeat(prefixLength);
3885
- const lines = node.value.split("\n");
3886
- const firstLine = `${indent}${prefix}${lines[0]}`;
3887
- const subsequentLines = lines.slice(1).map((line) => `${subsequentIndent}${line}`).join("\n");
3888
- let output = `${firstLine}
3889
- `;
3890
- if (subsequentLines) {
3891
- output += `${subsequentLines}
3892
- `;
3893
- }
3894
- node.children.forEach((child, index) => {
3895
- const childPrefix = `${" ".repeat(prefix.length)}${getIndentPrefix(index, indentLevel + 1)} `;
3896
- output += printNode(child, childPrefix, indentLevel + 1);
3897
- });
3898
- return output;
3899
- }
3900
- function printTree(tree) {
3901
- if (tree.length === 0) {
3902
- return "";
3903
- }
3904
- let output = "";
3905
- tree.forEach((node, index) => {
3906
- if (index > 0) {
3907
- output += "\n";
3908
- }
3909
- output += printNode(node, `${getIndentPrefix(index, 0)} `);
3910
- });
3911
- return output.trim();
3912
- }
3913
- function useMCPClient(config) {
3914
- const {
3915
- autoConnect = true,
3916
- onConnectionStateChange,
3917
- onToolsChange,
3918
- onElicitationRequest,
3919
- onError,
3920
- onNotification,
3921
- ...clientConfig
3922
- } = config;
3923
- const clientRef = useRef(null);
3924
- const mountedRef = useRef(true);
3925
- const [state, setState] = useState({
3926
- connectionState: "disconnected",
3927
- tools: []
3872
+ function useMCPClient(config) {
3873
+ const {
3874
+ autoConnect = true,
3875
+ onConnectionStateChange,
3876
+ onToolsChange,
3877
+ onElicitationRequest,
3878
+ onError,
3879
+ onNotification,
3880
+ ...clientConfig
3881
+ } = config;
3882
+ const clientRef = useRef(null);
3883
+ const mountedRef = useRef(true);
3884
+ const [state, setState] = useState({
3885
+ connectionState: "disconnected",
3886
+ tools: []
3928
3887
  });
3929
3888
  const getClient = useCallback(() => {
3930
3889
  if (!clientRef.current) {
@@ -4057,78 +4016,132 @@ function useMCPClient(config) {
4057
4016
  };
4058
4017
  }
4059
4018
 
4060
- // src/react/hooks/useMCPTools.ts
4061
- function useMCPTools(config) {
4062
- const {
4063
- prefixToolNames = true,
4064
- autoRegister = true,
4065
- hidden = false,
4066
- source = "mcp",
4067
- ...clientConfig
4068
- } = config;
4069
- const { registerTool, unregisterTool } = useCopilot();
4070
- const registeredToolsRef = useRef([]);
4071
- const mcpClient = useMCPClient(clientConfig);
4072
- const toolAdapter = useMemo(
4073
- () => new MCPToolAdapter(clientConfig.name),
4074
- [clientConfig.name]
4075
- );
4076
- const toolDefinitions = useMemo(() => {
4077
- if (!mcpClient.isConnected || mcpClient.state.tools.length === 0) {
4078
- return [];
4019
+ // src/react/internal/ReactChatWithTools.ts
4020
+ var ReactChatWithTools = class extends ChatWithTools {
4021
+ constructor(config, callbacks = {}) {
4022
+ const reactState = new ReactChatState(config.initialMessages);
4023
+ super({ ...config, state: reactState }, callbacks);
4024
+ /**
4025
+ * Subscribe to state changes (for useSyncExternalStore)
4026
+ */
4027
+ this.subscribe = (callback) => {
4028
+ return this.reactState.subscribe(callback);
4029
+ };
4030
+ this.reactState = reactState;
4031
+ }
4032
+ // ============================================
4033
+ // Branching API — pass-throughs to ReactChatState
4034
+ // ============================================
4035
+ /**
4036
+ * Navigate to a sibling branch.
4037
+ */
4038
+ switchBranch(messageId) {
4039
+ this.reactState.switchBranch(messageId);
4040
+ }
4041
+ /**
4042
+ * Get branch navigation info for a message.
4043
+ */
4044
+ getBranchInfo(messageId) {
4045
+ return this.reactState.getBranchInfo(messageId);
4046
+ }
4047
+ /**
4048
+ * Get all messages across all branches (for persistence).
4049
+ */
4050
+ getAllMessages() {
4051
+ return this.reactState.getAllMessages();
4052
+ }
4053
+ /**
4054
+ * Whether any message has siblings (branching has occurred).
4055
+ */
4056
+ get hasBranches() {
4057
+ return this.reactState.hasBranches;
4058
+ }
4059
+ /**
4060
+ * Dispose and cleanup
4061
+ */
4062
+ dispose() {
4063
+ super.dispose();
4064
+ this.reactState.dispose();
4065
+ }
4066
+ /**
4067
+ * Revive a disposed instance (for React StrictMode compatibility)
4068
+ */
4069
+ revive() {
4070
+ super.revive();
4071
+ this.reactState.revive();
4072
+ }
4073
+ };
4074
+
4075
+ // src/react/utils/context-tree.ts
4076
+ function addNode(tree, node, parentId) {
4077
+ const newNode = {
4078
+ ...node,
4079
+ children: node.children || []
4080
+ };
4081
+ if (!parentId) {
4082
+ return [...tree, newNode];
4083
+ }
4084
+ return tree.map((n) => {
4085
+ if (n.id === parentId) {
4086
+ return { ...n, children: [...n.children, newNode] };
4079
4087
  }
4080
- return mcpClient.state.tools.map(
4081
- (tool2) => toolAdapter.toToolDefinition(tool2, {
4082
- prefix: prefixToolNames,
4083
- asServerTool: true,
4084
- // MCP tools execute remotely
4085
- callTool: mcpClient.callTool,
4086
- hidden,
4087
- // Hide from chat UI
4088
- source
4089
- // Tool source for UI differentiation
4090
- })
4091
- );
4092
- }, [
4093
- mcpClient.isConnected,
4094
- mcpClient.state.tools,
4095
- mcpClient.callTool,
4096
- toolAdapter,
4097
- prefixToolNames,
4098
- hidden,
4099
- source
4100
- ]);
4101
- useEffect(() => {
4102
- if (!autoRegister) {
4103
- return;
4088
+ if (n.children.length > 0) {
4089
+ return { ...n, children: addNode(n.children, node, parentId) };
4104
4090
  }
4105
- for (const toolName of registeredToolsRef.current) {
4106
- unregisterTool(toolName);
4091
+ return n;
4092
+ });
4093
+ }
4094
+ function removeNode(tree, id) {
4095
+ return tree.reduce((result, node) => {
4096
+ if (node.id !== id) {
4097
+ const newNode = { ...node, children: removeNode(node.children, id) };
4098
+ result.push(newNode);
4107
4099
  }
4108
- registeredToolsRef.current = [];
4109
- if (mcpClient.isConnected && toolDefinitions.length > 0) {
4110
- for (const tool2 of toolDefinitions) {
4111
- registerTool(tool2);
4112
- registeredToolsRef.current.push(tool2.name);
4113
- }
4100
+ return result;
4101
+ }, []);
4102
+ }
4103
+ function getIndentPrefix(index, level) {
4104
+ if (level === 0) {
4105
+ return `${index + 1}.`;
4106
+ } else if (level === 1) {
4107
+ return `${String.fromCharCode(65 + index)}.`;
4108
+ } else if (level === 2) {
4109
+ return `${String.fromCharCode(97 + index)}.`;
4110
+ } else {
4111
+ return "-";
4112
+ }
4113
+ }
4114
+ function printNode(node, prefix = "", indentLevel = 0) {
4115
+ const indent = " ".repeat(indentLevel);
4116
+ const prefixLength = prefix.length + indent.length;
4117
+ const subsequentIndent = " ".repeat(prefixLength);
4118
+ const lines = node.value.split("\n");
4119
+ const firstLine = `${indent}${prefix}${lines[0]}`;
4120
+ const subsequentLines = lines.slice(1).map((line) => `${subsequentIndent}${line}`).join("\n");
4121
+ let output = `${firstLine}
4122
+ `;
4123
+ if (subsequentLines) {
4124
+ output += `${subsequentLines}
4125
+ `;
4126
+ }
4127
+ node.children.forEach((child, index) => {
4128
+ const childPrefix = `${" ".repeat(prefix.length)}${getIndentPrefix(index, indentLevel + 1)} `;
4129
+ output += printNode(child, childPrefix, indentLevel + 1);
4130
+ });
4131
+ return output;
4132
+ }
4133
+ function printTree(tree) {
4134
+ if (tree.length === 0) {
4135
+ return "";
4136
+ }
4137
+ let output = "";
4138
+ tree.forEach((node, index) => {
4139
+ if (index > 0) {
4140
+ output += "\n";
4114
4141
  }
4115
- return () => {
4116
- for (const toolName of registeredToolsRef.current) {
4117
- unregisterTool(toolName);
4118
- }
4119
- registeredToolsRef.current = [];
4120
- };
4121
- }, [
4122
- autoRegister,
4123
- mcpClient.isConnected,
4124
- toolDefinitions,
4125
- registerTool,
4126
- unregisterTool
4127
- ]);
4128
- return {
4129
- ...mcpClient,
4130
- toolDefinitions
4131
- };
4142
+ output += printNode(node, `${getIndentPrefix(index, 0)} `);
4143
+ });
4144
+ return output.trim();
4132
4145
  }
4133
4146
  var defaultTokenUsage = {
4134
4147
  current: 0,
@@ -4837,7 +4850,7 @@ function useTool(config, dependencies = []) {
4837
4850
  return config.inputSchema;
4838
4851
  }, [config.inputSchema]);
4839
4852
  useEffect(() => {
4840
- const tool2 = {
4853
+ const tool = {
4841
4854
  name: config.name,
4842
4855
  description: config.description,
4843
4856
  location: "client",
@@ -4863,7 +4876,7 @@ function useTool(config, dependencies = []) {
4863
4876
  aiResponseMode: config.aiResponseMode,
4864
4877
  aiContext: config.aiContext
4865
4878
  };
4866
- registerTool(tool2);
4879
+ registerTool(tool);
4867
4880
  return () => {
4868
4881
  unregisterTool(config.name);
4869
4882
  };
@@ -5157,6 +5170,35 @@ ${cs.rollingSummary}`,
5157
5170
  }, []);
5158
5171
  return null;
5159
5172
  }
5173
+ var _MessageMetaStore = class _MessageMetaStore {
5174
+ constructor() {
5175
+ this.store = /* @__PURE__ */ new Map();
5176
+ this.listeners = /* @__PURE__ */ new Set();
5177
+ this.subscribe = (cb) => {
5178
+ this.listeners.add(cb);
5179
+ return () => this.listeners.delete(cb);
5180
+ };
5181
+ this.getSnapshot = () => this.store;
5182
+ this.getMeta = (messageId) => this.store.get(messageId) ?? _MessageMetaStore.EMPTY;
5183
+ this.setMeta = (messageId, meta) => {
5184
+ this.store.set(messageId, meta);
5185
+ this.listeners.forEach((cb) => cb());
5186
+ };
5187
+ this.updateMeta = (messageId, updater) => {
5188
+ const prev = this.store.get(messageId) ?? {};
5189
+ this.store.set(messageId, updater(prev));
5190
+ this.listeners.forEach((cb) => cb());
5191
+ };
5192
+ this.clear = () => {
5193
+ this.store.clear();
5194
+ this.listeners.forEach((cb) => cb());
5195
+ };
5196
+ }
5197
+ };
5198
+ // Stable empty object — returned for unknown messageIds so useSyncExternalStore
5199
+ // sees the same reference and doesn't trigger infinite re-renders.
5200
+ _MessageMetaStore.EMPTY = {};
5201
+ var MessageMetaStore = _MessageMetaStore;
5160
5202
  var CopilotContext = createContext(null);
5161
5203
  function useCopilot() {
5162
5204
  const context = useContext(CopilotContext);
@@ -5185,6 +5227,15 @@ function CopilotProvider({
5185
5227
  messageHistory,
5186
5228
  skills
5187
5229
  }) {
5230
+ const streamListenersRef = useRef(/* @__PURE__ */ new Set());
5231
+ const subscribeToStreamEvents = useCallback(
5232
+ (handler) => {
5233
+ streamListenersRef.current.add(handler);
5234
+ return () => streamListenersRef.current.delete(handler);
5235
+ },
5236
+ []
5237
+ );
5238
+ const messageMetaStoreRef = useRef(new MessageMetaStore());
5188
5239
  const debugLog = useCallback(
5189
5240
  (action, data) => {
5190
5241
  createLogger("provider", () => debug ?? false)(action, data);
@@ -5199,6 +5250,7 @@ function CopilotProvider({
5199
5250
  }
5200
5251
  }, [toolsConfig]);
5201
5252
  const [toolExecutions, setToolExecutions] = useState([]);
5253
+ const [agentIteration, setAgentIteration] = useState(0);
5202
5254
  const chatRef = useRef(null);
5203
5255
  if (chatRef.current !== null && chatRef.current.disposed) {
5204
5256
  chatRef.current.revive();
@@ -5213,7 +5265,9 @@ function CopilotProvider({
5213
5265
  createdAt: m.created_at ?? /* @__PURE__ */ new Date(),
5214
5266
  attachments: m.metadata?.attachments,
5215
5267
  toolCalls: m.tool_calls,
5216
- toolCallId: m.tool_call_id
5268
+ toolCallId: m.tool_call_id,
5269
+ parentId: m.parent_id,
5270
+ childrenIds: m.children_ids
5217
5271
  })
5218
5272
  );
5219
5273
  chatRef.current = new ReactChatWithTools(
@@ -5234,6 +5288,7 @@ function CopilotProvider({
5234
5288
  onToolExecutionsChange: (executions) => {
5235
5289
  debugLog("Tool executions changed:", executions.length);
5236
5290
  setToolExecutions(executions);
5291
+ setAgentIteration(chatRef.current?.iteration ?? 0);
5237
5292
  },
5238
5293
  onApprovalRequired: (execution) => {
5239
5294
  debugLog("Tool approval required:", execution.name);
@@ -5243,6 +5298,13 @@ function CopilotProvider({
5243
5298
  },
5244
5299
  onError: (error2) => {
5245
5300
  if (error2) onError?.(error2);
5301
+ },
5302
+ onStreamChunk: (chunk) => {
5303
+ if (streamListenersRef.current.size > 0) {
5304
+ for (const handler of streamListenersRef.current) {
5305
+ handler(chunk);
5306
+ }
5307
+ }
5246
5308
  }
5247
5309
  }
5248
5310
  );
@@ -5296,8 +5358,8 @@ function CopilotProvider({
5296
5358
  );
5297
5359
  const error = errorFromChat ?? null;
5298
5360
  const isLoading = status === "streaming" || status === "submitted";
5299
- const registerTool = useCallback((tool2) => {
5300
- chatRef.current?.registerTool(tool2);
5361
+ const registerTool = useCallback((tool) => {
5362
+ chatRef.current?.registerTool(tool);
5301
5363
  }, []);
5302
5364
  const unregisterTool = useCallback((name) => {
5303
5365
  chatRef.current?.unregisterTool(name);
@@ -5354,2005 +5416,41 @@ function CopilotProvider({
5354
5416
  chatRef.current?.setContext(contextString);
5355
5417
  setContextChars(contextString.length);
5356
5418
  debugLog("Context added:", id);
5357
- return id;
5358
- },
5359
- [debugLog]
5360
- );
5361
- const removeContext = useCallback(
5362
- (id) => {
5363
- contextTreeRef.current = removeNode(contextTreeRef.current, id);
5364
- const contextString = printTree(contextTreeRef.current);
5365
- chatRef.current?.setContext(contextString);
5366
- setContextChars(contextString.length);
5367
- debugLog("Context removed:", id);
5368
- },
5369
- [debugLog]
5370
- );
5371
- const setSystemPrompt = useCallback(
5372
- (prompt) => {
5373
- chatRef.current?.setSystemPrompt(prompt);
5374
- debugLog("System prompt updated via function");
5375
- },
5376
- [debugLog]
5377
- );
5378
- const setInlineSkills = useCallback(
5379
- (skills2) => {
5380
- chatRef.current?.setInlineSkills(skills2);
5381
- debugLog("Inline skills updated", { count: skills2.length });
5382
- },
5383
- [debugLog]
5384
- );
5385
- const sendMessage = useCallback(
5386
- async (content, attachments) => {
5387
- debugLog("Sending message:", content);
5388
- await chatRef.current?.sendMessage(content, attachments);
5389
- },
5390
- [debugLog]
5391
- );
5392
- const stop = useCallback(() => {
5393
- chatRef.current?.stop();
5394
- }, []);
5395
- const clearMessages = useCallback(() => {
5396
- chatRef.current?.clearMessages();
5397
- }, []);
5398
- const setMessages = useCallback((messages2) => {
5399
- chatRef.current?.setMessages(messages2);
5400
- }, []);
5401
- const regenerate = useCallback(async (messageId) => {
5402
- await chatRef.current?.regenerate(messageId);
5403
- }, []);
5404
- const switchBranch = useCallback((messageId) => {
5405
- chatRef.current?.switchBranch(messageId);
5406
- }, []);
5407
- const getBranchInfo = useCallback(
5408
- (messageId) => chatRef.current?.getBranchInfo(messageId) ?? null,
5409
- []
5410
- );
5411
- const editMessage = useCallback(
5412
- async (messageId, newContent) => {
5413
- await chatRef.current?.sendMessage(newContent, void 0, {
5414
- editMessageId: messageId
5415
- });
5416
- },
5417
- []
5418
- );
5419
- const getHasBranchesSnapshot = useCallback(
5420
- () => chatRef.current.hasBranches,
5421
- []
5422
- );
5423
- const hasBranches = useSyncExternalStore(
5424
- chatRef.current.subscribe,
5425
- getHasBranchesSnapshot,
5426
- () => false
5427
- );
5428
- const getAllMessages = useCallback(
5429
- () => chatRef.current?.getAllMessages?.() ?? [],
5430
- []
5431
- );
5432
- useEffect(() => {
5433
- if (onMessagesChange && messages.length > 0) {
5434
- const allUIMessages = chatRef.current?.getAllMessages?.() ?? messages;
5435
- const coreMessages = allUIMessages.map((m) => ({
5436
- id: m.id,
5437
- role: m.role,
5438
- content: m.content,
5439
- created_at: m.createdAt,
5440
- tool_calls: m.toolCalls,
5441
- tool_call_id: m.toolCallId,
5442
- parent_id: m.parentId,
5443
- children_ids: m.childrenIds,
5444
- metadata: {
5445
- attachments: m.attachments,
5446
- thinking: m.thinking
5447
- }
5448
- }));
5449
- onMessagesChange(coreMessages);
5450
- }
5451
- }, [messages, onMessagesChange]);
5452
- useEffect(() => {
5453
- if (error && onError) {
5454
- onError(error);
5455
- }
5456
- }, [error, onError]);
5457
- useEffect(() => {
5458
- return () => {
5459
- chatRef.current?.dispose();
5460
- };
5461
- }, []);
5462
- const contextValue = useMemo(
5463
- () => ({
5464
- // Chat state
5465
- messages,
5466
- status,
5467
- error,
5468
- isLoading,
5469
- // Chat actions
5470
- sendMessage,
5471
- stop,
5472
- clearMessages,
5473
- setMessages,
5474
- regenerate,
5475
- // Branching
5476
- switchBranch,
5477
- getBranchInfo,
5478
- editMessage,
5479
- hasBranches,
5480
- getAllMessages,
5481
- // Tool execution
5482
- registerTool,
5483
- unregisterTool,
5484
- registeredTools,
5485
- toolExecutions,
5486
- pendingApprovals,
5487
- approveToolExecution,
5488
- rejectToolExecution,
5489
- // Actions
5490
- registerAction,
5491
- unregisterAction,
5492
- registeredActions,
5493
- // AI Context
5494
- addContext,
5495
- removeContext,
5496
- contextChars,
5497
- contextUsage,
5498
- // System Prompt
5499
- setSystemPrompt,
5500
- // Skills
5501
- setInlineSkills,
5502
- // Config
5503
- threadId,
5504
- runtimeUrl,
5505
- toolsConfig
5506
- }),
5507
- [
5508
- messages,
5509
- status,
5510
- error,
5511
- isLoading,
5512
- sendMessage,
5513
- stop,
5514
- clearMessages,
5515
- setMessages,
5516
- regenerate,
5517
- switchBranch,
5518
- getBranchInfo,
5519
- editMessage,
5520
- hasBranches,
5521
- getAllMessages,
5522
- registerTool,
5523
- unregisterTool,
5524
- registeredTools,
5525
- toolExecutions,
5526
- pendingApprovals,
5527
- approveToolExecution,
5528
- rejectToolExecution,
5529
- registerAction,
5530
- unregisterAction,
5531
- registeredActions,
5532
- addContext,
5533
- removeContext,
5534
- contextChars,
5535
- contextUsage,
5536
- setSystemPrompt,
5537
- setInlineSkills,
5538
- threadId,
5539
- runtimeUrl,
5540
- toolsConfig
5541
- ]
5542
- );
5543
- const messageHistoryContextValue = React2.useMemo(
5544
- () => ({
5545
- config: { ...defaultMessageHistoryConfig, ...messageHistory },
5546
- tokenUsage: {
5547
- current: 0,
5548
- max: messageHistory?.maxContextTokens ?? 128e3,
5549
- percentage: 0,
5550
- isApproaching: false
5551
- },
5552
- compactionState: {
5553
- rollingSummary: null,
5554
- lastCompactionAt: null,
5555
- compactionCount: 0,
5556
- totalTokensSaved: 0,
5557
- workingMemory: [],
5558
- displayMessageCount: 0,
5559
- llmMessageCount: 0
5560
- }
5561
- }),
5562
- [messageHistory]
5563
- );
5564
- return /* @__PURE__ */ jsx(MessageHistoryContext.Provider, { value: messageHistoryContextValue, children: /* @__PURE__ */ jsxs(CopilotContext.Provider, { value: contextValue, children: [
5565
- mcpServers?.map((config) => /* @__PURE__ */ jsx(MCPConnection, { config }, config.name)),
5566
- messageHistory?.strategy && messageHistory.strategy !== "none" && /* @__PURE__ */ jsx(MessageHistoryBridge, { chatRef }),
5567
- skills ? /* @__PURE__ */ jsx(SkillProvider, { skills, children }) : children
5568
- ] }) });
5569
- }
5570
- function useAIActions(actions) {
5571
- const { registerAction, unregisterAction } = useCopilot();
5572
- useEffect(() => {
5573
- for (const action of actions) {
5574
- registerAction(action);
5575
- }
5576
- return () => {
5577
- for (const action of actions) {
5578
- unregisterAction(action.name);
5579
- }
5580
- };
5581
- }, [actions, registerAction, unregisterAction]);
5582
- }
5583
- function useAIAction(action) {
5584
- useAIActions([action]);
5585
- }
5586
- function useAITools(options = {}) {
5587
- const {
5588
- screenshot = false,
5589
- console: consoleCapture = false,
5590
- network = false,
5591
- requireConsent = true,
5592
- screenshotOptions,
5593
- consoleOptions,
5594
- networkOptions,
5595
- onConsentRequest,
5596
- autoStart = true
5597
- } = options;
5598
- const [isEnabled] = useState(screenshot || consoleCapture || network);
5599
- const [activeCaptures, setActiveCaptures] = useState({
5600
- console: false,
5601
- network: false
5602
- });
5603
- const [pendingConsent, setPendingConsent] = useState(null);
5604
- const consentResolverRef = useRef(null);
5605
- const rememberedConsentRef = useRef(/* @__PURE__ */ new Set());
5606
- useEffect(() => {
5607
- if (!autoStart || !isEnabled) return;
5608
- if (consoleCapture && !isConsoleCaptureActive()) {
5609
- startConsoleCapture(consoleOptions);
5610
- setActiveCaptures((prev) => ({ ...prev, console: true }));
5611
- }
5612
- if (network && !isNetworkCaptureActive()) {
5613
- startNetworkCapture(networkOptions);
5614
- setActiveCaptures((prev) => ({ ...prev, network: true }));
5615
- }
5616
- return () => {
5617
- stopConsoleCapture();
5618
- stopNetworkCapture();
5619
- };
5620
- }, [
5621
- autoStart,
5622
- isEnabled,
5623
- consoleCapture,
5624
- network,
5625
- consoleOptions,
5626
- networkOptions
5627
- ]);
5628
- const captureScreenshotFn = useCallback(
5629
- async (opts) => {
5630
- if (!screenshot) {
5631
- throw new Error("Screenshot capture is not enabled");
5632
- }
5633
- if (!isScreenshotSupported()) {
5634
- throw new Error(
5635
- "Screenshot capture is not supported in this environment"
5636
- );
5637
- }
5638
- return captureScreenshot({ ...screenshotOptions, ...opts });
5639
- },
5640
- [screenshot, screenshotOptions]
5641
- );
5642
- const getConsoleLogsFn = useCallback(
5643
- (opts) => {
5644
- if (!consoleCapture) {
5645
- return { logs: [], totalCaptured: 0 };
5646
- }
5647
- return getConsoleLogs({ ...consoleOptions, ...opts });
5648
- },
5649
- [consoleCapture, consoleOptions]
5650
- );
5651
- const getNetworkRequestsFn = useCallback(
5652
- (opts) => {
5653
- if (!network) {
5654
- return { requests: [], totalCaptured: 0 };
5655
- }
5656
- return getNetworkRequests({ ...networkOptions, ...opts });
5657
- },
5658
- [network, networkOptions]
5659
- );
5660
- const requestConsent = useCallback(
5661
- async (tools, reason = "") => {
5662
- const enabledTools = tools.filter((tool2) => {
5663
- if (tool2 === "screenshot") return screenshot;
5664
- if (tool2 === "console") return consoleCapture;
5665
- if (tool2 === "network") return network;
5666
- return false;
5667
- });
5668
- if (enabledTools.length === 0) {
5669
- return { approved: [], denied: [] };
5670
- }
5671
- if (!requireConsent) {
5672
- return { approved: enabledTools, denied: [] };
5673
- }
5674
- const needsConsent = enabledTools.filter(
5675
- (tool2) => !rememberedConsentRef.current.has(tool2)
5676
- );
5677
- if (needsConsent.length === 0) {
5678
- return { approved: enabledTools, denied: [] };
5679
- }
5680
- const request = {
5681
- tools: needsConsent,
5682
- reason,
5683
- keywords: []
5684
- };
5685
- if (onConsentRequest) {
5686
- const response = await onConsentRequest(request);
5687
- if (response.remember) {
5688
- response.approved.forEach(
5689
- (tool2) => rememberedConsentRef.current.add(tool2)
5690
- );
5691
- }
5692
- return response;
5693
- }
5694
- return new Promise((resolve) => {
5695
- setPendingConsent(request);
5696
- consentResolverRef.current = (response) => {
5697
- if (response.remember) {
5698
- response.approved.forEach(
5699
- (tool2) => rememberedConsentRef.current.add(tool2)
5700
- );
5701
- }
5702
- resolve(response);
5703
- };
5704
- });
5705
- },
5706
- [screenshot, consoleCapture, network, requireConsent, onConsentRequest]
5707
- );
5708
- const respondToConsent = useCallback((response) => {
5709
- if (consentResolverRef.current) {
5710
- consentResolverRef.current(response);
5711
- consentResolverRef.current = null;
5712
- }
5713
- setPendingConsent(null);
5714
- }, []);
5715
- const captureContext = useCallback(
5716
- async (tools) => {
5717
- const toolsToCapture = tools || ["screenshot", "console", "network"];
5718
- const context = {
5719
- timestamp: Date.now()
5720
- };
5721
- const captures = [];
5722
- if (toolsToCapture.includes("screenshot") && screenshot) {
5723
- captures.push(
5724
- captureScreenshotFn().then((result) => {
5725
- context.screenshot = result;
5726
- }).catch(() => {
5727
- })
5728
- );
5729
- }
5730
- if (toolsToCapture.includes("console") && consoleCapture) {
5731
- context.consoleLogs = getConsoleLogsFn();
5732
- }
5733
- if (toolsToCapture.includes("network") && network) {
5734
- context.networkRequests = getNetworkRequestsFn();
5735
- }
5736
- await Promise.all(captures);
5737
- return context;
5738
- },
5739
- [
5740
- screenshot,
5741
- consoleCapture,
5742
- network,
5743
- captureScreenshotFn,
5744
- getConsoleLogsFn,
5745
- getNetworkRequestsFn
5746
- ]
5747
- );
5748
- const startCapturing = useCallback(() => {
5749
- if (consoleCapture && !isConsoleCaptureActive()) {
5750
- startConsoleCapture(consoleOptions);
5751
- setActiveCaptures((prev) => ({ ...prev, console: true }));
5752
- }
5753
- if (network && !isNetworkCaptureActive()) {
5754
- startNetworkCapture(networkOptions);
5755
- setActiveCaptures((prev) => ({ ...prev, network: true }));
5756
- }
5757
- }, [consoleCapture, network, consoleOptions, networkOptions]);
5758
- const stopCapturing = useCallback(() => {
5759
- stopConsoleCapture();
5760
- stopNetworkCapture();
5761
- setActiveCaptures({ console: false, network: false });
5762
- }, []);
5763
- const clearCaptured = useCallback(() => {
5764
- clearConsoleLogs();
5765
- clearNetworkRequests();
5766
- }, []);
5767
- const formatForAI = useCallback((context) => {
5768
- const parts = [];
5769
- if (context.screenshot) {
5770
- parts.push(
5771
- `Screenshot captured (${context.screenshot.width}x${context.screenshot.height}, ${context.screenshot.format})`
5772
- );
5773
- }
5774
- if (context.consoleLogs && context.consoleLogs.logs.length > 0) {
5775
- parts.push(formatLogsForAI(context.consoleLogs.logs));
5776
- }
5777
- if (context.networkRequests && context.networkRequests.requests.length > 0) {
5778
- parts.push(formatRequestsForAI(context.networkRequests.requests));
5779
- }
5780
- return parts.length > 0 ? parts.join("\n\n---\n\n") : "No context captured.";
5781
- }, []);
5782
- const detectIntentFn = useCallback(
5783
- (message) => {
5784
- const result = detectIntent(message);
5785
- result.suggestedTools = result.suggestedTools.filter((tool2) => {
5786
- if (tool2 === "screenshot") return screenshot;
5787
- if (tool2 === "console") return consoleCapture;
5788
- if (tool2 === "network") return network;
5789
- return false;
5790
- });
5791
- return result;
5792
- },
5793
- [screenshot, consoleCapture, network]
5794
- );
5795
- return useMemo(
5796
- () => ({
5797
- isEnabled,
5798
- activeCaptures,
5799
- captureScreenshot: captureScreenshotFn,
5800
- getConsoleLogs: getConsoleLogsFn,
5801
- getNetworkRequests: getNetworkRequestsFn,
5802
- captureContext,
5803
- detectIntent: detectIntentFn,
5804
- requestConsent,
5805
- startCapturing,
5806
- stopCapturing,
5807
- clearCaptured,
5808
- formatForAI,
5809
- pendingConsent,
5810
- respondToConsent
5811
- }),
5812
- [
5813
- isEnabled,
5814
- activeCaptures,
5815
- captureScreenshotFn,
5816
- getConsoleLogsFn,
5817
- getNetworkRequestsFn,
5818
- captureContext,
5819
- detectIntentFn,
5820
- requestConsent,
5821
- startCapturing,
5822
- stopCapturing,
5823
- clearCaptured,
5824
- formatForAI,
5825
- pendingConsent,
5826
- respondToConsent
5827
- ]
5828
- );
5829
- }
5830
- function convertZodSchema(schema, _toolName) {
5831
- try {
5832
- const zodWithJsonSchema = z;
5833
- if (typeof zodWithJsonSchema.toJSONSchema === "function") {
5834
- const jsonSchema = zodWithJsonSchema.toJSONSchema(
5835
- schema
5836
- );
5837
- if (jsonSchema.type === "object") {
5838
- return {
5839
- type: "object",
5840
- properties: jsonSchema.properties || {},
5841
- required: jsonSchema.required
5842
- };
5843
- }
5844
- }
5845
- } catch {
5846
- }
5847
- return zodObjectToInputSchema(schema);
5848
- }
5849
- function useToolWithSchema(config, dependencies = []) {
5850
- const { registerTool, unregisterTool } = useCopilot();
5851
- const configRef = useRef(config);
5852
- configRef.current = config;
5853
- const inputSchema = useMemo(() => {
5854
- try {
5855
- return convertZodSchema(config.schema, config.name);
5856
- } catch (error) {
5857
- console.warn(
5858
- `[useToolWithSchema] Failed to convert Zod schema for tool "${config.name}"`,
5859
- error
5860
- );
5861
- return {
5862
- type: "object",
5863
- properties: {}
5864
- };
5865
- }
5866
- }, [config.schema, config.name]);
5867
- useEffect(() => {
5868
- const tool2 = {
5869
- name: config.name,
5870
- description: config.description,
5871
- location: "client",
5872
- inputSchema,
5873
- handler: async (params, context) => {
5874
- return configRef.current.handler(params, context);
5875
- },
5876
- render: config.render,
5877
- available: config.available ?? true
5878
- };
5879
- registerTool(tool2);
5880
- return () => {
5881
- unregisterTool(config.name);
5882
- };
5883
- }, [config.name, inputSchema, ...dependencies]);
5884
- }
5885
- function useToolsWithSchema(tools, dependencies = []) {
5886
- const { registerTool, unregisterTool } = useCopilot();
5887
- const toolsRef = useRef(tools);
5888
- toolsRef.current = tools;
5889
- useEffect(() => {
5890
- const toolNames = [];
5891
- for (const config of tools) {
5892
- let inputSchema;
5893
- try {
5894
- inputSchema = convertZodSchema(config.schema, config.name);
5895
- } catch (error) {
5896
- console.warn(
5897
- `[useToolsWithSchema] Failed to convert Zod schema for tool "${config.name}"`,
5898
- error
5899
- );
5900
- inputSchema = { type: "object", properties: {} };
5901
- }
5902
- const tool2 = {
5903
- name: config.name,
5904
- description: config.description,
5905
- location: "client",
5906
- inputSchema,
5907
- handler: async (params, context) => {
5908
- const currentConfig = toolsRef.current.find(
5909
- (t) => t.name === config.name
5910
- );
5911
- if (currentConfig) {
5912
- return currentConfig.handler(params, context);
5913
- }
5914
- return { success: false, error: "Tool handler not found" };
5915
- },
5916
- available: config.available ?? true
5917
- };
5918
- registerTool(tool2);
5919
- toolNames.push(config.name);
5920
- }
5921
- return () => {
5922
- for (const name of toolNames) {
5923
- unregisterTool(name);
5924
- }
5925
- };
5926
- }, [tools.map((t) => t.name).join(","), ...dependencies]);
5927
- }
5928
- var CopilotContext2 = createContext(null);
5929
- function useCopilotContext() {
5930
- const context = useContext(CopilotContext2);
5931
- if (!context) {
5932
- throw new Error("useCopilotContext must be used within a CopilotProvider");
5933
- }
5934
- return context;
5935
- }
5936
-
5937
- // src/react/hooks/useToolExecutor.ts
5938
- function useToolExecutor() {
5939
- const {
5940
- registeredTools,
5941
- config,
5942
- chat,
5943
- addToolExecution,
5944
- updateToolExecution
5945
- } = useCopilotContext();
5946
- const toolsRef = useRef(registeredTools);
5947
- toolsRef.current = registeredTools;
5948
- const executeTool = useCallback(
5949
- async (toolCall) => {
5950
- const tool2 = toolsRef.current.find((t) => t.name === toolCall.name);
5951
- if (!tool2) {
5952
- return {
5953
- success: false,
5954
- error: `Unknown tool: ${toolCall.name}`
5955
- };
5956
- }
5957
- if (!tool2.handler) {
5958
- return {
5959
- success: false,
5960
- error: `Tool "${toolCall.name}" has no handler`
5961
- };
5962
- }
5963
- const execution = {
5964
- id: toolCall.id,
5965
- name: toolCall.name,
5966
- args: toolCall.input,
5967
- status: "executing",
5968
- timestamp: Date.now(),
5969
- approvalStatus: "none",
5970
- hidden: tool2.hidden
5971
- };
5972
- addToolExecution?.(execution);
5973
- try {
5974
- const startTime = Date.now();
5975
- const result = await tool2.handler(toolCall.input);
5976
- const duration = Date.now() - startTime;
5977
- updateToolExecution?.(toolCall.id, {
5978
- status: result.success ? "completed" : "error",
5979
- result,
5980
- error: result.error,
5981
- duration
5982
- });
5983
- return result;
5984
- } catch (error) {
5985
- const errorMessage = error instanceof Error ? error.message : "Tool execution failed";
5986
- updateToolExecution?.(toolCall.id, {
5987
- status: "error",
5988
- error: errorMessage
5989
- });
5990
- return {
5991
- success: false,
5992
- error: errorMessage
5993
- };
5994
- }
5995
- },
5996
- [addToolExecution, updateToolExecution]
5997
- );
5998
- const sendToolResult = useCallback(
5999
- async (toolCallId, result) => {
6000
- const runtimeUrl = config.runtimeUrl || config.cloud?.endpoint;
6001
- if (!runtimeUrl) {
6002
- console.warn(
6003
- "[useToolExecutor] No runtime URL configured, cannot send tool result"
6004
- );
6005
- return;
6006
- }
6007
- const baseUrl = runtimeUrl.replace(/\/chat\/?$/, "");
6008
- try {
6009
- const response = await fetch(`${baseUrl}/tool-result`, {
6010
- method: "POST",
6011
- headers: {
6012
- "Content-Type": "application/json"
6013
- },
6014
- body: JSON.stringify({
6015
- threadId: chat.threadId || "default",
6016
- toolCallId,
6017
- result
6018
- })
6019
- });
6020
- if (!response.ok) {
6021
- console.error(
6022
- "[useToolExecutor] Failed to send tool result:",
6023
- await response.text()
6024
- );
6025
- }
6026
- } catch (error) {
6027
- console.error("[useToolExecutor] Error sending tool result:", error);
6028
- }
6029
- },
6030
- [config.runtimeUrl, config.cloud?.endpoint, chat.threadId]
6031
- );
6032
- const getTool = useCallback((name) => {
6033
- return toolsRef.current.find((t) => t.name === name);
6034
- }, []);
6035
- const hasTool = useCallback((name) => {
6036
- return toolsRef.current.some((t) => t.name === name);
6037
- }, []);
6038
- return {
6039
- executeTool,
6040
- sendToolResult,
6041
- getTool,
6042
- hasTool
6043
- };
6044
- }
6045
- function useSuggestions(options = {}) {
6046
- const {
6047
- count = 3,
6048
- context,
6049
- suggestions: staticSuggestions,
6050
- autoRefresh = true
6051
- } = options;
6052
- const { chat, actions, config } = useCopilotContext();
6053
- const [suggestions, setSuggestions] = useState([]);
6054
- const [isLoading, setIsLoading] = useState(false);
6055
- const normalizedStatic = useMemo(
6056
- () => staticSuggestions?.map((s) => typeof s === "string" ? { text: s } : s),
6057
- [staticSuggestions]
6058
- );
6059
- const refresh = useCallback(async () => {
6060
- if (normalizedStatic) {
6061
- setSuggestions(normalizedStatic.slice(0, count));
6062
- return;
6063
- }
6064
- if (!config.cloud) {
6065
- return;
6066
- }
6067
- setIsLoading(true);
6068
- try {
6069
- const endpoint = config.cloud.endpoint || "https://api.yourgpt.ai/v1";
6070
- const response = await fetch(`${endpoint}/suggestions`, {
6071
- method: "POST",
6072
- headers: {
6073
- "Content-Type": "application/json",
6074
- Authorization: `Bearer ${config.cloud.apiKey}`
6075
- },
6076
- body: JSON.stringify({
6077
- botId: config.cloud.botId,
6078
- count,
6079
- context,
6080
- messages: chat.messages.slice(-5)
6081
- // Last 5 messages for context
6082
- })
6083
- });
6084
- if (response.ok) {
6085
- const data = await response.json();
6086
- setSuggestions(
6087
- data.suggestions.map(
6088
- (s) => typeof s === "string" ? { text: s } : s
6089
- )
6090
- );
6091
- }
6092
- } catch (error) {
6093
- console.error("Failed to fetch suggestions:", error);
6094
- } finally {
6095
- setIsLoading(false);
6096
- }
6097
- }, [config.cloud, count, context, chat.messages, normalizedStatic]);
6098
- const select = useCallback(
6099
- (suggestion) => {
6100
- const text = typeof suggestion === "string" ? suggestion : suggestion.text;
6101
- actions.sendMessage(text);
6102
- },
6103
- [actions]
6104
- );
6105
- useEffect(() => {
6106
- if (autoRefresh && chat.messages.length === 0) {
6107
- refresh();
6108
- }
6109
- }, [autoRefresh, chat.messages.length, refresh]);
6110
- return {
6111
- suggestions: normalizedStatic?.slice(0, count) || suggestions,
6112
- isLoading,
6113
- refresh,
6114
- select
6115
- };
6116
- }
6117
- function useAgent(options) {
6118
- const { name, initialState = {}, onStateChange } = options;
6119
- const { config } = useCopilotContext();
6120
- const [state, setStateInternal] = useState(initialState);
6121
- const [isRunning, setIsRunning] = useState(false);
6122
- const [nodeName, setNodeName] = useState(null);
6123
- const [error, setError] = useState(null);
6124
- const abortControllerRef = useRef(null);
6125
- const getEndpoint = useCallback(() => {
6126
- if (config.cloud) {
6127
- return `${config.cloud.endpoint || "https://api.yourgpt.ai/v1"}/agents/${name}`;
6128
- }
6129
- return `${config.runtimeUrl || "/api"}/agents/${name}`;
6130
- }, [config, name]);
6131
- const start = useCallback(
6132
- async (input) => {
6133
- setIsRunning(true);
6134
- setError(null);
6135
- abortControllerRef.current = new AbortController();
6136
- try {
6137
- const endpoint = getEndpoint();
6138
- const headers = {
6139
- "Content-Type": "application/json"
6140
- };
6141
- if (config.cloud?.apiKey) {
6142
- headers["Authorization"] = `Bearer ${config.cloud.apiKey}`;
6143
- }
6144
- const response = await fetch(`${endpoint}/start`, {
6145
- method: "POST",
6146
- headers,
6147
- body: JSON.stringify({
6148
- input: typeof input === "string" ? { message: input } : input,
6149
- state
6150
- }),
6151
- signal: abortControllerRef.current.signal
6152
- });
6153
- if (!response.ok) {
6154
- throw new Error(`Agent error: ${response.status}`);
6155
- }
6156
- for await (const event of streamSSE(response)) {
6157
- handleAgentEvent(event);
6158
- }
6159
- } catch (err) {
6160
- if (err.name !== "AbortError") {
6161
- setError(err instanceof Error ? err : new Error("Unknown error"));
6162
- }
6163
- } finally {
6164
- setIsRunning(false);
6165
- abortControllerRef.current = null;
6166
- }
6167
- },
6168
- [config, getEndpoint, state]
6169
- );
6170
- const handleAgentEvent = useCallback(
6171
- (event) => {
6172
- if (event.type === "error") {
6173
- setError(new Error(event.message));
6174
- return;
6175
- }
6176
- if ("state" in event && event.state) {
6177
- setStateInternal(event.state);
6178
- onStateChange?.(event.state);
6179
- }
6180
- if ("nodeName" in event && event.nodeName) {
6181
- setNodeName(event.nodeName);
6182
- }
6183
- },
6184
- [onStateChange]
6185
- );
6186
- const stop = useCallback(() => {
6187
- abortControllerRef.current?.abort();
6188
- }, []);
6189
- const setState = useCallback(
6190
- (partialState) => {
6191
- setStateInternal((prev) => {
6192
- const newState = { ...prev, ...partialState };
6193
- onStateChange?.(newState);
6194
- return newState;
6195
- });
6196
- },
6197
- [onStateChange]
6198
- );
6199
- useEffect(() => {
6200
- return () => {
6201
- abortControllerRef.current?.abort();
6202
- };
6203
- }, []);
6204
- return {
6205
- state,
6206
- isRunning,
6207
- nodeName,
6208
- start,
6209
- stop,
6210
- setState,
6211
- error
6212
- };
6213
- }
6214
-
6215
- // src/react/utils/knowledge-base.ts
6216
- var KNOWLEDGE_BASE_API = "https://api.yourgpt.ai/chatbot/v1/searchIndexDocument";
6217
- async function searchKnowledgeBase(query, config) {
6218
- try {
6219
- const response = await fetch(KNOWLEDGE_BASE_API, {
6220
- method: "POST",
6221
- headers: {
6222
- accept: "*/*",
6223
- "content-type": "application/json",
6224
- authorization: `Bearer ${config.token}`
6225
- },
6226
- body: JSON.stringify({
6227
- project_uid: config.projectUid,
6228
- query,
6229
- page: 1,
6230
- limit: String(config.limit || 10),
6231
- app_id: config.appId || "1"
6232
- })
6233
- });
6234
- if (!response.ok) {
6235
- return {
6236
- success: false,
6237
- results: [],
6238
- error: `API error: ${response.status} ${response.statusText}`
6239
- };
6240
- }
6241
- const data = await response.json();
6242
- const results = (data.data || data.results || []).map((item) => ({
6243
- id: item.id || item._id || String(Math.random()),
6244
- title: item.title || item.name || void 0,
6245
- content: item.content || item.text || item.snippet || "",
6246
- score: item.score || item.relevance || void 0,
6247
- url: item.url || item.source_url || void 0,
6248
- metadata: item.metadata || {}
6249
- }));
6250
- return {
6251
- success: true,
6252
- results,
6253
- total: data.total || results.length,
6254
- page: data.page || 1
6255
- };
6256
- } catch (error) {
6257
- return {
6258
- success: false,
6259
- results: [],
6260
- error: error instanceof Error ? error.message : "Unknown error"
6261
- };
6262
- }
6263
- }
6264
- function formatKnowledgeResultsForAI(results) {
6265
- if (results.length === 0) {
6266
- return "No relevant documents found in the knowledge base.";
6267
- }
6268
- return results.map((result, index) => {
6269
- const parts = [`[${index + 1}]`];
6270
- if (result.title) parts.push(`**${result.title}**`);
6271
- parts.push(result.content);
6272
- if (result.url) parts.push(`Source: ${result.url}`);
6273
- return parts.join("\n");
6274
- }).join("\n\n---\n\n");
6275
- }
6276
-
6277
- // src/react/hooks/useKnowledgeBase.ts
6278
- function useKnowledgeBase(config) {
6279
- const { registerTool, unregisterTool } = useCopilot();
6280
- const configRef = useRef(config);
6281
- configRef.current = config;
6282
- const handleSearch = useCallback(
6283
- async (params) => {
6284
- const query = params.query;
6285
- if (!query) {
6286
- return {
6287
- success: false,
6288
- error: "Query is required"
6289
- };
6290
- }
6291
- const currentConfig = configRef.current;
6292
- const kbConfig = {
6293
- projectUid: currentConfig.projectUid,
6294
- token: currentConfig.token,
6295
- appId: currentConfig.appId,
6296
- limit: currentConfig.limit || 5
6297
- };
6298
- const response = await searchKnowledgeBase(
6299
- query,
6300
- kbConfig
6301
- );
6302
- if (!response.success) {
6303
- return {
6304
- success: false,
6305
- error: response.error || "Knowledge base search failed"
6306
- };
6307
- }
6308
- const formattedResults = formatKnowledgeResultsForAI(response.results);
6309
- return {
6310
- success: true,
6311
- message: formattedResults,
6312
- data: {
6313
- resultCount: response.results.length,
6314
- total: response.total
6315
- }
6316
- };
6317
- },
6318
- []
6319
- );
6320
- useEffect(() => {
6321
- if (config.enabled === false) {
6322
- return;
6323
- }
6324
- registerTool({
6325
- name: "search_knowledge",
6326
- description: "Search the knowledge base for relevant information about the product, documentation, or company. Use this to answer questions about features, pricing, policies, guides, or any factual information.",
6327
- location: "client",
6328
- inputSchema: {
6329
- type: "object",
6330
- properties: {
6331
- query: {
6332
- type: "string",
6333
- description: "The search query to find relevant information in the knowledge base"
6334
- }
6335
- },
6336
- required: ["query"]
6337
- },
6338
- handler: handleSearch
6339
- });
6340
- return () => {
6341
- unregisterTool("search_knowledge");
6342
- };
6343
- }, [
6344
- config.enabled,
6345
- config.projectUid,
6346
- config.token,
6347
- registerTool,
6348
- unregisterTool,
6349
- handleSearch
6350
- ]);
6351
- }
6352
- var DEFAULT_CAPABILITIES = {
6353
- supportsVision: false,
6354
- supportsTools: true,
6355
- supportsThinking: false,
6356
- supportsStreaming: true,
6357
- supportsPDF: false,
6358
- supportsAudio: false,
6359
- supportsVideo: false,
6360
- maxTokens: 8192,
6361
- supportedImageTypes: [],
6362
- supportsJsonMode: false,
6363
- supportsSystemMessages: true
6364
- };
6365
- function useCapabilities() {
6366
- const { config } = useCopilotContext();
6367
- const [capabilities, setCapabilities] = useState(DEFAULT_CAPABILITIES);
6368
- const [provider, setProvider] = useState("unknown");
6369
- const [model, setModel] = useState("unknown");
6370
- const [supportedModels, setSupportedModels] = useState([]);
6371
- const [isLoading, setIsLoading] = useState(true);
6372
- const [error, setError] = useState(null);
6373
- const capabilitiesUrl = config.runtimeUrl ? config.runtimeUrl.replace(/\/chat\/?$/, "/capabilities") : null;
6374
- const fetchCapabilities = useCallback(async () => {
6375
- if (!capabilitiesUrl) {
6376
- setIsLoading(false);
6377
- return;
6378
- }
6379
- try {
6380
- setIsLoading(true);
6381
- setError(null);
6382
- const response = await fetch(capabilitiesUrl);
6383
- if (!response.ok) {
6384
- throw new Error(`Failed to fetch capabilities: ${response.status}`);
6385
- }
6386
- const data = await response.json();
6387
- setCapabilities(data.capabilities);
6388
- setProvider(data.provider);
6389
- setModel(data.model);
6390
- setSupportedModels(data.supportedModels);
6391
- } catch (err) {
6392
- setError(err instanceof Error ? err : new Error("Unknown error"));
6393
- } finally {
6394
- setIsLoading(false);
6395
- }
6396
- }, [capabilitiesUrl]);
6397
- useEffect(() => {
6398
- fetchCapabilities();
6399
- }, [fetchCapabilities]);
6400
- return {
6401
- /** Current model capabilities */
6402
- capabilities,
6403
- /** Current provider name */
6404
- provider,
6405
- /** Current model ID */
6406
- model,
6407
- /** List of supported models for current provider */
6408
- supportedModels,
6409
- /** Whether capabilities are being loaded */
6410
- isLoading,
6411
- /** Error if fetch failed */
6412
- error,
6413
- /** Refetch capabilities */
6414
- refetch: fetchCapabilities
6415
- };
6416
- }
6417
- function useFeatureSupport(feature) {
6418
- const { capabilities } = useCapabilities();
6419
- return capabilities[feature] ?? false;
6420
- }
6421
- function useSupportedMediaTypes() {
6422
- const { capabilities } = useCapabilities();
6423
- return {
6424
- /** Supported image MIME types */
6425
- imageTypes: capabilities.supportedImageTypes || [],
6426
- /** Supported audio MIME types */
6427
- audioTypes: capabilities.supportedAudioTypes || [],
6428
- /** Supported video MIME types */
6429
- videoTypes: capabilities.supportedVideoTypes || [],
6430
- /** Whether any image types are supported */
6431
- hasImageSupport: (capabilities.supportedImageTypes?.length ?? 0) > 0,
6432
- /** Whether any audio types are supported */
6433
- hasAudioSupport: (capabilities.supportedAudioTypes?.length ?? 0) > 0,
6434
- /** Whether any video types are supported */
6435
- hasVideoSupport: (capabilities.supportedVideoTypes?.length ?? 0) > 0
6436
- };
6437
- }
6438
- function useDevLogger() {
6439
- const ctx = useCopilotContext();
6440
- return useMemo(() => {
6441
- const toolExecutions = (ctx.agentLoop?.toolExecutions || []).map(
6442
- (exec) => ({
6443
- id: exec.id,
6444
- name: exec.name,
6445
- status: exec.status,
6446
- approvalStatus: exec.approvalStatus || "not_required"
6447
- })
6448
- );
6449
- const pendingApprovalsCount = ctx.pendingApprovals?.length || 0;
6450
- const registeredTools = (ctx.registeredTools || []).map((tool2) => ({
6451
- name: tool2.name,
6452
- location: tool2.location || "client"
6453
- }));
6454
- const registeredActions = (ctx.registeredActions || []).map((action) => ({
6455
- name: action.name
6456
- }));
6457
- const storedPermissions = (ctx.storedPermissions || []).map((p) => ({
6458
- toolName: p.toolName,
6459
- level: p.level
6460
- }));
6461
- return {
6462
- chat: {
6463
- isLoading: ctx.chat?.isLoading || false,
6464
- messageCount: ctx.chat?.messages?.length || 0,
6465
- threadId: ctx.chat?.threadId || "none",
6466
- error: ctx.chat?.error?.message || null
6467
- },
6468
- tools: {
6469
- isEnabled: !!ctx.toolsConfig,
6470
- isCapturing: ctx.tools?.isCapturing || false,
6471
- pendingConsent: !!ctx.tools?.pendingConsent
6472
- },
6473
- agentLoop: {
6474
- toolExecutions,
6475
- pendingApprovals: pendingApprovalsCount,
6476
- iteration: ctx.agentLoop?.iteration || 0,
6477
- maxIterations: ctx.agentLoop?.maxIterations || 10
6478
- },
6479
- registered: {
6480
- tools: registeredTools,
6481
- actions: registeredActions,
6482
- contextCount: ctx.contextTree?.length || 0
6483
- },
6484
- permissions: {
6485
- stored: storedPermissions,
6486
- loaded: ctx.permissionsLoaded || false
6487
- },
6488
- config: {
6489
- runtimeUrl: ctx.config?.runtimeUrl || ctx.config?.cloud?.endpoint || ""
6490
- }
6491
- };
6492
- }, [
6493
- ctx.chat,
6494
- ctx.tools,
6495
- ctx.toolsConfig,
6496
- ctx.agentLoop,
6497
- ctx.pendingApprovals,
6498
- ctx.registeredTools,
6499
- ctx.registeredActions,
6500
- ctx.contextTree,
6501
- ctx.storedPermissions,
6502
- ctx.permissionsLoaded,
6503
- ctx.config
6504
- ]);
6505
- }
6506
-
6507
- // src/react/internal/ReactThreadManagerState.ts
6508
- var ReactThreadManagerState = class {
6509
- constructor(initialThreads) {
6510
- this._threads = [];
6511
- this._currentThreadId = null;
6512
- this._currentThread = null;
6513
- this._loadStatus = "idle";
6514
- this._error = void 0;
6515
- // Callbacks for React subscriptions (useSyncExternalStore)
6516
- this.subscribers = /* @__PURE__ */ new Set();
6517
- // ============================================
6518
- // Subscription (for useSyncExternalStore)
6519
- // ============================================
6520
- /**
6521
- * Subscribe to state changes.
6522
- * Returns an unsubscribe function.
6523
- *
6524
- * @example
6525
- * ```tsx
6526
- * const threads = useSyncExternalStore(
6527
- * state.subscribe,
6528
- * () => state.threads
6529
- * );
6530
- * ```
6531
- */
6532
- this.subscribe = (callback) => {
6533
- this.subscribers.add(callback);
6534
- return () => {
6535
- this.subscribers.delete(callback);
6536
- };
6537
- };
6538
- if (initialThreads) {
6539
- this._threads = initialThreads;
6540
- }
6541
- }
6542
- // ============================================
6543
- // Getters
6544
- // ============================================
6545
- get threads() {
6546
- return this._threads;
6547
- }
6548
- get currentThreadId() {
6549
- return this._currentThreadId;
6550
- }
6551
- get currentThread() {
6552
- return this._currentThread;
6553
- }
6554
- get loadStatus() {
6555
- return this._loadStatus;
6556
- }
6557
- get error() {
6558
- return this._error;
6559
- }
6560
- // ============================================
6561
- // Setters (trigger reactivity)
6562
- // ============================================
6563
- set threads(value) {
6564
- this._threads = value;
6565
- this.notify();
6566
- }
6567
- // ============================================
6568
- // Mutations
6569
- // ============================================
6570
- setThreads(threads) {
6571
- this._threads = threads;
6572
- this.notify();
6573
- }
6574
- setCurrentThread(thread) {
6575
- this._currentThread = thread;
6576
- this._currentThreadId = thread?.id ?? null;
6577
- this.notify();
6578
- }
6579
- setCurrentThreadId(id) {
6580
- this._currentThreadId = id;
6581
- this.notify();
6582
- }
6583
- addThread(thread) {
6584
- this._threads = [thread, ...this._threads];
6585
- this.notify();
6586
- }
6587
- updateThread(id, updates) {
6588
- this._threads = this._threads.map(
6589
- (t) => t.id === id ? { ...t, ...updates } : t
6590
- );
6591
- if (updates.updatedAt) {
6592
- this._threads = [...this._threads].sort(
6593
- (a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()
6594
- );
6595
- }
6596
- if (this._currentThread?.id === id) {
6597
- this._currentThread = { ...this._currentThread, ...updates };
6598
- }
6599
- this.notify();
6600
- }
6601
- removeThread(id) {
6602
- this._threads = this._threads.filter((t) => t.id !== id);
6603
- if (this._currentThreadId === id) {
6604
- this._currentThreadId = null;
6605
- this._currentThread = null;
6606
- }
6607
- this.notify();
6608
- }
6609
- setLoadStatus(status) {
6610
- this._loadStatus = status;
6611
- this.notify();
6612
- }
6613
- setError(error) {
6614
- this._error = error;
6615
- this.notify();
6616
- }
6617
- // ============================================
6618
- // Snapshots (for useSyncExternalStore)
6619
- // ============================================
6620
- getThreadsSnapshot() {
6621
- return this._threads;
6622
- }
6623
- getCurrentThreadSnapshot() {
6624
- return this._currentThread;
6625
- }
6626
- getLoadStatusSnapshot() {
6627
- return this._loadStatus;
6628
- }
6629
- getErrorSnapshot() {
6630
- return this._error;
6631
- }
6632
- // ============================================
6633
- // Private Methods
6634
- // ============================================
6635
- notify() {
6636
- this.subscribers.forEach((cb) => cb());
6637
- }
6638
- /**
6639
- * Cleanup subscriptions
6640
- */
6641
- dispose() {
6642
- this.subscribers.clear();
6643
- }
6644
- };
6645
- function createReactThreadManagerState(initialThreads) {
6646
- return new ReactThreadManagerState(initialThreads);
6647
- }
6648
-
6649
- // src/react/internal/ReactThreadManager.ts
6650
- var _ReactThreadManager = class _ReactThreadManager extends ThreadManager {
6651
- constructor(config = {}, callbacks = {}) {
6652
- const reactState = new ReactThreadManagerState();
6653
- super({ ...config, state: reactState }, callbacks);
6654
- // ============================================
6655
- // Subscription Methods (for useSyncExternalStore)
6656
- // ============================================
6657
- /**
6658
- * Subscribe to state changes
6659
- * Use with useSyncExternalStore
6660
- */
6661
- this.subscribe = (callback) => {
6662
- return this.state.subscribe(callback);
6663
- };
6664
- // ============================================
6665
- // Snapshot Getters (for useSyncExternalStore)
6666
- // ============================================
6667
- /**
6668
- * Get threads snapshot
6669
- */
6670
- this.getThreadsSnapshot = () => {
6671
- return this.state.getThreadsSnapshot();
6672
- };
6673
- /**
6674
- * Get current thread snapshot
6675
- */
6676
- this.getCurrentThreadSnapshot = () => {
6677
- return this.state.getCurrentThreadSnapshot();
6678
- };
6679
- /**
6680
- * Get current thread ID snapshot
6681
- */
6682
- this.getCurrentThreadIdSnapshot = () => {
6683
- return this.state.currentThreadId;
6684
- };
6685
- /**
6686
- * Get load status snapshot
6687
- */
6688
- this.getLoadStatusSnapshot = () => {
6689
- return this.state.getLoadStatusSnapshot();
6690
- };
6691
- /**
6692
- * Get error snapshot
6693
- */
6694
- this.getErrorSnapshot = () => {
6695
- return this.state.getErrorSnapshot();
6696
- };
6697
- /**
6698
- * Get isLoading snapshot
6699
- */
6700
- this.getIsLoadingSnapshot = () => {
6701
- return this.state.getLoadStatusSnapshot() === "loading";
6702
- };
6703
- /**
6704
- * Get threads snapshot for server (always empty for hydration consistency)
6705
- */
6706
- this.getThreadsServerSnapshot = () => {
6707
- return _ReactThreadManager.EMPTY_THREADS;
6708
- };
6709
- /**
6710
- * Get current thread snapshot for server (always null)
6711
- */
6712
- this.getCurrentThreadServerSnapshot = () => {
6713
- return null;
6714
- };
6715
- /**
6716
- * Get current thread ID snapshot for server (always null)
6717
- */
6718
- this.getCurrentThreadIdServerSnapshot = () => {
6719
- return null;
6720
- };
6721
- /**
6722
- * Get load status snapshot for server (always "idle")
6723
- */
6724
- this.getLoadStatusServerSnapshot = () => {
6725
- return _ReactThreadManager.IDLE_STATUS;
6726
- };
6727
- /**
6728
- * Get error snapshot for server (always undefined)
6729
- */
6730
- this.getErrorServerSnapshot = () => {
6731
- return void 0;
6732
- };
6733
- /**
6734
- * Get isLoading snapshot for server (always false)
6735
- */
6736
- this.getIsLoadingServerSnapshot = () => {
6737
- return false;
6738
- };
6739
- }
6740
- // ============================================
6741
- // Cleanup
6742
- // ============================================
6743
- /**
6744
- * Dispose of the manager
6745
- */
6746
- async dispose() {
6747
- this.state.dispose();
6748
- await super.dispose();
6749
- }
6750
- };
6751
- // ============================================
6752
- // Server Snapshots (for SSR - stable cached values)
6753
- // ============================================
6754
- // Cached values for server snapshots (must be stable references)
6755
- _ReactThreadManager.EMPTY_THREADS = [];
6756
- _ReactThreadManager.IDLE_STATUS = "idle";
6757
- var ReactThreadManager = _ReactThreadManager;
6758
- function createReactThreadManager(config, callbacks) {
6759
- return new ReactThreadManager(config, callbacks);
6760
- }
6761
-
6762
- // src/react/hooks/useThreadManager.ts
6763
- var defaultManager = null;
6764
- function getDefaultManager() {
6765
- if (!defaultManager) {
6766
- defaultManager = createReactThreadManager();
6767
- }
6768
- return defaultManager;
6769
- }
6770
- var internalManager = null;
6771
- function getInternalManager(config) {
6772
- if (!internalManager) {
6773
- internalManager = createReactThreadManager(
6774
- {
6775
- adapter: config.adapter,
6776
- saveDebounce: config.saveDebounce,
6777
- autoLoad: config.autoLoad,
6778
- autoRestoreLastThread: config.autoRestoreLastThread
6779
- },
6780
- config.callbacks
6781
- );
6782
- }
6783
- return internalManager;
6784
- }
6785
- function useThreadManager(config) {
6786
- const manager = useMemo(() => {
6787
- if (!config) {
6788
- return getDefaultManager();
6789
- }
6790
- if (!config.adapter) {
6791
- return getInternalManager(config);
6792
- }
6793
- return createReactThreadManager(
6794
- {
6795
- adapter: config.adapter,
6796
- saveDebounce: config.saveDebounce,
6797
- autoLoad: config.autoLoad,
6798
- autoRestoreLastThread: config.autoRestoreLastThread
6799
- },
6800
- config.callbacks
6801
- );
6802
- }, [
6803
- config?.adapter,
6804
- config?.saveDebounce,
6805
- config?.autoLoad,
6806
- config?.autoRestoreLastThread
6807
- // Note: callbacks are intentionally not in deps to avoid recreating manager
6808
- ]);
6809
- const threads = useSyncExternalStore(
6810
- manager.subscribe,
6811
- manager.getThreadsSnapshot,
6812
- manager.getThreadsServerSnapshot
6813
- // SSR - always empty array
6814
- );
6815
- const currentThread = useSyncExternalStore(
6816
- manager.subscribe,
6817
- manager.getCurrentThreadSnapshot,
6818
- manager.getCurrentThreadServerSnapshot
6819
- // SSR - always null
6820
- );
6821
- const currentThreadId = useSyncExternalStore(
6822
- manager.subscribe,
6823
- manager.getCurrentThreadIdSnapshot,
6824
- manager.getCurrentThreadIdServerSnapshot
6825
- // SSR - always null
6826
- );
6827
- const loadStatus = useSyncExternalStore(
6828
- manager.subscribe,
6829
- manager.getLoadStatusSnapshot,
6830
- manager.getLoadStatusServerSnapshot
6831
- // SSR - always "idle"
6832
- );
6833
- const error = useSyncExternalStore(
6834
- manager.subscribe,
6835
- manager.getErrorSnapshot,
6836
- manager.getErrorServerSnapshot
6837
- // SSR - always undefined
6838
- );
6839
- const isLoading = useSyncExternalStore(
6840
- manager.subscribe,
6841
- manager.getIsLoadingSnapshot,
6842
- manager.getIsLoadingServerSnapshot
6843
- // SSR - always false
6844
- );
6845
- useEffect(() => {
6846
- return () => {
6847
- if (config?.adapter && manager !== defaultManager && manager !== internalManager) {
6848
- manager.dispose();
6849
- }
6850
- };
6851
- }, [manager, config]);
6852
- useEffect(() => {
6853
- const handleBeforeUnload = () => {
6854
- if (manager.hasPendingChanges) {
6855
- manager.saveNow().catch(() => {
6856
- });
6857
- }
6858
- };
6859
- if (typeof window !== "undefined") {
6860
- window.addEventListener("beforeunload", handleBeforeUnload);
6861
- return () => {
6862
- window.removeEventListener("beforeunload", handleBeforeUnload);
6863
- };
6864
- }
6865
- }, [manager]);
6866
- const createThread = useCallback(
6867
- (options) => manager.createThread(options),
6868
- [manager]
6869
- );
6870
- const switchThread = useCallback(
6871
- (id) => manager.switchThread(id),
6872
- [manager]
6873
- );
6874
- const updateCurrentThread = useCallback(
6875
- (updates) => manager.updateCurrentThread(updates),
6876
- [manager]
6877
- );
6878
- const deleteThread = useCallback(
6879
- (id) => manager.deleteThread(id),
6880
- [manager]
6881
- );
6882
- const clearCurrentThread = useCallback(
6883
- () => manager.clearCurrentThread(),
6884
- [manager]
6885
- );
6886
- const refreshThreads = useCallback(() => manager.loadThreads(), [manager]);
6887
- const saveNow = useCallback(() => manager.saveNow(), [manager]);
6888
- const clearAllThreads = useCallback(
6889
- () => manager.clearAllThreads(),
6890
- [manager]
6891
- );
6892
- const messages = useMemo(
6893
- () => currentThread?.messages ?? [],
6894
- [currentThread]
6895
- );
6896
- const setMessages = useCallback(
6897
- (newMessages) => updateCurrentThread({ messages: newMessages }),
6898
- [updateCurrentThread]
6899
- );
6900
- const hasPendingChanges = manager.hasPendingChanges;
6901
- return {
6902
- // State
6903
- threads,
6904
- currentThread,
6905
- currentThreadId,
6906
- isLoading,
6907
- loadStatus,
6908
- error,
6909
- // Actions
6910
- createThread,
6911
- switchThread,
6912
- updateCurrentThread,
6913
- deleteThread,
6914
- clearCurrentThread,
6915
- refreshThreads,
6916
- saveNow,
6917
- clearAllThreads,
6918
- // Utilities
6919
- messages,
6920
- setMessages,
6921
- hasPendingChanges
6922
- };
6923
- }
6924
- function useMCPUIIntents(config = {}) {
6925
- const {
6926
- onToolCall,
6927
- onIntent,
6928
- onPrompt,
6929
- onNotify,
6930
- onLink,
6931
- requireConsent = { tool: false, link: true }
6932
- } = config;
6933
- const handleIntent = useCallback(
6934
- async (intent, context) => {
6935
- switch (intent.type) {
6936
- case "tool": {
6937
- if (requireConsent.tool) ;
6938
- await onToolCall?.(intent.name, intent.arguments, context);
6939
- break;
6940
- }
6941
- case "intent": {
6942
- await onIntent?.(intent.action, intent.data, context);
6943
- break;
6944
- }
6945
- case "prompt": {
6946
- onPrompt?.(intent.text, context);
6947
- break;
6948
- }
6949
- case "notify": {
6950
- onNotify?.(intent.message, intent.level, context);
6951
- break;
6952
- }
6953
- case "link": {
6954
- const shouldContinue = onLink?.(intent.url, intent.newTab, context);
6955
- if (shouldContinue === false) {
6956
- break;
6957
- }
6958
- if (requireConsent.link) {
6959
- const isSafeUrl = intent.url.startsWith("https://") || intent.url.startsWith("http://localhost");
6960
- if (!isSafeUrl) {
6961
- console.warn(
6962
- "[MCP-UI] Blocked potentially unsafe link:",
6963
- intent.url
6964
- );
6965
- break;
6966
- }
6967
- }
6968
- if (typeof window !== "undefined") {
6969
- if (intent.newTab !== false) {
6970
- window.open(intent.url, "_blank", "noopener,noreferrer");
6971
- } else {
6972
- window.location.href = intent.url;
6973
- }
6974
- }
6975
- break;
6976
- }
6977
- default: {
6978
- console.warn(
6979
- "[MCP-UI] Unknown intent type:",
6980
- intent.type
6981
- );
6982
- }
6983
- }
6984
- },
6985
- [onToolCall, onIntent, onPrompt, onNotify, onLink, requireConsent]
6986
- );
6987
- return useMemo(
6988
- () => ({
6989
- handleIntent
6990
- }),
6991
- [handleIntent]
6992
- );
6993
- }
6994
- function createMessageIntentHandler(sendMessage) {
6995
- return {
6996
- onIntent: async (action, data) => {
6997
- const dataStr = data ? ` with ${JSON.stringify(data)}` : "";
6998
- await sendMessage(`User action: ${action}${dataStr}`);
6999
- },
7000
- onPrompt: (text) => {
7001
- sendMessage(text);
7002
- },
7003
- onNotify: (message, level) => {
7004
- if (level === "error") {
7005
- sendMessage(`Error: ${message}`);
7006
- }
7007
- }
7008
- };
7009
- }
7010
- function createToolIntentHandler(callTool) {
7011
- return {
7012
- onToolCall: async (name, args) => {
7013
- await callTool(name, args);
7014
- }
7015
- };
7016
- }
7017
- function getLastResponseUsage(messages) {
7018
- for (let i = messages.length - 1; i >= 0; i--) {
7019
- const msg = messages[i];
7020
- if (msg.role === "assistant" && msg.metadata?.usage) {
7021
- const u = msg.metadata.usage;
7022
- const prompt = u.prompt_tokens ?? 0;
7023
- const completion = u.completion_tokens ?? 0;
7024
- return {
7025
- prompt_tokens: prompt,
7026
- completion_tokens: completion,
7027
- total_tokens: u.total_tokens ?? prompt + completion
7028
- };
7029
- }
7030
- }
7031
- return null;
7032
- }
7033
- function useContextStats() {
7034
- const { contextChars, contextUsage, registeredTools, messages } = useCopilot();
7035
- const toolCount = useMemo(() => registeredTools.length, [registeredTools]);
7036
- const messageCount = useMemo(
7037
- () => messages.filter((m) => m.role !== "system").length,
7038
- [messages]
7039
- );
7040
- const totalTokens = useMemo(() => {
7041
- if (contextUsage) return contextUsage.total.tokens;
7042
- return Math.ceil(contextChars / 3.5);
7043
- }, [contextUsage, contextChars]);
7044
- const usagePercent = useMemo(() => {
7045
- if (contextUsage) return contextUsage.total.percent;
7046
- return 0;
7047
- }, [contextUsage]);
7048
- const lastResponseUsage = useMemo(
7049
- () => getLastResponseUsage(messages),
7050
- [messages]
7051
- );
7052
- return {
7053
- contextUsage,
7054
- totalTokens,
7055
- usagePercent,
7056
- contextChars,
7057
- toolCount,
7058
- messageCount,
7059
- lastResponseUsage
7060
- };
7061
- }
7062
- var DEV_CONTENT_WARN_THRESHOLD = 2e3;
7063
- function useSkill(skill) {
7064
- const { register, unregister } = useSkillContext();
7065
- if (process.env.NODE_ENV !== "production" && skill.source.type === "inline" && skill.source.content.length > DEV_CONTENT_WARN_THRESHOLD) {
7066
- console.warn(
7067
- `[copilot-sdk/skills] Inline skill "${skill.name}" has ${skill.source.content.length} characters. Inline skills are sent on every request \u2014 keep them under ${DEV_CONTENT_WARN_THRESHOLD} characters. Consider using a file or URL skill instead.`
7068
- );
7069
- }
7070
- useEffect(() => {
7071
- if (skill.source.type !== "inline") {
7072
- console.warn(
7073
- `[copilot-sdk/skills] useSkill only supports inline skills client-side. Skill "${skill.name}" has source type "${skill.source.type}" and will be skipped.`
7074
- );
7075
- return;
7076
- }
7077
- const resolved = {
7078
- ...skill,
7079
- content: skill.source.content
7080
- };
7081
- register(resolved);
7082
- return () => {
7083
- unregister(skill.name);
7084
- };
7085
- }, [
7086
- skill.name,
7087
- skill.source.type === "inline" ? skill.source.content : "",
7088
- skill.strategy,
7089
- skill.description
7090
- ]);
7091
- }
7092
- function useSkillStatus() {
7093
- const { skills, registry } = useSkillContext();
7094
- const has = useCallback(
7095
- (name) => registry.has(name),
7096
- // eslint-disable-next-line react-hooks/exhaustive-deps
7097
- [skills]
7098
- );
7099
- return {
7100
- skills,
7101
- count: skills.length,
7102
- has
7103
- };
7104
- }
7105
-
7106
- // src/react/utils/permission-storage.ts
7107
- var DEFAULT_KEY_PREFIX = "yourgpt-permissions";
7108
- function createPermissionStorage(config) {
7109
- switch (config.type) {
7110
- case "localStorage":
7111
- return createBrowserStorageAdapter(
7112
- typeof window !== "undefined" ? localStorage : null,
7113
- config.keyPrefix
7114
- );
7115
- case "sessionStorage":
7116
- return createBrowserStorageAdapter(
7117
- typeof window !== "undefined" ? sessionStorage : null,
7118
- config.keyPrefix
7119
- );
7120
- case "memory":
7121
- default:
7122
- return createMemoryStorageAdapter();
7123
- }
7124
- }
7125
- function createBrowserStorageAdapter(storage, keyPrefix = DEFAULT_KEY_PREFIX) {
7126
- const getStorageKey = () => keyPrefix;
7127
- const loadPermissions = () => {
7128
- if (!storage) return /* @__PURE__ */ new Map();
7129
- try {
7130
- const data = storage.getItem(getStorageKey());
7131
- if (!data) return /* @__PURE__ */ new Map();
7132
- const parsed = JSON.parse(data);
7133
- return new Map(parsed.map((p) => [p.toolName, p]));
7134
- } catch {
7135
- return /* @__PURE__ */ new Map();
7136
- }
7137
- };
7138
- const savePermissions = (permissions) => {
7139
- if (!storage) return;
7140
- try {
7141
- storage.setItem(
7142
- getStorageKey(),
7143
- JSON.stringify(Array.from(permissions.values()))
7144
- );
7145
- } catch (e) {
7146
- console.warn("[PermissionStorage] Failed to save permissions:", e);
7147
- }
7148
- };
7149
- return {
7150
- async get(toolName) {
7151
- const permissions = loadPermissions();
7152
- return permissions.get(toolName) || null;
7153
- },
7154
- async set(permission) {
7155
- const permissions = loadPermissions();
7156
- permissions.set(permission.toolName, permission);
7157
- savePermissions(permissions);
7158
- },
7159
- async remove(toolName) {
7160
- const permissions = loadPermissions();
7161
- permissions.delete(toolName);
7162
- savePermissions(permissions);
7163
- },
7164
- async getAll() {
7165
- const permissions = loadPermissions();
7166
- return Array.from(permissions.values());
7167
- },
7168
- async clear() {
7169
- if (!storage) return;
7170
- storage.removeItem(getStorageKey());
7171
- }
7172
- };
7173
- }
7174
- function createMemoryStorageAdapter() {
7175
- const permissions = /* @__PURE__ */ new Map();
7176
- return {
7177
- async get(toolName) {
7178
- return permissions.get(toolName) || null;
7179
- },
7180
- async set(permission) {
7181
- permissions.set(permission.toolName, permission);
7182
- },
7183
- async remove(toolName) {
7184
- permissions.delete(toolName);
7185
- },
7186
- async getAll() {
7187
- return Array.from(permissions.values());
7188
- },
7189
- async clear() {
7190
- permissions.clear();
7191
- }
7192
- };
7193
- }
7194
- function createSessionPermissionCache() {
7195
- return /* @__PURE__ */ new Map();
7196
- }
7197
-
7198
- // src/react/internal/ReactChat.ts
7199
- var ReactChat = class extends AbstractChat {
7200
- constructor(config) {
7201
- const reactState = new ReactChatState(config.initialMessages);
7202
- const init = {
7203
- runtimeUrl: config.runtimeUrl,
7204
- systemPrompt: config.systemPrompt,
7205
- llm: config.llm,
7206
- threadId: config.threadId,
7207
- streaming: config.streaming ?? true,
7208
- headers: config.headers,
7209
- initialMessages: config.initialMessages,
7210
- state: reactState,
7211
- callbacks: config.callbacks,
7212
- debug: config.debug
7213
- };
7214
- super(init);
7215
- // ============================================
7216
- // Subscribe (for useSyncExternalStore)
7217
- // ============================================
7218
- /**
7219
- * Subscribe to state changes.
7220
- * Returns an unsubscribe function.
7221
- *
7222
- * @example
7223
- * ```tsx
7224
- * const messages = useSyncExternalStore(
7225
- * chat.subscribe,
7226
- * () => chat.messages
7227
- * );
7228
- * ```
7229
- */
7230
- this.subscribe = (callback) => {
7231
- return this.reactState.subscribe(callback);
7232
- };
7233
- this.reactState = reactState;
7234
- }
7235
- // ============================================
7236
- // Event handling shortcuts
7237
- // ============================================
7238
- /**
7239
- * Subscribe to tool calls events
7240
- */
7241
- onToolCalls(handler) {
7242
- return this.on("toolCalls", handler);
7243
- }
7244
- /**
7245
- * Subscribe to done events
7246
- */
7247
- onDone(handler) {
7248
- return this.on("done", handler);
7249
- }
7250
- /**
7251
- * Subscribe to error events
7252
- */
7253
- onError(handler) {
7254
- return this.on("error", handler);
7255
- }
7256
- // ============================================
7257
- // Branching API — pass-throughs to ReactChatState
7258
- // ============================================
7259
- /**
7260
- * Navigate to a sibling branch (makes it the active path).
7261
- */
7262
- switchBranch(messageId) {
7263
- this.reactState.switchBranch(messageId);
7264
- }
7265
- /**
7266
- * Get branch navigation info for a message.
7267
- * Returns null if the message has no siblings.
7268
- */
7269
- getBranchInfo(messageId) {
7270
- return this.reactState.getBranchInfo(messageId);
7271
- }
7272
- /**
7273
- * Get all messages across all branches (for persistence).
7274
- */
7275
- getAllMessages() {
7276
- return this.reactState.getAllMessages();
7277
- }
7278
- /**
7279
- * Whether any message has siblings (branching has occurred).
7280
- */
7281
- get hasBranches() {
7282
- return this.reactState.hasBranches;
7283
- }
7284
- // ============================================
7285
- // Override dispose to clean up state
7286
- // ============================================
7287
- dispose() {
7288
- super.dispose();
7289
- this.reactState.dispose();
7290
- }
7291
- /**
7292
- * Revive a disposed instance (for React StrictMode compatibility)
7293
- */
7294
- revive() {
7295
- super.revive();
7296
- this.reactState.revive();
7297
- }
7298
- };
7299
- function createReactChat(config) {
7300
- return new ReactChat(config);
7301
- }
7302
- function useChat(config) {
7303
- const chatRef = useRef(null);
7304
- const [input, setInput] = useState("");
7305
- if (chatRef.current !== null && chatRef.current.disposed) {
7306
- chatRef.current.revive();
7307
- }
7308
- if (chatRef.current === null) {
7309
- chatRef.current = createReactChat({
7310
- runtimeUrl: config.runtimeUrl,
7311
- systemPrompt: config.systemPrompt,
7312
- llm: config.llm,
7313
- threadId: config.threadId,
7314
- streaming: config.streaming,
7315
- headers: config.headers,
7316
- initialMessages: config.initialMessages,
7317
- debug: config.debug,
7318
- callbacks: {
7319
- onMessagesChange: config.onMessagesChange,
7320
- onError: config.onError,
7321
- onFinish: config.onFinish,
7322
- onToolCalls: config.onToolCalls
7323
- }
7324
- });
7325
- }
7326
- const messages = useSyncExternalStore(
7327
- chatRef.current.subscribe,
7328
- () => chatRef.current.messages,
7329
- () => chatRef.current.messages
7330
- // Server snapshot
5419
+ return id;
5420
+ },
5421
+ [debugLog]
7331
5422
  );
7332
- const status = useSyncExternalStore(
7333
- chatRef.current.subscribe,
7334
- () => chatRef.current.status,
7335
- () => "ready"
7336
- // Server snapshot
5423
+ const removeContext = useCallback(
5424
+ (id) => {
5425
+ contextTreeRef.current = removeNode(contextTreeRef.current, id);
5426
+ const contextString = printTree(contextTreeRef.current);
5427
+ chatRef.current?.setContext(contextString);
5428
+ setContextChars(contextString.length);
5429
+ debugLog("Context removed:", id);
5430
+ },
5431
+ [debugLog]
7337
5432
  );
7338
- const error = useSyncExternalStore(
7339
- chatRef.current.subscribe,
7340
- () => chatRef.current.error,
7341
- () => void 0
7342
- // Server snapshot
5433
+ const setSystemPrompt = useCallback(
5434
+ (prompt) => {
5435
+ chatRef.current?.setSystemPrompt(prompt);
5436
+ debugLog("System prompt updated via function");
5437
+ },
5438
+ [debugLog]
7343
5439
  );
7344
- const hasBranches = useSyncExternalStore(
7345
- chatRef.current.subscribe,
7346
- () => chatRef.current.hasBranches,
7347
- () => false
5440
+ const setInlineSkills = useCallback(
5441
+ (skills2) => {
5442
+ chatRef.current?.setInlineSkills(skills2);
5443
+ debugLog("Inline skills updated", { count: skills2.length });
5444
+ },
5445
+ [debugLog]
7348
5446
  );
7349
- const isLoading = status === "streaming" || status === "submitted";
7350
5447
  const sendMessage = useCallback(
7351
5448
  async (content, attachments) => {
5449
+ debugLog("Sending message:", content);
5450
+ setAgentIteration(0);
7352
5451
  await chatRef.current?.sendMessage(content, attachments);
7353
- setInput("");
7354
5452
  },
7355
- []
5453
+ [debugLog]
7356
5454
  );
7357
5455
  const stop = useCallback(() => {
7358
5456
  chatRef.current?.stop();
@@ -7366,19 +5464,11 @@ function useChat(config) {
7366
5464
  const regenerate = useCallback(async (messageId) => {
7367
5465
  await chatRef.current?.regenerate(messageId);
7368
5466
  }, []);
7369
- const continueWithToolResults = useCallback(
7370
- async (toolResults) => {
7371
- await chatRef.current?.continueWithToolResults(toolResults);
7372
- },
7373
- []
7374
- );
7375
5467
  const switchBranch = useCallback((messageId) => {
7376
5468
  chatRef.current?.switchBranch(messageId);
7377
5469
  }, []);
7378
5470
  const getBranchInfo = useCallback(
7379
- (messageId) => {
7380
- return chatRef.current?.getBranchInfo(messageId) ?? null;
7381
- },
5471
+ (messageId) => chatRef.current?.getBranchInfo(messageId) ?? null,
7382
5472
  []
7383
5473
  );
7384
5474
  const editMessage = useCallback(
@@ -7386,42 +5476,240 @@ function useChat(config) {
7386
5476
  await chatRef.current?.sendMessage(newContent, void 0, {
7387
5477
  editMessageId: messageId
7388
5478
  });
7389
- setInput("");
7390
5479
  },
7391
5480
  []
7392
5481
  );
5482
+ const getHasBranchesSnapshot = useCallback(
5483
+ () => chatRef.current.hasBranches,
5484
+ []
5485
+ );
5486
+ const hasBranches = useSyncExternalStore(
5487
+ chatRef.current.subscribe,
5488
+ getHasBranchesSnapshot,
5489
+ () => false
5490
+ );
5491
+ const getAllMessages = useCallback(
5492
+ () => chatRef.current?.getAllMessages?.() ?? [],
5493
+ []
5494
+ );
5495
+ useEffect(() => {
5496
+ if (onMessagesChange && messages.length > 0) {
5497
+ const allUIMessages = chatRef.current?.getAllMessages?.() ?? messages;
5498
+ const coreMessages = allUIMessages.map((m) => ({
5499
+ id: m.id,
5500
+ role: m.role,
5501
+ content: m.content,
5502
+ created_at: m.createdAt,
5503
+ tool_calls: m.toolCalls,
5504
+ tool_call_id: m.toolCallId,
5505
+ parent_id: m.parentId,
5506
+ children_ids: m.childrenIds,
5507
+ metadata: {
5508
+ attachments: m.attachments,
5509
+ thinking: m.thinking
5510
+ }
5511
+ }));
5512
+ onMessagesChange(coreMessages);
5513
+ }
5514
+ }, [messages, onMessagesChange]);
5515
+ useEffect(() => {
5516
+ if (error && onError) {
5517
+ onError(error);
5518
+ }
5519
+ }, [error, onError]);
7393
5520
  useEffect(() => {
7394
5521
  return () => {
7395
5522
  chatRef.current?.dispose();
7396
5523
  };
7397
5524
  }, []);
7398
- return {
7399
- messages,
7400
- status,
7401
- error,
7402
- isLoading,
7403
- input,
7404
- setInput,
7405
- sendMessage,
7406
- stop,
7407
- clearMessages,
7408
- setMessages,
7409
- regenerate,
7410
- continueWithToolResults,
7411
- chatRef,
7412
- // Branching
7413
- switchBranch,
7414
- getBranchInfo,
7415
- editMessage,
7416
- hasBranches
7417
- };
5525
+ const contextValue = useMemo(
5526
+ () => ({
5527
+ // Chat state
5528
+ messages,
5529
+ status,
5530
+ error,
5531
+ isLoading,
5532
+ // Chat actions
5533
+ sendMessage,
5534
+ stop,
5535
+ clearMessages,
5536
+ setMessages,
5537
+ regenerate,
5538
+ // Branching
5539
+ switchBranch,
5540
+ getBranchInfo,
5541
+ editMessage,
5542
+ hasBranches,
5543
+ getAllMessages,
5544
+ // Tool execution
5545
+ registerTool,
5546
+ unregisterTool,
5547
+ registeredTools,
5548
+ toolExecutions,
5549
+ pendingApprovals,
5550
+ approveToolExecution,
5551
+ rejectToolExecution,
5552
+ agentIteration,
5553
+ // Actions
5554
+ registerAction,
5555
+ unregisterAction,
5556
+ registeredActions,
5557
+ // AI Context
5558
+ addContext,
5559
+ removeContext,
5560
+ contextChars,
5561
+ contextUsage,
5562
+ // System Prompt
5563
+ setSystemPrompt,
5564
+ // Skills
5565
+ setInlineSkills,
5566
+ // Config
5567
+ threadId,
5568
+ runtimeUrl,
5569
+ toolsConfig,
5570
+ // Headless primitives
5571
+ subscribeToStreamEvents,
5572
+ messageMeta: messageMetaStoreRef.current
5573
+ }),
5574
+ [
5575
+ messages,
5576
+ status,
5577
+ error,
5578
+ isLoading,
5579
+ sendMessage,
5580
+ stop,
5581
+ clearMessages,
5582
+ setMessages,
5583
+ regenerate,
5584
+ switchBranch,
5585
+ getBranchInfo,
5586
+ editMessage,
5587
+ hasBranches,
5588
+ getAllMessages,
5589
+ registerTool,
5590
+ unregisterTool,
5591
+ registeredTools,
5592
+ toolExecutions,
5593
+ pendingApprovals,
5594
+ approveToolExecution,
5595
+ rejectToolExecution,
5596
+ agentIteration,
5597
+ registerAction,
5598
+ unregisterAction,
5599
+ registeredActions,
5600
+ addContext,
5601
+ removeContext,
5602
+ contextChars,
5603
+ contextUsage,
5604
+ setSystemPrompt,
5605
+ setInlineSkills,
5606
+ threadId,
5607
+ runtimeUrl,
5608
+ toolsConfig
5609
+ ]
5610
+ );
5611
+ const messageHistoryContextValue = React2.useMemo(
5612
+ () => ({
5613
+ config: { ...defaultMessageHistoryConfig, ...messageHistory },
5614
+ tokenUsage: {
5615
+ current: 0,
5616
+ max: messageHistory?.maxContextTokens ?? 128e3,
5617
+ percentage: 0,
5618
+ isApproaching: false
5619
+ },
5620
+ compactionState: {
5621
+ rollingSummary: null,
5622
+ lastCompactionAt: null,
5623
+ compactionCount: 0,
5624
+ totalTokensSaved: 0,
5625
+ workingMemory: [],
5626
+ displayMessageCount: 0,
5627
+ llmMessageCount: 0
5628
+ }
5629
+ }),
5630
+ [messageHistory]
5631
+ );
5632
+ return /* @__PURE__ */ jsx(MessageHistoryContext.Provider, { value: messageHistoryContextValue, children: /* @__PURE__ */ jsxs(CopilotContext.Provider, { value: contextValue, children: [
5633
+ mcpServers?.map((config) => /* @__PURE__ */ jsx(MCPConnection, { config }, config.name)),
5634
+ messageHistory?.strategy && messageHistory.strategy !== "none" && /* @__PURE__ */ jsx(MessageHistoryBridge, { chatRef }),
5635
+ skills ? /* @__PURE__ */ jsx(SkillProvider, { skills, children }) : children
5636
+ ] }) });
7418
5637
  }
7419
5638
 
7420
- // src/react/skill/define-skill.ts
7421
- function defineSkill(def) {
7422
- return def;
5639
+ // src/react/hooks/useMCPTools.ts
5640
+ function useMCPTools(config) {
5641
+ const {
5642
+ prefixToolNames = true,
5643
+ autoRegister = true,
5644
+ hidden = false,
5645
+ source = "mcp",
5646
+ ...clientConfig
5647
+ } = config;
5648
+ const { registerTool, unregisterTool } = useCopilot();
5649
+ const registeredToolsRef = useRef([]);
5650
+ const mcpClient = useMCPClient(clientConfig);
5651
+ const toolAdapter = useMemo(
5652
+ () => new MCPToolAdapter(clientConfig.name),
5653
+ [clientConfig.name]
5654
+ );
5655
+ const toolDefinitions = useMemo(() => {
5656
+ if (!mcpClient.isConnected || mcpClient.state.tools.length === 0) {
5657
+ return [];
5658
+ }
5659
+ return mcpClient.state.tools.map(
5660
+ (tool) => toolAdapter.toToolDefinition(tool, {
5661
+ prefix: prefixToolNames,
5662
+ asServerTool: true,
5663
+ // MCP tools execute remotely
5664
+ callTool: mcpClient.callTool,
5665
+ hidden,
5666
+ // Hide from chat UI
5667
+ source
5668
+ // Tool source for UI differentiation
5669
+ })
5670
+ );
5671
+ }, [
5672
+ mcpClient.isConnected,
5673
+ mcpClient.state.tools,
5674
+ mcpClient.callTool,
5675
+ toolAdapter,
5676
+ prefixToolNames,
5677
+ hidden,
5678
+ source
5679
+ ]);
5680
+ useEffect(() => {
5681
+ if (!autoRegister) {
5682
+ return;
5683
+ }
5684
+ for (const toolName of registeredToolsRef.current) {
5685
+ unregisterTool(toolName);
5686
+ }
5687
+ registeredToolsRef.current = [];
5688
+ if (mcpClient.isConnected && toolDefinitions.length > 0) {
5689
+ for (const tool of toolDefinitions) {
5690
+ registerTool(tool);
5691
+ registeredToolsRef.current.push(tool.name);
5692
+ }
5693
+ }
5694
+ return () => {
5695
+ for (const toolName of registeredToolsRef.current) {
5696
+ unregisterTool(toolName);
5697
+ }
5698
+ registeredToolsRef.current = [];
5699
+ };
5700
+ }, [
5701
+ autoRegister,
5702
+ mcpClient.isConnected,
5703
+ toolDefinitions,
5704
+ registerTool,
5705
+ unregisterTool
5706
+ ]);
5707
+ return {
5708
+ ...mcpClient,
5709
+ toolDefinitions
5710
+ };
7423
5711
  }
7424
5712
 
7425
- export { AbstractAgentLoop, AbstractChat, CopilotProvider, MessageHistoryContext, MessageTree, ReactChat, ReactChatState, ReactThreadManager, ReactThreadManagerState, SkillProvider, createMessageIntentHandler, createPermissionStorage, createReactChat, createReactChatState, createReactThreadManager, createReactThreadManagerState, createSessionPermissionCache, createToolIntentHandler, defaultMessageHistoryConfig, defineSkill, formatKnowledgeResultsForAI, initialAgentLoopState, isCompactionMarker, keepToolPairsAtomic, searchKnowledgeBase, toDisplayMessage, toLLMMessage, toLLMMessages, useAIAction, useAIActions, useAIContext, useAIContexts, useAITools, useAgent, useCapabilities, useChat, useContextStats, useCopilot, useDevLogger, useFeatureSupport, useKnowledgeBase, useMCPClient, useMCPTools, useMCPUIIntents, useMessageHistory, useMessageHistoryContext, useSkill, useSkillStatus, useSuggestions, useSupportedMediaTypes, useThreadManager, useTool, useToolExecutor, useToolWithSchema, useTools, useToolsWithSchema };
7426
- //# sourceMappingURL=chunk-PT2TOHG5.js.map
7427
- //# sourceMappingURL=chunk-PT2TOHG5.js.map
5713
+ export { AbstractAgentLoop, AbstractChat, CopilotProvider, MessageHistoryContext, MessageTree, ReactChatState, SkillProvider, createReactChatState, defaultMessageHistoryConfig, initialAgentLoopState, isCompactionMarker, keepToolPairsAtomic, toDisplayMessage, toLLMMessage, toLLMMessages, useAIContext, useAIContexts, useCopilot, useMCPClient, useMCPTools, useMessageHistory, useMessageHistoryContext, useSkillContext, useTool, useTools };
5714
+ //# sourceMappingURL=chunk-DH6EO6NW.js.map
5715
+ //# sourceMappingURL=chunk-DH6EO6NW.js.map