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

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-5Q72LZ5H.js} +3107 -357
  10. package/dist/chunk-5Q72LZ5H.js.map +1 -0
  11. package/dist/chunk-BJYA5NDL.cjs +96 -0
  12. package/dist/chunk-BJYA5NDL.cjs.map +1 -0
  13. package/dist/{chunk-LZMBBGWH.js → chunk-ENFWM3EY.js} +4 -4
  14. package/dist/{chunk-LZMBBGWH.js.map → chunk-ENFWM3EY.js.map} +1 -1
  15. package/dist/{chunk-OQPRIB73.cjs → chunk-I3SQUNTT.cjs} +71 -25
  16. package/dist/chunk-I3SQUNTT.cjs.map +1 -0
  17. package/dist/{chunk-N6VZ7FOW.cjs → chunk-IXWNDR7H.cjs} +3290 -522
  18. package/dist/chunk-IXWNDR7H.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 +509 -209
  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 +457 -158
  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,6 +1,7 @@
1
- import { ThreadManager, isConsoleCaptureActive, startConsoleCapture, isNetworkCaptureActive, startNetworkCapture, stopConsoleCapture, stopNetworkCapture, isScreenshotSupported, captureScreenshot, getConsoleLogs, getNetworkRequests, clearConsoleLogs, clearNetworkRequests, formatLogsForAI, formatRequestsForAI, detectIntent, zodToJsonSchema, streamSSE, zodObjectToInputSchema } from './chunk-37KEHUCE.js';
1
+ import { ThreadManager, createLogger, zodToJsonSchema, isConsoleCaptureActive, startConsoleCapture, isNetworkCaptureActive, startNetworkCapture, stopConsoleCapture, stopNetworkCapture, isScreenshotSupported, captureScreenshot, getConsoleLogs, getNetworkRequests, clearConsoleLogs, clearNetworkRequests, formatLogsForAI, formatRequestsForAI, detectIntent, streamSSE, zodObjectToInputSchema } from './chunk-UXJ6LIZB.js';
2
2
  import { createMCPClient, MCPToolAdapter } from './chunk-EWVQWTNV.js';
3
- import { createContext, useRef, useState, useCallback, useEffect, useMemo, useContext, useSyncExternalStore } from 'react';
3
+ import { SkillRegistry } from './chunk-VNLLW3ZI.js';
4
+ import React2, { createContext, useRef, useState, useCallback, useEffect, useMemo, useContext, useSyncExternalStore } from 'react';
4
5
  import { jsxs, jsx } from 'react/jsx-runtime';
5
6
  import * as z from 'zod';
6
7
 
@@ -119,15 +120,6 @@ function parseSSELine(line) {
119
120
  function isStreamDone(chunk) {
120
121
  return chunk.type === "done" || chunk.type === "error";
121
122
  }
122
- function requiresToolExecution(chunk) {
123
- if (chunk.type === "done" && chunk.requiresAction) {
124
- return true;
125
- }
126
- if (chunk.type === "tool_calls") {
127
- return true;
128
- }
129
- return false;
130
- }
131
123
 
132
124
  // src/chat/functions/stream/processChunk.ts
133
125
  function processStreamChunk(chunk, state) {
@@ -262,13 +254,14 @@ function createStreamState(messageId) {
262
254
  function generateMessageId() {
263
255
  return `msg-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
264
256
  }
265
- function createUserMessage(content, attachments) {
257
+ function createUserMessage(content, attachments, options) {
266
258
  return {
267
259
  id: generateMessageId(),
268
260
  role: "user",
269
261
  content,
270
262
  attachments,
271
- createdAt: /* @__PURE__ */ new Date()
263
+ createdAt: /* @__PURE__ */ new Date(),
264
+ ...options?.parentId !== void 0 ? { parentId: options.parentId } : {}
272
265
  };
273
266
  }
274
267
  function streamStateToMessage(state) {
@@ -307,12 +300,13 @@ function streamStateToMessage(state) {
307
300
  metadata: Object.keys(metadata).length > 0 ? metadata : void 0
308
301
  };
309
302
  }
310
- function createEmptyAssistantMessage(id) {
303
+ function createEmptyAssistantMessage(id, options) {
311
304
  return {
312
305
  id: generateMessageId(),
313
306
  role: "assistant",
314
307
  content: "",
315
- createdAt: /* @__PURE__ */ new Date()
308
+ createdAt: /* @__PURE__ */ new Date(),
309
+ ...options?.parentId !== void 0 ? { parentId: options.parentId } : {}
316
310
  };
317
311
  }
318
312
 
@@ -381,6 +375,7 @@ var HttpTransport = class {
381
375
  tools: request.tools,
382
376
  actions: request.actions,
383
377
  streaming: this.config.streaming,
378
+ __skills: request.__skills,
384
379
  ...resolved.configBody,
385
380
  ...request.body
386
381
  }),
@@ -521,17 +516,443 @@ var HttpTransport = class {
521
516
  }
522
517
  };
523
518
 
524
- // src/chat/classes/AbstractChat.ts
525
- function buildToolResultContentForAI(result, tool2, args) {
526
- if (typeof result === "string") return result;
527
- const typedResult = result;
519
+ // src/chat/optimizations.ts
520
+ var DEFAULT_CHARS_PER_TOKEN = 4;
521
+ var DEFAULT_SAFETY_MARGIN = 1.2;
522
+ var DEFAULT_INPUT_HEADROOM_RATIO = 0.75;
523
+ var DEFAULT_SYSTEM_PROMPT_SHARE = 0.15;
524
+ var DEFAULT_HISTORY_SHARE = 0.5;
525
+ var DEFAULT_TOOL_RESULTS_SHARE = 0.3;
526
+ var DEFAULT_TOOL_DEFINITIONS_SHARE = 0.05;
527
+ var DEFAULT_MAX_TOOL_RESULT_CONTEXT_SHARE = 0.3;
528
+ var DEFAULT_TOOL_RESULT_HARD_MAX_CHARS = 4e5;
529
+ var DEFAULT_TOOL_RESULT_MIN_KEEP_CHARS = 2e3;
530
+ var DEFAULT_TOOL_RESULT_STRATEGY = "head-tail";
531
+ var DEFAULT_RECENT_HISTORY_PRESERVE = 6;
532
+ var TOOL_RESULT_TRUNCATION_NOTICE = "\n\n[tool result truncated to fit prompt budget]";
533
+ var TOOL_RESULT_COMPACTION_NOTICE = "[tool result compacted to preserve context budget]";
534
+ var SYSTEM_PROMPT_TRUNCATION_NOTICE = "\n\n[system prompt truncated to fit prompt budget]";
535
+ var HISTORY_SUMMARY_HEADER = "Conversation summary of earlier context:";
536
+ var HISTORY_SUMMARY_COMPACTION_NOTICE = "\n\n[summary compacted to preserve context continuity]";
537
+ var DEFAULT_SUMMARY_TRIGGER = 12;
538
+ var DEFAULT_SUMMARY_CHUNK_SIZE = 10;
539
+ var DEFAULT_SUMMARY_MAX_CHARS = 1600;
540
+ var SUMMARY_STOP_WORDS = /* @__PURE__ */ new Set([
541
+ "about",
542
+ "after",
543
+ "again",
544
+ "also",
545
+ "because",
546
+ "been",
547
+ "before",
548
+ "being",
549
+ "could",
550
+ "from",
551
+ "have",
552
+ "into",
553
+ "just",
554
+ "more",
555
+ "need",
556
+ "only",
557
+ "over",
558
+ "same",
559
+ "some",
560
+ "than",
561
+ "that",
562
+ "their",
563
+ "them",
564
+ "then",
565
+ "there",
566
+ "these",
567
+ "they",
568
+ "this",
569
+ "those",
570
+ "through",
571
+ "under",
572
+ "very",
573
+ "want",
574
+ "were",
575
+ "what",
576
+ "when",
577
+ "where",
578
+ "which",
579
+ "while",
580
+ "with",
581
+ "would",
582
+ "your"
583
+ ]);
584
+ function clampRatio(value, fallback) {
585
+ if (!Number.isFinite(value)) {
586
+ return fallback;
587
+ }
588
+ return Math.min(1, Math.max(0, value));
589
+ }
590
+ function unique(values) {
591
+ return [...new Set(values)];
592
+ }
593
+ function stringifyContent(value) {
594
+ if (typeof value === "string") {
595
+ return value;
596
+ }
597
+ if (value == null) {
598
+ return "";
599
+ }
600
+ try {
601
+ return JSON.stringify(value);
602
+ } catch {
603
+ return String(value);
604
+ }
605
+ }
606
+ function normalizeWhitespace(text) {
607
+ return text.replace(/\s+/g, " ").trim();
608
+ }
609
+ function abbreviateText(text, maxChars = 220) {
610
+ const normalized = normalizeWhitespace(text);
611
+ if (!normalized) {
612
+ return "";
613
+ }
614
+ if (normalized.length <= maxChars) {
615
+ return normalized;
616
+ }
617
+ return `${normalized.slice(0, Math.max(1, maxChars - 3)).trimEnd()}...`;
618
+ }
619
+ function tokenize(text) {
620
+ return text.toLowerCase().replace(/[^a-z0-9_\s-]/g, " ").split(/\s+/).filter((token) => token.length > 1);
621
+ }
622
+ function estimateTokens(text, charsPerToken = DEFAULT_CHARS_PER_TOKEN) {
623
+ if (!text) {
624
+ return 0;
625
+ }
626
+ return Math.ceil(text.length / Math.max(1, charsPerToken));
627
+ }
628
+ function estimateMessageTokens(message, charsPerToken = DEFAULT_CHARS_PER_TOKEN) {
629
+ const content = typeof message.content === "string" ? message.content : JSON.stringify(message.content ?? "");
630
+ const toolCalls = message.tool_calls ? JSON.stringify(message.tool_calls) : "";
631
+ const attachments = message.attachments ? JSON.stringify(message.attachments) : "";
632
+ return estimateTokens(
633
+ `${message.role}
634
+ ${content}
635
+ ${toolCalls}
636
+ ${attachments}`,
637
+ charsPerToken
638
+ );
639
+ }
640
+ function estimateToolTokens(tool2, charsPerToken = DEFAULT_CHARS_PER_TOKEN) {
641
+ return estimateTokens(JSON.stringify(tool2), charsPerToken);
642
+ }
643
+ function buildToolQuery(messages) {
644
+ return messages.filter(
645
+ (message) => message.role === "user" || message.role === "assistant"
646
+ ).slice(-3).map((message) => message.content).filter(Boolean).join(" ");
647
+ }
648
+ function matchesSelector(tool2, selector, activeProfile) {
649
+ const normalized = selector.trim().toLowerCase();
650
+ if (!normalized) {
651
+ return false;
652
+ }
653
+ if (normalized === "*" || normalized === "all") {
654
+ return true;
655
+ }
656
+ if (normalized === tool2.name.toLowerCase()) {
657
+ return true;
658
+ }
659
+ if (normalized.startsWith("group:")) {
660
+ return (tool2.group ?? "").toLowerCase() === normalized.slice(6);
661
+ }
662
+ if (normalized.startsWith("category:")) {
663
+ return (tool2.category ?? "").toLowerCase() === normalized.slice(9);
664
+ }
665
+ if (normalized.startsWith("profile:")) {
666
+ return (tool2.profiles ?? []).map((value) => value.toLowerCase()).includes(normalized.slice(8));
667
+ }
668
+ if (activeProfile && normalized === activeProfile.toLowerCase()) {
669
+ return (tool2.profiles ?? []).map((value) => value.toLowerCase()).includes(normalized);
670
+ }
671
+ return false;
672
+ }
673
+ function scoreTool(tool2, queryTokens, activeProfile) {
674
+ const haystack = [
675
+ tool2.name,
676
+ tool2.description,
677
+ tool2.category,
678
+ tool2.group,
679
+ ...tool2.profiles ?? [],
680
+ ...tool2.searchKeywords ?? []
681
+ ].filter(Boolean).join(" ").toLowerCase();
682
+ let score = tool2.deferLoading ? 0 : 2;
683
+ if (activeProfile && tool2.profiles?.includes(activeProfile)) {
684
+ score += 2;
685
+ }
686
+ for (const token of queryTokens) {
687
+ if (tool2.name.toLowerCase() === token) {
688
+ score += 6;
689
+ } else if (tool2.name.toLowerCase().includes(token)) {
690
+ score += 4;
691
+ } else if (haystack.includes(token)) {
692
+ score += 2;
693
+ }
694
+ }
695
+ return score;
696
+ }
697
+ function truncateText(text, maxChars, strategy, notice = TOOL_RESULT_TRUNCATION_NOTICE) {
698
+ if (text.length <= maxChars) {
699
+ return text;
700
+ }
701
+ const bodyBudget = Math.max(1, maxChars - notice.length);
702
+ if (strategy === "head") {
703
+ return text.slice(0, bodyBudget) + notice;
704
+ }
705
+ if (strategy === "head-tail" || strategy === "smart") {
706
+ const tailLooksImportant = strategy === "smart" ? /\b(error|exception|failed|traceback|summary|result|done|complete)\b/i.test(
707
+ text.slice(-2e3)
708
+ ) : true;
709
+ if (tailLooksImportant && bodyBudget > 32) {
710
+ const tailBudget = Math.min(Math.floor(bodyBudget * 0.3), 4e3);
711
+ const headBudget = Math.max(1, bodyBudget - tailBudget - 32);
712
+ return text.slice(0, headBudget) + "\n\n[... omitted ...]\n\n" + text.slice(-tailBudget) + notice;
713
+ }
714
+ }
715
+ return text.slice(0, bodyBudget) + notice;
716
+ }
717
+ function isHistorySummaryMessage(message) {
718
+ return message.role === "system" && typeof message.content === "string" && message.content.startsWith(HISTORY_SUMMARY_HEADER);
719
+ }
720
+ function collectTopKeywords(messages) {
721
+ const counts = /* @__PURE__ */ new Map();
722
+ for (const message of messages) {
723
+ if (message.role !== "user") {
724
+ continue;
725
+ }
726
+ for (const token of tokenize(stringifyContent(message.content))) {
727
+ if (token.length < 3 || SUMMARY_STOP_WORDS.has(token)) {
728
+ continue;
729
+ }
730
+ counts.set(token, (counts.get(token) ?? 0) + 1);
731
+ }
732
+ }
733
+ return [...counts.entries()].sort((left, right) => {
734
+ const countDiff = right[1] - left[1];
735
+ if (countDiff !== 0) {
736
+ return countDiff;
737
+ }
738
+ return left[0].localeCompare(right[0]);
739
+ }).slice(0, 5).map(([token]) => token);
740
+ }
741
+ function collectToolCallNames(messages) {
742
+ const toolCallNames = /* @__PURE__ */ new Map();
743
+ for (const message of messages) {
744
+ if (message.role !== "assistant" || !message.tool_calls?.length) {
745
+ continue;
746
+ }
747
+ for (const toolCall of message.tool_calls) {
748
+ const parsedToolCall = toolCall;
749
+ if (parsedToolCall.id && parsedToolCall.function?.name) {
750
+ toolCallNames.set(parsedToolCall.id, parsedToolCall.function.name);
751
+ }
752
+ }
753
+ }
754
+ return toolCallNames;
755
+ }
756
+ function compressSummaryContent(content, maxChars, fallbackBehavior) {
757
+ if (content.length <= maxChars) {
758
+ return content;
759
+ }
760
+ if (fallbackBehavior === "error") {
761
+ throw new Error("History summary exceeded configured continuity budget.");
762
+ }
763
+ if (fallbackBehavior === "statistical") {
764
+ const lines = content.split("\n");
765
+ const retained = lines.filter(
766
+ (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(
767
+ line
768
+ )
769
+ );
770
+ const statistical = retained.join("\n");
771
+ if (statistical.length <= maxChars) {
772
+ return statistical;
773
+ }
774
+ }
775
+ return truncateText(
776
+ content,
777
+ maxChars,
778
+ "head",
779
+ HISTORY_SUMMARY_COMPACTION_NOTICE
780
+ );
781
+ }
782
+ function buildHistorySummary(messages, summarization, maxChars = DEFAULT_SUMMARY_MAX_CHARS) {
783
+ if (messages.length === 0) {
784
+ return null;
785
+ }
786
+ if (summarization?.enabled === false) {
787
+ return null;
788
+ }
789
+ const previousSummaries = messages.filter(isHistorySummaryMessage);
790
+ const rawMessages = messages.filter(
791
+ (message) => !isHistorySummaryMessage(message)
792
+ );
793
+ const focusWindowSize = Math.max(
794
+ 1,
795
+ summarization?.chunkSize ?? DEFAULT_SUMMARY_CHUNK_SIZE
796
+ );
797
+ const detailedThreshold = Math.max(
798
+ 1,
799
+ summarization?.triggerAt ?? DEFAULT_SUMMARY_TRIGGER
800
+ );
801
+ const focusMessages = rawMessages.slice(-focusWindowSize);
802
+ const userMessages = rawMessages.filter((message) => message.role === "user");
803
+ const assistantMessages = rawMessages.filter(
804
+ (message) => message.role === "assistant"
805
+ );
806
+ const toolMessages = rawMessages.filter((message) => message.role === "tool");
807
+ const recentUser = abbreviateText(stringifyContent(userMessages.at(-1)?.content), 240) || "n/a";
808
+ const recentAssistant = abbreviateText(stringifyContent(assistantMessages.at(-1)?.content), 240) || "n/a";
809
+ const recentUserGoals = userMessages.slice(-3).map((message) => abbreviateText(stringifyContent(message.content), 180)).filter(Boolean);
810
+ const recentAssistantNotes = assistantMessages.map((message) => abbreviateText(stringifyContent(message.content), 180)).filter(Boolean).slice(-2);
811
+ const toolCallNames = collectToolCallNames(rawMessages);
812
+ const toolActivity = unique([
813
+ ...focusMessages.filter((message) => message.role === "assistant").flatMap(
814
+ (message) => (message.tool_calls ?? []).map((toolCall) => {
815
+ const parsedToolCall = toolCall;
816
+ return parsedToolCall.function?.name;
817
+ }).filter((name) => Boolean(name))
818
+ ),
819
+ ...toolMessages.slice(-2).map((message) => {
820
+ const toolName = message.tool_call_id ? toolCallNames.get(message.tool_call_id) : void 0;
821
+ const snippet = abbreviateText(stringifyContent(message.content), 120);
822
+ return toolName && snippet ? `${toolName}: ${snippet}` : toolName ?? snippet;
823
+ }).filter(Boolean)
824
+ ]).slice(-4);
825
+ const priorSummaryCarryForward = previousSummaries.map(
826
+ (message) => abbreviateText(
827
+ stringifyContent(message.content).replace(
828
+ `${HISTORY_SUMMARY_HEADER}
829
+ `,
830
+ ""
831
+ ),
832
+ 180
833
+ )
834
+ ).filter(Boolean).slice(-2);
835
+ const topKeywords = rawMessages.length >= detailedThreshold ? collectTopKeywords(
836
+ focusMessages.length > 0 ? focusMessages : rawMessages
837
+ ) : [];
838
+ const lines = [
839
+ HISTORY_SUMMARY_HEADER,
840
+ "Stats:",
841
+ `- Messages compacted: ${messages.length}`,
842
+ `- User turns compacted: ${userMessages.length}`,
843
+ `- Assistant turns compacted: ${assistantMessages.length}`,
844
+ `- Tool results compacted: ${toolMessages.length}`
845
+ ];
846
+ if (previousSummaries.length > 0) {
847
+ lines.push(`- Previous summaries merged: ${previousSummaries.length}`);
848
+ }
849
+ if (rawMessages.length > focusMessages.length) {
850
+ lines.push(
851
+ `- Older compacted messages outside the detailed window: ${rawMessages.length - focusMessages.length}`
852
+ );
853
+ }
854
+ if (topKeywords.length > 0) {
855
+ lines.push(`- Recurring user topics: ${topKeywords.join(", ")}`);
856
+ }
857
+ if (recentUser !== "n/a") {
858
+ lines.push(`- Latest user request before preserved window: ${recentUser}`);
859
+ }
860
+ if (recentAssistant !== "n/a") {
861
+ lines.push(
862
+ `- Latest assistant response before preserved window: ${recentAssistant}`
863
+ );
864
+ }
865
+ if (recentUserGoals.length > 0) {
866
+ lines.push("Carry forward user goals:");
867
+ for (const goal of recentUserGoals) {
868
+ lines.push(`- ${goal}`);
869
+ }
870
+ }
871
+ if (recentAssistantNotes.length > 0) {
872
+ lines.push("Carry forward assistant commitments:");
873
+ for (const note of recentAssistantNotes) {
874
+ lines.push(`- ${note}`);
875
+ }
876
+ }
877
+ if (toolActivity.length > 0) {
878
+ lines.push("Recent tool activity:");
879
+ for (const item of toolActivity) {
880
+ lines.push(`- ${item}`);
881
+ }
882
+ }
883
+ if (priorSummaryCarryForward.length > 0) {
884
+ lines.push("Earlier carried-forward context:");
885
+ for (const item of priorSummaryCarryForward) {
886
+ lines.push(`- ${item}`);
887
+ }
888
+ }
889
+ const content = compressSummaryContent(
890
+ lines.join("\n"),
891
+ maxChars,
892
+ summarization?.fallbackBehavior
893
+ );
894
+ return {
895
+ role: "system",
896
+ content
897
+ };
898
+ }
899
+ function buildToolDefinitions(selectedTools) {
900
+ if (selectedTools.length === 0) {
901
+ return void 0;
902
+ }
903
+ return selectedTools.map((tool2) => ({
904
+ name: tool2.name,
905
+ description: tool2.description,
906
+ category: tool2.category,
907
+ group: tool2.group,
908
+ deferLoading: tool2.deferLoading,
909
+ profiles: tool2.profiles,
910
+ searchKeywords: tool2.searchKeywords,
911
+ inputSchema: tool2.inputSchema
912
+ }));
913
+ }
914
+ function resolveTruncationConfig(params) {
915
+ const charsPerToken = params.config?.contextManagement?.tokenEstimation?.charsPerToken ?? DEFAULT_CHARS_PER_TOKEN;
916
+ const contextWindowTokens = params.config?.contextBudget?.budget?.contextWindowTokens;
917
+ const globalConfig = params.config?.toolResultConfig?.truncation;
918
+ const perToolConfig = params.tool?.resultConfig?.truncation;
919
+ const merged = { ...globalConfig, ...perToolConfig };
920
+ const hardMaxChars = merged.hardMaxChars ?? (contextWindowTokens ? Math.floor(
921
+ contextWindowTokens * clampRatio(
922
+ merged.maxContextShare,
923
+ DEFAULT_MAX_TOOL_RESULT_CONTEXT_SHARE
924
+ ) * charsPerToken
925
+ ) : DEFAULT_TOOL_RESULT_HARD_MAX_CHARS);
926
+ return {
927
+ enabled: merged.enabled ?? true,
928
+ maxContextShare: clampRatio(
929
+ merged.maxContextShare,
930
+ DEFAULT_MAX_TOOL_RESULT_CONTEXT_SHARE
931
+ ),
932
+ hardMaxChars: Math.max(1, hardMaxChars),
933
+ minKeepChars: Math.max(
934
+ 256,
935
+ merged.minKeepChars ?? DEFAULT_TOOL_RESULT_MIN_KEEP_CHARS
936
+ ),
937
+ strategy: merged.strategy ?? DEFAULT_TOOL_RESULT_STRATEGY,
938
+ preserveErrors: merged.preserveErrors ?? true
939
+ };
940
+ }
941
+ function buildToolResultContent(result, tool2, args) {
942
+ if (typeof result === "string") {
943
+ return result;
944
+ }
945
+ const typedResult = result ?? null;
528
946
  const responseMode = typedResult?._aiResponseMode ?? tool2?.aiResponseMode ?? "full";
529
947
  if (typedResult?._aiContent) {
530
948
  return JSON.stringify(typedResult._aiContent);
531
949
  }
532
950
  let aiContext = typedResult?._aiContext;
533
951
  if (!aiContext && tool2?.aiContext) {
534
- aiContext = typeof tool2.aiContext === "function" ? tool2.aiContext(typedResult, args ?? {}) : tool2.aiContext;
952
+ aiContext = typeof tool2.aiContext === "function" ? tool2.aiContext(
953
+ typedResult ?? { success: true },
954
+ args ?? {}
955
+ ) : tool2.aiContext;
535
956
  }
536
957
  switch (responseMode) {
537
958
  case "none":
@@ -539,7 +960,7 @@ function buildToolResultContentForAI(result, tool2, args) {
539
960
  case "brief":
540
961
  return aiContext ?? "[Tool executed successfully]";
541
962
  case "full":
542
- default:
963
+ default: {
543
964
  if (aiContext) {
544
965
  const {
545
966
  _aiResponseMode,
@@ -557,18 +978,526 @@ Full data: ${JSON.stringify(dataOnly)}`;
557
978
  return JSON.stringify(dataOnly);
558
979
  }
559
980
  return JSON.stringify(result);
981
+ }
982
+ }
983
+ }
984
+ function buildToolResultContentForPrompt(result, tool2, args, config) {
985
+ const text = buildToolResultContent(result, tool2, args);
986
+ const truncation = resolveTruncationConfig({ tool: tool2, config });
987
+ if (!truncation.enabled) {
988
+ return text;
989
+ }
990
+ if (truncation.preserveErrors && typeof result === "object" && result !== null && "error" in result && typeof result.error === "string") {
991
+ return text;
992
+ }
993
+ const maxChars = Math.max(truncation.minKeepChars, truncation.hardMaxChars);
994
+ return truncateText(text, maxChars, truncation.strategy);
995
+ }
996
+ function sliceHistoryToMaxMessages(params) {
997
+ const { historyMessages, maxMessages, pruneStrategy, summarization } = params;
998
+ if (!maxMessages || historyMessages.length <= maxMessages) {
999
+ return historyMessages;
1000
+ }
1001
+ const dropped = historyMessages.slice(
1002
+ 0,
1003
+ historyMessages.length - maxMessages
1004
+ );
1005
+ const kept = historyMessages.slice(-maxMessages);
1006
+ if (pruneStrategy === "summarize") {
1007
+ const summary = buildHistorySummary(dropped, summarization);
1008
+ return summary ? [summary, ...kept] : kept;
1009
+ }
1010
+ return kept;
1011
+ }
1012
+ function compactHistoryToTokenBudget(params) {
1013
+ const {
1014
+ maxTokens,
1015
+ preserveRecent,
1016
+ charsPerToken,
1017
+ pruneStrategy,
1018
+ summarization
1019
+ } = params;
1020
+ let historyMessages = params.historyMessages;
1021
+ if (!maxTokens) {
1022
+ return historyMessages;
1023
+ }
1024
+ const getHistoryTokens = () => historyMessages.reduce(
1025
+ (sum, message) => sum + estimateMessageTokens(message, charsPerToken),
1026
+ 0
1027
+ );
1028
+ while (historyMessages.length > 1 && getHistoryTokens() > maxTokens) {
1029
+ const prunableCount = Math.max(0, historyMessages.length - preserveRecent);
1030
+ if (prunableCount <= 0) {
1031
+ const firstMessage = historyMessages[0];
1032
+ if (isHistorySummaryMessage(firstMessage) && typeof firstMessage.content === "string") {
1033
+ const compactedSummary = compressSummaryContent(
1034
+ firstMessage.content,
1035
+ Math.max(400, Math.floor(maxTokens * charsPerToken * 0.25)),
1036
+ summarization?.fallbackBehavior
1037
+ );
1038
+ if (compactedSummary !== firstMessage.content) {
1039
+ historyMessages = [
1040
+ { ...firstMessage, content: compactedSummary },
1041
+ ...historyMessages.slice(1)
1042
+ ];
1043
+ continue;
1044
+ }
1045
+ }
1046
+ historyMessages = historyMessages.slice(1);
1047
+ continue;
1048
+ }
1049
+ const pruned = historyMessages.slice(0, prunableCount);
1050
+ const kept = historyMessages.slice(prunableCount);
1051
+ if (pruneStrategy === "summarize") {
1052
+ const summary = buildHistorySummary(
1053
+ pruned,
1054
+ summarization,
1055
+ Math.max(500, Math.floor(maxTokens * charsPerToken * 0.35))
1056
+ );
1057
+ historyMessages = summary ? [summary, ...kept] : kept;
1058
+ } else {
1059
+ historyMessages = kept;
1060
+ }
1061
+ }
1062
+ return historyMessages;
1063
+ }
1064
+ function compactToolResultsToBudget(params) {
1065
+ let toolResultMessages = params.toolResultMessages;
1066
+ if (!params.maxTokens) {
1067
+ return toolResultMessages;
1068
+ }
1069
+ const getToolResultTokens = () => toolResultMessages.reduce(
1070
+ (sum, message) => sum + estimateMessageTokens(message, params.charsPerToken),
1071
+ 0
1072
+ );
1073
+ while (toolResultMessages.length > 0 && getToolResultTokens() > params.maxTokens) {
1074
+ const index = toolResultMessages.findIndex(
1075
+ (message) => message.content !== TOOL_RESULT_COMPACTION_NOTICE
1076
+ );
1077
+ if (index === -1) {
1078
+ break;
1079
+ }
1080
+ toolResultMessages = toolResultMessages.map(
1081
+ (message, currentIndex) => currentIndex === index ? { ...message, content: TOOL_RESULT_COMPACTION_NOTICE } : message
1082
+ );
1083
+ }
1084
+ return toolResultMessages;
1085
+ }
1086
+ function fitToolsToBudget(params) {
1087
+ let tools = params.tools;
1088
+ if (!tools?.length || !params.maxTokens) {
1089
+ return tools;
1090
+ }
1091
+ const getToolTokens = () => tools.reduce(
1092
+ (sum, tool2) => sum + estimateToolTokens(tool2, params.charsPerToken),
1093
+ 0
1094
+ );
1095
+ while (tools.length > 0 && getToolTokens() > params.maxTokens) {
1096
+ tools = tools.slice(0, -1);
560
1097
  }
1098
+ return tools;
1099
+ }
1100
+ function truncateSystemPromptToBudget(params) {
1101
+ const { systemPrompt, maxTokens, charsPerToken } = params;
1102
+ if (!systemPrompt || !maxTokens) {
1103
+ return systemPrompt;
1104
+ }
1105
+ const maxChars = maxTokens * charsPerToken;
1106
+ if (systemPrompt.length <= maxChars) {
1107
+ return systemPrompt;
1108
+ }
1109
+ return truncateText(
1110
+ systemPrompt,
1111
+ maxChars,
1112
+ "head",
1113
+ SYSTEM_PROMPT_TRUNCATION_NOTICE
1114
+ );
1115
+ }
1116
+ function calculateBuckets(params) {
1117
+ const systemPromptTokens = estimateTokens(
1118
+ params.systemPrompt ?? "",
1119
+ params.charsPerToken
1120
+ );
1121
+ const historyTokens = params.historyMessages.reduce(
1122
+ (sum, message) => sum + estimateMessageTokens(message, params.charsPerToken),
1123
+ 0
1124
+ );
1125
+ const toolResultsTokens = params.toolResultMessages.reduce(
1126
+ (sum, message) => sum + estimateMessageTokens(message, params.charsPerToken),
1127
+ 0
1128
+ );
1129
+ const toolDefinitionTokens = (params.requestTools ?? []).reduce(
1130
+ (sum, tool2) => sum + estimateToolTokens(tool2, params.charsPerToken),
1131
+ 0
1132
+ );
1133
+ const total = systemPromptTokens + historyTokens + toolResultsTokens + toolDefinitionTokens;
1134
+ const budget = Number.isFinite(params.availableBudget) ? params.availableBudget : total;
1135
+ const toPart = (tokens) => ({
1136
+ tokens,
1137
+ percent: budget > 0 ? Number((tokens / budget * 100).toFixed(2)) : 0
1138
+ });
1139
+ return {
1140
+ total: toPart(total),
1141
+ breakdown: {
1142
+ systemPrompt: toPart(systemPromptTokens),
1143
+ history: toPart(historyTokens),
1144
+ toolResults: toPart(toolResultsTokens),
1145
+ tools: toPart(toolDefinitionTokens)
1146
+ },
1147
+ budget: {
1148
+ available: budget,
1149
+ remaining: Math.max(0, budget - total)
1150
+ },
1151
+ warnings: unique(params.warnings)
1152
+ };
1153
+ }
1154
+ function mergeBucketsInOriginalOrder(params) {
1155
+ const historyQueue = [...params.historyMessages];
1156
+ const toolQueue = [...params.toolResultMessages];
1157
+ return params.transformedMessages.flatMap((message) => {
1158
+ if (message.role === "tool") {
1159
+ const nextTool = toolQueue.shift();
1160
+ return nextTool ? [nextTool] : [];
1161
+ }
1162
+ const nextHistory = historyQueue.shift();
1163
+ return nextHistory ? [nextHistory] : [];
1164
+ });
561
1165
  }
1166
+ var ChatContextOptimizer = class {
1167
+ constructor(config) {
1168
+ this.lastContextUsage = null;
1169
+ this.config = config;
1170
+ this.activeProfile = config?.toolProfiles?.defaultProfile;
1171
+ }
1172
+ updateConfig(config) {
1173
+ this.config = config;
1174
+ if (!this.activeProfile) {
1175
+ this.activeProfile = config?.toolProfiles?.defaultProfile;
1176
+ }
1177
+ }
1178
+ setActiveProfile(profile) {
1179
+ this.activeProfile = profile?.trim() || void 0;
1180
+ }
1181
+ getContextUsage() {
1182
+ return this.lastContextUsage;
1183
+ }
1184
+ prepare(params) {
1185
+ const charsPerToken = this.config?.contextManagement?.tokenEstimation?.charsPerToken ?? DEFAULT_CHARS_PER_TOKEN;
1186
+ const safetyMargin = this.config?.contextManagement?.tokenEstimation?.safetyMargin ?? DEFAULT_SAFETY_MARGIN;
1187
+ const warnings = [];
1188
+ const contextManagement = this.config?.contextManagement;
1189
+ const contextBudget = this.config?.contextBudget;
1190
+ const allTools = params.tools ?? [];
1191
+ const selectedTools = this.selectTools(allTools, params.messages);
1192
+ const transformedMessages = this.transformMessages(
1193
+ params.messages,
1194
+ allTools
1195
+ );
1196
+ const preserveRecent = contextManagement?.summarization?.preserveRecent ?? DEFAULT_RECENT_HISTORY_PRESERVE;
1197
+ let buckets = {
1198
+ systemPrompt: params.systemPrompt,
1199
+ transformedMessages,
1200
+ historyMessages: transformedMessages.filter(
1201
+ (message) => message.role !== "tool"
1202
+ ),
1203
+ toolResultMessages: transformedMessages.filter(
1204
+ (message) => message.role === "tool"
1205
+ ),
1206
+ requestTools: buildToolDefinitions(selectedTools)
1207
+ };
1208
+ if (contextManagement?.enabled) {
1209
+ buckets.historyMessages = sliceHistoryToMaxMessages({
1210
+ historyMessages: buckets.historyMessages,
1211
+ maxMessages: contextManagement.history?.maxMessages,
1212
+ pruneStrategy: contextManagement.history?.pruneStrategy,
1213
+ summarization: contextManagement?.summarization
1214
+ });
1215
+ }
1216
+ const budgetConfig = contextBudget?.budget;
1217
+ const contextWindowTokens = budgetConfig?.contextWindowTokens;
1218
+ const inputHeadroomRatio = clampRatio(
1219
+ budgetConfig?.inputHeadroomRatio,
1220
+ DEFAULT_INPUT_HEADROOM_RATIO
1221
+ );
1222
+ const availableBudget = contextWindowTokens ? Math.max(1, Math.floor(contextWindowTokens * inputHeadroomRatio)) : Number.POSITIVE_INFINITY;
1223
+ const sharedBudget = Number.isFinite(availableBudget) ? availableBudget : void 0;
1224
+ const systemPromptBudget = sharedBudget ? Math.max(
1225
+ 1,
1226
+ Math.floor(
1227
+ sharedBudget * clampRatio(
1228
+ budgetConfig?.systemPromptShare,
1229
+ DEFAULT_SYSTEM_PROMPT_SHARE
1230
+ )
1231
+ )
1232
+ ) : void 0;
1233
+ const historyBudgetByShare = sharedBudget ? Math.max(
1234
+ 1,
1235
+ Math.floor(
1236
+ sharedBudget * clampRatio(budgetConfig?.historyShare, DEFAULT_HISTORY_SHARE)
1237
+ )
1238
+ ) : void 0;
1239
+ const historyBudgetByConfig = contextManagement?.enabled && contextManagement.history?.maxTokens ? Math.floor(contextManagement.history.maxTokens / safetyMargin) : void 0;
1240
+ const historyBudget = historyBudgetByShare && historyBudgetByConfig ? Math.min(historyBudgetByShare, historyBudgetByConfig) : historyBudgetByShare ?? historyBudgetByConfig;
1241
+ const toolResultsBudget = sharedBudget ? Math.max(
1242
+ 1,
1243
+ Math.floor(
1244
+ sharedBudget * clampRatio(
1245
+ budgetConfig?.toolResultsShare,
1246
+ DEFAULT_TOOL_RESULTS_SHARE
1247
+ )
1248
+ )
1249
+ ) : void 0;
1250
+ const toolDefinitionsBudget = sharedBudget ? Math.max(
1251
+ 1,
1252
+ Math.floor(
1253
+ sharedBudget * clampRatio(
1254
+ budgetConfig?.toolDefinitionsShare,
1255
+ DEFAULT_TOOL_DEFINITIONS_SHARE
1256
+ )
1257
+ )
1258
+ ) : void 0;
1259
+ if (contextBudget?.enabled) {
1260
+ buckets.systemPrompt = truncateSystemPromptToBudget({
1261
+ systemPrompt: buckets.systemPrompt,
1262
+ maxTokens: systemPromptBudget,
1263
+ charsPerToken
1264
+ });
1265
+ }
1266
+ buckets.historyMessages = compactHistoryToTokenBudget({
1267
+ historyMessages: buckets.historyMessages,
1268
+ maxTokens: historyBudget,
1269
+ preserveRecent,
1270
+ charsPerToken,
1271
+ pruneStrategy: contextManagement?.history?.pruneStrategy,
1272
+ summarization: contextManagement?.summarization
1273
+ });
1274
+ buckets.toolResultMessages = compactToolResultsToBudget({
1275
+ toolResultMessages: buckets.toolResultMessages,
1276
+ maxTokens: toolResultsBudget,
1277
+ charsPerToken
1278
+ });
1279
+ buckets.requestTools = fitToolsToBudget({
1280
+ tools: buckets.requestTools,
1281
+ maxTokens: toolDefinitionsBudget,
1282
+ charsPerToken
1283
+ });
1284
+ let usage = calculateBuckets({
1285
+ ...buckets,
1286
+ charsPerToken,
1287
+ availableBudget,
1288
+ warnings
1289
+ });
1290
+ if (Number.isFinite(availableBudget) && usage.total.tokens > availableBudget) {
1291
+ buckets.toolResultMessages = compactToolResultsToBudget({
1292
+ toolResultMessages: buckets.toolResultMessages,
1293
+ maxTokens: Math.max(
1294
+ 1,
1295
+ usage.breakdown.toolResults.tokens - usage.total.tokens + availableBudget
1296
+ ),
1297
+ charsPerToken
1298
+ });
1299
+ usage = calculateBuckets({
1300
+ ...buckets,
1301
+ charsPerToken,
1302
+ availableBudget,
1303
+ warnings
1304
+ });
1305
+ if (usage.total.tokens > availableBudget) {
1306
+ const overflow = usage.total.tokens - availableBudget;
1307
+ buckets.historyMessages = compactHistoryToTokenBudget({
1308
+ historyMessages: buckets.historyMessages,
1309
+ maxTokens: Math.max(1, usage.breakdown.history.tokens - overflow),
1310
+ preserveRecent,
1311
+ charsPerToken,
1312
+ pruneStrategy: contextManagement?.history?.pruneStrategy
1313
+ });
1314
+ usage = calculateBuckets({
1315
+ ...buckets,
1316
+ charsPerToken,
1317
+ availableBudget,
1318
+ warnings
1319
+ });
1320
+ }
1321
+ if (usage.total.tokens > availableBudget) {
1322
+ buckets.requestTools = fitToolsToBudget({
1323
+ tools: buckets.requestTools,
1324
+ maxTokens: Math.max(
1325
+ 1,
1326
+ usage.breakdown.tools.tokens - (usage.total.tokens - availableBudget)
1327
+ ),
1328
+ charsPerToken
1329
+ });
1330
+ usage = calculateBuckets({
1331
+ ...buckets,
1332
+ charsPerToken,
1333
+ availableBudget,
1334
+ warnings
1335
+ });
1336
+ }
1337
+ }
1338
+ if (Number.isFinite(availableBudget) && usage.total.tokens > availableBudget) {
1339
+ warnings.push(
1340
+ `Prompt budget exceeded: using ${usage.total.tokens} tokens of ${availableBudget}.`
1341
+ );
1342
+ usage = {
1343
+ ...usage,
1344
+ warnings: unique(warnings)
1345
+ };
1346
+ if (contextBudget?.enforcement?.mode === "error") {
1347
+ throw new Error(warnings[warnings.length - 1]);
1348
+ }
1349
+ contextBudget?.enforcement?.onBudgetExceeded?.(usage);
1350
+ } else {
1351
+ usage = {
1352
+ ...usage,
1353
+ warnings: unique(warnings)
1354
+ };
1355
+ }
1356
+ contextBudget?.monitoring?.onUsageUpdate?.(usage);
1357
+ this.lastContextUsage = usage;
1358
+ return {
1359
+ messages: mergeBucketsInOriginalOrder(buckets),
1360
+ tools: buckets.requestTools,
1361
+ contextUsage: usage,
1362
+ warnings: usage.warnings
1363
+ };
1364
+ }
1365
+ selectTools(tools, messages) {
1366
+ if (!tools.length) {
1367
+ return [];
1368
+ }
1369
+ const available = tools.filter((tool2) => tool2.available !== false);
1370
+ const profileConfig = this.config?.toolProfiles;
1371
+ if (!profileConfig?.enabled) {
1372
+ return available;
1373
+ }
1374
+ const activeProfile = this.activeProfile ?? profileConfig.defaultProfile;
1375
+ const includeUnprofiled = profileConfig.includeUnprofiled ?? true;
1376
+ const profile = activeProfile ? profileConfig.profiles?.[activeProfile] : void 0;
1377
+ let filtered = available;
1378
+ if (profile?.include?.length) {
1379
+ filtered = filtered.filter(
1380
+ (tool2) => profile.include.some(
1381
+ (selector) => matchesSelector(tool2, selector, activeProfile)
1382
+ ) || !!activeProfile && tool2.profiles?.includes(activeProfile)
1383
+ );
1384
+ } else if (activeProfile) {
1385
+ filtered = filtered.filter((tool2) => {
1386
+ if (tool2.profiles?.length) {
1387
+ return tool2.profiles.includes(activeProfile);
1388
+ }
1389
+ return includeUnprofiled;
1390
+ });
1391
+ }
1392
+ if (profile?.exclude?.length) {
1393
+ filtered = filtered.filter(
1394
+ (tool2) => !profile.exclude.some(
1395
+ (selector) => matchesSelector(tool2, selector, activeProfile)
1396
+ )
1397
+ );
1398
+ }
1399
+ if (!profileConfig.dynamicSelection?.enabled) {
1400
+ return filtered;
1401
+ }
1402
+ const maxTools = Math.max(
1403
+ 1,
1404
+ Math.min(
1405
+ profileConfig.dynamicSelection.maxTools ?? filtered.length,
1406
+ filtered.length
1407
+ )
1408
+ );
1409
+ const queryTokens = tokenize(buildToolQuery(messages));
1410
+ return [...filtered].sort((left, right) => {
1411
+ const scoreDiff = scoreTool(right, queryTokens, activeProfile) - scoreTool(left, queryTokens, activeProfile);
1412
+ if (scoreDiff !== 0) {
1413
+ return scoreDiff;
1414
+ }
1415
+ return left.name.localeCompare(right.name);
1416
+ }).slice(0, maxTools);
1417
+ }
1418
+ transformMessages(messages, allTools) {
1419
+ const toolCallMap = /* @__PURE__ */ new Map();
1420
+ for (const message of messages) {
1421
+ if (message.role !== "assistant" || !message.toolCalls?.length) {
1422
+ continue;
1423
+ }
1424
+ for (const toolCall of message.toolCalls) {
1425
+ try {
1426
+ toolCallMap.set(toolCall.id, {
1427
+ toolName: toolCall.function.name,
1428
+ args: JSON.parse(toolCall.function.arguments)
1429
+ });
1430
+ } catch {
1431
+ toolCallMap.set(toolCall.id, {
1432
+ toolName: toolCall.function.name,
1433
+ args: {}
1434
+ });
1435
+ }
1436
+ }
1437
+ }
1438
+ const toolDefMap = new Map(
1439
+ allTools.map((tool2) => [tool2.name, tool2])
1440
+ );
1441
+ return messages.map((message) => {
1442
+ if (message.role !== "tool") {
1443
+ return {
1444
+ role: message.role,
1445
+ content: message.content,
1446
+ tool_calls: message.toolCalls,
1447
+ tool_call_id: message.toolCallId,
1448
+ attachments: message.attachments
1449
+ };
1450
+ }
1451
+ const toolCall = message.toolCallId ? toolCallMap.get(message.toolCallId) : void 0;
1452
+ const tool2 = toolCall ? toolDefMap.get(toolCall.toolName) : void 0;
1453
+ let content = message.content;
1454
+ try {
1455
+ const parsed = JSON.parse(message.content);
1456
+ content = buildToolResultContentForPrompt(
1457
+ parsed,
1458
+ tool2,
1459
+ toolCall?.args ?? {},
1460
+ this.config
1461
+ );
1462
+ } catch {
1463
+ content = buildToolResultContentForPrompt(
1464
+ message.content,
1465
+ tool2,
1466
+ toolCall?.args ?? {},
1467
+ this.config
1468
+ );
1469
+ }
1470
+ return {
1471
+ role: message.role,
1472
+ content,
1473
+ tool_call_id: message.toolCallId
1474
+ };
1475
+ });
1476
+ }
1477
+ };
1478
+
1479
+ // src/chat/classes/AbstractChat.ts
562
1480
  var AbstractChat = class {
563
1481
  constructor(init) {
1482
+ this.lastContextUsage = null;
564
1483
  // Event handlers
565
1484
  this.eventHandlers = /* @__PURE__ */ new Map();
566
1485
  // Current streaming state
567
1486
  this.streamState = null;
1487
+ /**
1488
+ * Inline skills from the client (sent on every request for server to merge)
1489
+ */
1490
+ this.inlineSkills = [];
568
1491
  /**
569
1492
  * Dynamic context from useAIContext hook
570
1493
  */
571
1494
  this.dynamicContext = "";
1495
+ /**
1496
+ * Optional transform applied to messages just before building the HTTP request.
1497
+ * Used by the message-history / compaction system to send a pruned message list
1498
+ * without mutating the in-memory store (which keeps the full history for display).
1499
+ */
1500
+ this.requestMessageTransform = null;
572
1501
  this._isDisposed = false;
573
1502
  this.config = {
574
1503
  runtimeUrl: init.runtimeUrl,
@@ -578,7 +1507,8 @@ var AbstractChat = class {
578
1507
  headers: init.headers,
579
1508
  body: init.body,
580
1509
  threadId: init.threadId,
581
- debug: init.debug
1510
+ debug: init.debug,
1511
+ optimization: init.optimization
582
1512
  };
583
1513
  this.state = init.state ?? new SimpleChatState();
584
1514
  this.transport = init.transport ?? new HttpTransport({
@@ -588,6 +1518,7 @@ var AbstractChat = class {
588
1518
  streaming: init.streaming ?? true
589
1519
  });
590
1520
  this.callbacks = init.callbacks ?? {};
1521
+ this.optimizer = new ChatContextOptimizer(init.optimization);
591
1522
  if (init.initialMessages?.length) {
592
1523
  this.state.setMessages(init.initialMessages);
593
1524
  }
@@ -619,20 +1550,42 @@ var AbstractChat = class {
619
1550
  /**
620
1551
  * Send a message
621
1552
  * Returns false if a request is already in progress
1553
+ *
1554
+ * @param content - Message content
1555
+ * @param attachments - Optional attachments
1556
+ * @param options - Optional branching options
1557
+ * @param options.editMessageId - Edit flow: new message branches from the
1558
+ * same parent as this message ID, creating a parallel conversation path
622
1559
  */
623
- async sendMessage(content, attachments) {
1560
+ async sendMessage(content, attachments, options) {
624
1561
  if (this.isBusy) {
625
1562
  this.debug("sendMessage", "Blocked - request already in progress");
626
1563
  return false;
627
1564
  }
628
- this.debug("sendMessage", { content, attachments });
1565
+ this.debug("sendMessage", { content, attachments, options });
629
1566
  try {
630
1567
  this.resolveUnresolvedToolCalls();
631
- const userMessage = createUserMessage(content, attachments);
1568
+ let newParentId;
1569
+ const visibleMessages = this.state.messages;
1570
+ if (options?.editMessageId && this.state.setCurrentLeaf) {
1571
+ const allMessages = this.state.getAllMessages?.() ?? this.state.messages;
1572
+ const target = allMessages.find((m) => m.id === options.editMessageId);
1573
+ if (target && target.parentId !== void 0) {
1574
+ newParentId = target.parentId;
1575
+ this.state.setCurrentLeaf(
1576
+ typeof target.parentId === "string" ? target.parentId : null
1577
+ );
1578
+ }
1579
+ } else if (visibleMessages.length > 0) {
1580
+ newParentId = visibleMessages[visibleMessages.length - 1].id;
1581
+ }
1582
+ const userMessage = createUserMessage(content, attachments, {
1583
+ parentId: newParentId
1584
+ });
632
1585
  this.state.pushMessage(userMessage);
633
1586
  this.state.status = "submitted";
634
1587
  this.state.error = void 0;
635
- this.callbacks.onMessagesChange?.(this.state.messages);
1588
+ this.callbacks.onMessagesChange?.(this._allMessages());
636
1589
  this.callbacks.onStatusChange?.("submitted");
637
1590
  await Promise.resolve();
638
1591
  await this.processRequest();
@@ -669,20 +1622,25 @@ var AbstractChat = class {
669
1622
  "resolveUnresolvedToolCalls",
670
1623
  `Adding ${unresolvedIds.length} missing tool results`
671
1624
  );
1625
+ const visibleMsgs = this.state.messages;
1626
+ let errorChainParentId = visibleMsgs.length > 0 ? visibleMsgs[visibleMsgs.length - 1].id : void 0;
672
1627
  for (const toolCallId of unresolvedIds) {
1628
+ const toolMessageId = generateMessageId();
673
1629
  const toolMessage = {
674
- id: generateMessageId(),
1630
+ id: toolMessageId,
675
1631
  role: "tool",
676
1632
  content: JSON.stringify({
677
1633
  success: false,
678
1634
  error: "Tool execution was interrupted. Please try again."
679
1635
  }),
680
1636
  toolCallId,
681
- createdAt: /* @__PURE__ */ new Date()
1637
+ createdAt: /* @__PURE__ */ new Date(),
1638
+ ...errorChainParentId !== void 0 ? { parentId: errorChainParentId } : {}
682
1639
  };
683
1640
  this.state.pushMessage(toolMessage);
1641
+ errorChainParentId = toolMessageId;
684
1642
  }
685
- this.callbacks.onMessagesChange?.(this.state.messages);
1643
+ this.callbacks.onMessagesChange?.(this._allMessages());
686
1644
  }
687
1645
  }
688
1646
  /**
@@ -696,6 +1654,8 @@ var AbstractChat = class {
696
1654
  this.debug("continueWithToolResults", toolResults);
697
1655
  try {
698
1656
  const attachmentsToAdd = [];
1657
+ const visibleMessages = this.state.messages;
1658
+ let chainParentId = visibleMessages.length > 0 ? visibleMessages[visibleMessages.length - 1].id : void 0;
699
1659
  for (const { toolCallId, result } of toolResults) {
700
1660
  const typedResult = result;
701
1661
  let messageContent;
@@ -712,14 +1672,17 @@ var AbstractChat = class {
712
1672
  } else {
713
1673
  messageContent = typeof result === "string" ? result : JSON.stringify(result);
714
1674
  }
1675
+ const toolMessageId = generateMessageId();
715
1676
  const toolMessage = {
716
- id: generateMessageId(),
1677
+ id: toolMessageId,
717
1678
  role: "tool",
718
1679
  content: messageContent,
719
1680
  toolCallId,
720
- createdAt: /* @__PURE__ */ new Date()
1681
+ createdAt: /* @__PURE__ */ new Date(),
1682
+ ...chainParentId !== void 0 ? { parentId: chainParentId } : {}
721
1683
  };
722
1684
  this.state.pushMessage(toolMessage);
1685
+ chainParentId = toolMessageId;
723
1686
  }
724
1687
  if (attachmentsToAdd.length > 0) {
725
1688
  this.debug(
@@ -731,14 +1694,15 @@ var AbstractChat = class {
731
1694
  role: "user",
732
1695
  content: "Here's my screen:",
733
1696
  attachments: attachmentsToAdd,
734
- createdAt: /* @__PURE__ */ new Date()
1697
+ createdAt: /* @__PURE__ */ new Date(),
1698
+ ...chainParentId !== void 0 ? { parentId: chainParentId } : {}
735
1699
  };
736
1700
  this.state.pushMessage(userMessage);
737
1701
  }
738
1702
  this.state.status = "submitted";
739
- this.callbacks.onMessagesChange?.(this.state.messages);
1703
+ this.callbacks.onMessagesChange?.(this._allMessages());
740
1704
  this.callbacks.onStatusChange?.("submitted");
741
- await Promise.resolve();
1705
+ await new Promise((resolve) => setTimeout(resolve, 0));
742
1706
  await this.processRequest();
743
1707
  } catch (error) {
744
1708
  this.handleError(error);
@@ -767,30 +1731,58 @@ var AbstractChat = class {
767
1731
  this.callbacks.onMessagesChange?.(messages);
768
1732
  }
769
1733
  /**
770
- * Regenerate last response
1734
+ * Regenerate last response.
1735
+ *
1736
+ * Branch-aware: when the state supports branching (setCurrentLeaf is available),
1737
+ * regenerate creates a new sibling response instead of destroying the original.
1738
+ * The old response is preserved and navigable via switchBranch().
1739
+ *
1740
+ * Legacy fallback: when branching is not available, uses old slice() behavior.
771
1741
  */
772
1742
  async regenerate(messageId) {
1743
+ if (this.isBusy) return;
773
1744
  const messages = this.state.messages;
774
- let targetIndex = messages.length - 1;
1745
+ let targetMessage;
775
1746
  if (messageId) {
776
- targetIndex = messages.findIndex((m) => m.id === messageId);
1747
+ targetMessage = messages.find((m) => m.id === messageId);
1748
+ if (!targetMessage) {
1749
+ targetMessage = this.state.getAllMessages?.().find((m) => m.id === messageId);
1750
+ }
777
1751
  } else {
778
1752
  for (let i = messages.length - 1; i >= 0; i--) {
779
1753
  if (messages[i].role === "assistant") {
780
- targetIndex = i;
1754
+ targetMessage = messages[i];
781
1755
  break;
782
1756
  }
783
1757
  }
784
1758
  }
1759
+ if (!targetMessage) return;
1760
+ if (targetMessage.parentId !== void 0 && this.state.setCurrentLeaf) {
1761
+ this.state.setCurrentLeaf(targetMessage.parentId ?? null);
1762
+ this.callbacks.onMessagesChange?.(this._allMessages());
1763
+ this.state.status = "submitted";
1764
+ await Promise.resolve();
1765
+ await this.processRequest();
1766
+ return;
1767
+ }
1768
+ const targetIndex = messages.indexOf(targetMessage);
785
1769
  if (targetIndex > 0) {
786
1770
  this.state.setMessages(messages.slice(0, targetIndex));
787
- this.callbacks.onMessagesChange?.(this.state.messages);
1771
+ this.callbacks.onMessagesChange?.(this._allMessages());
788
1772
  await this.processRequest();
789
1773
  }
790
1774
  }
791
1775
  // ============================================
792
1776
  // Event Handling
793
1777
  // ============================================
1778
+ /**
1779
+ * Returns all messages across all branches when the state supports it
1780
+ * (branch-aware), otherwise returns the visible path.
1781
+ * Use this whenever firing onMessagesChange so inactive branches are not lost.
1782
+ */
1783
+ _allMessages() {
1784
+ return this.state.getAllMessages?.() ?? this.state.messages;
1785
+ }
794
1786
  /**
795
1787
  * Subscribe to events
796
1788
  */
@@ -822,10 +1814,32 @@ var AbstractChat = class {
822
1814
  */
823
1815
  async processRequest() {
824
1816
  const request = this.buildRequest();
1817
+ let preCreatedMessageId;
1818
+ if (this.config.streaming !== false) {
1819
+ const visibleMessages = this.state.messages;
1820
+ const currentLeafId = visibleMessages.length > 0 ? visibleMessages[visibleMessages.length - 1].id : void 0;
1821
+ const preMsg = createEmptyAssistantMessage(void 0, {
1822
+ parentId: currentLeafId
1823
+ });
1824
+ this.state.pushMessage(preMsg);
1825
+ this.callbacks.onMessagesChange?.(this._allMessages());
1826
+ preCreatedMessageId = preMsg.id;
1827
+ }
825
1828
  const response = await this.transport.send(request);
826
1829
  if (this.isAsyncIterable(response)) {
827
- await this.handleStreamResponse(response);
1830
+ await this.handleStreamResponse(response, preCreatedMessageId);
828
1831
  } else {
1832
+ if (preCreatedMessageId) {
1833
+ const id = preCreatedMessageId;
1834
+ const visibleMsgs = this.state.messages;
1835
+ const placeholderIdx = visibleMsgs.findIndex((m) => m.id === id);
1836
+ const intendedLeafId = placeholderIdx > 0 ? visibleMsgs[placeholderIdx - 1].id : null;
1837
+ const allMsgs = this.state.getAllMessages?.() ?? this.state.messages;
1838
+ this.state.setMessages(allMsgs.filter((m) => m.id !== id));
1839
+ if (intendedLeafId && this.state.setCurrentLeaf) {
1840
+ this.state.setCurrentLeaf(intendedLeafId);
1841
+ }
1842
+ }
829
1843
  this.handleJsonResponse(response);
830
1844
  }
831
1845
  }
@@ -836,19 +1850,50 @@ var AbstractChat = class {
836
1850
  this.config.tools = tools;
837
1851
  }
838
1852
  /**
839
- * Set dynamic context (appended to system prompt)
1853
+ * Update prompt/tool optimization behavior.
840
1854
  */
841
- setContext(context) {
842
- this.dynamicContext = context;
843
- this.debug("Context updated", { length: context.length });
1855
+ setOptimizationConfig(config) {
1856
+ this.config.optimization = config;
1857
+ this.optimizer.updateConfig(config);
844
1858
  }
845
1859
  /**
846
- * Set system prompt dynamically
847
- * This allows updating the system prompt after initialization
1860
+ * Select the active tool profile for future requests.
848
1861
  */
849
- setSystemPrompt(prompt) {
850
- this.config.systemPrompt = prompt;
851
- this.debug("System prompt updated", { length: prompt.length });
1862
+ setToolProfile(profile) {
1863
+ this.optimizer.setActiveProfile(profile);
1864
+ }
1865
+ /**
1866
+ * Get the most recent prompt context usage snapshot.
1867
+ */
1868
+ getContextUsage() {
1869
+ return this.lastContextUsage;
1870
+ }
1871
+ /**
1872
+ * Set inline skills (called by SkillProvider via React layer)
1873
+ */
1874
+ setInlineSkills(skills) {
1875
+ this.inlineSkills = skills;
1876
+ this.debug("Inline skills updated", { count: skills.length });
1877
+ }
1878
+ /**
1879
+ * Set (or clear) the per-request message transform.
1880
+ * Pass null to disable.
1881
+ */
1882
+ setRequestMessageTransform(fn) {
1883
+ this.requestMessageTransform = fn;
1884
+ }
1885
+ /**
1886
+ * Set dynamic context (appended to system prompt)
1887
+ */
1888
+ setContext(context) {
1889
+ this.dynamicContext = context;
1890
+ }
1891
+ /**
1892
+ * Set system prompt dynamically
1893
+ * This allows updating the system prompt after initialization
1894
+ */
1895
+ setSystemPrompt(prompt) {
1896
+ this.config.systemPrompt = prompt;
852
1897
  }
853
1898
  /**
854
1899
  * Set headers configuration
@@ -859,7 +1904,6 @@ var AbstractChat = class {
859
1904
  if (this.transport.setHeaders && headers !== void 0) {
860
1905
  this.transport.setHeaders(headers);
861
1906
  }
862
- this.debug("Headers config updated");
863
1907
  }
864
1908
  /**
865
1909
  * Set URL configuration
@@ -870,7 +1914,6 @@ var AbstractChat = class {
870
1914
  if (this.transport.setUrl) {
871
1915
  this.transport.setUrl(url);
872
1916
  }
873
- this.debug("URL config updated");
874
1917
  }
875
1918
  /**
876
1919
  * Set body configuration
@@ -881,110 +1924,88 @@ var AbstractChat = class {
881
1924
  if (this.transport.setBody && body !== void 0) {
882
1925
  this.transport.setBody(body);
883
1926
  }
884
- this.debug("Body config updated");
885
1927
  }
886
1928
  /**
887
1929
  * Build the request payload
888
1930
  */
889
1931
  buildRequest() {
890
- const tools = this.config.tools?.filter((tool2) => tool2.available !== false).map((tool2) => ({
891
- name: tool2.name,
892
- description: tool2.description,
893
- inputSchema: tool2.inputSchema
894
- }));
895
- const toolCallMap = /* @__PURE__ */ new Map();
896
- for (const msg of this.state.messages) {
897
- if (msg.role === "assistant" && msg.toolCalls) {
898
- for (const tc of msg.toolCalls) {
899
- try {
900
- const args = tc.function?.arguments ? JSON.parse(tc.function.arguments) : {};
901
- toolCallMap.set(tc.id, { toolName: tc.function.name, args });
902
- } catch {
903
- toolCallMap.set(tc.id, { toolName: tc.function.name, args: {} });
904
- }
905
- }
906
- }
907
- }
908
- const toolDefMap = /* @__PURE__ */ new Map();
909
- if (this.config.tools) {
910
- for (const tool2 of this.config.tools) {
911
- toolDefMap.set(tool2.name, {
912
- name: tool2.name,
913
- aiResponseMode: tool2.aiResponseMode,
914
- aiContext: tool2.aiContext
915
- });
916
- }
917
- }
918
- return {
919
- messages: this.state.messages.map((m) => {
920
- if (m.role === "tool" && m.content && m.toolCallId) {
921
- try {
922
- const fullResult = JSON.parse(m.content);
923
- const toolCallInfo = toolCallMap.get(m.toolCallId);
924
- const toolDef = toolCallInfo ? toolDefMap.get(toolCallInfo.toolName) : void 0;
925
- const toolArgs = toolCallInfo?.args;
926
- const transformedContent = buildToolResultContentForAI(
927
- fullResult,
928
- toolDef,
929
- toolArgs
930
- );
931
- return {
932
- role: m.role,
933
- content: transformedContent,
934
- tool_call_id: m.toolCallId
935
- };
936
- } catch (e) {
937
- this.debug("Failed to parse tool message JSON", {
938
- content: m.content?.slice(0, 100),
939
- error: e instanceof Error ? e.message : String(e)
940
- });
941
- return {
942
- role: m.role,
943
- content: m.content,
944
- tool_call_id: m.toolCallId
945
- };
946
- }
947
- }
948
- return {
949
- role: m.role,
950
- content: m.content,
951
- tool_calls: m.toolCalls,
952
- tool_call_id: m.toolCallId,
953
- attachments: m.attachments
954
- };
955
- }),
956
- threadId: this.config.threadId,
957
- systemPrompt: this.dynamicContext ? `${this.config.systemPrompt || ""}
1932
+ const systemPrompt = this.dynamicContext ? `${this.config.systemPrompt || ""}
958
1933
 
959
1934
  ## Current App Context:
960
- ${this.dynamicContext}`.trim() : this.config.systemPrompt,
1935
+ ${this.dynamicContext}`.trim() : this.config.systemPrompt;
1936
+ const rawMessages = this.requestMessageTransform ? this.requestMessageTransform(
1937
+ this.state.messages
1938
+ ) : this.state.messages;
1939
+ const optimized = this.optimizer.prepare({
1940
+ messages: rawMessages,
1941
+ tools: this.config.tools,
1942
+ systemPrompt
1943
+ });
1944
+ this.lastContextUsage = optimized.contextUsage;
1945
+ this.callbacks.onContextUsageChange?.(optimized.contextUsage);
1946
+ return {
1947
+ messages: optimized.messages,
1948
+ threadId: this.config.threadId,
1949
+ systemPrompt,
961
1950
  llm: this.config.llm,
962
- tools: tools?.length ? tools : void 0
1951
+ tools: this.config.tools?.length ? this.config.tools.map((tool2) => ({
1952
+ name: tool2.name,
1953
+ description: tool2.description,
1954
+ category: tool2.category,
1955
+ group: tool2.group,
1956
+ deferLoading: tool2.deferLoading,
1957
+ profiles: tool2.profiles,
1958
+ searchKeywords: tool2.searchKeywords,
1959
+ inputSchema: tool2.inputSchema
1960
+ })) : void 0,
1961
+ __skills: this.inlineSkills.length ? this.inlineSkills : void 0
963
1962
  };
964
1963
  }
965
1964
  /**
966
1965
  * Handle streaming response
967
1966
  */
968
- async handleStreamResponse(stream) {
1967
+ async handleStreamResponse(stream, preCreatedMessageId) {
969
1968
  this.state.status = "streaming";
970
1969
  this.callbacks.onStatusChange?.("streaming");
971
- const assistantMessage = createEmptyAssistantMessage();
972
- this.state.pushMessage(assistantMessage);
1970
+ let assistantMessage;
1971
+ if (preCreatedMessageId) {
1972
+ const existing = this.state.messages.find(
1973
+ (m) => m.id === preCreatedMessageId
1974
+ );
1975
+ if (existing) {
1976
+ assistantMessage = existing;
1977
+ } else {
1978
+ assistantMessage = createEmptyAssistantMessage();
1979
+ this.state.pushMessage(assistantMessage);
1980
+ }
1981
+ } else {
1982
+ assistantMessage = createEmptyAssistantMessage();
1983
+ this.state.pushMessage(assistantMessage);
1984
+ }
973
1985
  this.streamState = createStreamState(assistantMessage.id);
974
1986
  this.callbacks.onMessageStart?.(assistantMessage.id);
975
- this.debug("handleStreamResponse", "Starting to process stream");
1987
+ this.debugGroup("handleStreamResponse");
1988
+ this.debug("Starting to process stream");
976
1989
  let chunkCount = 0;
977
1990
  let toolCallsEmitted = false;
1991
+ let pendingClientToolCalls;
978
1992
  for await (const chunk of stream) {
979
1993
  chunkCount++;
980
- this.debug("chunk", { count: chunkCount, type: chunk.type });
1994
+ if (chunk.type !== "message:delta") {
1995
+ this.debug("chunk", { count: chunkCount, type: chunk.type });
1996
+ }
981
1997
  if (chunk.type === "error") {
982
1998
  const error = new Error(chunk.message || "Stream error");
983
1999
  this.handleError(error);
984
2000
  return;
985
2001
  }
986
2002
  if (chunk.type === "message:end" && this.streamState?.content) {
987
- this.debug("message:end mid-stream - finalizing current turn");
2003
+ this.debug("message:end mid-stream", {
2004
+ messageId: this.streamState.messageId,
2005
+ contentLength: this.streamState.content.length,
2006
+ toolCallsInState: this.streamState.toolCalls?.length ?? 0,
2007
+ chunkCount
2008
+ });
988
2009
  const turnMessage = streamStateToMessage(this.streamState);
989
2010
  const toolCallsHidden = {};
990
2011
  for (const [id, result] of this.streamState.toolResults) {
@@ -1000,7 +2021,11 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt,
1000
2021
  }
1001
2022
  this.state.updateMessageById(
1002
2023
  this.streamState.messageId,
1003
- () => turnMessage
2024
+ (existing) => ({
2025
+ ...turnMessage,
2026
+ ...existing.parentId !== void 0 ? { parentId: existing.parentId } : {},
2027
+ ...existing.childrenIds !== void 0 ? { childrenIds: existing.childrenIds } : {}
2028
+ })
1004
2029
  );
1005
2030
  this.callbacks.onMessageFinish?.(turnMessage);
1006
2031
  this.streamState = null;
@@ -1015,6 +2040,93 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt,
1015
2040
  continue;
1016
2041
  }
1017
2042
  if (!this.streamState) {
2043
+ if (chunk.type === "tool_calls") {
2044
+ pendingClientToolCalls = chunk.toolCalls;
2045
+ this.debug("tool_calls (post-message:end, stored as pending)", {
2046
+ count: pendingClientToolCalls?.length,
2047
+ ids: pendingClientToolCalls?.map((tc) => tc.id)
2048
+ });
2049
+ continue;
2050
+ }
2051
+ if (chunk.type === "done") {
2052
+ this.debug("done (post-message:end)", {
2053
+ hasPendingToolCalls: !!pendingClientToolCalls?.length,
2054
+ pendingCount: pendingClientToolCalls?.length ?? 0,
2055
+ doneMessagesCount: chunk.messages?.length ?? 0,
2056
+ requiresAction: chunk.requiresAction,
2057
+ toolCallsEmitted
2058
+ });
2059
+ if (chunk.messages?.length) {
2060
+ const pendingIds = new Set(
2061
+ (pendingClientToolCalls ?? []).filter((tc) => tc?.id).map((tc) => tc.id)
2062
+ );
2063
+ const messagesToInsert = [];
2064
+ let clientAssistantToolCalls;
2065
+ for (const msg of chunk.messages) {
2066
+ if (msg.role === "assistant" && msg.tool_calls?.length && pendingIds.size > 0 && msg.tool_calls.every(
2067
+ (tc) => pendingIds.has(tc?.id ?? "")
2068
+ )) {
2069
+ clientAssistantToolCalls = msg.tool_calls;
2070
+ continue;
2071
+ }
2072
+ if (msg.role === "assistant" && !msg.tool_calls?.length) continue;
2073
+ messagesToInsert.push({
2074
+ id: generateMessageId(),
2075
+ role: msg.role,
2076
+ content: msg.content ?? "",
2077
+ toolCalls: msg.tool_calls,
2078
+ toolCallId: msg.tool_call_id,
2079
+ createdAt: /* @__PURE__ */ new Date()
2080
+ });
2081
+ }
2082
+ if (clientAssistantToolCalls) {
2083
+ const currentMessages = this.state.messages;
2084
+ for (let i = currentMessages.length - 1; i >= 0; i--) {
2085
+ if (currentMessages[i].role === "assistant") {
2086
+ this.state.updateMessageById(
2087
+ currentMessages[i].id,
2088
+ (m) => ({
2089
+ ...m,
2090
+ toolCalls: clientAssistantToolCalls
2091
+ })
2092
+ );
2093
+ break;
2094
+ }
2095
+ }
2096
+ }
2097
+ if (messagesToInsert.length > 0) {
2098
+ const currentMessages = this.state.messages;
2099
+ let insertIdx = currentMessages.length;
2100
+ for (let i = currentMessages.length - 1; i >= 0; i--) {
2101
+ if (currentMessages[i].role === "assistant") {
2102
+ insertIdx = i;
2103
+ break;
2104
+ }
2105
+ }
2106
+ this.state.setMessages([
2107
+ ...currentMessages.slice(0, insertIdx),
2108
+ ...messagesToInsert,
2109
+ ...currentMessages.slice(insertIdx)
2110
+ ]);
2111
+ }
2112
+ }
2113
+ if (!toolCallsEmitted && pendingClientToolCalls?.length) {
2114
+ toolCallsEmitted = true;
2115
+ this.debug("emit toolCalls (post-message:end path)", {
2116
+ count: pendingClientToolCalls.length,
2117
+ names: pendingClientToolCalls.map(
2118
+ (tc) => tc.function?.name ?? tc.name
2119
+ )
2120
+ });
2121
+ this.emit("toolCalls", { toolCalls: pendingClientToolCalls });
2122
+ } else {
2123
+ this.debug("skip emit toolCalls (post-message:end path)", {
2124
+ toolCallsEmitted,
2125
+ hasPending: !!pendingClientToolCalls?.length
2126
+ });
2127
+ }
2128
+ continue;
2129
+ }
1018
2130
  this.debug("warning", "streamState is null, skipping chunk");
1019
2131
  continue;
1020
2132
  }
@@ -1050,22 +2162,38 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt,
1050
2162
  const updatedMessage = streamStateToMessage(this.streamState);
1051
2163
  this.state.updateMessageById(
1052
2164
  this.streamState.messageId,
1053
- () => updatedMessage
2165
+ // Preserve parentId/childrenIds from the existing placeholder so the
2166
+ // branch tree structure (activeChildMap) is not corrupted when
2167
+ // setCurrentLeaf() walks up the chain later.
2168
+ (existing) => ({
2169
+ ...updatedMessage,
2170
+ ...existing.parentId !== void 0 ? { parentId: existing.parentId } : {},
2171
+ ...existing.childrenIds !== void 0 ? { childrenIds: existing.childrenIds } : {}
2172
+ })
1054
2173
  );
1055
2174
  if (chunk.type === "message:delta") {
1056
2175
  this.callbacks.onMessageDelta?.(assistantMessage.id, chunk.content);
1057
2176
  }
1058
- if (requiresToolExecution(chunk) && !toolCallsEmitted) {
1059
- toolCallsEmitted = true;
1060
- this.debug("toolCalls", { toolCalls: updatedMessage.toolCalls });
1061
- this.emit("toolCalls", { toolCalls: updatedMessage.toolCalls });
1062
- }
1063
2177
  if (isStreamDone(chunk)) {
1064
- this.debug("streamDone", { chunk });
2178
+ this.debug("streamDone", {
2179
+ chunkType: chunk.type,
2180
+ requiresAction: chunk.requiresAction,
2181
+ doneMessagesCount: chunk.messages?.length ?? 0,
2182
+ streamToolCallsCount: this.streamState?.toolCalls?.length ?? 0,
2183
+ toolCallsEmitted,
2184
+ chunkCount
2185
+ });
1065
2186
  if (chunk.type === "done" && chunk.messages?.length) {
1066
2187
  this.debug("processDoneMessages", {
1067
- count: chunk.messages.length
2188
+ count: chunk.messages.length,
2189
+ roles: chunk.messages.map(
2190
+ (m) => `${m.role}${m.tool_calls?.length ? `[${m.tool_calls.length}tc]` : ""}`
2191
+ )
1068
2192
  });
2193
+ const currentStreamToolCallIds = new Set(
2194
+ this.streamState?.toolCalls?.map((toolCall) => toolCall.id) ?? []
2195
+ );
2196
+ const messagesToInsert = [];
1069
2197
  const toolCallsHidden = {};
1070
2198
  if (this.streamState?.toolResults) {
1071
2199
  for (const [id, result] of this.streamState.toolResults) {
@@ -1075,7 +2203,12 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt,
1075
2203
  }
1076
2204
  }
1077
2205
  for (const msg of chunk.messages) {
1078
- if (msg.role === "assistant") {
2206
+ if (msg.role === "assistant" && !msg.tool_calls?.length) {
2207
+ continue;
2208
+ }
2209
+ if (msg.role === "assistant" && msg.tool_calls?.length && msg.tool_calls.every(
2210
+ (toolCall) => currentStreamToolCallIds.has(toolCall.id)
2211
+ )) {
1079
2212
  continue;
1080
2213
  }
1081
2214
  let metadata;
@@ -1091,7 +2224,60 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt,
1091
2224
  createdAt: /* @__PURE__ */ new Date(),
1092
2225
  metadata
1093
2226
  };
1094
- this.state.pushMessage(message);
2227
+ messagesToInsert.push(message);
2228
+ }
2229
+ if (messagesToInsert.length > 0) {
2230
+ const currentMessages = this.state.messages;
2231
+ const currentStreamIndex = this.streamState ? currentMessages.findIndex(
2232
+ (message) => message.id === this.streamState.messageId
2233
+ ) : -1;
2234
+ if (currentStreamIndex === -1) {
2235
+ this.state.setMessages([...currentMessages, ...messagesToInsert]);
2236
+ } else {
2237
+ this.state.setMessages([
2238
+ ...currentMessages.slice(0, currentStreamIndex),
2239
+ ...messagesToInsert,
2240
+ ...currentMessages.slice(currentStreamIndex)
2241
+ ]);
2242
+ }
2243
+ }
2244
+ this.debug("requiresAction check", {
2245
+ requiresAction: chunk.requiresAction,
2246
+ toolCallsEmitted,
2247
+ updatedMessageToolCallsCount: updatedMessage.toolCalls?.length ?? 0,
2248
+ messagesToInsertCount: messagesToInsert.length
2249
+ });
2250
+ if (chunk.requiresAction && !toolCallsEmitted) {
2251
+ let clientToolCalls = updatedMessage.toolCalls;
2252
+ if (!clientToolCalls?.length && messagesToInsert.length > 0) {
2253
+ for (let i = messagesToInsert.length - 1; i >= 0; i--) {
2254
+ const m = messagesToInsert[i];
2255
+ if (m.role === "assistant" && m.toolCalls?.length) {
2256
+ clientToolCalls = m.toolCalls;
2257
+ this.debug("clientToolCalls from messagesToInsert", {
2258
+ index: i,
2259
+ count: clientToolCalls?.length
2260
+ });
2261
+ break;
2262
+ }
2263
+ }
2264
+ }
2265
+ if (clientToolCalls?.length) {
2266
+ toolCallsEmitted = true;
2267
+ this.debug("emit toolCalls (normal done path)", {
2268
+ count: clientToolCalls.length,
2269
+ names: clientToolCalls.map((tc) => tc.function?.name ?? tc.name)
2270
+ });
2271
+ this.emit("toolCalls", { toolCalls: clientToolCalls });
2272
+ } else {
2273
+ this.debug("requiresAction=true but no clientToolCalls found", {
2274
+ updatedMessageToolCalls: updatedMessage.toolCalls,
2275
+ messagesToInsert: messagesToInsert.map((m) => ({
2276
+ role: m.role,
2277
+ hasToolCalls: !!m.toolCalls?.length
2278
+ }))
2279
+ });
2280
+ }
1095
2281
  }
1096
2282
  }
1097
2283
  break;
@@ -1116,15 +2302,22 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt,
1116
2302
  toolCallsHidden
1117
2303
  };
1118
2304
  }
1119
- this.state.updateMessageById(
1120
- this.streamState.messageId,
1121
- () => finalMessage
1122
- );
2305
+ this.state.updateMessageById(this.streamState.messageId, (existing) => ({
2306
+ ...finalMessage,
2307
+ ...existing.parentId !== void 0 ? { parentId: existing.parentId } : {},
2308
+ ...existing.childrenIds !== void 0 ? { childrenIds: existing.childrenIds } : {}
2309
+ }));
1123
2310
  if (!finalMessage.content && (!finalMessage.toolCalls || finalMessage.toolCalls.length === 0)) {
1124
2311
  this.debug("warning", "Empty response - no content and no tool calls");
1125
2312
  }
1126
2313
  }
1127
- this.callbacks.onMessagesChange?.(this.state.messages);
2314
+ this.callbacks.onMessagesChange?.(this._allMessages());
2315
+ this.debugGroupEnd();
2316
+ this.debug("stream end", {
2317
+ toolCallsEmitted,
2318
+ totalChunks: chunkCount,
2319
+ messagesInState: this.state.messages.length
2320
+ });
1128
2321
  if (!toolCallsEmitted) {
1129
2322
  this.state.status = "ready";
1130
2323
  this.callbacks.onStatusChange?.("ready");
@@ -1145,6 +2338,7 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt,
1145
2338
  }
1146
2339
  }
1147
2340
  }
2341
+ let currentParentId = this.state.messages.length > 0 ? this.state.messages[this.state.messages.length - 1].id : void 0;
1148
2342
  for (const msg of response.messages ?? []) {
1149
2343
  let metadata;
1150
2344
  if (msg.role === "assistant" && msg.tool_calls && toolCallHiddenMap.size > 0) {
@@ -1167,11 +2361,15 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt,
1167
2361
  // CRITICAL: Preserve toolCallId for tool messages (fixes Anthropic API errors)
1168
2362
  toolCallId: msg.tool_call_id,
1169
2363
  createdAt: /* @__PURE__ */ new Date(),
1170
- metadata
2364
+ metadata,
2365
+ // Preserve branch tree structure: each message is a child of the
2366
+ // current leaf so the tree is not corrupted for non-streaming mode.
2367
+ ...currentParentId !== void 0 ? { parentId: currentParentId } : {}
1171
2368
  };
1172
2369
  this.state.pushMessage(message);
2370
+ currentParentId = message.id;
1173
2371
  }
1174
- this.callbacks.onMessagesChange?.(this.state.messages);
2372
+ this.callbacks.onMessagesChange?.(this._allMessages());
1175
2373
  const hasToolCalls = response.requiresAction && this.state.messages.length > 0 && this.state.messages[this.state.messages.length - 1]?.toolCalls?.length;
1176
2374
  if (hasToolCalls) {
1177
2375
  const lastMessage = this.state.messages[this.state.messages.length - 1];
@@ -1194,14 +2392,25 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt,
1194
2392
  this.callbacks.onStatusChange?.("error");
1195
2393
  this.emit("error", { error });
1196
2394
  }
1197
- /**
1198
- * Debug logging
1199
- */
2395
+ get log() {
2396
+ if (!this._log) {
2397
+ this._log = createLogger("streaming", () => this.config.debug ?? false);
2398
+ }
2399
+ return this._log;
2400
+ }
1200
2401
  debug(action, data) {
1201
- if (this.config.debug) {
1202
- console.log(`[AbstractChat] ${action}`, data);
2402
+ this.log(action, data);
2403
+ }
2404
+ debugGroup(label, collapsed = true) {
2405
+ if (collapsed) {
2406
+ this.log.groupCollapsed(label);
2407
+ } else {
2408
+ this.log.group(label);
1203
2409
  }
1204
2410
  }
2411
+ debugGroupEnd() {
2412
+ this.log.groupEnd();
2413
+ }
1205
2414
  /**
1206
2415
  * Type guard for async iterable
1207
2416
  */
@@ -1412,19 +2621,18 @@ var AbstractAgentLoop = class {
1412
2621
  this._isCancelled = false;
1413
2622
  this._isProcessing = true;
1414
2623
  this.setIteration(this._iteration + 1);
1415
- const results = [];
1416
- for (const toolCall of toolCalls) {
1417
- if (this._isCancelled || this.abortController.signal.aborted) {
1418
- results.push({
1419
- toolCallId: toolCall.id,
1420
- success: false,
1421
- error: "Tool execution cancelled"
1422
- });
1423
- continue;
1424
- }
1425
- const result = await this.executeSingleTool(toolCall);
1426
- results.push(result);
1427
- }
2624
+ const results = await Promise.all(
2625
+ toolCalls.map((toolCall) => {
2626
+ if (this._isCancelled || this.abortController.signal.aborted) {
2627
+ return Promise.resolve({
2628
+ toolCallId: toolCall.id,
2629
+ success: false,
2630
+ error: "Tool execution cancelled"
2631
+ });
2632
+ }
2633
+ return this.executeSingleTool(toolCall);
2634
+ })
2635
+ );
1428
2636
  this._isProcessing = false;
1429
2637
  return results;
1430
2638
  }
@@ -1742,6 +2950,7 @@ var ChatWithTools = class {
1742
2950
  streaming: config.streaming,
1743
2951
  headers: config.headers,
1744
2952
  body: config.body,
2953
+ optimization: config.optimization,
1745
2954
  threadId: config.threadId,
1746
2955
  debug: config.debug,
1747
2956
  initialMessages: config.initialMessages,
@@ -1756,6 +2965,7 @@ var ChatWithTools = class {
1756
2965
  onMessageFinish: callbacks.onMessageFinish,
1757
2966
  onToolCalls: callbacks.onToolCalls,
1758
2967
  onFinish: callbacks.onFinish,
2968
+ onContextUsageChange: callbacks.onContextUsageChange,
1759
2969
  // Server-side tool callbacks - track in agentLoop for UI display
1760
2970
  // IMPORTANT: Only track tools that are NOT registered client-side
1761
2971
  // Client-side tools are tracked via executeToolCalls() path
@@ -1923,14 +3133,17 @@ var ChatWithTools = class {
1923
3133
  /**
1924
3134
  * Send a message
1925
3135
  * Returns false if a request is already in progress
3136
+ *
3137
+ * @param options.editMessageId - Edit flow: new message branches from the
3138
+ * same parent as this message ID
1926
3139
  */
1927
- async sendMessage(content, attachments) {
3140
+ async sendMessage(content, attachments, options) {
1928
3141
  if (this.isLoading) {
1929
3142
  this.debug("sendMessage blocked - request already in progress");
1930
3143
  return false;
1931
3144
  }
1932
3145
  this.agentLoop.resetIterations();
1933
- return await this.chat.sendMessage(content, attachments);
3146
+ return await this.chat.sendMessage(content, attachments, options);
1934
3147
  }
1935
3148
  /**
1936
3149
  * Stop generation and cancel any running tools
@@ -1965,6 +3178,25 @@ var ChatWithTools = class {
1965
3178
  setTools(tools) {
1966
3179
  this.chat.setTools(tools);
1967
3180
  }
3181
+ /**
3182
+ * Update prompt/tool optimization controls.
3183
+ */
3184
+ setOptimizationConfig(config) {
3185
+ this.config.optimization = config;
3186
+ this.chat.setOptimizationConfig(config);
3187
+ }
3188
+ /**
3189
+ * Set the active tool profile used for request-time tool selection.
3190
+ */
3191
+ setToolProfile(profile) {
3192
+ this.chat.setToolProfile(profile);
3193
+ }
3194
+ /**
3195
+ * Get the most recent prompt context usage snapshot.
3196
+ */
3197
+ getContextUsage() {
3198
+ return this.chat.getContextUsage();
3199
+ }
1968
3200
  /**
1969
3201
  * Set dynamic context (from useAIContext hook)
1970
3202
  */
@@ -1998,6 +3230,15 @@ var ChatWithTools = class {
1998
3230
  setBody(body) {
1999
3231
  this.chat.setBody(body);
2000
3232
  }
3233
+ setRequestMessageTransform(fn) {
3234
+ this.chat.setRequestMessageTransform(fn);
3235
+ }
3236
+ /**
3237
+ * Set inline skills (forwarded to underlying chat instance)
3238
+ */
3239
+ setInlineSkills(skills) {
3240
+ this.chat.setInlineSkills(skills);
3241
+ }
2001
3242
  // ============================================
2002
3243
  // Tool Registration
2003
3244
  // ============================================
@@ -2074,16 +3315,307 @@ var ChatWithTools = class {
2074
3315
  // Private
2075
3316
  // ============================================
2076
3317
  debug(message, ...args) {
2077
- if (this.config.debug) {
2078
- console.log(`[ChatWithTools] ${message}`, ...args);
3318
+ createLogger("tools", () => this.config.debug ?? false)(
3319
+ message,
3320
+ args.length === 1 ? args[0] : args.length > 1 ? args : void 0
3321
+ );
3322
+ }
3323
+ };
3324
+
3325
+ // src/chat/branching/MessageTree.ts
3326
+ var _MessageTree = class _MessageTree {
3327
+ constructor(messages) {
3328
+ /** All messages by ID */
3329
+ this.nodeMap = /* @__PURE__ */ new Map();
3330
+ /** parentKey → ordered list of child IDs (insertion order = oldest-first) */
3331
+ this.childrenOf = /* @__PURE__ */ new Map();
3332
+ /** parentKey → currently-active child ID */
3333
+ this.activeChildMap = /* @__PURE__ */ new Map();
3334
+ /** Current leaf message ID (tip of the active path) */
3335
+ this._currentLeafId = null;
3336
+ /** Cached visible messages — invalidated on every mutation */
3337
+ this._visibleCache = null;
3338
+ if (messages?.length) {
3339
+ this._buildFromMessages(messages);
3340
+ }
3341
+ }
3342
+ // ============================================
3343
+ // Static Migration Helpers
3344
+ // ============================================
3345
+ /**
3346
+ * Convert a legacy flat array (no parentId) to a tree-linked array.
3347
+ *
3348
+ * Rules:
3349
+ * - Tool messages get parentId = the owning assistant message's id
3350
+ * (matched via toolCallId → toolCall.id).
3351
+ * - All other messages get parentId of the previous non-tool message
3352
+ * (or null for the first message).
3353
+ *
3354
+ * Returns a new array with parentId/childrenIds filled in.
3355
+ * Does NOT mutate the original messages.
3356
+ */
3357
+ static fromFlatArray(messages) {
3358
+ if (messages.length === 0) return messages;
3359
+ const alreadyLinked = messages.some((m) => m.parentId !== void 0);
3360
+ if (alreadyLinked) return messages;
3361
+ const result = [];
3362
+ let prevNonToolId = null;
3363
+ const assistantById = /* @__PURE__ */ new Map();
3364
+ for (const msg of messages) {
3365
+ if (msg.role === "assistant") {
3366
+ assistantById.set(msg.id, msg);
3367
+ }
3368
+ }
3369
+ for (const msg of messages) {
3370
+ if (msg.role === "tool" && msg.toolCallId) {
3371
+ let ownerAssistantId = null;
3372
+ for (const [, assistant] of assistantById) {
3373
+ if (assistant.toolCalls?.some((tc) => tc.id === msg.toolCallId)) {
3374
+ ownerAssistantId = assistant.id;
3375
+ break;
3376
+ }
3377
+ }
3378
+ result.push({
3379
+ ...msg,
3380
+ parentId: ownerAssistantId ?? prevNonToolId,
3381
+ childrenIds: []
3382
+ });
3383
+ } else {
3384
+ result.push({
3385
+ ...msg,
3386
+ parentId: prevNonToolId,
3387
+ childrenIds: []
3388
+ });
3389
+ prevNonToolId = msg.id;
3390
+ }
3391
+ }
3392
+ const childrenMap = /* @__PURE__ */ new Map();
3393
+ for (const msg of result) {
3394
+ const parentKey = msg.parentId == null ? _MessageTree.ROOT_KEY : msg.parentId;
3395
+ if (!childrenMap.has(parentKey)) {
3396
+ childrenMap.set(parentKey, []);
3397
+ }
3398
+ childrenMap.get(parentKey).push(msg.id);
3399
+ }
3400
+ return result.map((msg) => ({
3401
+ ...msg,
3402
+ childrenIds: childrenMap.get(msg.id) ?? []
3403
+ }));
3404
+ }
3405
+ // ============================================
3406
+ // Core Queries
3407
+ // ============================================
3408
+ /**
3409
+ * Returns the visible path (root → current leaf) — what the UI renders
3410
+ * and what gets sent to the API.
3411
+ *
3412
+ * Backward-compat: if NO message has parentId set (all undefined),
3413
+ * falls back to insertion order (legacy linear mode).
3414
+ */
3415
+ getVisibleMessages() {
3416
+ if (this._visibleCache !== null) return this._visibleCache;
3417
+ if (this.nodeMap.size === 0) {
3418
+ this._visibleCache = [];
3419
+ return this._visibleCache;
3420
+ }
3421
+ const hasTreeStructure = Array.from(this.nodeMap.values()).some(
3422
+ (m) => m.parentId !== void 0
3423
+ );
3424
+ this._visibleCache = hasTreeStructure ? this._getActivePath().map((id) => this.nodeMap.get(id)) : Array.from(this.nodeMap.values());
3425
+ return this._visibleCache;
3426
+ }
3427
+ _invalidateCache() {
3428
+ this._visibleCache = null;
3429
+ }
3430
+ /**
3431
+ * Returns ALL messages across every branch (for persistence / ThreadManager).
3432
+ */
3433
+ getAllMessages() {
3434
+ return Array.from(this.nodeMap.values());
3435
+ }
3436
+ /**
3437
+ * Branch navigation info for the UI navigator.
3438
+ * Returns null if the message has no siblings (only child).
3439
+ */
3440
+ getBranchInfo(messageId) {
3441
+ const msg = this.nodeMap.get(messageId);
3442
+ if (!msg) return null;
3443
+ const parentKey = this._parentKey(msg.parentId);
3444
+ const siblings = this.childrenOf.get(parentKey) ?? [];
3445
+ if (siblings.length <= 1) return null;
3446
+ const siblingIndex = siblings.indexOf(messageId);
3447
+ return {
3448
+ siblingIndex,
3449
+ totalSiblings: siblings.length,
3450
+ siblingIds: [...siblings],
3451
+ hasPrevious: siblingIndex > 0,
3452
+ hasNext: siblingIndex < siblings.length - 1
3453
+ };
3454
+ }
3455
+ get currentLeafId() {
3456
+ return this._currentLeafId;
3457
+ }
3458
+ get hasBranches() {
3459
+ for (const children of this.childrenOf.values()) {
3460
+ if (children.length > 1) return true;
3461
+ }
3462
+ return false;
3463
+ }
3464
+ // ============================================
3465
+ // Mutations
3466
+ // ============================================
3467
+ /**
3468
+ * Insert a new message.
3469
+ * - Updates childrenOf and nodeMap.
3470
+ * - New branch becomes active (activeChildMap updated).
3471
+ * - Updates current leaf.
3472
+ */
3473
+ addMessage(message) {
3474
+ this.nodeMap.set(message.id, message);
3475
+ const parentKey = this._parentKey(message.parentId);
3476
+ if (!this.childrenOf.has(parentKey)) {
3477
+ this.childrenOf.set(parentKey, []);
3478
+ }
3479
+ const siblings = this.childrenOf.get(parentKey);
3480
+ if (!siblings.includes(message.id)) {
3481
+ siblings.push(message.id);
3482
+ }
3483
+ this.activeChildMap.set(parentKey, message.id);
3484
+ this._currentLeafId = this._walkToLeaf(message.id);
3485
+ this._invalidateCache();
3486
+ return message;
3487
+ }
3488
+ /**
3489
+ * Navigate: make messageId the active child at its parent fork,
3490
+ * then walk to its leaf and update currentLeafId.
3491
+ */
3492
+ switchBranch(messageId) {
3493
+ const msg = this.nodeMap.get(messageId);
3494
+ if (!msg) return;
3495
+ const parentKey = this._parentKey(msg.parentId);
3496
+ this.activeChildMap.set(parentKey, messageId);
3497
+ this._currentLeafId = this._walkToLeaf(messageId);
3498
+ this._invalidateCache();
3499
+ }
3500
+ /**
3501
+ * Update message content in-place (streaming updates).
3502
+ * No tree structure change.
3503
+ */
3504
+ updateMessage(id, updater) {
3505
+ const existing = this.nodeMap.get(id);
3506
+ if (!existing) return false;
3507
+ this.nodeMap.set(id, updater(existing));
3508
+ this._invalidateCache();
3509
+ return true;
3510
+ }
3511
+ /**
3512
+ * Set current leaf explicitly.
3513
+ * Used by regenerate() to rewind the active path before pushing a new message.
3514
+ */
3515
+ setCurrentLeaf(leafId) {
3516
+ this._currentLeafId = leafId;
3517
+ if (leafId === null) return;
3518
+ const msg = this.nodeMap.get(leafId);
3519
+ if (!msg) return;
3520
+ let current = msg;
3521
+ while (current) {
3522
+ const parentKey = this._parentKey(current.parentId);
3523
+ this.activeChildMap.set(parentKey, current.id);
3524
+ if (current.parentId == null || current.parentId === void 0) break;
3525
+ current = this.nodeMap.get(current.parentId);
3526
+ }
3527
+ this._invalidateCache();
3528
+ }
3529
+ /**
3530
+ * Rebuild entire tree from a message array.
3531
+ * Used by setMessages().
3532
+ */
3533
+ reset(messages) {
3534
+ this.nodeMap.clear();
3535
+ this.childrenOf.clear();
3536
+ this.activeChildMap.clear();
3537
+ this._currentLeafId = null;
3538
+ this._invalidateCache();
3539
+ if (messages.length > 0) {
3540
+ this._buildFromMessages(messages);
3541
+ }
3542
+ }
3543
+ // ============================================
3544
+ // Private Helpers
3545
+ // ============================================
3546
+ _buildFromMessages(messages) {
3547
+ const linked = messages.some((m) => m.parentId !== void 0) ? messages : _MessageTree.fromFlatArray(messages);
3548
+ for (const msg of linked) {
3549
+ this.nodeMap.set(msg.id, msg);
3550
+ const parentKey = this._parentKey(msg.parentId);
3551
+ if (!this.childrenOf.has(parentKey)) {
3552
+ this.childrenOf.set(parentKey, []);
3553
+ }
3554
+ const siblings = this.childrenOf.get(parentKey);
3555
+ if (!siblings.includes(msg.id)) {
3556
+ siblings.push(msg.id);
3557
+ }
3558
+ }
3559
+ for (const [parentKey, children] of this.childrenOf) {
3560
+ if (children.length > 0) {
3561
+ this.activeChildMap.set(parentKey, children[children.length - 1]);
3562
+ }
2079
3563
  }
3564
+ const path = this._getActivePath();
3565
+ this._currentLeafId = path.length > 0 ? path[path.length - 1] : null;
3566
+ }
3567
+ _parentKey(parentId) {
3568
+ if (parentId == null || parentId === void 0) {
3569
+ return _MessageTree.ROOT_KEY;
3570
+ }
3571
+ return parentId;
3572
+ }
3573
+ /**
3574
+ * Walk forward from a message along active children to find the leaf.
3575
+ */
3576
+ _walkToLeaf(fromId) {
3577
+ let current = fromId;
3578
+ while (true) {
3579
+ const children = this.childrenOf.get(current);
3580
+ if (!children || children.length === 0) break;
3581
+ const activeChild = this.activeChildMap.get(current);
3582
+ if (!activeChild) break;
3583
+ if (!this.nodeMap.has(activeChild)) break;
3584
+ current = activeChild;
3585
+ }
3586
+ return current;
3587
+ }
3588
+ /**
3589
+ * Walk the active path from root to the current leaf.
3590
+ */
3591
+ _getActivePath() {
3592
+ const path = [];
3593
+ const visited = /* @__PURE__ */ new Set();
3594
+ const rootChildren = this.childrenOf.get(_MessageTree.ROOT_KEY) ?? [];
3595
+ if (rootChildren.length === 0) return path;
3596
+ let activeId = this.activeChildMap.get(_MessageTree.ROOT_KEY);
3597
+ if (!activeId) {
3598
+ activeId = rootChildren[rootChildren.length - 1];
3599
+ }
3600
+ let current = activeId;
3601
+ while (current && !visited.has(current)) {
3602
+ if (!this.nodeMap.has(current)) break;
3603
+ visited.add(current);
3604
+ path.push(current);
3605
+ const activeChild = this.activeChildMap.get(current);
3606
+ if (!activeChild || !this.nodeMap.has(activeChild)) break;
3607
+ current = activeChild;
3608
+ }
3609
+ return path;
2080
3610
  }
2081
3611
  };
3612
+ /** Sentinel key used for root-level messages (parentId === null) */
3613
+ _MessageTree.ROOT_KEY = "__root__";
3614
+ var MessageTree = _MessageTree;
2082
3615
 
2083
3616
  // src/react/internal/ReactChatState.ts
2084
3617
  var ReactChatState = class {
2085
3618
  constructor(initialMessages) {
2086
- this._messages = [];
2087
3619
  this._status = "ready";
2088
3620
  this._error = void 0;
2089
3621
  // Callbacks for React subscriptions (useSyncExternalStore)
@@ -2109,15 +3641,19 @@ var ReactChatState = class {
2109
3641
  this.subscribers.delete(callback);
2110
3642
  };
2111
3643
  };
2112
- if (initialMessages) {
2113
- this._messages = initialMessages;
2114
- }
3644
+ this.tree = new MessageTree(initialMessages);
2115
3645
  }
2116
3646
  // ============================================
2117
- // Getters
3647
+ // Getters — visible path only
2118
3648
  // ============================================
3649
+ /**
3650
+ * Returns the VISIBLE PATH (active branch) — what the UI renders
3651
+ * and what gets sent to the API.
3652
+ *
3653
+ * For all messages across all branches, use getAllMessages().
3654
+ */
2119
3655
  get messages() {
2120
- return this._messages;
3656
+ return this.tree.getVisibleMessages();
2121
3657
  }
2122
3658
  get status() {
2123
3659
  return this._status;
@@ -2129,7 +3665,7 @@ var ReactChatState = class {
2129
3665
  // Setters (trigger reactivity)
2130
3666
  // ============================================
2131
3667
  set messages(value) {
2132
- this._messages = value;
3668
+ this.tree.reset(value);
2133
3669
  this.notify();
2134
3670
  }
2135
3671
  set status(value) {
@@ -2144,65 +3680,104 @@ var ReactChatState = class {
2144
3680
  // Mutations
2145
3681
  // ============================================
2146
3682
  pushMessage(message) {
2147
- this._messages = [...this._messages, message];
3683
+ this.tree.addMessage(message);
2148
3684
  this.notify();
2149
3685
  }
2150
3686
  popMessage() {
2151
- this._messages = this._messages.slice(0, -1);
3687
+ const leafId = this.tree.currentLeafId;
3688
+ if (!leafId) return;
3689
+ const allMessages = this.tree.getAllMessages().filter((m) => m.id !== leafId);
3690
+ const leaf = this.tree.getAllMessages().find((m) => m.id === leafId);
3691
+ const newLeafId = leaf && leaf.parentId !== void 0 && leaf.parentId !== null ? leaf.parentId : null;
3692
+ this.tree.reset(allMessages);
3693
+ if (newLeafId) {
3694
+ this.tree.setCurrentLeaf(newLeafId);
3695
+ }
2152
3696
  this.notify();
2153
3697
  }
2154
3698
  replaceMessage(index, message) {
2155
- this._messages = this._messages.map((m, i) => i === index ? message : m);
3699
+ const visible = this.tree.getVisibleMessages();
3700
+ const target = visible[index];
3701
+ if (!target) return;
3702
+ this.tree.updateMessage(target.id, () => message);
2156
3703
  this.notify();
2157
3704
  }
2158
3705
  updateLastMessage(updater) {
2159
- if (this._messages.length === 0) return;
2160
- const lastIndex = this._messages.length - 1;
2161
- const lastMessage = this._messages[lastIndex];
2162
- this._messages = [
2163
- ...this._messages.slice(0, lastIndex),
2164
- updater(lastMessage)
2165
- ];
3706
+ const leafId = this.tree.currentLeafId;
3707
+ if (!leafId) return;
3708
+ this.tree.updateMessage(leafId, updater);
2166
3709
  this.notify();
2167
3710
  }
2168
3711
  updateMessageById(id, updater) {
2169
- const index = this._messages.findIndex((m) => m.id === id);
2170
- if (index === -1) return false;
2171
- this._messages = this._messages.map(
2172
- (m, i) => i === index ? updater(m) : m
2173
- );
2174
- this.notify();
2175
- return true;
3712
+ const updated = this.tree.updateMessage(id, updater);
3713
+ if (updated) this.notify();
3714
+ return updated;
2176
3715
  }
2177
3716
  setMessages(messages) {
2178
- this._messages = messages;
3717
+ this.tree.reset(messages);
2179
3718
  this.notify();
2180
3719
  }
2181
3720
  clearMessages() {
2182
- this._messages = [];
3721
+ this.tree.reset([]);
2183
3722
  this.notify();
2184
3723
  }
2185
3724
  // ============================================
2186
- // Private Methods
3725
+ // Branching API
2187
3726
  // ============================================
2188
- notify() {
2189
- this.subscribers.forEach((cb) => cb());
3727
+ /**
3728
+ * Returns ALL messages across all branches.
3729
+ * Use this for persistence (ThreadManager save).
3730
+ */
3731
+ getAllMessages() {
3732
+ return this.tree.getAllMessages();
2190
3733
  }
2191
3734
  /**
2192
- * Cleanup subscriptions
3735
+ * Get branch navigation info for a message.
3736
+ * Returns null if the message has no siblings.
2193
3737
  */
2194
- dispose() {
2195
- this.subscribers.clear();
3738
+ getBranchInfo(messageId) {
3739
+ return this.tree.getBranchInfo(messageId);
2196
3740
  }
2197
3741
  /**
2198
- * Revive after dispose (for React StrictMode compatibility)
2199
- * Subscribers will be re-added automatically via useSyncExternalStore
3742
+ * Navigate to a sibling branch.
3743
+ * Triggers re-render via notify().
2200
3744
  */
2201
- revive() {
3745
+ switchBranch(messageId) {
3746
+ this.tree.switchBranch(messageId);
3747
+ this.notify();
2202
3748
  }
2203
- };
2204
- function createReactChatState(initialMessages) {
2205
- return new ReactChatState(initialMessages);
3749
+ /**
3750
+ * Set the current leaf (used by regenerate() to rewind active path).
3751
+ * Triggers re-render via notify().
3752
+ */
3753
+ setCurrentLeaf(leafId) {
3754
+ this.tree.setCurrentLeaf(leafId);
3755
+ this.notify();
3756
+ }
3757
+ get hasBranches() {
3758
+ return this.tree.hasBranches;
3759
+ }
3760
+ // ============================================
3761
+ // Private Methods
3762
+ // ============================================
3763
+ notify() {
3764
+ this.subscribers.forEach((cb) => cb());
3765
+ }
3766
+ /**
3767
+ * Cleanup subscriptions
3768
+ */
3769
+ dispose() {
3770
+ this.subscribers.clear();
3771
+ }
3772
+ /**
3773
+ * Revive after dispose (for React StrictMode compatibility)
3774
+ * Subscribers will be re-added automatically via useSyncExternalStore
3775
+ */
3776
+ revive() {
3777
+ }
3778
+ };
3779
+ function createReactChatState(initialMessages) {
3780
+ return new ReactChatState(initialMessages);
2206
3781
  }
2207
3782
 
2208
3783
  // src/react/internal/ReactChatWithTools.ts
@@ -2218,6 +3793,33 @@ var ReactChatWithTools = class extends ChatWithTools {
2218
3793
  };
2219
3794
  this.reactState = reactState;
2220
3795
  }
3796
+ // ============================================
3797
+ // Branching API — pass-throughs to ReactChatState
3798
+ // ============================================
3799
+ /**
3800
+ * Navigate to a sibling branch.
3801
+ */
3802
+ switchBranch(messageId) {
3803
+ this.reactState.switchBranch(messageId);
3804
+ }
3805
+ /**
3806
+ * Get branch navigation info for a message.
3807
+ */
3808
+ getBranchInfo(messageId) {
3809
+ return this.reactState.getBranchInfo(messageId);
3810
+ }
3811
+ /**
3812
+ * Get all messages across all branches (for persistence).
3813
+ */
3814
+ getAllMessages() {
3815
+ return this.reactState.getAllMessages();
3816
+ }
3817
+ /**
3818
+ * Whether any message has siblings (branching has occurred).
3819
+ */
3820
+ get hasBranches() {
3821
+ return this.reactState.hasBranches;
3822
+ }
2221
3823
  /**
2222
3824
  * Dispose and cleanup
2223
3825
  */
@@ -2508,22 +4110,919 @@ function useMCPTools(config) {
2508
4110
  }
2509
4111
  }
2510
4112
  return () => {
2511
- for (const toolName of registeredToolsRef.current) {
2512
- unregisterTool(toolName);
4113
+ for (const toolName of registeredToolsRef.current) {
4114
+ unregisterTool(toolName);
4115
+ }
4116
+ registeredToolsRef.current = [];
4117
+ };
4118
+ }, [
4119
+ autoRegister,
4120
+ mcpClient.isConnected,
4121
+ toolDefinitions,
4122
+ registerTool,
4123
+ unregisterTool
4124
+ ]);
4125
+ return {
4126
+ ...mcpClient,
4127
+ toolDefinitions
4128
+ };
4129
+ }
4130
+ var defaultTokenUsage = {
4131
+ current: 0,
4132
+ max: 128e3,
4133
+ percentage: 0,
4134
+ isApproaching: false
4135
+ };
4136
+ var defaultCompactionState = {
4137
+ rollingSummary: null,
4138
+ lastCompactionAt: null,
4139
+ compactionCount: 0,
4140
+ totalTokensSaved: 0,
4141
+ workingMemory: [],
4142
+ displayMessageCount: 0,
4143
+ llmMessageCount: 0
4144
+ };
4145
+ var defaultMessageHistoryConfig = {
4146
+ strategy: "none",
4147
+ maxContextTokens: 128e3,
4148
+ reserveForResponse: 4096,
4149
+ compactionThreshold: 0.75,
4150
+ recentBuffer: 10,
4151
+ toolResultMaxChars: 1e4,
4152
+ persistSession: false,
4153
+ storageKey: "copilot-session"
4154
+ };
4155
+ var MessageHistoryContext = createContext({
4156
+ config: defaultMessageHistoryConfig,
4157
+ tokenUsage: defaultTokenUsage,
4158
+ compactionState: defaultCompactionState
4159
+ });
4160
+ function useMessageHistoryContext() {
4161
+ return useContext(MessageHistoryContext);
4162
+ }
4163
+
4164
+ // src/react/message-history/message-utils.ts
4165
+ function toDisplayMessage(msg) {
4166
+ return {
4167
+ ...msg,
4168
+ timestamp: msg.createdAt instanceof Date ? msg.createdAt.getTime() : Date.now()
4169
+ };
4170
+ }
4171
+ function toLLMMessage(msg) {
4172
+ if (isCompactionMarker(msg)) {
4173
+ return {
4174
+ role: "system",
4175
+ content: `[Previous conversation summary]
4176
+ ${msg.content}`
4177
+ };
4178
+ }
4179
+ const base = {
4180
+ role: msg.role,
4181
+ content: msg.content
4182
+ };
4183
+ if (msg.toolCalls?.length) {
4184
+ base.tool_calls = msg.toolCalls;
4185
+ }
4186
+ if (msg.toolCallId) {
4187
+ base.tool_call_id = msg.toolCallId;
4188
+ }
4189
+ return base;
4190
+ }
4191
+ function toLLMMessages(messages) {
4192
+ return messages.map(toLLMMessage);
4193
+ }
4194
+ function keepToolPairsAtomic(messages) {
4195
+ if (messages.length === 0) return messages;
4196
+ const firstIdx = messages.findIndex((msg, i) => {
4197
+ if (msg.role !== "assistant" || !msg.toolCalls?.length) return false;
4198
+ const toolCallIds = new Set(msg.toolCalls.map((tc) => tc.id));
4199
+ const resultIds = new Set(
4200
+ messages.slice(i + 1).filter((m) => m.role === "tool" && m.toolCallId).map((m) => m.toolCallId)
4201
+ );
4202
+ return [...toolCallIds].some((id) => !resultIds.has(id));
4203
+ });
4204
+ if (firstIdx === -1) return messages;
4205
+ return messages.slice(firstIdx);
4206
+ }
4207
+ function findSafeWindowStart(messages, desiredStart) {
4208
+ for (let i = desiredStart; i < messages.length; i++) {
4209
+ const msg = messages[i];
4210
+ if (msg.role === "user" || msg.role === "assistant" && !msg.toolCalls?.length) {
4211
+ return i;
4212
+ }
4213
+ }
4214
+ return desiredStart;
4215
+ }
4216
+ function isCompactionMarker(msg) {
4217
+ return msg.role === "system" && msg.type === "compaction-marker";
4218
+ }
4219
+
4220
+ // src/react/message-history/token-counter.ts
4221
+ function estimateMessageTokens2(msg) {
4222
+ let chars = msg.content?.length ?? 0;
4223
+ if (msg.tool_calls?.length) {
4224
+ for (const tc of msg.tool_calls) {
4225
+ chars += JSON.stringify(tc).length;
4226
+ }
4227
+ }
4228
+ return Math.ceil(chars / 3.5) + 4;
4229
+ }
4230
+ function estimateMessagesTokens(messages) {
4231
+ return messages.reduce((sum, msg) => sum + estimateMessageTokens2(msg), 0);
4232
+ }
4233
+ function estimateTokens2(messages, mode = "fast") {
4234
+ if (mode === "off") return 0;
4235
+ return estimateMessagesTokens(messages);
4236
+ }
4237
+
4238
+ // src/react/message-history/strategies/sliding-window.ts
4239
+ function applySlidingWindow(messages, options) {
4240
+ const { tokenBudget, recentBuffer } = options;
4241
+ if (messages.length === 0) return messages;
4242
+ const systemMessages = messages.filter(
4243
+ (m) => m.role === "system" || isCompactionMarker(m)
4244
+ );
4245
+ const conversationMessages = messages.filter(
4246
+ (m) => m.role !== "system" && !isCompactionMarker(m)
4247
+ );
4248
+ const systemTokens = estimateMessagesTokens(toLLMMessages(systemMessages));
4249
+ const remainingBudget = tokenBudget - systemTokens;
4250
+ if (conversationMessages.length === 0) return systemMessages;
4251
+ const recentStart = Math.max(0, conversationMessages.length - recentBuffer);
4252
+ const recent = conversationMessages.slice(recentStart);
4253
+ const older = conversationMessages.slice(0, recentStart);
4254
+ const allTokens = estimateMessagesTokens(toLLMMessages(conversationMessages));
4255
+ if (allTokens <= remainingBudget) {
4256
+ return messages;
4257
+ }
4258
+ const recentTokens = estimateMessagesTokens(toLLMMessages(recent));
4259
+ let available = remainingBudget - recentTokens;
4260
+ const included = [];
4261
+ for (let i = older.length - 1; i >= 0; i--) {
4262
+ const msgTokens = estimateMessagesTokens(toLLMMessages([older[i]]));
4263
+ if (available - msgTokens < 0) break;
4264
+ included.unshift(older[i]);
4265
+ available -= msgTokens;
4266
+ }
4267
+ const combined = [...included, ...recent];
4268
+ const safeStart = findSafeWindowStart(combined, 0);
4269
+ const safeWindow = combined.slice(safeStart);
4270
+ return [...systemMessages, ...safeWindow];
4271
+ }
4272
+ function truncateToolResults(messages, maxChars) {
4273
+ if (maxChars === 0) return messages;
4274
+ return messages.map((msg) => {
4275
+ if (msg.role !== "tool") return msg;
4276
+ if (!msg.content || msg.content.length <= maxChars) return msg;
4277
+ return {
4278
+ ...msg,
4279
+ content: msg.content.slice(0, maxChars) + `
4280
+ [truncated \u2014 original ${msg.content.length} chars, limit ${maxChars}]`
4281
+ };
4282
+ });
4283
+ }
4284
+
4285
+ // src/react/message-history/strategies/selective-prune.ts
4286
+ function applySelectivePrune(displayMessages, recentBuffer, options = {}) {
4287
+ const {
4288
+ toolResultAgeTurns = 3,
4289
+ stripOldReasoning = true,
4290
+ deduplicateSkills = true
4291
+ } = options;
4292
+ const cutoff = Math.max(0, displayMessages.length - recentBuffer);
4293
+ const seenSkillContent = /* @__PURE__ */ new Set();
4294
+ return displayMessages.map((msg, idx) => {
4295
+ const llm = toLLMMessage(msg);
4296
+ const isOld = idx < cutoff;
4297
+ if (deduplicateSkills && msg.role === "system" && llm.content) {
4298
+ const key = llm.content.slice(0, 100);
4299
+ if (seenSkillContent.has(key)) {
4300
+ return { ...llm, content: "[skill instruction \u2014 deduplicated]" };
4301
+ }
4302
+ seenSkillContent.add(key);
4303
+ }
4304
+ if (!isOld) return llm;
4305
+ if (stripOldReasoning && msg.role === "assistant" && msg.thinking) {
4306
+ llm.content = llm.content;
4307
+ }
4308
+ if (msg.role === "tool" && llm.content) {
4309
+ const originalSize = llm.content.length;
4310
+ if (originalSize > 500) {
4311
+ const stub = buildToolResultStub(msg, llm.content);
4312
+ return {
4313
+ role: "tool",
4314
+ tool_call_id: llm.tool_call_id,
4315
+ content: JSON.stringify(stub)
4316
+ };
4317
+ }
4318
+ }
4319
+ return llm;
4320
+ });
4321
+ }
4322
+ function buildToolResultStub(msg, content) {
4323
+ return {
4324
+ type: "compacted-tool-result",
4325
+ toolName: msg.metadata?.toolName ?? "tool",
4326
+ toolCallId: msg.toolCallId ?? "",
4327
+ args: msg.metadata?.toolArgs ?? {},
4328
+ executedAt: msg.timestamp,
4329
+ status: content.includes('"error"') ? "error" : "success",
4330
+ originalSize: content.length,
4331
+ summary: buildSummary(content),
4332
+ extract: content.slice(0, 200)
4333
+ };
4334
+ }
4335
+ function buildSummary(content) {
4336
+ try {
4337
+ const parsed = JSON.parse(content);
4338
+ if (parsed?.message) return String(parsed.message).slice(0, 120);
4339
+ if (parsed?.error) return `Error: ${String(parsed.error).slice(0, 100)}`;
4340
+ if (Array.isArray(parsed)) return `Array result \u2014 ${parsed.length} items`;
4341
+ const keys = Object.keys(parsed).slice(0, 3).join(", ");
4342
+ return `Object result \u2014 keys: ${keys}`;
4343
+ } catch {
4344
+ return content.slice(0, 120);
4345
+ }
4346
+ }
4347
+
4348
+ // src/react/message-history/strategies/summary-buffer.ts
4349
+ function buildSummaryBufferContext(displayMessages, compactionState, options) {
4350
+ const { recentBuffer } = options;
4351
+ const systemMessages = displayMessages.filter(
4352
+ (m) => m.role === "system" || isCompactionMarker(m)
4353
+ );
4354
+ const conversationMessages = displayMessages.filter(
4355
+ (m) => m.role !== "system" && !isCompactionMarker(m)
4356
+ );
4357
+ const recentStart = Math.max(0, conversationMessages.length - recentBuffer);
4358
+ const recentMessages = conversationMessages.slice(recentStart);
4359
+ const result = [];
4360
+ if (compactionState.workingMemory.length > 0) {
4361
+ result.push({
4362
+ role: "system",
4363
+ content: `[Working memory \u2014 always active]
4364
+ ${compactionState.workingMemory.join("\n")}`
4365
+ });
4366
+ }
4367
+ if (compactionState.rollingSummary) {
4368
+ result.push({
4369
+ role: "system",
4370
+ content: `[Previous conversation summary]
4371
+ ${compactionState.rollingSummary}`
4372
+ });
4373
+ }
4374
+ result.push(...toLLMMessages(systemMessages));
4375
+ result.push(...toLLMMessages(recentMessages));
4376
+ return result;
4377
+ }
4378
+ async function runCompaction(displayMessages, compactionState, options) {
4379
+ const { recentBuffer, compactionUrl, summarizer } = options;
4380
+ const conversationMessages = displayMessages.filter(
4381
+ (m) => m.role !== "system" && !isCompactionMarker(m)
4382
+ );
4383
+ const cutoff = Math.max(0, conversationMessages.length - recentBuffer);
4384
+ const toSummarize = conversationMessages.slice(0, cutoff);
4385
+ if (toSummarize.length === 0) {
4386
+ return { llmMessages: toLLMMessages(displayMessages) };
4387
+ }
4388
+ const llmToSummarize = toLLMMessages(toSummarize);
4389
+ const originalTokens = estimateMessagesTokens(llmToSummarize);
4390
+ let newSummary;
4391
+ if (summarizer) {
4392
+ newSummary = await summarizer(llmToSummarize);
4393
+ } else if (compactionUrl) {
4394
+ newSummary = await fetchSummary(compactionUrl, {
4395
+ messages: llmToSummarize,
4396
+ existingSummary: compactionState.rollingSummary,
4397
+ workingMemory: compactionState.workingMemory
4398
+ });
4399
+ } else {
4400
+ newSummary = buildFallbackSummary(
4401
+ llmToSummarize,
4402
+ compactionState.rollingSummary
4403
+ );
4404
+ }
4405
+ const summaryTokens = Math.ceil(newSummary.length / 3.5);
4406
+ const tokensSaved = Math.max(0, originalTokens - summaryTokens);
4407
+ return {
4408
+ llmMessages: buildSummaryBufferContext(
4409
+ displayMessages,
4410
+ { ...compactionState, rollingSummary: newSummary },
4411
+ options
4412
+ ),
4413
+ newSummary,
4414
+ tokensSaved,
4415
+ messagesSummarized: toSummarize.length
4416
+ };
4417
+ }
4418
+ async function fetchSummary(url, body) {
4419
+ const res = await fetch(url, {
4420
+ method: "POST",
4421
+ headers: { "Content-Type": "application/json" },
4422
+ body: JSON.stringify(body)
4423
+ });
4424
+ if (!res.ok) {
4425
+ throw new Error(`Compaction endpoint returned ${res.status}`);
4426
+ }
4427
+ const data = await res.json();
4428
+ if (!data?.summary) {
4429
+ throw new Error("Compaction endpoint did not return { summary: string }");
4430
+ }
4431
+ return data.summary;
4432
+ }
4433
+ function buildFallbackSummary(messages, existingSummary) {
4434
+ const lines = messages.filter((m) => m.role === "user" || m.role === "assistant").map((m) => `${m.role}: ${(m.content ?? "").slice(0, 200)}`).join("\n");
4435
+ return existingSummary ? `${existingSummary}
4436
+
4437
+ [Additional context]
4438
+ ${lines}` : lines;
4439
+ }
4440
+
4441
+ // src/react/message-history/session-persistence.ts
4442
+ var IDB_DB_NAME = "copilot-sdk";
4443
+ var IDB_STORE = "sessions";
4444
+ var IDB_VERSION = 1;
4445
+ function saveCompactionState(storageKey, state) {
4446
+ try {
4447
+ localStorage.setItem(
4448
+ `${storageKey}-state`,
4449
+ JSON.stringify({ ...state, _savedAt: Date.now() })
4450
+ );
4451
+ } catch {
4452
+ }
4453
+ }
4454
+ function loadCompactionState(storageKey) {
4455
+ try {
4456
+ const raw = localStorage.getItem(`${storageKey}-state`);
4457
+ if (!raw) return null;
4458
+ const parsed = JSON.parse(raw);
4459
+ delete parsed._savedAt;
4460
+ return parsed;
4461
+ } catch {
4462
+ return null;
4463
+ }
4464
+ }
4465
+ function clearCompactionState(storageKey) {
4466
+ try {
4467
+ localStorage.removeItem(`${storageKey}-state`);
4468
+ } catch {
4469
+ }
4470
+ }
4471
+ function openDB() {
4472
+ return new Promise((resolve, reject) => {
4473
+ const req = indexedDB.open(IDB_DB_NAME, IDB_VERSION);
4474
+ req.onupgradeneeded = () => {
4475
+ req.result.createObjectStore(IDB_STORE, { keyPath: "sessionId" });
4476
+ };
4477
+ req.onsuccess = () => resolve(req.result);
4478
+ req.onerror = () => reject(req.error);
4479
+ });
4480
+ }
4481
+ async function saveDisplayMessages(storageKey, messages) {
4482
+ try {
4483
+ const db = await openDB();
4484
+ const tx = db.transaction(IDB_STORE, "readwrite");
4485
+ tx.objectStore(IDB_STORE).put({
4486
+ sessionId: storageKey,
4487
+ messages,
4488
+ savedAt: Date.now()
4489
+ });
4490
+ await new Promise((res, rej) => {
4491
+ tx.oncomplete = () => res();
4492
+ tx.onerror = () => rej(tx.error);
4493
+ });
4494
+ db.close();
4495
+ } catch {
4496
+ }
4497
+ }
4498
+ async function loadDisplayMessages(storageKey) {
4499
+ try {
4500
+ const db = await openDB();
4501
+ const tx = db.transaction(IDB_STORE, "readonly");
4502
+ const req = tx.objectStore(IDB_STORE).get(storageKey);
4503
+ const result = await new Promise((res, rej) => {
4504
+ req.onsuccess = () => res(req.result);
4505
+ req.onerror = () => rej(req.error);
4506
+ });
4507
+ db.close();
4508
+ return result?.messages ?? null;
4509
+ } catch {
4510
+ return null;
4511
+ }
4512
+ }
4513
+ async function clearDisplayMessages(storageKey) {
4514
+ try {
4515
+ const db = await openDB();
4516
+ const tx = db.transaction(IDB_STORE, "readwrite");
4517
+ tx.objectStore(IDB_STORE).delete(storageKey);
4518
+ await new Promise((res, rej) => {
4519
+ tx.oncomplete = () => res();
4520
+ tx.onerror = () => rej(tx.error);
4521
+ });
4522
+ db.close();
4523
+ } catch {
4524
+ }
4525
+ }
4526
+ async function clearSession(storageKey) {
4527
+ clearCompactionState(storageKey);
4528
+ await clearDisplayMessages(storageKey);
4529
+ }
4530
+
4531
+ // src/react/message-history/useMessageHistory.ts
4532
+ var DEFAULT_COMPACTION_STATE = {
4533
+ rollingSummary: null,
4534
+ lastCompactionAt: null,
4535
+ compactionCount: 0,
4536
+ totalTokensSaved: 0,
4537
+ workingMemory: [],
4538
+ displayMessageCount: 0,
4539
+ llmMessageCount: 0
4540
+ };
4541
+ function useMessageHistory(options = {}) {
4542
+ const { messages } = useCopilot();
4543
+ const ctx = useMessageHistoryContext();
4544
+ const config = useMemo(
4545
+ () => ({ ...defaultMessageHistoryConfig, ...ctx.config, ...options }),
4546
+ // eslint-disable-next-line react-hooks/exhaustive-deps
4547
+ [
4548
+ ctx.config,
4549
+ options.strategy,
4550
+ options.maxContextTokens,
4551
+ options.recentBuffer,
4552
+ options.compactionThreshold
4553
+ ]
4554
+ );
4555
+ const storageKey = config.storageKey ?? "copilot-session";
4556
+ const strategy = options.skipCompaction ? "none" : config.strategy ?? "none";
4557
+ const [compactionState, setCompactionState] = useState(() => {
4558
+ if (config.persistSession) {
4559
+ return loadCompactionState(storageKey) ?? DEFAULT_COMPACTION_STATE;
4560
+ }
4561
+ return DEFAULT_COMPACTION_STATE;
4562
+ });
4563
+ const displayMessages = useMemo(
4564
+ () => messages.map(toDisplayMessage),
4565
+ [messages]
4566
+ );
4567
+ const restoredRef = useRef(false);
4568
+ useEffect(() => {
4569
+ if (!config.persistSession || restoredRef.current) return;
4570
+ restoredRef.current = true;
4571
+ loadDisplayMessages(storageKey).then((saved) => {
4572
+ if (saved?.length && messages.length === 0) ;
4573
+ });
4574
+ }, [config.persistSession, storageKey, messages.length]);
4575
+ useEffect(() => {
4576
+ if (!config.persistSession || displayMessages.length === 0) return;
4577
+ saveDisplayMessages(storageKey, displayMessages);
4578
+ }, [config.persistSession, storageKey, displayMessages]);
4579
+ const llmMessages = useMemo(() => {
4580
+ const maxTokens = config.maxContextTokens ?? 128e3;
4581
+ const reserve = config.reserveForResponse ?? 4096;
4582
+ const tokenBudget = maxTokens - reserve;
4583
+ const recentBuffer = config.recentBuffer ?? 10;
4584
+ const maxChars = config.toolResultMaxChars ?? 1e4;
4585
+ let result;
4586
+ switch (strategy) {
4587
+ case "sliding-window": {
4588
+ const windowed = applySlidingWindow(displayMessages, {
4589
+ tokenBudget,
4590
+ recentBuffer
4591
+ });
4592
+ result = truncateToolResults(toLLMMessages(windowed), maxChars);
4593
+ break;
4594
+ }
4595
+ case "selective-prune": {
4596
+ result = truncateToolResults(
4597
+ applySelectivePrune(displayMessages, recentBuffer),
4598
+ maxChars
4599
+ );
4600
+ break;
4601
+ }
4602
+ case "summary-buffer": {
4603
+ result = truncateToolResults(
4604
+ buildSummaryBufferContext(displayMessages, compactionState, {
4605
+ recentBuffer,
4606
+ compactionThreshold: config.compactionThreshold ?? 0.75,
4607
+ compactionUrl: config.compactionUrl,
4608
+ summarizer: options.summarizer
4609
+ }),
4610
+ maxChars
4611
+ );
4612
+ break;
4613
+ }
4614
+ default:
4615
+ result = truncateToolResults(toLLMMessages(displayMessages), maxChars);
4616
+ }
4617
+ return result;
4618
+ }, [displayMessages, compactionState, strategy, config, options.summarizer]);
4619
+ const tokenUsage = useMemo(() => {
4620
+ const mode = options.tokenEstimation ?? "fast";
4621
+ const current = estimateTokens2(toLLMMessages(displayMessages), mode);
4622
+ const max = config.maxContextTokens ?? 128e3;
4623
+ const threshold = config.compactionThreshold ?? 0.75;
4624
+ const percentage = current / max;
4625
+ return { current, max, percentage, isApproaching: percentage >= threshold };
4626
+ }, [
4627
+ displayMessages,
4628
+ config.maxContextTokens,
4629
+ config.compactionThreshold,
4630
+ options.tokenEstimation
4631
+ ]);
4632
+ useEffect(() => {
4633
+ if (config.onTokenUsage && tokenUsage.current > 0) {
4634
+ config.onTokenUsage(tokenUsage);
4635
+ }
4636
+ }, [tokenUsage, config.onTokenUsage]);
4637
+ useEffect(() => {
4638
+ if (config.persistSession) {
4639
+ saveCompactionState(storageKey, {
4640
+ ...compactionState,
4641
+ displayMessageCount: displayMessages.length,
4642
+ llmMessageCount: llmMessages.length
4643
+ });
4644
+ }
4645
+ }, [
4646
+ config.persistSession,
4647
+ storageKey,
4648
+ compactionState,
4649
+ displayMessages.length,
4650
+ llmMessages.length
4651
+ ]);
4652
+ const isCompactingRef = useRef(false);
4653
+ const [isCompacting, setIsCompacting] = useState(false);
4654
+ useEffect(() => {
4655
+ if (strategy !== "summary-buffer" || options.skipCompaction || isCompactingRef.current || !tokenUsage.isApproaching)
4656
+ return;
4657
+ isCompactingRef.current = true;
4658
+ setIsCompacting(true);
4659
+ runCompaction(displayMessages, compactionState, {
4660
+ recentBuffer: config.recentBuffer ?? 10,
4661
+ tokenBudget: (config.maxContextTokens ?? 128e3) - (config.reserveForResponse ?? 4096),
4662
+ compactionThreshold: config.compactionThreshold ?? 0.75,
4663
+ compactionUrl: config.compactionUrl,
4664
+ summarizer: options.summarizer
4665
+ }).then((result) => {
4666
+ if (result.newSummary) {
4667
+ const event = {
4668
+ type: "auto",
4669
+ compactionCount: compactionState.compactionCount + 1,
4670
+ messagesSummarized: result.messagesSummarized ?? 0,
4671
+ tokensSaved: result.tokensSaved ?? 0,
4672
+ timestamp: Date.now()
4673
+ };
4674
+ setCompactionState((prev) => ({
4675
+ ...prev,
4676
+ rollingSummary: result.newSummary,
4677
+ lastCompactionAt: Date.now(),
4678
+ compactionCount: prev.compactionCount + 1,
4679
+ totalTokensSaved: prev.totalTokensSaved + (result.tokensSaved ?? 0)
4680
+ }));
4681
+ config.onCompaction?.(event);
4682
+ }
4683
+ }).finally(() => {
4684
+ isCompactingRef.current = false;
4685
+ setIsCompacting(false);
4686
+ });
4687
+ }, [tokenUsage.isApproaching, strategy]);
4688
+ const compactSession = useCallback(
4689
+ async (instructions) => {
4690
+ if (strategy !== "summary-buffer") return;
4691
+ const result = await runCompaction(displayMessages, compactionState, {
4692
+ recentBuffer: config.recentBuffer ?? 10,
4693
+ tokenBudget: (config.maxContextTokens ?? 128e3) - (config.reserveForResponse ?? 4096),
4694
+ compactionThreshold: config.compactionThreshold ?? 0.75,
4695
+ compactionUrl: config.compactionUrl,
4696
+ summarizer: options.summarizer ? (msgs) => options.summarizer(msgs) : instructions ? (msgs) => fetchWithInstructions(config.compactionUrl, msgs, instructions) : void 0
4697
+ });
4698
+ if (result.newSummary) {
4699
+ const event = {
4700
+ type: "manual",
4701
+ compactionCount: compactionState.compactionCount + 1,
4702
+ messagesSummarized: result.messagesSummarized ?? 0,
4703
+ tokensSaved: result.tokensSaved ?? 0,
4704
+ timestamp: Date.now()
4705
+ };
4706
+ setCompactionState((prev) => ({
4707
+ ...prev,
4708
+ rollingSummary: result.newSummary,
4709
+ lastCompactionAt: Date.now(),
4710
+ compactionCount: prev.compactionCount + 1,
4711
+ totalTokensSaved: prev.totalTokensSaved + (result.tokensSaved ?? 0)
4712
+ }));
4713
+ config.onCompaction?.(event);
4714
+ }
4715
+ },
4716
+ [displayMessages, compactionState, config, strategy, options.summarizer]
4717
+ );
4718
+ const addToWorkingMemory = useCallback((fact) => {
4719
+ setCompactionState((prev) => ({
4720
+ ...prev,
4721
+ workingMemory: [...prev.workingMemory, fact]
4722
+ }));
4723
+ }, []);
4724
+ const clearWorkingMemory = useCallback(() => {
4725
+ setCompactionState((prev) => ({ ...prev, workingMemory: [] }));
4726
+ }, []);
4727
+ const resetSession = useCallback(async () => {
4728
+ setCompactionState(DEFAULT_COMPACTION_STATE);
4729
+ if (config.persistSession) {
4730
+ await clearSession(storageKey);
4731
+ }
4732
+ }, [config.persistSession, storageKey]);
4733
+ return {
4734
+ displayMessages,
4735
+ llmMessages,
4736
+ tokenUsage,
4737
+ isCompacting,
4738
+ compactionState: {
4739
+ ...compactionState,
4740
+ displayMessageCount: displayMessages.length,
4741
+ llmMessageCount: llmMessages.length
4742
+ },
4743
+ compactSession,
4744
+ addToWorkingMemory,
4745
+ clearWorkingMemory,
4746
+ resetSession
4747
+ };
4748
+ }
4749
+ async function fetchWithInstructions(url, messages, instructions) {
4750
+ const res = await fetch(url, {
4751
+ method: "POST",
4752
+ headers: { "Content-Type": "application/json" },
4753
+ body: JSON.stringify({ messages, instructions })
4754
+ });
4755
+ const data = await res.json();
4756
+ return data.summary;
4757
+ }
4758
+ var SkillContext = createContext(null);
4759
+ function useSkillContext() {
4760
+ const ctx = useContext(SkillContext);
4761
+ if (!ctx) {
4762
+ throw new Error("useSkillContext must be used within <SkillProvider>");
4763
+ }
4764
+ return ctx;
4765
+ }
4766
+ function useAIContext(item) {
4767
+ const { addContext, removeContext } = useCopilot();
4768
+ const contextIdRef = useRef(null);
4769
+ const serializedData = typeof item.data === "string" ? item.data : JSON.stringify(item.data);
4770
+ useEffect(() => {
4771
+ const formattedValue = typeof item.data === "string" ? item.data : JSON.stringify(item.data, null, 2);
4772
+ const contextString = item.description ? `${item.description}:
4773
+ ${formattedValue}` : `${item.key}:
4774
+ ${formattedValue}`;
4775
+ contextIdRef.current = addContext(contextString, item.parentId);
4776
+ return () => {
4777
+ if (contextIdRef.current) {
4778
+ removeContext(contextIdRef.current);
4779
+ contextIdRef.current = null;
4780
+ }
4781
+ };
4782
+ }, [
4783
+ item.key,
4784
+ serializedData,
4785
+ item.description,
4786
+ item.parentId,
4787
+ addContext,
4788
+ removeContext
4789
+ ]);
4790
+ return contextIdRef.current ?? void 0;
4791
+ }
4792
+ function useAIContexts(items) {
4793
+ const { addContext, removeContext } = useCopilot();
4794
+ const contextIdsRef = useRef([]);
4795
+ const serializedItems = JSON.stringify(
4796
+ items.map((item) => ({
4797
+ key: item.key,
4798
+ data: item.data,
4799
+ description: item.description,
4800
+ parentId: item.parentId
4801
+ }))
4802
+ );
4803
+ useEffect(() => {
4804
+ contextIdsRef.current.forEach((id) => removeContext(id));
4805
+ contextIdsRef.current = [];
4806
+ const parsedItems = JSON.parse(serializedItems);
4807
+ for (const item of parsedItems) {
4808
+ const formattedValue = typeof item.data === "string" ? item.data : JSON.stringify(item.data, null, 2);
4809
+ const contextString = item.description ? `${item.description}:
4810
+ ${formattedValue}` : `${item.key}:
4811
+ ${formattedValue}`;
4812
+ const id = addContext(contextString, item.parentId);
4813
+ contextIdsRef.current.push(id);
4814
+ }
4815
+ return () => {
4816
+ contextIdsRef.current.forEach((id) => removeContext(id));
4817
+ contextIdsRef.current = [];
4818
+ };
4819
+ }, [serializedItems, addContext, removeContext]);
4820
+ }
4821
+ function isZodSchema(value) {
4822
+ if (value === null || typeof value !== "object") return false;
4823
+ const obj = value;
4824
+ return "_def" in obj && typeof obj._def === "object" || "_zod" in obj && typeof obj._zod === "object" || "~standard" in obj;
4825
+ }
4826
+ function useTool(config, dependencies = []) {
4827
+ const { registerTool, unregisterTool } = useCopilot();
4828
+ const configRef = useRef(config);
4829
+ configRef.current = config;
4830
+ const inputSchema = useMemo(() => {
4831
+ if (isZodSchema(config.inputSchema)) {
4832
+ return zodToJsonSchema(config.inputSchema);
4833
+ }
4834
+ return config.inputSchema;
4835
+ }, [config.inputSchema]);
4836
+ useEffect(() => {
4837
+ const tool2 = {
4838
+ name: config.name,
4839
+ description: config.description,
4840
+ location: "client",
4841
+ inputSchema,
4842
+ handler: async (params, context) => {
4843
+ return configRef.current.handler(params, context);
4844
+ },
4845
+ render: config.render,
4846
+ available: config.available ?? true,
4847
+ needsApproval: config.needsApproval,
4848
+ approvalMessage: config.approvalMessage,
4849
+ hidden: config.hidden,
4850
+ deferLoading: config.deferLoading,
4851
+ profiles: config.profiles,
4852
+ searchKeywords: config.searchKeywords,
4853
+ group: config.group,
4854
+ category: config.category,
4855
+ resultConfig: config.resultConfig,
4856
+ title: config.title,
4857
+ executingTitle: config.executingTitle,
4858
+ completedTitle: config.completedTitle,
4859
+ aiResponseMode: config.aiResponseMode,
4860
+ aiContext: config.aiContext
4861
+ };
4862
+ registerTool(tool2);
4863
+ return () => {
4864
+ unregisterTool(config.name);
4865
+ };
4866
+ }, [config.name, inputSchema, ...dependencies]);
4867
+ }
4868
+ function useTools(tools) {
4869
+ const { registerTool, unregisterTool } = useCopilot();
4870
+ const registeredToolsRef = useRef([]);
4871
+ const toolsRef = useRef(tools);
4872
+ toolsRef.current = tools;
4873
+ const toolsKey = Object.keys(tools).sort().join(",");
4874
+ useEffect(() => {
4875
+ const currentTools = toolsRef.current;
4876
+ const toolNames = [];
4877
+ for (const [name, toolDef] of Object.entries(currentTools)) {
4878
+ const fullTool = {
4879
+ ...toolDef,
4880
+ name
4881
+ // Use the key as the name
4882
+ };
4883
+ registerTool(fullTool);
4884
+ toolNames.push(name);
4885
+ }
4886
+ registeredToolsRef.current = toolNames;
4887
+ return () => {
4888
+ for (const name of registeredToolsRef.current) {
4889
+ unregisterTool(name);
4890
+ }
4891
+ registeredToolsRef.current = [];
4892
+ };
4893
+ }, [toolsKey]);
4894
+ }
4895
+ function SkillContextInjector({
4896
+ registry,
4897
+ skills
4898
+ }) {
4899
+ const catalog = useMemo(() => registry.buildCatalog(), [skills]);
4900
+ const eagerContent = useMemo(() => registry.buildEagerContent(), [skills]);
4901
+ useAIContext({
4902
+ key: "__skill_catalog__",
4903
+ description: "Skills the AI can load on demand",
4904
+ data: catalog ? `You have access to specialized skills. Call load_skill({ name }) when relevant.
4905
+
4906
+ ${catalog}` : ""
4907
+ });
4908
+ useAIContext({
4909
+ key: "__skill_eager__",
4910
+ description: "Always-active skill instructions",
4911
+ data: eagerContent
4912
+ });
4913
+ return null;
4914
+ }
4915
+ function SkillRequestSync({ skills }) {
4916
+ const { setInlineSkills } = useCopilot();
4917
+ useEffect(() => {
4918
+ const inlineSkills = skills.filter((s) => s.source.type === "inline").map((s) => ({
4919
+ name: s.name,
4920
+ description: s.description,
4921
+ content: s.content,
4922
+ strategy: s.strategy
4923
+ }));
4924
+ setInlineSkills(inlineSkills);
4925
+ }, [skills, setInlineSkills]);
4926
+ return null;
4927
+ }
4928
+ function SkillToolRegistrar({
4929
+ registry,
4930
+ skills
4931
+ }) {
4932
+ useTool(
4933
+ {
4934
+ name: "load_skill",
4935
+ description: "Load a skill by name to get full instructions for a specialized task.",
4936
+ inputSchema: {
4937
+ type: "object",
4938
+ properties: {
4939
+ name: {
4940
+ type: "string",
4941
+ description: "The name of the skill to load."
4942
+ }
4943
+ },
4944
+ required: ["name"]
4945
+ },
4946
+ handler: async ({ name }) => {
4947
+ const skill = registry.get(name);
4948
+ if (!skill) {
4949
+ const available = registry.getAuto().map((s) => s.name).join(", ") || "none";
4950
+ return {
4951
+ success: false,
4952
+ error: `Skill "${name}" not found. Available skills: ${available}`
4953
+ };
4954
+ }
4955
+ const sourceTypeMap = {
4956
+ inline: "client-inline",
4957
+ url: "remote-url",
4958
+ file: "server-dir"
4959
+ };
4960
+ return {
4961
+ success: true,
4962
+ name: skill.name,
4963
+ description: skill.description,
4964
+ strategy: skill.strategy ?? "auto",
4965
+ content: skill.content,
4966
+ source: sourceTypeMap[skill.source.type]
4967
+ };
4968
+ }
4969
+ },
4970
+ // eslint-disable-next-line react-hooks/exhaustive-deps
4971
+ [skills]
4972
+ );
4973
+ return null;
4974
+ }
4975
+ function SkillProvider({
4976
+ children,
4977
+ skills: skillsProp
4978
+ }) {
4979
+ const registryRef = useRef(null);
4980
+ if (registryRef.current === null) {
4981
+ registryRef.current = new SkillRegistry();
4982
+ }
4983
+ const registry = registryRef.current;
4984
+ const [skills, setSkills] = useState([]);
4985
+ useEffect(() => {
4986
+ if (!skillsProp?.length) return;
4987
+ for (const def of skillsProp) {
4988
+ if (def.source.type !== "inline") {
4989
+ console.warn(
4990
+ `[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.`
4991
+ );
4992
+ continue;
4993
+ }
4994
+ const resolved = {
4995
+ ...def,
4996
+ content: def.source.content
4997
+ };
4998
+ registry.register(resolved);
4999
+ }
5000
+ setSkills(registry.getAll());
5001
+ return () => {
5002
+ for (const def of skillsProp ?? []) {
5003
+ registry.unregister(def.name);
2513
5004
  }
2514
- registeredToolsRef.current = [];
5005
+ setSkills(registry.getAll());
2515
5006
  };
2516
- }, [
2517
- autoRegister,
2518
- mcpClient.isConnected,
2519
- toolDefinitions,
2520
- registerTool,
2521
- unregisterTool
2522
- ]);
2523
- return {
2524
- ...mcpClient,
2525
- toolDefinitions
2526
- };
5007
+ }, [skillsProp]);
5008
+ const register = useCallback((skill) => {
5009
+ registry.register(skill);
5010
+ setSkills(registry.getAll());
5011
+ }, []);
5012
+ const unregister = useCallback((name) => {
5013
+ registry.unregister(name);
5014
+ setSkills(registry.getAll());
5015
+ }, []);
5016
+ const contextValue = useMemo(
5017
+ () => ({ registry, register, unregister, skills }),
5018
+ [register, unregister, skills]
5019
+ );
5020
+ return /* @__PURE__ */ jsxs(SkillContext.Provider, { value: contextValue, children: [
5021
+ /* @__PURE__ */ jsx(SkillContextInjector, { registry, skills }),
5022
+ /* @__PURE__ */ jsx(SkillToolRegistrar, { registry, skills }),
5023
+ /* @__PURE__ */ jsx(SkillRequestSync, { skills }),
5024
+ children
5025
+ ] });
2527
5026
  }
2528
5027
  function MCPConnection({ config }) {
2529
5028
  useMCPTools({
@@ -2537,6 +5036,123 @@ function MCPConnection({ config }) {
2537
5036
  });
2538
5037
  return null;
2539
5038
  }
5039
+ var COMPACTING_MARKER_ID = "__compacting-in-progress__";
5040
+ function MessageHistoryBridge({
5041
+ chatRef
5042
+ }) {
5043
+ const { compactionState, tokenUsage } = useMessageHistory();
5044
+ const ctx = useMessageHistoryContext();
5045
+ const loaderAddedRef = useRef(false);
5046
+ const prevCompactionCountRef = useRef(compactionState.compactionCount);
5047
+ useEffect(() => {
5048
+ if (!tokenUsage.isApproaching) {
5049
+ loaderAddedRef.current = false;
5050
+ return;
5051
+ }
5052
+ if (loaderAddedRef.current) return;
5053
+ const chat = chatRef.current;
5054
+ if (!chat) return;
5055
+ const alreadyAdded = chat.messages.some(
5056
+ (m) => m.id === COMPACTING_MARKER_ID
5057
+ );
5058
+ if (alreadyAdded) return;
5059
+ loaderAddedRef.current = true;
5060
+ const loading = {
5061
+ id: COMPACTING_MARKER_ID,
5062
+ role: "system",
5063
+ content: "Compacting conversation\u2026",
5064
+ createdAt: /* @__PURE__ */ new Date(),
5065
+ metadata: { type: "compaction-marker", compacting: true }
5066
+ };
5067
+ chat.setMessages([...chat.messages, loading]);
5068
+ }, [tokenUsage.isApproaching]);
5069
+ useEffect(() => {
5070
+ if (compactionState.compactionCount <= prevCompactionCountRef.current)
5071
+ return;
5072
+ prevCompactionCountRef.current = compactionState.compactionCount;
5073
+ loaderAddedRef.current = false;
5074
+ const chat = chatRef.current;
5075
+ if (!chat) return;
5076
+ const hasLoader = chat.messages.some((m) => m.id === COMPACTING_MARKER_ID);
5077
+ const base = hasLoader ? chat.messages.map(
5078
+ (m) => m.id === COMPACTING_MARKER_ID ? {
5079
+ ...m,
5080
+ id: `compaction-marker-${compactionState.compactionCount}`,
5081
+ content: `Conversation compacted \u2014 context window refreshed`,
5082
+ metadata: { type: "compaction-marker", compacting: false }
5083
+ } : m
5084
+ ) : [
5085
+ ...chat.messages,
5086
+ {
5087
+ id: `compaction-marker-${compactionState.compactionCount}`,
5088
+ role: "system",
5089
+ content: `Conversation compacted \u2014 context window refreshed`,
5090
+ createdAt: /* @__PURE__ */ new Date(),
5091
+ metadata: { type: "compaction-marker", compacting: false }
5092
+ }
5093
+ ];
5094
+ chat.setMessages(base);
5095
+ }, [compactionState.compactionCount]);
5096
+ const compactionStateRef = useRef(compactionState);
5097
+ compactionStateRef.current = compactionState;
5098
+ const configRef = useRef(ctx.config);
5099
+ configRef.current = ctx.config;
5100
+ useEffect(() => {
5101
+ const chat = chatRef.current;
5102
+ if (!chat) return;
5103
+ chat.setRequestMessageTransform((allMessages) => {
5104
+ if (allMessages.length === 0) return allMessages;
5105
+ let lastUserIdx = -1;
5106
+ for (let i = allMessages.length - 1; i >= 0; i--) {
5107
+ if (allMessages[i].role === "user") {
5108
+ lastUserIdx = i;
5109
+ break;
5110
+ }
5111
+ }
5112
+ if (lastUserIdx === -1) return allMessages;
5113
+ const historyMessages = allMessages.slice(0, lastUserIdx);
5114
+ const currentTurn = allMessages.slice(lastUserIdx);
5115
+ if (historyMessages.length === 0) return allMessages;
5116
+ const cfg = configRef.current;
5117
+ const cs = compactionStateRef.current;
5118
+ const recentBuffer = cfg.recentBuffer ?? 10;
5119
+ const isCompactionMsg = (m) => m.metadata?.["type"] === "compaction-marker";
5120
+ const windowedHistory = [];
5121
+ if (cs.workingMemory.length > 0) {
5122
+ windowedHistory.push({
5123
+ id: "working-memory",
5124
+ role: "system",
5125
+ content: `[Working memory \u2014 always active]
5126
+ ${cs.workingMemory.join("\n")}`,
5127
+ createdAt: /* @__PURE__ */ new Date()
5128
+ });
5129
+ }
5130
+ if (cs.rollingSummary) {
5131
+ windowedHistory.push({
5132
+ id: "rolling-summary",
5133
+ role: "system",
5134
+ content: `[Previous conversation summary]
5135
+ ${cs.rollingSummary}`,
5136
+ createdAt: /* @__PURE__ */ new Date()
5137
+ });
5138
+ }
5139
+ const systemMsgs = historyMessages.filter(
5140
+ (m) => m.role === "system" && !isCompactionMsg(m)
5141
+ );
5142
+ windowedHistory.push(...systemMsgs);
5143
+ const conversationMsgs = historyMessages.filter(
5144
+ (m) => m.role !== "system"
5145
+ );
5146
+ const recentStart = Math.max(0, conversationMsgs.length - recentBuffer);
5147
+ windowedHistory.push(...conversationMsgs.slice(recentStart));
5148
+ return [...windowedHistory, ...currentTurn];
5149
+ });
5150
+ return () => {
5151
+ chatRef.current?.setRequestMessageTransform(null);
5152
+ };
5153
+ }, []);
5154
+ return null;
5155
+ }
2540
5156
  var CopilotContext = createContext(null);
2541
5157
  function useCopilot() {
2542
5158
  const context = useContext(CopilotContext);
@@ -2560,11 +5176,14 @@ function CopilotProvider({
2560
5176
  debug = false,
2561
5177
  maxIterations,
2562
5178
  maxIterationsMessage,
2563
- mcpServers
5179
+ mcpServers,
5180
+ optimization,
5181
+ messageHistory,
5182
+ skills
2564
5183
  }) {
2565
5184
  const debugLog = useCallback(
2566
- (...args) => {
2567
- if (debug) console.log("[Copilot SDK]", ...args);
5185
+ (action, data) => {
5186
+ createLogger("provider", () => debug ?? false)(action, data);
2568
5187
  },
2569
5188
  [debug]
2570
5189
  );
@@ -2604,7 +5223,8 @@ function CopilotProvider({
2604
5223
  body,
2605
5224
  debug,
2606
5225
  maxIterations,
2607
- maxIterationsMessage
5226
+ maxIterationsMessage,
5227
+ optimization
2608
5228
  },
2609
5229
  {
2610
5230
  onToolExecutionsChange: (executions) => {
@@ -2614,6 +5234,9 @@ function CopilotProvider({
2614
5234
  onApprovalRequired: (execution) => {
2615
5235
  debugLog("Tool approval required:", execution.name);
2616
5236
  },
5237
+ onContextUsageChange: (usage) => {
5238
+ setContextUsage(usage);
5239
+ },
2617
5240
  onError: (error2) => {
2618
5241
  if (error2) onError?.(error2);
2619
5242
  }
@@ -2644,19 +5267,27 @@ function CopilotProvider({
2644
5267
  debugLog("URL config updated from prop");
2645
5268
  }
2646
5269
  }, [runtimeUrl, debugLog]);
5270
+ const EMPTY_MESSAGES = useRef([]);
5271
+ const getMessagesSnapshot = useCallback(() => chatRef.current.messages, []);
5272
+ const getServerMessagesSnapshot = useCallback(
5273
+ () => EMPTY_MESSAGES.current,
5274
+ []
5275
+ );
5276
+ const getStatusSnapshot = useCallback(() => chatRef.current.status, []);
5277
+ const getErrorSnapshot = useCallback(() => chatRef.current.error, []);
2647
5278
  const messages = useSyncExternalStore(
2648
5279
  chatRef.current.subscribe,
2649
- () => chatRef.current.messages,
2650
- () => chatRef.current.messages
5280
+ getMessagesSnapshot,
5281
+ getServerMessagesSnapshot
2651
5282
  );
2652
5283
  const status = useSyncExternalStore(
2653
5284
  chatRef.current.subscribe,
2654
- () => chatRef.current.status,
5285
+ getStatusSnapshot,
2655
5286
  () => "ready"
2656
5287
  );
2657
5288
  const errorFromChat = useSyncExternalStore(
2658
5289
  chatRef.current.subscribe,
2659
- () => chatRef.current.error,
5290
+ getErrorSnapshot,
2660
5291
  () => void 0
2661
5292
  );
2662
5293
  const error = errorFromChat ?? null;
@@ -2679,9 +5310,15 @@ function CopilotProvider({
2679
5310
  },
2680
5311
  []
2681
5312
  );
2682
- const registeredTools = chatRef.current?.tools ?? [];
2683
- const pendingApprovals = toolExecutions.filter(
2684
- (e) => e.approvalStatus === "required"
5313
+ const registeredTools = useMemo(
5314
+ () => chatRef.current?.tools ?? [],
5315
+ // eslint-disable-next-line react-hooks/exhaustive-deps
5316
+ [toolExecutions]
5317
+ // re-derive when tool executions change (tools change alongside)
5318
+ );
5319
+ const pendingApprovals = useMemo(
5320
+ () => toolExecutions.filter((e) => e.approvalStatus === "required"),
5321
+ [toolExecutions]
2685
5322
  );
2686
5323
  const actionsRef = useRef(/* @__PURE__ */ new Map());
2687
5324
  const [actionsVersion, setActionsVersion] = useState(0);
@@ -2699,6 +5336,8 @@ function CopilotProvider({
2699
5336
  );
2700
5337
  const contextTreeRef = useRef([]);
2701
5338
  const contextIdCounter = useRef(0);
5339
+ const [contextChars, setContextChars] = useState(0);
5340
+ const [contextUsage, setContextUsage] = useState(null);
2702
5341
  const addContext = useCallback(
2703
5342
  (context, parentId) => {
2704
5343
  const id = `ctx-${++contextIdCounter.current}`;
@@ -2709,6 +5348,7 @@ function CopilotProvider({
2709
5348
  );
2710
5349
  const contextString = printTree(contextTreeRef.current);
2711
5350
  chatRef.current?.setContext(contextString);
5351
+ setContextChars(contextString.length);
2712
5352
  debugLog("Context added:", id);
2713
5353
  return id;
2714
5354
  },
@@ -2719,6 +5359,7 @@ function CopilotProvider({
2719
5359
  contextTreeRef.current = removeNode(contextTreeRef.current, id);
2720
5360
  const contextString = printTree(contextTreeRef.current);
2721
5361
  chatRef.current?.setContext(contextString);
5362
+ setContextChars(contextString.length);
2722
5363
  debugLog("Context removed:", id);
2723
5364
  },
2724
5365
  [debugLog]
@@ -2730,6 +5371,13 @@ function CopilotProvider({
2730
5371
  },
2731
5372
  [debugLog]
2732
5373
  );
5374
+ const setInlineSkills = useCallback(
5375
+ (skills2) => {
5376
+ chatRef.current?.setInlineSkills(skills2);
5377
+ debugLog("Inline skills updated", { count: skills2.length });
5378
+ },
5379
+ [debugLog]
5380
+ );
2733
5381
  const sendMessage = useCallback(
2734
5382
  async (content, attachments) => {
2735
5383
  debugLog("Sending message:", content);
@@ -2749,15 +5397,46 @@ function CopilotProvider({
2749
5397
  const regenerate = useCallback(async (messageId) => {
2750
5398
  await chatRef.current?.regenerate(messageId);
2751
5399
  }, []);
5400
+ const switchBranch = useCallback((messageId) => {
5401
+ chatRef.current?.switchBranch(messageId);
5402
+ }, []);
5403
+ const getBranchInfo = useCallback(
5404
+ (messageId) => chatRef.current?.getBranchInfo(messageId) ?? null,
5405
+ []
5406
+ );
5407
+ const editMessage = useCallback(
5408
+ async (messageId, newContent) => {
5409
+ await chatRef.current?.sendMessage(newContent, void 0, {
5410
+ editMessageId: messageId
5411
+ });
5412
+ },
5413
+ []
5414
+ );
5415
+ const getHasBranchesSnapshot = useCallback(
5416
+ () => chatRef.current.hasBranches,
5417
+ []
5418
+ );
5419
+ const hasBranches = useSyncExternalStore(
5420
+ chatRef.current.subscribe,
5421
+ getHasBranchesSnapshot,
5422
+ () => false
5423
+ );
5424
+ const getAllMessages = useCallback(
5425
+ () => chatRef.current?.getAllMessages?.() ?? [],
5426
+ []
5427
+ );
2752
5428
  useEffect(() => {
2753
5429
  if (onMessagesChange && messages.length > 0) {
2754
- const coreMessages = messages.map((m) => ({
5430
+ const allUIMessages = chatRef.current?.getAllMessages?.() ?? messages;
5431
+ const coreMessages = allUIMessages.map((m) => ({
2755
5432
  id: m.id,
2756
5433
  role: m.role,
2757
5434
  content: m.content,
2758
5435
  created_at: m.createdAt,
2759
5436
  tool_calls: m.toolCalls,
2760
5437
  tool_call_id: m.toolCallId,
5438
+ parent_id: m.parentId,
5439
+ children_ids: m.childrenIds,
2761
5440
  metadata: {
2762
5441
  attachments: m.attachments,
2763
5442
  thinking: m.thinking
@@ -2789,6 +5468,12 @@ function CopilotProvider({
2789
5468
  clearMessages,
2790
5469
  setMessages,
2791
5470
  regenerate,
5471
+ // Branching
5472
+ switchBranch,
5473
+ getBranchInfo,
5474
+ editMessage,
5475
+ hasBranches,
5476
+ getAllMessages,
2792
5477
  // Tool execution
2793
5478
  registerTool,
2794
5479
  unregisterTool,
@@ -2804,8 +5489,12 @@ function CopilotProvider({
2804
5489
  // AI Context
2805
5490
  addContext,
2806
5491
  removeContext,
5492
+ contextChars,
5493
+ contextUsage,
2807
5494
  // System Prompt
2808
5495
  setSystemPrompt,
5496
+ // Skills
5497
+ setInlineSkills,
2809
5498
  // Config
2810
5499
  threadId,
2811
5500
  runtimeUrl,
@@ -2821,6 +5510,11 @@ function CopilotProvider({
2821
5510
  clearMessages,
2822
5511
  setMessages,
2823
5512
  regenerate,
5513
+ switchBranch,
5514
+ getBranchInfo,
5515
+ editMessage,
5516
+ hasBranches,
5517
+ getAllMessages,
2824
5518
  registerTool,
2825
5519
  unregisterTool,
2826
5520
  registeredTools,
@@ -2833,16 +5527,41 @@ function CopilotProvider({
2833
5527
  registeredActions,
2834
5528
  addContext,
2835
5529
  removeContext,
5530
+ contextChars,
5531
+ contextUsage,
2836
5532
  setSystemPrompt,
5533
+ setInlineSkills,
2837
5534
  threadId,
2838
5535
  runtimeUrl,
2839
5536
  toolsConfig
2840
5537
  ]
2841
5538
  );
2842
- return /* @__PURE__ */ jsxs(CopilotContext.Provider, { value: contextValue, children: [
5539
+ const messageHistoryContextValue = React2.useMemo(
5540
+ () => ({
5541
+ config: { ...defaultMessageHistoryConfig, ...messageHistory },
5542
+ tokenUsage: {
5543
+ current: 0,
5544
+ max: messageHistory?.maxContextTokens ?? 128e3,
5545
+ percentage: 0,
5546
+ isApproaching: false
5547
+ },
5548
+ compactionState: {
5549
+ rollingSummary: null,
5550
+ lastCompactionAt: null,
5551
+ compactionCount: 0,
5552
+ totalTokensSaved: 0,
5553
+ workingMemory: [],
5554
+ displayMessageCount: 0,
5555
+ llmMessageCount: 0
5556
+ }
5557
+ }),
5558
+ [messageHistory]
5559
+ );
5560
+ return /* @__PURE__ */ jsx(MessageHistoryContext.Provider, { value: messageHistoryContextValue, children: /* @__PURE__ */ jsxs(CopilotContext.Provider, { value: contextValue, children: [
2843
5561
  mcpServers?.map((config) => /* @__PURE__ */ jsx(MCPConnection, { config }, config.name)),
2844
- children
2845
- ] });
5562
+ messageHistory?.strategy && messageHistory.strategy !== "none" && /* @__PURE__ */ jsx(MessageHistoryBridge, { chatRef }),
5563
+ skills ? /* @__PURE__ */ jsx(SkillProvider, { skills, children }) : children
5564
+ ] }) });
2846
5565
  }
2847
5566
  function useAIActions(actions) {
2848
5567
  const { registerAction, unregisterAction } = useCopilot();
@@ -2860,61 +5579,6 @@ function useAIActions(actions) {
2860
5579
  function useAIAction(action) {
2861
5580
  useAIActions([action]);
2862
5581
  }
2863
- function useAIContext(item) {
2864
- const { addContext, removeContext } = useCopilot();
2865
- const contextIdRef = useRef(null);
2866
- const serializedData = typeof item.data === "string" ? item.data : JSON.stringify(item.data);
2867
- useEffect(() => {
2868
- const formattedValue = typeof item.data === "string" ? item.data : JSON.stringify(item.data, null, 2);
2869
- const contextString = item.description ? `${item.description}:
2870
- ${formattedValue}` : `${item.key}:
2871
- ${formattedValue}`;
2872
- contextIdRef.current = addContext(contextString, item.parentId);
2873
- return () => {
2874
- if (contextIdRef.current) {
2875
- removeContext(contextIdRef.current);
2876
- contextIdRef.current = null;
2877
- }
2878
- };
2879
- }, [
2880
- item.key,
2881
- serializedData,
2882
- item.description,
2883
- item.parentId,
2884
- addContext,
2885
- removeContext
2886
- ]);
2887
- return contextIdRef.current ?? void 0;
2888
- }
2889
- function useAIContexts(items) {
2890
- const { addContext, removeContext } = useCopilot();
2891
- const contextIdsRef = useRef([]);
2892
- const serializedItems = JSON.stringify(
2893
- items.map((item) => ({
2894
- key: item.key,
2895
- data: item.data,
2896
- description: item.description,
2897
- parentId: item.parentId
2898
- }))
2899
- );
2900
- useEffect(() => {
2901
- contextIdsRef.current.forEach((id) => removeContext(id));
2902
- contextIdsRef.current = [];
2903
- const parsedItems = JSON.parse(serializedItems);
2904
- for (const item of parsedItems) {
2905
- const formattedValue = typeof item.data === "string" ? item.data : JSON.stringify(item.data, null, 2);
2906
- const contextString = item.description ? `${item.description}:
2907
- ${formattedValue}` : `${item.key}:
2908
- ${formattedValue}`;
2909
- const id = addContext(contextString, item.parentId);
2910
- contextIdsRef.current.push(id);
2911
- }
2912
- return () => {
2913
- contextIdsRef.current.forEach((id) => removeContext(id));
2914
- contextIdsRef.current = [];
2915
- };
2916
- }, [serializedItems, addContext, removeContext]);
2917
- }
2918
5582
  function useAITools(options = {}) {
2919
5583
  const {
2920
5584
  screenshot = false,
@@ -3159,69 +5823,6 @@ function useAITools(options = {}) {
3159
5823
  ]
3160
5824
  );
3161
5825
  }
3162
- function isZodSchema(value) {
3163
- if (value === null || typeof value !== "object") return false;
3164
- const obj = value;
3165
- return "_def" in obj && typeof obj._def === "object" || "_zod" in obj && typeof obj._zod === "object" || "~standard" in obj;
3166
- }
3167
- function useTool(config, dependencies = []) {
3168
- const { registerTool, unregisterTool } = useCopilot();
3169
- const configRef = useRef(config);
3170
- configRef.current = config;
3171
- const inputSchema = useMemo(() => {
3172
- if (isZodSchema(config.inputSchema)) {
3173
- return zodToJsonSchema(config.inputSchema);
3174
- }
3175
- return config.inputSchema;
3176
- }, [config.inputSchema]);
3177
- useEffect(() => {
3178
- const tool2 = {
3179
- name: config.name,
3180
- description: config.description,
3181
- location: "client",
3182
- inputSchema,
3183
- handler: async (params, context) => {
3184
- return configRef.current.handler(params, context);
3185
- },
3186
- render: config.render,
3187
- available: config.available ?? true,
3188
- needsApproval: config.needsApproval,
3189
- approvalMessage: config.approvalMessage,
3190
- hidden: config.hidden
3191
- };
3192
- registerTool(tool2);
3193
- return () => {
3194
- unregisterTool(config.name);
3195
- };
3196
- }, [config.name, inputSchema, ...dependencies]);
3197
- }
3198
- function useTools(tools) {
3199
- const { registerTool, unregisterTool } = useCopilot();
3200
- const registeredToolsRef = useRef([]);
3201
- const toolsRef = useRef(tools);
3202
- toolsRef.current = tools;
3203
- const toolsKey = Object.keys(tools).sort().join(",");
3204
- useEffect(() => {
3205
- const currentTools = toolsRef.current;
3206
- const toolNames = [];
3207
- for (const [name, toolDef] of Object.entries(currentTools)) {
3208
- const fullTool = {
3209
- ...toolDef,
3210
- name
3211
- // Use the key as the name
3212
- };
3213
- registerTool(fullTool);
3214
- toolNames.push(name);
3215
- }
3216
- registeredToolsRef.current = toolNames;
3217
- return () => {
3218
- for (const name of registeredToolsRef.current) {
3219
- unregisterTool(name);
3220
- }
3221
- registeredToolsRef.current = [];
3222
- };
3223
- }, [toolsKey]);
3224
- }
3225
5826
  function convertZodSchema(schema, _toolName) {
3226
5827
  try {
3227
5828
  const zodWithJsonSchema = z;
@@ -4409,6 +7010,94 @@ function createToolIntentHandler(callTool) {
4409
7010
  }
4410
7011
  };
4411
7012
  }
7013
+ function getLastResponseUsage(messages) {
7014
+ for (let i = messages.length - 1; i >= 0; i--) {
7015
+ const msg = messages[i];
7016
+ if (msg.role === "assistant" && msg.metadata?.usage) {
7017
+ const u = msg.metadata.usage;
7018
+ const prompt = u.prompt_tokens ?? 0;
7019
+ const completion = u.completion_tokens ?? 0;
7020
+ return {
7021
+ prompt_tokens: prompt,
7022
+ completion_tokens: completion,
7023
+ total_tokens: u.total_tokens ?? prompt + completion
7024
+ };
7025
+ }
7026
+ }
7027
+ return null;
7028
+ }
7029
+ function useContextStats() {
7030
+ const { contextChars, contextUsage, registeredTools, messages } = useCopilot();
7031
+ const toolCount = useMemo(() => registeredTools.length, [registeredTools]);
7032
+ const messageCount = useMemo(
7033
+ () => messages.filter((m) => m.role !== "system").length,
7034
+ [messages]
7035
+ );
7036
+ const totalTokens = useMemo(() => {
7037
+ if (contextUsage) return contextUsage.total.tokens;
7038
+ return Math.ceil(contextChars / 3.5);
7039
+ }, [contextUsage, contextChars]);
7040
+ const usagePercent = useMemo(() => {
7041
+ if (contextUsage) return contextUsage.total.percent;
7042
+ return 0;
7043
+ }, [contextUsage]);
7044
+ const lastResponseUsage = useMemo(
7045
+ () => getLastResponseUsage(messages),
7046
+ [messages]
7047
+ );
7048
+ return {
7049
+ contextUsage,
7050
+ totalTokens,
7051
+ usagePercent,
7052
+ contextChars,
7053
+ toolCount,
7054
+ messageCount,
7055
+ lastResponseUsage
7056
+ };
7057
+ }
7058
+ var DEV_CONTENT_WARN_THRESHOLD = 2e3;
7059
+ function useSkill(skill) {
7060
+ const { register, unregister } = useSkillContext();
7061
+ if (process.env.NODE_ENV !== "production" && skill.source.type === "inline" && skill.source.content.length > DEV_CONTENT_WARN_THRESHOLD) {
7062
+ console.warn(
7063
+ `[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.`
7064
+ );
7065
+ }
7066
+ useEffect(() => {
7067
+ if (skill.source.type !== "inline") {
7068
+ console.warn(
7069
+ `[copilot-sdk/skills] useSkill only supports inline skills client-side. Skill "${skill.name}" has source type "${skill.source.type}" and will be skipped.`
7070
+ );
7071
+ return;
7072
+ }
7073
+ const resolved = {
7074
+ ...skill,
7075
+ content: skill.source.content
7076
+ };
7077
+ register(resolved);
7078
+ return () => {
7079
+ unregister(skill.name);
7080
+ };
7081
+ }, [
7082
+ skill.name,
7083
+ skill.source.type === "inline" ? skill.source.content : "",
7084
+ skill.strategy,
7085
+ skill.description
7086
+ ]);
7087
+ }
7088
+ function useSkillStatus() {
7089
+ const { skills, registry } = useSkillContext();
7090
+ const has = useCallback(
7091
+ (name) => registry.has(name),
7092
+ // eslint-disable-next-line react-hooks/exhaustive-deps
7093
+ [skills]
7094
+ );
7095
+ return {
7096
+ skills,
7097
+ count: skills.length,
7098
+ has
7099
+ };
7100
+ }
4412
7101
 
4413
7102
  // src/react/utils/permission-storage.ts
4414
7103
  var DEFAULT_KEY_PREFIX = "yourgpt-permissions";
@@ -4561,6 +7250,34 @@ var ReactChat = class extends AbstractChat {
4561
7250
  return this.on("error", handler);
4562
7251
  }
4563
7252
  // ============================================
7253
+ // Branching API — pass-throughs to ReactChatState
7254
+ // ============================================
7255
+ /**
7256
+ * Navigate to a sibling branch (makes it the active path).
7257
+ */
7258
+ switchBranch(messageId) {
7259
+ this.reactState.switchBranch(messageId);
7260
+ }
7261
+ /**
7262
+ * Get branch navigation info for a message.
7263
+ * Returns null if the message has no siblings.
7264
+ */
7265
+ getBranchInfo(messageId) {
7266
+ return this.reactState.getBranchInfo(messageId);
7267
+ }
7268
+ /**
7269
+ * Get all messages across all branches (for persistence).
7270
+ */
7271
+ getAllMessages() {
7272
+ return this.reactState.getAllMessages();
7273
+ }
7274
+ /**
7275
+ * Whether any message has siblings (branching has occurred).
7276
+ */
7277
+ get hasBranches() {
7278
+ return this.reactState.hasBranches;
7279
+ }
7280
+ // ============================================
4564
7281
  // Override dispose to clean up state
4565
7282
  // ============================================
4566
7283
  dispose() {
@@ -4620,6 +7337,11 @@ function useChat(config) {
4620
7337
  () => void 0
4621
7338
  // Server snapshot
4622
7339
  );
7340
+ const hasBranches = useSyncExternalStore(
7341
+ chatRef.current.subscribe,
7342
+ () => chatRef.current.hasBranches,
7343
+ () => false
7344
+ );
4623
7345
  const isLoading = status === "streaming" || status === "submitted";
4624
7346
  const sendMessage = useCallback(
4625
7347
  async (content, attachments) => {
@@ -4646,6 +7368,24 @@ function useChat(config) {
4646
7368
  },
4647
7369
  []
4648
7370
  );
7371
+ const switchBranch = useCallback((messageId) => {
7372
+ chatRef.current?.switchBranch(messageId);
7373
+ }, []);
7374
+ const getBranchInfo = useCallback(
7375
+ (messageId) => {
7376
+ return chatRef.current?.getBranchInfo(messageId) ?? null;
7377
+ },
7378
+ []
7379
+ );
7380
+ const editMessage = useCallback(
7381
+ async (messageId, newContent) => {
7382
+ await chatRef.current?.sendMessage(newContent, void 0, {
7383
+ editMessageId: messageId
7384
+ });
7385
+ setInput("");
7386
+ },
7387
+ []
7388
+ );
4649
7389
  useEffect(() => {
4650
7390
  return () => {
4651
7391
  chatRef.current?.dispose();
@@ -4664,10 +7404,20 @@ function useChat(config) {
4664
7404
  setMessages,
4665
7405
  regenerate,
4666
7406
  continueWithToolResults,
4667
- chatRef
7407
+ chatRef,
7408
+ // Branching
7409
+ switchBranch,
7410
+ getBranchInfo,
7411
+ editMessage,
7412
+ hasBranches
4668
7413
  };
4669
7414
  }
4670
7415
 
4671
- export { AbstractAgentLoop, AbstractChat, CopilotProvider, ReactChat, ReactChatState, ReactThreadManager, ReactThreadManagerState, createMessageIntentHandler, createPermissionStorage, createReactChat, createReactChatState, createReactThreadManager, createReactThreadManagerState, createSessionPermissionCache, createToolIntentHandler, formatKnowledgeResultsForAI, initialAgentLoopState, searchKnowledgeBase, useAIAction, useAIActions, useAIContext, useAIContexts, useAITools, useAgent, useCapabilities, useChat, useCopilot, useDevLogger, useFeatureSupport, useKnowledgeBase, useMCPClient, useMCPTools, useMCPUIIntents, useSuggestions, useSupportedMediaTypes, useThreadManager, useTool, useToolExecutor, useToolWithSchema, useTools, useToolsWithSchema };
4672
- //# sourceMappingURL=chunk-7PKGRYHY.js.map
4673
- //# sourceMappingURL=chunk-7PKGRYHY.js.map
7416
+ // src/react/skill/define-skill.ts
7417
+ function defineSkill(def) {
7418
+ return def;
7419
+ }
7420
+
7421
+ export { AbstractAgentLoop, AbstractChat, CopilotProvider, MessageHistoryContext, MessageTree, ReactChat, ReactChatState, ReactThreadManager, ReactThreadManagerState, SkillProvider, createMessageIntentHandler, createPermissionStorage, createReactChat, createReactChatState, createReactThreadManager, createReactThreadManagerState, createSessionPermissionCache, createToolIntentHandler, defaultMessageHistoryConfig, defineSkill, formatKnowledgeResultsForAI, initialAgentLoopState, isCompactionMarker, keepToolPairsAtomic, searchKnowledgeBase, toDisplayMessage, toLLMMessage, toLLMMessages, useAIAction, useAIActions, useAIContext, useAIContexts, useAITools, useAgent, useCapabilities, useChat, useContextStats, useCopilot, useDevLogger, useFeatureSupport, useKnowledgeBase, useMCPClient, useMCPTools, useMCPUIIntents, useMessageHistory, useMessageHistoryContext, useSkill, useSkillStatus, useSuggestions, useSupportedMediaTypes, useThreadManager, useTool, useToolExecutor, useToolWithSchema, useTools, useToolsWithSchema };
7422
+ //# sourceMappingURL=chunk-5Q72LZ5H.js.map
7423
+ //# sourceMappingURL=chunk-5Q72LZ5H.js.map