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