@yourgpt/copilot-sdk 2.1.4 → 2.1.5-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/dist/MessageTree-CoIt_4nB.d.cts +161 -0
  2. package/dist/MessageTree-CzaN9Eul.d.ts +161 -0
  3. package/dist/{ThreadManager-Dkp_eLty.d.ts → ThreadManager-BEAECB7Y.d.ts} +1 -1
  4. package/dist/{ThreadManager-LfFRhr4e.d.cts → ThreadManager-Cw5fwyCN.d.cts} +1 -1
  5. package/dist/{chunk-POZNNKNJ.cjs → chunk-246B6X5D.cjs} +8 -2
  6. package/dist/chunk-246B6X5D.cjs.map +1 -0
  7. package/dist/{chunk-QLH6TSCC.js → chunk-4QXY2PBG.js} +8 -2
  8. package/dist/chunk-4QXY2PBG.js.map +1 -0
  9. package/dist/{chunk-7PKGRYHY.js → chunk-6BMQZIS3.js} +3094 -355
  10. package/dist/chunk-6BMQZIS3.js.map +1 -0
  11. package/dist/{chunk-N6VZ7FOW.cjs → chunk-76RE7AJE.cjs} +3277 -520
  12. package/dist/chunk-76RE7AJE.cjs.map +1 -0
  13. package/dist/chunk-BJYA5NDL.cjs +96 -0
  14. package/dist/chunk-BJYA5NDL.cjs.map +1 -0
  15. package/dist/{chunk-LZMBBGWH.js → chunk-ENFWM3EY.js} +4 -4
  16. package/dist/{chunk-LZMBBGWH.js.map → chunk-ENFWM3EY.js.map} +1 -1
  17. package/dist/{chunk-OQPRIB73.cjs → chunk-I3SQUNTT.cjs} +71 -25
  18. package/dist/chunk-I3SQUNTT.cjs.map +1 -0
  19. package/dist/{chunk-WAPGTQDR.cjs → chunk-JKGFQUHJ.cjs} +10 -10
  20. package/dist/{chunk-WAPGTQDR.cjs.map → chunk-JKGFQUHJ.cjs.map} +1 -1
  21. package/dist/{chunk-XGITAEXU.js → chunk-LLM7AHMO.js} +2 -2
  22. package/dist/{chunk-XGITAEXU.js.map → chunk-LLM7AHMO.js.map} +1 -1
  23. package/dist/{chunk-ASV6JLYG.cjs → chunk-NUXLAZOE.cjs} +2 -2
  24. package/dist/{chunk-ASV6JLYG.cjs.map → chunk-NUXLAZOE.cjs.map} +1 -1
  25. package/dist/{chunk-37KEHUCE.js → chunk-UXJ6LIZB.js} +51 -7
  26. package/dist/chunk-UXJ6LIZB.js.map +1 -0
  27. package/dist/chunk-VNLLW3ZI.js +94 -0
  28. package/dist/chunk-VNLLW3ZI.js.map +1 -0
  29. package/dist/core/index.cjs +99 -91
  30. package/dist/core/index.d.cts +7 -7
  31. package/dist/core/index.d.ts +7 -7
  32. package/dist/core/index.js +5 -5
  33. package/dist/{index-BHkRA0mM.d.cts → index-CiExk87c.d.cts} +1 -1
  34. package/dist/{index-tB0qI8my.d.ts → index-Dwrcf-CP.d.ts} +1 -1
  35. package/dist/mcp/index.d.cts +3 -3
  36. package/dist/mcp/index.d.ts +3 -3
  37. package/dist/react/index.cjs +113 -52
  38. package/dist/react/index.d.cts +673 -77
  39. package/dist/react/index.d.ts +673 -77
  40. package/dist/react/index.js +7 -6
  41. package/dist/server/index.cjs +339 -0
  42. package/dist/server/index.cjs.map +1 -0
  43. package/dist/server/index.d.cts +171 -0
  44. package/dist/server/index.d.ts +171 -0
  45. package/dist/server/index.js +332 -0
  46. package/dist/server/index.js.map +1 -0
  47. package/dist/tools/anthropic/index.cjs +3 -3
  48. package/dist/tools/anthropic/index.d.cts +1 -1
  49. package/dist/tools/anthropic/index.d.ts +1 -1
  50. package/dist/tools/anthropic/index.js +3 -3
  51. package/dist/tools/brave/index.cjs +6 -6
  52. package/dist/tools/brave/index.d.cts +1 -1
  53. package/dist/tools/brave/index.d.ts +1 -1
  54. package/dist/tools/brave/index.js +3 -3
  55. package/dist/tools/exa/index.cjs +6 -6
  56. package/dist/tools/exa/index.d.cts +1 -1
  57. package/dist/tools/exa/index.d.ts +1 -1
  58. package/dist/tools/exa/index.js +3 -3
  59. package/dist/tools/google/index.cjs +6 -6
  60. package/dist/tools/google/index.d.cts +1 -1
  61. package/dist/tools/google/index.d.ts +1 -1
  62. package/dist/tools/google/index.js +3 -3
  63. package/dist/tools/openai/index.cjs +6 -6
  64. package/dist/tools/openai/index.d.cts +1 -1
  65. package/dist/tools/openai/index.d.ts +1 -1
  66. package/dist/tools/openai/index.js +3 -3
  67. package/dist/tools/searxng/index.cjs +6 -6
  68. package/dist/tools/searxng/index.d.cts +1 -1
  69. package/dist/tools/searxng/index.d.ts +1 -1
  70. package/dist/tools/searxng/index.js +3 -3
  71. package/dist/tools/serper/index.cjs +6 -6
  72. package/dist/tools/serper/index.d.cts +1 -1
  73. package/dist/tools/serper/index.d.ts +1 -1
  74. package/dist/tools/serper/index.js +3 -3
  75. package/dist/tools/tavily/index.cjs +6 -6
  76. package/dist/tools/tavily/index.d.cts +1 -1
  77. package/dist/tools/tavily/index.d.ts +1 -1
  78. package/dist/tools/tavily/index.js +3 -3
  79. package/dist/tools/web-search/index.cjs +7 -7
  80. package/dist/tools/web-search/index.d.cts +2 -2
  81. package/dist/tools/web-search/index.d.ts +2 -2
  82. package/dist/tools/web-search/index.js +4 -4
  83. package/dist/{tools-coIcskZ4.d.ts → tools-DHZhF5km.d.cts} +161 -1
  84. package/dist/{tools-coIcskZ4.d.cts → tools-DHZhF5km.d.ts} +161 -1
  85. package/dist/{types-rjaSVmEF.d.ts → types-BTyJu0WD.d.ts} +1 -1
  86. package/dist/types-BckL3hiw.d.cts +93 -0
  87. package/dist/types-BckL3hiw.d.ts +93 -0
  88. package/dist/{types-C8t4Ut8f.d.cts → types-BdX7uPj0.d.cts} +1 -1
  89. package/dist/{types-DG2ya08y.d.ts → types-BeFBBZ5i.d.cts} +64 -1
  90. package/dist/{types-DG2ya08y.d.cts → types-BeFBBZ5i.d.ts} +64 -1
  91. package/dist/ui/index.cjs +468 -163
  92. package/dist/ui/index.cjs.map +1 -1
  93. package/dist/ui/index.d.cts +81 -4
  94. package/dist/ui/index.d.ts +81 -4
  95. package/dist/ui/index.js +416 -112
  96. package/dist/ui/index.js.map +1 -1
  97. package/package.json +6 -1
  98. package/dist/chunk-37KEHUCE.js.map +0 -1
  99. package/dist/chunk-7PKGRYHY.js.map +0 -1
  100. package/dist/chunk-N6VZ7FOW.cjs.map +0 -1
  101. package/dist/chunk-OQPRIB73.cjs.map +0 -1
  102. package/dist/chunk-POZNNKNJ.cjs.map +0 -1
  103. package/dist/chunk-QLH6TSCC.js.map +0 -1
@@ -1,11 +1,14 @@
1
1
  'use strict';
2
2
 
3
- var chunkOQPRIB73_cjs = require('./chunk-OQPRIB73.cjs');
3
+ var chunkI3SQUNTT_cjs = require('./chunk-I3SQUNTT.cjs');
4
4
  var chunkJGPDQDY4_cjs = require('./chunk-JGPDQDY4.cjs');
5
- var react = require('react');
5
+ var chunkBJYA5NDL_cjs = require('./chunk-BJYA5NDL.cjs');
6
+ var React2 = require('react');
6
7
  var jsxRuntime = require('react/jsx-runtime');
7
8
  var z = require('zod');
8
9
 
10
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
+
9
12
  function _interopNamespace(e) {
10
13
  if (e && e.__esModule) return e;
11
14
  var n = Object.create(null);
@@ -24,6 +27,7 @@ function _interopNamespace(e) {
24
27
  return Object.freeze(n);
25
28
  }
26
29
 
30
+ var React2__default = /*#__PURE__*/_interopDefault(React2);
27
31
  var z__namespace = /*#__PURE__*/_interopNamespace(z);
28
32
 
29
33
  // src/chat/types/tool.ts
@@ -141,15 +145,6 @@ function parseSSELine(line) {
141
145
  function isStreamDone(chunk) {
142
146
  return chunk.type === "done" || chunk.type === "error";
143
147
  }
144
- function requiresToolExecution(chunk) {
145
- if (chunk.type === "done" && chunk.requiresAction) {
146
- return true;
147
- }
148
- if (chunk.type === "tool_calls") {
149
- return true;
150
- }
151
- return false;
152
- }
153
148
 
154
149
  // src/chat/functions/stream/processChunk.ts
155
150
  function processStreamChunk(chunk, state) {
@@ -284,13 +279,14 @@ function createStreamState(messageId) {
284
279
  function generateMessageId() {
285
280
  return `msg-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
286
281
  }
287
- function createUserMessage(content, attachments) {
282
+ function createUserMessage(content, attachments, options) {
288
283
  return {
289
284
  id: generateMessageId(),
290
285
  role: "user",
291
286
  content,
292
287
  attachments,
293
- createdAt: /* @__PURE__ */ new Date()
288
+ createdAt: /* @__PURE__ */ new Date(),
289
+ ...options?.parentId !== void 0 ? { parentId: options.parentId } : {}
294
290
  };
295
291
  }
296
292
  function streamStateToMessage(state) {
@@ -329,12 +325,13 @@ function streamStateToMessage(state) {
329
325
  metadata: Object.keys(metadata).length > 0 ? metadata : void 0
330
326
  };
331
327
  }
332
- function createEmptyAssistantMessage(id) {
328
+ function createEmptyAssistantMessage(id, options) {
333
329
  return {
334
330
  id: generateMessageId(),
335
331
  role: "assistant",
336
332
  content: "",
337
- createdAt: /* @__PURE__ */ new Date()
333
+ createdAt: /* @__PURE__ */ new Date(),
334
+ ...options?.parentId !== void 0 ? { parentId: options.parentId } : {}
338
335
  };
339
336
  }
340
337
 
@@ -403,6 +400,7 @@ var HttpTransport = class {
403
400
  tools: request.tools,
404
401
  actions: request.actions,
405
402
  streaming: this.config.streaming,
403
+ __skills: request.__skills,
406
404
  ...resolved.configBody,
407
405
  ...request.body
408
406
  }),
@@ -543,17 +541,443 @@ var HttpTransport = class {
543
541
  }
544
542
  };
545
543
 
546
- // src/chat/classes/AbstractChat.ts
547
- function buildToolResultContentForAI(result, tool2, args) {
548
- if (typeof result === "string") return result;
549
- const typedResult = result;
544
+ // src/chat/optimizations.ts
545
+ var DEFAULT_CHARS_PER_TOKEN = 4;
546
+ var DEFAULT_SAFETY_MARGIN = 1.2;
547
+ var DEFAULT_INPUT_HEADROOM_RATIO = 0.75;
548
+ var DEFAULT_SYSTEM_PROMPT_SHARE = 0.15;
549
+ var DEFAULT_HISTORY_SHARE = 0.5;
550
+ var DEFAULT_TOOL_RESULTS_SHARE = 0.3;
551
+ var DEFAULT_TOOL_DEFINITIONS_SHARE = 0.05;
552
+ var DEFAULT_MAX_TOOL_RESULT_CONTEXT_SHARE = 0.3;
553
+ var DEFAULT_TOOL_RESULT_HARD_MAX_CHARS = 4e5;
554
+ var DEFAULT_TOOL_RESULT_MIN_KEEP_CHARS = 2e3;
555
+ var DEFAULT_TOOL_RESULT_STRATEGY = "head-tail";
556
+ var DEFAULT_RECENT_HISTORY_PRESERVE = 6;
557
+ var TOOL_RESULT_TRUNCATION_NOTICE = "\n\n[tool result truncated to fit prompt budget]";
558
+ var TOOL_RESULT_COMPACTION_NOTICE = "[tool result compacted to preserve context budget]";
559
+ var SYSTEM_PROMPT_TRUNCATION_NOTICE = "\n\n[system prompt truncated to fit prompt budget]";
560
+ var HISTORY_SUMMARY_HEADER = "Conversation summary of earlier context:";
561
+ var HISTORY_SUMMARY_COMPACTION_NOTICE = "\n\n[summary compacted to preserve context continuity]";
562
+ var DEFAULT_SUMMARY_TRIGGER = 12;
563
+ var DEFAULT_SUMMARY_CHUNK_SIZE = 10;
564
+ var DEFAULT_SUMMARY_MAX_CHARS = 1600;
565
+ var SUMMARY_STOP_WORDS = /* @__PURE__ */ new Set([
566
+ "about",
567
+ "after",
568
+ "again",
569
+ "also",
570
+ "because",
571
+ "been",
572
+ "before",
573
+ "being",
574
+ "could",
575
+ "from",
576
+ "have",
577
+ "into",
578
+ "just",
579
+ "more",
580
+ "need",
581
+ "only",
582
+ "over",
583
+ "same",
584
+ "some",
585
+ "than",
586
+ "that",
587
+ "their",
588
+ "them",
589
+ "then",
590
+ "there",
591
+ "these",
592
+ "they",
593
+ "this",
594
+ "those",
595
+ "through",
596
+ "under",
597
+ "very",
598
+ "want",
599
+ "were",
600
+ "what",
601
+ "when",
602
+ "where",
603
+ "which",
604
+ "while",
605
+ "with",
606
+ "would",
607
+ "your"
608
+ ]);
609
+ function clampRatio(value, fallback) {
610
+ if (!Number.isFinite(value)) {
611
+ return fallback;
612
+ }
613
+ return Math.min(1, Math.max(0, value));
614
+ }
615
+ function unique(values) {
616
+ return [...new Set(values)];
617
+ }
618
+ function stringifyContent(value) {
619
+ if (typeof value === "string") {
620
+ return value;
621
+ }
622
+ if (value == null) {
623
+ return "";
624
+ }
625
+ try {
626
+ return JSON.stringify(value);
627
+ } catch {
628
+ return String(value);
629
+ }
630
+ }
631
+ function normalizeWhitespace(text) {
632
+ return text.replace(/\s+/g, " ").trim();
633
+ }
634
+ function abbreviateText(text, maxChars = 220) {
635
+ const normalized = normalizeWhitespace(text);
636
+ if (!normalized) {
637
+ return "";
638
+ }
639
+ if (normalized.length <= maxChars) {
640
+ return normalized;
641
+ }
642
+ return `${normalized.slice(0, Math.max(1, maxChars - 3)).trimEnd()}...`;
643
+ }
644
+ function tokenize(text) {
645
+ return text.toLowerCase().replace(/[^a-z0-9_\s-]/g, " ").split(/\s+/).filter((token) => token.length > 1);
646
+ }
647
+ function estimateTokens(text, charsPerToken = DEFAULT_CHARS_PER_TOKEN) {
648
+ if (!text) {
649
+ return 0;
650
+ }
651
+ return Math.ceil(text.length / Math.max(1, charsPerToken));
652
+ }
653
+ function estimateMessageTokens(message, charsPerToken = DEFAULT_CHARS_PER_TOKEN) {
654
+ const content = typeof message.content === "string" ? message.content : JSON.stringify(message.content ?? "");
655
+ const toolCalls = message.tool_calls ? JSON.stringify(message.tool_calls) : "";
656
+ const attachments = message.attachments ? JSON.stringify(message.attachments) : "";
657
+ return estimateTokens(
658
+ `${message.role}
659
+ ${content}
660
+ ${toolCalls}
661
+ ${attachments}`,
662
+ charsPerToken
663
+ );
664
+ }
665
+ function estimateToolTokens(tool2, charsPerToken = DEFAULT_CHARS_PER_TOKEN) {
666
+ return estimateTokens(JSON.stringify(tool2), charsPerToken);
667
+ }
668
+ function buildToolQuery(messages) {
669
+ return messages.filter(
670
+ (message) => message.role === "user" || message.role === "assistant"
671
+ ).slice(-3).map((message) => message.content).filter(Boolean).join(" ");
672
+ }
673
+ function matchesSelector(tool2, selector, activeProfile) {
674
+ const normalized = selector.trim().toLowerCase();
675
+ if (!normalized) {
676
+ return false;
677
+ }
678
+ if (normalized === "*" || normalized === "all") {
679
+ return true;
680
+ }
681
+ if (normalized === tool2.name.toLowerCase()) {
682
+ return true;
683
+ }
684
+ if (normalized.startsWith("group:")) {
685
+ return (tool2.group ?? "").toLowerCase() === normalized.slice(6);
686
+ }
687
+ if (normalized.startsWith("category:")) {
688
+ return (tool2.category ?? "").toLowerCase() === normalized.slice(9);
689
+ }
690
+ if (normalized.startsWith("profile:")) {
691
+ return (tool2.profiles ?? []).map((value) => value.toLowerCase()).includes(normalized.slice(8));
692
+ }
693
+ if (activeProfile && normalized === activeProfile.toLowerCase()) {
694
+ return (tool2.profiles ?? []).map((value) => value.toLowerCase()).includes(normalized);
695
+ }
696
+ return false;
697
+ }
698
+ function scoreTool(tool2, queryTokens, activeProfile) {
699
+ const haystack = [
700
+ tool2.name,
701
+ tool2.description,
702
+ tool2.category,
703
+ tool2.group,
704
+ ...tool2.profiles ?? [],
705
+ ...tool2.searchKeywords ?? []
706
+ ].filter(Boolean).join(" ").toLowerCase();
707
+ let score = tool2.deferLoading ? 0 : 2;
708
+ if (activeProfile && tool2.profiles?.includes(activeProfile)) {
709
+ score += 2;
710
+ }
711
+ for (const token of queryTokens) {
712
+ if (tool2.name.toLowerCase() === token) {
713
+ score += 6;
714
+ } else if (tool2.name.toLowerCase().includes(token)) {
715
+ score += 4;
716
+ } else if (haystack.includes(token)) {
717
+ score += 2;
718
+ }
719
+ }
720
+ return score;
721
+ }
722
+ function truncateText(text, maxChars, strategy, notice = TOOL_RESULT_TRUNCATION_NOTICE) {
723
+ if (text.length <= maxChars) {
724
+ return text;
725
+ }
726
+ const bodyBudget = Math.max(1, maxChars - notice.length);
727
+ if (strategy === "head") {
728
+ return text.slice(0, bodyBudget) + notice;
729
+ }
730
+ if (strategy === "head-tail" || strategy === "smart") {
731
+ const tailLooksImportant = strategy === "smart" ? /\b(error|exception|failed|traceback|summary|result|done|complete)\b/i.test(
732
+ text.slice(-2e3)
733
+ ) : true;
734
+ if (tailLooksImportant && bodyBudget > 32) {
735
+ const tailBudget = Math.min(Math.floor(bodyBudget * 0.3), 4e3);
736
+ const headBudget = Math.max(1, bodyBudget - tailBudget - 32);
737
+ return text.slice(0, headBudget) + "\n\n[... omitted ...]\n\n" + text.slice(-tailBudget) + notice;
738
+ }
739
+ }
740
+ return text.slice(0, bodyBudget) + notice;
741
+ }
742
+ function isHistorySummaryMessage(message) {
743
+ return message.role === "system" && typeof message.content === "string" && message.content.startsWith(HISTORY_SUMMARY_HEADER);
744
+ }
745
+ function collectTopKeywords(messages) {
746
+ const counts = /* @__PURE__ */ new Map();
747
+ for (const message of messages) {
748
+ if (message.role !== "user") {
749
+ continue;
750
+ }
751
+ for (const token of tokenize(stringifyContent(message.content))) {
752
+ if (token.length < 3 || SUMMARY_STOP_WORDS.has(token)) {
753
+ continue;
754
+ }
755
+ counts.set(token, (counts.get(token) ?? 0) + 1);
756
+ }
757
+ }
758
+ return [...counts.entries()].sort((left, right) => {
759
+ const countDiff = right[1] - left[1];
760
+ if (countDiff !== 0) {
761
+ return countDiff;
762
+ }
763
+ return left[0].localeCompare(right[0]);
764
+ }).slice(0, 5).map(([token]) => token);
765
+ }
766
+ function collectToolCallNames(messages) {
767
+ const toolCallNames = /* @__PURE__ */ new Map();
768
+ for (const message of messages) {
769
+ if (message.role !== "assistant" || !message.tool_calls?.length) {
770
+ continue;
771
+ }
772
+ for (const toolCall of message.tool_calls) {
773
+ const parsedToolCall = toolCall;
774
+ if (parsedToolCall.id && parsedToolCall.function?.name) {
775
+ toolCallNames.set(parsedToolCall.id, parsedToolCall.function.name);
776
+ }
777
+ }
778
+ }
779
+ return toolCallNames;
780
+ }
781
+ function compressSummaryContent(content, maxChars, fallbackBehavior) {
782
+ if (content.length <= maxChars) {
783
+ return content;
784
+ }
785
+ if (fallbackBehavior === "error") {
786
+ throw new Error("History summary exceeded configured continuity budget.");
787
+ }
788
+ if (fallbackBehavior === "statistical") {
789
+ const lines = content.split("\n");
790
+ const retained = lines.filter(
791
+ (line) => /^(Conversation summary|Stats:|- Messages compacted:|- User turns compacted:|- Assistant turns compacted:|- Tool results compacted:|- Latest user request before preserved window:|- Latest assistant response before preserved window:)/.test(
792
+ line
793
+ )
794
+ );
795
+ const statistical = retained.join("\n");
796
+ if (statistical.length <= maxChars) {
797
+ return statistical;
798
+ }
799
+ }
800
+ return truncateText(
801
+ content,
802
+ maxChars,
803
+ "head",
804
+ HISTORY_SUMMARY_COMPACTION_NOTICE
805
+ );
806
+ }
807
+ function buildHistorySummary(messages, summarization, maxChars = DEFAULT_SUMMARY_MAX_CHARS) {
808
+ if (messages.length === 0) {
809
+ return null;
810
+ }
811
+ if (summarization?.enabled === false) {
812
+ return null;
813
+ }
814
+ const previousSummaries = messages.filter(isHistorySummaryMessage);
815
+ const rawMessages = messages.filter(
816
+ (message) => !isHistorySummaryMessage(message)
817
+ );
818
+ const focusWindowSize = Math.max(
819
+ 1,
820
+ summarization?.chunkSize ?? DEFAULT_SUMMARY_CHUNK_SIZE
821
+ );
822
+ const detailedThreshold = Math.max(
823
+ 1,
824
+ summarization?.triggerAt ?? DEFAULT_SUMMARY_TRIGGER
825
+ );
826
+ const focusMessages = rawMessages.slice(-focusWindowSize);
827
+ const userMessages = rawMessages.filter((message) => message.role === "user");
828
+ const assistantMessages = rawMessages.filter(
829
+ (message) => message.role === "assistant"
830
+ );
831
+ const toolMessages = rawMessages.filter((message) => message.role === "tool");
832
+ const recentUser = abbreviateText(stringifyContent(userMessages.at(-1)?.content), 240) || "n/a";
833
+ const recentAssistant = abbreviateText(stringifyContent(assistantMessages.at(-1)?.content), 240) || "n/a";
834
+ const recentUserGoals = userMessages.slice(-3).map((message) => abbreviateText(stringifyContent(message.content), 180)).filter(Boolean);
835
+ const recentAssistantNotes = assistantMessages.map((message) => abbreviateText(stringifyContent(message.content), 180)).filter(Boolean).slice(-2);
836
+ const toolCallNames = collectToolCallNames(rawMessages);
837
+ const toolActivity = unique([
838
+ ...focusMessages.filter((message) => message.role === "assistant").flatMap(
839
+ (message) => (message.tool_calls ?? []).map((toolCall) => {
840
+ const parsedToolCall = toolCall;
841
+ return parsedToolCall.function?.name;
842
+ }).filter((name) => Boolean(name))
843
+ ),
844
+ ...toolMessages.slice(-2).map((message) => {
845
+ const toolName = message.tool_call_id ? toolCallNames.get(message.tool_call_id) : void 0;
846
+ const snippet = abbreviateText(stringifyContent(message.content), 120);
847
+ return toolName && snippet ? `${toolName}: ${snippet}` : toolName ?? snippet;
848
+ }).filter(Boolean)
849
+ ]).slice(-4);
850
+ const priorSummaryCarryForward = previousSummaries.map(
851
+ (message) => abbreviateText(
852
+ stringifyContent(message.content).replace(
853
+ `${HISTORY_SUMMARY_HEADER}
854
+ `,
855
+ ""
856
+ ),
857
+ 180
858
+ )
859
+ ).filter(Boolean).slice(-2);
860
+ const topKeywords = rawMessages.length >= detailedThreshold ? collectTopKeywords(
861
+ focusMessages.length > 0 ? focusMessages : rawMessages
862
+ ) : [];
863
+ const lines = [
864
+ HISTORY_SUMMARY_HEADER,
865
+ "Stats:",
866
+ `- Messages compacted: ${messages.length}`,
867
+ `- User turns compacted: ${userMessages.length}`,
868
+ `- Assistant turns compacted: ${assistantMessages.length}`,
869
+ `- Tool results compacted: ${toolMessages.length}`
870
+ ];
871
+ if (previousSummaries.length > 0) {
872
+ lines.push(`- Previous summaries merged: ${previousSummaries.length}`);
873
+ }
874
+ if (rawMessages.length > focusMessages.length) {
875
+ lines.push(
876
+ `- Older compacted messages outside the detailed window: ${rawMessages.length - focusMessages.length}`
877
+ );
878
+ }
879
+ if (topKeywords.length > 0) {
880
+ lines.push(`- Recurring user topics: ${topKeywords.join(", ")}`);
881
+ }
882
+ if (recentUser !== "n/a") {
883
+ lines.push(`- Latest user request before preserved window: ${recentUser}`);
884
+ }
885
+ if (recentAssistant !== "n/a") {
886
+ lines.push(
887
+ `- Latest assistant response before preserved window: ${recentAssistant}`
888
+ );
889
+ }
890
+ if (recentUserGoals.length > 0) {
891
+ lines.push("Carry forward user goals:");
892
+ for (const goal of recentUserGoals) {
893
+ lines.push(`- ${goal}`);
894
+ }
895
+ }
896
+ if (recentAssistantNotes.length > 0) {
897
+ lines.push("Carry forward assistant commitments:");
898
+ for (const note of recentAssistantNotes) {
899
+ lines.push(`- ${note}`);
900
+ }
901
+ }
902
+ if (toolActivity.length > 0) {
903
+ lines.push("Recent tool activity:");
904
+ for (const item of toolActivity) {
905
+ lines.push(`- ${item}`);
906
+ }
907
+ }
908
+ if (priorSummaryCarryForward.length > 0) {
909
+ lines.push("Earlier carried-forward context:");
910
+ for (const item of priorSummaryCarryForward) {
911
+ lines.push(`- ${item}`);
912
+ }
913
+ }
914
+ const content = compressSummaryContent(
915
+ lines.join("\n"),
916
+ maxChars,
917
+ summarization?.fallbackBehavior
918
+ );
919
+ return {
920
+ role: "system",
921
+ content
922
+ };
923
+ }
924
+ function buildToolDefinitions(selectedTools) {
925
+ if (selectedTools.length === 0) {
926
+ return void 0;
927
+ }
928
+ return selectedTools.map((tool2) => ({
929
+ name: tool2.name,
930
+ description: tool2.description,
931
+ category: tool2.category,
932
+ group: tool2.group,
933
+ deferLoading: tool2.deferLoading,
934
+ profiles: tool2.profiles,
935
+ searchKeywords: tool2.searchKeywords,
936
+ inputSchema: tool2.inputSchema
937
+ }));
938
+ }
939
+ function resolveTruncationConfig(params) {
940
+ const charsPerToken = params.config?.contextManagement?.tokenEstimation?.charsPerToken ?? DEFAULT_CHARS_PER_TOKEN;
941
+ const contextWindowTokens = params.config?.contextBudget?.budget?.contextWindowTokens;
942
+ const globalConfig = params.config?.toolResultConfig?.truncation;
943
+ const perToolConfig = params.tool?.resultConfig?.truncation;
944
+ const merged = { ...globalConfig, ...perToolConfig };
945
+ const hardMaxChars = merged.hardMaxChars ?? (contextWindowTokens ? Math.floor(
946
+ contextWindowTokens * clampRatio(
947
+ merged.maxContextShare,
948
+ DEFAULT_MAX_TOOL_RESULT_CONTEXT_SHARE
949
+ ) * charsPerToken
950
+ ) : DEFAULT_TOOL_RESULT_HARD_MAX_CHARS);
951
+ return {
952
+ enabled: merged.enabled ?? true,
953
+ maxContextShare: clampRatio(
954
+ merged.maxContextShare,
955
+ DEFAULT_MAX_TOOL_RESULT_CONTEXT_SHARE
956
+ ),
957
+ hardMaxChars: Math.max(1, hardMaxChars),
958
+ minKeepChars: Math.max(
959
+ 256,
960
+ merged.minKeepChars ?? DEFAULT_TOOL_RESULT_MIN_KEEP_CHARS
961
+ ),
962
+ strategy: merged.strategy ?? DEFAULT_TOOL_RESULT_STRATEGY,
963
+ preserveErrors: merged.preserveErrors ?? true
964
+ };
965
+ }
966
+ function buildToolResultContent(result, tool2, args) {
967
+ if (typeof result === "string") {
968
+ return result;
969
+ }
970
+ const typedResult = result ?? null;
550
971
  const responseMode = typedResult?._aiResponseMode ?? tool2?.aiResponseMode ?? "full";
551
972
  if (typedResult?._aiContent) {
552
973
  return JSON.stringify(typedResult._aiContent);
553
974
  }
554
975
  let aiContext = typedResult?._aiContext;
555
976
  if (!aiContext && tool2?.aiContext) {
556
- aiContext = typeof tool2.aiContext === "function" ? tool2.aiContext(typedResult, args ?? {}) : tool2.aiContext;
977
+ aiContext = typeof tool2.aiContext === "function" ? tool2.aiContext(
978
+ typedResult ?? { success: true },
979
+ args ?? {}
980
+ ) : tool2.aiContext;
557
981
  }
558
982
  switch (responseMode) {
559
983
  case "none":
@@ -561,7 +985,7 @@ function buildToolResultContentForAI(result, tool2, args) {
561
985
  case "brief":
562
986
  return aiContext ?? "[Tool executed successfully]";
563
987
  case "full":
564
- default:
988
+ default: {
565
989
  if (aiContext) {
566
990
  const {
567
991
  _aiResponseMode,
@@ -579,18 +1003,526 @@ Full data: ${JSON.stringify(dataOnly)}`;
579
1003
  return JSON.stringify(dataOnly);
580
1004
  }
581
1005
  return JSON.stringify(result);
1006
+ }
1007
+ }
1008
+ }
1009
+ function buildToolResultContentForPrompt(result, tool2, args, config) {
1010
+ const text = buildToolResultContent(result, tool2, args);
1011
+ const truncation = resolveTruncationConfig({ tool: tool2, config });
1012
+ if (!truncation.enabled) {
1013
+ return text;
1014
+ }
1015
+ if (truncation.preserveErrors && typeof result === "object" && result !== null && "error" in result && typeof result.error === "string") {
1016
+ return text;
1017
+ }
1018
+ const maxChars = Math.max(truncation.minKeepChars, truncation.hardMaxChars);
1019
+ return truncateText(text, maxChars, truncation.strategy);
1020
+ }
1021
+ function sliceHistoryToMaxMessages(params) {
1022
+ const { historyMessages, maxMessages, pruneStrategy, summarization } = params;
1023
+ if (!maxMessages || historyMessages.length <= maxMessages) {
1024
+ return historyMessages;
1025
+ }
1026
+ const dropped = historyMessages.slice(
1027
+ 0,
1028
+ historyMessages.length - maxMessages
1029
+ );
1030
+ const kept = historyMessages.slice(-maxMessages);
1031
+ if (pruneStrategy === "summarize") {
1032
+ const summary = buildHistorySummary(dropped, summarization);
1033
+ return summary ? [summary, ...kept] : kept;
1034
+ }
1035
+ return kept;
1036
+ }
1037
+ function compactHistoryToTokenBudget(params) {
1038
+ const {
1039
+ maxTokens,
1040
+ preserveRecent,
1041
+ charsPerToken,
1042
+ pruneStrategy,
1043
+ summarization
1044
+ } = params;
1045
+ let historyMessages = params.historyMessages;
1046
+ if (!maxTokens) {
1047
+ return historyMessages;
1048
+ }
1049
+ const getHistoryTokens = () => historyMessages.reduce(
1050
+ (sum, message) => sum + estimateMessageTokens(message, charsPerToken),
1051
+ 0
1052
+ );
1053
+ while (historyMessages.length > 1 && getHistoryTokens() > maxTokens) {
1054
+ const prunableCount = Math.max(0, historyMessages.length - preserveRecent);
1055
+ if (prunableCount <= 0) {
1056
+ const firstMessage = historyMessages[0];
1057
+ if (isHistorySummaryMessage(firstMessage) && typeof firstMessage.content === "string") {
1058
+ const compactedSummary = compressSummaryContent(
1059
+ firstMessage.content,
1060
+ Math.max(400, Math.floor(maxTokens * charsPerToken * 0.25)),
1061
+ summarization?.fallbackBehavior
1062
+ );
1063
+ if (compactedSummary !== firstMessage.content) {
1064
+ historyMessages = [
1065
+ { ...firstMessage, content: compactedSummary },
1066
+ ...historyMessages.slice(1)
1067
+ ];
1068
+ continue;
1069
+ }
1070
+ }
1071
+ historyMessages = historyMessages.slice(1);
1072
+ continue;
1073
+ }
1074
+ const pruned = historyMessages.slice(0, prunableCount);
1075
+ const kept = historyMessages.slice(prunableCount);
1076
+ if (pruneStrategy === "summarize") {
1077
+ const summary = buildHistorySummary(
1078
+ pruned,
1079
+ summarization,
1080
+ Math.max(500, Math.floor(maxTokens * charsPerToken * 0.35))
1081
+ );
1082
+ historyMessages = summary ? [summary, ...kept] : kept;
1083
+ } else {
1084
+ historyMessages = kept;
1085
+ }
1086
+ }
1087
+ return historyMessages;
1088
+ }
1089
+ function compactToolResultsToBudget(params) {
1090
+ let toolResultMessages = params.toolResultMessages;
1091
+ if (!params.maxTokens) {
1092
+ return toolResultMessages;
1093
+ }
1094
+ const getToolResultTokens = () => toolResultMessages.reduce(
1095
+ (sum, message) => sum + estimateMessageTokens(message, params.charsPerToken),
1096
+ 0
1097
+ );
1098
+ while (toolResultMessages.length > 0 && getToolResultTokens() > params.maxTokens) {
1099
+ const index = toolResultMessages.findIndex(
1100
+ (message) => message.content !== TOOL_RESULT_COMPACTION_NOTICE
1101
+ );
1102
+ if (index === -1) {
1103
+ break;
1104
+ }
1105
+ toolResultMessages = toolResultMessages.map(
1106
+ (message, currentIndex) => currentIndex === index ? { ...message, content: TOOL_RESULT_COMPACTION_NOTICE } : message
1107
+ );
1108
+ }
1109
+ return toolResultMessages;
1110
+ }
1111
+ function fitToolsToBudget(params) {
1112
+ let tools = params.tools;
1113
+ if (!tools?.length || !params.maxTokens) {
1114
+ return tools;
582
1115
  }
1116
+ const getToolTokens = () => tools.reduce(
1117
+ (sum, tool2) => sum + estimateToolTokens(tool2, params.charsPerToken),
1118
+ 0
1119
+ );
1120
+ while (tools.length > 0 && getToolTokens() > params.maxTokens) {
1121
+ tools = tools.slice(0, -1);
1122
+ }
1123
+ return tools;
1124
+ }
1125
+ function truncateSystemPromptToBudget(params) {
1126
+ const { systemPrompt, maxTokens, charsPerToken } = params;
1127
+ if (!systemPrompt || !maxTokens) {
1128
+ return systemPrompt;
1129
+ }
1130
+ const maxChars = maxTokens * charsPerToken;
1131
+ if (systemPrompt.length <= maxChars) {
1132
+ return systemPrompt;
1133
+ }
1134
+ return truncateText(
1135
+ systemPrompt,
1136
+ maxChars,
1137
+ "head",
1138
+ SYSTEM_PROMPT_TRUNCATION_NOTICE
1139
+ );
1140
+ }
1141
+ function calculateBuckets(params) {
1142
+ const systemPromptTokens = estimateTokens(
1143
+ params.systemPrompt ?? "",
1144
+ params.charsPerToken
1145
+ );
1146
+ const historyTokens = params.historyMessages.reduce(
1147
+ (sum, message) => sum + estimateMessageTokens(message, params.charsPerToken),
1148
+ 0
1149
+ );
1150
+ const toolResultsTokens = params.toolResultMessages.reduce(
1151
+ (sum, message) => sum + estimateMessageTokens(message, params.charsPerToken),
1152
+ 0
1153
+ );
1154
+ const toolDefinitionTokens = (params.requestTools ?? []).reduce(
1155
+ (sum, tool2) => sum + estimateToolTokens(tool2, params.charsPerToken),
1156
+ 0
1157
+ );
1158
+ const total = systemPromptTokens + historyTokens + toolResultsTokens + toolDefinitionTokens;
1159
+ const budget = Number.isFinite(params.availableBudget) ? params.availableBudget : total;
1160
+ const toPart = (tokens) => ({
1161
+ tokens,
1162
+ percent: budget > 0 ? Number((tokens / budget * 100).toFixed(2)) : 0
1163
+ });
1164
+ return {
1165
+ total: toPart(total),
1166
+ breakdown: {
1167
+ systemPrompt: toPart(systemPromptTokens),
1168
+ history: toPart(historyTokens),
1169
+ toolResults: toPart(toolResultsTokens),
1170
+ tools: toPart(toolDefinitionTokens)
1171
+ },
1172
+ budget: {
1173
+ available: budget,
1174
+ remaining: Math.max(0, budget - total)
1175
+ },
1176
+ warnings: unique(params.warnings)
1177
+ };
1178
+ }
1179
+ function mergeBucketsInOriginalOrder(params) {
1180
+ const historyQueue = [...params.historyMessages];
1181
+ const toolQueue = [...params.toolResultMessages];
1182
+ return params.transformedMessages.flatMap((message) => {
1183
+ if (message.role === "tool") {
1184
+ const nextTool = toolQueue.shift();
1185
+ return nextTool ? [nextTool] : [];
1186
+ }
1187
+ const nextHistory = historyQueue.shift();
1188
+ return nextHistory ? [nextHistory] : [];
1189
+ });
583
1190
  }
1191
+ var ChatContextOptimizer = class {
1192
+ constructor(config) {
1193
+ this.lastContextUsage = null;
1194
+ this.config = config;
1195
+ this.activeProfile = config?.toolProfiles?.defaultProfile;
1196
+ }
1197
+ updateConfig(config) {
1198
+ this.config = config;
1199
+ if (!this.activeProfile) {
1200
+ this.activeProfile = config?.toolProfiles?.defaultProfile;
1201
+ }
1202
+ }
1203
+ setActiveProfile(profile) {
1204
+ this.activeProfile = profile?.trim() || void 0;
1205
+ }
1206
+ getContextUsage() {
1207
+ return this.lastContextUsage;
1208
+ }
1209
+ prepare(params) {
1210
+ const charsPerToken = this.config?.contextManagement?.tokenEstimation?.charsPerToken ?? DEFAULT_CHARS_PER_TOKEN;
1211
+ const safetyMargin = this.config?.contextManagement?.tokenEstimation?.safetyMargin ?? DEFAULT_SAFETY_MARGIN;
1212
+ const warnings = [];
1213
+ const contextManagement = this.config?.contextManagement;
1214
+ const contextBudget = this.config?.contextBudget;
1215
+ const allTools = params.tools ?? [];
1216
+ const selectedTools = this.selectTools(allTools, params.messages);
1217
+ const transformedMessages = this.transformMessages(
1218
+ params.messages,
1219
+ allTools
1220
+ );
1221
+ const preserveRecent = contextManagement?.summarization?.preserveRecent ?? DEFAULT_RECENT_HISTORY_PRESERVE;
1222
+ let buckets = {
1223
+ systemPrompt: params.systemPrompt,
1224
+ transformedMessages,
1225
+ historyMessages: transformedMessages.filter(
1226
+ (message) => message.role !== "tool"
1227
+ ),
1228
+ toolResultMessages: transformedMessages.filter(
1229
+ (message) => message.role === "tool"
1230
+ ),
1231
+ requestTools: buildToolDefinitions(selectedTools)
1232
+ };
1233
+ if (contextManagement?.enabled) {
1234
+ buckets.historyMessages = sliceHistoryToMaxMessages({
1235
+ historyMessages: buckets.historyMessages,
1236
+ maxMessages: contextManagement.history?.maxMessages,
1237
+ pruneStrategy: contextManagement.history?.pruneStrategy,
1238
+ summarization: contextManagement?.summarization
1239
+ });
1240
+ }
1241
+ const budgetConfig = contextBudget?.budget;
1242
+ const contextWindowTokens = budgetConfig?.contextWindowTokens;
1243
+ const inputHeadroomRatio = clampRatio(
1244
+ budgetConfig?.inputHeadroomRatio,
1245
+ DEFAULT_INPUT_HEADROOM_RATIO
1246
+ );
1247
+ const availableBudget = contextWindowTokens ? Math.max(1, Math.floor(contextWindowTokens * inputHeadroomRatio)) : Number.POSITIVE_INFINITY;
1248
+ const sharedBudget = Number.isFinite(availableBudget) ? availableBudget : void 0;
1249
+ const systemPromptBudget = sharedBudget ? Math.max(
1250
+ 1,
1251
+ Math.floor(
1252
+ sharedBudget * clampRatio(
1253
+ budgetConfig?.systemPromptShare,
1254
+ DEFAULT_SYSTEM_PROMPT_SHARE
1255
+ )
1256
+ )
1257
+ ) : void 0;
1258
+ const historyBudgetByShare = sharedBudget ? Math.max(
1259
+ 1,
1260
+ Math.floor(
1261
+ sharedBudget * clampRatio(budgetConfig?.historyShare, DEFAULT_HISTORY_SHARE)
1262
+ )
1263
+ ) : void 0;
1264
+ const historyBudgetByConfig = contextManagement?.enabled && contextManagement.history?.maxTokens ? Math.floor(contextManagement.history.maxTokens / safetyMargin) : void 0;
1265
+ const historyBudget = historyBudgetByShare && historyBudgetByConfig ? Math.min(historyBudgetByShare, historyBudgetByConfig) : historyBudgetByShare ?? historyBudgetByConfig;
1266
+ const toolResultsBudget = sharedBudget ? Math.max(
1267
+ 1,
1268
+ Math.floor(
1269
+ sharedBudget * clampRatio(
1270
+ budgetConfig?.toolResultsShare,
1271
+ DEFAULT_TOOL_RESULTS_SHARE
1272
+ )
1273
+ )
1274
+ ) : void 0;
1275
+ const toolDefinitionsBudget = sharedBudget ? Math.max(
1276
+ 1,
1277
+ Math.floor(
1278
+ sharedBudget * clampRatio(
1279
+ budgetConfig?.toolDefinitionsShare,
1280
+ DEFAULT_TOOL_DEFINITIONS_SHARE
1281
+ )
1282
+ )
1283
+ ) : void 0;
1284
+ if (contextBudget?.enabled) {
1285
+ buckets.systemPrompt = truncateSystemPromptToBudget({
1286
+ systemPrompt: buckets.systemPrompt,
1287
+ maxTokens: systemPromptBudget,
1288
+ charsPerToken
1289
+ });
1290
+ }
1291
+ buckets.historyMessages = compactHistoryToTokenBudget({
1292
+ historyMessages: buckets.historyMessages,
1293
+ maxTokens: historyBudget,
1294
+ preserveRecent,
1295
+ charsPerToken,
1296
+ pruneStrategy: contextManagement?.history?.pruneStrategy,
1297
+ summarization: contextManagement?.summarization
1298
+ });
1299
+ buckets.toolResultMessages = compactToolResultsToBudget({
1300
+ toolResultMessages: buckets.toolResultMessages,
1301
+ maxTokens: toolResultsBudget,
1302
+ charsPerToken
1303
+ });
1304
+ buckets.requestTools = fitToolsToBudget({
1305
+ tools: buckets.requestTools,
1306
+ maxTokens: toolDefinitionsBudget,
1307
+ charsPerToken
1308
+ });
1309
+ let usage = calculateBuckets({
1310
+ ...buckets,
1311
+ charsPerToken,
1312
+ availableBudget,
1313
+ warnings
1314
+ });
1315
+ if (Number.isFinite(availableBudget) && usage.total.tokens > availableBudget) {
1316
+ buckets.toolResultMessages = compactToolResultsToBudget({
1317
+ toolResultMessages: buckets.toolResultMessages,
1318
+ maxTokens: Math.max(
1319
+ 1,
1320
+ usage.breakdown.toolResults.tokens - usage.total.tokens + availableBudget
1321
+ ),
1322
+ charsPerToken
1323
+ });
1324
+ usage = calculateBuckets({
1325
+ ...buckets,
1326
+ charsPerToken,
1327
+ availableBudget,
1328
+ warnings
1329
+ });
1330
+ if (usage.total.tokens > availableBudget) {
1331
+ const overflow = usage.total.tokens - availableBudget;
1332
+ buckets.historyMessages = compactHistoryToTokenBudget({
1333
+ historyMessages: buckets.historyMessages,
1334
+ maxTokens: Math.max(1, usage.breakdown.history.tokens - overflow),
1335
+ preserveRecent,
1336
+ charsPerToken,
1337
+ pruneStrategy: contextManagement?.history?.pruneStrategy
1338
+ });
1339
+ usage = calculateBuckets({
1340
+ ...buckets,
1341
+ charsPerToken,
1342
+ availableBudget,
1343
+ warnings
1344
+ });
1345
+ }
1346
+ if (usage.total.tokens > availableBudget) {
1347
+ buckets.requestTools = fitToolsToBudget({
1348
+ tools: buckets.requestTools,
1349
+ maxTokens: Math.max(
1350
+ 1,
1351
+ usage.breakdown.tools.tokens - (usage.total.tokens - availableBudget)
1352
+ ),
1353
+ charsPerToken
1354
+ });
1355
+ usage = calculateBuckets({
1356
+ ...buckets,
1357
+ charsPerToken,
1358
+ availableBudget,
1359
+ warnings
1360
+ });
1361
+ }
1362
+ }
1363
+ if (Number.isFinite(availableBudget) && usage.total.tokens > availableBudget) {
1364
+ warnings.push(
1365
+ `Prompt budget exceeded: using ${usage.total.tokens} tokens of ${availableBudget}.`
1366
+ );
1367
+ usage = {
1368
+ ...usage,
1369
+ warnings: unique(warnings)
1370
+ };
1371
+ if (contextBudget?.enforcement?.mode === "error") {
1372
+ throw new Error(warnings[warnings.length - 1]);
1373
+ }
1374
+ contextBudget?.enforcement?.onBudgetExceeded?.(usage);
1375
+ } else {
1376
+ usage = {
1377
+ ...usage,
1378
+ warnings: unique(warnings)
1379
+ };
1380
+ }
1381
+ contextBudget?.monitoring?.onUsageUpdate?.(usage);
1382
+ this.lastContextUsage = usage;
1383
+ return {
1384
+ messages: mergeBucketsInOriginalOrder(buckets),
1385
+ tools: buckets.requestTools,
1386
+ contextUsage: usage,
1387
+ warnings: usage.warnings
1388
+ };
1389
+ }
1390
+ selectTools(tools, messages) {
1391
+ if (!tools.length) {
1392
+ return [];
1393
+ }
1394
+ const available = tools.filter((tool2) => tool2.available !== false);
1395
+ const profileConfig = this.config?.toolProfiles;
1396
+ if (!profileConfig?.enabled) {
1397
+ return available;
1398
+ }
1399
+ const activeProfile = this.activeProfile ?? profileConfig.defaultProfile;
1400
+ const includeUnprofiled = profileConfig.includeUnprofiled ?? true;
1401
+ const profile = activeProfile ? profileConfig.profiles?.[activeProfile] : void 0;
1402
+ let filtered = available;
1403
+ if (profile?.include?.length) {
1404
+ filtered = filtered.filter(
1405
+ (tool2) => profile.include.some(
1406
+ (selector) => matchesSelector(tool2, selector, activeProfile)
1407
+ ) || !!activeProfile && tool2.profiles?.includes(activeProfile)
1408
+ );
1409
+ } else if (activeProfile) {
1410
+ filtered = filtered.filter((tool2) => {
1411
+ if (tool2.profiles?.length) {
1412
+ return tool2.profiles.includes(activeProfile);
1413
+ }
1414
+ return includeUnprofiled;
1415
+ });
1416
+ }
1417
+ if (profile?.exclude?.length) {
1418
+ filtered = filtered.filter(
1419
+ (tool2) => !profile.exclude.some(
1420
+ (selector) => matchesSelector(tool2, selector, activeProfile)
1421
+ )
1422
+ );
1423
+ }
1424
+ if (!profileConfig.dynamicSelection?.enabled) {
1425
+ return filtered;
1426
+ }
1427
+ const maxTools = Math.max(
1428
+ 1,
1429
+ Math.min(
1430
+ profileConfig.dynamicSelection.maxTools ?? filtered.length,
1431
+ filtered.length
1432
+ )
1433
+ );
1434
+ const queryTokens = tokenize(buildToolQuery(messages));
1435
+ return [...filtered].sort((left, right) => {
1436
+ const scoreDiff = scoreTool(right, queryTokens, activeProfile) - scoreTool(left, queryTokens, activeProfile);
1437
+ if (scoreDiff !== 0) {
1438
+ return scoreDiff;
1439
+ }
1440
+ return left.name.localeCompare(right.name);
1441
+ }).slice(0, maxTools);
1442
+ }
1443
+ transformMessages(messages, allTools) {
1444
+ const toolCallMap = /* @__PURE__ */ new Map();
1445
+ for (const message of messages) {
1446
+ if (message.role !== "assistant" || !message.toolCalls?.length) {
1447
+ continue;
1448
+ }
1449
+ for (const toolCall of message.toolCalls) {
1450
+ try {
1451
+ toolCallMap.set(toolCall.id, {
1452
+ toolName: toolCall.function.name,
1453
+ args: JSON.parse(toolCall.function.arguments)
1454
+ });
1455
+ } catch {
1456
+ toolCallMap.set(toolCall.id, {
1457
+ toolName: toolCall.function.name,
1458
+ args: {}
1459
+ });
1460
+ }
1461
+ }
1462
+ }
1463
+ const toolDefMap = new Map(
1464
+ allTools.map((tool2) => [tool2.name, tool2])
1465
+ );
1466
+ return messages.map((message) => {
1467
+ if (message.role !== "tool") {
1468
+ return {
1469
+ role: message.role,
1470
+ content: message.content,
1471
+ tool_calls: message.toolCalls,
1472
+ tool_call_id: message.toolCallId,
1473
+ attachments: message.attachments
1474
+ };
1475
+ }
1476
+ const toolCall = message.toolCallId ? toolCallMap.get(message.toolCallId) : void 0;
1477
+ const tool2 = toolCall ? toolDefMap.get(toolCall.toolName) : void 0;
1478
+ let content = message.content;
1479
+ try {
1480
+ const parsed = JSON.parse(message.content);
1481
+ content = buildToolResultContentForPrompt(
1482
+ parsed,
1483
+ tool2,
1484
+ toolCall?.args ?? {},
1485
+ this.config
1486
+ );
1487
+ } catch {
1488
+ content = buildToolResultContentForPrompt(
1489
+ message.content,
1490
+ tool2,
1491
+ toolCall?.args ?? {},
1492
+ this.config
1493
+ );
1494
+ }
1495
+ return {
1496
+ role: message.role,
1497
+ content,
1498
+ tool_call_id: message.toolCallId
1499
+ };
1500
+ });
1501
+ }
1502
+ };
1503
+
1504
+ // src/chat/classes/AbstractChat.ts
584
1505
  var AbstractChat = class {
585
1506
  constructor(init) {
1507
+ this.lastContextUsage = null;
586
1508
  // Event handlers
587
1509
  this.eventHandlers = /* @__PURE__ */ new Map();
588
1510
  // Current streaming state
589
1511
  this.streamState = null;
1512
+ /**
1513
+ * Inline skills from the client (sent on every request for server to merge)
1514
+ */
1515
+ this.inlineSkills = [];
590
1516
  /**
591
1517
  * Dynamic context from useAIContext hook
592
1518
  */
593
1519
  this.dynamicContext = "";
1520
+ /**
1521
+ * Optional transform applied to messages just before building the HTTP request.
1522
+ * Used by the message-history / compaction system to send a pruned message list
1523
+ * without mutating the in-memory store (which keeps the full history for display).
1524
+ */
1525
+ this.requestMessageTransform = null;
594
1526
  this._isDisposed = false;
595
1527
  this.config = {
596
1528
  runtimeUrl: init.runtimeUrl,
@@ -600,7 +1532,8 @@ var AbstractChat = class {
600
1532
  headers: init.headers,
601
1533
  body: init.body,
602
1534
  threadId: init.threadId,
603
- debug: init.debug
1535
+ debug: init.debug,
1536
+ optimization: init.optimization
604
1537
  };
605
1538
  this.state = init.state ?? new SimpleChatState();
606
1539
  this.transport = init.transport ?? new HttpTransport({
@@ -610,6 +1543,7 @@ var AbstractChat = class {
610
1543
  streaming: init.streaming ?? true
611
1544
  });
612
1545
  this.callbacks = init.callbacks ?? {};
1546
+ this.optimizer = new ChatContextOptimizer(init.optimization);
613
1547
  if (init.initialMessages?.length) {
614
1548
  this.state.setMessages(init.initialMessages);
615
1549
  }
@@ -641,20 +1575,42 @@ var AbstractChat = class {
641
1575
  /**
642
1576
  * Send a message
643
1577
  * Returns false if a request is already in progress
1578
+ *
1579
+ * @param content - Message content
1580
+ * @param attachments - Optional attachments
1581
+ * @param options - Optional branching options
1582
+ * @param options.editMessageId - Edit flow: new message branches from the
1583
+ * same parent as this message ID, creating a parallel conversation path
644
1584
  */
645
- async sendMessage(content, attachments) {
1585
+ async sendMessage(content, attachments, options) {
646
1586
  if (this.isBusy) {
647
1587
  this.debug("sendMessage", "Blocked - request already in progress");
648
1588
  return false;
649
1589
  }
650
- this.debug("sendMessage", { content, attachments });
1590
+ this.debug("sendMessage", { content, attachments, options });
651
1591
  try {
652
1592
  this.resolveUnresolvedToolCalls();
653
- const userMessage = createUserMessage(content, attachments);
1593
+ let newParentId;
1594
+ const visibleMessages = this.state.messages;
1595
+ if (options?.editMessageId && this.state.setCurrentLeaf) {
1596
+ const allMessages = this.state.getAllMessages?.() ?? this.state.messages;
1597
+ const target = allMessages.find((m) => m.id === options.editMessageId);
1598
+ if (target && target.parentId !== void 0) {
1599
+ newParentId = target.parentId;
1600
+ this.state.setCurrentLeaf(
1601
+ typeof target.parentId === "string" ? target.parentId : null
1602
+ );
1603
+ }
1604
+ } else if (visibleMessages.length > 0) {
1605
+ newParentId = visibleMessages[visibleMessages.length - 1].id;
1606
+ }
1607
+ const userMessage = createUserMessage(content, attachments, {
1608
+ parentId: newParentId
1609
+ });
654
1610
  this.state.pushMessage(userMessage);
655
1611
  this.state.status = "submitted";
656
1612
  this.state.error = void 0;
657
- this.callbacks.onMessagesChange?.(this.state.messages);
1613
+ this.callbacks.onMessagesChange?.(this._allMessages());
658
1614
  this.callbacks.onStatusChange?.("submitted");
659
1615
  await Promise.resolve();
660
1616
  await this.processRequest();
@@ -704,7 +1660,7 @@ var AbstractChat = class {
704
1660
  };
705
1661
  this.state.pushMessage(toolMessage);
706
1662
  }
707
- this.callbacks.onMessagesChange?.(this.state.messages);
1663
+ this.callbacks.onMessagesChange?.(this._allMessages());
708
1664
  }
709
1665
  }
710
1666
  /**
@@ -758,9 +1714,9 @@ var AbstractChat = class {
758
1714
  this.state.pushMessage(userMessage);
759
1715
  }
760
1716
  this.state.status = "submitted";
761
- this.callbacks.onMessagesChange?.(this.state.messages);
1717
+ this.callbacks.onMessagesChange?.(this._allMessages());
762
1718
  this.callbacks.onStatusChange?.("submitted");
763
- await Promise.resolve();
1719
+ await new Promise((resolve) => setTimeout(resolve, 0));
764
1720
  await this.processRequest();
765
1721
  } catch (error) {
766
1722
  this.handleError(error);
@@ -789,30 +1745,58 @@ var AbstractChat = class {
789
1745
  this.callbacks.onMessagesChange?.(messages);
790
1746
  }
791
1747
  /**
792
- * Regenerate last response
1748
+ * Regenerate last response.
1749
+ *
1750
+ * Branch-aware: when the state supports branching (setCurrentLeaf is available),
1751
+ * regenerate creates a new sibling response instead of destroying the original.
1752
+ * The old response is preserved and navigable via switchBranch().
1753
+ *
1754
+ * Legacy fallback: when branching is not available, uses old slice() behavior.
793
1755
  */
794
1756
  async regenerate(messageId) {
1757
+ if (this.isBusy) return;
795
1758
  const messages = this.state.messages;
796
- let targetIndex = messages.length - 1;
1759
+ let targetMessage;
797
1760
  if (messageId) {
798
- targetIndex = messages.findIndex((m) => m.id === messageId);
1761
+ targetMessage = messages.find((m) => m.id === messageId);
1762
+ if (!targetMessage) {
1763
+ targetMessage = this.state.getAllMessages?.().find((m) => m.id === messageId);
1764
+ }
799
1765
  } else {
800
1766
  for (let i = messages.length - 1; i >= 0; i--) {
801
1767
  if (messages[i].role === "assistant") {
802
- targetIndex = i;
1768
+ targetMessage = messages[i];
803
1769
  break;
804
1770
  }
805
1771
  }
806
1772
  }
1773
+ if (!targetMessage) return;
1774
+ if (targetMessage.parentId !== void 0 && this.state.setCurrentLeaf) {
1775
+ this.state.setCurrentLeaf(targetMessage.parentId ?? null);
1776
+ this.callbacks.onMessagesChange?.(this._allMessages());
1777
+ this.state.status = "submitted";
1778
+ await Promise.resolve();
1779
+ await this.processRequest();
1780
+ return;
1781
+ }
1782
+ const targetIndex = messages.indexOf(targetMessage);
807
1783
  if (targetIndex > 0) {
808
1784
  this.state.setMessages(messages.slice(0, targetIndex));
809
- this.callbacks.onMessagesChange?.(this.state.messages);
1785
+ this.callbacks.onMessagesChange?.(this._allMessages());
810
1786
  await this.processRequest();
811
1787
  }
812
1788
  }
813
1789
  // ============================================
814
1790
  // Event Handling
815
1791
  // ============================================
1792
+ /**
1793
+ * Returns all messages across all branches when the state supports it
1794
+ * (branch-aware), otherwise returns the visible path.
1795
+ * Use this whenever firing onMessagesChange so inactive branches are not lost.
1796
+ */
1797
+ _allMessages() {
1798
+ return this.state.getAllMessages?.() ?? this.state.messages;
1799
+ }
816
1800
  /**
817
1801
  * Subscribe to events
818
1802
  */
@@ -844,10 +1828,32 @@ var AbstractChat = class {
844
1828
  */
845
1829
  async processRequest() {
846
1830
  const request = this.buildRequest();
1831
+ let preCreatedMessageId;
1832
+ if (this.config.streaming !== false) {
1833
+ const visibleMessages = this.state.messages;
1834
+ const currentLeafId = visibleMessages.length > 0 ? visibleMessages[visibleMessages.length - 1].id : void 0;
1835
+ const preMsg = createEmptyAssistantMessage(void 0, {
1836
+ parentId: currentLeafId
1837
+ });
1838
+ this.state.pushMessage(preMsg);
1839
+ this.callbacks.onMessagesChange?.(this._allMessages());
1840
+ preCreatedMessageId = preMsg.id;
1841
+ }
847
1842
  const response = await this.transport.send(request);
848
1843
  if (this.isAsyncIterable(response)) {
849
- await this.handleStreamResponse(response);
1844
+ await this.handleStreamResponse(response, preCreatedMessageId);
850
1845
  } else {
1846
+ if (preCreatedMessageId) {
1847
+ const id = preCreatedMessageId;
1848
+ const visibleMsgs = this.state.messages;
1849
+ const placeholderIdx = visibleMsgs.findIndex((m) => m.id === id);
1850
+ const intendedLeafId = placeholderIdx > 0 ? visibleMsgs[placeholderIdx - 1].id : null;
1851
+ const allMsgs = this.state.getAllMessages?.() ?? this.state.messages;
1852
+ this.state.setMessages(allMsgs.filter((m) => m.id !== id));
1853
+ if (intendedLeafId && this.state.setCurrentLeaf) {
1854
+ this.state.setCurrentLeaf(intendedLeafId);
1855
+ }
1856
+ }
851
1857
  this.handleJsonResponse(response);
852
1858
  }
853
1859
  }
@@ -858,30 +1864,60 @@ var AbstractChat = class {
858
1864
  this.config.tools = tools;
859
1865
  }
860
1866
  /**
861
- * Set dynamic context (appended to system prompt)
1867
+ * Update prompt/tool optimization behavior.
862
1868
  */
863
- setContext(context) {
864
- this.dynamicContext = context;
865
- this.debug("Context updated", { length: context.length });
1869
+ setOptimizationConfig(config) {
1870
+ this.config.optimization = config;
1871
+ this.optimizer.updateConfig(config);
866
1872
  }
867
1873
  /**
868
- * Set system prompt dynamically
869
- * This allows updating the system prompt after initialization
1874
+ * Select the active tool profile for future requests.
870
1875
  */
871
- setSystemPrompt(prompt) {
872
- this.config.systemPrompt = prompt;
873
- this.debug("System prompt updated", { length: prompt.length });
1876
+ setToolProfile(profile) {
1877
+ this.optimizer.setActiveProfile(profile);
874
1878
  }
875
1879
  /**
876
- * Set headers configuration
877
- * Can be static headers or a getter function for dynamic resolution
1880
+ * Get the most recent prompt context usage snapshot.
878
1881
  */
879
- setHeaders(headers) {
880
- this.config.headers = headers;
1882
+ getContextUsage() {
1883
+ return this.lastContextUsage;
1884
+ }
1885
+ /**
1886
+ * Set inline skills (called by SkillProvider via React layer)
1887
+ */
1888
+ setInlineSkills(skills) {
1889
+ this.inlineSkills = skills;
1890
+ this.debug("Inline skills updated", { count: skills.length });
1891
+ }
1892
+ /**
1893
+ * Set (or clear) the per-request message transform.
1894
+ * Pass null to disable.
1895
+ */
1896
+ setRequestMessageTransform(fn) {
1897
+ this.requestMessageTransform = fn;
1898
+ }
1899
+ /**
1900
+ * Set dynamic context (appended to system prompt)
1901
+ */
1902
+ setContext(context) {
1903
+ this.dynamicContext = context;
1904
+ }
1905
+ /**
1906
+ * Set system prompt dynamically
1907
+ * This allows updating the system prompt after initialization
1908
+ */
1909
+ setSystemPrompt(prompt) {
1910
+ this.config.systemPrompt = prompt;
1911
+ }
1912
+ /**
1913
+ * Set headers configuration
1914
+ * Can be static headers or a getter function for dynamic resolution
1915
+ */
1916
+ setHeaders(headers) {
1917
+ this.config.headers = headers;
881
1918
  if (this.transport.setHeaders && headers !== void 0) {
882
1919
  this.transport.setHeaders(headers);
883
1920
  }
884
- this.debug("Headers config updated");
885
1921
  }
886
1922
  /**
887
1923
  * Set URL configuration
@@ -892,7 +1928,6 @@ var AbstractChat = class {
892
1928
  if (this.transport.setUrl) {
893
1929
  this.transport.setUrl(url);
894
1930
  }
895
- this.debug("URL config updated");
896
1931
  }
897
1932
  /**
898
1933
  * Set body configuration
@@ -903,110 +1938,88 @@ var AbstractChat = class {
903
1938
  if (this.transport.setBody && body !== void 0) {
904
1939
  this.transport.setBody(body);
905
1940
  }
906
- this.debug("Body config updated");
907
1941
  }
908
1942
  /**
909
1943
  * Build the request payload
910
1944
  */
911
1945
  buildRequest() {
912
- const tools = this.config.tools?.filter((tool2) => tool2.available !== false).map((tool2) => ({
913
- name: tool2.name,
914
- description: tool2.description,
915
- inputSchema: tool2.inputSchema
916
- }));
917
- const toolCallMap = /* @__PURE__ */ new Map();
918
- for (const msg of this.state.messages) {
919
- if (msg.role === "assistant" && msg.toolCalls) {
920
- for (const tc of msg.toolCalls) {
921
- try {
922
- const args = tc.function?.arguments ? JSON.parse(tc.function.arguments) : {};
923
- toolCallMap.set(tc.id, { toolName: tc.function.name, args });
924
- } catch {
925
- toolCallMap.set(tc.id, { toolName: tc.function.name, args: {} });
926
- }
927
- }
928
- }
929
- }
930
- const toolDefMap = /* @__PURE__ */ new Map();
931
- if (this.config.tools) {
932
- for (const tool2 of this.config.tools) {
933
- toolDefMap.set(tool2.name, {
934
- name: tool2.name,
935
- aiResponseMode: tool2.aiResponseMode,
936
- aiContext: tool2.aiContext
937
- });
938
- }
939
- }
940
- return {
941
- messages: this.state.messages.map((m) => {
942
- if (m.role === "tool" && m.content && m.toolCallId) {
943
- try {
944
- const fullResult = JSON.parse(m.content);
945
- const toolCallInfo = toolCallMap.get(m.toolCallId);
946
- const toolDef = toolCallInfo ? toolDefMap.get(toolCallInfo.toolName) : void 0;
947
- const toolArgs = toolCallInfo?.args;
948
- const transformedContent = buildToolResultContentForAI(
949
- fullResult,
950
- toolDef,
951
- toolArgs
952
- );
953
- return {
954
- role: m.role,
955
- content: transformedContent,
956
- tool_call_id: m.toolCallId
957
- };
958
- } catch (e) {
959
- this.debug("Failed to parse tool message JSON", {
960
- content: m.content?.slice(0, 100),
961
- error: e instanceof Error ? e.message : String(e)
962
- });
963
- return {
964
- role: m.role,
965
- content: m.content,
966
- tool_call_id: m.toolCallId
967
- };
968
- }
969
- }
970
- return {
971
- role: m.role,
972
- content: m.content,
973
- tool_calls: m.toolCalls,
974
- tool_call_id: m.toolCallId,
975
- attachments: m.attachments
976
- };
977
- }),
978
- threadId: this.config.threadId,
979
- systemPrompt: this.dynamicContext ? `${this.config.systemPrompt || ""}
1946
+ const systemPrompt = this.dynamicContext ? `${this.config.systemPrompt || ""}
980
1947
 
981
1948
  ## Current App Context:
982
- ${this.dynamicContext}`.trim() : this.config.systemPrompt,
1949
+ ${this.dynamicContext}`.trim() : this.config.systemPrompt;
1950
+ const rawMessages = this.requestMessageTransform ? this.requestMessageTransform(
1951
+ this.state.messages
1952
+ ) : this.state.messages;
1953
+ const optimized = this.optimizer.prepare({
1954
+ messages: rawMessages,
1955
+ tools: this.config.tools,
1956
+ systemPrompt
1957
+ });
1958
+ this.lastContextUsage = optimized.contextUsage;
1959
+ this.callbacks.onContextUsageChange?.(optimized.contextUsage);
1960
+ return {
1961
+ messages: optimized.messages,
1962
+ threadId: this.config.threadId,
1963
+ systemPrompt,
983
1964
  llm: this.config.llm,
984
- tools: tools?.length ? tools : void 0
1965
+ tools: this.config.tools?.length ? this.config.tools.map((tool2) => ({
1966
+ name: tool2.name,
1967
+ description: tool2.description,
1968
+ category: tool2.category,
1969
+ group: tool2.group,
1970
+ deferLoading: tool2.deferLoading,
1971
+ profiles: tool2.profiles,
1972
+ searchKeywords: tool2.searchKeywords,
1973
+ inputSchema: tool2.inputSchema
1974
+ })) : void 0,
1975
+ __skills: this.inlineSkills.length ? this.inlineSkills : void 0
985
1976
  };
986
1977
  }
987
1978
  /**
988
1979
  * Handle streaming response
989
1980
  */
990
- async handleStreamResponse(stream) {
1981
+ async handleStreamResponse(stream, preCreatedMessageId) {
991
1982
  this.state.status = "streaming";
992
1983
  this.callbacks.onStatusChange?.("streaming");
993
- const assistantMessage = createEmptyAssistantMessage();
994
- this.state.pushMessage(assistantMessage);
1984
+ let assistantMessage;
1985
+ if (preCreatedMessageId) {
1986
+ const existing = this.state.messages.find(
1987
+ (m) => m.id === preCreatedMessageId
1988
+ );
1989
+ if (existing) {
1990
+ assistantMessage = existing;
1991
+ } else {
1992
+ assistantMessage = createEmptyAssistantMessage();
1993
+ this.state.pushMessage(assistantMessage);
1994
+ }
1995
+ } else {
1996
+ assistantMessage = createEmptyAssistantMessage();
1997
+ this.state.pushMessage(assistantMessage);
1998
+ }
995
1999
  this.streamState = createStreamState(assistantMessage.id);
996
2000
  this.callbacks.onMessageStart?.(assistantMessage.id);
997
- this.debug("handleStreamResponse", "Starting to process stream");
2001
+ this.debugGroup("handleStreamResponse");
2002
+ this.debug("Starting to process stream");
998
2003
  let chunkCount = 0;
999
2004
  let toolCallsEmitted = false;
2005
+ let pendingClientToolCalls;
1000
2006
  for await (const chunk of stream) {
1001
2007
  chunkCount++;
1002
- this.debug("chunk", { count: chunkCount, type: chunk.type });
2008
+ if (chunk.type !== "message:delta") {
2009
+ this.debug("chunk", { count: chunkCount, type: chunk.type });
2010
+ }
1003
2011
  if (chunk.type === "error") {
1004
2012
  const error = new Error(chunk.message || "Stream error");
1005
2013
  this.handleError(error);
1006
2014
  return;
1007
2015
  }
1008
2016
  if (chunk.type === "message:end" && this.streamState?.content) {
1009
- this.debug("message:end mid-stream - finalizing current turn");
2017
+ this.debug("message:end mid-stream", {
2018
+ messageId: this.streamState.messageId,
2019
+ contentLength: this.streamState.content.length,
2020
+ toolCallsInState: this.streamState.toolCalls?.length ?? 0,
2021
+ chunkCount
2022
+ });
1010
2023
  const turnMessage = streamStateToMessage(this.streamState);
1011
2024
  const toolCallsHidden = {};
1012
2025
  for (const [id, result] of this.streamState.toolResults) {
@@ -1022,7 +2035,11 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt,
1022
2035
  }
1023
2036
  this.state.updateMessageById(
1024
2037
  this.streamState.messageId,
1025
- () => turnMessage
2038
+ (existing) => ({
2039
+ ...turnMessage,
2040
+ ...existing.parentId !== void 0 ? { parentId: existing.parentId } : {},
2041
+ ...existing.childrenIds !== void 0 ? { childrenIds: existing.childrenIds } : {}
2042
+ })
1026
2043
  );
1027
2044
  this.callbacks.onMessageFinish?.(turnMessage);
1028
2045
  this.streamState = null;
@@ -1037,6 +2054,93 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt,
1037
2054
  continue;
1038
2055
  }
1039
2056
  if (!this.streamState) {
2057
+ if (chunk.type === "tool_calls") {
2058
+ pendingClientToolCalls = chunk.toolCalls;
2059
+ this.debug("tool_calls (post-message:end, stored as pending)", {
2060
+ count: pendingClientToolCalls?.length,
2061
+ ids: pendingClientToolCalls?.map((tc) => tc.id)
2062
+ });
2063
+ continue;
2064
+ }
2065
+ if (chunk.type === "done") {
2066
+ this.debug("done (post-message:end)", {
2067
+ hasPendingToolCalls: !!pendingClientToolCalls?.length,
2068
+ pendingCount: pendingClientToolCalls?.length ?? 0,
2069
+ doneMessagesCount: chunk.messages?.length ?? 0,
2070
+ requiresAction: chunk.requiresAction,
2071
+ toolCallsEmitted
2072
+ });
2073
+ if (chunk.messages?.length) {
2074
+ const pendingIds = new Set(
2075
+ (pendingClientToolCalls ?? []).filter((tc) => tc?.id).map((tc) => tc.id)
2076
+ );
2077
+ const messagesToInsert = [];
2078
+ let clientAssistantToolCalls;
2079
+ for (const msg of chunk.messages) {
2080
+ if (msg.role === "assistant" && msg.tool_calls?.length && pendingIds.size > 0 && msg.tool_calls.every(
2081
+ (tc) => pendingIds.has(tc?.id ?? "")
2082
+ )) {
2083
+ clientAssistantToolCalls = msg.tool_calls;
2084
+ continue;
2085
+ }
2086
+ if (msg.role === "assistant" && !msg.tool_calls?.length) continue;
2087
+ messagesToInsert.push({
2088
+ id: generateMessageId(),
2089
+ role: msg.role,
2090
+ content: msg.content ?? "",
2091
+ toolCalls: msg.tool_calls,
2092
+ toolCallId: msg.tool_call_id,
2093
+ createdAt: /* @__PURE__ */ new Date()
2094
+ });
2095
+ }
2096
+ if (clientAssistantToolCalls) {
2097
+ const currentMessages = this.state.messages;
2098
+ for (let i = currentMessages.length - 1; i >= 0; i--) {
2099
+ if (currentMessages[i].role === "assistant") {
2100
+ this.state.updateMessageById(
2101
+ currentMessages[i].id,
2102
+ (m) => ({
2103
+ ...m,
2104
+ toolCalls: clientAssistantToolCalls
2105
+ })
2106
+ );
2107
+ break;
2108
+ }
2109
+ }
2110
+ }
2111
+ if (messagesToInsert.length > 0) {
2112
+ const currentMessages = this.state.messages;
2113
+ let insertIdx = currentMessages.length;
2114
+ for (let i = currentMessages.length - 1; i >= 0; i--) {
2115
+ if (currentMessages[i].role === "assistant") {
2116
+ insertIdx = i;
2117
+ break;
2118
+ }
2119
+ }
2120
+ this.state.setMessages([
2121
+ ...currentMessages.slice(0, insertIdx),
2122
+ ...messagesToInsert,
2123
+ ...currentMessages.slice(insertIdx)
2124
+ ]);
2125
+ }
2126
+ }
2127
+ if (!toolCallsEmitted && pendingClientToolCalls?.length) {
2128
+ toolCallsEmitted = true;
2129
+ this.debug("emit toolCalls (post-message:end path)", {
2130
+ count: pendingClientToolCalls.length,
2131
+ names: pendingClientToolCalls.map(
2132
+ (tc) => tc.function?.name ?? tc.name
2133
+ )
2134
+ });
2135
+ this.emit("toolCalls", { toolCalls: pendingClientToolCalls });
2136
+ } else {
2137
+ this.debug("skip emit toolCalls (post-message:end path)", {
2138
+ toolCallsEmitted,
2139
+ hasPending: !!pendingClientToolCalls?.length
2140
+ });
2141
+ }
2142
+ continue;
2143
+ }
1040
2144
  this.debug("warning", "streamState is null, skipping chunk");
1041
2145
  continue;
1042
2146
  }
@@ -1072,22 +2176,38 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt,
1072
2176
  const updatedMessage = streamStateToMessage(this.streamState);
1073
2177
  this.state.updateMessageById(
1074
2178
  this.streamState.messageId,
1075
- () => updatedMessage
2179
+ // Preserve parentId/childrenIds from the existing placeholder so the
2180
+ // branch tree structure (activeChildMap) is not corrupted when
2181
+ // setCurrentLeaf() walks up the chain later.
2182
+ (existing) => ({
2183
+ ...updatedMessage,
2184
+ ...existing.parentId !== void 0 ? { parentId: existing.parentId } : {},
2185
+ ...existing.childrenIds !== void 0 ? { childrenIds: existing.childrenIds } : {}
2186
+ })
1076
2187
  );
1077
2188
  if (chunk.type === "message:delta") {
1078
2189
  this.callbacks.onMessageDelta?.(assistantMessage.id, chunk.content);
1079
2190
  }
1080
- if (requiresToolExecution(chunk) && !toolCallsEmitted) {
1081
- toolCallsEmitted = true;
1082
- this.debug("toolCalls", { toolCalls: updatedMessage.toolCalls });
1083
- this.emit("toolCalls", { toolCalls: updatedMessage.toolCalls });
1084
- }
1085
2191
  if (isStreamDone(chunk)) {
1086
- this.debug("streamDone", { chunk });
2192
+ this.debug("streamDone", {
2193
+ chunkType: chunk.type,
2194
+ requiresAction: chunk.requiresAction,
2195
+ doneMessagesCount: chunk.messages?.length ?? 0,
2196
+ streamToolCallsCount: this.streamState?.toolCalls?.length ?? 0,
2197
+ toolCallsEmitted,
2198
+ chunkCount
2199
+ });
1087
2200
  if (chunk.type === "done" && chunk.messages?.length) {
1088
2201
  this.debug("processDoneMessages", {
1089
- count: chunk.messages.length
2202
+ count: chunk.messages.length,
2203
+ roles: chunk.messages.map(
2204
+ (m) => `${m.role}${m.tool_calls?.length ? `[${m.tool_calls.length}tc]` : ""}`
2205
+ )
1090
2206
  });
2207
+ const currentStreamToolCallIds = new Set(
2208
+ this.streamState?.toolCalls?.map((toolCall) => toolCall.id) ?? []
2209
+ );
2210
+ const messagesToInsert = [];
1091
2211
  const toolCallsHidden = {};
1092
2212
  if (this.streamState?.toolResults) {
1093
2213
  for (const [id, result] of this.streamState.toolResults) {
@@ -1097,7 +2217,12 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt,
1097
2217
  }
1098
2218
  }
1099
2219
  for (const msg of chunk.messages) {
1100
- if (msg.role === "assistant") {
2220
+ if (msg.role === "assistant" && !msg.tool_calls?.length) {
2221
+ continue;
2222
+ }
2223
+ if (msg.role === "assistant" && msg.tool_calls?.length && msg.tool_calls.every(
2224
+ (toolCall) => currentStreamToolCallIds.has(toolCall.id)
2225
+ )) {
1101
2226
  continue;
1102
2227
  }
1103
2228
  let metadata;
@@ -1113,7 +2238,60 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt,
1113
2238
  createdAt: /* @__PURE__ */ new Date(),
1114
2239
  metadata
1115
2240
  };
1116
- this.state.pushMessage(message);
2241
+ messagesToInsert.push(message);
2242
+ }
2243
+ if (messagesToInsert.length > 0) {
2244
+ const currentMessages = this.state.messages;
2245
+ const currentStreamIndex = this.streamState ? currentMessages.findIndex(
2246
+ (message) => message.id === this.streamState.messageId
2247
+ ) : -1;
2248
+ if (currentStreamIndex === -1) {
2249
+ this.state.setMessages([...currentMessages, ...messagesToInsert]);
2250
+ } else {
2251
+ this.state.setMessages([
2252
+ ...currentMessages.slice(0, currentStreamIndex),
2253
+ ...messagesToInsert,
2254
+ ...currentMessages.slice(currentStreamIndex)
2255
+ ]);
2256
+ }
2257
+ }
2258
+ this.debug("requiresAction check", {
2259
+ requiresAction: chunk.requiresAction,
2260
+ toolCallsEmitted,
2261
+ updatedMessageToolCallsCount: updatedMessage.toolCalls?.length ?? 0,
2262
+ messagesToInsertCount: messagesToInsert.length
2263
+ });
2264
+ if (chunk.requiresAction && !toolCallsEmitted) {
2265
+ let clientToolCalls = updatedMessage.toolCalls;
2266
+ if (!clientToolCalls?.length && messagesToInsert.length > 0) {
2267
+ for (let i = messagesToInsert.length - 1; i >= 0; i--) {
2268
+ const m = messagesToInsert[i];
2269
+ if (m.role === "assistant" && m.toolCalls?.length) {
2270
+ clientToolCalls = m.toolCalls;
2271
+ this.debug("clientToolCalls from messagesToInsert", {
2272
+ index: i,
2273
+ count: clientToolCalls?.length
2274
+ });
2275
+ break;
2276
+ }
2277
+ }
2278
+ }
2279
+ if (clientToolCalls?.length) {
2280
+ toolCallsEmitted = true;
2281
+ this.debug("emit toolCalls (normal done path)", {
2282
+ count: clientToolCalls.length,
2283
+ names: clientToolCalls.map((tc) => tc.function?.name ?? tc.name)
2284
+ });
2285
+ this.emit("toolCalls", { toolCalls: clientToolCalls });
2286
+ } else {
2287
+ this.debug("requiresAction=true but no clientToolCalls found", {
2288
+ updatedMessageToolCalls: updatedMessage.toolCalls,
2289
+ messagesToInsert: messagesToInsert.map((m) => ({
2290
+ role: m.role,
2291
+ hasToolCalls: !!m.toolCalls?.length
2292
+ }))
2293
+ });
2294
+ }
1117
2295
  }
1118
2296
  }
1119
2297
  break;
@@ -1138,15 +2316,22 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt,
1138
2316
  toolCallsHidden
1139
2317
  };
1140
2318
  }
1141
- this.state.updateMessageById(
1142
- this.streamState.messageId,
1143
- () => finalMessage
1144
- );
2319
+ this.state.updateMessageById(this.streamState.messageId, (existing) => ({
2320
+ ...finalMessage,
2321
+ ...existing.parentId !== void 0 ? { parentId: existing.parentId } : {},
2322
+ ...existing.childrenIds !== void 0 ? { childrenIds: existing.childrenIds } : {}
2323
+ }));
1145
2324
  if (!finalMessage.content && (!finalMessage.toolCalls || finalMessage.toolCalls.length === 0)) {
1146
2325
  this.debug("warning", "Empty response - no content and no tool calls");
1147
2326
  }
1148
2327
  }
1149
- this.callbacks.onMessagesChange?.(this.state.messages);
2328
+ this.callbacks.onMessagesChange?.(this._allMessages());
2329
+ this.debugGroupEnd();
2330
+ this.debug("stream end", {
2331
+ toolCallsEmitted,
2332
+ totalChunks: chunkCount,
2333
+ messagesInState: this.state.messages.length
2334
+ });
1150
2335
  if (!toolCallsEmitted) {
1151
2336
  this.state.status = "ready";
1152
2337
  this.callbacks.onStatusChange?.("ready");
@@ -1167,6 +2352,7 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt,
1167
2352
  }
1168
2353
  }
1169
2354
  }
2355
+ let currentParentId = this.state.messages.length > 0 ? this.state.messages[this.state.messages.length - 1].id : void 0;
1170
2356
  for (const msg of response.messages ?? []) {
1171
2357
  let metadata;
1172
2358
  if (msg.role === "assistant" && msg.tool_calls && toolCallHiddenMap.size > 0) {
@@ -1189,11 +2375,15 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt,
1189
2375
  // CRITICAL: Preserve toolCallId for tool messages (fixes Anthropic API errors)
1190
2376
  toolCallId: msg.tool_call_id,
1191
2377
  createdAt: /* @__PURE__ */ new Date(),
1192
- metadata
2378
+ metadata,
2379
+ // Preserve branch tree structure: each message is a child of the
2380
+ // current leaf so the tree is not corrupted for non-streaming mode.
2381
+ ...currentParentId !== void 0 ? { parentId: currentParentId } : {}
1193
2382
  };
1194
2383
  this.state.pushMessage(message);
2384
+ currentParentId = message.id;
1195
2385
  }
1196
- this.callbacks.onMessagesChange?.(this.state.messages);
2386
+ this.callbacks.onMessagesChange?.(this._allMessages());
1197
2387
  const hasToolCalls = response.requiresAction && this.state.messages.length > 0 && this.state.messages[this.state.messages.length - 1]?.toolCalls?.length;
1198
2388
  if (hasToolCalls) {
1199
2389
  const lastMessage = this.state.messages[this.state.messages.length - 1];
@@ -1216,14 +2406,25 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt,
1216
2406
  this.callbacks.onStatusChange?.("error");
1217
2407
  this.emit("error", { error });
1218
2408
  }
1219
- /**
1220
- * Debug logging
1221
- */
2409
+ get log() {
2410
+ if (!this._log) {
2411
+ this._log = chunkI3SQUNTT_cjs.createLogger("streaming", () => this.config.debug ?? false);
2412
+ }
2413
+ return this._log;
2414
+ }
1222
2415
  debug(action, data) {
1223
- if (this.config.debug) {
1224
- console.log(`[AbstractChat] ${action}`, data);
2416
+ this.log(action, data);
2417
+ }
2418
+ debugGroup(label, collapsed = true) {
2419
+ if (collapsed) {
2420
+ this.log.groupCollapsed(label);
2421
+ } else {
2422
+ this.log.group(label);
1225
2423
  }
1226
2424
  }
2425
+ debugGroupEnd() {
2426
+ this.log.groupEnd();
2427
+ }
1227
2428
  /**
1228
2429
  * Type guard for async iterable
1229
2430
  */
@@ -1434,19 +2635,18 @@ var AbstractAgentLoop = class {
1434
2635
  this._isCancelled = false;
1435
2636
  this._isProcessing = true;
1436
2637
  this.setIteration(this._iteration + 1);
1437
- const results = [];
1438
- for (const toolCall of toolCalls) {
1439
- if (this._isCancelled || this.abortController.signal.aborted) {
1440
- results.push({
1441
- toolCallId: toolCall.id,
1442
- success: false,
1443
- error: "Tool execution cancelled"
1444
- });
1445
- continue;
1446
- }
1447
- const result = await this.executeSingleTool(toolCall);
1448
- results.push(result);
1449
- }
2638
+ const results = await Promise.all(
2639
+ toolCalls.map((toolCall) => {
2640
+ if (this._isCancelled || this.abortController.signal.aborted) {
2641
+ return Promise.resolve({
2642
+ toolCallId: toolCall.id,
2643
+ success: false,
2644
+ error: "Tool execution cancelled"
2645
+ });
2646
+ }
2647
+ return this.executeSingleTool(toolCall);
2648
+ })
2649
+ );
1450
2650
  this._isProcessing = false;
1451
2651
  return results;
1452
2652
  }
@@ -1764,6 +2964,7 @@ var ChatWithTools = class {
1764
2964
  streaming: config.streaming,
1765
2965
  headers: config.headers,
1766
2966
  body: config.body,
2967
+ optimization: config.optimization,
1767
2968
  threadId: config.threadId,
1768
2969
  debug: config.debug,
1769
2970
  initialMessages: config.initialMessages,
@@ -1778,6 +2979,7 @@ var ChatWithTools = class {
1778
2979
  onMessageFinish: callbacks.onMessageFinish,
1779
2980
  onToolCalls: callbacks.onToolCalls,
1780
2981
  onFinish: callbacks.onFinish,
2982
+ onContextUsageChange: callbacks.onContextUsageChange,
1781
2983
  // Server-side tool callbacks - track in agentLoop for UI display
1782
2984
  // IMPORTANT: Only track tools that are NOT registered client-side
1783
2985
  // Client-side tools are tracked via executeToolCalls() path
@@ -1945,14 +3147,17 @@ var ChatWithTools = class {
1945
3147
  /**
1946
3148
  * Send a message
1947
3149
  * Returns false if a request is already in progress
3150
+ *
3151
+ * @param options.editMessageId - Edit flow: new message branches from the
3152
+ * same parent as this message ID
1948
3153
  */
1949
- async sendMessage(content, attachments) {
3154
+ async sendMessage(content, attachments, options) {
1950
3155
  if (this.isLoading) {
1951
3156
  this.debug("sendMessage blocked - request already in progress");
1952
3157
  return false;
1953
3158
  }
1954
3159
  this.agentLoop.resetIterations();
1955
- return await this.chat.sendMessage(content, attachments);
3160
+ return await this.chat.sendMessage(content, attachments, options);
1956
3161
  }
1957
3162
  /**
1958
3163
  * Stop generation and cancel any running tools
@@ -1987,6 +3192,25 @@ var ChatWithTools = class {
1987
3192
  setTools(tools) {
1988
3193
  this.chat.setTools(tools);
1989
3194
  }
3195
+ /**
3196
+ * Update prompt/tool optimization controls.
3197
+ */
3198
+ setOptimizationConfig(config) {
3199
+ this.config.optimization = config;
3200
+ this.chat.setOptimizationConfig(config);
3201
+ }
3202
+ /**
3203
+ * Set the active tool profile used for request-time tool selection.
3204
+ */
3205
+ setToolProfile(profile) {
3206
+ this.chat.setToolProfile(profile);
3207
+ }
3208
+ /**
3209
+ * Get the most recent prompt context usage snapshot.
3210
+ */
3211
+ getContextUsage() {
3212
+ return this.chat.getContextUsage();
3213
+ }
1990
3214
  /**
1991
3215
  * Set dynamic context (from useAIContext hook)
1992
3216
  */
@@ -2020,6 +3244,15 @@ var ChatWithTools = class {
2020
3244
  setBody(body) {
2021
3245
  this.chat.setBody(body);
2022
3246
  }
3247
+ setRequestMessageTransform(fn) {
3248
+ this.chat.setRequestMessageTransform(fn);
3249
+ }
3250
+ /**
3251
+ * Set inline skills (forwarded to underlying chat instance)
3252
+ */
3253
+ setInlineSkills(skills) {
3254
+ this.chat.setInlineSkills(skills);
3255
+ }
2023
3256
  // ============================================
2024
3257
  // Tool Registration
2025
3258
  // ============================================
@@ -2096,16 +3329,307 @@ var ChatWithTools = class {
2096
3329
  // Private
2097
3330
  // ============================================
2098
3331
  debug(message, ...args) {
2099
- if (this.config.debug) {
2100
- console.log(`[ChatWithTools] ${message}`, ...args);
3332
+ chunkI3SQUNTT_cjs.createLogger("tools", () => this.config.debug ?? false)(
3333
+ message,
3334
+ args.length === 1 ? args[0] : args.length > 1 ? args : void 0
3335
+ );
3336
+ }
3337
+ };
3338
+
3339
+ // src/chat/branching/MessageTree.ts
3340
+ var _MessageTree = class _MessageTree {
3341
+ constructor(messages) {
3342
+ /** All messages by ID */
3343
+ this.nodeMap = /* @__PURE__ */ new Map();
3344
+ /** parentKey → ordered list of child IDs (insertion order = oldest-first) */
3345
+ this.childrenOf = /* @__PURE__ */ new Map();
3346
+ /** parentKey → currently-active child ID */
3347
+ this.activeChildMap = /* @__PURE__ */ new Map();
3348
+ /** Current leaf message ID (tip of the active path) */
3349
+ this._currentLeafId = null;
3350
+ /** Cached visible messages — invalidated on every mutation */
3351
+ this._visibleCache = null;
3352
+ if (messages?.length) {
3353
+ this._buildFromMessages(messages);
3354
+ }
3355
+ }
3356
+ // ============================================
3357
+ // Static Migration Helpers
3358
+ // ============================================
3359
+ /**
3360
+ * Convert a legacy flat array (no parentId) to a tree-linked array.
3361
+ *
3362
+ * Rules:
3363
+ * - Tool messages get parentId = the owning assistant message's id
3364
+ * (matched via toolCallId → toolCall.id).
3365
+ * - All other messages get parentId of the previous non-tool message
3366
+ * (or null for the first message).
3367
+ *
3368
+ * Returns a new array with parentId/childrenIds filled in.
3369
+ * Does NOT mutate the original messages.
3370
+ */
3371
+ static fromFlatArray(messages) {
3372
+ if (messages.length === 0) return messages;
3373
+ const alreadyLinked = messages.some((m) => m.parentId !== void 0);
3374
+ if (alreadyLinked) return messages;
3375
+ const result = [];
3376
+ let prevNonToolId = null;
3377
+ const assistantById = /* @__PURE__ */ new Map();
3378
+ for (const msg of messages) {
3379
+ if (msg.role === "assistant") {
3380
+ assistantById.set(msg.id, msg);
3381
+ }
3382
+ }
3383
+ for (const msg of messages) {
3384
+ if (msg.role === "tool" && msg.toolCallId) {
3385
+ let ownerAssistantId = null;
3386
+ for (const [, assistant] of assistantById) {
3387
+ if (assistant.toolCalls?.some((tc) => tc.id === msg.toolCallId)) {
3388
+ ownerAssistantId = assistant.id;
3389
+ break;
3390
+ }
3391
+ }
3392
+ result.push({
3393
+ ...msg,
3394
+ parentId: ownerAssistantId ?? prevNonToolId,
3395
+ childrenIds: []
3396
+ });
3397
+ } else {
3398
+ result.push({
3399
+ ...msg,
3400
+ parentId: prevNonToolId,
3401
+ childrenIds: []
3402
+ });
3403
+ prevNonToolId = msg.id;
3404
+ }
3405
+ }
3406
+ const childrenMap = /* @__PURE__ */ new Map();
3407
+ for (const msg of result) {
3408
+ const parentKey = msg.parentId == null ? _MessageTree.ROOT_KEY : msg.parentId;
3409
+ if (!childrenMap.has(parentKey)) {
3410
+ childrenMap.set(parentKey, []);
3411
+ }
3412
+ childrenMap.get(parentKey).push(msg.id);
3413
+ }
3414
+ return result.map((msg) => ({
3415
+ ...msg,
3416
+ childrenIds: childrenMap.get(msg.id) ?? []
3417
+ }));
3418
+ }
3419
+ // ============================================
3420
+ // Core Queries
3421
+ // ============================================
3422
+ /**
3423
+ * Returns the visible path (root → current leaf) — what the UI renders
3424
+ * and what gets sent to the API.
3425
+ *
3426
+ * Backward-compat: if NO message has parentId set (all undefined),
3427
+ * falls back to insertion order (legacy linear mode).
3428
+ */
3429
+ getVisibleMessages() {
3430
+ if (this._visibleCache !== null) return this._visibleCache;
3431
+ if (this.nodeMap.size === 0) {
3432
+ this._visibleCache = [];
3433
+ return this._visibleCache;
3434
+ }
3435
+ const hasTreeStructure = Array.from(this.nodeMap.values()).some(
3436
+ (m) => m.parentId !== void 0
3437
+ );
3438
+ this._visibleCache = hasTreeStructure ? this._getActivePath().map((id) => this.nodeMap.get(id)) : Array.from(this.nodeMap.values());
3439
+ return this._visibleCache;
3440
+ }
3441
+ _invalidateCache() {
3442
+ this._visibleCache = null;
3443
+ }
3444
+ /**
3445
+ * Returns ALL messages across every branch (for persistence / ThreadManager).
3446
+ */
3447
+ getAllMessages() {
3448
+ return Array.from(this.nodeMap.values());
3449
+ }
3450
+ /**
3451
+ * Branch navigation info for the UI navigator.
3452
+ * Returns null if the message has no siblings (only child).
3453
+ */
3454
+ getBranchInfo(messageId) {
3455
+ const msg = this.nodeMap.get(messageId);
3456
+ if (!msg) return null;
3457
+ const parentKey = this._parentKey(msg.parentId);
3458
+ const siblings = this.childrenOf.get(parentKey) ?? [];
3459
+ if (siblings.length <= 1) return null;
3460
+ const siblingIndex = siblings.indexOf(messageId);
3461
+ return {
3462
+ siblingIndex,
3463
+ totalSiblings: siblings.length,
3464
+ siblingIds: [...siblings],
3465
+ hasPrevious: siblingIndex > 0,
3466
+ hasNext: siblingIndex < siblings.length - 1
3467
+ };
3468
+ }
3469
+ get currentLeafId() {
3470
+ return this._currentLeafId;
3471
+ }
3472
+ get hasBranches() {
3473
+ for (const children of this.childrenOf.values()) {
3474
+ if (children.length > 1) return true;
3475
+ }
3476
+ return false;
3477
+ }
3478
+ // ============================================
3479
+ // Mutations
3480
+ // ============================================
3481
+ /**
3482
+ * Insert a new message.
3483
+ * - Updates childrenOf and nodeMap.
3484
+ * - New branch becomes active (activeChildMap updated).
3485
+ * - Updates current leaf.
3486
+ */
3487
+ addMessage(message) {
3488
+ this.nodeMap.set(message.id, message);
3489
+ const parentKey = this._parentKey(message.parentId);
3490
+ if (!this.childrenOf.has(parentKey)) {
3491
+ this.childrenOf.set(parentKey, []);
3492
+ }
3493
+ const siblings = this.childrenOf.get(parentKey);
3494
+ if (!siblings.includes(message.id)) {
3495
+ siblings.push(message.id);
3496
+ }
3497
+ this.activeChildMap.set(parentKey, message.id);
3498
+ this._currentLeafId = this._walkToLeaf(message.id);
3499
+ this._invalidateCache();
3500
+ return message;
3501
+ }
3502
+ /**
3503
+ * Navigate: make messageId the active child at its parent fork,
3504
+ * then walk to its leaf and update currentLeafId.
3505
+ */
3506
+ switchBranch(messageId) {
3507
+ const msg = this.nodeMap.get(messageId);
3508
+ if (!msg) return;
3509
+ const parentKey = this._parentKey(msg.parentId);
3510
+ this.activeChildMap.set(parentKey, messageId);
3511
+ this._currentLeafId = this._walkToLeaf(messageId);
3512
+ this._invalidateCache();
3513
+ }
3514
+ /**
3515
+ * Update message content in-place (streaming updates).
3516
+ * No tree structure change.
3517
+ */
3518
+ updateMessage(id, updater) {
3519
+ const existing = this.nodeMap.get(id);
3520
+ if (!existing) return false;
3521
+ this.nodeMap.set(id, updater(existing));
3522
+ this._invalidateCache();
3523
+ return true;
3524
+ }
3525
+ /**
3526
+ * Set current leaf explicitly.
3527
+ * Used by regenerate() to rewind the active path before pushing a new message.
3528
+ */
3529
+ setCurrentLeaf(leafId) {
3530
+ this._currentLeafId = leafId;
3531
+ if (leafId === null) return;
3532
+ const msg = this.nodeMap.get(leafId);
3533
+ if (!msg) return;
3534
+ let current = msg;
3535
+ while (current) {
3536
+ const parentKey = this._parentKey(current.parentId);
3537
+ this.activeChildMap.set(parentKey, current.id);
3538
+ if (current.parentId == null || current.parentId === void 0) break;
3539
+ current = this.nodeMap.get(current.parentId);
3540
+ }
3541
+ this._invalidateCache();
3542
+ }
3543
+ /**
3544
+ * Rebuild entire tree from a message array.
3545
+ * Used by setMessages().
3546
+ */
3547
+ reset(messages) {
3548
+ this.nodeMap.clear();
3549
+ this.childrenOf.clear();
3550
+ this.activeChildMap.clear();
3551
+ this._currentLeafId = null;
3552
+ this._invalidateCache();
3553
+ if (messages.length > 0) {
3554
+ this._buildFromMessages(messages);
3555
+ }
3556
+ }
3557
+ // ============================================
3558
+ // Private Helpers
3559
+ // ============================================
3560
+ _buildFromMessages(messages) {
3561
+ const linked = messages.some((m) => m.parentId !== void 0) ? messages : _MessageTree.fromFlatArray(messages);
3562
+ for (const msg of linked) {
3563
+ this.nodeMap.set(msg.id, msg);
3564
+ const parentKey = this._parentKey(msg.parentId);
3565
+ if (!this.childrenOf.has(parentKey)) {
3566
+ this.childrenOf.set(parentKey, []);
3567
+ }
3568
+ const siblings = this.childrenOf.get(parentKey);
3569
+ if (!siblings.includes(msg.id)) {
3570
+ siblings.push(msg.id);
3571
+ }
3572
+ }
3573
+ for (const [parentKey, children] of this.childrenOf) {
3574
+ if (children.length > 0) {
3575
+ this.activeChildMap.set(parentKey, children[children.length - 1]);
3576
+ }
3577
+ }
3578
+ const path = this._getActivePath();
3579
+ this._currentLeafId = path.length > 0 ? path[path.length - 1] : null;
3580
+ }
3581
+ _parentKey(parentId) {
3582
+ if (parentId == null || parentId === void 0) {
3583
+ return _MessageTree.ROOT_KEY;
3584
+ }
3585
+ return parentId;
3586
+ }
3587
+ /**
3588
+ * Walk forward from a message along active children to find the leaf.
3589
+ */
3590
+ _walkToLeaf(fromId) {
3591
+ let current = fromId;
3592
+ while (true) {
3593
+ const children = this.childrenOf.get(current);
3594
+ if (!children || children.length === 0) break;
3595
+ const activeChild = this.activeChildMap.get(current);
3596
+ if (!activeChild) break;
3597
+ if (!this.nodeMap.has(activeChild)) break;
3598
+ current = activeChild;
3599
+ }
3600
+ return current;
3601
+ }
3602
+ /**
3603
+ * Walk the active path from root to the current leaf.
3604
+ */
3605
+ _getActivePath() {
3606
+ const path = [];
3607
+ const visited = /* @__PURE__ */ new Set();
3608
+ const rootChildren = this.childrenOf.get(_MessageTree.ROOT_KEY) ?? [];
3609
+ if (rootChildren.length === 0) return path;
3610
+ let activeId = this.activeChildMap.get(_MessageTree.ROOT_KEY);
3611
+ if (!activeId) {
3612
+ activeId = rootChildren[rootChildren.length - 1];
2101
3613
  }
3614
+ let current = activeId;
3615
+ while (current && !visited.has(current)) {
3616
+ if (!this.nodeMap.has(current)) break;
3617
+ visited.add(current);
3618
+ path.push(current);
3619
+ const activeChild = this.activeChildMap.get(current);
3620
+ if (!activeChild || !this.nodeMap.has(activeChild)) break;
3621
+ current = activeChild;
3622
+ }
3623
+ return path;
2102
3624
  }
2103
3625
  };
3626
+ /** Sentinel key used for root-level messages (parentId === null) */
3627
+ _MessageTree.ROOT_KEY = "__root__";
3628
+ var MessageTree = _MessageTree;
2104
3629
 
2105
3630
  // src/react/internal/ReactChatState.ts
2106
3631
  var ReactChatState = class {
2107
3632
  constructor(initialMessages) {
2108
- this._messages = [];
2109
3633
  this._status = "ready";
2110
3634
  this._error = void 0;
2111
3635
  // Callbacks for React subscriptions (useSyncExternalStore)
@@ -2131,15 +3655,19 @@ var ReactChatState = class {
2131
3655
  this.subscribers.delete(callback);
2132
3656
  };
2133
3657
  };
2134
- if (initialMessages) {
2135
- this._messages = initialMessages;
2136
- }
3658
+ this.tree = new MessageTree(initialMessages);
2137
3659
  }
2138
3660
  // ============================================
2139
- // Getters
3661
+ // Getters — visible path only
2140
3662
  // ============================================
3663
+ /**
3664
+ * Returns the VISIBLE PATH (active branch) — what the UI renders
3665
+ * and what gets sent to the API.
3666
+ *
3667
+ * For all messages across all branches, use getAllMessages().
3668
+ */
2141
3669
  get messages() {
2142
- return this._messages;
3670
+ return this.tree.getVisibleMessages();
2143
3671
  }
2144
3672
  get status() {
2145
3673
  return this._status;
@@ -2151,7 +3679,7 @@ var ReactChatState = class {
2151
3679
  // Setters (trigger reactivity)
2152
3680
  // ============================================
2153
3681
  set messages(value) {
2154
- this._messages = value;
3682
+ this.tree.reset(value);
2155
3683
  this.notify();
2156
3684
  }
2157
3685
  set status(value) {
@@ -2166,63 +3694,102 @@ var ReactChatState = class {
2166
3694
  // Mutations
2167
3695
  // ============================================
2168
3696
  pushMessage(message) {
2169
- this._messages = [...this._messages, message];
3697
+ this.tree.addMessage(message);
2170
3698
  this.notify();
2171
3699
  }
2172
3700
  popMessage() {
2173
- this._messages = this._messages.slice(0, -1);
3701
+ const leafId = this.tree.currentLeafId;
3702
+ if (!leafId) return;
3703
+ const allMessages = this.tree.getAllMessages().filter((m) => m.id !== leafId);
3704
+ const leaf = this.tree.getAllMessages().find((m) => m.id === leafId);
3705
+ const newLeafId = leaf && leaf.parentId !== void 0 && leaf.parentId !== null ? leaf.parentId : null;
3706
+ this.tree.reset(allMessages);
3707
+ if (newLeafId) {
3708
+ this.tree.setCurrentLeaf(newLeafId);
3709
+ }
2174
3710
  this.notify();
2175
3711
  }
2176
3712
  replaceMessage(index, message) {
2177
- this._messages = this._messages.map((m, i) => i === index ? message : m);
3713
+ const visible = this.tree.getVisibleMessages();
3714
+ const target = visible[index];
3715
+ if (!target) return;
3716
+ this.tree.updateMessage(target.id, () => message);
2178
3717
  this.notify();
2179
3718
  }
2180
3719
  updateLastMessage(updater) {
2181
- if (this._messages.length === 0) return;
2182
- const lastIndex = this._messages.length - 1;
2183
- const lastMessage = this._messages[lastIndex];
2184
- this._messages = [
2185
- ...this._messages.slice(0, lastIndex),
2186
- updater(lastMessage)
2187
- ];
3720
+ const leafId = this.tree.currentLeafId;
3721
+ if (!leafId) return;
3722
+ this.tree.updateMessage(leafId, updater);
2188
3723
  this.notify();
2189
3724
  }
2190
3725
  updateMessageById(id, updater) {
2191
- const index = this._messages.findIndex((m) => m.id === id);
2192
- if (index === -1) return false;
2193
- this._messages = this._messages.map(
2194
- (m, i) => i === index ? updater(m) : m
2195
- );
2196
- this.notify();
2197
- return true;
3726
+ const updated = this.tree.updateMessage(id, updater);
3727
+ if (updated) this.notify();
3728
+ return updated;
2198
3729
  }
2199
3730
  setMessages(messages) {
2200
- this._messages = messages;
3731
+ this.tree.reset(messages);
2201
3732
  this.notify();
2202
3733
  }
2203
3734
  clearMessages() {
2204
- this._messages = [];
3735
+ this.tree.reset([]);
2205
3736
  this.notify();
2206
3737
  }
2207
3738
  // ============================================
2208
- // Private Methods
3739
+ // Branching API
2209
3740
  // ============================================
2210
- notify() {
2211
- this.subscribers.forEach((cb) => cb());
2212
- }
2213
3741
  /**
2214
- * Cleanup subscriptions
3742
+ * Returns ALL messages across all branches.
3743
+ * Use this for persistence (ThreadManager save).
2215
3744
  */
2216
- dispose() {
2217
- this.subscribers.clear();
3745
+ getAllMessages() {
3746
+ return this.tree.getAllMessages();
2218
3747
  }
2219
3748
  /**
2220
- * Revive after dispose (for React StrictMode compatibility)
2221
- * Subscribers will be re-added automatically via useSyncExternalStore
3749
+ * Get branch navigation info for a message.
3750
+ * Returns null if the message has no siblings.
2222
3751
  */
2223
- revive() {
3752
+ getBranchInfo(messageId) {
3753
+ return this.tree.getBranchInfo(messageId);
2224
3754
  }
2225
- };
3755
+ /**
3756
+ * Navigate to a sibling branch.
3757
+ * Triggers re-render via notify().
3758
+ */
3759
+ switchBranch(messageId) {
3760
+ this.tree.switchBranch(messageId);
3761
+ this.notify();
3762
+ }
3763
+ /**
3764
+ * Set the current leaf (used by regenerate() to rewind active path).
3765
+ * Triggers re-render via notify().
3766
+ */
3767
+ setCurrentLeaf(leafId) {
3768
+ this.tree.setCurrentLeaf(leafId);
3769
+ this.notify();
3770
+ }
3771
+ get hasBranches() {
3772
+ return this.tree.hasBranches;
3773
+ }
3774
+ // ============================================
3775
+ // Private Methods
3776
+ // ============================================
3777
+ notify() {
3778
+ this.subscribers.forEach((cb) => cb());
3779
+ }
3780
+ /**
3781
+ * Cleanup subscriptions
3782
+ */
3783
+ dispose() {
3784
+ this.subscribers.clear();
3785
+ }
3786
+ /**
3787
+ * Revive after dispose (for React StrictMode compatibility)
3788
+ * Subscribers will be re-added automatically via useSyncExternalStore
3789
+ */
3790
+ revive() {
3791
+ }
3792
+ };
2226
3793
  function createReactChatState(initialMessages) {
2227
3794
  return new ReactChatState(initialMessages);
2228
3795
  }
@@ -2240,6 +3807,33 @@ var ReactChatWithTools = class extends ChatWithTools {
2240
3807
  };
2241
3808
  this.reactState = reactState;
2242
3809
  }
3810
+ // ============================================
3811
+ // Branching API — pass-throughs to ReactChatState
3812
+ // ============================================
3813
+ /**
3814
+ * Navigate to a sibling branch.
3815
+ */
3816
+ switchBranch(messageId) {
3817
+ this.reactState.switchBranch(messageId);
3818
+ }
3819
+ /**
3820
+ * Get branch navigation info for a message.
3821
+ */
3822
+ getBranchInfo(messageId) {
3823
+ return this.reactState.getBranchInfo(messageId);
3824
+ }
3825
+ /**
3826
+ * Get all messages across all branches (for persistence).
3827
+ */
3828
+ getAllMessages() {
3829
+ return this.reactState.getAllMessages();
3830
+ }
3831
+ /**
3832
+ * Whether any message has siblings (branching has occurred).
3833
+ */
3834
+ get hasBranches() {
3835
+ return this.reactState.hasBranches;
3836
+ }
2243
3837
  /**
2244
3838
  * Dispose and cleanup
2245
3839
  */
@@ -2337,13 +3931,13 @@ function useMCPClient(config) {
2337
3931
  onNotification,
2338
3932
  ...clientConfig
2339
3933
  } = config;
2340
- const clientRef = react.useRef(null);
2341
- const mountedRef = react.useRef(true);
2342
- const [state, setState] = react.useState({
3934
+ const clientRef = React2.useRef(null);
3935
+ const mountedRef = React2.useRef(true);
3936
+ const [state, setState] = React2.useState({
2343
3937
  connectionState: "disconnected",
2344
3938
  tools: []
2345
3939
  });
2346
- const getClient = react.useCallback(() => {
3940
+ const getClient = React2.useCallback(() => {
2347
3941
  if (!clientRef.current) {
2348
3942
  clientRef.current = chunkJGPDQDY4_cjs.createMCPClient(clientConfig, {
2349
3943
  onConnectionStateChange: (newState) => {
@@ -2377,7 +3971,7 @@ function useMCPClient(config) {
2377
3971
  onError,
2378
3972
  onNotification
2379
3973
  ]);
2380
- const connect = react.useCallback(async () => {
3974
+ const connect = React2.useCallback(async () => {
2381
3975
  const client = getClient();
2382
3976
  try {
2383
3977
  setState((prev) => ({
@@ -2408,7 +4002,7 @@ function useMCPClient(config) {
2408
4002
  throw error;
2409
4003
  }
2410
4004
  }, [getClient]);
2411
- const disconnect = react.useCallback(async () => {
4005
+ const disconnect = React2.useCallback(async () => {
2412
4006
  const client = clientRef.current;
2413
4007
  if (client) {
2414
4008
  await client.disconnect();
@@ -2420,7 +4014,7 @@ function useMCPClient(config) {
2420
4014
  }
2421
4015
  }
2422
4016
  }, []);
2423
- const callTool = react.useCallback(
4017
+ const callTool = React2.useCallback(
2424
4018
  async (name, args) => {
2425
4019
  const client = clientRef.current;
2426
4020
  if (!client || !client.isConnected()) {
@@ -2434,7 +4028,7 @@ function useMCPClient(config) {
2434
4028
  },
2435
4029
  []
2436
4030
  );
2437
- const refreshTools = react.useCallback(async () => {
4031
+ const refreshTools = React2.useCallback(async () => {
2438
4032
  const client = clientRef.current;
2439
4033
  if (!client || !client.isConnected()) {
2440
4034
  throw new Error("MCP client not connected");
@@ -2445,7 +4039,7 @@ function useMCPClient(config) {
2445
4039
  }
2446
4040
  return tools;
2447
4041
  }, []);
2448
- react.useEffect(() => {
4042
+ React2.useEffect(() => {
2449
4043
  mountedRef.current = true;
2450
4044
  if (autoConnect) {
2451
4045
  connect().catch(() => {
@@ -2484,13 +4078,13 @@ function useMCPTools(config) {
2484
4078
  ...clientConfig
2485
4079
  } = config;
2486
4080
  const { registerTool, unregisterTool } = useCopilot();
2487
- const registeredToolsRef = react.useRef([]);
4081
+ const registeredToolsRef = React2.useRef([]);
2488
4082
  const mcpClient = useMCPClient(clientConfig);
2489
- const toolAdapter = react.useMemo(
4083
+ const toolAdapter = React2.useMemo(
2490
4084
  () => new chunkJGPDQDY4_cjs.MCPToolAdapter(clientConfig.name),
2491
4085
  [clientConfig.name]
2492
4086
  );
2493
- const toolDefinitions = react.useMemo(() => {
4087
+ const toolDefinitions = React2.useMemo(() => {
2494
4088
  if (!mcpClient.isConnected || mcpClient.state.tools.length === 0) {
2495
4089
  return [];
2496
4090
  }
@@ -2515,7 +4109,7 @@ function useMCPTools(config) {
2515
4109
  hidden,
2516
4110
  source
2517
4111
  ]);
2518
- react.useEffect(() => {
4112
+ React2.useEffect(() => {
2519
4113
  if (!autoRegister) {
2520
4114
  return;
2521
4115
  }
@@ -2530,22 +4124,919 @@ function useMCPTools(config) {
2530
4124
  }
2531
4125
  }
2532
4126
  return () => {
2533
- for (const toolName of registeredToolsRef.current) {
2534
- unregisterTool(toolName);
4127
+ for (const toolName of registeredToolsRef.current) {
4128
+ unregisterTool(toolName);
4129
+ }
4130
+ registeredToolsRef.current = [];
4131
+ };
4132
+ }, [
4133
+ autoRegister,
4134
+ mcpClient.isConnected,
4135
+ toolDefinitions,
4136
+ registerTool,
4137
+ unregisterTool
4138
+ ]);
4139
+ return {
4140
+ ...mcpClient,
4141
+ toolDefinitions
4142
+ };
4143
+ }
4144
+ var defaultTokenUsage = {
4145
+ current: 0,
4146
+ max: 128e3,
4147
+ percentage: 0,
4148
+ isApproaching: false
4149
+ };
4150
+ var defaultCompactionState = {
4151
+ rollingSummary: null,
4152
+ lastCompactionAt: null,
4153
+ compactionCount: 0,
4154
+ totalTokensSaved: 0,
4155
+ workingMemory: [],
4156
+ displayMessageCount: 0,
4157
+ llmMessageCount: 0
4158
+ };
4159
+ var defaultMessageHistoryConfig = {
4160
+ strategy: "none",
4161
+ maxContextTokens: 128e3,
4162
+ reserveForResponse: 4096,
4163
+ compactionThreshold: 0.75,
4164
+ recentBuffer: 10,
4165
+ toolResultMaxChars: 1e4,
4166
+ persistSession: false,
4167
+ storageKey: "copilot-session"
4168
+ };
4169
+ var MessageHistoryContext = React2.createContext({
4170
+ config: defaultMessageHistoryConfig,
4171
+ tokenUsage: defaultTokenUsage,
4172
+ compactionState: defaultCompactionState
4173
+ });
4174
+ function useMessageHistoryContext() {
4175
+ return React2.useContext(MessageHistoryContext);
4176
+ }
4177
+
4178
+ // src/react/message-history/message-utils.ts
4179
+ function toDisplayMessage(msg) {
4180
+ return {
4181
+ ...msg,
4182
+ timestamp: msg.createdAt instanceof Date ? msg.createdAt.getTime() : Date.now()
4183
+ };
4184
+ }
4185
+ function toLLMMessage(msg) {
4186
+ if (isCompactionMarker(msg)) {
4187
+ return {
4188
+ role: "system",
4189
+ content: `[Previous conversation summary]
4190
+ ${msg.content}`
4191
+ };
4192
+ }
4193
+ const base = {
4194
+ role: msg.role,
4195
+ content: msg.content
4196
+ };
4197
+ if (msg.toolCalls?.length) {
4198
+ base.tool_calls = msg.toolCalls;
4199
+ }
4200
+ if (msg.toolCallId) {
4201
+ base.tool_call_id = msg.toolCallId;
4202
+ }
4203
+ return base;
4204
+ }
4205
+ function toLLMMessages(messages) {
4206
+ return messages.map(toLLMMessage);
4207
+ }
4208
+ function keepToolPairsAtomic(messages) {
4209
+ if (messages.length === 0) return messages;
4210
+ const firstIdx = messages.findIndex((msg, i) => {
4211
+ if (msg.role !== "assistant" || !msg.toolCalls?.length) return false;
4212
+ const toolCallIds = new Set(msg.toolCalls.map((tc) => tc.id));
4213
+ const resultIds = new Set(
4214
+ messages.slice(i + 1).filter((m) => m.role === "tool" && m.toolCallId).map((m) => m.toolCallId)
4215
+ );
4216
+ return [...toolCallIds].some((id) => !resultIds.has(id));
4217
+ });
4218
+ if (firstIdx === -1) return messages;
4219
+ return messages.slice(firstIdx);
4220
+ }
4221
+ function findSafeWindowStart(messages, desiredStart) {
4222
+ for (let i = desiredStart; i < messages.length; i++) {
4223
+ const msg = messages[i];
4224
+ if (msg.role === "user" || msg.role === "assistant" && !msg.toolCalls?.length) {
4225
+ return i;
4226
+ }
4227
+ }
4228
+ return desiredStart;
4229
+ }
4230
+ function isCompactionMarker(msg) {
4231
+ return msg.role === "system" && msg.type === "compaction-marker";
4232
+ }
4233
+
4234
+ // src/react/message-history/token-counter.ts
4235
+ function estimateMessageTokens2(msg) {
4236
+ let chars = msg.content?.length ?? 0;
4237
+ if (msg.tool_calls?.length) {
4238
+ for (const tc of msg.tool_calls) {
4239
+ chars += JSON.stringify(tc).length;
4240
+ }
4241
+ }
4242
+ return Math.ceil(chars / 3.5) + 4;
4243
+ }
4244
+ function estimateMessagesTokens(messages) {
4245
+ return messages.reduce((sum, msg) => sum + estimateMessageTokens2(msg), 0);
4246
+ }
4247
+ function estimateTokens2(messages, mode = "fast") {
4248
+ if (mode === "off") return 0;
4249
+ return estimateMessagesTokens(messages);
4250
+ }
4251
+
4252
+ // src/react/message-history/strategies/sliding-window.ts
4253
+ function applySlidingWindow(messages, options) {
4254
+ const { tokenBudget, recentBuffer } = options;
4255
+ if (messages.length === 0) return messages;
4256
+ const systemMessages = messages.filter(
4257
+ (m) => m.role === "system" || isCompactionMarker(m)
4258
+ );
4259
+ const conversationMessages = messages.filter(
4260
+ (m) => m.role !== "system" && !isCompactionMarker(m)
4261
+ );
4262
+ const systemTokens = estimateMessagesTokens(toLLMMessages(systemMessages));
4263
+ const remainingBudget = tokenBudget - systemTokens;
4264
+ if (conversationMessages.length === 0) return systemMessages;
4265
+ const recentStart = Math.max(0, conversationMessages.length - recentBuffer);
4266
+ const recent = conversationMessages.slice(recentStart);
4267
+ const older = conversationMessages.slice(0, recentStart);
4268
+ const allTokens = estimateMessagesTokens(toLLMMessages(conversationMessages));
4269
+ if (allTokens <= remainingBudget) {
4270
+ return messages;
4271
+ }
4272
+ const recentTokens = estimateMessagesTokens(toLLMMessages(recent));
4273
+ let available = remainingBudget - recentTokens;
4274
+ const included = [];
4275
+ for (let i = older.length - 1; i >= 0; i--) {
4276
+ const msgTokens = estimateMessagesTokens(toLLMMessages([older[i]]));
4277
+ if (available - msgTokens < 0) break;
4278
+ included.unshift(older[i]);
4279
+ available -= msgTokens;
4280
+ }
4281
+ const combined = [...included, ...recent];
4282
+ const safeStart = findSafeWindowStart(combined, 0);
4283
+ const safeWindow = combined.slice(safeStart);
4284
+ return [...systemMessages, ...safeWindow];
4285
+ }
4286
+ function truncateToolResults(messages, maxChars) {
4287
+ if (maxChars === 0) return messages;
4288
+ return messages.map((msg) => {
4289
+ if (msg.role !== "tool") return msg;
4290
+ if (!msg.content || msg.content.length <= maxChars) return msg;
4291
+ return {
4292
+ ...msg,
4293
+ content: msg.content.slice(0, maxChars) + `
4294
+ [truncated \u2014 original ${msg.content.length} chars, limit ${maxChars}]`
4295
+ };
4296
+ });
4297
+ }
4298
+
4299
+ // src/react/message-history/strategies/selective-prune.ts
4300
+ function applySelectivePrune(displayMessages, recentBuffer, options = {}) {
4301
+ const {
4302
+ toolResultAgeTurns = 3,
4303
+ stripOldReasoning = true,
4304
+ deduplicateSkills = true
4305
+ } = options;
4306
+ const cutoff = Math.max(0, displayMessages.length - recentBuffer);
4307
+ const seenSkillContent = /* @__PURE__ */ new Set();
4308
+ return displayMessages.map((msg, idx) => {
4309
+ const llm = toLLMMessage(msg);
4310
+ const isOld = idx < cutoff;
4311
+ if (deduplicateSkills && msg.role === "system" && llm.content) {
4312
+ const key = llm.content.slice(0, 100);
4313
+ if (seenSkillContent.has(key)) {
4314
+ return { ...llm, content: "[skill instruction \u2014 deduplicated]" };
4315
+ }
4316
+ seenSkillContent.add(key);
4317
+ }
4318
+ if (!isOld) return llm;
4319
+ if (stripOldReasoning && msg.role === "assistant" && msg.thinking) {
4320
+ llm.content = llm.content;
4321
+ }
4322
+ if (msg.role === "tool" && llm.content) {
4323
+ const originalSize = llm.content.length;
4324
+ if (originalSize > 500) {
4325
+ const stub = buildToolResultStub(msg, llm.content);
4326
+ return {
4327
+ role: "tool",
4328
+ tool_call_id: llm.tool_call_id,
4329
+ content: JSON.stringify(stub)
4330
+ };
4331
+ }
4332
+ }
4333
+ return llm;
4334
+ });
4335
+ }
4336
+ function buildToolResultStub(msg, content) {
4337
+ return {
4338
+ type: "compacted-tool-result",
4339
+ toolName: msg.metadata?.toolName ?? "tool",
4340
+ toolCallId: msg.toolCallId ?? "",
4341
+ args: msg.metadata?.toolArgs ?? {},
4342
+ executedAt: msg.timestamp,
4343
+ status: content.includes('"error"') ? "error" : "success",
4344
+ originalSize: content.length,
4345
+ summary: buildSummary(content),
4346
+ extract: content.slice(0, 200)
4347
+ };
4348
+ }
4349
+ function buildSummary(content) {
4350
+ try {
4351
+ const parsed = JSON.parse(content);
4352
+ if (parsed?.message) return String(parsed.message).slice(0, 120);
4353
+ if (parsed?.error) return `Error: ${String(parsed.error).slice(0, 100)}`;
4354
+ if (Array.isArray(parsed)) return `Array result \u2014 ${parsed.length} items`;
4355
+ const keys = Object.keys(parsed).slice(0, 3).join(", ");
4356
+ return `Object result \u2014 keys: ${keys}`;
4357
+ } catch {
4358
+ return content.slice(0, 120);
4359
+ }
4360
+ }
4361
+
4362
+ // src/react/message-history/strategies/summary-buffer.ts
4363
+ function buildSummaryBufferContext(displayMessages, compactionState, options) {
4364
+ const { recentBuffer } = options;
4365
+ const systemMessages = displayMessages.filter(
4366
+ (m) => m.role === "system" || isCompactionMarker(m)
4367
+ );
4368
+ const conversationMessages = displayMessages.filter(
4369
+ (m) => m.role !== "system" && !isCompactionMarker(m)
4370
+ );
4371
+ const recentStart = Math.max(0, conversationMessages.length - recentBuffer);
4372
+ const recentMessages = conversationMessages.slice(recentStart);
4373
+ const result = [];
4374
+ if (compactionState.workingMemory.length > 0) {
4375
+ result.push({
4376
+ role: "system",
4377
+ content: `[Working memory \u2014 always active]
4378
+ ${compactionState.workingMemory.join("\n")}`
4379
+ });
4380
+ }
4381
+ if (compactionState.rollingSummary) {
4382
+ result.push({
4383
+ role: "system",
4384
+ content: `[Previous conversation summary]
4385
+ ${compactionState.rollingSummary}`
4386
+ });
4387
+ }
4388
+ result.push(...toLLMMessages(systemMessages));
4389
+ result.push(...toLLMMessages(recentMessages));
4390
+ return result;
4391
+ }
4392
+ async function runCompaction(displayMessages, compactionState, options) {
4393
+ const { recentBuffer, compactionUrl, summarizer } = options;
4394
+ const conversationMessages = displayMessages.filter(
4395
+ (m) => m.role !== "system" && !isCompactionMarker(m)
4396
+ );
4397
+ const cutoff = Math.max(0, conversationMessages.length - recentBuffer);
4398
+ const toSummarize = conversationMessages.slice(0, cutoff);
4399
+ if (toSummarize.length === 0) {
4400
+ return { llmMessages: toLLMMessages(displayMessages) };
4401
+ }
4402
+ const llmToSummarize = toLLMMessages(toSummarize);
4403
+ const originalTokens = estimateMessagesTokens(llmToSummarize);
4404
+ let newSummary;
4405
+ if (summarizer) {
4406
+ newSummary = await summarizer(llmToSummarize);
4407
+ } else if (compactionUrl) {
4408
+ newSummary = await fetchSummary(compactionUrl, {
4409
+ messages: llmToSummarize,
4410
+ existingSummary: compactionState.rollingSummary,
4411
+ workingMemory: compactionState.workingMemory
4412
+ });
4413
+ } else {
4414
+ newSummary = buildFallbackSummary(
4415
+ llmToSummarize,
4416
+ compactionState.rollingSummary
4417
+ );
4418
+ }
4419
+ const summaryTokens = Math.ceil(newSummary.length / 3.5);
4420
+ const tokensSaved = Math.max(0, originalTokens - summaryTokens);
4421
+ return {
4422
+ llmMessages: buildSummaryBufferContext(
4423
+ displayMessages,
4424
+ { ...compactionState, rollingSummary: newSummary },
4425
+ options
4426
+ ),
4427
+ newSummary,
4428
+ tokensSaved,
4429
+ messagesSummarized: toSummarize.length
4430
+ };
4431
+ }
4432
+ async function fetchSummary(url, body) {
4433
+ const res = await fetch(url, {
4434
+ method: "POST",
4435
+ headers: { "Content-Type": "application/json" },
4436
+ body: JSON.stringify(body)
4437
+ });
4438
+ if (!res.ok) {
4439
+ throw new Error(`Compaction endpoint returned ${res.status}`);
4440
+ }
4441
+ const data = await res.json();
4442
+ if (!data?.summary) {
4443
+ throw new Error("Compaction endpoint did not return { summary: string }");
4444
+ }
4445
+ return data.summary;
4446
+ }
4447
+ function buildFallbackSummary(messages, existingSummary) {
4448
+ const lines = messages.filter((m) => m.role === "user" || m.role === "assistant").map((m) => `${m.role}: ${(m.content ?? "").slice(0, 200)}`).join("\n");
4449
+ return existingSummary ? `${existingSummary}
4450
+
4451
+ [Additional context]
4452
+ ${lines}` : lines;
4453
+ }
4454
+
4455
+ // src/react/message-history/session-persistence.ts
4456
+ var IDB_DB_NAME = "copilot-sdk";
4457
+ var IDB_STORE = "sessions";
4458
+ var IDB_VERSION = 1;
4459
+ function saveCompactionState(storageKey, state) {
4460
+ try {
4461
+ localStorage.setItem(
4462
+ `${storageKey}-state`,
4463
+ JSON.stringify({ ...state, _savedAt: Date.now() })
4464
+ );
4465
+ } catch {
4466
+ }
4467
+ }
4468
+ function loadCompactionState(storageKey) {
4469
+ try {
4470
+ const raw = localStorage.getItem(`${storageKey}-state`);
4471
+ if (!raw) return null;
4472
+ const parsed = JSON.parse(raw);
4473
+ delete parsed._savedAt;
4474
+ return parsed;
4475
+ } catch {
4476
+ return null;
4477
+ }
4478
+ }
4479
+ function clearCompactionState(storageKey) {
4480
+ try {
4481
+ localStorage.removeItem(`${storageKey}-state`);
4482
+ } catch {
4483
+ }
4484
+ }
4485
+ function openDB() {
4486
+ return new Promise((resolve, reject) => {
4487
+ const req = indexedDB.open(IDB_DB_NAME, IDB_VERSION);
4488
+ req.onupgradeneeded = () => {
4489
+ req.result.createObjectStore(IDB_STORE, { keyPath: "sessionId" });
4490
+ };
4491
+ req.onsuccess = () => resolve(req.result);
4492
+ req.onerror = () => reject(req.error);
4493
+ });
4494
+ }
4495
+ async function saveDisplayMessages(storageKey, messages) {
4496
+ try {
4497
+ const db = await openDB();
4498
+ const tx = db.transaction(IDB_STORE, "readwrite");
4499
+ tx.objectStore(IDB_STORE).put({
4500
+ sessionId: storageKey,
4501
+ messages,
4502
+ savedAt: Date.now()
4503
+ });
4504
+ await new Promise((res, rej) => {
4505
+ tx.oncomplete = () => res();
4506
+ tx.onerror = () => rej(tx.error);
4507
+ });
4508
+ db.close();
4509
+ } catch {
4510
+ }
4511
+ }
4512
+ async function loadDisplayMessages(storageKey) {
4513
+ try {
4514
+ const db = await openDB();
4515
+ const tx = db.transaction(IDB_STORE, "readonly");
4516
+ const req = tx.objectStore(IDB_STORE).get(storageKey);
4517
+ const result = await new Promise((res, rej) => {
4518
+ req.onsuccess = () => res(req.result);
4519
+ req.onerror = () => rej(req.error);
4520
+ });
4521
+ db.close();
4522
+ return result?.messages ?? null;
4523
+ } catch {
4524
+ return null;
4525
+ }
4526
+ }
4527
+ async function clearDisplayMessages(storageKey) {
4528
+ try {
4529
+ const db = await openDB();
4530
+ const tx = db.transaction(IDB_STORE, "readwrite");
4531
+ tx.objectStore(IDB_STORE).delete(storageKey);
4532
+ await new Promise((res, rej) => {
4533
+ tx.oncomplete = () => res();
4534
+ tx.onerror = () => rej(tx.error);
4535
+ });
4536
+ db.close();
4537
+ } catch {
4538
+ }
4539
+ }
4540
+ async function clearSession(storageKey) {
4541
+ clearCompactionState(storageKey);
4542
+ await clearDisplayMessages(storageKey);
4543
+ }
4544
+
4545
+ // src/react/message-history/useMessageHistory.ts
4546
+ var DEFAULT_COMPACTION_STATE = {
4547
+ rollingSummary: null,
4548
+ lastCompactionAt: null,
4549
+ compactionCount: 0,
4550
+ totalTokensSaved: 0,
4551
+ workingMemory: [],
4552
+ displayMessageCount: 0,
4553
+ llmMessageCount: 0
4554
+ };
4555
+ function useMessageHistory(options = {}) {
4556
+ const { messages } = useCopilot();
4557
+ const ctx = useMessageHistoryContext();
4558
+ const config = React2.useMemo(
4559
+ () => ({ ...defaultMessageHistoryConfig, ...ctx.config, ...options }),
4560
+ // eslint-disable-next-line react-hooks/exhaustive-deps
4561
+ [
4562
+ ctx.config,
4563
+ options.strategy,
4564
+ options.maxContextTokens,
4565
+ options.recentBuffer,
4566
+ options.compactionThreshold
4567
+ ]
4568
+ );
4569
+ const storageKey = config.storageKey ?? "copilot-session";
4570
+ const strategy = options.skipCompaction ? "none" : config.strategy ?? "none";
4571
+ const [compactionState, setCompactionState] = React2.useState(() => {
4572
+ if (config.persistSession) {
4573
+ return loadCompactionState(storageKey) ?? DEFAULT_COMPACTION_STATE;
4574
+ }
4575
+ return DEFAULT_COMPACTION_STATE;
4576
+ });
4577
+ const displayMessages = React2.useMemo(
4578
+ () => messages.map(toDisplayMessage),
4579
+ [messages]
4580
+ );
4581
+ const restoredRef = React2.useRef(false);
4582
+ React2.useEffect(() => {
4583
+ if (!config.persistSession || restoredRef.current) return;
4584
+ restoredRef.current = true;
4585
+ loadDisplayMessages(storageKey).then((saved) => {
4586
+ if (saved?.length && messages.length === 0) ;
4587
+ });
4588
+ }, [config.persistSession, storageKey, messages.length]);
4589
+ React2.useEffect(() => {
4590
+ if (!config.persistSession || displayMessages.length === 0) return;
4591
+ saveDisplayMessages(storageKey, displayMessages);
4592
+ }, [config.persistSession, storageKey, displayMessages]);
4593
+ const llmMessages = React2.useMemo(() => {
4594
+ const maxTokens = config.maxContextTokens ?? 128e3;
4595
+ const reserve = config.reserveForResponse ?? 4096;
4596
+ const tokenBudget = maxTokens - reserve;
4597
+ const recentBuffer = config.recentBuffer ?? 10;
4598
+ const maxChars = config.toolResultMaxChars ?? 1e4;
4599
+ let result;
4600
+ switch (strategy) {
4601
+ case "sliding-window": {
4602
+ const windowed = applySlidingWindow(displayMessages, {
4603
+ tokenBudget,
4604
+ recentBuffer
4605
+ });
4606
+ result = truncateToolResults(toLLMMessages(windowed), maxChars);
4607
+ break;
4608
+ }
4609
+ case "selective-prune": {
4610
+ result = truncateToolResults(
4611
+ applySelectivePrune(displayMessages, recentBuffer),
4612
+ maxChars
4613
+ );
4614
+ break;
4615
+ }
4616
+ case "summary-buffer": {
4617
+ result = truncateToolResults(
4618
+ buildSummaryBufferContext(displayMessages, compactionState, {
4619
+ recentBuffer,
4620
+ compactionThreshold: config.compactionThreshold ?? 0.75,
4621
+ compactionUrl: config.compactionUrl,
4622
+ summarizer: options.summarizer
4623
+ }),
4624
+ maxChars
4625
+ );
4626
+ break;
4627
+ }
4628
+ default:
4629
+ result = truncateToolResults(toLLMMessages(displayMessages), maxChars);
4630
+ }
4631
+ return result;
4632
+ }, [displayMessages, compactionState, strategy, config, options.summarizer]);
4633
+ const tokenUsage = React2.useMemo(() => {
4634
+ const mode = options.tokenEstimation ?? "fast";
4635
+ const current = estimateTokens2(toLLMMessages(displayMessages), mode);
4636
+ const max = config.maxContextTokens ?? 128e3;
4637
+ const threshold = config.compactionThreshold ?? 0.75;
4638
+ const percentage = current / max;
4639
+ return { current, max, percentage, isApproaching: percentage >= threshold };
4640
+ }, [
4641
+ displayMessages,
4642
+ config.maxContextTokens,
4643
+ config.compactionThreshold,
4644
+ options.tokenEstimation
4645
+ ]);
4646
+ React2.useEffect(() => {
4647
+ if (config.onTokenUsage && tokenUsage.current > 0) {
4648
+ config.onTokenUsage(tokenUsage);
4649
+ }
4650
+ }, [tokenUsage, config.onTokenUsage]);
4651
+ React2.useEffect(() => {
4652
+ if (config.persistSession) {
4653
+ saveCompactionState(storageKey, {
4654
+ ...compactionState,
4655
+ displayMessageCount: displayMessages.length,
4656
+ llmMessageCount: llmMessages.length
4657
+ });
4658
+ }
4659
+ }, [
4660
+ config.persistSession,
4661
+ storageKey,
4662
+ compactionState,
4663
+ displayMessages.length,
4664
+ llmMessages.length
4665
+ ]);
4666
+ const isCompactingRef = React2.useRef(false);
4667
+ const [isCompacting, setIsCompacting] = React2.useState(false);
4668
+ React2.useEffect(() => {
4669
+ if (strategy !== "summary-buffer" || options.skipCompaction || isCompactingRef.current || !tokenUsage.isApproaching)
4670
+ return;
4671
+ isCompactingRef.current = true;
4672
+ setIsCompacting(true);
4673
+ runCompaction(displayMessages, compactionState, {
4674
+ recentBuffer: config.recentBuffer ?? 10,
4675
+ tokenBudget: (config.maxContextTokens ?? 128e3) - (config.reserveForResponse ?? 4096),
4676
+ compactionThreshold: config.compactionThreshold ?? 0.75,
4677
+ compactionUrl: config.compactionUrl,
4678
+ summarizer: options.summarizer
4679
+ }).then((result) => {
4680
+ if (result.newSummary) {
4681
+ const event = {
4682
+ type: "auto",
4683
+ compactionCount: compactionState.compactionCount + 1,
4684
+ messagesSummarized: result.messagesSummarized ?? 0,
4685
+ tokensSaved: result.tokensSaved ?? 0,
4686
+ timestamp: Date.now()
4687
+ };
4688
+ setCompactionState((prev) => ({
4689
+ ...prev,
4690
+ rollingSummary: result.newSummary,
4691
+ lastCompactionAt: Date.now(),
4692
+ compactionCount: prev.compactionCount + 1,
4693
+ totalTokensSaved: prev.totalTokensSaved + (result.tokensSaved ?? 0)
4694
+ }));
4695
+ config.onCompaction?.(event);
4696
+ }
4697
+ }).finally(() => {
4698
+ isCompactingRef.current = false;
4699
+ setIsCompacting(false);
4700
+ });
4701
+ }, [tokenUsage.isApproaching, strategy]);
4702
+ const compactSession = React2.useCallback(
4703
+ async (instructions) => {
4704
+ if (strategy !== "summary-buffer") return;
4705
+ const result = await runCompaction(displayMessages, compactionState, {
4706
+ recentBuffer: config.recentBuffer ?? 10,
4707
+ tokenBudget: (config.maxContextTokens ?? 128e3) - (config.reserveForResponse ?? 4096),
4708
+ compactionThreshold: config.compactionThreshold ?? 0.75,
4709
+ compactionUrl: config.compactionUrl,
4710
+ summarizer: options.summarizer ? (msgs) => options.summarizer(msgs) : instructions ? (msgs) => fetchWithInstructions(config.compactionUrl, msgs, instructions) : void 0
4711
+ });
4712
+ if (result.newSummary) {
4713
+ const event = {
4714
+ type: "manual",
4715
+ compactionCount: compactionState.compactionCount + 1,
4716
+ messagesSummarized: result.messagesSummarized ?? 0,
4717
+ tokensSaved: result.tokensSaved ?? 0,
4718
+ timestamp: Date.now()
4719
+ };
4720
+ setCompactionState((prev) => ({
4721
+ ...prev,
4722
+ rollingSummary: result.newSummary,
4723
+ lastCompactionAt: Date.now(),
4724
+ compactionCount: prev.compactionCount + 1,
4725
+ totalTokensSaved: prev.totalTokensSaved + (result.tokensSaved ?? 0)
4726
+ }));
4727
+ config.onCompaction?.(event);
4728
+ }
4729
+ },
4730
+ [displayMessages, compactionState, config, strategy, options.summarizer]
4731
+ );
4732
+ const addToWorkingMemory = React2.useCallback((fact) => {
4733
+ setCompactionState((prev) => ({
4734
+ ...prev,
4735
+ workingMemory: [...prev.workingMemory, fact]
4736
+ }));
4737
+ }, []);
4738
+ const clearWorkingMemory = React2.useCallback(() => {
4739
+ setCompactionState((prev) => ({ ...prev, workingMemory: [] }));
4740
+ }, []);
4741
+ const resetSession = React2.useCallback(async () => {
4742
+ setCompactionState(DEFAULT_COMPACTION_STATE);
4743
+ if (config.persistSession) {
4744
+ await clearSession(storageKey);
4745
+ }
4746
+ }, [config.persistSession, storageKey]);
4747
+ return {
4748
+ displayMessages,
4749
+ llmMessages,
4750
+ tokenUsage,
4751
+ isCompacting,
4752
+ compactionState: {
4753
+ ...compactionState,
4754
+ displayMessageCount: displayMessages.length,
4755
+ llmMessageCount: llmMessages.length
4756
+ },
4757
+ compactSession,
4758
+ addToWorkingMemory,
4759
+ clearWorkingMemory,
4760
+ resetSession
4761
+ };
4762
+ }
4763
+ async function fetchWithInstructions(url, messages, instructions) {
4764
+ const res = await fetch(url, {
4765
+ method: "POST",
4766
+ headers: { "Content-Type": "application/json" },
4767
+ body: JSON.stringify({ messages, instructions })
4768
+ });
4769
+ const data = await res.json();
4770
+ return data.summary;
4771
+ }
4772
+ var SkillContext = React2.createContext(null);
4773
+ function useSkillContext() {
4774
+ const ctx = React2.useContext(SkillContext);
4775
+ if (!ctx) {
4776
+ throw new Error("useSkillContext must be used within <SkillProvider>");
4777
+ }
4778
+ return ctx;
4779
+ }
4780
+ function useAIContext(item) {
4781
+ const { addContext, removeContext } = useCopilot();
4782
+ const contextIdRef = React2.useRef(null);
4783
+ const serializedData = typeof item.data === "string" ? item.data : JSON.stringify(item.data);
4784
+ React2.useEffect(() => {
4785
+ const formattedValue = typeof item.data === "string" ? item.data : JSON.stringify(item.data, null, 2);
4786
+ const contextString = item.description ? `${item.description}:
4787
+ ${formattedValue}` : `${item.key}:
4788
+ ${formattedValue}`;
4789
+ contextIdRef.current = addContext(contextString, item.parentId);
4790
+ return () => {
4791
+ if (contextIdRef.current) {
4792
+ removeContext(contextIdRef.current);
4793
+ contextIdRef.current = null;
4794
+ }
4795
+ };
4796
+ }, [
4797
+ item.key,
4798
+ serializedData,
4799
+ item.description,
4800
+ item.parentId,
4801
+ addContext,
4802
+ removeContext
4803
+ ]);
4804
+ return contextIdRef.current ?? void 0;
4805
+ }
4806
+ function useAIContexts(items) {
4807
+ const { addContext, removeContext } = useCopilot();
4808
+ const contextIdsRef = React2.useRef([]);
4809
+ const serializedItems = JSON.stringify(
4810
+ items.map((item) => ({
4811
+ key: item.key,
4812
+ data: item.data,
4813
+ description: item.description,
4814
+ parentId: item.parentId
4815
+ }))
4816
+ );
4817
+ React2.useEffect(() => {
4818
+ contextIdsRef.current.forEach((id) => removeContext(id));
4819
+ contextIdsRef.current = [];
4820
+ const parsedItems = JSON.parse(serializedItems);
4821
+ for (const item of parsedItems) {
4822
+ const formattedValue = typeof item.data === "string" ? item.data : JSON.stringify(item.data, null, 2);
4823
+ const contextString = item.description ? `${item.description}:
4824
+ ${formattedValue}` : `${item.key}:
4825
+ ${formattedValue}`;
4826
+ const id = addContext(contextString, item.parentId);
4827
+ contextIdsRef.current.push(id);
4828
+ }
4829
+ return () => {
4830
+ contextIdsRef.current.forEach((id) => removeContext(id));
4831
+ contextIdsRef.current = [];
4832
+ };
4833
+ }, [serializedItems, addContext, removeContext]);
4834
+ }
4835
+ function isZodSchema(value) {
4836
+ if (value === null || typeof value !== "object") return false;
4837
+ const obj = value;
4838
+ return "_def" in obj && typeof obj._def === "object" || "_zod" in obj && typeof obj._zod === "object" || "~standard" in obj;
4839
+ }
4840
+ function useTool(config, dependencies = []) {
4841
+ const { registerTool, unregisterTool } = useCopilot();
4842
+ const configRef = React2.useRef(config);
4843
+ configRef.current = config;
4844
+ const inputSchema = React2.useMemo(() => {
4845
+ if (isZodSchema(config.inputSchema)) {
4846
+ return chunkI3SQUNTT_cjs.zodToJsonSchema(config.inputSchema);
4847
+ }
4848
+ return config.inputSchema;
4849
+ }, [config.inputSchema]);
4850
+ React2.useEffect(() => {
4851
+ const tool2 = {
4852
+ name: config.name,
4853
+ description: config.description,
4854
+ location: "client",
4855
+ inputSchema,
4856
+ handler: async (params, context) => {
4857
+ return configRef.current.handler(params, context);
4858
+ },
4859
+ render: config.render,
4860
+ available: config.available ?? true,
4861
+ needsApproval: config.needsApproval,
4862
+ approvalMessage: config.approvalMessage,
4863
+ hidden: config.hidden,
4864
+ deferLoading: config.deferLoading,
4865
+ profiles: config.profiles,
4866
+ searchKeywords: config.searchKeywords,
4867
+ group: config.group,
4868
+ category: config.category,
4869
+ resultConfig: config.resultConfig,
4870
+ title: config.title,
4871
+ executingTitle: config.executingTitle,
4872
+ completedTitle: config.completedTitle,
4873
+ aiResponseMode: config.aiResponseMode,
4874
+ aiContext: config.aiContext
4875
+ };
4876
+ registerTool(tool2);
4877
+ return () => {
4878
+ unregisterTool(config.name);
4879
+ };
4880
+ }, [config.name, inputSchema, ...dependencies]);
4881
+ }
4882
+ function useTools(tools) {
4883
+ const { registerTool, unregisterTool } = useCopilot();
4884
+ const registeredToolsRef = React2.useRef([]);
4885
+ const toolsRef = React2.useRef(tools);
4886
+ toolsRef.current = tools;
4887
+ const toolsKey = Object.keys(tools).sort().join(",");
4888
+ React2.useEffect(() => {
4889
+ const currentTools = toolsRef.current;
4890
+ const toolNames = [];
4891
+ for (const [name, toolDef] of Object.entries(currentTools)) {
4892
+ const fullTool = {
4893
+ ...toolDef,
4894
+ name
4895
+ // Use the key as the name
4896
+ };
4897
+ registerTool(fullTool);
4898
+ toolNames.push(name);
4899
+ }
4900
+ registeredToolsRef.current = toolNames;
4901
+ return () => {
4902
+ for (const name of registeredToolsRef.current) {
4903
+ unregisterTool(name);
4904
+ }
4905
+ registeredToolsRef.current = [];
4906
+ };
4907
+ }, [toolsKey]);
4908
+ }
4909
+ function SkillContextInjector({
4910
+ registry,
4911
+ skills
4912
+ }) {
4913
+ const catalog = React2.useMemo(() => registry.buildCatalog(), [skills]);
4914
+ const eagerContent = React2.useMemo(() => registry.buildEagerContent(), [skills]);
4915
+ useAIContext({
4916
+ key: "__skill_catalog__",
4917
+ description: "Skills the AI can load on demand",
4918
+ data: catalog ? `You have access to specialized skills. Call load_skill({ name }) when relevant.
4919
+
4920
+ ${catalog}` : ""
4921
+ });
4922
+ useAIContext({
4923
+ key: "__skill_eager__",
4924
+ description: "Always-active skill instructions",
4925
+ data: eagerContent
4926
+ });
4927
+ return null;
4928
+ }
4929
+ function SkillRequestSync({ skills }) {
4930
+ const { setInlineSkills } = useCopilot();
4931
+ React2.useEffect(() => {
4932
+ const inlineSkills = skills.filter((s) => s.source.type === "inline").map((s) => ({
4933
+ name: s.name,
4934
+ description: s.description,
4935
+ content: s.content,
4936
+ strategy: s.strategy
4937
+ }));
4938
+ setInlineSkills(inlineSkills);
4939
+ }, [skills, setInlineSkills]);
4940
+ return null;
4941
+ }
4942
+ function SkillToolRegistrar({
4943
+ registry,
4944
+ skills
4945
+ }) {
4946
+ useTool(
4947
+ {
4948
+ name: "load_skill",
4949
+ description: "Load a skill by name to get full instructions for a specialized task.",
4950
+ inputSchema: {
4951
+ type: "object",
4952
+ properties: {
4953
+ name: {
4954
+ type: "string",
4955
+ description: "The name of the skill to load."
4956
+ }
4957
+ },
4958
+ required: ["name"]
4959
+ },
4960
+ handler: async ({ name }) => {
4961
+ const skill = registry.get(name);
4962
+ if (!skill) {
4963
+ const available = registry.getAuto().map((s) => s.name).join(", ") || "none";
4964
+ return {
4965
+ success: false,
4966
+ error: `Skill "${name}" not found. Available skills: ${available}`
4967
+ };
4968
+ }
4969
+ const sourceTypeMap = {
4970
+ inline: "client-inline",
4971
+ url: "remote-url",
4972
+ file: "server-dir"
4973
+ };
4974
+ return {
4975
+ success: true,
4976
+ name: skill.name,
4977
+ description: skill.description,
4978
+ strategy: skill.strategy ?? "auto",
4979
+ content: skill.content,
4980
+ source: sourceTypeMap[skill.source.type]
4981
+ };
4982
+ }
4983
+ },
4984
+ // eslint-disable-next-line react-hooks/exhaustive-deps
4985
+ [skills]
4986
+ );
4987
+ return null;
4988
+ }
4989
+ function SkillProvider({
4990
+ children,
4991
+ skills: skillsProp
4992
+ }) {
4993
+ const registryRef = React2.useRef(null);
4994
+ if (registryRef.current === null) {
4995
+ registryRef.current = new chunkBJYA5NDL_cjs.SkillRegistry();
4996
+ }
4997
+ const registry = registryRef.current;
4998
+ const [skills, setSkills] = React2.useState([]);
4999
+ React2.useEffect(() => {
5000
+ if (!skillsProp?.length) return;
5001
+ for (const def of skillsProp) {
5002
+ if (def.source.type !== "inline") {
5003
+ console.warn(
5004
+ `[copilot-sdk/skills] Client-side SkillProvider only supports inline skills. Skill "${def.name}" has source type "${def.source.type}" and will be skipped. Use loadSkills() on the server for file/url skills.`
5005
+ );
5006
+ continue;
5007
+ }
5008
+ const resolved = {
5009
+ ...def,
5010
+ content: def.source.content
5011
+ };
5012
+ registry.register(resolved);
5013
+ }
5014
+ setSkills(registry.getAll());
5015
+ return () => {
5016
+ for (const def of skillsProp ?? []) {
5017
+ registry.unregister(def.name);
2535
5018
  }
2536
- registeredToolsRef.current = [];
5019
+ setSkills(registry.getAll());
2537
5020
  };
2538
- }, [
2539
- autoRegister,
2540
- mcpClient.isConnected,
2541
- toolDefinitions,
2542
- registerTool,
2543
- unregisterTool
2544
- ]);
2545
- return {
2546
- ...mcpClient,
2547
- toolDefinitions
2548
- };
5021
+ }, [skillsProp]);
5022
+ const register = React2.useCallback((skill) => {
5023
+ registry.register(skill);
5024
+ setSkills(registry.getAll());
5025
+ }, []);
5026
+ const unregister = React2.useCallback((name) => {
5027
+ registry.unregister(name);
5028
+ setSkills(registry.getAll());
5029
+ }, []);
5030
+ const contextValue = React2.useMemo(
5031
+ () => ({ registry, register, unregister, skills }),
5032
+ [register, unregister, skills]
5033
+ );
5034
+ return /* @__PURE__ */ jsxRuntime.jsxs(SkillContext.Provider, { value: contextValue, children: [
5035
+ /* @__PURE__ */ jsxRuntime.jsx(SkillContextInjector, { registry, skills }),
5036
+ /* @__PURE__ */ jsxRuntime.jsx(SkillToolRegistrar, { registry, skills }),
5037
+ /* @__PURE__ */ jsxRuntime.jsx(SkillRequestSync, { skills }),
5038
+ children
5039
+ ] });
2549
5040
  }
2550
5041
  function MCPConnection({ config }) {
2551
5042
  useMCPTools({
@@ -2559,9 +5050,126 @@ function MCPConnection({ config }) {
2559
5050
  });
2560
5051
  return null;
2561
5052
  }
2562
- var CopilotContext = react.createContext(null);
5053
+ var COMPACTING_MARKER_ID = "__compacting-in-progress__";
5054
+ function MessageHistoryBridge({
5055
+ chatRef
5056
+ }) {
5057
+ const { compactionState, tokenUsage } = useMessageHistory();
5058
+ const ctx = useMessageHistoryContext();
5059
+ const loaderAddedRef = React2.useRef(false);
5060
+ const prevCompactionCountRef = React2.useRef(compactionState.compactionCount);
5061
+ React2.useEffect(() => {
5062
+ if (!tokenUsage.isApproaching) {
5063
+ loaderAddedRef.current = false;
5064
+ return;
5065
+ }
5066
+ if (loaderAddedRef.current) return;
5067
+ const chat = chatRef.current;
5068
+ if (!chat) return;
5069
+ const alreadyAdded = chat.messages.some(
5070
+ (m) => m.id === COMPACTING_MARKER_ID
5071
+ );
5072
+ if (alreadyAdded) return;
5073
+ loaderAddedRef.current = true;
5074
+ const loading = {
5075
+ id: COMPACTING_MARKER_ID,
5076
+ role: "system",
5077
+ content: "Compacting conversation\u2026",
5078
+ createdAt: /* @__PURE__ */ new Date(),
5079
+ metadata: { type: "compaction-marker", compacting: true }
5080
+ };
5081
+ chat.setMessages([...chat.messages, loading]);
5082
+ }, [tokenUsage.isApproaching]);
5083
+ React2.useEffect(() => {
5084
+ if (compactionState.compactionCount <= prevCompactionCountRef.current)
5085
+ return;
5086
+ prevCompactionCountRef.current = compactionState.compactionCount;
5087
+ loaderAddedRef.current = false;
5088
+ const chat = chatRef.current;
5089
+ if (!chat) return;
5090
+ const hasLoader = chat.messages.some((m) => m.id === COMPACTING_MARKER_ID);
5091
+ const base = hasLoader ? chat.messages.map(
5092
+ (m) => m.id === COMPACTING_MARKER_ID ? {
5093
+ ...m,
5094
+ id: `compaction-marker-${compactionState.compactionCount}`,
5095
+ content: `Conversation compacted \u2014 context window refreshed`,
5096
+ metadata: { type: "compaction-marker", compacting: false }
5097
+ } : m
5098
+ ) : [
5099
+ ...chat.messages,
5100
+ {
5101
+ id: `compaction-marker-${compactionState.compactionCount}`,
5102
+ role: "system",
5103
+ content: `Conversation compacted \u2014 context window refreshed`,
5104
+ createdAt: /* @__PURE__ */ new Date(),
5105
+ metadata: { type: "compaction-marker", compacting: false }
5106
+ }
5107
+ ];
5108
+ chat.setMessages(base);
5109
+ }, [compactionState.compactionCount]);
5110
+ const compactionStateRef = React2.useRef(compactionState);
5111
+ compactionStateRef.current = compactionState;
5112
+ const configRef = React2.useRef(ctx.config);
5113
+ configRef.current = ctx.config;
5114
+ React2.useEffect(() => {
5115
+ const chat = chatRef.current;
5116
+ if (!chat) return;
5117
+ chat.setRequestMessageTransform((allMessages) => {
5118
+ if (allMessages.length === 0) return allMessages;
5119
+ let lastUserIdx = -1;
5120
+ for (let i = allMessages.length - 1; i >= 0; i--) {
5121
+ if (allMessages[i].role === "user") {
5122
+ lastUserIdx = i;
5123
+ break;
5124
+ }
5125
+ }
5126
+ if (lastUserIdx === -1) return allMessages;
5127
+ const historyMessages = allMessages.slice(0, lastUserIdx);
5128
+ const currentTurn = allMessages.slice(lastUserIdx);
5129
+ if (historyMessages.length === 0) return allMessages;
5130
+ const cfg = configRef.current;
5131
+ const cs = compactionStateRef.current;
5132
+ const recentBuffer = cfg.recentBuffer ?? 10;
5133
+ const isCompactionMsg = (m) => m.metadata?.["type"] === "compaction-marker";
5134
+ const windowedHistory = [];
5135
+ if (cs.workingMemory.length > 0) {
5136
+ windowedHistory.push({
5137
+ id: "working-memory",
5138
+ role: "system",
5139
+ content: `[Working memory \u2014 always active]
5140
+ ${cs.workingMemory.join("\n")}`,
5141
+ createdAt: /* @__PURE__ */ new Date()
5142
+ });
5143
+ }
5144
+ if (cs.rollingSummary) {
5145
+ windowedHistory.push({
5146
+ id: "rolling-summary",
5147
+ role: "system",
5148
+ content: `[Previous conversation summary]
5149
+ ${cs.rollingSummary}`,
5150
+ createdAt: /* @__PURE__ */ new Date()
5151
+ });
5152
+ }
5153
+ const systemMsgs = historyMessages.filter(
5154
+ (m) => m.role === "system" && !isCompactionMsg(m)
5155
+ );
5156
+ windowedHistory.push(...systemMsgs);
5157
+ const conversationMsgs = historyMessages.filter(
5158
+ (m) => m.role !== "system"
5159
+ );
5160
+ const recentStart = Math.max(0, conversationMsgs.length - recentBuffer);
5161
+ windowedHistory.push(...conversationMsgs.slice(recentStart));
5162
+ return [...windowedHistory, ...currentTurn];
5163
+ });
5164
+ return () => {
5165
+ chatRef.current?.setRequestMessageTransform(null);
5166
+ };
5167
+ }, []);
5168
+ return null;
5169
+ }
5170
+ var CopilotContext = React2.createContext(null);
2563
5171
  function useCopilot() {
2564
- const context = react.useContext(CopilotContext);
5172
+ const context = React2.useContext(CopilotContext);
2565
5173
  if (!context) {
2566
5174
  throw new Error("useCopilot must be used within CopilotProvider");
2567
5175
  }
@@ -2582,23 +5190,26 @@ function CopilotProvider({
2582
5190
  debug = false,
2583
5191
  maxIterations,
2584
5192
  maxIterationsMessage,
2585
- mcpServers
5193
+ mcpServers,
5194
+ optimization,
5195
+ messageHistory,
5196
+ skills
2586
5197
  }) {
2587
- const debugLog = react.useCallback(
2588
- (...args) => {
2589
- if (debug) console.log("[Copilot SDK]", ...args);
5198
+ const debugLog = React2.useCallback(
5199
+ (action, data) => {
5200
+ chunkI3SQUNTT_cjs.createLogger("provider", () => debug ?? false)(action, data);
2590
5201
  },
2591
5202
  [debug]
2592
5203
  );
2593
- react.useEffect(() => {
5204
+ React2.useEffect(() => {
2594
5205
  if (toolsConfig && (toolsConfig.screenshot || toolsConfig.console || toolsConfig.network)) {
2595
5206
  console.warn(
2596
5207
  "[Copilot SDK] The `tools` prop is deprecated. Use the `useTools` hook instead."
2597
5208
  );
2598
5209
  }
2599
5210
  }, [toolsConfig]);
2600
- const [toolExecutions, setToolExecutions] = react.useState([]);
2601
- const chatRef = react.useRef(null);
5211
+ const [toolExecutions, setToolExecutions] = React2.useState([]);
5212
+ const chatRef = React2.useRef(null);
2602
5213
  if (chatRef.current !== null && chatRef.current.disposed) {
2603
5214
  chatRef.current.revive();
2604
5215
  debugLog("Revived disposed instance (React StrictMode)");
@@ -2626,7 +5237,8 @@ function CopilotProvider({
2626
5237
  body,
2627
5238
  debug,
2628
5239
  maxIterations,
2629
- maxIterationsMessage
5240
+ maxIterationsMessage,
5241
+ optimization
2630
5242
  },
2631
5243
  {
2632
5244
  onToolExecutionsChange: (executions) => {
@@ -2636,92 +5248,111 @@ function CopilotProvider({
2636
5248
  onApprovalRequired: (execution) => {
2637
5249
  debugLog("Tool approval required:", execution.name);
2638
5250
  },
5251
+ onContextUsageChange: (usage) => {
5252
+ setContextUsage(usage);
5253
+ },
2639
5254
  onError: (error2) => {
2640
5255
  if (error2) onError?.(error2);
2641
5256
  }
2642
5257
  }
2643
5258
  );
2644
5259
  }
2645
- react.useEffect(() => {
5260
+ React2.useEffect(() => {
2646
5261
  if (chatRef.current && systemPrompt !== void 0) {
2647
5262
  chatRef.current.setSystemPrompt(systemPrompt);
2648
5263
  debugLog("System prompt updated from prop");
2649
5264
  }
2650
5265
  }, [systemPrompt, debugLog]);
2651
- react.useEffect(() => {
5266
+ React2.useEffect(() => {
2652
5267
  if (chatRef.current && headers !== void 0) {
2653
5268
  chatRef.current.setHeaders(headers);
2654
5269
  debugLog("Headers config updated from prop");
2655
5270
  }
2656
5271
  }, [headers, debugLog]);
2657
- react.useEffect(() => {
5272
+ React2.useEffect(() => {
2658
5273
  if (chatRef.current && body !== void 0) {
2659
5274
  chatRef.current.setBody(body);
2660
5275
  debugLog("Body config updated from prop");
2661
5276
  }
2662
5277
  }, [body, debugLog]);
2663
- react.useEffect(() => {
5278
+ React2.useEffect(() => {
2664
5279
  if (chatRef.current && runtimeUrl !== void 0) {
2665
5280
  chatRef.current.setUrl(runtimeUrl);
2666
5281
  debugLog("URL config updated from prop");
2667
5282
  }
2668
5283
  }, [runtimeUrl, debugLog]);
2669
- const messages = react.useSyncExternalStore(
5284
+ const EMPTY_MESSAGES = React2.useRef([]);
5285
+ const getMessagesSnapshot = React2.useCallback(() => chatRef.current.messages, []);
5286
+ const getServerMessagesSnapshot = React2.useCallback(
5287
+ () => EMPTY_MESSAGES.current,
5288
+ []
5289
+ );
5290
+ const getStatusSnapshot = React2.useCallback(() => chatRef.current.status, []);
5291
+ const getErrorSnapshot = React2.useCallback(() => chatRef.current.error, []);
5292
+ const messages = React2.useSyncExternalStore(
2670
5293
  chatRef.current.subscribe,
2671
- () => chatRef.current.messages,
2672
- () => chatRef.current.messages
5294
+ getMessagesSnapshot,
5295
+ getServerMessagesSnapshot
2673
5296
  );
2674
- const status = react.useSyncExternalStore(
5297
+ const status = React2.useSyncExternalStore(
2675
5298
  chatRef.current.subscribe,
2676
- () => chatRef.current.status,
5299
+ getStatusSnapshot,
2677
5300
  () => "ready"
2678
5301
  );
2679
- const errorFromChat = react.useSyncExternalStore(
5302
+ const errorFromChat = React2.useSyncExternalStore(
2680
5303
  chatRef.current.subscribe,
2681
- () => chatRef.current.error,
5304
+ getErrorSnapshot,
2682
5305
  () => void 0
2683
5306
  );
2684
5307
  const error = errorFromChat ?? null;
2685
5308
  const isLoading = status === "streaming" || status === "submitted";
2686
- const registerTool = react.useCallback((tool2) => {
5309
+ const registerTool = React2.useCallback((tool2) => {
2687
5310
  chatRef.current?.registerTool(tool2);
2688
5311
  }, []);
2689
- const unregisterTool = react.useCallback((name) => {
5312
+ const unregisterTool = React2.useCallback((name) => {
2690
5313
  chatRef.current?.unregisterTool(name);
2691
5314
  }, []);
2692
- const approveToolExecution = react.useCallback(
5315
+ const approveToolExecution = React2.useCallback(
2693
5316
  (id, extraData, permissionLevel) => {
2694
5317
  chatRef.current?.approveToolExecution(id, extraData, permissionLevel);
2695
5318
  },
2696
5319
  []
2697
5320
  );
2698
- const rejectToolExecution = react.useCallback(
5321
+ const rejectToolExecution = React2.useCallback(
2699
5322
  (id, reason, permissionLevel) => {
2700
5323
  chatRef.current?.rejectToolExecution(id, reason, permissionLevel);
2701
5324
  },
2702
5325
  []
2703
5326
  );
2704
- const registeredTools = chatRef.current?.tools ?? [];
2705
- const pendingApprovals = toolExecutions.filter(
2706
- (e) => e.approvalStatus === "required"
5327
+ const registeredTools = React2.useMemo(
5328
+ () => chatRef.current?.tools ?? [],
5329
+ // eslint-disable-next-line react-hooks/exhaustive-deps
5330
+ [toolExecutions]
5331
+ // re-derive when tool executions change (tools change alongside)
2707
5332
  );
2708
- const actionsRef = react.useRef(/* @__PURE__ */ new Map());
2709
- const [actionsVersion, setActionsVersion] = react.useState(0);
2710
- const registerAction = react.useCallback((action) => {
5333
+ const pendingApprovals = React2.useMemo(
5334
+ () => toolExecutions.filter((e) => e.approvalStatus === "required"),
5335
+ [toolExecutions]
5336
+ );
5337
+ const actionsRef = React2.useRef(/* @__PURE__ */ new Map());
5338
+ const [actionsVersion, setActionsVersion] = React2.useState(0);
5339
+ const registerAction = React2.useCallback((action) => {
2711
5340
  actionsRef.current.set(action.name, action);
2712
5341
  setActionsVersion((v) => v + 1);
2713
5342
  }, []);
2714
- const unregisterAction = react.useCallback((name) => {
5343
+ const unregisterAction = React2.useCallback((name) => {
2715
5344
  actionsRef.current.delete(name);
2716
5345
  setActionsVersion((v) => v + 1);
2717
5346
  }, []);
2718
- const registeredActions = react.useMemo(
5347
+ const registeredActions = React2.useMemo(
2719
5348
  () => Array.from(actionsRef.current.values()),
2720
5349
  [actionsVersion]
2721
5350
  );
2722
- const contextTreeRef = react.useRef([]);
2723
- const contextIdCounter = react.useRef(0);
2724
- const addContext = react.useCallback(
5351
+ const contextTreeRef = React2.useRef([]);
5352
+ const contextIdCounter = React2.useRef(0);
5353
+ const [contextChars, setContextChars] = React2.useState(0);
5354
+ const [contextUsage, setContextUsage] = React2.useState(null);
5355
+ const addContext = React2.useCallback(
2725
5356
  (context, parentId) => {
2726
5357
  const id = `ctx-${++contextIdCounter.current}`;
2727
5358
  contextTreeRef.current = addNode(
@@ -2731,55 +5362,95 @@ function CopilotProvider({
2731
5362
  );
2732
5363
  const contextString = printTree(contextTreeRef.current);
2733
5364
  chatRef.current?.setContext(contextString);
5365
+ setContextChars(contextString.length);
2734
5366
  debugLog("Context added:", id);
2735
5367
  return id;
2736
5368
  },
2737
5369
  [debugLog]
2738
5370
  );
2739
- const removeContext = react.useCallback(
5371
+ const removeContext = React2.useCallback(
2740
5372
  (id) => {
2741
5373
  contextTreeRef.current = removeNode(contextTreeRef.current, id);
2742
5374
  const contextString = printTree(contextTreeRef.current);
2743
5375
  chatRef.current?.setContext(contextString);
5376
+ setContextChars(contextString.length);
2744
5377
  debugLog("Context removed:", id);
2745
5378
  },
2746
5379
  [debugLog]
2747
5380
  );
2748
- const setSystemPrompt = react.useCallback(
5381
+ const setSystemPrompt = React2.useCallback(
2749
5382
  (prompt) => {
2750
5383
  chatRef.current?.setSystemPrompt(prompt);
2751
5384
  debugLog("System prompt updated via function");
2752
5385
  },
2753
5386
  [debugLog]
2754
5387
  );
2755
- const sendMessage = react.useCallback(
5388
+ const setInlineSkills = React2.useCallback(
5389
+ (skills2) => {
5390
+ chatRef.current?.setInlineSkills(skills2);
5391
+ debugLog("Inline skills updated", { count: skills2.length });
5392
+ },
5393
+ [debugLog]
5394
+ );
5395
+ const sendMessage = React2.useCallback(
2756
5396
  async (content, attachments) => {
2757
5397
  debugLog("Sending message:", content);
2758
5398
  await chatRef.current?.sendMessage(content, attachments);
2759
5399
  },
2760
5400
  [debugLog]
2761
5401
  );
2762
- const stop = react.useCallback(() => {
5402
+ const stop = React2.useCallback(() => {
2763
5403
  chatRef.current?.stop();
2764
5404
  }, []);
2765
- const clearMessages = react.useCallback(() => {
5405
+ const clearMessages = React2.useCallback(() => {
2766
5406
  chatRef.current?.clearMessages();
2767
5407
  }, []);
2768
- const setMessages = react.useCallback((messages2) => {
5408
+ const setMessages = React2.useCallback((messages2) => {
2769
5409
  chatRef.current?.setMessages(messages2);
2770
5410
  }, []);
2771
- const regenerate = react.useCallback(async (messageId) => {
5411
+ const regenerate = React2.useCallback(async (messageId) => {
2772
5412
  await chatRef.current?.regenerate(messageId);
2773
5413
  }, []);
2774
- react.useEffect(() => {
5414
+ const switchBranch = React2.useCallback((messageId) => {
5415
+ chatRef.current?.switchBranch(messageId);
5416
+ }, []);
5417
+ const getBranchInfo = React2.useCallback(
5418
+ (messageId) => chatRef.current?.getBranchInfo(messageId) ?? null,
5419
+ []
5420
+ );
5421
+ const editMessage = React2.useCallback(
5422
+ async (messageId, newContent) => {
5423
+ await chatRef.current?.sendMessage(newContent, void 0, {
5424
+ editMessageId: messageId
5425
+ });
5426
+ },
5427
+ []
5428
+ );
5429
+ const getHasBranchesSnapshot = React2.useCallback(
5430
+ () => chatRef.current.hasBranches,
5431
+ []
5432
+ );
5433
+ const hasBranches = React2.useSyncExternalStore(
5434
+ chatRef.current.subscribe,
5435
+ getHasBranchesSnapshot,
5436
+ () => false
5437
+ );
5438
+ const getAllMessages = React2.useCallback(
5439
+ () => chatRef.current?.getAllMessages?.() ?? [],
5440
+ []
5441
+ );
5442
+ React2.useEffect(() => {
2775
5443
  if (onMessagesChange && messages.length > 0) {
2776
- const coreMessages = messages.map((m) => ({
5444
+ const allUIMessages = chatRef.current?.getAllMessages?.() ?? messages;
5445
+ const coreMessages = allUIMessages.map((m) => ({
2777
5446
  id: m.id,
2778
5447
  role: m.role,
2779
5448
  content: m.content,
2780
5449
  created_at: m.createdAt,
2781
5450
  tool_calls: m.toolCalls,
2782
5451
  tool_call_id: m.toolCallId,
5452
+ parent_id: m.parentId,
5453
+ children_ids: m.childrenIds,
2783
5454
  metadata: {
2784
5455
  attachments: m.attachments,
2785
5456
  thinking: m.thinking
@@ -2788,17 +5459,17 @@ function CopilotProvider({
2788
5459
  onMessagesChange(coreMessages);
2789
5460
  }
2790
5461
  }, [messages, onMessagesChange]);
2791
- react.useEffect(() => {
5462
+ React2.useEffect(() => {
2792
5463
  if (error && onError) {
2793
5464
  onError(error);
2794
5465
  }
2795
5466
  }, [error, onError]);
2796
- react.useEffect(() => {
5467
+ React2.useEffect(() => {
2797
5468
  return () => {
2798
5469
  chatRef.current?.dispose();
2799
5470
  };
2800
5471
  }, []);
2801
- const contextValue = react.useMemo(
5472
+ const contextValue = React2.useMemo(
2802
5473
  () => ({
2803
5474
  // Chat state
2804
5475
  messages,
@@ -2811,6 +5482,12 @@ function CopilotProvider({
2811
5482
  clearMessages,
2812
5483
  setMessages,
2813
5484
  regenerate,
5485
+ // Branching
5486
+ switchBranch,
5487
+ getBranchInfo,
5488
+ editMessage,
5489
+ hasBranches,
5490
+ getAllMessages,
2814
5491
  // Tool execution
2815
5492
  registerTool,
2816
5493
  unregisterTool,
@@ -2826,8 +5503,12 @@ function CopilotProvider({
2826
5503
  // AI Context
2827
5504
  addContext,
2828
5505
  removeContext,
5506
+ contextChars,
5507
+ contextUsage,
2829
5508
  // System Prompt
2830
5509
  setSystemPrompt,
5510
+ // Skills
5511
+ setInlineSkills,
2831
5512
  // Config
2832
5513
  threadId,
2833
5514
  runtimeUrl,
@@ -2843,6 +5524,11 @@ function CopilotProvider({
2843
5524
  clearMessages,
2844
5525
  setMessages,
2845
5526
  regenerate,
5527
+ switchBranch,
5528
+ getBranchInfo,
5529
+ editMessage,
5530
+ hasBranches,
5531
+ getAllMessages,
2846
5532
  registerTool,
2847
5533
  unregisterTool,
2848
5534
  registeredTools,
@@ -2855,20 +5541,45 @@ function CopilotProvider({
2855
5541
  registeredActions,
2856
5542
  addContext,
2857
5543
  removeContext,
5544
+ contextChars,
5545
+ contextUsage,
2858
5546
  setSystemPrompt,
5547
+ setInlineSkills,
2859
5548
  threadId,
2860
5549
  runtimeUrl,
2861
5550
  toolsConfig
2862
5551
  ]
2863
5552
  );
2864
- return /* @__PURE__ */ jsxRuntime.jsxs(CopilotContext.Provider, { value: contextValue, children: [
5553
+ const messageHistoryContextValue = React2__default.default.useMemo(
5554
+ () => ({
5555
+ config: { ...defaultMessageHistoryConfig, ...messageHistory },
5556
+ tokenUsage: {
5557
+ current: 0,
5558
+ max: messageHistory?.maxContextTokens ?? 128e3,
5559
+ percentage: 0,
5560
+ isApproaching: false
5561
+ },
5562
+ compactionState: {
5563
+ rollingSummary: null,
5564
+ lastCompactionAt: null,
5565
+ compactionCount: 0,
5566
+ totalTokensSaved: 0,
5567
+ workingMemory: [],
5568
+ displayMessageCount: 0,
5569
+ llmMessageCount: 0
5570
+ }
5571
+ }),
5572
+ [messageHistory]
5573
+ );
5574
+ return /* @__PURE__ */ jsxRuntime.jsx(MessageHistoryContext.Provider, { value: messageHistoryContextValue, children: /* @__PURE__ */ jsxRuntime.jsxs(CopilotContext.Provider, { value: contextValue, children: [
2865
5575
  mcpServers?.map((config) => /* @__PURE__ */ jsxRuntime.jsx(MCPConnection, { config }, config.name)),
2866
- children
2867
- ] });
5576
+ messageHistory?.strategy && messageHistory.strategy !== "none" && /* @__PURE__ */ jsxRuntime.jsx(MessageHistoryBridge, { chatRef }),
5577
+ skills ? /* @__PURE__ */ jsxRuntime.jsx(SkillProvider, { skills, children }) : children
5578
+ ] }) });
2868
5579
  }
2869
5580
  function useAIActions(actions) {
2870
5581
  const { registerAction, unregisterAction } = useCopilot();
2871
- react.useEffect(() => {
5582
+ React2.useEffect(() => {
2872
5583
  for (const action of actions) {
2873
5584
  registerAction(action);
2874
5585
  }
@@ -2882,61 +5593,6 @@ function useAIActions(actions) {
2882
5593
  function useAIAction(action) {
2883
5594
  useAIActions([action]);
2884
5595
  }
2885
- function useAIContext(item) {
2886
- const { addContext, removeContext } = useCopilot();
2887
- const contextIdRef = react.useRef(null);
2888
- const serializedData = typeof item.data === "string" ? item.data : JSON.stringify(item.data);
2889
- react.useEffect(() => {
2890
- const formattedValue = typeof item.data === "string" ? item.data : JSON.stringify(item.data, null, 2);
2891
- const contextString = item.description ? `${item.description}:
2892
- ${formattedValue}` : `${item.key}:
2893
- ${formattedValue}`;
2894
- contextIdRef.current = addContext(contextString, item.parentId);
2895
- return () => {
2896
- if (contextIdRef.current) {
2897
- removeContext(contextIdRef.current);
2898
- contextIdRef.current = null;
2899
- }
2900
- };
2901
- }, [
2902
- item.key,
2903
- serializedData,
2904
- item.description,
2905
- item.parentId,
2906
- addContext,
2907
- removeContext
2908
- ]);
2909
- return contextIdRef.current ?? void 0;
2910
- }
2911
- function useAIContexts(items) {
2912
- const { addContext, removeContext } = useCopilot();
2913
- const contextIdsRef = react.useRef([]);
2914
- const serializedItems = JSON.stringify(
2915
- items.map((item) => ({
2916
- key: item.key,
2917
- data: item.data,
2918
- description: item.description,
2919
- parentId: item.parentId
2920
- }))
2921
- );
2922
- react.useEffect(() => {
2923
- contextIdsRef.current.forEach((id) => removeContext(id));
2924
- contextIdsRef.current = [];
2925
- const parsedItems = JSON.parse(serializedItems);
2926
- for (const item of parsedItems) {
2927
- const formattedValue = typeof item.data === "string" ? item.data : JSON.stringify(item.data, null, 2);
2928
- const contextString = item.description ? `${item.description}:
2929
- ${formattedValue}` : `${item.key}:
2930
- ${formattedValue}`;
2931
- const id = addContext(contextString, item.parentId);
2932
- contextIdsRef.current.push(id);
2933
- }
2934
- return () => {
2935
- contextIdsRef.current.forEach((id) => removeContext(id));
2936
- contextIdsRef.current = [];
2937
- };
2938
- }, [serializedItems, addContext, removeContext]);
2939
- }
2940
5596
  function useAITools(options = {}) {
2941
5597
  const {
2942
5598
  screenshot = false,
@@ -2949,27 +5605,27 @@ function useAITools(options = {}) {
2949
5605
  onConsentRequest,
2950
5606
  autoStart = true
2951
5607
  } = options;
2952
- const [isEnabled] = react.useState(screenshot || consoleCapture || network);
2953
- const [activeCaptures, setActiveCaptures] = react.useState({
5608
+ const [isEnabled] = React2.useState(screenshot || consoleCapture || network);
5609
+ const [activeCaptures, setActiveCaptures] = React2.useState({
2954
5610
  console: false,
2955
5611
  network: false
2956
5612
  });
2957
- const [pendingConsent, setPendingConsent] = react.useState(null);
2958
- const consentResolverRef = react.useRef(null);
2959
- const rememberedConsentRef = react.useRef(/* @__PURE__ */ new Set());
2960
- react.useEffect(() => {
5613
+ const [pendingConsent, setPendingConsent] = React2.useState(null);
5614
+ const consentResolverRef = React2.useRef(null);
5615
+ const rememberedConsentRef = React2.useRef(/* @__PURE__ */ new Set());
5616
+ React2.useEffect(() => {
2961
5617
  if (!autoStart || !isEnabled) return;
2962
- if (consoleCapture && !chunkOQPRIB73_cjs.isConsoleCaptureActive()) {
2963
- chunkOQPRIB73_cjs.startConsoleCapture(consoleOptions);
5618
+ if (consoleCapture && !chunkI3SQUNTT_cjs.isConsoleCaptureActive()) {
5619
+ chunkI3SQUNTT_cjs.startConsoleCapture(consoleOptions);
2964
5620
  setActiveCaptures((prev) => ({ ...prev, console: true }));
2965
5621
  }
2966
- if (network && !chunkOQPRIB73_cjs.isNetworkCaptureActive()) {
2967
- chunkOQPRIB73_cjs.startNetworkCapture(networkOptions);
5622
+ if (network && !chunkI3SQUNTT_cjs.isNetworkCaptureActive()) {
5623
+ chunkI3SQUNTT_cjs.startNetworkCapture(networkOptions);
2968
5624
  setActiveCaptures((prev) => ({ ...prev, network: true }));
2969
5625
  }
2970
5626
  return () => {
2971
- chunkOQPRIB73_cjs.stopConsoleCapture();
2972
- chunkOQPRIB73_cjs.stopNetworkCapture();
5627
+ chunkI3SQUNTT_cjs.stopConsoleCapture();
5628
+ chunkI3SQUNTT_cjs.stopNetworkCapture();
2973
5629
  };
2974
5630
  }, [
2975
5631
  autoStart,
@@ -2979,39 +5635,39 @@ function useAITools(options = {}) {
2979
5635
  consoleOptions,
2980
5636
  networkOptions
2981
5637
  ]);
2982
- const captureScreenshotFn = react.useCallback(
5638
+ const captureScreenshotFn = React2.useCallback(
2983
5639
  async (opts) => {
2984
5640
  if (!screenshot) {
2985
5641
  throw new Error("Screenshot capture is not enabled");
2986
5642
  }
2987
- if (!chunkOQPRIB73_cjs.isScreenshotSupported()) {
5643
+ if (!chunkI3SQUNTT_cjs.isScreenshotSupported()) {
2988
5644
  throw new Error(
2989
5645
  "Screenshot capture is not supported in this environment"
2990
5646
  );
2991
5647
  }
2992
- return chunkOQPRIB73_cjs.captureScreenshot({ ...screenshotOptions, ...opts });
5648
+ return chunkI3SQUNTT_cjs.captureScreenshot({ ...screenshotOptions, ...opts });
2993
5649
  },
2994
5650
  [screenshot, screenshotOptions]
2995
5651
  );
2996
- const getConsoleLogsFn = react.useCallback(
5652
+ const getConsoleLogsFn = React2.useCallback(
2997
5653
  (opts) => {
2998
5654
  if (!consoleCapture) {
2999
5655
  return { logs: [], totalCaptured: 0 };
3000
5656
  }
3001
- return chunkOQPRIB73_cjs.getConsoleLogs({ ...consoleOptions, ...opts });
5657
+ return chunkI3SQUNTT_cjs.getConsoleLogs({ ...consoleOptions, ...opts });
3002
5658
  },
3003
5659
  [consoleCapture, consoleOptions]
3004
5660
  );
3005
- const getNetworkRequestsFn = react.useCallback(
5661
+ const getNetworkRequestsFn = React2.useCallback(
3006
5662
  (opts) => {
3007
5663
  if (!network) {
3008
5664
  return { requests: [], totalCaptured: 0 };
3009
5665
  }
3010
- return chunkOQPRIB73_cjs.getNetworkRequests({ ...networkOptions, ...opts });
5666
+ return chunkI3SQUNTT_cjs.getNetworkRequests({ ...networkOptions, ...opts });
3011
5667
  },
3012
5668
  [network, networkOptions]
3013
5669
  );
3014
- const requestConsent = react.useCallback(
5670
+ const requestConsent = React2.useCallback(
3015
5671
  async (tools, reason = "") => {
3016
5672
  const enabledTools = tools.filter((tool2) => {
3017
5673
  if (tool2 === "screenshot") return screenshot;
@@ -3059,14 +5715,14 @@ function useAITools(options = {}) {
3059
5715
  },
3060
5716
  [screenshot, consoleCapture, network, requireConsent, onConsentRequest]
3061
5717
  );
3062
- const respondToConsent = react.useCallback((response) => {
5718
+ const respondToConsent = React2.useCallback((response) => {
3063
5719
  if (consentResolverRef.current) {
3064
5720
  consentResolverRef.current(response);
3065
5721
  consentResolverRef.current = null;
3066
5722
  }
3067
5723
  setPendingConsent(null);
3068
5724
  }, []);
3069
- const captureContext = react.useCallback(
5725
+ const captureContext = React2.useCallback(
3070
5726
  async (tools) => {
3071
5727
  const toolsToCapture = tools || ["screenshot", "console", "network"];
3072
5728
  const context = {
@@ -3099,26 +5755,26 @@ function useAITools(options = {}) {
3099
5755
  getNetworkRequestsFn
3100
5756
  ]
3101
5757
  );
3102
- const startCapturing = react.useCallback(() => {
3103
- if (consoleCapture && !chunkOQPRIB73_cjs.isConsoleCaptureActive()) {
3104
- chunkOQPRIB73_cjs.startConsoleCapture(consoleOptions);
5758
+ const startCapturing = React2.useCallback(() => {
5759
+ if (consoleCapture && !chunkI3SQUNTT_cjs.isConsoleCaptureActive()) {
5760
+ chunkI3SQUNTT_cjs.startConsoleCapture(consoleOptions);
3105
5761
  setActiveCaptures((prev) => ({ ...prev, console: true }));
3106
5762
  }
3107
- if (network && !chunkOQPRIB73_cjs.isNetworkCaptureActive()) {
3108
- chunkOQPRIB73_cjs.startNetworkCapture(networkOptions);
5763
+ if (network && !chunkI3SQUNTT_cjs.isNetworkCaptureActive()) {
5764
+ chunkI3SQUNTT_cjs.startNetworkCapture(networkOptions);
3109
5765
  setActiveCaptures((prev) => ({ ...prev, network: true }));
3110
5766
  }
3111
5767
  }, [consoleCapture, network, consoleOptions, networkOptions]);
3112
- const stopCapturing = react.useCallback(() => {
3113
- chunkOQPRIB73_cjs.stopConsoleCapture();
3114
- chunkOQPRIB73_cjs.stopNetworkCapture();
5768
+ const stopCapturing = React2.useCallback(() => {
5769
+ chunkI3SQUNTT_cjs.stopConsoleCapture();
5770
+ chunkI3SQUNTT_cjs.stopNetworkCapture();
3115
5771
  setActiveCaptures({ console: false, network: false });
3116
5772
  }, []);
3117
- const clearCaptured = react.useCallback(() => {
3118
- chunkOQPRIB73_cjs.clearConsoleLogs();
3119
- chunkOQPRIB73_cjs.clearNetworkRequests();
5773
+ const clearCaptured = React2.useCallback(() => {
5774
+ chunkI3SQUNTT_cjs.clearConsoleLogs();
5775
+ chunkI3SQUNTT_cjs.clearNetworkRequests();
3120
5776
  }, []);
3121
- const formatForAI = react.useCallback((context) => {
5777
+ const formatForAI = React2.useCallback((context) => {
3122
5778
  const parts = [];
3123
5779
  if (context.screenshot) {
3124
5780
  parts.push(
@@ -3126,16 +5782,16 @@ function useAITools(options = {}) {
3126
5782
  );
3127
5783
  }
3128
5784
  if (context.consoleLogs && context.consoleLogs.logs.length > 0) {
3129
- parts.push(chunkOQPRIB73_cjs.formatLogsForAI(context.consoleLogs.logs));
5785
+ parts.push(chunkI3SQUNTT_cjs.formatLogsForAI(context.consoleLogs.logs));
3130
5786
  }
3131
5787
  if (context.networkRequests && context.networkRequests.requests.length > 0) {
3132
- parts.push(chunkOQPRIB73_cjs.formatRequestsForAI(context.networkRequests.requests));
5788
+ parts.push(chunkI3SQUNTT_cjs.formatRequestsForAI(context.networkRequests.requests));
3133
5789
  }
3134
5790
  return parts.length > 0 ? parts.join("\n\n---\n\n") : "No context captured.";
3135
5791
  }, []);
3136
- const detectIntentFn = react.useCallback(
5792
+ const detectIntentFn = React2.useCallback(
3137
5793
  (message) => {
3138
- const result = chunkOQPRIB73_cjs.detectIntent(message);
5794
+ const result = chunkI3SQUNTT_cjs.detectIntent(message);
3139
5795
  result.suggestedTools = result.suggestedTools.filter((tool2) => {
3140
5796
  if (tool2 === "screenshot") return screenshot;
3141
5797
  if (tool2 === "console") return consoleCapture;
@@ -3146,7 +5802,7 @@ function useAITools(options = {}) {
3146
5802
  },
3147
5803
  [screenshot, consoleCapture, network]
3148
5804
  );
3149
- return react.useMemo(
5805
+ return React2.useMemo(
3150
5806
  () => ({
3151
5807
  isEnabled,
3152
5808
  activeCaptures,
@@ -3181,69 +5837,6 @@ function useAITools(options = {}) {
3181
5837
  ]
3182
5838
  );
3183
5839
  }
3184
- function isZodSchema(value) {
3185
- if (value === null || typeof value !== "object") return false;
3186
- const obj = value;
3187
- return "_def" in obj && typeof obj._def === "object" || "_zod" in obj && typeof obj._zod === "object" || "~standard" in obj;
3188
- }
3189
- function useTool(config, dependencies = []) {
3190
- const { registerTool, unregisterTool } = useCopilot();
3191
- const configRef = react.useRef(config);
3192
- configRef.current = config;
3193
- const inputSchema = react.useMemo(() => {
3194
- if (isZodSchema(config.inputSchema)) {
3195
- return chunkOQPRIB73_cjs.zodToJsonSchema(config.inputSchema);
3196
- }
3197
- return config.inputSchema;
3198
- }, [config.inputSchema]);
3199
- react.useEffect(() => {
3200
- const tool2 = {
3201
- name: config.name,
3202
- description: config.description,
3203
- location: "client",
3204
- inputSchema,
3205
- handler: async (params, context) => {
3206
- return configRef.current.handler(params, context);
3207
- },
3208
- render: config.render,
3209
- available: config.available ?? true,
3210
- needsApproval: config.needsApproval,
3211
- approvalMessage: config.approvalMessage,
3212
- hidden: config.hidden
3213
- };
3214
- registerTool(tool2);
3215
- return () => {
3216
- unregisterTool(config.name);
3217
- };
3218
- }, [config.name, inputSchema, ...dependencies]);
3219
- }
3220
- function useTools(tools) {
3221
- const { registerTool, unregisterTool } = useCopilot();
3222
- const registeredToolsRef = react.useRef([]);
3223
- const toolsRef = react.useRef(tools);
3224
- toolsRef.current = tools;
3225
- const toolsKey = Object.keys(tools).sort().join(",");
3226
- react.useEffect(() => {
3227
- const currentTools = toolsRef.current;
3228
- const toolNames = [];
3229
- for (const [name, toolDef] of Object.entries(currentTools)) {
3230
- const fullTool = {
3231
- ...toolDef,
3232
- name
3233
- // Use the key as the name
3234
- };
3235
- registerTool(fullTool);
3236
- toolNames.push(name);
3237
- }
3238
- registeredToolsRef.current = toolNames;
3239
- return () => {
3240
- for (const name of registeredToolsRef.current) {
3241
- unregisterTool(name);
3242
- }
3243
- registeredToolsRef.current = [];
3244
- };
3245
- }, [toolsKey]);
3246
- }
3247
5840
  function convertZodSchema(schema, _toolName) {
3248
5841
  try {
3249
5842
  const zodWithJsonSchema = z__namespace;
@@ -3261,13 +5854,13 @@ function convertZodSchema(schema, _toolName) {
3261
5854
  }
3262
5855
  } catch {
3263
5856
  }
3264
- return chunkOQPRIB73_cjs.zodObjectToInputSchema(schema);
5857
+ return chunkI3SQUNTT_cjs.zodObjectToInputSchema(schema);
3265
5858
  }
3266
5859
  function useToolWithSchema(config, dependencies = []) {
3267
5860
  const { registerTool, unregisterTool } = useCopilot();
3268
- const configRef = react.useRef(config);
5861
+ const configRef = React2.useRef(config);
3269
5862
  configRef.current = config;
3270
- const inputSchema = react.useMemo(() => {
5863
+ const inputSchema = React2.useMemo(() => {
3271
5864
  try {
3272
5865
  return convertZodSchema(config.schema, config.name);
3273
5866
  } catch (error) {
@@ -3281,7 +5874,7 @@ function useToolWithSchema(config, dependencies = []) {
3281
5874
  };
3282
5875
  }
3283
5876
  }, [config.schema, config.name]);
3284
- react.useEffect(() => {
5877
+ React2.useEffect(() => {
3285
5878
  const tool2 = {
3286
5879
  name: config.name,
3287
5880
  description: config.description,
@@ -3301,9 +5894,9 @@ function useToolWithSchema(config, dependencies = []) {
3301
5894
  }
3302
5895
  function useToolsWithSchema(tools, dependencies = []) {
3303
5896
  const { registerTool, unregisterTool } = useCopilot();
3304
- const toolsRef = react.useRef(tools);
5897
+ const toolsRef = React2.useRef(tools);
3305
5898
  toolsRef.current = tools;
3306
- react.useEffect(() => {
5899
+ React2.useEffect(() => {
3307
5900
  const toolNames = [];
3308
5901
  for (const config of tools) {
3309
5902
  let inputSchema;
@@ -3342,9 +5935,9 @@ function useToolsWithSchema(tools, dependencies = []) {
3342
5935
  };
3343
5936
  }, [tools.map((t) => t.name).join(","), ...dependencies]);
3344
5937
  }
3345
- var CopilotContext2 = react.createContext(null);
5938
+ var CopilotContext2 = React2.createContext(null);
3346
5939
  function useCopilotContext() {
3347
- const context = react.useContext(CopilotContext2);
5940
+ const context = React2.useContext(CopilotContext2);
3348
5941
  if (!context) {
3349
5942
  throw new Error("useCopilotContext must be used within a CopilotProvider");
3350
5943
  }
@@ -3360,9 +5953,9 @@ function useToolExecutor() {
3360
5953
  addToolExecution,
3361
5954
  updateToolExecution
3362
5955
  } = useCopilotContext();
3363
- const toolsRef = react.useRef(registeredTools);
5956
+ const toolsRef = React2.useRef(registeredTools);
3364
5957
  toolsRef.current = registeredTools;
3365
- const executeTool = react.useCallback(
5958
+ const executeTool = React2.useCallback(
3366
5959
  async (toolCall) => {
3367
5960
  const tool2 = toolsRef.current.find((t) => t.name === toolCall.name);
3368
5961
  if (!tool2) {
@@ -3412,7 +6005,7 @@ function useToolExecutor() {
3412
6005
  },
3413
6006
  [addToolExecution, updateToolExecution]
3414
6007
  );
3415
- const sendToolResult = react.useCallback(
6008
+ const sendToolResult = React2.useCallback(
3416
6009
  async (toolCallId, result) => {
3417
6010
  const runtimeUrl = config.runtimeUrl || config.cloud?.endpoint;
3418
6011
  if (!runtimeUrl) {
@@ -3446,10 +6039,10 @@ function useToolExecutor() {
3446
6039
  },
3447
6040
  [config.runtimeUrl, config.cloud?.endpoint, chat.threadId]
3448
6041
  );
3449
- const getTool = react.useCallback((name) => {
6042
+ const getTool = React2.useCallback((name) => {
3450
6043
  return toolsRef.current.find((t) => t.name === name);
3451
6044
  }, []);
3452
- const hasTool = react.useCallback((name) => {
6045
+ const hasTool = React2.useCallback((name) => {
3453
6046
  return toolsRef.current.some((t) => t.name === name);
3454
6047
  }, []);
3455
6048
  return {
@@ -3467,13 +6060,13 @@ function useSuggestions(options = {}) {
3467
6060
  autoRefresh = true
3468
6061
  } = options;
3469
6062
  const { chat, actions, config } = useCopilotContext();
3470
- const [suggestions, setSuggestions] = react.useState([]);
3471
- const [isLoading, setIsLoading] = react.useState(false);
3472
- const normalizedStatic = react.useMemo(
6063
+ const [suggestions, setSuggestions] = React2.useState([]);
6064
+ const [isLoading, setIsLoading] = React2.useState(false);
6065
+ const normalizedStatic = React2.useMemo(
3473
6066
  () => staticSuggestions?.map((s) => typeof s === "string" ? { text: s } : s),
3474
6067
  [staticSuggestions]
3475
6068
  );
3476
- const refresh = react.useCallback(async () => {
6069
+ const refresh = React2.useCallback(async () => {
3477
6070
  if (normalizedStatic) {
3478
6071
  setSuggestions(normalizedStatic.slice(0, count));
3479
6072
  return;
@@ -3512,14 +6105,14 @@ function useSuggestions(options = {}) {
3512
6105
  setIsLoading(false);
3513
6106
  }
3514
6107
  }, [config.cloud, count, context, chat.messages, normalizedStatic]);
3515
- const select = react.useCallback(
6108
+ const select = React2.useCallback(
3516
6109
  (suggestion) => {
3517
6110
  const text = typeof suggestion === "string" ? suggestion : suggestion.text;
3518
6111
  actions.sendMessage(text);
3519
6112
  },
3520
6113
  [actions]
3521
6114
  );
3522
- react.useEffect(() => {
6115
+ React2.useEffect(() => {
3523
6116
  if (autoRefresh && chat.messages.length === 0) {
3524
6117
  refresh();
3525
6118
  }
@@ -3534,18 +6127,18 @@ function useSuggestions(options = {}) {
3534
6127
  function useAgent(options) {
3535
6128
  const { name, initialState = {}, onStateChange } = options;
3536
6129
  const { config } = useCopilotContext();
3537
- const [state, setStateInternal] = react.useState(initialState);
3538
- const [isRunning, setIsRunning] = react.useState(false);
3539
- const [nodeName, setNodeName] = react.useState(null);
3540
- const [error, setError] = react.useState(null);
3541
- const abortControllerRef = react.useRef(null);
3542
- const getEndpoint = react.useCallback(() => {
6130
+ const [state, setStateInternal] = React2.useState(initialState);
6131
+ const [isRunning, setIsRunning] = React2.useState(false);
6132
+ const [nodeName, setNodeName] = React2.useState(null);
6133
+ const [error, setError] = React2.useState(null);
6134
+ const abortControllerRef = React2.useRef(null);
6135
+ const getEndpoint = React2.useCallback(() => {
3543
6136
  if (config.cloud) {
3544
6137
  return `${config.cloud.endpoint || "https://api.yourgpt.ai/v1"}/agents/${name}`;
3545
6138
  }
3546
6139
  return `${config.runtimeUrl || "/api"}/agents/${name}`;
3547
6140
  }, [config, name]);
3548
- const start = react.useCallback(
6141
+ const start = React2.useCallback(
3549
6142
  async (input) => {
3550
6143
  setIsRunning(true);
3551
6144
  setError(null);
@@ -3570,7 +6163,7 @@ function useAgent(options) {
3570
6163
  if (!response.ok) {
3571
6164
  throw new Error(`Agent error: ${response.status}`);
3572
6165
  }
3573
- for await (const event of chunkOQPRIB73_cjs.streamSSE(response)) {
6166
+ for await (const event of chunkI3SQUNTT_cjs.streamSSE(response)) {
3574
6167
  handleAgentEvent(event);
3575
6168
  }
3576
6169
  } catch (err) {
@@ -3584,7 +6177,7 @@ function useAgent(options) {
3584
6177
  },
3585
6178
  [config, getEndpoint, state]
3586
6179
  );
3587
- const handleAgentEvent = react.useCallback(
6180
+ const handleAgentEvent = React2.useCallback(
3588
6181
  (event) => {
3589
6182
  if (event.type === "error") {
3590
6183
  setError(new Error(event.message));
@@ -3600,10 +6193,10 @@ function useAgent(options) {
3600
6193
  },
3601
6194
  [onStateChange]
3602
6195
  );
3603
- const stop = react.useCallback(() => {
6196
+ const stop = React2.useCallback(() => {
3604
6197
  abortControllerRef.current?.abort();
3605
6198
  }, []);
3606
- const setState = react.useCallback(
6199
+ const setState = React2.useCallback(
3607
6200
  (partialState) => {
3608
6201
  setStateInternal((prev) => {
3609
6202
  const newState = { ...prev, ...partialState };
@@ -3613,7 +6206,7 @@ function useAgent(options) {
3613
6206
  },
3614
6207
  [onStateChange]
3615
6208
  );
3616
- react.useEffect(() => {
6209
+ React2.useEffect(() => {
3617
6210
  return () => {
3618
6211
  abortControllerRef.current?.abort();
3619
6212
  };
@@ -3694,9 +6287,9 @@ function formatKnowledgeResultsForAI(results) {
3694
6287
  // src/react/hooks/useKnowledgeBase.ts
3695
6288
  function useKnowledgeBase(config) {
3696
6289
  const { registerTool, unregisterTool } = useCopilot();
3697
- const configRef = react.useRef(config);
6290
+ const configRef = React2.useRef(config);
3698
6291
  configRef.current = config;
3699
- const handleSearch = react.useCallback(
6292
+ const handleSearch = React2.useCallback(
3700
6293
  async (params) => {
3701
6294
  const query = params.query;
3702
6295
  if (!query) {
@@ -3734,7 +6327,7 @@ function useKnowledgeBase(config) {
3734
6327
  },
3735
6328
  []
3736
6329
  );
3737
- react.useEffect(() => {
6330
+ React2.useEffect(() => {
3738
6331
  if (config.enabled === false) {
3739
6332
  return;
3740
6333
  }
@@ -3781,14 +6374,14 @@ var DEFAULT_CAPABILITIES = {
3781
6374
  };
3782
6375
  function useCapabilities() {
3783
6376
  const { config } = useCopilotContext();
3784
- const [capabilities, setCapabilities] = react.useState(DEFAULT_CAPABILITIES);
3785
- const [provider, setProvider] = react.useState("unknown");
3786
- const [model, setModel] = react.useState("unknown");
3787
- const [supportedModels, setSupportedModels] = react.useState([]);
3788
- const [isLoading, setIsLoading] = react.useState(true);
3789
- const [error, setError] = react.useState(null);
6377
+ const [capabilities, setCapabilities] = React2.useState(DEFAULT_CAPABILITIES);
6378
+ const [provider, setProvider] = React2.useState("unknown");
6379
+ const [model, setModel] = React2.useState("unknown");
6380
+ const [supportedModels, setSupportedModels] = React2.useState([]);
6381
+ const [isLoading, setIsLoading] = React2.useState(true);
6382
+ const [error, setError] = React2.useState(null);
3790
6383
  const capabilitiesUrl = config.runtimeUrl ? config.runtimeUrl.replace(/\/chat\/?$/, "/capabilities") : null;
3791
- const fetchCapabilities = react.useCallback(async () => {
6384
+ const fetchCapabilities = React2.useCallback(async () => {
3792
6385
  if (!capabilitiesUrl) {
3793
6386
  setIsLoading(false);
3794
6387
  return;
@@ -3811,7 +6404,7 @@ function useCapabilities() {
3811
6404
  setIsLoading(false);
3812
6405
  }
3813
6406
  }, [capabilitiesUrl]);
3814
- react.useEffect(() => {
6407
+ React2.useEffect(() => {
3815
6408
  fetchCapabilities();
3816
6409
  }, [fetchCapabilities]);
3817
6410
  return {
@@ -3854,7 +6447,7 @@ function useSupportedMediaTypes() {
3854
6447
  }
3855
6448
  function useDevLogger() {
3856
6449
  const ctx = useCopilotContext();
3857
- return react.useMemo(() => {
6450
+ return React2.useMemo(() => {
3858
6451
  const toolExecutions = (ctx.agentLoop?.toolExecutions || []).map(
3859
6452
  (exec) => ({
3860
6453
  id: exec.id,
@@ -4064,7 +6657,7 @@ function createReactThreadManagerState(initialThreads) {
4064
6657
  }
4065
6658
 
4066
6659
  // src/react/internal/ReactThreadManager.ts
4067
- var _ReactThreadManager = class _ReactThreadManager extends chunkOQPRIB73_cjs.ThreadManager {
6660
+ var _ReactThreadManager = class _ReactThreadManager extends chunkI3SQUNTT_cjs.ThreadManager {
4068
6661
  constructor(config = {}, callbacks = {}) {
4069
6662
  const reactState = new ReactThreadManagerState();
4070
6663
  super({ ...config, state: reactState }, callbacks);
@@ -4200,7 +6793,7 @@ function getInternalManager(config) {
4200
6793
  return internalManager;
4201
6794
  }
4202
6795
  function useThreadManager(config) {
4203
- const manager = react.useMemo(() => {
6796
+ const manager = React2.useMemo(() => {
4204
6797
  if (!config) {
4205
6798
  return getDefaultManager();
4206
6799
  }
@@ -4223,50 +6816,50 @@ function useThreadManager(config) {
4223
6816
  config?.autoRestoreLastThread
4224
6817
  // Note: callbacks are intentionally not in deps to avoid recreating manager
4225
6818
  ]);
4226
- const threads = react.useSyncExternalStore(
6819
+ const threads = React2.useSyncExternalStore(
4227
6820
  manager.subscribe,
4228
6821
  manager.getThreadsSnapshot,
4229
6822
  manager.getThreadsServerSnapshot
4230
6823
  // SSR - always empty array
4231
6824
  );
4232
- const currentThread = react.useSyncExternalStore(
6825
+ const currentThread = React2.useSyncExternalStore(
4233
6826
  manager.subscribe,
4234
6827
  manager.getCurrentThreadSnapshot,
4235
6828
  manager.getCurrentThreadServerSnapshot
4236
6829
  // SSR - always null
4237
6830
  );
4238
- const currentThreadId = react.useSyncExternalStore(
6831
+ const currentThreadId = React2.useSyncExternalStore(
4239
6832
  manager.subscribe,
4240
6833
  manager.getCurrentThreadIdSnapshot,
4241
6834
  manager.getCurrentThreadIdServerSnapshot
4242
6835
  // SSR - always null
4243
6836
  );
4244
- const loadStatus = react.useSyncExternalStore(
6837
+ const loadStatus = React2.useSyncExternalStore(
4245
6838
  manager.subscribe,
4246
6839
  manager.getLoadStatusSnapshot,
4247
6840
  manager.getLoadStatusServerSnapshot
4248
6841
  // SSR - always "idle"
4249
6842
  );
4250
- const error = react.useSyncExternalStore(
6843
+ const error = React2.useSyncExternalStore(
4251
6844
  manager.subscribe,
4252
6845
  manager.getErrorSnapshot,
4253
6846
  manager.getErrorServerSnapshot
4254
6847
  // SSR - always undefined
4255
6848
  );
4256
- const isLoading = react.useSyncExternalStore(
6849
+ const isLoading = React2.useSyncExternalStore(
4257
6850
  manager.subscribe,
4258
6851
  manager.getIsLoadingSnapshot,
4259
6852
  manager.getIsLoadingServerSnapshot
4260
6853
  // SSR - always false
4261
6854
  );
4262
- react.useEffect(() => {
6855
+ React2.useEffect(() => {
4263
6856
  return () => {
4264
6857
  if (config?.adapter && manager !== defaultManager && manager !== internalManager) {
4265
6858
  manager.dispose();
4266
6859
  }
4267
6860
  };
4268
6861
  }, [manager, config]);
4269
- react.useEffect(() => {
6862
+ React2.useEffect(() => {
4270
6863
  const handleBeforeUnload = () => {
4271
6864
  if (manager.hasPendingChanges) {
4272
6865
  manager.saveNow().catch(() => {
@@ -4280,37 +6873,37 @@ function useThreadManager(config) {
4280
6873
  };
4281
6874
  }
4282
6875
  }, [manager]);
4283
- const createThread = react.useCallback(
6876
+ const createThread = React2.useCallback(
4284
6877
  (options) => manager.createThread(options),
4285
6878
  [manager]
4286
6879
  );
4287
- const switchThread = react.useCallback(
6880
+ const switchThread = React2.useCallback(
4288
6881
  (id) => manager.switchThread(id),
4289
6882
  [manager]
4290
6883
  );
4291
- const updateCurrentThread = react.useCallback(
6884
+ const updateCurrentThread = React2.useCallback(
4292
6885
  (updates) => manager.updateCurrentThread(updates),
4293
6886
  [manager]
4294
6887
  );
4295
- const deleteThread = react.useCallback(
6888
+ const deleteThread = React2.useCallback(
4296
6889
  (id) => manager.deleteThread(id),
4297
6890
  [manager]
4298
6891
  );
4299
- const clearCurrentThread = react.useCallback(
6892
+ const clearCurrentThread = React2.useCallback(
4300
6893
  () => manager.clearCurrentThread(),
4301
6894
  [manager]
4302
6895
  );
4303
- const refreshThreads = react.useCallback(() => manager.loadThreads(), [manager]);
4304
- const saveNow = react.useCallback(() => manager.saveNow(), [manager]);
4305
- const clearAllThreads = react.useCallback(
6896
+ const refreshThreads = React2.useCallback(() => manager.loadThreads(), [manager]);
6897
+ const saveNow = React2.useCallback(() => manager.saveNow(), [manager]);
6898
+ const clearAllThreads = React2.useCallback(
4306
6899
  () => manager.clearAllThreads(),
4307
6900
  [manager]
4308
6901
  );
4309
- const messages = react.useMemo(
6902
+ const messages = React2.useMemo(
4310
6903
  () => currentThread?.messages ?? [],
4311
6904
  [currentThread]
4312
6905
  );
4313
- const setMessages = react.useCallback(
6906
+ const setMessages = React2.useCallback(
4314
6907
  (newMessages) => updateCurrentThread({ messages: newMessages }),
4315
6908
  [updateCurrentThread]
4316
6909
  );
@@ -4347,7 +6940,7 @@ function useMCPUIIntents(config = {}) {
4347
6940
  onLink,
4348
6941
  requireConsent = { tool: false, link: true }
4349
6942
  } = config;
4350
- const handleIntent = react.useCallback(
6943
+ const handleIntent = React2.useCallback(
4351
6944
  async (intent, context) => {
4352
6945
  switch (intent.type) {
4353
6946
  case "tool": {
@@ -4401,7 +6994,7 @@ function useMCPUIIntents(config = {}) {
4401
6994
  },
4402
6995
  [onToolCall, onIntent, onPrompt, onNotify, onLink, requireConsent]
4403
6996
  );
4404
- return react.useMemo(
6997
+ return React2.useMemo(
4405
6998
  () => ({
4406
6999
  handleIntent
4407
7000
  }),
@@ -4431,6 +7024,94 @@ function createToolIntentHandler(callTool) {
4431
7024
  }
4432
7025
  };
4433
7026
  }
7027
+ function getLastResponseUsage(messages) {
7028
+ for (let i = messages.length - 1; i >= 0; i--) {
7029
+ const msg = messages[i];
7030
+ if (msg.role === "assistant" && msg.metadata?.usage) {
7031
+ const u = msg.metadata.usage;
7032
+ const prompt = u.prompt_tokens ?? 0;
7033
+ const completion = u.completion_tokens ?? 0;
7034
+ return {
7035
+ prompt_tokens: prompt,
7036
+ completion_tokens: completion,
7037
+ total_tokens: u.total_tokens ?? prompt + completion
7038
+ };
7039
+ }
7040
+ }
7041
+ return null;
7042
+ }
7043
+ function useContextStats() {
7044
+ const { contextChars, contextUsage, registeredTools, messages } = useCopilot();
7045
+ const toolCount = React2.useMemo(() => registeredTools.length, [registeredTools]);
7046
+ const messageCount = React2.useMemo(
7047
+ () => messages.filter((m) => m.role !== "system").length,
7048
+ [messages]
7049
+ );
7050
+ const totalTokens = React2.useMemo(() => {
7051
+ if (contextUsage) return contextUsage.total.tokens;
7052
+ return Math.ceil(contextChars / 3.5);
7053
+ }, [contextUsage, contextChars]);
7054
+ const usagePercent = React2.useMemo(() => {
7055
+ if (contextUsage) return contextUsage.total.percent;
7056
+ return 0;
7057
+ }, [contextUsage]);
7058
+ const lastResponseUsage = React2.useMemo(
7059
+ () => getLastResponseUsage(messages),
7060
+ [messages]
7061
+ );
7062
+ return {
7063
+ contextUsage,
7064
+ totalTokens,
7065
+ usagePercent,
7066
+ contextChars,
7067
+ toolCount,
7068
+ messageCount,
7069
+ lastResponseUsage
7070
+ };
7071
+ }
7072
+ var DEV_CONTENT_WARN_THRESHOLD = 2e3;
7073
+ function useSkill(skill) {
7074
+ const { register, unregister } = useSkillContext();
7075
+ if (process.env.NODE_ENV !== "production" && skill.source.type === "inline" && skill.source.content.length > DEV_CONTENT_WARN_THRESHOLD) {
7076
+ console.warn(
7077
+ `[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.`
7078
+ );
7079
+ }
7080
+ React2.useEffect(() => {
7081
+ if (skill.source.type !== "inline") {
7082
+ console.warn(
7083
+ `[copilot-sdk/skills] useSkill only supports inline skills client-side. Skill "${skill.name}" has source type "${skill.source.type}" and will be skipped.`
7084
+ );
7085
+ return;
7086
+ }
7087
+ const resolved = {
7088
+ ...skill,
7089
+ content: skill.source.content
7090
+ };
7091
+ register(resolved);
7092
+ return () => {
7093
+ unregister(skill.name);
7094
+ };
7095
+ }, [
7096
+ skill.name,
7097
+ skill.source.type === "inline" ? skill.source.content : "",
7098
+ skill.strategy,
7099
+ skill.description
7100
+ ]);
7101
+ }
7102
+ function useSkillStatus() {
7103
+ const { skills, registry } = useSkillContext();
7104
+ const has = React2.useCallback(
7105
+ (name) => registry.has(name),
7106
+ // eslint-disable-next-line react-hooks/exhaustive-deps
7107
+ [skills]
7108
+ );
7109
+ return {
7110
+ skills,
7111
+ count: skills.length,
7112
+ has
7113
+ };
7114
+ }
4434
7115
 
4435
7116
  // src/react/utils/permission-storage.ts
4436
7117
  var DEFAULT_KEY_PREFIX = "yourgpt-permissions";
@@ -4583,6 +7264,34 @@ var ReactChat = class extends AbstractChat {
4583
7264
  return this.on("error", handler);
4584
7265
  }
4585
7266
  // ============================================
7267
+ // Branching API — pass-throughs to ReactChatState
7268
+ // ============================================
7269
+ /**
7270
+ * Navigate to a sibling branch (makes it the active path).
7271
+ */
7272
+ switchBranch(messageId) {
7273
+ this.reactState.switchBranch(messageId);
7274
+ }
7275
+ /**
7276
+ * Get branch navigation info for a message.
7277
+ * Returns null if the message has no siblings.
7278
+ */
7279
+ getBranchInfo(messageId) {
7280
+ return this.reactState.getBranchInfo(messageId);
7281
+ }
7282
+ /**
7283
+ * Get all messages across all branches (for persistence).
7284
+ */
7285
+ getAllMessages() {
7286
+ return this.reactState.getAllMessages();
7287
+ }
7288
+ /**
7289
+ * Whether any message has siblings (branching has occurred).
7290
+ */
7291
+ get hasBranches() {
7292
+ return this.reactState.hasBranches;
7293
+ }
7294
+ // ============================================
4586
7295
  // Override dispose to clean up state
4587
7296
  // ============================================
4588
7297
  dispose() {
@@ -4601,8 +7310,8 @@ function createReactChat(config) {
4601
7310
  return new ReactChat(config);
4602
7311
  }
4603
7312
  function useChat(config) {
4604
- const chatRef = react.useRef(null);
4605
- const [input, setInput] = react.useState("");
7313
+ const chatRef = React2.useRef(null);
7314
+ const [input, setInput] = React2.useState("");
4606
7315
  if (chatRef.current !== null && chatRef.current.disposed) {
4607
7316
  chatRef.current.revive();
4608
7317
  }
@@ -4624,51 +7333,74 @@ function useChat(config) {
4624
7333
  }
4625
7334
  });
4626
7335
  }
4627
- const messages = react.useSyncExternalStore(
7336
+ const messages = React2.useSyncExternalStore(
4628
7337
  chatRef.current.subscribe,
4629
7338
  () => chatRef.current.messages,
4630
7339
  () => chatRef.current.messages
4631
7340
  // Server snapshot
4632
7341
  );
4633
- const status = react.useSyncExternalStore(
7342
+ const status = React2.useSyncExternalStore(
4634
7343
  chatRef.current.subscribe,
4635
7344
  () => chatRef.current.status,
4636
7345
  () => "ready"
4637
7346
  // Server snapshot
4638
7347
  );
4639
- const error = react.useSyncExternalStore(
7348
+ const error = React2.useSyncExternalStore(
4640
7349
  chatRef.current.subscribe,
4641
7350
  () => chatRef.current.error,
4642
7351
  () => void 0
4643
7352
  // Server snapshot
4644
7353
  );
7354
+ const hasBranches = React2.useSyncExternalStore(
7355
+ chatRef.current.subscribe,
7356
+ () => chatRef.current.hasBranches,
7357
+ () => false
7358
+ );
4645
7359
  const isLoading = status === "streaming" || status === "submitted";
4646
- const sendMessage = react.useCallback(
7360
+ const sendMessage = React2.useCallback(
4647
7361
  async (content, attachments) => {
4648
7362
  await chatRef.current?.sendMessage(content, attachments);
4649
7363
  setInput("");
4650
7364
  },
4651
7365
  []
4652
7366
  );
4653
- const stop = react.useCallback(() => {
7367
+ const stop = React2.useCallback(() => {
4654
7368
  chatRef.current?.stop();
4655
7369
  }, []);
4656
- const clearMessages = react.useCallback(() => {
7370
+ const clearMessages = React2.useCallback(() => {
4657
7371
  chatRef.current?.clearMessages();
4658
7372
  }, []);
4659
- const setMessages = react.useCallback((messages2) => {
7373
+ const setMessages = React2.useCallback((messages2) => {
4660
7374
  chatRef.current?.setMessages(messages2);
4661
7375
  }, []);
4662
- const regenerate = react.useCallback(async (messageId) => {
7376
+ const regenerate = React2.useCallback(async (messageId) => {
4663
7377
  await chatRef.current?.regenerate(messageId);
4664
7378
  }, []);
4665
- const continueWithToolResults = react.useCallback(
7379
+ const continueWithToolResults = React2.useCallback(
4666
7380
  async (toolResults) => {
4667
7381
  await chatRef.current?.continueWithToolResults(toolResults);
4668
7382
  },
4669
7383
  []
4670
7384
  );
4671
- react.useEffect(() => {
7385
+ const switchBranch = React2.useCallback((messageId) => {
7386
+ chatRef.current?.switchBranch(messageId);
7387
+ }, []);
7388
+ const getBranchInfo = React2.useCallback(
7389
+ (messageId) => {
7390
+ return chatRef.current?.getBranchInfo(messageId) ?? null;
7391
+ },
7392
+ []
7393
+ );
7394
+ const editMessage = React2.useCallback(
7395
+ async (messageId, newContent) => {
7396
+ await chatRef.current?.sendMessage(newContent, void 0, {
7397
+ editMessageId: messageId
7398
+ });
7399
+ setInput("");
7400
+ },
7401
+ []
7402
+ );
7403
+ React2.useEffect(() => {
4672
7404
  return () => {
4673
7405
  chatRef.current?.dispose();
4674
7406
  };
@@ -4686,17 +7418,30 @@ function useChat(config) {
4686
7418
  setMessages,
4687
7419
  regenerate,
4688
7420
  continueWithToolResults,
4689
- chatRef
7421
+ chatRef,
7422
+ // Branching
7423
+ switchBranch,
7424
+ getBranchInfo,
7425
+ editMessage,
7426
+ hasBranches
4690
7427
  };
4691
7428
  }
4692
7429
 
7430
+ // src/react/skill/define-skill.ts
7431
+ function defineSkill(def) {
7432
+ return def;
7433
+ }
7434
+
4693
7435
  exports.AbstractAgentLoop = AbstractAgentLoop;
4694
7436
  exports.AbstractChat = AbstractChat;
4695
7437
  exports.CopilotProvider = CopilotProvider;
7438
+ exports.MessageHistoryContext = MessageHistoryContext;
7439
+ exports.MessageTree = MessageTree;
4696
7440
  exports.ReactChat = ReactChat;
4697
7441
  exports.ReactChatState = ReactChatState;
4698
7442
  exports.ReactThreadManager = ReactThreadManager;
4699
7443
  exports.ReactThreadManagerState = ReactThreadManagerState;
7444
+ exports.SkillProvider = SkillProvider;
4700
7445
  exports.createMessageIntentHandler = createMessageIntentHandler;
4701
7446
  exports.createPermissionStorage = createPermissionStorage;
4702
7447
  exports.createReactChat = createReactChat;
@@ -4705,9 +7450,16 @@ exports.createReactThreadManager = createReactThreadManager;
4705
7450
  exports.createReactThreadManagerState = createReactThreadManagerState;
4706
7451
  exports.createSessionPermissionCache = createSessionPermissionCache;
4707
7452
  exports.createToolIntentHandler = createToolIntentHandler;
7453
+ exports.defaultMessageHistoryConfig = defaultMessageHistoryConfig;
7454
+ exports.defineSkill = defineSkill;
4708
7455
  exports.formatKnowledgeResultsForAI = formatKnowledgeResultsForAI;
4709
7456
  exports.initialAgentLoopState = initialAgentLoopState;
7457
+ exports.isCompactionMarker = isCompactionMarker;
7458
+ exports.keepToolPairsAtomic = keepToolPairsAtomic;
4710
7459
  exports.searchKnowledgeBase = searchKnowledgeBase;
7460
+ exports.toDisplayMessage = toDisplayMessage;
7461
+ exports.toLLMMessage = toLLMMessage;
7462
+ exports.toLLMMessages = toLLMMessages;
4711
7463
  exports.useAIAction = useAIAction;
4712
7464
  exports.useAIActions = useAIActions;
4713
7465
  exports.useAIContext = useAIContext;
@@ -4716,6 +7468,7 @@ exports.useAITools = useAITools;
4716
7468
  exports.useAgent = useAgent;
4717
7469
  exports.useCapabilities = useCapabilities;
4718
7470
  exports.useChat = useChat;
7471
+ exports.useContextStats = useContextStats;
4719
7472
  exports.useCopilot = useCopilot;
4720
7473
  exports.useDevLogger = useDevLogger;
4721
7474
  exports.useFeatureSupport = useFeatureSupport;
@@ -4723,6 +7476,10 @@ exports.useKnowledgeBase = useKnowledgeBase;
4723
7476
  exports.useMCPClient = useMCPClient;
4724
7477
  exports.useMCPTools = useMCPTools;
4725
7478
  exports.useMCPUIIntents = useMCPUIIntents;
7479
+ exports.useMessageHistory = useMessageHistory;
7480
+ exports.useMessageHistoryContext = useMessageHistoryContext;
7481
+ exports.useSkill = useSkill;
7482
+ exports.useSkillStatus = useSkillStatus;
4726
7483
  exports.useSuggestions = useSuggestions;
4727
7484
  exports.useSupportedMediaTypes = useSupportedMediaTypes;
4728
7485
  exports.useThreadManager = useThreadManager;
@@ -4731,5 +7488,5 @@ exports.useToolExecutor = useToolExecutor;
4731
7488
  exports.useToolWithSchema = useToolWithSchema;
4732
7489
  exports.useTools = useTools;
4733
7490
  exports.useToolsWithSchema = useToolsWithSchema;
4734
- //# sourceMappingURL=chunk-N6VZ7FOW.cjs.map
4735
- //# sourceMappingURL=chunk-N6VZ7FOW.cjs.map
7491
+ //# sourceMappingURL=chunk-76RE7AJE.cjs.map
7492
+ //# sourceMappingURL=chunk-76RE7AJE.cjs.map