@yourgpt/copilot-sdk 2.1.3 → 2.1.5-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/MessageTree-CoIt_4nB.d.cts +161 -0
- package/dist/MessageTree-CzaN9Eul.d.ts +161 -0
- package/dist/{ThreadManager-Dkp_eLty.d.ts → ThreadManager-BEAECB7Y.d.ts} +1 -1
- package/dist/{ThreadManager-LfFRhr4e.d.cts → ThreadManager-Cw5fwyCN.d.cts} +1 -1
- package/dist/{chunk-POZNNKNJ.cjs → chunk-246B6X5D.cjs} +8 -2
- package/dist/chunk-246B6X5D.cjs.map +1 -0
- package/dist/{chunk-QLH6TSCC.js → chunk-4QXY2PBG.js} +8 -2
- package/dist/chunk-4QXY2PBG.js.map +1 -0
- package/dist/{chunk-DMBFN7KO.js → chunk-6BMQZIS3.js} +3105 -355
- package/dist/chunk-6BMQZIS3.js.map +1 -0
- package/dist/{chunk-R6LKHKAI.cjs → chunk-76RE7AJE.cjs} +3277 -509
- package/dist/chunk-76RE7AJE.cjs.map +1 -0
- package/dist/chunk-BJYA5NDL.cjs +96 -0
- package/dist/chunk-BJYA5NDL.cjs.map +1 -0
- package/dist/{chunk-LZMBBGWH.js → chunk-ENFWM3EY.js} +4 -4
- package/dist/{chunk-LZMBBGWH.js.map → chunk-ENFWM3EY.js.map} +1 -1
- package/dist/{chunk-WQSK3Z4K.cjs → chunk-I3SQUNTT.cjs} +81 -31
- package/dist/chunk-I3SQUNTT.cjs.map +1 -0
- package/dist/{chunk-WAPGTQDR.cjs → chunk-JKGFQUHJ.cjs} +10 -10
- package/dist/{chunk-WAPGTQDR.cjs.map → chunk-JKGFQUHJ.cjs.map} +1 -1
- package/dist/{chunk-XGITAEXU.js → chunk-LLM7AHMO.js} +2 -2
- package/dist/{chunk-XGITAEXU.js.map → chunk-LLM7AHMO.js.map} +1 -1
- package/dist/{chunk-ASV6JLYG.cjs → chunk-NUXLAZOE.cjs} +2 -2
- package/dist/{chunk-ASV6JLYG.cjs.map → chunk-NUXLAZOE.cjs.map} +1 -1
- package/dist/{chunk-VFV5FVVI.js → chunk-UXJ6LIZB.js} +61 -13
- package/dist/chunk-UXJ6LIZB.js.map +1 -0
- package/dist/chunk-VNLLW3ZI.js +94 -0
- package/dist/chunk-VNLLW3ZI.js.map +1 -0
- package/dist/core/index.cjs +99 -91
- package/dist/core/index.d.cts +21 -10
- package/dist/core/index.d.ts +21 -10
- package/dist/core/index.js +5 -5
- package/dist/{index-BHkRA0mM.d.cts → index-CiExk87c.d.cts} +1 -1
- package/dist/{index-tB0qI8my.d.ts → index-Dwrcf-CP.d.ts} +1 -1
- package/dist/mcp/index.d.cts +3 -3
- package/dist/mcp/index.d.ts +3 -3
- package/dist/react/index.cjs +113 -52
- package/dist/react/index.d.cts +703 -90
- package/dist/react/index.d.ts +703 -90
- package/dist/react/index.js +7 -6
- package/dist/server/index.cjs +339 -0
- package/dist/server/index.cjs.map +1 -0
- package/dist/server/index.d.cts +171 -0
- package/dist/server/index.d.ts +171 -0
- package/dist/server/index.js +332 -0
- package/dist/server/index.js.map +1 -0
- package/dist/tools/anthropic/index.cjs +3 -3
- package/dist/tools/anthropic/index.d.cts +1 -1
- package/dist/tools/anthropic/index.d.ts +1 -1
- package/dist/tools/anthropic/index.js +3 -3
- package/dist/tools/brave/index.cjs +6 -6
- package/dist/tools/brave/index.d.cts +1 -1
- package/dist/tools/brave/index.d.ts +1 -1
- package/dist/tools/brave/index.js +3 -3
- package/dist/tools/exa/index.cjs +6 -6
- package/dist/tools/exa/index.d.cts +1 -1
- package/dist/tools/exa/index.d.ts +1 -1
- package/dist/tools/exa/index.js +3 -3
- package/dist/tools/google/index.cjs +6 -6
- package/dist/tools/google/index.d.cts +1 -1
- package/dist/tools/google/index.d.ts +1 -1
- package/dist/tools/google/index.js +3 -3
- package/dist/tools/openai/index.cjs +6 -6
- package/dist/tools/openai/index.d.cts +1 -1
- package/dist/tools/openai/index.d.ts +1 -1
- package/dist/tools/openai/index.js +3 -3
- package/dist/tools/searxng/index.cjs +6 -6
- package/dist/tools/searxng/index.d.cts +1 -1
- package/dist/tools/searxng/index.d.ts +1 -1
- package/dist/tools/searxng/index.js +3 -3
- package/dist/tools/serper/index.cjs +6 -6
- package/dist/tools/serper/index.d.cts +1 -1
- package/dist/tools/serper/index.d.ts +1 -1
- package/dist/tools/serper/index.js +3 -3
- package/dist/tools/tavily/index.cjs +6 -6
- package/dist/tools/tavily/index.d.cts +1 -1
- package/dist/tools/tavily/index.d.ts +1 -1
- package/dist/tools/tavily/index.js +3 -3
- package/dist/tools/web-search/index.cjs +7 -7
- package/dist/tools/web-search/index.d.cts +2 -2
- package/dist/tools/web-search/index.d.ts +2 -2
- package/dist/tools/web-search/index.js +4 -4
- package/dist/{tools-coIcskZ4.d.ts → tools-DHZhF5km.d.cts} +161 -1
- package/dist/{tools-coIcskZ4.d.cts → tools-DHZhF5km.d.ts} +161 -1
- package/dist/{types-rjaSVmEF.d.ts → types-BTyJu0WD.d.ts} +1 -1
- package/dist/types-BckL3hiw.d.cts +93 -0
- package/dist/types-BckL3hiw.d.ts +93 -0
- package/dist/{types-C8t4Ut8f.d.cts → types-BdX7uPj0.d.cts} +1 -1
- package/dist/{types-DG2ya08y.d.ts → types-BeFBBZ5i.d.cts} +64 -1
- package/dist/{types-DG2ya08y.d.cts → types-BeFBBZ5i.d.ts} +64 -1
- package/dist/ui/index.cjs +468 -163
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.d.cts +81 -4
- package/dist/ui/index.d.ts +81 -4
- package/dist/ui/index.js +416 -112
- package/dist/ui/index.js.map +1 -1
- package/package.json +6 -1
- package/dist/chunk-DMBFN7KO.js.map +0 -1
- package/dist/chunk-POZNNKNJ.cjs.map +0 -1
- package/dist/chunk-QLH6TSCC.js.map +0 -1
- package/dist/chunk-R6LKHKAI.cjs.map +0 -1
- package/dist/chunk-VFV5FVVI.js.map +0 -1
- package/dist/chunk-WQSK3Z4K.cjs.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, streamSSE, zodObjectToInputSchema } from './chunk-
|
|
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 {
|
|
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/
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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(
|
|
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
|
-
|
|
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.
|
|
1588
|
+
this.callbacks.onMessagesChange?.(this._allMessages());
|
|
636
1589
|
this.callbacks.onStatusChange?.("submitted");
|
|
637
1590
|
await Promise.resolve();
|
|
638
1591
|
await this.processRequest();
|
|
@@ -682,7 +1635,7 @@ var AbstractChat = class {
|
|
|
682
1635
|
};
|
|
683
1636
|
this.state.pushMessage(toolMessage);
|
|
684
1637
|
}
|
|
685
|
-
this.callbacks.onMessagesChange?.(this.
|
|
1638
|
+
this.callbacks.onMessagesChange?.(this._allMessages());
|
|
686
1639
|
}
|
|
687
1640
|
}
|
|
688
1641
|
/**
|
|
@@ -736,9 +1689,9 @@ var AbstractChat = class {
|
|
|
736
1689
|
this.state.pushMessage(userMessage);
|
|
737
1690
|
}
|
|
738
1691
|
this.state.status = "submitted";
|
|
739
|
-
this.callbacks.onMessagesChange?.(this.
|
|
1692
|
+
this.callbacks.onMessagesChange?.(this._allMessages());
|
|
740
1693
|
this.callbacks.onStatusChange?.("submitted");
|
|
741
|
-
await Promise
|
|
1694
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
742
1695
|
await this.processRequest();
|
|
743
1696
|
} catch (error) {
|
|
744
1697
|
this.handleError(error);
|
|
@@ -767,30 +1720,58 @@ var AbstractChat = class {
|
|
|
767
1720
|
this.callbacks.onMessagesChange?.(messages);
|
|
768
1721
|
}
|
|
769
1722
|
/**
|
|
770
|
-
* Regenerate last response
|
|
1723
|
+
* Regenerate last response.
|
|
1724
|
+
*
|
|
1725
|
+
* Branch-aware: when the state supports branching (setCurrentLeaf is available),
|
|
1726
|
+
* regenerate creates a new sibling response instead of destroying the original.
|
|
1727
|
+
* The old response is preserved and navigable via switchBranch().
|
|
1728
|
+
*
|
|
1729
|
+
* Legacy fallback: when branching is not available, uses old slice() behavior.
|
|
771
1730
|
*/
|
|
772
1731
|
async regenerate(messageId) {
|
|
1732
|
+
if (this.isBusy) return;
|
|
773
1733
|
const messages = this.state.messages;
|
|
774
|
-
let
|
|
1734
|
+
let targetMessage;
|
|
775
1735
|
if (messageId) {
|
|
776
|
-
|
|
1736
|
+
targetMessage = messages.find((m) => m.id === messageId);
|
|
1737
|
+
if (!targetMessage) {
|
|
1738
|
+
targetMessage = this.state.getAllMessages?.().find((m) => m.id === messageId);
|
|
1739
|
+
}
|
|
777
1740
|
} else {
|
|
778
1741
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
779
1742
|
if (messages[i].role === "assistant") {
|
|
780
|
-
|
|
1743
|
+
targetMessage = messages[i];
|
|
781
1744
|
break;
|
|
782
1745
|
}
|
|
783
1746
|
}
|
|
784
1747
|
}
|
|
1748
|
+
if (!targetMessage) return;
|
|
1749
|
+
if (targetMessage.parentId !== void 0 && this.state.setCurrentLeaf) {
|
|
1750
|
+
this.state.setCurrentLeaf(targetMessage.parentId ?? null);
|
|
1751
|
+
this.callbacks.onMessagesChange?.(this._allMessages());
|
|
1752
|
+
this.state.status = "submitted";
|
|
1753
|
+
await Promise.resolve();
|
|
1754
|
+
await this.processRequest();
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
const targetIndex = messages.indexOf(targetMessage);
|
|
785
1758
|
if (targetIndex > 0) {
|
|
786
1759
|
this.state.setMessages(messages.slice(0, targetIndex));
|
|
787
|
-
this.callbacks.onMessagesChange?.(this.
|
|
1760
|
+
this.callbacks.onMessagesChange?.(this._allMessages());
|
|
788
1761
|
await this.processRequest();
|
|
789
1762
|
}
|
|
790
1763
|
}
|
|
791
1764
|
// ============================================
|
|
792
1765
|
// Event Handling
|
|
793
1766
|
// ============================================
|
|
1767
|
+
/**
|
|
1768
|
+
* Returns all messages across all branches when the state supports it
|
|
1769
|
+
* (branch-aware), otherwise returns the visible path.
|
|
1770
|
+
* Use this whenever firing onMessagesChange so inactive branches are not lost.
|
|
1771
|
+
*/
|
|
1772
|
+
_allMessages() {
|
|
1773
|
+
return this.state.getAllMessages?.() ?? this.state.messages;
|
|
1774
|
+
}
|
|
794
1775
|
/**
|
|
795
1776
|
* Subscribe to events
|
|
796
1777
|
*/
|
|
@@ -822,10 +1803,32 @@ var AbstractChat = class {
|
|
|
822
1803
|
*/
|
|
823
1804
|
async processRequest() {
|
|
824
1805
|
const request = this.buildRequest();
|
|
1806
|
+
let preCreatedMessageId;
|
|
1807
|
+
if (this.config.streaming !== false) {
|
|
1808
|
+
const visibleMessages = this.state.messages;
|
|
1809
|
+
const currentLeafId = visibleMessages.length > 0 ? visibleMessages[visibleMessages.length - 1].id : void 0;
|
|
1810
|
+
const preMsg = createEmptyAssistantMessage(void 0, {
|
|
1811
|
+
parentId: currentLeafId
|
|
1812
|
+
});
|
|
1813
|
+
this.state.pushMessage(preMsg);
|
|
1814
|
+
this.callbacks.onMessagesChange?.(this._allMessages());
|
|
1815
|
+
preCreatedMessageId = preMsg.id;
|
|
1816
|
+
}
|
|
825
1817
|
const response = await this.transport.send(request);
|
|
826
1818
|
if (this.isAsyncIterable(response)) {
|
|
827
|
-
await this.handleStreamResponse(response);
|
|
1819
|
+
await this.handleStreamResponse(response, preCreatedMessageId);
|
|
828
1820
|
} else {
|
|
1821
|
+
if (preCreatedMessageId) {
|
|
1822
|
+
const id = preCreatedMessageId;
|
|
1823
|
+
const visibleMsgs = this.state.messages;
|
|
1824
|
+
const placeholderIdx = visibleMsgs.findIndex((m) => m.id === id);
|
|
1825
|
+
const intendedLeafId = placeholderIdx > 0 ? visibleMsgs[placeholderIdx - 1].id : null;
|
|
1826
|
+
const allMsgs = this.state.getAllMessages?.() ?? this.state.messages;
|
|
1827
|
+
this.state.setMessages(allMsgs.filter((m) => m.id !== id));
|
|
1828
|
+
if (intendedLeafId && this.state.setCurrentLeaf) {
|
|
1829
|
+
this.state.setCurrentLeaf(intendedLeafId);
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
829
1832
|
this.handleJsonResponse(response);
|
|
830
1833
|
}
|
|
831
1834
|
}
|
|
@@ -836,30 +1839,60 @@ var AbstractChat = class {
|
|
|
836
1839
|
this.config.tools = tools;
|
|
837
1840
|
}
|
|
838
1841
|
/**
|
|
839
|
-
*
|
|
1842
|
+
* Update prompt/tool optimization behavior.
|
|
840
1843
|
*/
|
|
841
|
-
|
|
842
|
-
this.
|
|
843
|
-
this.
|
|
1844
|
+
setOptimizationConfig(config) {
|
|
1845
|
+
this.config.optimization = config;
|
|
1846
|
+
this.optimizer.updateConfig(config);
|
|
844
1847
|
}
|
|
845
1848
|
/**
|
|
846
|
-
*
|
|
847
|
-
* This allows updating the system prompt after initialization
|
|
1849
|
+
* Select the active tool profile for future requests.
|
|
848
1850
|
*/
|
|
849
|
-
|
|
850
|
-
this.
|
|
851
|
-
this.debug("System prompt updated", { length: prompt.length });
|
|
1851
|
+
setToolProfile(profile) {
|
|
1852
|
+
this.optimizer.setActiveProfile(profile);
|
|
852
1853
|
}
|
|
853
1854
|
/**
|
|
854
|
-
*
|
|
855
|
-
* Can be static headers or a getter function for dynamic resolution
|
|
1855
|
+
* Get the most recent prompt context usage snapshot.
|
|
856
1856
|
*/
|
|
857
|
-
|
|
858
|
-
this.
|
|
859
|
-
|
|
1857
|
+
getContextUsage() {
|
|
1858
|
+
return this.lastContextUsage;
|
|
1859
|
+
}
|
|
1860
|
+
/**
|
|
1861
|
+
* Set inline skills (called by SkillProvider via React layer)
|
|
1862
|
+
*/
|
|
1863
|
+
setInlineSkills(skills) {
|
|
1864
|
+
this.inlineSkills = skills;
|
|
1865
|
+
this.debug("Inline skills updated", { count: skills.length });
|
|
1866
|
+
}
|
|
1867
|
+
/**
|
|
1868
|
+
* Set (or clear) the per-request message transform.
|
|
1869
|
+
* Pass null to disable.
|
|
1870
|
+
*/
|
|
1871
|
+
setRequestMessageTransform(fn) {
|
|
1872
|
+
this.requestMessageTransform = fn;
|
|
1873
|
+
}
|
|
1874
|
+
/**
|
|
1875
|
+
* Set dynamic context (appended to system prompt)
|
|
1876
|
+
*/
|
|
1877
|
+
setContext(context) {
|
|
1878
|
+
this.dynamicContext = context;
|
|
1879
|
+
}
|
|
1880
|
+
/**
|
|
1881
|
+
* Set system prompt dynamically
|
|
1882
|
+
* This allows updating the system prompt after initialization
|
|
1883
|
+
*/
|
|
1884
|
+
setSystemPrompt(prompt) {
|
|
1885
|
+
this.config.systemPrompt = prompt;
|
|
1886
|
+
}
|
|
1887
|
+
/**
|
|
1888
|
+
* Set headers configuration
|
|
1889
|
+
* Can be static headers or a getter function for dynamic resolution
|
|
1890
|
+
*/
|
|
1891
|
+
setHeaders(headers) {
|
|
1892
|
+
this.config.headers = headers;
|
|
1893
|
+
if (this.transport.setHeaders && headers !== void 0) {
|
|
860
1894
|
this.transport.setHeaders(headers);
|
|
861
1895
|
}
|
|
862
|
-
this.debug("Headers config updated");
|
|
863
1896
|
}
|
|
864
1897
|
/**
|
|
865
1898
|
* Set URL configuration
|
|
@@ -870,7 +1903,6 @@ var AbstractChat = class {
|
|
|
870
1903
|
if (this.transport.setUrl) {
|
|
871
1904
|
this.transport.setUrl(url);
|
|
872
1905
|
}
|
|
873
|
-
this.debug("URL config updated");
|
|
874
1906
|
}
|
|
875
1907
|
/**
|
|
876
1908
|
* Set body configuration
|
|
@@ -881,110 +1913,88 @@ var AbstractChat = class {
|
|
|
881
1913
|
if (this.transport.setBody && body !== void 0) {
|
|
882
1914
|
this.transport.setBody(body);
|
|
883
1915
|
}
|
|
884
|
-
this.debug("Body config updated");
|
|
885
1916
|
}
|
|
886
1917
|
/**
|
|
887
1918
|
* Build the request payload
|
|
888
1919
|
*/
|
|
889
1920
|
buildRequest() {
|
|
890
|
-
const
|
|
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 || ""}
|
|
1921
|
+
const systemPrompt = this.dynamicContext ? `${this.config.systemPrompt || ""}
|
|
958
1922
|
|
|
959
1923
|
## Current App Context:
|
|
960
|
-
${this.dynamicContext}`.trim() : this.config.systemPrompt
|
|
1924
|
+
${this.dynamicContext}`.trim() : this.config.systemPrompt;
|
|
1925
|
+
const rawMessages = this.requestMessageTransform ? this.requestMessageTransform(
|
|
1926
|
+
this.state.messages
|
|
1927
|
+
) : this.state.messages;
|
|
1928
|
+
const optimized = this.optimizer.prepare({
|
|
1929
|
+
messages: rawMessages,
|
|
1930
|
+
tools: this.config.tools,
|
|
1931
|
+
systemPrompt
|
|
1932
|
+
});
|
|
1933
|
+
this.lastContextUsage = optimized.contextUsage;
|
|
1934
|
+
this.callbacks.onContextUsageChange?.(optimized.contextUsage);
|
|
1935
|
+
return {
|
|
1936
|
+
messages: optimized.messages,
|
|
1937
|
+
threadId: this.config.threadId,
|
|
1938
|
+
systemPrompt,
|
|
961
1939
|
llm: this.config.llm,
|
|
962
|
-
tools: tools?.length ? tools
|
|
1940
|
+
tools: this.config.tools?.length ? this.config.tools.map((tool2) => ({
|
|
1941
|
+
name: tool2.name,
|
|
1942
|
+
description: tool2.description,
|
|
1943
|
+
category: tool2.category,
|
|
1944
|
+
group: tool2.group,
|
|
1945
|
+
deferLoading: tool2.deferLoading,
|
|
1946
|
+
profiles: tool2.profiles,
|
|
1947
|
+
searchKeywords: tool2.searchKeywords,
|
|
1948
|
+
inputSchema: tool2.inputSchema
|
|
1949
|
+
})) : void 0,
|
|
1950
|
+
__skills: this.inlineSkills.length ? this.inlineSkills : void 0
|
|
963
1951
|
};
|
|
964
1952
|
}
|
|
965
1953
|
/**
|
|
966
1954
|
* Handle streaming response
|
|
967
1955
|
*/
|
|
968
|
-
async handleStreamResponse(stream) {
|
|
1956
|
+
async handleStreamResponse(stream, preCreatedMessageId) {
|
|
969
1957
|
this.state.status = "streaming";
|
|
970
1958
|
this.callbacks.onStatusChange?.("streaming");
|
|
971
|
-
|
|
972
|
-
|
|
1959
|
+
let assistantMessage;
|
|
1960
|
+
if (preCreatedMessageId) {
|
|
1961
|
+
const existing = this.state.messages.find(
|
|
1962
|
+
(m) => m.id === preCreatedMessageId
|
|
1963
|
+
);
|
|
1964
|
+
if (existing) {
|
|
1965
|
+
assistantMessage = existing;
|
|
1966
|
+
} else {
|
|
1967
|
+
assistantMessage = createEmptyAssistantMessage();
|
|
1968
|
+
this.state.pushMessage(assistantMessage);
|
|
1969
|
+
}
|
|
1970
|
+
} else {
|
|
1971
|
+
assistantMessage = createEmptyAssistantMessage();
|
|
1972
|
+
this.state.pushMessage(assistantMessage);
|
|
1973
|
+
}
|
|
973
1974
|
this.streamState = createStreamState(assistantMessage.id);
|
|
974
1975
|
this.callbacks.onMessageStart?.(assistantMessage.id);
|
|
975
|
-
this.
|
|
1976
|
+
this.debugGroup("handleStreamResponse");
|
|
1977
|
+
this.debug("Starting to process stream");
|
|
976
1978
|
let chunkCount = 0;
|
|
977
1979
|
let toolCallsEmitted = false;
|
|
1980
|
+
let pendingClientToolCalls;
|
|
978
1981
|
for await (const chunk of stream) {
|
|
979
1982
|
chunkCount++;
|
|
980
|
-
|
|
1983
|
+
if (chunk.type !== "message:delta") {
|
|
1984
|
+
this.debug("chunk", { count: chunkCount, type: chunk.type });
|
|
1985
|
+
}
|
|
981
1986
|
if (chunk.type === "error") {
|
|
982
1987
|
const error = new Error(chunk.message || "Stream error");
|
|
983
1988
|
this.handleError(error);
|
|
984
1989
|
return;
|
|
985
1990
|
}
|
|
986
1991
|
if (chunk.type === "message:end" && this.streamState?.content) {
|
|
987
|
-
this.debug("message:end mid-stream
|
|
1992
|
+
this.debug("message:end mid-stream", {
|
|
1993
|
+
messageId: this.streamState.messageId,
|
|
1994
|
+
contentLength: this.streamState.content.length,
|
|
1995
|
+
toolCallsInState: this.streamState.toolCalls?.length ?? 0,
|
|
1996
|
+
chunkCount
|
|
1997
|
+
});
|
|
988
1998
|
const turnMessage = streamStateToMessage(this.streamState);
|
|
989
1999
|
const toolCallsHidden = {};
|
|
990
2000
|
for (const [id, result] of this.streamState.toolResults) {
|
|
@@ -1000,7 +2010,11 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt,
|
|
|
1000
2010
|
}
|
|
1001
2011
|
this.state.updateMessageById(
|
|
1002
2012
|
this.streamState.messageId,
|
|
1003
|
-
() =>
|
|
2013
|
+
(existing) => ({
|
|
2014
|
+
...turnMessage,
|
|
2015
|
+
...existing.parentId !== void 0 ? { parentId: existing.parentId } : {},
|
|
2016
|
+
...existing.childrenIds !== void 0 ? { childrenIds: existing.childrenIds } : {}
|
|
2017
|
+
})
|
|
1004
2018
|
);
|
|
1005
2019
|
this.callbacks.onMessageFinish?.(turnMessage);
|
|
1006
2020
|
this.streamState = null;
|
|
@@ -1015,6 +2029,93 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt,
|
|
|
1015
2029
|
continue;
|
|
1016
2030
|
}
|
|
1017
2031
|
if (!this.streamState) {
|
|
2032
|
+
if (chunk.type === "tool_calls") {
|
|
2033
|
+
pendingClientToolCalls = chunk.toolCalls;
|
|
2034
|
+
this.debug("tool_calls (post-message:end, stored as pending)", {
|
|
2035
|
+
count: pendingClientToolCalls?.length,
|
|
2036
|
+
ids: pendingClientToolCalls?.map((tc) => tc.id)
|
|
2037
|
+
});
|
|
2038
|
+
continue;
|
|
2039
|
+
}
|
|
2040
|
+
if (chunk.type === "done") {
|
|
2041
|
+
this.debug("done (post-message:end)", {
|
|
2042
|
+
hasPendingToolCalls: !!pendingClientToolCalls?.length,
|
|
2043
|
+
pendingCount: pendingClientToolCalls?.length ?? 0,
|
|
2044
|
+
doneMessagesCount: chunk.messages?.length ?? 0,
|
|
2045
|
+
requiresAction: chunk.requiresAction,
|
|
2046
|
+
toolCallsEmitted
|
|
2047
|
+
});
|
|
2048
|
+
if (chunk.messages?.length) {
|
|
2049
|
+
const pendingIds = new Set(
|
|
2050
|
+
(pendingClientToolCalls ?? []).filter((tc) => tc?.id).map((tc) => tc.id)
|
|
2051
|
+
);
|
|
2052
|
+
const messagesToInsert = [];
|
|
2053
|
+
let clientAssistantToolCalls;
|
|
2054
|
+
for (const msg of chunk.messages) {
|
|
2055
|
+
if (msg.role === "assistant" && msg.tool_calls?.length && pendingIds.size > 0 && msg.tool_calls.every(
|
|
2056
|
+
(tc) => pendingIds.has(tc?.id ?? "")
|
|
2057
|
+
)) {
|
|
2058
|
+
clientAssistantToolCalls = msg.tool_calls;
|
|
2059
|
+
continue;
|
|
2060
|
+
}
|
|
2061
|
+
if (msg.role === "assistant" && !msg.tool_calls?.length) continue;
|
|
2062
|
+
messagesToInsert.push({
|
|
2063
|
+
id: generateMessageId(),
|
|
2064
|
+
role: msg.role,
|
|
2065
|
+
content: msg.content ?? "",
|
|
2066
|
+
toolCalls: msg.tool_calls,
|
|
2067
|
+
toolCallId: msg.tool_call_id,
|
|
2068
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
2069
|
+
});
|
|
2070
|
+
}
|
|
2071
|
+
if (clientAssistantToolCalls) {
|
|
2072
|
+
const currentMessages = this.state.messages;
|
|
2073
|
+
for (let i = currentMessages.length - 1; i >= 0; i--) {
|
|
2074
|
+
if (currentMessages[i].role === "assistant") {
|
|
2075
|
+
this.state.updateMessageById(
|
|
2076
|
+
currentMessages[i].id,
|
|
2077
|
+
(m) => ({
|
|
2078
|
+
...m,
|
|
2079
|
+
toolCalls: clientAssistantToolCalls
|
|
2080
|
+
})
|
|
2081
|
+
);
|
|
2082
|
+
break;
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
if (messagesToInsert.length > 0) {
|
|
2087
|
+
const currentMessages = this.state.messages;
|
|
2088
|
+
let insertIdx = currentMessages.length;
|
|
2089
|
+
for (let i = currentMessages.length - 1; i >= 0; i--) {
|
|
2090
|
+
if (currentMessages[i].role === "assistant") {
|
|
2091
|
+
insertIdx = i;
|
|
2092
|
+
break;
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
this.state.setMessages([
|
|
2096
|
+
...currentMessages.slice(0, insertIdx),
|
|
2097
|
+
...messagesToInsert,
|
|
2098
|
+
...currentMessages.slice(insertIdx)
|
|
2099
|
+
]);
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
if (!toolCallsEmitted && pendingClientToolCalls?.length) {
|
|
2103
|
+
toolCallsEmitted = true;
|
|
2104
|
+
this.debug("emit toolCalls (post-message:end path)", {
|
|
2105
|
+
count: pendingClientToolCalls.length,
|
|
2106
|
+
names: pendingClientToolCalls.map(
|
|
2107
|
+
(tc) => tc.function?.name ?? tc.name
|
|
2108
|
+
)
|
|
2109
|
+
});
|
|
2110
|
+
this.emit("toolCalls", { toolCalls: pendingClientToolCalls });
|
|
2111
|
+
} else {
|
|
2112
|
+
this.debug("skip emit toolCalls (post-message:end path)", {
|
|
2113
|
+
toolCallsEmitted,
|
|
2114
|
+
hasPending: !!pendingClientToolCalls?.length
|
|
2115
|
+
});
|
|
2116
|
+
}
|
|
2117
|
+
continue;
|
|
2118
|
+
}
|
|
1018
2119
|
this.debug("warning", "streamState is null, skipping chunk");
|
|
1019
2120
|
continue;
|
|
1020
2121
|
}
|
|
@@ -1050,22 +2151,38 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt,
|
|
|
1050
2151
|
const updatedMessage = streamStateToMessage(this.streamState);
|
|
1051
2152
|
this.state.updateMessageById(
|
|
1052
2153
|
this.streamState.messageId,
|
|
1053
|
-
|
|
2154
|
+
// Preserve parentId/childrenIds from the existing placeholder so the
|
|
2155
|
+
// branch tree structure (activeChildMap) is not corrupted when
|
|
2156
|
+
// setCurrentLeaf() walks up the chain later.
|
|
2157
|
+
(existing) => ({
|
|
2158
|
+
...updatedMessage,
|
|
2159
|
+
...existing.parentId !== void 0 ? { parentId: existing.parentId } : {},
|
|
2160
|
+
...existing.childrenIds !== void 0 ? { childrenIds: existing.childrenIds } : {}
|
|
2161
|
+
})
|
|
1054
2162
|
);
|
|
1055
2163
|
if (chunk.type === "message:delta") {
|
|
1056
2164
|
this.callbacks.onMessageDelta?.(assistantMessage.id, chunk.content);
|
|
1057
2165
|
}
|
|
1058
|
-
if (requiresToolExecution(chunk) && !toolCallsEmitted) {
|
|
1059
|
-
toolCallsEmitted = true;
|
|
1060
|
-
this.debug("toolCalls", { toolCalls: updatedMessage.toolCalls });
|
|
1061
|
-
this.emit("toolCalls", { toolCalls: updatedMessage.toolCalls });
|
|
1062
|
-
}
|
|
1063
2166
|
if (isStreamDone(chunk)) {
|
|
1064
|
-
this.debug("streamDone", {
|
|
2167
|
+
this.debug("streamDone", {
|
|
2168
|
+
chunkType: chunk.type,
|
|
2169
|
+
requiresAction: chunk.requiresAction,
|
|
2170
|
+
doneMessagesCount: chunk.messages?.length ?? 0,
|
|
2171
|
+
streamToolCallsCount: this.streamState?.toolCalls?.length ?? 0,
|
|
2172
|
+
toolCallsEmitted,
|
|
2173
|
+
chunkCount
|
|
2174
|
+
});
|
|
1065
2175
|
if (chunk.type === "done" && chunk.messages?.length) {
|
|
1066
2176
|
this.debug("processDoneMessages", {
|
|
1067
|
-
count: chunk.messages.length
|
|
2177
|
+
count: chunk.messages.length,
|
|
2178
|
+
roles: chunk.messages.map(
|
|
2179
|
+
(m) => `${m.role}${m.tool_calls?.length ? `[${m.tool_calls.length}tc]` : ""}`
|
|
2180
|
+
)
|
|
1068
2181
|
});
|
|
2182
|
+
const currentStreamToolCallIds = new Set(
|
|
2183
|
+
this.streamState?.toolCalls?.map((toolCall) => toolCall.id) ?? []
|
|
2184
|
+
);
|
|
2185
|
+
const messagesToInsert = [];
|
|
1069
2186
|
const toolCallsHidden = {};
|
|
1070
2187
|
if (this.streamState?.toolResults) {
|
|
1071
2188
|
for (const [id, result] of this.streamState.toolResults) {
|
|
@@ -1075,7 +2192,12 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt,
|
|
|
1075
2192
|
}
|
|
1076
2193
|
}
|
|
1077
2194
|
for (const msg of chunk.messages) {
|
|
1078
|
-
if (msg.role === "assistant") {
|
|
2195
|
+
if (msg.role === "assistant" && !msg.tool_calls?.length) {
|
|
2196
|
+
continue;
|
|
2197
|
+
}
|
|
2198
|
+
if (msg.role === "assistant" && msg.tool_calls?.length && msg.tool_calls.every(
|
|
2199
|
+
(toolCall) => currentStreamToolCallIds.has(toolCall.id)
|
|
2200
|
+
)) {
|
|
1079
2201
|
continue;
|
|
1080
2202
|
}
|
|
1081
2203
|
let metadata;
|
|
@@ -1091,7 +2213,60 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt,
|
|
|
1091
2213
|
createdAt: /* @__PURE__ */ new Date(),
|
|
1092
2214
|
metadata
|
|
1093
2215
|
};
|
|
1094
|
-
|
|
2216
|
+
messagesToInsert.push(message);
|
|
2217
|
+
}
|
|
2218
|
+
if (messagesToInsert.length > 0) {
|
|
2219
|
+
const currentMessages = this.state.messages;
|
|
2220
|
+
const currentStreamIndex = this.streamState ? currentMessages.findIndex(
|
|
2221
|
+
(message) => message.id === this.streamState.messageId
|
|
2222
|
+
) : -1;
|
|
2223
|
+
if (currentStreamIndex === -1) {
|
|
2224
|
+
this.state.setMessages([...currentMessages, ...messagesToInsert]);
|
|
2225
|
+
} else {
|
|
2226
|
+
this.state.setMessages([
|
|
2227
|
+
...currentMessages.slice(0, currentStreamIndex),
|
|
2228
|
+
...messagesToInsert,
|
|
2229
|
+
...currentMessages.slice(currentStreamIndex)
|
|
2230
|
+
]);
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
this.debug("requiresAction check", {
|
|
2234
|
+
requiresAction: chunk.requiresAction,
|
|
2235
|
+
toolCallsEmitted,
|
|
2236
|
+
updatedMessageToolCallsCount: updatedMessage.toolCalls?.length ?? 0,
|
|
2237
|
+
messagesToInsertCount: messagesToInsert.length
|
|
2238
|
+
});
|
|
2239
|
+
if (chunk.requiresAction && !toolCallsEmitted) {
|
|
2240
|
+
let clientToolCalls = updatedMessage.toolCalls;
|
|
2241
|
+
if (!clientToolCalls?.length && messagesToInsert.length > 0) {
|
|
2242
|
+
for (let i = messagesToInsert.length - 1; i >= 0; i--) {
|
|
2243
|
+
const m = messagesToInsert[i];
|
|
2244
|
+
if (m.role === "assistant" && m.toolCalls?.length) {
|
|
2245
|
+
clientToolCalls = m.toolCalls;
|
|
2246
|
+
this.debug("clientToolCalls from messagesToInsert", {
|
|
2247
|
+
index: i,
|
|
2248
|
+
count: clientToolCalls?.length
|
|
2249
|
+
});
|
|
2250
|
+
break;
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
if (clientToolCalls?.length) {
|
|
2255
|
+
toolCallsEmitted = true;
|
|
2256
|
+
this.debug("emit toolCalls (normal done path)", {
|
|
2257
|
+
count: clientToolCalls.length,
|
|
2258
|
+
names: clientToolCalls.map((tc) => tc.function?.name ?? tc.name)
|
|
2259
|
+
});
|
|
2260
|
+
this.emit("toolCalls", { toolCalls: clientToolCalls });
|
|
2261
|
+
} else {
|
|
2262
|
+
this.debug("requiresAction=true but no clientToolCalls found", {
|
|
2263
|
+
updatedMessageToolCalls: updatedMessage.toolCalls,
|
|
2264
|
+
messagesToInsert: messagesToInsert.map((m) => ({
|
|
2265
|
+
role: m.role,
|
|
2266
|
+
hasToolCalls: !!m.toolCalls?.length
|
|
2267
|
+
}))
|
|
2268
|
+
});
|
|
2269
|
+
}
|
|
1095
2270
|
}
|
|
1096
2271
|
}
|
|
1097
2272
|
break;
|
|
@@ -1116,15 +2291,22 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt,
|
|
|
1116
2291
|
toolCallsHidden
|
|
1117
2292
|
};
|
|
1118
2293
|
}
|
|
1119
|
-
this.state.updateMessageById(
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
2294
|
+
this.state.updateMessageById(this.streamState.messageId, (existing) => ({
|
|
2295
|
+
...finalMessage,
|
|
2296
|
+
...existing.parentId !== void 0 ? { parentId: existing.parentId } : {},
|
|
2297
|
+
...existing.childrenIds !== void 0 ? { childrenIds: existing.childrenIds } : {}
|
|
2298
|
+
}));
|
|
1123
2299
|
if (!finalMessage.content && (!finalMessage.toolCalls || finalMessage.toolCalls.length === 0)) {
|
|
1124
2300
|
this.debug("warning", "Empty response - no content and no tool calls");
|
|
1125
2301
|
}
|
|
1126
2302
|
}
|
|
1127
|
-
this.callbacks.onMessagesChange?.(this.
|
|
2303
|
+
this.callbacks.onMessagesChange?.(this._allMessages());
|
|
2304
|
+
this.debugGroupEnd();
|
|
2305
|
+
this.debug("stream end", {
|
|
2306
|
+
toolCallsEmitted,
|
|
2307
|
+
totalChunks: chunkCount,
|
|
2308
|
+
messagesInState: this.state.messages.length
|
|
2309
|
+
});
|
|
1128
2310
|
if (!toolCallsEmitted) {
|
|
1129
2311
|
this.state.status = "ready";
|
|
1130
2312
|
this.callbacks.onStatusChange?.("ready");
|
|
@@ -1145,6 +2327,7 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt,
|
|
|
1145
2327
|
}
|
|
1146
2328
|
}
|
|
1147
2329
|
}
|
|
2330
|
+
let currentParentId = this.state.messages.length > 0 ? this.state.messages[this.state.messages.length - 1].id : void 0;
|
|
1148
2331
|
for (const msg of response.messages ?? []) {
|
|
1149
2332
|
let metadata;
|
|
1150
2333
|
if (msg.role === "assistant" && msg.tool_calls && toolCallHiddenMap.size > 0) {
|
|
@@ -1167,11 +2350,15 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt,
|
|
|
1167
2350
|
// CRITICAL: Preserve toolCallId for tool messages (fixes Anthropic API errors)
|
|
1168
2351
|
toolCallId: msg.tool_call_id,
|
|
1169
2352
|
createdAt: /* @__PURE__ */ new Date(),
|
|
1170
|
-
metadata
|
|
2353
|
+
metadata,
|
|
2354
|
+
// Preserve branch tree structure: each message is a child of the
|
|
2355
|
+
// current leaf so the tree is not corrupted for non-streaming mode.
|
|
2356
|
+
...currentParentId !== void 0 ? { parentId: currentParentId } : {}
|
|
1171
2357
|
};
|
|
1172
2358
|
this.state.pushMessage(message);
|
|
2359
|
+
currentParentId = message.id;
|
|
1173
2360
|
}
|
|
1174
|
-
this.callbacks.onMessagesChange?.(this.
|
|
2361
|
+
this.callbacks.onMessagesChange?.(this._allMessages());
|
|
1175
2362
|
const hasToolCalls = response.requiresAction && this.state.messages.length > 0 && this.state.messages[this.state.messages.length - 1]?.toolCalls?.length;
|
|
1176
2363
|
if (hasToolCalls) {
|
|
1177
2364
|
const lastMessage = this.state.messages[this.state.messages.length - 1];
|
|
@@ -1194,14 +2381,25 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt,
|
|
|
1194
2381
|
this.callbacks.onStatusChange?.("error");
|
|
1195
2382
|
this.emit("error", { error });
|
|
1196
2383
|
}
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
2384
|
+
get log() {
|
|
2385
|
+
if (!this._log) {
|
|
2386
|
+
this._log = createLogger("streaming", () => this.config.debug ?? false);
|
|
2387
|
+
}
|
|
2388
|
+
return this._log;
|
|
2389
|
+
}
|
|
1200
2390
|
debug(action, data) {
|
|
1201
|
-
|
|
1202
|
-
|
|
2391
|
+
this.log(action, data);
|
|
2392
|
+
}
|
|
2393
|
+
debugGroup(label, collapsed = true) {
|
|
2394
|
+
if (collapsed) {
|
|
2395
|
+
this.log.groupCollapsed(label);
|
|
2396
|
+
} else {
|
|
2397
|
+
this.log.group(label);
|
|
1203
2398
|
}
|
|
1204
2399
|
}
|
|
2400
|
+
debugGroupEnd() {
|
|
2401
|
+
this.log.groupEnd();
|
|
2402
|
+
}
|
|
1205
2403
|
/**
|
|
1206
2404
|
* Type guard for async iterable
|
|
1207
2405
|
*/
|
|
@@ -1412,19 +2610,18 @@ var AbstractAgentLoop = class {
|
|
|
1412
2610
|
this._isCancelled = false;
|
|
1413
2611
|
this._isProcessing = true;
|
|
1414
2612
|
this.setIteration(this._iteration + 1);
|
|
1415
|
-
const results =
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
}
|
|
2613
|
+
const results = await Promise.all(
|
|
2614
|
+
toolCalls.map((toolCall) => {
|
|
2615
|
+
if (this._isCancelled || this.abortController.signal.aborted) {
|
|
2616
|
+
return Promise.resolve({
|
|
2617
|
+
toolCallId: toolCall.id,
|
|
2618
|
+
success: false,
|
|
2619
|
+
error: "Tool execution cancelled"
|
|
2620
|
+
});
|
|
2621
|
+
}
|
|
2622
|
+
return this.executeSingleTool(toolCall);
|
|
2623
|
+
})
|
|
2624
|
+
);
|
|
1428
2625
|
this._isProcessing = false;
|
|
1429
2626
|
return results;
|
|
1430
2627
|
}
|
|
@@ -1742,6 +2939,7 @@ var ChatWithTools = class {
|
|
|
1742
2939
|
streaming: config.streaming,
|
|
1743
2940
|
headers: config.headers,
|
|
1744
2941
|
body: config.body,
|
|
2942
|
+
optimization: config.optimization,
|
|
1745
2943
|
threadId: config.threadId,
|
|
1746
2944
|
debug: config.debug,
|
|
1747
2945
|
initialMessages: config.initialMessages,
|
|
@@ -1756,6 +2954,7 @@ var ChatWithTools = class {
|
|
|
1756
2954
|
onMessageFinish: callbacks.onMessageFinish,
|
|
1757
2955
|
onToolCalls: callbacks.onToolCalls,
|
|
1758
2956
|
onFinish: callbacks.onFinish,
|
|
2957
|
+
onContextUsageChange: callbacks.onContextUsageChange,
|
|
1759
2958
|
// Server-side tool callbacks - track in agentLoop for UI display
|
|
1760
2959
|
// IMPORTANT: Only track tools that are NOT registered client-side
|
|
1761
2960
|
// Client-side tools are tracked via executeToolCalls() path
|
|
@@ -1923,14 +3122,17 @@ var ChatWithTools = class {
|
|
|
1923
3122
|
/**
|
|
1924
3123
|
* Send a message
|
|
1925
3124
|
* Returns false if a request is already in progress
|
|
3125
|
+
*
|
|
3126
|
+
* @param options.editMessageId - Edit flow: new message branches from the
|
|
3127
|
+
* same parent as this message ID
|
|
1926
3128
|
*/
|
|
1927
|
-
async sendMessage(content, attachments) {
|
|
3129
|
+
async sendMessage(content, attachments, options) {
|
|
1928
3130
|
if (this.isLoading) {
|
|
1929
3131
|
this.debug("sendMessage blocked - request already in progress");
|
|
1930
3132
|
return false;
|
|
1931
3133
|
}
|
|
1932
3134
|
this.agentLoop.resetIterations();
|
|
1933
|
-
return await this.chat.sendMessage(content, attachments);
|
|
3135
|
+
return await this.chat.sendMessage(content, attachments, options);
|
|
1934
3136
|
}
|
|
1935
3137
|
/**
|
|
1936
3138
|
* Stop generation and cancel any running tools
|
|
@@ -1965,6 +3167,25 @@ var ChatWithTools = class {
|
|
|
1965
3167
|
setTools(tools) {
|
|
1966
3168
|
this.chat.setTools(tools);
|
|
1967
3169
|
}
|
|
3170
|
+
/**
|
|
3171
|
+
* Update prompt/tool optimization controls.
|
|
3172
|
+
*/
|
|
3173
|
+
setOptimizationConfig(config) {
|
|
3174
|
+
this.config.optimization = config;
|
|
3175
|
+
this.chat.setOptimizationConfig(config);
|
|
3176
|
+
}
|
|
3177
|
+
/**
|
|
3178
|
+
* Set the active tool profile used for request-time tool selection.
|
|
3179
|
+
*/
|
|
3180
|
+
setToolProfile(profile) {
|
|
3181
|
+
this.chat.setToolProfile(profile);
|
|
3182
|
+
}
|
|
3183
|
+
/**
|
|
3184
|
+
* Get the most recent prompt context usage snapshot.
|
|
3185
|
+
*/
|
|
3186
|
+
getContextUsage() {
|
|
3187
|
+
return this.chat.getContextUsage();
|
|
3188
|
+
}
|
|
1968
3189
|
/**
|
|
1969
3190
|
* Set dynamic context (from useAIContext hook)
|
|
1970
3191
|
*/
|
|
@@ -1998,6 +3219,15 @@ var ChatWithTools = class {
|
|
|
1998
3219
|
setBody(body) {
|
|
1999
3220
|
this.chat.setBody(body);
|
|
2000
3221
|
}
|
|
3222
|
+
setRequestMessageTransform(fn) {
|
|
3223
|
+
this.chat.setRequestMessageTransform(fn);
|
|
3224
|
+
}
|
|
3225
|
+
/**
|
|
3226
|
+
* Set inline skills (forwarded to underlying chat instance)
|
|
3227
|
+
*/
|
|
3228
|
+
setInlineSkills(skills) {
|
|
3229
|
+
this.chat.setInlineSkills(skills);
|
|
3230
|
+
}
|
|
2001
3231
|
// ============================================
|
|
2002
3232
|
// Tool Registration
|
|
2003
3233
|
// ============================================
|
|
@@ -2074,16 +3304,307 @@ var ChatWithTools = class {
|
|
|
2074
3304
|
// Private
|
|
2075
3305
|
// ============================================
|
|
2076
3306
|
debug(message, ...args) {
|
|
2077
|
-
|
|
2078
|
-
|
|
3307
|
+
createLogger("tools", () => this.config.debug ?? false)(
|
|
3308
|
+
message,
|
|
3309
|
+
args.length === 1 ? args[0] : args.length > 1 ? args : void 0
|
|
3310
|
+
);
|
|
3311
|
+
}
|
|
3312
|
+
};
|
|
3313
|
+
|
|
3314
|
+
// src/chat/branching/MessageTree.ts
|
|
3315
|
+
var _MessageTree = class _MessageTree {
|
|
3316
|
+
constructor(messages) {
|
|
3317
|
+
/** All messages by ID */
|
|
3318
|
+
this.nodeMap = /* @__PURE__ */ new Map();
|
|
3319
|
+
/** parentKey → ordered list of child IDs (insertion order = oldest-first) */
|
|
3320
|
+
this.childrenOf = /* @__PURE__ */ new Map();
|
|
3321
|
+
/** parentKey → currently-active child ID */
|
|
3322
|
+
this.activeChildMap = /* @__PURE__ */ new Map();
|
|
3323
|
+
/** Current leaf message ID (tip of the active path) */
|
|
3324
|
+
this._currentLeafId = null;
|
|
3325
|
+
/** Cached visible messages — invalidated on every mutation */
|
|
3326
|
+
this._visibleCache = null;
|
|
3327
|
+
if (messages?.length) {
|
|
3328
|
+
this._buildFromMessages(messages);
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3331
|
+
// ============================================
|
|
3332
|
+
// Static Migration Helpers
|
|
3333
|
+
// ============================================
|
|
3334
|
+
/**
|
|
3335
|
+
* Convert a legacy flat array (no parentId) to a tree-linked array.
|
|
3336
|
+
*
|
|
3337
|
+
* Rules:
|
|
3338
|
+
* - Tool messages get parentId = the owning assistant message's id
|
|
3339
|
+
* (matched via toolCallId → toolCall.id).
|
|
3340
|
+
* - All other messages get parentId of the previous non-tool message
|
|
3341
|
+
* (or null for the first message).
|
|
3342
|
+
*
|
|
3343
|
+
* Returns a new array with parentId/childrenIds filled in.
|
|
3344
|
+
* Does NOT mutate the original messages.
|
|
3345
|
+
*/
|
|
3346
|
+
static fromFlatArray(messages) {
|
|
3347
|
+
if (messages.length === 0) return messages;
|
|
3348
|
+
const alreadyLinked = messages.some((m) => m.parentId !== void 0);
|
|
3349
|
+
if (alreadyLinked) return messages;
|
|
3350
|
+
const result = [];
|
|
3351
|
+
let prevNonToolId = null;
|
|
3352
|
+
const assistantById = /* @__PURE__ */ new Map();
|
|
3353
|
+
for (const msg of messages) {
|
|
3354
|
+
if (msg.role === "assistant") {
|
|
3355
|
+
assistantById.set(msg.id, msg);
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
for (const msg of messages) {
|
|
3359
|
+
if (msg.role === "tool" && msg.toolCallId) {
|
|
3360
|
+
let ownerAssistantId = null;
|
|
3361
|
+
for (const [, assistant] of assistantById) {
|
|
3362
|
+
if (assistant.toolCalls?.some((tc) => tc.id === msg.toolCallId)) {
|
|
3363
|
+
ownerAssistantId = assistant.id;
|
|
3364
|
+
break;
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3367
|
+
result.push({
|
|
3368
|
+
...msg,
|
|
3369
|
+
parentId: ownerAssistantId ?? prevNonToolId,
|
|
3370
|
+
childrenIds: []
|
|
3371
|
+
});
|
|
3372
|
+
} else {
|
|
3373
|
+
result.push({
|
|
3374
|
+
...msg,
|
|
3375
|
+
parentId: prevNonToolId,
|
|
3376
|
+
childrenIds: []
|
|
3377
|
+
});
|
|
3378
|
+
prevNonToolId = msg.id;
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
const childrenMap = /* @__PURE__ */ new Map();
|
|
3382
|
+
for (const msg of result) {
|
|
3383
|
+
const parentKey = msg.parentId == null ? _MessageTree.ROOT_KEY : msg.parentId;
|
|
3384
|
+
if (!childrenMap.has(parentKey)) {
|
|
3385
|
+
childrenMap.set(parentKey, []);
|
|
3386
|
+
}
|
|
3387
|
+
childrenMap.get(parentKey).push(msg.id);
|
|
3388
|
+
}
|
|
3389
|
+
return result.map((msg) => ({
|
|
3390
|
+
...msg,
|
|
3391
|
+
childrenIds: childrenMap.get(msg.id) ?? []
|
|
3392
|
+
}));
|
|
3393
|
+
}
|
|
3394
|
+
// ============================================
|
|
3395
|
+
// Core Queries
|
|
3396
|
+
// ============================================
|
|
3397
|
+
/**
|
|
3398
|
+
* Returns the visible path (root → current leaf) — what the UI renders
|
|
3399
|
+
* and what gets sent to the API.
|
|
3400
|
+
*
|
|
3401
|
+
* Backward-compat: if NO message has parentId set (all undefined),
|
|
3402
|
+
* falls back to insertion order (legacy linear mode).
|
|
3403
|
+
*/
|
|
3404
|
+
getVisibleMessages() {
|
|
3405
|
+
if (this._visibleCache !== null) return this._visibleCache;
|
|
3406
|
+
if (this.nodeMap.size === 0) {
|
|
3407
|
+
this._visibleCache = [];
|
|
3408
|
+
return this._visibleCache;
|
|
3409
|
+
}
|
|
3410
|
+
const hasTreeStructure = Array.from(this.nodeMap.values()).some(
|
|
3411
|
+
(m) => m.parentId !== void 0
|
|
3412
|
+
);
|
|
3413
|
+
this._visibleCache = hasTreeStructure ? this._getActivePath().map((id) => this.nodeMap.get(id)) : Array.from(this.nodeMap.values());
|
|
3414
|
+
return this._visibleCache;
|
|
3415
|
+
}
|
|
3416
|
+
_invalidateCache() {
|
|
3417
|
+
this._visibleCache = null;
|
|
3418
|
+
}
|
|
3419
|
+
/**
|
|
3420
|
+
* Returns ALL messages across every branch (for persistence / ThreadManager).
|
|
3421
|
+
*/
|
|
3422
|
+
getAllMessages() {
|
|
3423
|
+
return Array.from(this.nodeMap.values());
|
|
3424
|
+
}
|
|
3425
|
+
/**
|
|
3426
|
+
* Branch navigation info for the UI navigator.
|
|
3427
|
+
* Returns null if the message has no siblings (only child).
|
|
3428
|
+
*/
|
|
3429
|
+
getBranchInfo(messageId) {
|
|
3430
|
+
const msg = this.nodeMap.get(messageId);
|
|
3431
|
+
if (!msg) return null;
|
|
3432
|
+
const parentKey = this._parentKey(msg.parentId);
|
|
3433
|
+
const siblings = this.childrenOf.get(parentKey) ?? [];
|
|
3434
|
+
if (siblings.length <= 1) return null;
|
|
3435
|
+
const siblingIndex = siblings.indexOf(messageId);
|
|
3436
|
+
return {
|
|
3437
|
+
siblingIndex,
|
|
3438
|
+
totalSiblings: siblings.length,
|
|
3439
|
+
siblingIds: [...siblings],
|
|
3440
|
+
hasPrevious: siblingIndex > 0,
|
|
3441
|
+
hasNext: siblingIndex < siblings.length - 1
|
|
3442
|
+
};
|
|
3443
|
+
}
|
|
3444
|
+
get currentLeafId() {
|
|
3445
|
+
return this._currentLeafId;
|
|
3446
|
+
}
|
|
3447
|
+
get hasBranches() {
|
|
3448
|
+
for (const children of this.childrenOf.values()) {
|
|
3449
|
+
if (children.length > 1) return true;
|
|
3450
|
+
}
|
|
3451
|
+
return false;
|
|
3452
|
+
}
|
|
3453
|
+
// ============================================
|
|
3454
|
+
// Mutations
|
|
3455
|
+
// ============================================
|
|
3456
|
+
/**
|
|
3457
|
+
* Insert a new message.
|
|
3458
|
+
* - Updates childrenOf and nodeMap.
|
|
3459
|
+
* - New branch becomes active (activeChildMap updated).
|
|
3460
|
+
* - Updates current leaf.
|
|
3461
|
+
*/
|
|
3462
|
+
addMessage(message) {
|
|
3463
|
+
this.nodeMap.set(message.id, message);
|
|
3464
|
+
const parentKey = this._parentKey(message.parentId);
|
|
3465
|
+
if (!this.childrenOf.has(parentKey)) {
|
|
3466
|
+
this.childrenOf.set(parentKey, []);
|
|
3467
|
+
}
|
|
3468
|
+
const siblings = this.childrenOf.get(parentKey);
|
|
3469
|
+
if (!siblings.includes(message.id)) {
|
|
3470
|
+
siblings.push(message.id);
|
|
3471
|
+
}
|
|
3472
|
+
this.activeChildMap.set(parentKey, message.id);
|
|
3473
|
+
this._currentLeafId = this._walkToLeaf(message.id);
|
|
3474
|
+
this._invalidateCache();
|
|
3475
|
+
return message;
|
|
3476
|
+
}
|
|
3477
|
+
/**
|
|
3478
|
+
* Navigate: make messageId the active child at its parent fork,
|
|
3479
|
+
* then walk to its leaf and update currentLeafId.
|
|
3480
|
+
*/
|
|
3481
|
+
switchBranch(messageId) {
|
|
3482
|
+
const msg = this.nodeMap.get(messageId);
|
|
3483
|
+
if (!msg) return;
|
|
3484
|
+
const parentKey = this._parentKey(msg.parentId);
|
|
3485
|
+
this.activeChildMap.set(parentKey, messageId);
|
|
3486
|
+
this._currentLeafId = this._walkToLeaf(messageId);
|
|
3487
|
+
this._invalidateCache();
|
|
3488
|
+
}
|
|
3489
|
+
/**
|
|
3490
|
+
* Update message content in-place (streaming updates).
|
|
3491
|
+
* No tree structure change.
|
|
3492
|
+
*/
|
|
3493
|
+
updateMessage(id, updater) {
|
|
3494
|
+
const existing = this.nodeMap.get(id);
|
|
3495
|
+
if (!existing) return false;
|
|
3496
|
+
this.nodeMap.set(id, updater(existing));
|
|
3497
|
+
this._invalidateCache();
|
|
3498
|
+
return true;
|
|
3499
|
+
}
|
|
3500
|
+
/**
|
|
3501
|
+
* Set current leaf explicitly.
|
|
3502
|
+
* Used by regenerate() to rewind the active path before pushing a new message.
|
|
3503
|
+
*/
|
|
3504
|
+
setCurrentLeaf(leafId) {
|
|
3505
|
+
this._currentLeafId = leafId;
|
|
3506
|
+
if (leafId === null) return;
|
|
3507
|
+
const msg = this.nodeMap.get(leafId);
|
|
3508
|
+
if (!msg) return;
|
|
3509
|
+
let current = msg;
|
|
3510
|
+
while (current) {
|
|
3511
|
+
const parentKey = this._parentKey(current.parentId);
|
|
3512
|
+
this.activeChildMap.set(parentKey, current.id);
|
|
3513
|
+
if (current.parentId == null || current.parentId === void 0) break;
|
|
3514
|
+
current = this.nodeMap.get(current.parentId);
|
|
3515
|
+
}
|
|
3516
|
+
this._invalidateCache();
|
|
3517
|
+
}
|
|
3518
|
+
/**
|
|
3519
|
+
* Rebuild entire tree from a message array.
|
|
3520
|
+
* Used by setMessages().
|
|
3521
|
+
*/
|
|
3522
|
+
reset(messages) {
|
|
3523
|
+
this.nodeMap.clear();
|
|
3524
|
+
this.childrenOf.clear();
|
|
3525
|
+
this.activeChildMap.clear();
|
|
3526
|
+
this._currentLeafId = null;
|
|
3527
|
+
this._invalidateCache();
|
|
3528
|
+
if (messages.length > 0) {
|
|
3529
|
+
this._buildFromMessages(messages);
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
// ============================================
|
|
3533
|
+
// Private Helpers
|
|
3534
|
+
// ============================================
|
|
3535
|
+
_buildFromMessages(messages) {
|
|
3536
|
+
const linked = messages.some((m) => m.parentId !== void 0) ? messages : _MessageTree.fromFlatArray(messages);
|
|
3537
|
+
for (const msg of linked) {
|
|
3538
|
+
this.nodeMap.set(msg.id, msg);
|
|
3539
|
+
const parentKey = this._parentKey(msg.parentId);
|
|
3540
|
+
if (!this.childrenOf.has(parentKey)) {
|
|
3541
|
+
this.childrenOf.set(parentKey, []);
|
|
3542
|
+
}
|
|
3543
|
+
const siblings = this.childrenOf.get(parentKey);
|
|
3544
|
+
if (!siblings.includes(msg.id)) {
|
|
3545
|
+
siblings.push(msg.id);
|
|
3546
|
+
}
|
|
3547
|
+
}
|
|
3548
|
+
for (const [parentKey, children] of this.childrenOf) {
|
|
3549
|
+
if (children.length > 0) {
|
|
3550
|
+
this.activeChildMap.set(parentKey, children[children.length - 1]);
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
const path = this._getActivePath();
|
|
3554
|
+
this._currentLeafId = path.length > 0 ? path[path.length - 1] : null;
|
|
3555
|
+
}
|
|
3556
|
+
_parentKey(parentId) {
|
|
3557
|
+
if (parentId == null || parentId === void 0) {
|
|
3558
|
+
return _MessageTree.ROOT_KEY;
|
|
3559
|
+
}
|
|
3560
|
+
return parentId;
|
|
3561
|
+
}
|
|
3562
|
+
/**
|
|
3563
|
+
* Walk forward from a message along active children to find the leaf.
|
|
3564
|
+
*/
|
|
3565
|
+
_walkToLeaf(fromId) {
|
|
3566
|
+
let current = fromId;
|
|
3567
|
+
while (true) {
|
|
3568
|
+
const children = this.childrenOf.get(current);
|
|
3569
|
+
if (!children || children.length === 0) break;
|
|
3570
|
+
const activeChild = this.activeChildMap.get(current);
|
|
3571
|
+
if (!activeChild) break;
|
|
3572
|
+
if (!this.nodeMap.has(activeChild)) break;
|
|
3573
|
+
current = activeChild;
|
|
3574
|
+
}
|
|
3575
|
+
return current;
|
|
3576
|
+
}
|
|
3577
|
+
/**
|
|
3578
|
+
* Walk the active path from root to the current leaf.
|
|
3579
|
+
*/
|
|
3580
|
+
_getActivePath() {
|
|
3581
|
+
const path = [];
|
|
3582
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3583
|
+
const rootChildren = this.childrenOf.get(_MessageTree.ROOT_KEY) ?? [];
|
|
3584
|
+
if (rootChildren.length === 0) return path;
|
|
3585
|
+
let activeId = this.activeChildMap.get(_MessageTree.ROOT_KEY);
|
|
3586
|
+
if (!activeId) {
|
|
3587
|
+
activeId = rootChildren[rootChildren.length - 1];
|
|
3588
|
+
}
|
|
3589
|
+
let current = activeId;
|
|
3590
|
+
while (current && !visited.has(current)) {
|
|
3591
|
+
if (!this.nodeMap.has(current)) break;
|
|
3592
|
+
visited.add(current);
|
|
3593
|
+
path.push(current);
|
|
3594
|
+
const activeChild = this.activeChildMap.get(current);
|
|
3595
|
+
if (!activeChild || !this.nodeMap.has(activeChild)) break;
|
|
3596
|
+
current = activeChild;
|
|
2079
3597
|
}
|
|
3598
|
+
return path;
|
|
2080
3599
|
}
|
|
2081
3600
|
};
|
|
3601
|
+
/** Sentinel key used for root-level messages (parentId === null) */
|
|
3602
|
+
_MessageTree.ROOT_KEY = "__root__";
|
|
3603
|
+
var MessageTree = _MessageTree;
|
|
2082
3604
|
|
|
2083
3605
|
// src/react/internal/ReactChatState.ts
|
|
2084
3606
|
var ReactChatState = class {
|
|
2085
3607
|
constructor(initialMessages) {
|
|
2086
|
-
this._messages = [];
|
|
2087
3608
|
this._status = "ready";
|
|
2088
3609
|
this._error = void 0;
|
|
2089
3610
|
// Callbacks for React subscriptions (useSyncExternalStore)
|
|
@@ -2109,15 +3630,19 @@ var ReactChatState = class {
|
|
|
2109
3630
|
this.subscribers.delete(callback);
|
|
2110
3631
|
};
|
|
2111
3632
|
};
|
|
2112
|
-
|
|
2113
|
-
this._messages = initialMessages;
|
|
2114
|
-
}
|
|
3633
|
+
this.tree = new MessageTree(initialMessages);
|
|
2115
3634
|
}
|
|
2116
3635
|
// ============================================
|
|
2117
|
-
// Getters
|
|
3636
|
+
// Getters — visible path only
|
|
2118
3637
|
// ============================================
|
|
3638
|
+
/**
|
|
3639
|
+
* Returns the VISIBLE PATH (active branch) — what the UI renders
|
|
3640
|
+
* and what gets sent to the API.
|
|
3641
|
+
*
|
|
3642
|
+
* For all messages across all branches, use getAllMessages().
|
|
3643
|
+
*/
|
|
2119
3644
|
get messages() {
|
|
2120
|
-
return this.
|
|
3645
|
+
return this.tree.getVisibleMessages();
|
|
2121
3646
|
}
|
|
2122
3647
|
get status() {
|
|
2123
3648
|
return this._status;
|
|
@@ -2129,7 +3654,7 @@ var ReactChatState = class {
|
|
|
2129
3654
|
// Setters (trigger reactivity)
|
|
2130
3655
|
// ============================================
|
|
2131
3656
|
set messages(value) {
|
|
2132
|
-
this.
|
|
3657
|
+
this.tree.reset(value);
|
|
2133
3658
|
this.notify();
|
|
2134
3659
|
}
|
|
2135
3660
|
set status(value) {
|
|
@@ -2144,61 +3669,100 @@ var ReactChatState = class {
|
|
|
2144
3669
|
// Mutations
|
|
2145
3670
|
// ============================================
|
|
2146
3671
|
pushMessage(message) {
|
|
2147
|
-
this.
|
|
3672
|
+
this.tree.addMessage(message);
|
|
2148
3673
|
this.notify();
|
|
2149
3674
|
}
|
|
2150
3675
|
popMessage() {
|
|
2151
|
-
|
|
3676
|
+
const leafId = this.tree.currentLeafId;
|
|
3677
|
+
if (!leafId) return;
|
|
3678
|
+
const allMessages = this.tree.getAllMessages().filter((m) => m.id !== leafId);
|
|
3679
|
+
const leaf = this.tree.getAllMessages().find((m) => m.id === leafId);
|
|
3680
|
+
const newLeafId = leaf && leaf.parentId !== void 0 && leaf.parentId !== null ? leaf.parentId : null;
|
|
3681
|
+
this.tree.reset(allMessages);
|
|
3682
|
+
if (newLeafId) {
|
|
3683
|
+
this.tree.setCurrentLeaf(newLeafId);
|
|
3684
|
+
}
|
|
2152
3685
|
this.notify();
|
|
2153
3686
|
}
|
|
2154
3687
|
replaceMessage(index, message) {
|
|
2155
|
-
|
|
3688
|
+
const visible = this.tree.getVisibleMessages();
|
|
3689
|
+
const target = visible[index];
|
|
3690
|
+
if (!target) return;
|
|
3691
|
+
this.tree.updateMessage(target.id, () => message);
|
|
2156
3692
|
this.notify();
|
|
2157
3693
|
}
|
|
2158
3694
|
updateLastMessage(updater) {
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
this._messages = [
|
|
2163
|
-
...this._messages.slice(0, lastIndex),
|
|
2164
|
-
updater(lastMessage)
|
|
2165
|
-
];
|
|
3695
|
+
const leafId = this.tree.currentLeafId;
|
|
3696
|
+
if (!leafId) return;
|
|
3697
|
+
this.tree.updateMessage(leafId, updater);
|
|
2166
3698
|
this.notify();
|
|
2167
3699
|
}
|
|
2168
3700
|
updateMessageById(id, updater) {
|
|
2169
|
-
const
|
|
2170
|
-
if (
|
|
2171
|
-
|
|
2172
|
-
(m, i) => i === index ? updater(m) : m
|
|
2173
|
-
);
|
|
2174
|
-
this.notify();
|
|
2175
|
-
return true;
|
|
3701
|
+
const updated = this.tree.updateMessage(id, updater);
|
|
3702
|
+
if (updated) this.notify();
|
|
3703
|
+
return updated;
|
|
2176
3704
|
}
|
|
2177
3705
|
setMessages(messages) {
|
|
2178
|
-
this.
|
|
3706
|
+
this.tree.reset(messages);
|
|
2179
3707
|
this.notify();
|
|
2180
3708
|
}
|
|
2181
3709
|
clearMessages() {
|
|
2182
|
-
this.
|
|
3710
|
+
this.tree.reset([]);
|
|
2183
3711
|
this.notify();
|
|
2184
3712
|
}
|
|
2185
3713
|
// ============================================
|
|
2186
|
-
//
|
|
3714
|
+
// Branching API
|
|
2187
3715
|
// ============================================
|
|
2188
|
-
notify() {
|
|
2189
|
-
this.subscribers.forEach((cb) => cb());
|
|
2190
|
-
}
|
|
2191
3716
|
/**
|
|
2192
|
-
*
|
|
3717
|
+
* Returns ALL messages across all branches.
|
|
3718
|
+
* Use this for persistence (ThreadManager save).
|
|
2193
3719
|
*/
|
|
2194
|
-
|
|
2195
|
-
this.
|
|
3720
|
+
getAllMessages() {
|
|
3721
|
+
return this.tree.getAllMessages();
|
|
2196
3722
|
}
|
|
2197
3723
|
/**
|
|
2198
|
-
*
|
|
2199
|
-
*
|
|
3724
|
+
* Get branch navigation info for a message.
|
|
3725
|
+
* Returns null if the message has no siblings.
|
|
2200
3726
|
*/
|
|
2201
|
-
|
|
3727
|
+
getBranchInfo(messageId) {
|
|
3728
|
+
return this.tree.getBranchInfo(messageId);
|
|
3729
|
+
}
|
|
3730
|
+
/**
|
|
3731
|
+
* Navigate to a sibling branch.
|
|
3732
|
+
* Triggers re-render via notify().
|
|
3733
|
+
*/
|
|
3734
|
+
switchBranch(messageId) {
|
|
3735
|
+
this.tree.switchBranch(messageId);
|
|
3736
|
+
this.notify();
|
|
3737
|
+
}
|
|
3738
|
+
/**
|
|
3739
|
+
* Set the current leaf (used by regenerate() to rewind active path).
|
|
3740
|
+
* Triggers re-render via notify().
|
|
3741
|
+
*/
|
|
3742
|
+
setCurrentLeaf(leafId) {
|
|
3743
|
+
this.tree.setCurrentLeaf(leafId);
|
|
3744
|
+
this.notify();
|
|
3745
|
+
}
|
|
3746
|
+
get hasBranches() {
|
|
3747
|
+
return this.tree.hasBranches;
|
|
3748
|
+
}
|
|
3749
|
+
// ============================================
|
|
3750
|
+
// Private Methods
|
|
3751
|
+
// ============================================
|
|
3752
|
+
notify() {
|
|
3753
|
+
this.subscribers.forEach((cb) => cb());
|
|
3754
|
+
}
|
|
3755
|
+
/**
|
|
3756
|
+
* Cleanup subscriptions
|
|
3757
|
+
*/
|
|
3758
|
+
dispose() {
|
|
3759
|
+
this.subscribers.clear();
|
|
3760
|
+
}
|
|
3761
|
+
/**
|
|
3762
|
+
* Revive after dispose (for React StrictMode compatibility)
|
|
3763
|
+
* Subscribers will be re-added automatically via useSyncExternalStore
|
|
3764
|
+
*/
|
|
3765
|
+
revive() {
|
|
2202
3766
|
}
|
|
2203
3767
|
};
|
|
2204
3768
|
function createReactChatState(initialMessages) {
|
|
@@ -2218,6 +3782,33 @@ var ReactChatWithTools = class extends ChatWithTools {
|
|
|
2218
3782
|
};
|
|
2219
3783
|
this.reactState = reactState;
|
|
2220
3784
|
}
|
|
3785
|
+
// ============================================
|
|
3786
|
+
// Branching API — pass-throughs to ReactChatState
|
|
3787
|
+
// ============================================
|
|
3788
|
+
/**
|
|
3789
|
+
* Navigate to a sibling branch.
|
|
3790
|
+
*/
|
|
3791
|
+
switchBranch(messageId) {
|
|
3792
|
+
this.reactState.switchBranch(messageId);
|
|
3793
|
+
}
|
|
3794
|
+
/**
|
|
3795
|
+
* Get branch navigation info for a message.
|
|
3796
|
+
*/
|
|
3797
|
+
getBranchInfo(messageId) {
|
|
3798
|
+
return this.reactState.getBranchInfo(messageId);
|
|
3799
|
+
}
|
|
3800
|
+
/**
|
|
3801
|
+
* Get all messages across all branches (for persistence).
|
|
3802
|
+
*/
|
|
3803
|
+
getAllMessages() {
|
|
3804
|
+
return this.reactState.getAllMessages();
|
|
3805
|
+
}
|
|
3806
|
+
/**
|
|
3807
|
+
* Whether any message has siblings (branching has occurred).
|
|
3808
|
+
*/
|
|
3809
|
+
get hasBranches() {
|
|
3810
|
+
return this.reactState.hasBranches;
|
|
3811
|
+
}
|
|
2221
3812
|
/**
|
|
2222
3813
|
* Dispose and cleanup
|
|
2223
3814
|
*/
|
|
@@ -2494,36 +4085,933 @@ function useMCPTools(config) {
|
|
|
2494
4085
|
source
|
|
2495
4086
|
]);
|
|
2496
4087
|
useEffect(() => {
|
|
2497
|
-
if (!autoRegister) {
|
|
2498
|
-
return;
|
|
2499
|
-
}
|
|
2500
|
-
for (const toolName of registeredToolsRef.current) {
|
|
2501
|
-
unregisterTool(toolName);
|
|
2502
|
-
}
|
|
2503
|
-
registeredToolsRef.current = [];
|
|
2504
|
-
if (mcpClient.isConnected && toolDefinitions.length > 0) {
|
|
2505
|
-
for (const tool2 of toolDefinitions) {
|
|
2506
|
-
registerTool(tool2);
|
|
2507
|
-
registeredToolsRef.current.push(tool2.name);
|
|
4088
|
+
if (!autoRegister) {
|
|
4089
|
+
return;
|
|
4090
|
+
}
|
|
4091
|
+
for (const toolName of registeredToolsRef.current) {
|
|
4092
|
+
unregisterTool(toolName);
|
|
4093
|
+
}
|
|
4094
|
+
registeredToolsRef.current = [];
|
|
4095
|
+
if (mcpClient.isConnected && toolDefinitions.length > 0) {
|
|
4096
|
+
for (const tool2 of toolDefinitions) {
|
|
4097
|
+
registerTool(tool2);
|
|
4098
|
+
registeredToolsRef.current.push(tool2.name);
|
|
4099
|
+
}
|
|
4100
|
+
}
|
|
4101
|
+
return () => {
|
|
4102
|
+
for (const toolName of registeredToolsRef.current) {
|
|
4103
|
+
unregisterTool(toolName);
|
|
4104
|
+
}
|
|
4105
|
+
registeredToolsRef.current = [];
|
|
4106
|
+
};
|
|
4107
|
+
}, [
|
|
4108
|
+
autoRegister,
|
|
4109
|
+
mcpClient.isConnected,
|
|
4110
|
+
toolDefinitions,
|
|
4111
|
+
registerTool,
|
|
4112
|
+
unregisterTool
|
|
4113
|
+
]);
|
|
4114
|
+
return {
|
|
4115
|
+
...mcpClient,
|
|
4116
|
+
toolDefinitions
|
|
4117
|
+
};
|
|
4118
|
+
}
|
|
4119
|
+
var defaultTokenUsage = {
|
|
4120
|
+
current: 0,
|
|
4121
|
+
max: 128e3,
|
|
4122
|
+
percentage: 0,
|
|
4123
|
+
isApproaching: false
|
|
4124
|
+
};
|
|
4125
|
+
var defaultCompactionState = {
|
|
4126
|
+
rollingSummary: null,
|
|
4127
|
+
lastCompactionAt: null,
|
|
4128
|
+
compactionCount: 0,
|
|
4129
|
+
totalTokensSaved: 0,
|
|
4130
|
+
workingMemory: [],
|
|
4131
|
+
displayMessageCount: 0,
|
|
4132
|
+
llmMessageCount: 0
|
|
4133
|
+
};
|
|
4134
|
+
var defaultMessageHistoryConfig = {
|
|
4135
|
+
strategy: "none",
|
|
4136
|
+
maxContextTokens: 128e3,
|
|
4137
|
+
reserveForResponse: 4096,
|
|
4138
|
+
compactionThreshold: 0.75,
|
|
4139
|
+
recentBuffer: 10,
|
|
4140
|
+
toolResultMaxChars: 1e4,
|
|
4141
|
+
persistSession: false,
|
|
4142
|
+
storageKey: "copilot-session"
|
|
4143
|
+
};
|
|
4144
|
+
var MessageHistoryContext = createContext({
|
|
4145
|
+
config: defaultMessageHistoryConfig,
|
|
4146
|
+
tokenUsage: defaultTokenUsage,
|
|
4147
|
+
compactionState: defaultCompactionState
|
|
4148
|
+
});
|
|
4149
|
+
function useMessageHistoryContext() {
|
|
4150
|
+
return useContext(MessageHistoryContext);
|
|
4151
|
+
}
|
|
4152
|
+
|
|
4153
|
+
// src/react/message-history/message-utils.ts
|
|
4154
|
+
function toDisplayMessage(msg) {
|
|
4155
|
+
return {
|
|
4156
|
+
...msg,
|
|
4157
|
+
timestamp: msg.createdAt instanceof Date ? msg.createdAt.getTime() : Date.now()
|
|
4158
|
+
};
|
|
4159
|
+
}
|
|
4160
|
+
function toLLMMessage(msg) {
|
|
4161
|
+
if (isCompactionMarker(msg)) {
|
|
4162
|
+
return {
|
|
4163
|
+
role: "system",
|
|
4164
|
+
content: `[Previous conversation summary]
|
|
4165
|
+
${msg.content}`
|
|
4166
|
+
};
|
|
4167
|
+
}
|
|
4168
|
+
const base = {
|
|
4169
|
+
role: msg.role,
|
|
4170
|
+
content: msg.content
|
|
4171
|
+
};
|
|
4172
|
+
if (msg.toolCalls?.length) {
|
|
4173
|
+
base.tool_calls = msg.toolCalls;
|
|
4174
|
+
}
|
|
4175
|
+
if (msg.toolCallId) {
|
|
4176
|
+
base.tool_call_id = msg.toolCallId;
|
|
4177
|
+
}
|
|
4178
|
+
return base;
|
|
4179
|
+
}
|
|
4180
|
+
function toLLMMessages(messages) {
|
|
4181
|
+
return messages.map(toLLMMessage);
|
|
4182
|
+
}
|
|
4183
|
+
function keepToolPairsAtomic(messages) {
|
|
4184
|
+
if (messages.length === 0) return messages;
|
|
4185
|
+
const firstIdx = messages.findIndex((msg, i) => {
|
|
4186
|
+
if (msg.role !== "assistant" || !msg.toolCalls?.length) return false;
|
|
4187
|
+
const toolCallIds = new Set(msg.toolCalls.map((tc) => tc.id));
|
|
4188
|
+
const resultIds = new Set(
|
|
4189
|
+
messages.slice(i + 1).filter((m) => m.role === "tool" && m.toolCallId).map((m) => m.toolCallId)
|
|
4190
|
+
);
|
|
4191
|
+
return [...toolCallIds].some((id) => !resultIds.has(id));
|
|
4192
|
+
});
|
|
4193
|
+
if (firstIdx === -1) return messages;
|
|
4194
|
+
return messages.slice(firstIdx);
|
|
4195
|
+
}
|
|
4196
|
+
function findSafeWindowStart(messages, desiredStart) {
|
|
4197
|
+
for (let i = desiredStart; i < messages.length; i++) {
|
|
4198
|
+
const msg = messages[i];
|
|
4199
|
+
if (msg.role === "user" || msg.role === "assistant" && !msg.toolCalls?.length) {
|
|
4200
|
+
return i;
|
|
4201
|
+
}
|
|
4202
|
+
}
|
|
4203
|
+
return desiredStart;
|
|
4204
|
+
}
|
|
4205
|
+
function isCompactionMarker(msg) {
|
|
4206
|
+
return msg.role === "system" && msg.type === "compaction-marker";
|
|
4207
|
+
}
|
|
4208
|
+
|
|
4209
|
+
// src/react/message-history/token-counter.ts
|
|
4210
|
+
function estimateMessageTokens2(msg) {
|
|
4211
|
+
let chars = msg.content?.length ?? 0;
|
|
4212
|
+
if (msg.tool_calls?.length) {
|
|
4213
|
+
for (const tc of msg.tool_calls) {
|
|
4214
|
+
chars += JSON.stringify(tc).length;
|
|
4215
|
+
}
|
|
4216
|
+
}
|
|
4217
|
+
return Math.ceil(chars / 3.5) + 4;
|
|
4218
|
+
}
|
|
4219
|
+
function estimateMessagesTokens(messages) {
|
|
4220
|
+
return messages.reduce((sum, msg) => sum + estimateMessageTokens2(msg), 0);
|
|
4221
|
+
}
|
|
4222
|
+
function estimateTokens2(messages, mode = "fast") {
|
|
4223
|
+
if (mode === "off") return 0;
|
|
4224
|
+
return estimateMessagesTokens(messages);
|
|
4225
|
+
}
|
|
4226
|
+
|
|
4227
|
+
// src/react/message-history/strategies/sliding-window.ts
|
|
4228
|
+
function applySlidingWindow(messages, options) {
|
|
4229
|
+
const { tokenBudget, recentBuffer } = options;
|
|
4230
|
+
if (messages.length === 0) return messages;
|
|
4231
|
+
const systemMessages = messages.filter(
|
|
4232
|
+
(m) => m.role === "system" || isCompactionMarker(m)
|
|
4233
|
+
);
|
|
4234
|
+
const conversationMessages = messages.filter(
|
|
4235
|
+
(m) => m.role !== "system" && !isCompactionMarker(m)
|
|
4236
|
+
);
|
|
4237
|
+
const systemTokens = estimateMessagesTokens(toLLMMessages(systemMessages));
|
|
4238
|
+
const remainingBudget = tokenBudget - systemTokens;
|
|
4239
|
+
if (conversationMessages.length === 0) return systemMessages;
|
|
4240
|
+
const recentStart = Math.max(0, conversationMessages.length - recentBuffer);
|
|
4241
|
+
const recent = conversationMessages.slice(recentStart);
|
|
4242
|
+
const older = conversationMessages.slice(0, recentStart);
|
|
4243
|
+
const allTokens = estimateMessagesTokens(toLLMMessages(conversationMessages));
|
|
4244
|
+
if (allTokens <= remainingBudget) {
|
|
4245
|
+
return messages;
|
|
4246
|
+
}
|
|
4247
|
+
const recentTokens = estimateMessagesTokens(toLLMMessages(recent));
|
|
4248
|
+
let available = remainingBudget - recentTokens;
|
|
4249
|
+
const included = [];
|
|
4250
|
+
for (let i = older.length - 1; i >= 0; i--) {
|
|
4251
|
+
const msgTokens = estimateMessagesTokens(toLLMMessages([older[i]]));
|
|
4252
|
+
if (available - msgTokens < 0) break;
|
|
4253
|
+
included.unshift(older[i]);
|
|
4254
|
+
available -= msgTokens;
|
|
4255
|
+
}
|
|
4256
|
+
const combined = [...included, ...recent];
|
|
4257
|
+
const safeStart = findSafeWindowStart(combined, 0);
|
|
4258
|
+
const safeWindow = combined.slice(safeStart);
|
|
4259
|
+
return [...systemMessages, ...safeWindow];
|
|
4260
|
+
}
|
|
4261
|
+
function truncateToolResults(messages, maxChars) {
|
|
4262
|
+
if (maxChars === 0) return messages;
|
|
4263
|
+
return messages.map((msg) => {
|
|
4264
|
+
if (msg.role !== "tool") return msg;
|
|
4265
|
+
if (!msg.content || msg.content.length <= maxChars) return msg;
|
|
4266
|
+
return {
|
|
4267
|
+
...msg,
|
|
4268
|
+
content: msg.content.slice(0, maxChars) + `
|
|
4269
|
+
[truncated \u2014 original ${msg.content.length} chars, limit ${maxChars}]`
|
|
4270
|
+
};
|
|
4271
|
+
});
|
|
4272
|
+
}
|
|
4273
|
+
|
|
4274
|
+
// src/react/message-history/strategies/selective-prune.ts
|
|
4275
|
+
function applySelectivePrune(displayMessages, recentBuffer, options = {}) {
|
|
4276
|
+
const {
|
|
4277
|
+
toolResultAgeTurns = 3,
|
|
4278
|
+
stripOldReasoning = true,
|
|
4279
|
+
deduplicateSkills = true
|
|
4280
|
+
} = options;
|
|
4281
|
+
const cutoff = Math.max(0, displayMessages.length - recentBuffer);
|
|
4282
|
+
const seenSkillContent = /* @__PURE__ */ new Set();
|
|
4283
|
+
return displayMessages.map((msg, idx) => {
|
|
4284
|
+
const llm = toLLMMessage(msg);
|
|
4285
|
+
const isOld = idx < cutoff;
|
|
4286
|
+
if (deduplicateSkills && msg.role === "system" && llm.content) {
|
|
4287
|
+
const key = llm.content.slice(0, 100);
|
|
4288
|
+
if (seenSkillContent.has(key)) {
|
|
4289
|
+
return { ...llm, content: "[skill instruction \u2014 deduplicated]" };
|
|
4290
|
+
}
|
|
4291
|
+
seenSkillContent.add(key);
|
|
4292
|
+
}
|
|
4293
|
+
if (!isOld) return llm;
|
|
4294
|
+
if (stripOldReasoning && msg.role === "assistant" && msg.thinking) {
|
|
4295
|
+
llm.content = llm.content;
|
|
4296
|
+
}
|
|
4297
|
+
if (msg.role === "tool" && llm.content) {
|
|
4298
|
+
const originalSize = llm.content.length;
|
|
4299
|
+
if (originalSize > 500) {
|
|
4300
|
+
const stub = buildToolResultStub(msg, llm.content);
|
|
4301
|
+
return {
|
|
4302
|
+
role: "tool",
|
|
4303
|
+
tool_call_id: llm.tool_call_id,
|
|
4304
|
+
content: JSON.stringify(stub)
|
|
4305
|
+
};
|
|
4306
|
+
}
|
|
4307
|
+
}
|
|
4308
|
+
return llm;
|
|
4309
|
+
});
|
|
4310
|
+
}
|
|
4311
|
+
function buildToolResultStub(msg, content) {
|
|
4312
|
+
return {
|
|
4313
|
+
type: "compacted-tool-result",
|
|
4314
|
+
toolName: msg.metadata?.toolName ?? "tool",
|
|
4315
|
+
toolCallId: msg.toolCallId ?? "",
|
|
4316
|
+
args: msg.metadata?.toolArgs ?? {},
|
|
4317
|
+
executedAt: msg.timestamp,
|
|
4318
|
+
status: content.includes('"error"') ? "error" : "success",
|
|
4319
|
+
originalSize: content.length,
|
|
4320
|
+
summary: buildSummary(content),
|
|
4321
|
+
extract: content.slice(0, 200)
|
|
4322
|
+
};
|
|
4323
|
+
}
|
|
4324
|
+
function buildSummary(content) {
|
|
4325
|
+
try {
|
|
4326
|
+
const parsed = JSON.parse(content);
|
|
4327
|
+
if (parsed?.message) return String(parsed.message).slice(0, 120);
|
|
4328
|
+
if (parsed?.error) return `Error: ${String(parsed.error).slice(0, 100)}`;
|
|
4329
|
+
if (Array.isArray(parsed)) return `Array result \u2014 ${parsed.length} items`;
|
|
4330
|
+
const keys = Object.keys(parsed).slice(0, 3).join(", ");
|
|
4331
|
+
return `Object result \u2014 keys: ${keys}`;
|
|
4332
|
+
} catch {
|
|
4333
|
+
return content.slice(0, 120);
|
|
4334
|
+
}
|
|
4335
|
+
}
|
|
4336
|
+
|
|
4337
|
+
// src/react/message-history/strategies/summary-buffer.ts
|
|
4338
|
+
function buildSummaryBufferContext(displayMessages, compactionState, options) {
|
|
4339
|
+
const { recentBuffer } = options;
|
|
4340
|
+
const systemMessages = displayMessages.filter(
|
|
4341
|
+
(m) => m.role === "system" || isCompactionMarker(m)
|
|
4342
|
+
);
|
|
4343
|
+
const conversationMessages = displayMessages.filter(
|
|
4344
|
+
(m) => m.role !== "system" && !isCompactionMarker(m)
|
|
4345
|
+
);
|
|
4346
|
+
const recentStart = Math.max(0, conversationMessages.length - recentBuffer);
|
|
4347
|
+
const recentMessages = conversationMessages.slice(recentStart);
|
|
4348
|
+
const result = [];
|
|
4349
|
+
if (compactionState.workingMemory.length > 0) {
|
|
4350
|
+
result.push({
|
|
4351
|
+
role: "system",
|
|
4352
|
+
content: `[Working memory \u2014 always active]
|
|
4353
|
+
${compactionState.workingMemory.join("\n")}`
|
|
4354
|
+
});
|
|
4355
|
+
}
|
|
4356
|
+
if (compactionState.rollingSummary) {
|
|
4357
|
+
result.push({
|
|
4358
|
+
role: "system",
|
|
4359
|
+
content: `[Previous conversation summary]
|
|
4360
|
+
${compactionState.rollingSummary}`
|
|
4361
|
+
});
|
|
4362
|
+
}
|
|
4363
|
+
result.push(...toLLMMessages(systemMessages));
|
|
4364
|
+
result.push(...toLLMMessages(recentMessages));
|
|
4365
|
+
return result;
|
|
4366
|
+
}
|
|
4367
|
+
async function runCompaction(displayMessages, compactionState, options) {
|
|
4368
|
+
const { recentBuffer, compactionUrl, summarizer } = options;
|
|
4369
|
+
const conversationMessages = displayMessages.filter(
|
|
4370
|
+
(m) => m.role !== "system" && !isCompactionMarker(m)
|
|
4371
|
+
);
|
|
4372
|
+
const cutoff = Math.max(0, conversationMessages.length - recentBuffer);
|
|
4373
|
+
const toSummarize = conversationMessages.slice(0, cutoff);
|
|
4374
|
+
if (toSummarize.length === 0) {
|
|
4375
|
+
return { llmMessages: toLLMMessages(displayMessages) };
|
|
4376
|
+
}
|
|
4377
|
+
const llmToSummarize = toLLMMessages(toSummarize);
|
|
4378
|
+
const originalTokens = estimateMessagesTokens(llmToSummarize);
|
|
4379
|
+
let newSummary;
|
|
4380
|
+
if (summarizer) {
|
|
4381
|
+
newSummary = await summarizer(llmToSummarize);
|
|
4382
|
+
} else if (compactionUrl) {
|
|
4383
|
+
newSummary = await fetchSummary(compactionUrl, {
|
|
4384
|
+
messages: llmToSummarize,
|
|
4385
|
+
existingSummary: compactionState.rollingSummary,
|
|
4386
|
+
workingMemory: compactionState.workingMemory
|
|
4387
|
+
});
|
|
4388
|
+
} else {
|
|
4389
|
+
newSummary = buildFallbackSummary(
|
|
4390
|
+
llmToSummarize,
|
|
4391
|
+
compactionState.rollingSummary
|
|
4392
|
+
);
|
|
4393
|
+
}
|
|
4394
|
+
const summaryTokens = Math.ceil(newSummary.length / 3.5);
|
|
4395
|
+
const tokensSaved = Math.max(0, originalTokens - summaryTokens);
|
|
4396
|
+
return {
|
|
4397
|
+
llmMessages: buildSummaryBufferContext(
|
|
4398
|
+
displayMessages,
|
|
4399
|
+
{ ...compactionState, rollingSummary: newSummary },
|
|
4400
|
+
options
|
|
4401
|
+
),
|
|
4402
|
+
newSummary,
|
|
4403
|
+
tokensSaved,
|
|
4404
|
+
messagesSummarized: toSummarize.length
|
|
4405
|
+
};
|
|
4406
|
+
}
|
|
4407
|
+
async function fetchSummary(url, body) {
|
|
4408
|
+
const res = await fetch(url, {
|
|
4409
|
+
method: "POST",
|
|
4410
|
+
headers: { "Content-Type": "application/json" },
|
|
4411
|
+
body: JSON.stringify(body)
|
|
4412
|
+
});
|
|
4413
|
+
if (!res.ok) {
|
|
4414
|
+
throw new Error(`Compaction endpoint returned ${res.status}`);
|
|
4415
|
+
}
|
|
4416
|
+
const data = await res.json();
|
|
4417
|
+
if (!data?.summary) {
|
|
4418
|
+
throw new Error("Compaction endpoint did not return { summary: string }");
|
|
4419
|
+
}
|
|
4420
|
+
return data.summary;
|
|
4421
|
+
}
|
|
4422
|
+
function buildFallbackSummary(messages, existingSummary) {
|
|
4423
|
+
const lines = messages.filter((m) => m.role === "user" || m.role === "assistant").map((m) => `${m.role}: ${(m.content ?? "").slice(0, 200)}`).join("\n");
|
|
4424
|
+
return existingSummary ? `${existingSummary}
|
|
4425
|
+
|
|
4426
|
+
[Additional context]
|
|
4427
|
+
${lines}` : lines;
|
|
4428
|
+
}
|
|
4429
|
+
|
|
4430
|
+
// src/react/message-history/session-persistence.ts
|
|
4431
|
+
var IDB_DB_NAME = "copilot-sdk";
|
|
4432
|
+
var IDB_STORE = "sessions";
|
|
4433
|
+
var IDB_VERSION = 1;
|
|
4434
|
+
function saveCompactionState(storageKey, state) {
|
|
4435
|
+
try {
|
|
4436
|
+
localStorage.setItem(
|
|
4437
|
+
`${storageKey}-state`,
|
|
4438
|
+
JSON.stringify({ ...state, _savedAt: Date.now() })
|
|
4439
|
+
);
|
|
4440
|
+
} catch {
|
|
4441
|
+
}
|
|
4442
|
+
}
|
|
4443
|
+
function loadCompactionState(storageKey) {
|
|
4444
|
+
try {
|
|
4445
|
+
const raw = localStorage.getItem(`${storageKey}-state`);
|
|
4446
|
+
if (!raw) return null;
|
|
4447
|
+
const parsed = JSON.parse(raw);
|
|
4448
|
+
delete parsed._savedAt;
|
|
4449
|
+
return parsed;
|
|
4450
|
+
} catch {
|
|
4451
|
+
return null;
|
|
4452
|
+
}
|
|
4453
|
+
}
|
|
4454
|
+
function clearCompactionState(storageKey) {
|
|
4455
|
+
try {
|
|
4456
|
+
localStorage.removeItem(`${storageKey}-state`);
|
|
4457
|
+
} catch {
|
|
4458
|
+
}
|
|
4459
|
+
}
|
|
4460
|
+
function openDB() {
|
|
4461
|
+
return new Promise((resolve, reject) => {
|
|
4462
|
+
const req = indexedDB.open(IDB_DB_NAME, IDB_VERSION);
|
|
4463
|
+
req.onupgradeneeded = () => {
|
|
4464
|
+
req.result.createObjectStore(IDB_STORE, { keyPath: "sessionId" });
|
|
4465
|
+
};
|
|
4466
|
+
req.onsuccess = () => resolve(req.result);
|
|
4467
|
+
req.onerror = () => reject(req.error);
|
|
4468
|
+
});
|
|
4469
|
+
}
|
|
4470
|
+
async function saveDisplayMessages(storageKey, messages) {
|
|
4471
|
+
try {
|
|
4472
|
+
const db = await openDB();
|
|
4473
|
+
const tx = db.transaction(IDB_STORE, "readwrite");
|
|
4474
|
+
tx.objectStore(IDB_STORE).put({
|
|
4475
|
+
sessionId: storageKey,
|
|
4476
|
+
messages,
|
|
4477
|
+
savedAt: Date.now()
|
|
4478
|
+
});
|
|
4479
|
+
await new Promise((res, rej) => {
|
|
4480
|
+
tx.oncomplete = () => res();
|
|
4481
|
+
tx.onerror = () => rej(tx.error);
|
|
4482
|
+
});
|
|
4483
|
+
db.close();
|
|
4484
|
+
} catch {
|
|
4485
|
+
}
|
|
4486
|
+
}
|
|
4487
|
+
async function loadDisplayMessages(storageKey) {
|
|
4488
|
+
try {
|
|
4489
|
+
const db = await openDB();
|
|
4490
|
+
const tx = db.transaction(IDB_STORE, "readonly");
|
|
4491
|
+
const req = tx.objectStore(IDB_STORE).get(storageKey);
|
|
4492
|
+
const result = await new Promise((res, rej) => {
|
|
4493
|
+
req.onsuccess = () => res(req.result);
|
|
4494
|
+
req.onerror = () => rej(req.error);
|
|
4495
|
+
});
|
|
4496
|
+
db.close();
|
|
4497
|
+
return result?.messages ?? null;
|
|
4498
|
+
} catch {
|
|
4499
|
+
return null;
|
|
4500
|
+
}
|
|
4501
|
+
}
|
|
4502
|
+
async function clearDisplayMessages(storageKey) {
|
|
4503
|
+
try {
|
|
4504
|
+
const db = await openDB();
|
|
4505
|
+
const tx = db.transaction(IDB_STORE, "readwrite");
|
|
4506
|
+
tx.objectStore(IDB_STORE).delete(storageKey);
|
|
4507
|
+
await new Promise((res, rej) => {
|
|
4508
|
+
tx.oncomplete = () => res();
|
|
4509
|
+
tx.onerror = () => rej(tx.error);
|
|
4510
|
+
});
|
|
4511
|
+
db.close();
|
|
4512
|
+
} catch {
|
|
4513
|
+
}
|
|
4514
|
+
}
|
|
4515
|
+
async function clearSession(storageKey) {
|
|
4516
|
+
clearCompactionState(storageKey);
|
|
4517
|
+
await clearDisplayMessages(storageKey);
|
|
4518
|
+
}
|
|
4519
|
+
|
|
4520
|
+
// src/react/message-history/useMessageHistory.ts
|
|
4521
|
+
var DEFAULT_COMPACTION_STATE = {
|
|
4522
|
+
rollingSummary: null,
|
|
4523
|
+
lastCompactionAt: null,
|
|
4524
|
+
compactionCount: 0,
|
|
4525
|
+
totalTokensSaved: 0,
|
|
4526
|
+
workingMemory: [],
|
|
4527
|
+
displayMessageCount: 0,
|
|
4528
|
+
llmMessageCount: 0
|
|
4529
|
+
};
|
|
4530
|
+
function useMessageHistory(options = {}) {
|
|
4531
|
+
const { messages } = useCopilot();
|
|
4532
|
+
const ctx = useMessageHistoryContext();
|
|
4533
|
+
const config = useMemo(
|
|
4534
|
+
() => ({ ...defaultMessageHistoryConfig, ...ctx.config, ...options }),
|
|
4535
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
4536
|
+
[
|
|
4537
|
+
ctx.config,
|
|
4538
|
+
options.strategy,
|
|
4539
|
+
options.maxContextTokens,
|
|
4540
|
+
options.recentBuffer,
|
|
4541
|
+
options.compactionThreshold
|
|
4542
|
+
]
|
|
4543
|
+
);
|
|
4544
|
+
const storageKey = config.storageKey ?? "copilot-session";
|
|
4545
|
+
const strategy = options.skipCompaction ? "none" : config.strategy ?? "none";
|
|
4546
|
+
const [compactionState, setCompactionState] = useState(() => {
|
|
4547
|
+
if (config.persistSession) {
|
|
4548
|
+
return loadCompactionState(storageKey) ?? DEFAULT_COMPACTION_STATE;
|
|
4549
|
+
}
|
|
4550
|
+
return DEFAULT_COMPACTION_STATE;
|
|
4551
|
+
});
|
|
4552
|
+
const displayMessages = useMemo(
|
|
4553
|
+
() => messages.map(toDisplayMessage),
|
|
4554
|
+
[messages]
|
|
4555
|
+
);
|
|
4556
|
+
const restoredRef = useRef(false);
|
|
4557
|
+
useEffect(() => {
|
|
4558
|
+
if (!config.persistSession || restoredRef.current) return;
|
|
4559
|
+
restoredRef.current = true;
|
|
4560
|
+
loadDisplayMessages(storageKey).then((saved) => {
|
|
4561
|
+
if (saved?.length && messages.length === 0) ;
|
|
4562
|
+
});
|
|
4563
|
+
}, [config.persistSession, storageKey, messages.length]);
|
|
4564
|
+
useEffect(() => {
|
|
4565
|
+
if (!config.persistSession || displayMessages.length === 0) return;
|
|
4566
|
+
saveDisplayMessages(storageKey, displayMessages);
|
|
4567
|
+
}, [config.persistSession, storageKey, displayMessages]);
|
|
4568
|
+
const llmMessages = useMemo(() => {
|
|
4569
|
+
const maxTokens = config.maxContextTokens ?? 128e3;
|
|
4570
|
+
const reserve = config.reserveForResponse ?? 4096;
|
|
4571
|
+
const tokenBudget = maxTokens - reserve;
|
|
4572
|
+
const recentBuffer = config.recentBuffer ?? 10;
|
|
4573
|
+
const maxChars = config.toolResultMaxChars ?? 1e4;
|
|
4574
|
+
let result;
|
|
4575
|
+
switch (strategy) {
|
|
4576
|
+
case "sliding-window": {
|
|
4577
|
+
const windowed = applySlidingWindow(displayMessages, {
|
|
4578
|
+
tokenBudget,
|
|
4579
|
+
recentBuffer
|
|
4580
|
+
});
|
|
4581
|
+
result = truncateToolResults(toLLMMessages(windowed), maxChars);
|
|
4582
|
+
break;
|
|
4583
|
+
}
|
|
4584
|
+
case "selective-prune": {
|
|
4585
|
+
result = truncateToolResults(
|
|
4586
|
+
applySelectivePrune(displayMessages, recentBuffer),
|
|
4587
|
+
maxChars
|
|
4588
|
+
);
|
|
4589
|
+
break;
|
|
4590
|
+
}
|
|
4591
|
+
case "summary-buffer": {
|
|
4592
|
+
result = truncateToolResults(
|
|
4593
|
+
buildSummaryBufferContext(displayMessages, compactionState, {
|
|
4594
|
+
recentBuffer,
|
|
4595
|
+
compactionThreshold: config.compactionThreshold ?? 0.75,
|
|
4596
|
+
compactionUrl: config.compactionUrl,
|
|
4597
|
+
summarizer: options.summarizer
|
|
4598
|
+
}),
|
|
4599
|
+
maxChars
|
|
4600
|
+
);
|
|
4601
|
+
break;
|
|
4602
|
+
}
|
|
4603
|
+
default:
|
|
4604
|
+
result = truncateToolResults(toLLMMessages(displayMessages), maxChars);
|
|
4605
|
+
}
|
|
4606
|
+
return result;
|
|
4607
|
+
}, [displayMessages, compactionState, strategy, config, options.summarizer]);
|
|
4608
|
+
const tokenUsage = useMemo(() => {
|
|
4609
|
+
const mode = options.tokenEstimation ?? "fast";
|
|
4610
|
+
const current = estimateTokens2(toLLMMessages(displayMessages), mode);
|
|
4611
|
+
const max = config.maxContextTokens ?? 128e3;
|
|
4612
|
+
const threshold = config.compactionThreshold ?? 0.75;
|
|
4613
|
+
const percentage = current / max;
|
|
4614
|
+
return { current, max, percentage, isApproaching: percentage >= threshold };
|
|
4615
|
+
}, [
|
|
4616
|
+
displayMessages,
|
|
4617
|
+
config.maxContextTokens,
|
|
4618
|
+
config.compactionThreshold,
|
|
4619
|
+
options.tokenEstimation
|
|
4620
|
+
]);
|
|
4621
|
+
useEffect(() => {
|
|
4622
|
+
if (config.onTokenUsage && tokenUsage.current > 0) {
|
|
4623
|
+
config.onTokenUsage(tokenUsage);
|
|
4624
|
+
}
|
|
4625
|
+
}, [tokenUsage, config.onTokenUsage]);
|
|
4626
|
+
useEffect(() => {
|
|
4627
|
+
if (config.persistSession) {
|
|
4628
|
+
saveCompactionState(storageKey, {
|
|
4629
|
+
...compactionState,
|
|
4630
|
+
displayMessageCount: displayMessages.length,
|
|
4631
|
+
llmMessageCount: llmMessages.length
|
|
4632
|
+
});
|
|
4633
|
+
}
|
|
4634
|
+
}, [
|
|
4635
|
+
config.persistSession,
|
|
4636
|
+
storageKey,
|
|
4637
|
+
compactionState,
|
|
4638
|
+
displayMessages.length,
|
|
4639
|
+
llmMessages.length
|
|
4640
|
+
]);
|
|
4641
|
+
const isCompactingRef = useRef(false);
|
|
4642
|
+
const [isCompacting, setIsCompacting] = useState(false);
|
|
4643
|
+
useEffect(() => {
|
|
4644
|
+
if (strategy !== "summary-buffer" || options.skipCompaction || isCompactingRef.current || !tokenUsage.isApproaching)
|
|
4645
|
+
return;
|
|
4646
|
+
isCompactingRef.current = true;
|
|
4647
|
+
setIsCompacting(true);
|
|
4648
|
+
runCompaction(displayMessages, compactionState, {
|
|
4649
|
+
recentBuffer: config.recentBuffer ?? 10,
|
|
4650
|
+
tokenBudget: (config.maxContextTokens ?? 128e3) - (config.reserveForResponse ?? 4096),
|
|
4651
|
+
compactionThreshold: config.compactionThreshold ?? 0.75,
|
|
4652
|
+
compactionUrl: config.compactionUrl,
|
|
4653
|
+
summarizer: options.summarizer
|
|
4654
|
+
}).then((result) => {
|
|
4655
|
+
if (result.newSummary) {
|
|
4656
|
+
const event = {
|
|
4657
|
+
type: "auto",
|
|
4658
|
+
compactionCount: compactionState.compactionCount + 1,
|
|
4659
|
+
messagesSummarized: result.messagesSummarized ?? 0,
|
|
4660
|
+
tokensSaved: result.tokensSaved ?? 0,
|
|
4661
|
+
timestamp: Date.now()
|
|
4662
|
+
};
|
|
4663
|
+
setCompactionState((prev) => ({
|
|
4664
|
+
...prev,
|
|
4665
|
+
rollingSummary: result.newSummary,
|
|
4666
|
+
lastCompactionAt: Date.now(),
|
|
4667
|
+
compactionCount: prev.compactionCount + 1,
|
|
4668
|
+
totalTokensSaved: prev.totalTokensSaved + (result.tokensSaved ?? 0)
|
|
4669
|
+
}));
|
|
4670
|
+
config.onCompaction?.(event);
|
|
4671
|
+
}
|
|
4672
|
+
}).finally(() => {
|
|
4673
|
+
isCompactingRef.current = false;
|
|
4674
|
+
setIsCompacting(false);
|
|
4675
|
+
});
|
|
4676
|
+
}, [tokenUsage.isApproaching, strategy]);
|
|
4677
|
+
const compactSession = useCallback(
|
|
4678
|
+
async (instructions) => {
|
|
4679
|
+
if (strategy !== "summary-buffer") return;
|
|
4680
|
+
const result = await runCompaction(displayMessages, compactionState, {
|
|
4681
|
+
recentBuffer: config.recentBuffer ?? 10,
|
|
4682
|
+
tokenBudget: (config.maxContextTokens ?? 128e3) - (config.reserveForResponse ?? 4096),
|
|
4683
|
+
compactionThreshold: config.compactionThreshold ?? 0.75,
|
|
4684
|
+
compactionUrl: config.compactionUrl,
|
|
4685
|
+
summarizer: options.summarizer ? (msgs) => options.summarizer(msgs) : instructions ? (msgs) => fetchWithInstructions(config.compactionUrl, msgs, instructions) : void 0
|
|
4686
|
+
});
|
|
4687
|
+
if (result.newSummary) {
|
|
4688
|
+
const event = {
|
|
4689
|
+
type: "manual",
|
|
4690
|
+
compactionCount: compactionState.compactionCount + 1,
|
|
4691
|
+
messagesSummarized: result.messagesSummarized ?? 0,
|
|
4692
|
+
tokensSaved: result.tokensSaved ?? 0,
|
|
4693
|
+
timestamp: Date.now()
|
|
4694
|
+
};
|
|
4695
|
+
setCompactionState((prev) => ({
|
|
4696
|
+
...prev,
|
|
4697
|
+
rollingSummary: result.newSummary,
|
|
4698
|
+
lastCompactionAt: Date.now(),
|
|
4699
|
+
compactionCount: prev.compactionCount + 1,
|
|
4700
|
+
totalTokensSaved: prev.totalTokensSaved + (result.tokensSaved ?? 0)
|
|
4701
|
+
}));
|
|
4702
|
+
config.onCompaction?.(event);
|
|
4703
|
+
}
|
|
4704
|
+
},
|
|
4705
|
+
[displayMessages, compactionState, config, strategy, options.summarizer]
|
|
4706
|
+
);
|
|
4707
|
+
const addToWorkingMemory = useCallback((fact) => {
|
|
4708
|
+
setCompactionState((prev) => ({
|
|
4709
|
+
...prev,
|
|
4710
|
+
workingMemory: [...prev.workingMemory, fact]
|
|
4711
|
+
}));
|
|
4712
|
+
}, []);
|
|
4713
|
+
const clearWorkingMemory = useCallback(() => {
|
|
4714
|
+
setCompactionState((prev) => ({ ...prev, workingMemory: [] }));
|
|
4715
|
+
}, []);
|
|
4716
|
+
const resetSession = useCallback(async () => {
|
|
4717
|
+
setCompactionState(DEFAULT_COMPACTION_STATE);
|
|
4718
|
+
if (config.persistSession) {
|
|
4719
|
+
await clearSession(storageKey);
|
|
4720
|
+
}
|
|
4721
|
+
}, [config.persistSession, storageKey]);
|
|
4722
|
+
return {
|
|
4723
|
+
displayMessages,
|
|
4724
|
+
llmMessages,
|
|
4725
|
+
tokenUsage,
|
|
4726
|
+
isCompacting,
|
|
4727
|
+
compactionState: {
|
|
4728
|
+
...compactionState,
|
|
4729
|
+
displayMessageCount: displayMessages.length,
|
|
4730
|
+
llmMessageCount: llmMessages.length
|
|
4731
|
+
},
|
|
4732
|
+
compactSession,
|
|
4733
|
+
addToWorkingMemory,
|
|
4734
|
+
clearWorkingMemory,
|
|
4735
|
+
resetSession
|
|
4736
|
+
};
|
|
4737
|
+
}
|
|
4738
|
+
async function fetchWithInstructions(url, messages, instructions) {
|
|
4739
|
+
const res = await fetch(url, {
|
|
4740
|
+
method: "POST",
|
|
4741
|
+
headers: { "Content-Type": "application/json" },
|
|
4742
|
+
body: JSON.stringify({ messages, instructions })
|
|
4743
|
+
});
|
|
4744
|
+
const data = await res.json();
|
|
4745
|
+
return data.summary;
|
|
4746
|
+
}
|
|
4747
|
+
var SkillContext = createContext(null);
|
|
4748
|
+
function useSkillContext() {
|
|
4749
|
+
const ctx = useContext(SkillContext);
|
|
4750
|
+
if (!ctx) {
|
|
4751
|
+
throw new Error("useSkillContext must be used within <SkillProvider>");
|
|
4752
|
+
}
|
|
4753
|
+
return ctx;
|
|
4754
|
+
}
|
|
4755
|
+
function useAIContext(item) {
|
|
4756
|
+
const { addContext, removeContext } = useCopilot();
|
|
4757
|
+
const contextIdRef = useRef(null);
|
|
4758
|
+
const serializedData = typeof item.data === "string" ? item.data : JSON.stringify(item.data);
|
|
4759
|
+
useEffect(() => {
|
|
4760
|
+
const formattedValue = typeof item.data === "string" ? item.data : JSON.stringify(item.data, null, 2);
|
|
4761
|
+
const contextString = item.description ? `${item.description}:
|
|
4762
|
+
${formattedValue}` : `${item.key}:
|
|
4763
|
+
${formattedValue}`;
|
|
4764
|
+
contextIdRef.current = addContext(contextString, item.parentId);
|
|
4765
|
+
return () => {
|
|
4766
|
+
if (contextIdRef.current) {
|
|
4767
|
+
removeContext(contextIdRef.current);
|
|
4768
|
+
contextIdRef.current = null;
|
|
4769
|
+
}
|
|
4770
|
+
};
|
|
4771
|
+
}, [
|
|
4772
|
+
item.key,
|
|
4773
|
+
serializedData,
|
|
4774
|
+
item.description,
|
|
4775
|
+
item.parentId,
|
|
4776
|
+
addContext,
|
|
4777
|
+
removeContext
|
|
4778
|
+
]);
|
|
4779
|
+
return contextIdRef.current ?? void 0;
|
|
4780
|
+
}
|
|
4781
|
+
function useAIContexts(items) {
|
|
4782
|
+
const { addContext, removeContext } = useCopilot();
|
|
4783
|
+
const contextIdsRef = useRef([]);
|
|
4784
|
+
const serializedItems = JSON.stringify(
|
|
4785
|
+
items.map((item) => ({
|
|
4786
|
+
key: item.key,
|
|
4787
|
+
data: item.data,
|
|
4788
|
+
description: item.description,
|
|
4789
|
+
parentId: item.parentId
|
|
4790
|
+
}))
|
|
4791
|
+
);
|
|
4792
|
+
useEffect(() => {
|
|
4793
|
+
contextIdsRef.current.forEach((id) => removeContext(id));
|
|
4794
|
+
contextIdsRef.current = [];
|
|
4795
|
+
const parsedItems = JSON.parse(serializedItems);
|
|
4796
|
+
for (const item of parsedItems) {
|
|
4797
|
+
const formattedValue = typeof item.data === "string" ? item.data : JSON.stringify(item.data, null, 2);
|
|
4798
|
+
const contextString = item.description ? `${item.description}:
|
|
4799
|
+
${formattedValue}` : `${item.key}:
|
|
4800
|
+
${formattedValue}`;
|
|
4801
|
+
const id = addContext(contextString, item.parentId);
|
|
4802
|
+
contextIdsRef.current.push(id);
|
|
4803
|
+
}
|
|
4804
|
+
return () => {
|
|
4805
|
+
contextIdsRef.current.forEach((id) => removeContext(id));
|
|
4806
|
+
contextIdsRef.current = [];
|
|
4807
|
+
};
|
|
4808
|
+
}, [serializedItems, addContext, removeContext]);
|
|
4809
|
+
}
|
|
4810
|
+
function isZodSchema(value) {
|
|
4811
|
+
if (value === null || typeof value !== "object") return false;
|
|
4812
|
+
const obj = value;
|
|
4813
|
+
return "_def" in obj && typeof obj._def === "object" || "_zod" in obj && typeof obj._zod === "object" || "~standard" in obj;
|
|
4814
|
+
}
|
|
4815
|
+
function useTool(config, dependencies = []) {
|
|
4816
|
+
const { registerTool, unregisterTool } = useCopilot();
|
|
4817
|
+
const configRef = useRef(config);
|
|
4818
|
+
configRef.current = config;
|
|
4819
|
+
const inputSchema = useMemo(() => {
|
|
4820
|
+
if (isZodSchema(config.inputSchema)) {
|
|
4821
|
+
return zodToJsonSchema(config.inputSchema);
|
|
4822
|
+
}
|
|
4823
|
+
return config.inputSchema;
|
|
4824
|
+
}, [config.inputSchema]);
|
|
4825
|
+
useEffect(() => {
|
|
4826
|
+
const tool2 = {
|
|
4827
|
+
name: config.name,
|
|
4828
|
+
description: config.description,
|
|
4829
|
+
location: "client",
|
|
4830
|
+
inputSchema,
|
|
4831
|
+
handler: async (params, context) => {
|
|
4832
|
+
return configRef.current.handler(params, context);
|
|
4833
|
+
},
|
|
4834
|
+
render: config.render,
|
|
4835
|
+
available: config.available ?? true,
|
|
4836
|
+
needsApproval: config.needsApproval,
|
|
4837
|
+
approvalMessage: config.approvalMessage,
|
|
4838
|
+
hidden: config.hidden,
|
|
4839
|
+
deferLoading: config.deferLoading,
|
|
4840
|
+
profiles: config.profiles,
|
|
4841
|
+
searchKeywords: config.searchKeywords,
|
|
4842
|
+
group: config.group,
|
|
4843
|
+
category: config.category,
|
|
4844
|
+
resultConfig: config.resultConfig,
|
|
4845
|
+
title: config.title,
|
|
4846
|
+
executingTitle: config.executingTitle,
|
|
4847
|
+
completedTitle: config.completedTitle,
|
|
4848
|
+
aiResponseMode: config.aiResponseMode,
|
|
4849
|
+
aiContext: config.aiContext
|
|
4850
|
+
};
|
|
4851
|
+
registerTool(tool2);
|
|
4852
|
+
return () => {
|
|
4853
|
+
unregisterTool(config.name);
|
|
4854
|
+
};
|
|
4855
|
+
}, [config.name, inputSchema, ...dependencies]);
|
|
4856
|
+
}
|
|
4857
|
+
function useTools(tools) {
|
|
4858
|
+
const { registerTool, unregisterTool } = useCopilot();
|
|
4859
|
+
const registeredToolsRef = useRef([]);
|
|
4860
|
+
const toolsRef = useRef(tools);
|
|
4861
|
+
toolsRef.current = tools;
|
|
4862
|
+
const toolsKey = Object.keys(tools).sort().join(",");
|
|
4863
|
+
useEffect(() => {
|
|
4864
|
+
const currentTools = toolsRef.current;
|
|
4865
|
+
const toolNames = [];
|
|
4866
|
+
for (const [name, toolDef] of Object.entries(currentTools)) {
|
|
4867
|
+
const fullTool = {
|
|
4868
|
+
...toolDef,
|
|
4869
|
+
name
|
|
4870
|
+
// Use the key as the name
|
|
4871
|
+
};
|
|
4872
|
+
registerTool(fullTool);
|
|
4873
|
+
toolNames.push(name);
|
|
4874
|
+
}
|
|
4875
|
+
registeredToolsRef.current = toolNames;
|
|
4876
|
+
return () => {
|
|
4877
|
+
for (const name of registeredToolsRef.current) {
|
|
4878
|
+
unregisterTool(name);
|
|
4879
|
+
}
|
|
4880
|
+
registeredToolsRef.current = [];
|
|
4881
|
+
};
|
|
4882
|
+
}, [toolsKey]);
|
|
4883
|
+
}
|
|
4884
|
+
function SkillContextInjector({
|
|
4885
|
+
registry,
|
|
4886
|
+
skills
|
|
4887
|
+
}) {
|
|
4888
|
+
const catalog = useMemo(() => registry.buildCatalog(), [skills]);
|
|
4889
|
+
const eagerContent = useMemo(() => registry.buildEagerContent(), [skills]);
|
|
4890
|
+
useAIContext({
|
|
4891
|
+
key: "__skill_catalog__",
|
|
4892
|
+
description: "Skills the AI can load on demand",
|
|
4893
|
+
data: catalog ? `You have access to specialized skills. Call load_skill({ name }) when relevant.
|
|
4894
|
+
|
|
4895
|
+
${catalog}` : ""
|
|
4896
|
+
});
|
|
4897
|
+
useAIContext({
|
|
4898
|
+
key: "__skill_eager__",
|
|
4899
|
+
description: "Always-active skill instructions",
|
|
4900
|
+
data: eagerContent
|
|
4901
|
+
});
|
|
4902
|
+
return null;
|
|
4903
|
+
}
|
|
4904
|
+
function SkillRequestSync({ skills }) {
|
|
4905
|
+
const { setInlineSkills } = useCopilot();
|
|
4906
|
+
useEffect(() => {
|
|
4907
|
+
const inlineSkills = skills.filter((s) => s.source.type === "inline").map((s) => ({
|
|
4908
|
+
name: s.name,
|
|
4909
|
+
description: s.description,
|
|
4910
|
+
content: s.content,
|
|
4911
|
+
strategy: s.strategy
|
|
4912
|
+
}));
|
|
4913
|
+
setInlineSkills(inlineSkills);
|
|
4914
|
+
}, [skills, setInlineSkills]);
|
|
4915
|
+
return null;
|
|
4916
|
+
}
|
|
4917
|
+
function SkillToolRegistrar({
|
|
4918
|
+
registry,
|
|
4919
|
+
skills
|
|
4920
|
+
}) {
|
|
4921
|
+
useTool(
|
|
4922
|
+
{
|
|
4923
|
+
name: "load_skill",
|
|
4924
|
+
description: "Load a skill by name to get full instructions for a specialized task.",
|
|
4925
|
+
inputSchema: {
|
|
4926
|
+
type: "object",
|
|
4927
|
+
properties: {
|
|
4928
|
+
name: {
|
|
4929
|
+
type: "string",
|
|
4930
|
+
description: "The name of the skill to load."
|
|
4931
|
+
}
|
|
4932
|
+
},
|
|
4933
|
+
required: ["name"]
|
|
4934
|
+
},
|
|
4935
|
+
handler: async ({ name }) => {
|
|
4936
|
+
const skill = registry.get(name);
|
|
4937
|
+
if (!skill) {
|
|
4938
|
+
const available = registry.getAuto().map((s) => s.name).join(", ") || "none";
|
|
4939
|
+
return {
|
|
4940
|
+
success: false,
|
|
4941
|
+
error: `Skill "${name}" not found. Available skills: ${available}`
|
|
4942
|
+
};
|
|
4943
|
+
}
|
|
4944
|
+
const sourceTypeMap = {
|
|
4945
|
+
inline: "client-inline",
|
|
4946
|
+
url: "remote-url",
|
|
4947
|
+
file: "server-dir"
|
|
4948
|
+
};
|
|
4949
|
+
return {
|
|
4950
|
+
success: true,
|
|
4951
|
+
name: skill.name,
|
|
4952
|
+
description: skill.description,
|
|
4953
|
+
strategy: skill.strategy ?? "auto",
|
|
4954
|
+
content: skill.content,
|
|
4955
|
+
source: sourceTypeMap[skill.source.type]
|
|
4956
|
+
};
|
|
4957
|
+
}
|
|
4958
|
+
},
|
|
4959
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
4960
|
+
[skills]
|
|
4961
|
+
);
|
|
4962
|
+
return null;
|
|
4963
|
+
}
|
|
4964
|
+
function SkillProvider({
|
|
4965
|
+
children,
|
|
4966
|
+
skills: skillsProp
|
|
4967
|
+
}) {
|
|
4968
|
+
const registryRef = useRef(null);
|
|
4969
|
+
if (registryRef.current === null) {
|
|
4970
|
+
registryRef.current = new SkillRegistry();
|
|
4971
|
+
}
|
|
4972
|
+
const registry = registryRef.current;
|
|
4973
|
+
const [skills, setSkills] = useState([]);
|
|
4974
|
+
useEffect(() => {
|
|
4975
|
+
if (!skillsProp?.length) return;
|
|
4976
|
+
for (const def of skillsProp) {
|
|
4977
|
+
if (def.source.type !== "inline") {
|
|
4978
|
+
console.warn(
|
|
4979
|
+
`[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.`
|
|
4980
|
+
);
|
|
4981
|
+
continue;
|
|
2508
4982
|
}
|
|
4983
|
+
const resolved = {
|
|
4984
|
+
...def,
|
|
4985
|
+
content: def.source.content
|
|
4986
|
+
};
|
|
4987
|
+
registry.register(resolved);
|
|
2509
4988
|
}
|
|
4989
|
+
setSkills(registry.getAll());
|
|
2510
4990
|
return () => {
|
|
2511
|
-
for (const
|
|
2512
|
-
|
|
4991
|
+
for (const def of skillsProp ?? []) {
|
|
4992
|
+
registry.unregister(def.name);
|
|
2513
4993
|
}
|
|
2514
|
-
|
|
4994
|
+
setSkills(registry.getAll());
|
|
2515
4995
|
};
|
|
2516
|
-
}, [
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
4996
|
+
}, [skillsProp]);
|
|
4997
|
+
const register = useCallback((skill) => {
|
|
4998
|
+
registry.register(skill);
|
|
4999
|
+
setSkills(registry.getAll());
|
|
5000
|
+
}, []);
|
|
5001
|
+
const unregister = useCallback((name) => {
|
|
5002
|
+
registry.unregister(name);
|
|
5003
|
+
setSkills(registry.getAll());
|
|
5004
|
+
}, []);
|
|
5005
|
+
const contextValue = useMemo(
|
|
5006
|
+
() => ({ registry, register, unregister, skills }),
|
|
5007
|
+
[register, unregister, skills]
|
|
5008
|
+
);
|
|
5009
|
+
return /* @__PURE__ */ jsxs(SkillContext.Provider, { value: contextValue, children: [
|
|
5010
|
+
/* @__PURE__ */ jsx(SkillContextInjector, { registry, skills }),
|
|
5011
|
+
/* @__PURE__ */ jsx(SkillToolRegistrar, { registry, skills }),
|
|
5012
|
+
/* @__PURE__ */ jsx(SkillRequestSync, { skills }),
|
|
5013
|
+
children
|
|
5014
|
+
] });
|
|
2527
5015
|
}
|
|
2528
5016
|
function MCPConnection({ config }) {
|
|
2529
5017
|
useMCPTools({
|
|
@@ -2537,6 +5025,123 @@ function MCPConnection({ config }) {
|
|
|
2537
5025
|
});
|
|
2538
5026
|
return null;
|
|
2539
5027
|
}
|
|
5028
|
+
var COMPACTING_MARKER_ID = "__compacting-in-progress__";
|
|
5029
|
+
function MessageHistoryBridge({
|
|
5030
|
+
chatRef
|
|
5031
|
+
}) {
|
|
5032
|
+
const { compactionState, tokenUsage } = useMessageHistory();
|
|
5033
|
+
const ctx = useMessageHistoryContext();
|
|
5034
|
+
const loaderAddedRef = useRef(false);
|
|
5035
|
+
const prevCompactionCountRef = useRef(compactionState.compactionCount);
|
|
5036
|
+
useEffect(() => {
|
|
5037
|
+
if (!tokenUsage.isApproaching) {
|
|
5038
|
+
loaderAddedRef.current = false;
|
|
5039
|
+
return;
|
|
5040
|
+
}
|
|
5041
|
+
if (loaderAddedRef.current) return;
|
|
5042
|
+
const chat = chatRef.current;
|
|
5043
|
+
if (!chat) return;
|
|
5044
|
+
const alreadyAdded = chat.messages.some(
|
|
5045
|
+
(m) => m.id === COMPACTING_MARKER_ID
|
|
5046
|
+
);
|
|
5047
|
+
if (alreadyAdded) return;
|
|
5048
|
+
loaderAddedRef.current = true;
|
|
5049
|
+
const loading = {
|
|
5050
|
+
id: COMPACTING_MARKER_ID,
|
|
5051
|
+
role: "system",
|
|
5052
|
+
content: "Compacting conversation\u2026",
|
|
5053
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
5054
|
+
metadata: { type: "compaction-marker", compacting: true }
|
|
5055
|
+
};
|
|
5056
|
+
chat.setMessages([...chat.messages, loading]);
|
|
5057
|
+
}, [tokenUsage.isApproaching]);
|
|
5058
|
+
useEffect(() => {
|
|
5059
|
+
if (compactionState.compactionCount <= prevCompactionCountRef.current)
|
|
5060
|
+
return;
|
|
5061
|
+
prevCompactionCountRef.current = compactionState.compactionCount;
|
|
5062
|
+
loaderAddedRef.current = false;
|
|
5063
|
+
const chat = chatRef.current;
|
|
5064
|
+
if (!chat) return;
|
|
5065
|
+
const hasLoader = chat.messages.some((m) => m.id === COMPACTING_MARKER_ID);
|
|
5066
|
+
const base = hasLoader ? chat.messages.map(
|
|
5067
|
+
(m) => m.id === COMPACTING_MARKER_ID ? {
|
|
5068
|
+
...m,
|
|
5069
|
+
id: `compaction-marker-${compactionState.compactionCount}`,
|
|
5070
|
+
content: `Conversation compacted \u2014 context window refreshed`,
|
|
5071
|
+
metadata: { type: "compaction-marker", compacting: false }
|
|
5072
|
+
} : m
|
|
5073
|
+
) : [
|
|
5074
|
+
...chat.messages,
|
|
5075
|
+
{
|
|
5076
|
+
id: `compaction-marker-${compactionState.compactionCount}`,
|
|
5077
|
+
role: "system",
|
|
5078
|
+
content: `Conversation compacted \u2014 context window refreshed`,
|
|
5079
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
5080
|
+
metadata: { type: "compaction-marker", compacting: false }
|
|
5081
|
+
}
|
|
5082
|
+
];
|
|
5083
|
+
chat.setMessages(base);
|
|
5084
|
+
}, [compactionState.compactionCount]);
|
|
5085
|
+
const compactionStateRef = useRef(compactionState);
|
|
5086
|
+
compactionStateRef.current = compactionState;
|
|
5087
|
+
const configRef = useRef(ctx.config);
|
|
5088
|
+
configRef.current = ctx.config;
|
|
5089
|
+
useEffect(() => {
|
|
5090
|
+
const chat = chatRef.current;
|
|
5091
|
+
if (!chat) return;
|
|
5092
|
+
chat.setRequestMessageTransform((allMessages) => {
|
|
5093
|
+
if (allMessages.length === 0) return allMessages;
|
|
5094
|
+
let lastUserIdx = -1;
|
|
5095
|
+
for (let i = allMessages.length - 1; i >= 0; i--) {
|
|
5096
|
+
if (allMessages[i].role === "user") {
|
|
5097
|
+
lastUserIdx = i;
|
|
5098
|
+
break;
|
|
5099
|
+
}
|
|
5100
|
+
}
|
|
5101
|
+
if (lastUserIdx === -1) return allMessages;
|
|
5102
|
+
const historyMessages = allMessages.slice(0, lastUserIdx);
|
|
5103
|
+
const currentTurn = allMessages.slice(lastUserIdx);
|
|
5104
|
+
if (historyMessages.length === 0) return allMessages;
|
|
5105
|
+
const cfg = configRef.current;
|
|
5106
|
+
const cs = compactionStateRef.current;
|
|
5107
|
+
const recentBuffer = cfg.recentBuffer ?? 10;
|
|
5108
|
+
const isCompactionMsg = (m) => m.metadata?.["type"] === "compaction-marker";
|
|
5109
|
+
const windowedHistory = [];
|
|
5110
|
+
if (cs.workingMemory.length > 0) {
|
|
5111
|
+
windowedHistory.push({
|
|
5112
|
+
id: "working-memory",
|
|
5113
|
+
role: "system",
|
|
5114
|
+
content: `[Working memory \u2014 always active]
|
|
5115
|
+
${cs.workingMemory.join("\n")}`,
|
|
5116
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
5117
|
+
});
|
|
5118
|
+
}
|
|
5119
|
+
if (cs.rollingSummary) {
|
|
5120
|
+
windowedHistory.push({
|
|
5121
|
+
id: "rolling-summary",
|
|
5122
|
+
role: "system",
|
|
5123
|
+
content: `[Previous conversation summary]
|
|
5124
|
+
${cs.rollingSummary}`,
|
|
5125
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
5126
|
+
});
|
|
5127
|
+
}
|
|
5128
|
+
const systemMsgs = historyMessages.filter(
|
|
5129
|
+
(m) => m.role === "system" && !isCompactionMsg(m)
|
|
5130
|
+
);
|
|
5131
|
+
windowedHistory.push(...systemMsgs);
|
|
5132
|
+
const conversationMsgs = historyMessages.filter(
|
|
5133
|
+
(m) => m.role !== "system"
|
|
5134
|
+
);
|
|
5135
|
+
const recentStart = Math.max(0, conversationMsgs.length - recentBuffer);
|
|
5136
|
+
windowedHistory.push(...conversationMsgs.slice(recentStart));
|
|
5137
|
+
return [...windowedHistory, ...currentTurn];
|
|
5138
|
+
});
|
|
5139
|
+
return () => {
|
|
5140
|
+
chatRef.current?.setRequestMessageTransform(null);
|
|
5141
|
+
};
|
|
5142
|
+
}, []);
|
|
5143
|
+
return null;
|
|
5144
|
+
}
|
|
2540
5145
|
var CopilotContext = createContext(null);
|
|
2541
5146
|
function useCopilot() {
|
|
2542
5147
|
const context = useContext(CopilotContext);
|
|
@@ -2560,11 +5165,14 @@ function CopilotProvider({
|
|
|
2560
5165
|
debug = false,
|
|
2561
5166
|
maxIterations,
|
|
2562
5167
|
maxIterationsMessage,
|
|
2563
|
-
mcpServers
|
|
5168
|
+
mcpServers,
|
|
5169
|
+
optimization,
|
|
5170
|
+
messageHistory,
|
|
5171
|
+
skills
|
|
2564
5172
|
}) {
|
|
2565
5173
|
const debugLog = useCallback(
|
|
2566
|
-
(
|
|
2567
|
-
|
|
5174
|
+
(action, data) => {
|
|
5175
|
+
createLogger("provider", () => debug ?? false)(action, data);
|
|
2568
5176
|
},
|
|
2569
5177
|
[debug]
|
|
2570
5178
|
);
|
|
@@ -2604,7 +5212,8 @@ function CopilotProvider({
|
|
|
2604
5212
|
body,
|
|
2605
5213
|
debug,
|
|
2606
5214
|
maxIterations,
|
|
2607
|
-
maxIterationsMessage
|
|
5215
|
+
maxIterationsMessage,
|
|
5216
|
+
optimization
|
|
2608
5217
|
},
|
|
2609
5218
|
{
|
|
2610
5219
|
onToolExecutionsChange: (executions) => {
|
|
@@ -2614,6 +5223,9 @@ function CopilotProvider({
|
|
|
2614
5223
|
onApprovalRequired: (execution) => {
|
|
2615
5224
|
debugLog("Tool approval required:", execution.name);
|
|
2616
5225
|
},
|
|
5226
|
+
onContextUsageChange: (usage) => {
|
|
5227
|
+
setContextUsage(usage);
|
|
5228
|
+
},
|
|
2617
5229
|
onError: (error2) => {
|
|
2618
5230
|
if (error2) onError?.(error2);
|
|
2619
5231
|
}
|
|
@@ -2644,19 +5256,27 @@ function CopilotProvider({
|
|
|
2644
5256
|
debugLog("URL config updated from prop");
|
|
2645
5257
|
}
|
|
2646
5258
|
}, [runtimeUrl, debugLog]);
|
|
5259
|
+
const EMPTY_MESSAGES = useRef([]);
|
|
5260
|
+
const getMessagesSnapshot = useCallback(() => chatRef.current.messages, []);
|
|
5261
|
+
const getServerMessagesSnapshot = useCallback(
|
|
5262
|
+
() => EMPTY_MESSAGES.current,
|
|
5263
|
+
[]
|
|
5264
|
+
);
|
|
5265
|
+
const getStatusSnapshot = useCallback(() => chatRef.current.status, []);
|
|
5266
|
+
const getErrorSnapshot = useCallback(() => chatRef.current.error, []);
|
|
2647
5267
|
const messages = useSyncExternalStore(
|
|
2648
5268
|
chatRef.current.subscribe,
|
|
2649
|
-
|
|
2650
|
-
|
|
5269
|
+
getMessagesSnapshot,
|
|
5270
|
+
getServerMessagesSnapshot
|
|
2651
5271
|
);
|
|
2652
5272
|
const status = useSyncExternalStore(
|
|
2653
5273
|
chatRef.current.subscribe,
|
|
2654
|
-
|
|
5274
|
+
getStatusSnapshot,
|
|
2655
5275
|
() => "ready"
|
|
2656
5276
|
);
|
|
2657
5277
|
const errorFromChat = useSyncExternalStore(
|
|
2658
5278
|
chatRef.current.subscribe,
|
|
2659
|
-
|
|
5279
|
+
getErrorSnapshot,
|
|
2660
5280
|
() => void 0
|
|
2661
5281
|
);
|
|
2662
5282
|
const error = errorFromChat ?? null;
|
|
@@ -2679,9 +5299,15 @@ function CopilotProvider({
|
|
|
2679
5299
|
},
|
|
2680
5300
|
[]
|
|
2681
5301
|
);
|
|
2682
|
-
const registeredTools =
|
|
2683
|
-
|
|
2684
|
-
|
|
5302
|
+
const registeredTools = useMemo(
|
|
5303
|
+
() => chatRef.current?.tools ?? [],
|
|
5304
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5305
|
+
[toolExecutions]
|
|
5306
|
+
// re-derive when tool executions change (tools change alongside)
|
|
5307
|
+
);
|
|
5308
|
+
const pendingApprovals = useMemo(
|
|
5309
|
+
() => toolExecutions.filter((e) => e.approvalStatus === "required"),
|
|
5310
|
+
[toolExecutions]
|
|
2685
5311
|
);
|
|
2686
5312
|
const actionsRef = useRef(/* @__PURE__ */ new Map());
|
|
2687
5313
|
const [actionsVersion, setActionsVersion] = useState(0);
|
|
@@ -2699,6 +5325,8 @@ function CopilotProvider({
|
|
|
2699
5325
|
);
|
|
2700
5326
|
const contextTreeRef = useRef([]);
|
|
2701
5327
|
const contextIdCounter = useRef(0);
|
|
5328
|
+
const [contextChars, setContextChars] = useState(0);
|
|
5329
|
+
const [contextUsage, setContextUsage] = useState(null);
|
|
2702
5330
|
const addContext = useCallback(
|
|
2703
5331
|
(context, parentId) => {
|
|
2704
5332
|
const id = `ctx-${++contextIdCounter.current}`;
|
|
@@ -2709,6 +5337,7 @@ function CopilotProvider({
|
|
|
2709
5337
|
);
|
|
2710
5338
|
const contextString = printTree(contextTreeRef.current);
|
|
2711
5339
|
chatRef.current?.setContext(contextString);
|
|
5340
|
+
setContextChars(contextString.length);
|
|
2712
5341
|
debugLog("Context added:", id);
|
|
2713
5342
|
return id;
|
|
2714
5343
|
},
|
|
@@ -2719,6 +5348,7 @@ function CopilotProvider({
|
|
|
2719
5348
|
contextTreeRef.current = removeNode(contextTreeRef.current, id);
|
|
2720
5349
|
const contextString = printTree(contextTreeRef.current);
|
|
2721
5350
|
chatRef.current?.setContext(contextString);
|
|
5351
|
+
setContextChars(contextString.length);
|
|
2722
5352
|
debugLog("Context removed:", id);
|
|
2723
5353
|
},
|
|
2724
5354
|
[debugLog]
|
|
@@ -2730,6 +5360,13 @@ function CopilotProvider({
|
|
|
2730
5360
|
},
|
|
2731
5361
|
[debugLog]
|
|
2732
5362
|
);
|
|
5363
|
+
const setInlineSkills = useCallback(
|
|
5364
|
+
(skills2) => {
|
|
5365
|
+
chatRef.current?.setInlineSkills(skills2);
|
|
5366
|
+
debugLog("Inline skills updated", { count: skills2.length });
|
|
5367
|
+
},
|
|
5368
|
+
[debugLog]
|
|
5369
|
+
);
|
|
2733
5370
|
const sendMessage = useCallback(
|
|
2734
5371
|
async (content, attachments) => {
|
|
2735
5372
|
debugLog("Sending message:", content);
|
|
@@ -2749,15 +5386,46 @@ function CopilotProvider({
|
|
|
2749
5386
|
const regenerate = useCallback(async (messageId) => {
|
|
2750
5387
|
await chatRef.current?.regenerate(messageId);
|
|
2751
5388
|
}, []);
|
|
5389
|
+
const switchBranch = useCallback((messageId) => {
|
|
5390
|
+
chatRef.current?.switchBranch(messageId);
|
|
5391
|
+
}, []);
|
|
5392
|
+
const getBranchInfo = useCallback(
|
|
5393
|
+
(messageId) => chatRef.current?.getBranchInfo(messageId) ?? null,
|
|
5394
|
+
[]
|
|
5395
|
+
);
|
|
5396
|
+
const editMessage = useCallback(
|
|
5397
|
+
async (messageId, newContent) => {
|
|
5398
|
+
await chatRef.current?.sendMessage(newContent, void 0, {
|
|
5399
|
+
editMessageId: messageId
|
|
5400
|
+
});
|
|
5401
|
+
},
|
|
5402
|
+
[]
|
|
5403
|
+
);
|
|
5404
|
+
const getHasBranchesSnapshot = useCallback(
|
|
5405
|
+
() => chatRef.current.hasBranches,
|
|
5406
|
+
[]
|
|
5407
|
+
);
|
|
5408
|
+
const hasBranches = useSyncExternalStore(
|
|
5409
|
+
chatRef.current.subscribe,
|
|
5410
|
+
getHasBranchesSnapshot,
|
|
5411
|
+
() => false
|
|
5412
|
+
);
|
|
5413
|
+
const getAllMessages = useCallback(
|
|
5414
|
+
() => chatRef.current?.getAllMessages?.() ?? [],
|
|
5415
|
+
[]
|
|
5416
|
+
);
|
|
2752
5417
|
useEffect(() => {
|
|
2753
5418
|
if (onMessagesChange && messages.length > 0) {
|
|
2754
|
-
const
|
|
5419
|
+
const allUIMessages = chatRef.current?.getAllMessages?.() ?? messages;
|
|
5420
|
+
const coreMessages = allUIMessages.map((m) => ({
|
|
2755
5421
|
id: m.id,
|
|
2756
5422
|
role: m.role,
|
|
2757
5423
|
content: m.content,
|
|
2758
5424
|
created_at: m.createdAt,
|
|
2759
5425
|
tool_calls: m.toolCalls,
|
|
2760
5426
|
tool_call_id: m.toolCallId,
|
|
5427
|
+
parent_id: m.parentId,
|
|
5428
|
+
children_ids: m.childrenIds,
|
|
2761
5429
|
metadata: {
|
|
2762
5430
|
attachments: m.attachments,
|
|
2763
5431
|
thinking: m.thinking
|
|
@@ -2789,6 +5457,12 @@ function CopilotProvider({
|
|
|
2789
5457
|
clearMessages,
|
|
2790
5458
|
setMessages,
|
|
2791
5459
|
regenerate,
|
|
5460
|
+
// Branching
|
|
5461
|
+
switchBranch,
|
|
5462
|
+
getBranchInfo,
|
|
5463
|
+
editMessage,
|
|
5464
|
+
hasBranches,
|
|
5465
|
+
getAllMessages,
|
|
2792
5466
|
// Tool execution
|
|
2793
5467
|
registerTool,
|
|
2794
5468
|
unregisterTool,
|
|
@@ -2804,8 +5478,12 @@ function CopilotProvider({
|
|
|
2804
5478
|
// AI Context
|
|
2805
5479
|
addContext,
|
|
2806
5480
|
removeContext,
|
|
5481
|
+
contextChars,
|
|
5482
|
+
contextUsage,
|
|
2807
5483
|
// System Prompt
|
|
2808
5484
|
setSystemPrompt,
|
|
5485
|
+
// Skills
|
|
5486
|
+
setInlineSkills,
|
|
2809
5487
|
// Config
|
|
2810
5488
|
threadId,
|
|
2811
5489
|
runtimeUrl,
|
|
@@ -2821,6 +5499,11 @@ function CopilotProvider({
|
|
|
2821
5499
|
clearMessages,
|
|
2822
5500
|
setMessages,
|
|
2823
5501
|
regenerate,
|
|
5502
|
+
switchBranch,
|
|
5503
|
+
getBranchInfo,
|
|
5504
|
+
editMessage,
|
|
5505
|
+
hasBranches,
|
|
5506
|
+
getAllMessages,
|
|
2824
5507
|
registerTool,
|
|
2825
5508
|
unregisterTool,
|
|
2826
5509
|
registeredTools,
|
|
@@ -2833,16 +5516,41 @@ function CopilotProvider({
|
|
|
2833
5516
|
registeredActions,
|
|
2834
5517
|
addContext,
|
|
2835
5518
|
removeContext,
|
|
5519
|
+
contextChars,
|
|
5520
|
+
contextUsage,
|
|
2836
5521
|
setSystemPrompt,
|
|
5522
|
+
setInlineSkills,
|
|
2837
5523
|
threadId,
|
|
2838
5524
|
runtimeUrl,
|
|
2839
5525
|
toolsConfig
|
|
2840
5526
|
]
|
|
2841
5527
|
);
|
|
2842
|
-
|
|
5528
|
+
const messageHistoryContextValue = React2.useMemo(
|
|
5529
|
+
() => ({
|
|
5530
|
+
config: { ...defaultMessageHistoryConfig, ...messageHistory },
|
|
5531
|
+
tokenUsage: {
|
|
5532
|
+
current: 0,
|
|
5533
|
+
max: messageHistory?.maxContextTokens ?? 128e3,
|
|
5534
|
+
percentage: 0,
|
|
5535
|
+
isApproaching: false
|
|
5536
|
+
},
|
|
5537
|
+
compactionState: {
|
|
5538
|
+
rollingSummary: null,
|
|
5539
|
+
lastCompactionAt: null,
|
|
5540
|
+
compactionCount: 0,
|
|
5541
|
+
totalTokensSaved: 0,
|
|
5542
|
+
workingMemory: [],
|
|
5543
|
+
displayMessageCount: 0,
|
|
5544
|
+
llmMessageCount: 0
|
|
5545
|
+
}
|
|
5546
|
+
}),
|
|
5547
|
+
[messageHistory]
|
|
5548
|
+
);
|
|
5549
|
+
return /* @__PURE__ */ jsx(MessageHistoryContext.Provider, { value: messageHistoryContextValue, children: /* @__PURE__ */ jsxs(CopilotContext.Provider, { value: contextValue, children: [
|
|
2843
5550
|
mcpServers?.map((config) => /* @__PURE__ */ jsx(MCPConnection, { config }, config.name)),
|
|
2844
|
-
|
|
2845
|
-
|
|
5551
|
+
messageHistory?.strategy && messageHistory.strategy !== "none" && /* @__PURE__ */ jsx(MessageHistoryBridge, { chatRef }),
|
|
5552
|
+
skills ? /* @__PURE__ */ jsx(SkillProvider, { skills, children }) : children
|
|
5553
|
+
] }) });
|
|
2846
5554
|
}
|
|
2847
5555
|
function useAIActions(actions) {
|
|
2848
5556
|
const { registerAction, unregisterAction } = useCopilot();
|
|
@@ -2860,61 +5568,6 @@ function useAIActions(actions) {
|
|
|
2860
5568
|
function useAIAction(action) {
|
|
2861
5569
|
useAIActions([action]);
|
|
2862
5570
|
}
|
|
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
5571
|
function useAITools(options = {}) {
|
|
2919
5572
|
const {
|
|
2920
5573
|
screenshot = false,
|
|
@@ -3159,58 +5812,6 @@ function useAITools(options = {}) {
|
|
|
3159
5812
|
]
|
|
3160
5813
|
);
|
|
3161
5814
|
}
|
|
3162
|
-
function useTool(config, dependencies = []) {
|
|
3163
|
-
const { registerTool, unregisterTool } = useCopilot();
|
|
3164
|
-
const configRef = useRef(config);
|
|
3165
|
-
configRef.current = config;
|
|
3166
|
-
useEffect(() => {
|
|
3167
|
-
const tool2 = {
|
|
3168
|
-
name: config.name,
|
|
3169
|
-
description: config.description,
|
|
3170
|
-
location: "client",
|
|
3171
|
-
inputSchema: config.inputSchema,
|
|
3172
|
-
handler: async (params, context) => {
|
|
3173
|
-
return configRef.current.handler(params, context);
|
|
3174
|
-
},
|
|
3175
|
-
render: config.render,
|
|
3176
|
-
available: config.available ?? true,
|
|
3177
|
-
needsApproval: config.needsApproval,
|
|
3178
|
-
approvalMessage: config.approvalMessage,
|
|
3179
|
-
hidden: config.hidden
|
|
3180
|
-
};
|
|
3181
|
-
registerTool(tool2);
|
|
3182
|
-
return () => {
|
|
3183
|
-
unregisterTool(config.name);
|
|
3184
|
-
};
|
|
3185
|
-
}, [config.name, ...dependencies]);
|
|
3186
|
-
}
|
|
3187
|
-
function useTools(tools) {
|
|
3188
|
-
const { registerTool, unregisterTool } = useCopilot();
|
|
3189
|
-
const registeredToolsRef = useRef([]);
|
|
3190
|
-
const toolsRef = useRef(tools);
|
|
3191
|
-
toolsRef.current = tools;
|
|
3192
|
-
const toolsKey = Object.keys(tools).sort().join(",");
|
|
3193
|
-
useEffect(() => {
|
|
3194
|
-
const currentTools = toolsRef.current;
|
|
3195
|
-
const toolNames = [];
|
|
3196
|
-
for (const [name, toolDef] of Object.entries(currentTools)) {
|
|
3197
|
-
const fullTool = {
|
|
3198
|
-
...toolDef,
|
|
3199
|
-
name
|
|
3200
|
-
// Use the key as the name
|
|
3201
|
-
};
|
|
3202
|
-
registerTool(fullTool);
|
|
3203
|
-
toolNames.push(name);
|
|
3204
|
-
}
|
|
3205
|
-
registeredToolsRef.current = toolNames;
|
|
3206
|
-
return () => {
|
|
3207
|
-
for (const name of registeredToolsRef.current) {
|
|
3208
|
-
unregisterTool(name);
|
|
3209
|
-
}
|
|
3210
|
-
registeredToolsRef.current = [];
|
|
3211
|
-
};
|
|
3212
|
-
}, [toolsKey]);
|
|
3213
|
-
}
|
|
3214
5815
|
function convertZodSchema(schema, _toolName) {
|
|
3215
5816
|
try {
|
|
3216
5817
|
const zodWithJsonSchema = z;
|
|
@@ -4398,6 +6999,94 @@ function createToolIntentHandler(callTool) {
|
|
|
4398
6999
|
}
|
|
4399
7000
|
};
|
|
4400
7001
|
}
|
|
7002
|
+
function getLastResponseUsage(messages) {
|
|
7003
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
7004
|
+
const msg = messages[i];
|
|
7005
|
+
if (msg.role === "assistant" && msg.metadata?.usage) {
|
|
7006
|
+
const u = msg.metadata.usage;
|
|
7007
|
+
const prompt = u.prompt_tokens ?? 0;
|
|
7008
|
+
const completion = u.completion_tokens ?? 0;
|
|
7009
|
+
return {
|
|
7010
|
+
prompt_tokens: prompt,
|
|
7011
|
+
completion_tokens: completion,
|
|
7012
|
+
total_tokens: u.total_tokens ?? prompt + completion
|
|
7013
|
+
};
|
|
7014
|
+
}
|
|
7015
|
+
}
|
|
7016
|
+
return null;
|
|
7017
|
+
}
|
|
7018
|
+
function useContextStats() {
|
|
7019
|
+
const { contextChars, contextUsage, registeredTools, messages } = useCopilot();
|
|
7020
|
+
const toolCount = useMemo(() => registeredTools.length, [registeredTools]);
|
|
7021
|
+
const messageCount = useMemo(
|
|
7022
|
+
() => messages.filter((m) => m.role !== "system").length,
|
|
7023
|
+
[messages]
|
|
7024
|
+
);
|
|
7025
|
+
const totalTokens = useMemo(() => {
|
|
7026
|
+
if (contextUsage) return contextUsage.total.tokens;
|
|
7027
|
+
return Math.ceil(contextChars / 3.5);
|
|
7028
|
+
}, [contextUsage, contextChars]);
|
|
7029
|
+
const usagePercent = useMemo(() => {
|
|
7030
|
+
if (contextUsage) return contextUsage.total.percent;
|
|
7031
|
+
return 0;
|
|
7032
|
+
}, [contextUsage]);
|
|
7033
|
+
const lastResponseUsage = useMemo(
|
|
7034
|
+
() => getLastResponseUsage(messages),
|
|
7035
|
+
[messages]
|
|
7036
|
+
);
|
|
7037
|
+
return {
|
|
7038
|
+
contextUsage,
|
|
7039
|
+
totalTokens,
|
|
7040
|
+
usagePercent,
|
|
7041
|
+
contextChars,
|
|
7042
|
+
toolCount,
|
|
7043
|
+
messageCount,
|
|
7044
|
+
lastResponseUsage
|
|
7045
|
+
};
|
|
7046
|
+
}
|
|
7047
|
+
var DEV_CONTENT_WARN_THRESHOLD = 2e3;
|
|
7048
|
+
function useSkill(skill) {
|
|
7049
|
+
const { register, unregister } = useSkillContext();
|
|
7050
|
+
if (process.env.NODE_ENV !== "production" && skill.source.type === "inline" && skill.source.content.length > DEV_CONTENT_WARN_THRESHOLD) {
|
|
7051
|
+
console.warn(
|
|
7052
|
+
`[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.`
|
|
7053
|
+
);
|
|
7054
|
+
}
|
|
7055
|
+
useEffect(() => {
|
|
7056
|
+
if (skill.source.type !== "inline") {
|
|
7057
|
+
console.warn(
|
|
7058
|
+
`[copilot-sdk/skills] useSkill only supports inline skills client-side. Skill "${skill.name}" has source type "${skill.source.type}" and will be skipped.`
|
|
7059
|
+
);
|
|
7060
|
+
return;
|
|
7061
|
+
}
|
|
7062
|
+
const resolved = {
|
|
7063
|
+
...skill,
|
|
7064
|
+
content: skill.source.content
|
|
7065
|
+
};
|
|
7066
|
+
register(resolved);
|
|
7067
|
+
return () => {
|
|
7068
|
+
unregister(skill.name);
|
|
7069
|
+
};
|
|
7070
|
+
}, [
|
|
7071
|
+
skill.name,
|
|
7072
|
+
skill.source.type === "inline" ? skill.source.content : "",
|
|
7073
|
+
skill.strategy,
|
|
7074
|
+
skill.description
|
|
7075
|
+
]);
|
|
7076
|
+
}
|
|
7077
|
+
function useSkillStatus() {
|
|
7078
|
+
const { skills, registry } = useSkillContext();
|
|
7079
|
+
const has = useCallback(
|
|
7080
|
+
(name) => registry.has(name),
|
|
7081
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
7082
|
+
[skills]
|
|
7083
|
+
);
|
|
7084
|
+
return {
|
|
7085
|
+
skills,
|
|
7086
|
+
count: skills.length,
|
|
7087
|
+
has
|
|
7088
|
+
};
|
|
7089
|
+
}
|
|
4401
7090
|
|
|
4402
7091
|
// src/react/utils/permission-storage.ts
|
|
4403
7092
|
var DEFAULT_KEY_PREFIX = "yourgpt-permissions";
|
|
@@ -4550,6 +7239,34 @@ var ReactChat = class extends AbstractChat {
|
|
|
4550
7239
|
return this.on("error", handler);
|
|
4551
7240
|
}
|
|
4552
7241
|
// ============================================
|
|
7242
|
+
// Branching API — pass-throughs to ReactChatState
|
|
7243
|
+
// ============================================
|
|
7244
|
+
/**
|
|
7245
|
+
* Navigate to a sibling branch (makes it the active path).
|
|
7246
|
+
*/
|
|
7247
|
+
switchBranch(messageId) {
|
|
7248
|
+
this.reactState.switchBranch(messageId);
|
|
7249
|
+
}
|
|
7250
|
+
/**
|
|
7251
|
+
* Get branch navigation info for a message.
|
|
7252
|
+
* Returns null if the message has no siblings.
|
|
7253
|
+
*/
|
|
7254
|
+
getBranchInfo(messageId) {
|
|
7255
|
+
return this.reactState.getBranchInfo(messageId);
|
|
7256
|
+
}
|
|
7257
|
+
/**
|
|
7258
|
+
* Get all messages across all branches (for persistence).
|
|
7259
|
+
*/
|
|
7260
|
+
getAllMessages() {
|
|
7261
|
+
return this.reactState.getAllMessages();
|
|
7262
|
+
}
|
|
7263
|
+
/**
|
|
7264
|
+
* Whether any message has siblings (branching has occurred).
|
|
7265
|
+
*/
|
|
7266
|
+
get hasBranches() {
|
|
7267
|
+
return this.reactState.hasBranches;
|
|
7268
|
+
}
|
|
7269
|
+
// ============================================
|
|
4553
7270
|
// Override dispose to clean up state
|
|
4554
7271
|
// ============================================
|
|
4555
7272
|
dispose() {
|
|
@@ -4609,6 +7326,11 @@ function useChat(config) {
|
|
|
4609
7326
|
() => void 0
|
|
4610
7327
|
// Server snapshot
|
|
4611
7328
|
);
|
|
7329
|
+
const hasBranches = useSyncExternalStore(
|
|
7330
|
+
chatRef.current.subscribe,
|
|
7331
|
+
() => chatRef.current.hasBranches,
|
|
7332
|
+
() => false
|
|
7333
|
+
);
|
|
4612
7334
|
const isLoading = status === "streaming" || status === "submitted";
|
|
4613
7335
|
const sendMessage = useCallback(
|
|
4614
7336
|
async (content, attachments) => {
|
|
@@ -4635,6 +7357,24 @@ function useChat(config) {
|
|
|
4635
7357
|
},
|
|
4636
7358
|
[]
|
|
4637
7359
|
);
|
|
7360
|
+
const switchBranch = useCallback((messageId) => {
|
|
7361
|
+
chatRef.current?.switchBranch(messageId);
|
|
7362
|
+
}, []);
|
|
7363
|
+
const getBranchInfo = useCallback(
|
|
7364
|
+
(messageId) => {
|
|
7365
|
+
return chatRef.current?.getBranchInfo(messageId) ?? null;
|
|
7366
|
+
},
|
|
7367
|
+
[]
|
|
7368
|
+
);
|
|
7369
|
+
const editMessage = useCallback(
|
|
7370
|
+
async (messageId, newContent) => {
|
|
7371
|
+
await chatRef.current?.sendMessage(newContent, void 0, {
|
|
7372
|
+
editMessageId: messageId
|
|
7373
|
+
});
|
|
7374
|
+
setInput("");
|
|
7375
|
+
},
|
|
7376
|
+
[]
|
|
7377
|
+
);
|
|
4638
7378
|
useEffect(() => {
|
|
4639
7379
|
return () => {
|
|
4640
7380
|
chatRef.current?.dispose();
|
|
@@ -4653,10 +7393,20 @@ function useChat(config) {
|
|
|
4653
7393
|
setMessages,
|
|
4654
7394
|
regenerate,
|
|
4655
7395
|
continueWithToolResults,
|
|
4656
|
-
chatRef
|
|
7396
|
+
chatRef,
|
|
7397
|
+
// Branching
|
|
7398
|
+
switchBranch,
|
|
7399
|
+
getBranchInfo,
|
|
7400
|
+
editMessage,
|
|
7401
|
+
hasBranches
|
|
4657
7402
|
};
|
|
4658
7403
|
}
|
|
4659
7404
|
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
7405
|
+
// src/react/skill/define-skill.ts
|
|
7406
|
+
function defineSkill(def) {
|
|
7407
|
+
return def;
|
|
7408
|
+
}
|
|
7409
|
+
|
|
7410
|
+
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 };
|
|
7411
|
+
//# sourceMappingURL=chunk-6BMQZIS3.js.map
|
|
7412
|
+
//# sourceMappingURL=chunk-6BMQZIS3.js.map
|