@yeaft/webchat-agent 1.0.464 → 1.0.465
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/local-runtime/version.json +1 -1
- package/package.json +1 -1
- package/yeaft/history-window.js +180 -35
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"1.0.
|
|
1
|
+
{"version":"1.0.465"}
|
package/package.json
CHANGED
package/yeaft/history-window.js
CHANGED
|
@@ -195,14 +195,21 @@ function hasContentAfterToolStrip(content) {
|
|
|
195
195
|
return content != null;
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
-
function
|
|
198
|
+
function toolContentPartCallId(part) {
|
|
199
|
+
if (!part || typeof part !== 'object') return null;
|
|
200
|
+
return part.id || part.tool_use_id || part.call_id || null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function stripToolContentParts(content, selectedCallIds = null) {
|
|
199
204
|
if (!Array.isArray(content)) return content;
|
|
200
205
|
return content.filter(part => {
|
|
201
206
|
if (!part || typeof part !== 'object') return true;
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
207
|
+
const isToolPart = part.type === 'tool_use'
|
|
208
|
+
|| part.type === 'tool_result'
|
|
209
|
+
|| part.type === 'function_call'
|
|
210
|
+
|| part.type === 'function_call_output';
|
|
211
|
+
if (!isToolPart) return true;
|
|
212
|
+
return selectedCallIds instanceof Set && selectedCallIds.has(toolContentPartCallId(part));
|
|
206
213
|
});
|
|
207
214
|
}
|
|
208
215
|
|
|
@@ -232,6 +239,7 @@ export function stripToolNoiseFromOlderTurns(messages, options = {}) {
|
|
|
232
239
|
|
|
233
240
|
const next = { ...message };
|
|
234
241
|
if (Array.isArray(next.toolCalls)) delete next.toolCalls;
|
|
242
|
+
if (Array.isArray(next.thinkingBlocks)) delete next.thinkingBlocks;
|
|
235
243
|
if (Array.isArray(next.content)) next.content = stripToolContentParts(next.content);
|
|
236
244
|
if (next.role === 'assistant' && !hasContentAfterToolStrip(next.content)) continue;
|
|
237
245
|
if (next.role === 'user' && Array.isArray(next.content) && next.content.length === 0) continue;
|
|
@@ -240,6 +248,21 @@ export function stripToolNoiseFromOlderTurns(messages, options = {}) {
|
|
|
240
248
|
return [...cleanedOlder, ...recent.map(message => ({ ...message }))];
|
|
241
249
|
}
|
|
242
250
|
|
|
251
|
+
function stripAllToolNoise(messages) {
|
|
252
|
+
const cleaned = [];
|
|
253
|
+
for (const message of messages) {
|
|
254
|
+
if (!message || typeof message !== 'object' || message.role === 'tool') continue;
|
|
255
|
+
const next = { ...message };
|
|
256
|
+
if (Array.isArray(next.toolCalls)) delete next.toolCalls;
|
|
257
|
+
if (Array.isArray(next.thinkingBlocks)) delete next.thinkingBlocks;
|
|
258
|
+
if (Array.isArray(next.content)) next.content = stripToolContentParts(next.content);
|
|
259
|
+
if (next.role === 'assistant' && !hasContentAfterToolStrip(next.content)) continue;
|
|
260
|
+
if (next.role === 'user' && Array.isArray(next.content) && next.content.length === 0) continue;
|
|
261
|
+
cleaned.push(next);
|
|
262
|
+
}
|
|
263
|
+
return cleaned;
|
|
264
|
+
}
|
|
265
|
+
|
|
243
266
|
function truncateTextToTokens(text, tokenBudget) {
|
|
244
267
|
if (typeof text !== 'string' || tokenBudget <= 0) return '';
|
|
245
268
|
if (estimateTokens(text) <= tokenBudget) return text;
|
|
@@ -411,6 +434,21 @@ function dropOldestHistoryUntilBudget(messages, tokenBudget) {
|
|
|
411
434
|
return out;
|
|
412
435
|
}
|
|
413
436
|
|
|
437
|
+
function dropOldestHistoryUntilMessageCap(messages, maxMessageCount) {
|
|
438
|
+
let out = pairSanitize(messages);
|
|
439
|
+
let turns = countTurns(out);
|
|
440
|
+
while (out.length > maxMessageCount && turns > 1) {
|
|
441
|
+
const next = pairSanitize(sliceLastNTurns(out, turns - 1));
|
|
442
|
+
if (next.length === out.length) break;
|
|
443
|
+
out = next;
|
|
444
|
+
turns = countTurns(out);
|
|
445
|
+
}
|
|
446
|
+
if (out.length > maxMessageCount) {
|
|
447
|
+
out = pairSanitize(out.slice(-maxMessageCount));
|
|
448
|
+
}
|
|
449
|
+
return out;
|
|
450
|
+
}
|
|
451
|
+
|
|
414
452
|
function providerUnits(messages) {
|
|
415
453
|
const units = [];
|
|
416
454
|
for (let index = 0; index < messages.length;) {
|
|
@@ -489,9 +527,7 @@ function fitMessagesToBudget(messages, tokenBudget) {
|
|
|
489
527
|
function truncateToolResultsForModel(messages, options = {}) {
|
|
490
528
|
if (!Array.isArray(messages) || messages.length === 0) return [];
|
|
491
529
|
return messages.map(message => {
|
|
492
|
-
if (!message || message.role !== 'tool'
|
|
493
|
-
return { ...message };
|
|
494
|
-
}
|
|
530
|
+
if (!message || message.role !== 'tool') return { ...message };
|
|
495
531
|
return {
|
|
496
532
|
...message,
|
|
497
533
|
content: truncateToolResultIfNeeded(message.content, {
|
|
@@ -502,16 +538,124 @@ function truncateToolResultsForModel(messages, options = {}) {
|
|
|
502
538
|
});
|
|
503
539
|
}
|
|
504
540
|
|
|
541
|
+
const HISTORY_SOURCE_INDEX = Symbol('historySourceIndex');
|
|
542
|
+
|
|
543
|
+
function withHistorySourceIndexes(messages) {
|
|
544
|
+
return messages.map((message, index) => (
|
|
545
|
+
message && typeof message === 'object'
|
|
546
|
+
? { ...message, [HISTORY_SOURCE_INDEX]: index }
|
|
547
|
+
: message
|
|
548
|
+
));
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function withoutHistorySourceIndexes(messages) {
|
|
552
|
+
return messages.map(message => {
|
|
553
|
+
if (!message || typeof message !== 'object') return message;
|
|
554
|
+
const next = { ...message };
|
|
555
|
+
delete next[HISTORY_SOURCE_INDEX];
|
|
556
|
+
return next;
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function completeToolCallIds(messages) {
|
|
561
|
+
const resultIds = new Set(messages
|
|
562
|
+
.filter(message => message?.role === 'tool' && typeof message.toolCallId === 'string')
|
|
563
|
+
.map(message => message.toolCallId));
|
|
564
|
+
const ids = [];
|
|
565
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
566
|
+
const message = messages[index];
|
|
567
|
+
if (message?.role !== 'assistant' || !Array.isArray(message.toolCalls)) continue;
|
|
568
|
+
for (let callIndex = message.toolCalls.length - 1; callIndex >= 0; callIndex -= 1) {
|
|
569
|
+
const id = message.toolCalls[callIndex]?.id;
|
|
570
|
+
if (typeof id === 'string' && resultIds.has(id)) ids.push(id);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return ids;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function enrichTextBaselineWithTools(textBaseline, toolSource, selectedCallIds) {
|
|
577
|
+
const messagesBySourceIndex = new Map();
|
|
578
|
+
for (const message of textBaseline) {
|
|
579
|
+
if (Number.isInteger(message?.[HISTORY_SOURCE_INDEX])) {
|
|
580
|
+
messagesBySourceIndex.set(message[HISTORY_SOURCE_INDEX], { ...message });
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
for (const source of toolSource) {
|
|
585
|
+
const sourceIndex = source?.[HISTORY_SOURCE_INDEX];
|
|
586
|
+
if (!Number.isInteger(sourceIndex)) continue;
|
|
587
|
+
if (source.role === 'tool') {
|
|
588
|
+
if (selectedCallIds.has(source.toolCallId)) {
|
|
589
|
+
messagesBySourceIndex.set(sourceIndex, { ...source });
|
|
590
|
+
}
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
593
|
+
if (source.role !== 'assistant' || !Array.isArray(source.toolCalls)) continue;
|
|
594
|
+
|
|
595
|
+
const selectedCalls = source.toolCalls.filter(call => selectedCallIds.has(call?.id));
|
|
596
|
+
if (selectedCalls.length === 0) continue;
|
|
597
|
+
const selectedIds = new Set(selectedCalls.map(call => call.id));
|
|
598
|
+
const baseline = messagesBySourceIndex.get(sourceIndex);
|
|
599
|
+
const owner = baseline ? { ...baseline } : { ...source };
|
|
600
|
+
owner.toolCalls = selectedCalls;
|
|
601
|
+
if (Array.isArray(source.thinkingBlocks)) owner.thinkingBlocks = source.thinkingBlocks;
|
|
602
|
+
if (Array.isArray(source.content)) {
|
|
603
|
+
const baselineContent = Array.isArray(baseline?.content)
|
|
604
|
+
? baseline.content
|
|
605
|
+
: stripToolContentParts(source.content);
|
|
606
|
+
const selectedParts = source.content.filter(part => selectedIds.has(toolContentPartCallId(part)));
|
|
607
|
+
owner.content = [...baselineContent, ...selectedParts];
|
|
608
|
+
}
|
|
609
|
+
messagesBySourceIndex.set(sourceIndex, owner);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
return pairSanitize([...messagesBySourceIndex.entries()]
|
|
613
|
+
.sort(([left], [right]) => left - right)
|
|
614
|
+
.map(([, message]) => message));
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function preservesTextBaseline(textBaseline, enriched) {
|
|
618
|
+
const projected = stripAllToolNoise(enriched);
|
|
619
|
+
if (projected.length !== textBaseline.length) return false;
|
|
620
|
+
return textBaseline.every((baseline, index) => {
|
|
621
|
+
const candidate = projected[index];
|
|
622
|
+
return candidate?.[HISTORY_SOURCE_INDEX] === baseline?.[HISTORY_SOURCE_INDEX]
|
|
623
|
+
&& candidate?.role === baseline?.role
|
|
624
|
+
&& serializeJsonValue(candidate?.content) === serializeJsonValue(baseline?.content);
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function addOptionalRecentToolPairs(textBaseline, toolSource, options) {
|
|
629
|
+
const selectedCallIds = new Set();
|
|
630
|
+
let out = pairSanitize(textBaseline);
|
|
631
|
+
for (const callId of completeToolCallIds(toolSource)) {
|
|
632
|
+
const trialIds = new Set(selectedCallIds);
|
|
633
|
+
trialIds.add(callId);
|
|
634
|
+
const trial = enrichTextBaselineWithTools(textBaseline, toolSource, trialIds);
|
|
635
|
+
if (trial.length > options.maxMessageCount) continue;
|
|
636
|
+
const fitted = fitMessagesToBudget(trial, options.messageTokenBudget);
|
|
637
|
+
const fittedCallIds = new Set(completeToolCallIds(fitted));
|
|
638
|
+
if (![...trialIds].every(id => fittedCallIds.has(id))) continue;
|
|
639
|
+
if (!preservesTextBaseline(textBaseline, fitted)) continue;
|
|
640
|
+
selectedCallIds.add(callId);
|
|
641
|
+
out = fitted;
|
|
642
|
+
}
|
|
643
|
+
return out;
|
|
644
|
+
}
|
|
645
|
+
|
|
505
646
|
/**
|
|
506
647
|
* Build a bounded, pair-safe copy for one provider request.
|
|
507
648
|
*
|
|
508
649
|
* The transform is deterministic and non-persistent:
|
|
509
|
-
* 1. keep at most `recentTurnCap` turns
|
|
510
|
-
* 2.
|
|
511
|
-
* 3.
|
|
512
|
-
* 4.
|
|
650
|
+
* 1. keep at most `recentTurnCap` turns;
|
|
651
|
+
* 2. build the text-only history baseline and fit it to row/token caps;
|
|
652
|
+
* 3. consider complete tool/function pairs from the newest `keepToolTurns`;
|
|
653
|
+
* 4. add each recent pair only while the fitted text baseline still fits;
|
|
513
654
|
* 5. remove orphan tool pairs.
|
|
514
655
|
*
|
|
656
|
+
* Tool replay is optional enrichment. Text history gets the hard row/token
|
|
657
|
+
* budget first so even recent function payloads cannot evict textual turns.
|
|
658
|
+
*
|
|
515
659
|
* @param {Array<object>} snapshot
|
|
516
660
|
* @param {{ messageTokenBudget?: number, recentTurnCap?: number, maxMessageCount?: number, keepToolTurns?: number, language?: string }} [options]
|
|
517
661
|
* @returns {Array<object>}
|
|
@@ -529,25 +673,24 @@ export function trimSnapshotForBudget(snapshot, options = {}) {
|
|
|
529
673
|
? Math.floor(options.maxMessageCount)
|
|
530
674
|
: DEFAULT_RUNTIME_CACHE_MESSAGE_CAP;
|
|
531
675
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
676
|
+
const keepToolTurns = Number.isFinite(options.keepToolTurns) && options.keepToolTurns >= 0
|
|
677
|
+
? Math.floor(options.keepToolTurns)
|
|
678
|
+
: DEFAULT_KEEP_TOOL_TURNS;
|
|
679
|
+
const bounded = withHistorySourceIndexes(sliceLastNTurns(snapshot, recentTurnCap));
|
|
680
|
+
let textBaseline = stripAllToolNoise(bounded);
|
|
681
|
+
textBaseline = dropOldestHistoryUntilMessageCap(textBaseline, maxMessageCount);
|
|
682
|
+
textBaseline = fitMessagesToBudget(textBaseline, messageTokenBudget);
|
|
683
|
+
|
|
684
|
+
const toolCutIndex = indexOfNthTurnFromEnd(bounded, keepToolTurns);
|
|
685
|
+
const recentToolSource = keepToolTurns > 0
|
|
686
|
+
? bounded.slice(toolCutIndex < 0 ? 0 : toolCutIndex)
|
|
687
|
+
: [];
|
|
688
|
+
const truncatedToolSource = truncateToolResultsForModel(recentToolSource, { language: options.language });
|
|
689
|
+
const enriched = addOptionalRecentToolPairs(textBaseline, truncatedToolSource, {
|
|
690
|
+
maxMessageCount,
|
|
691
|
+
messageTokenBudget,
|
|
547
692
|
});
|
|
548
|
-
|
|
549
|
-
trimmed = pairSanitize(trimmed);
|
|
550
|
-
return fitMessagesToBudget(trimmed, messageTokenBudget);
|
|
693
|
+
return withoutHistorySourceIndexes(enriched);
|
|
551
694
|
}
|
|
552
695
|
|
|
553
696
|
/**
|
|
@@ -560,11 +703,13 @@ export function trimSnapshotForBudget(snapshot, options = {}) {
|
|
|
560
703
|
* @returns {Array<object>}
|
|
561
704
|
*/
|
|
562
705
|
export function trimHistoryCacheForRuntime(snapshot, options = {}) {
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
maxMessageCount: DEFAULT_RUNTIME_CACHE_MESSAGE_CAP,
|
|
706
|
+
if (!Array.isArray(snapshot) || snapshot.length === 0) return [];
|
|
707
|
+
let trimmed = sliceLastNTurns(snapshot, DEFAULT_RUNTIME_CACHE_TURN_CAP);
|
|
708
|
+
trimmed = stripToolNoiseFromOlderTurns(trimmed, {
|
|
567
709
|
keepToolTurns: DEFAULT_KEEP_TOOL_TURNS,
|
|
568
|
-
language: options.language,
|
|
569
710
|
});
|
|
711
|
+
trimmed = dropOldestHistoryUntilMessageCap(trimmed, DEFAULT_RUNTIME_CACHE_MESSAGE_CAP);
|
|
712
|
+
trimmed = truncateToolResultsForModel(trimmed, { language: options.language });
|
|
713
|
+
trimmed = pairSanitize(trimmed);
|
|
714
|
+
return fitMessagesToBudget(trimmed, DEFAULT_RUNTIME_CACHE_TOKEN_BUDGET);
|
|
570
715
|
}
|