@genesislcap/ai-assistant 15.19.6 → 15.20.0-alpha.3
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/ai-assistant.api.json +707 -69
- package/dist/ai-assistant.d.ts +443 -25
- package/dist/chat-driver.cjs +440 -34
- package/dist/chat-driver.cjs.map +4 -4
- package/dist/chat-driver.mjs +440 -34
- package/dist/chat-driver.mjs.map +4 -4
- package/dist/custom-elements.json +709 -47
- package/dist/dts/components/ai-driver/ai-driver.d.ts +40 -7
- package/dist/dts/components/ai-driver/ai-driver.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/chat-driver.d.ts +81 -2
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +23 -3
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
- package/dist/dts/config/config.d.ts +44 -0
- package/dist/dts/config/config.d.ts.map +1 -1
- package/dist/dts/main/main.d.ts +187 -5
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/dts/main/main.styles.d.ts.map +1 -1
- package/dist/dts/main/main.template.d.ts.map +1 -1
- package/dist/dts/state/persistence/diagnostics-cursors.d.ts +7 -2
- package/dist/dts/state/persistence/diagnostics-cursors.d.ts.map +1 -1
- package/dist/dts/state/persistence/diagnostics.d.ts +30 -0
- package/dist/dts/state/persistence/diagnostics.d.ts.map +1 -1
- package/dist/dts/state/persistence/session-persister.d.ts +18 -0
- package/dist/dts/state/persistence/session-persister.d.ts.map +1 -1
- package/dist/dts/utils/condense-history.d.ts +35 -0
- package/dist/dts/utils/condense-history.d.ts.map +1 -1
- package/dist/dts/utils/context-tokens.d.ts +156 -0
- package/dist/dts/utils/context-tokens.d.ts.map +1 -0
- package/dist/dts/utils/history-transform.d.ts +76 -14
- package/dist/dts/utils/history-transform.d.ts.map +1 -1
- package/dist/dts/utils/resolve-context-budget.d.ts +98 -0
- package/dist/dts/utils/resolve-context-budget.d.ts.map +1 -0
- package/dist/esm/components/chat-driver/chat-driver.js +200 -35
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +28 -4
- package/dist/esm/main/main.js +400 -24
- package/dist/esm/main/main.styles.js +128 -0
- package/dist/esm/main/main.template.js +64 -29
- package/dist/esm/state/debug-event-log.js +1 -1
- package/dist/esm/state/persistence/diagnostics-cursors.js +1 -1
- package/dist/esm/state/persistence/diagnostics.js +49 -0
- package/dist/esm/state/persistence/session-persister.js +38 -18
- package/dist/esm/utils/condense-history.js +125 -26
- package/dist/esm/utils/context-tokens.js +339 -0
- package/dist/esm/utils/history-transform.js +101 -19
- package/dist/esm/utils/resolve-context-budget.js +84 -0
- package/package.json +16 -16
- package/sandbox/README.md +93 -4
- package/sandbox/controls.ts +77 -10
- package/sandbox/fixtures.ts +163 -6
- package/sandbox/sandbox.css +54 -1
- package/sandbox/sandbox.ts +384 -7
package/dist/chat-driver.mjs
CHANGED
|
@@ -1486,6 +1486,61 @@ function abortableDelay(ms, signal) {
|
|
|
1486
1486
|
});
|
|
1487
1487
|
}
|
|
1488
1488
|
|
|
1489
|
+
// ../../foundation-ai/dist/esm/transports/context-overflow-error.js
|
|
1490
|
+
var CONTEXT_OVERFLOW_PHRASES = Object.freeze([
|
|
1491
|
+
/prompt is too long/i,
|
|
1492
|
+
/input token count .* exceeds/i,
|
|
1493
|
+
/maximum context length/i,
|
|
1494
|
+
/context[_ ]length[_ ]exceeded/i,
|
|
1495
|
+
/too many (input )?tokens/i
|
|
1496
|
+
]);
|
|
1497
|
+
var TOKEN_FIGURES = /(\d[\d,]{2,})\D{0,40}?(?:>|exceeds)\D{0,80}?(\d[\d,]{2,})/i;
|
|
1498
|
+
var OVERFLOW_STATUS = 400;
|
|
1499
|
+
var DEFAULT_CONTEXT_OVERFLOW_MESSAGE = "This conversation has grown larger than the model can accept in one request. Compact it to free up room, then try again.";
|
|
1500
|
+
var ContextOverflowError = class extends Error {
|
|
1501
|
+
constructor(vendorLabel, promptTokens, limitTokens, detail) {
|
|
1502
|
+
super(`${vendorLabel} rejected the request \u2014 the prompt exceeds the model context window` + (promptTokens != null && limitTokens != null ? ` (${promptTokens} > ${limitTokens})` : "") + (detail ? `: ${detail}` : "") + ". Retrying will not clear this \u2014 the conversation must be compacted.");
|
|
1503
|
+
this.vendorLabel = vendorLabel;
|
|
1504
|
+
this.promptTokens = promptTokens;
|
|
1505
|
+
this.limitTokens = limitTokens;
|
|
1506
|
+
this.detail = detail;
|
|
1507
|
+
this.name = "ContextOverflowError";
|
|
1508
|
+
}
|
|
1509
|
+
};
|
|
1510
|
+
var MAX_ENVELOPE_DEPTH = 4;
|
|
1511
|
+
function messagesIn(payload) {
|
|
1512
|
+
if (!payload || typeof payload !== "object")
|
|
1513
|
+
return [];
|
|
1514
|
+
const found = [];
|
|
1515
|
+
const visit = (node, depth) => {
|
|
1516
|
+
if (depth > MAX_ENVELOPE_DEPTH || !node || typeof node !== "object")
|
|
1517
|
+
return;
|
|
1518
|
+
for (const value of Object.values(node)) {
|
|
1519
|
+
if (typeof value === "string")
|
|
1520
|
+
found.push(value);
|
|
1521
|
+
else if (typeof value === "object")
|
|
1522
|
+
visit(value, depth + 1);
|
|
1523
|
+
}
|
|
1524
|
+
};
|
|
1525
|
+
visit(payload, 0);
|
|
1526
|
+
return found;
|
|
1527
|
+
}
|
|
1528
|
+
function contextOverflowOf(_vendor, status, payload) {
|
|
1529
|
+
if (status !== OVERFLOW_STATUS)
|
|
1530
|
+
return void 0;
|
|
1531
|
+
for (const message of messagesIn(payload)) {
|
|
1532
|
+
if (!CONTEXT_OVERFLOW_PHRASES.some((phrase) => phrase.test(message)))
|
|
1533
|
+
continue;
|
|
1534
|
+
const figures = TOKEN_FIGURES.exec(message);
|
|
1535
|
+
return {
|
|
1536
|
+
promptTokens: figures ? Number(figures[1].replace(/,/g, "")) : void 0,
|
|
1537
|
+
limitTokens: figures ? Number(figures[2].replace(/,/g, "")) : void 0,
|
|
1538
|
+
message
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
return void 0;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1489
1544
|
// ../../foundation-ai/dist/esm/transports/ndjson-frames.js
|
|
1490
1545
|
var MALFORMED_FRAME_PREVIEW_CHARS = 120;
|
|
1491
1546
|
function readFramedBody(stream, onChunk, vendorLabel, stallTimeout) {
|
|
@@ -1614,7 +1669,7 @@ function serverBackoffSpentMs(resolvedMs, ladderMs) {
|
|
|
1614
1669
|
var MAX_RETRIES = 5;
|
|
1615
1670
|
function postWithRetry(options) {
|
|
1616
1671
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1617
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
1672
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
1618
1673
|
const { url, headers, body, credentials, vendorLabel, retryableStatuses, timeout, stallTimeout, backoffBaseMs, signal } = options;
|
|
1619
1674
|
const vendor = vendorOfTransportLabel(vendorLabel);
|
|
1620
1675
|
let serverBackoffSpent = 0;
|
|
@@ -1667,6 +1722,10 @@ function postWithRetry(options) {
|
|
|
1667
1722
|
if (refusal) {
|
|
1668
1723
|
throw providerRefusedFrom(vendorLabel, refusal, response.status, parsed, errText || void 0);
|
|
1669
1724
|
}
|
|
1725
|
+
const overflow = contextOverflowOf(vendor, response.status, parsed);
|
|
1726
|
+
if (overflow) {
|
|
1727
|
+
throw new ContextOverflowError(vendorLabel, overflow.promptTokens, overflow.limitTokens, (_d = overflow.message) !== null && _d !== void 0 ? _d : errText || void 0);
|
|
1728
|
+
}
|
|
1670
1729
|
if (isBudgetRejection(response.status, codeOf(parsed))) {
|
|
1671
1730
|
throw budgetExhaustedFrom(vendorLabel, parsed, errText || void 0);
|
|
1672
1731
|
}
|
|
@@ -1679,18 +1738,22 @@ function postWithRetry(options) {
|
|
|
1679
1738
|
}
|
|
1680
1739
|
throw new Error(`${vendorLabel} request error ${response.status}: ${errText}`);
|
|
1681
1740
|
}
|
|
1682
|
-
if (((
|
|
1741
|
+
if (((_f = (_e = response.headers) === null || _e === void 0 ? void 0 : _e.get("content-type")) === null || _f === void 0 ? void 0 : _f.toLowerCase().includes("application/x-ndjson")) && response.body) {
|
|
1683
1742
|
rearmStallTimer();
|
|
1684
1743
|
const frame = yield readFramedBody(response.body, rearmStallTimer, vendorLabel, stallTimeout);
|
|
1685
1744
|
if (frame.t === "ok")
|
|
1686
1745
|
return frame.body;
|
|
1687
1746
|
if (frame.code === "UPSTREAM_STALLED") {
|
|
1688
|
-
throw new DOMException(`${vendorLabel} request stalled: ${(
|
|
1747
|
+
throw new DOMException(`${vendorLabel} request stalled: ${(_g = frame.error) !== null && _g !== void 0 ? _g : "no upstream data"}`, "TimeoutError");
|
|
1689
1748
|
}
|
|
1690
1749
|
const framedRefusal = providerRefusalOf(vendor, frame.code, frame);
|
|
1691
1750
|
if (framedRefusal) {
|
|
1692
1751
|
throw providerRefusedFrom(vendorLabel, framedRefusal, frame.status, frame, frame.error);
|
|
1693
1752
|
}
|
|
1753
|
+
const framedOverflow = contextOverflowOf(vendor, frame.status, frame);
|
|
1754
|
+
if (framedOverflow) {
|
|
1755
|
+
throw new ContextOverflowError(vendorLabel, framedOverflow.promptTokens, framedOverflow.limitTokens, (_h = framedOverflow.message) !== null && _h !== void 0 ? _h : frame.error);
|
|
1756
|
+
}
|
|
1694
1757
|
if (isBudgetRejection(frame.status, frame.code)) {
|
|
1695
1758
|
throw budgetExhaustedFrom(vendorLabel, frame);
|
|
1696
1759
|
}
|
|
@@ -1700,7 +1763,7 @@ function postWithRetry(options) {
|
|
|
1700
1763
|
yield backoff(attempt, retryInfoMs(frame));
|
|
1701
1764
|
continue;
|
|
1702
1765
|
}
|
|
1703
|
-
throw new Error(`${vendorLabel} request error ${frame.status}: ${(
|
|
1766
|
+
throw new Error(`${vendorLabel} request error ${frame.status}: ${(_j = frame.error) !== null && _j !== void 0 ? _j : JSON.stringify(frame.details)}`);
|
|
1704
1767
|
}
|
|
1705
1768
|
return yield response.json();
|
|
1706
1769
|
} catch (e) {
|
|
@@ -3665,10 +3728,126 @@ function createInteractionContext(interactionId) {
|
|
|
3665
3728
|
};
|
|
3666
3729
|
}
|
|
3667
3730
|
|
|
3731
|
+
// src/utils/context-tokens.ts
|
|
3732
|
+
var APPROX_CHARS_PER_TOKEN = 4;
|
|
3733
|
+
var IMAGE_TOKEN_ESTIMATE = 1600;
|
|
3734
|
+
var PER_MESSAGE_FRAMING_TOKENS = 4;
|
|
3735
|
+
var COMPACTION_SUMMARY_TOKEN_ESTIMATE = 1200;
|
|
3736
|
+
function reachesProvider(m) {
|
|
3737
|
+
if (m.role === "system-event" || m.role === "synthetic-user") return false;
|
|
3738
|
+
if (m.category === "reasoning" || m.category === "narration") return false;
|
|
3739
|
+
return !m.thinking;
|
|
3740
|
+
}
|
|
3741
|
+
function imageCount(m) {
|
|
3742
|
+
const onMessage = m.attachments?.filter((a) => a.kind === "image").length ?? 0;
|
|
3743
|
+
const onResult = m.toolResult?.attachments?.filter((a) => a.kind === "image").length ?? 0;
|
|
3744
|
+
return onMessage + onResult;
|
|
3745
|
+
}
|
|
3746
|
+
var TEXT_ATTACHMENT_FRAMING_CHARS = 9;
|
|
3747
|
+
function wireChars(m) {
|
|
3748
|
+
let chars = m.content?.length ?? 0;
|
|
3749
|
+
for (const attachment of m.attachments ?? []) {
|
|
3750
|
+
if (attachment.kind === "image") continue;
|
|
3751
|
+
chars += TEXT_ATTACHMENT_FRAMING_CHARS + attachment.name.length + (attachment.content?.length ?? 0);
|
|
3752
|
+
}
|
|
3753
|
+
for (const tc of m.toolCalls ?? []) {
|
|
3754
|
+
chars += tc.name.length;
|
|
3755
|
+
try {
|
|
3756
|
+
chars += JSON.stringify(tc.args ?? {}).length;
|
|
3757
|
+
} catch {
|
|
3758
|
+
}
|
|
3759
|
+
}
|
|
3760
|
+
chars += m.toolResult?.content?.length ?? 0;
|
|
3761
|
+
return chars;
|
|
3762
|
+
}
|
|
3763
|
+
function baselineTokens(m) {
|
|
3764
|
+
if (!reachesProvider(m)) return 0;
|
|
3765
|
+
return Math.ceil(wireChars(m) / APPROX_CHARS_PER_TOKEN) + imageCount(m) * IMAGE_TOKEN_ESTIMATE + PER_MESSAGE_FRAMING_TOKENS;
|
|
3766
|
+
}
|
|
3767
|
+
function compactedAtOf(history) {
|
|
3768
|
+
return history.find((m) => m.role === "compacted-summary")?.compaction?.createdAt;
|
|
3769
|
+
}
|
|
3770
|
+
function usableAnchor(m, compactedAt) {
|
|
3771
|
+
if (m.inputTokens == null) return false;
|
|
3772
|
+
if (!compactedAt) return true;
|
|
3773
|
+
return !!m.timestamp && m.timestamp >= compactedAt;
|
|
3774
|
+
}
|
|
3775
|
+
function firstAnchor(history, compactedAt) {
|
|
3776
|
+
return history.findIndex((m) => usableAnchor(m, compactedAt));
|
|
3777
|
+
}
|
|
3778
|
+
function estimateMessageTokens(history) {
|
|
3779
|
+
const estimates = history.map(baselineTokens);
|
|
3780
|
+
const compactedAt = compactedAtOf(history);
|
|
3781
|
+
const anchors = [];
|
|
3782
|
+
history.forEach((m, i) => {
|
|
3783
|
+
if (usableAnchor(m, compactedAt)) anchors.push(i);
|
|
3784
|
+
});
|
|
3785
|
+
for (let a = 0; a < anchors.length - 1; a += 1) {
|
|
3786
|
+
const from = anchors[a];
|
|
3787
|
+
const to = anchors[a + 1];
|
|
3788
|
+
const measured = history[to].inputTokens - history[from].inputTokens;
|
|
3789
|
+
if (measured <= 0) continue;
|
|
3790
|
+
let baseline = 0;
|
|
3791
|
+
for (let i = from; i < to; i += 1) baseline += estimates[i];
|
|
3792
|
+
if (baseline <= 0) {
|
|
3793
|
+
estimates[from] = measured;
|
|
3794
|
+
continue;
|
|
3795
|
+
}
|
|
3796
|
+
const scale = measured / baseline;
|
|
3797
|
+
let assigned = 0;
|
|
3798
|
+
for (let i = from; i < to - 1; i += 1) {
|
|
3799
|
+
estimates[i] = Math.round(estimates[i] * scale);
|
|
3800
|
+
assigned += estimates[i];
|
|
3801
|
+
}
|
|
3802
|
+
estimates[to - 1] = measured - assigned;
|
|
3803
|
+
}
|
|
3804
|
+
return estimates;
|
|
3805
|
+
}
|
|
3806
|
+
function estimateSystemOverhead(history) {
|
|
3807
|
+
const first = firstAnchor(history, compactedAtOf(history));
|
|
3808
|
+
if (first < 0) return 0;
|
|
3809
|
+
const estimates = estimateMessageTokens(history);
|
|
3810
|
+
let messages = 0;
|
|
3811
|
+
for (let i = 0; i < first; i += 1) messages += estimates[i];
|
|
3812
|
+
return Math.max(0, history[first].inputTokens - messages);
|
|
3813
|
+
}
|
|
3814
|
+
function estimateContextTokens(history) {
|
|
3815
|
+
const estimates = estimateMessageTokens(history);
|
|
3816
|
+
let total = estimateSystemOverhead(history);
|
|
3817
|
+
for (const t of estimates) total += t;
|
|
3818
|
+
return total;
|
|
3819
|
+
}
|
|
3820
|
+
function estimateRequestTokens(storedHistory, requestHistory) {
|
|
3821
|
+
const overhead = estimateSystemOverhead(storedHistory);
|
|
3822
|
+
const calibratedMessages = Math.max(0, estimateContextTokens(storedHistory) - overhead);
|
|
3823
|
+
let storedBaseline = 0;
|
|
3824
|
+
for (const m of storedHistory) storedBaseline += baselineTokens(m);
|
|
3825
|
+
let requestBaseline = 0;
|
|
3826
|
+
for (const m of requestHistory) requestBaseline += baselineTokens(m);
|
|
3827
|
+
if (storedBaseline <= 0) return overhead + requestBaseline;
|
|
3828
|
+
return Math.round(overhead + calibratedMessages * (requestBaseline / storedBaseline));
|
|
3829
|
+
}
|
|
3830
|
+
function projectCompaction(history, cut, summaryTokens = COMPACTION_SUMMARY_TOKEN_ESTIMATE) {
|
|
3831
|
+
const estimates = estimateMessageTokens(history);
|
|
3832
|
+
const overhead = estimateSystemOverhead(history);
|
|
3833
|
+
let tail = 0;
|
|
3834
|
+
for (let i = cut; i < estimates.length; i += 1) tail += estimates[i];
|
|
3835
|
+
let all = 0;
|
|
3836
|
+
for (const t of estimates) all += t;
|
|
3837
|
+
const tokensBefore = overhead + all;
|
|
3838
|
+
const tokensAfter = overhead + summaryTokens + tail;
|
|
3839
|
+
return {
|
|
3840
|
+
cut,
|
|
3841
|
+
compactedCount: cut,
|
|
3842
|
+
tokensBefore,
|
|
3843
|
+
tokensAfter,
|
|
3844
|
+
reclaimed: Math.max(0, tokensBefore - tokensAfter)
|
|
3845
|
+
};
|
|
3846
|
+
}
|
|
3847
|
+
|
|
3668
3848
|
// src/utils/condense-history.ts
|
|
3669
3849
|
var CONDENSE_MIN_CHARS = 1e3;
|
|
3670
3850
|
var CONDENSED_ARGS_KEY = "condensed";
|
|
3671
|
-
var APPROX_CHARS_PER_TOKEN = 4;
|
|
3672
3851
|
function triggerReason(trigger) {
|
|
3673
3852
|
switch (trigger.kind) {
|
|
3674
3853
|
case "age":
|
|
@@ -3683,13 +3862,21 @@ function triggerReason(trigger) {
|
|
|
3683
3862
|
return "superseded";
|
|
3684
3863
|
}
|
|
3685
3864
|
}
|
|
3686
|
-
function condenseStub(target, tool, trigger, origLen, restorable,
|
|
3865
|
+
function condenseStub(target, tool, trigger, origLen, restorable, imageCount2 = 0) {
|
|
3687
3866
|
const what = target === "args" ? "args" : "result";
|
|
3688
3867
|
const key = trigger.kind === "superseded" ? ` ${trigger.by}` : "";
|
|
3689
3868
|
const restore = restorable ? "; re-call to restore" : "";
|
|
3690
|
-
const images =
|
|
3869
|
+
const images = imageCount2 > 0 ? ` + ${imageCount2} image${imageCount2 === 1 ? "" : "s"}` : "";
|
|
3691
3870
|
return `[${tool}${key} \u2014 ${what} elided, ~${origLen} chars${images} (${triggerReason(trigger)})${restore}]`;
|
|
3692
3871
|
}
|
|
3872
|
+
function renderCondensedArgs(policy, tool, fired, origLen) {
|
|
3873
|
+
return {
|
|
3874
|
+
[CONDENSED_ARGS_KEY]: policy === "drop" || policy === "pointer" ? condenseStub("args", tool, fired, origLen, policy === "pointer") : policy.replaceWith
|
|
3875
|
+
};
|
|
3876
|
+
}
|
|
3877
|
+
function renderCondensedResult(policy, tool, fired, textLen, imageCount2) {
|
|
3878
|
+
return policy === "drop" || policy === "pointer" ? condenseStub("response", tool, fired, textLen, policy === "pointer", imageCount2) : policy.replaceWith;
|
|
3879
|
+
}
|
|
3693
3880
|
function triggerLabel(trigger) {
|
|
3694
3881
|
switch (trigger.kind) {
|
|
3695
3882
|
case "age":
|
|
@@ -3764,10 +3951,7 @@ function applyCondensation(history, policies, ctx, onCondensed) {
|
|
|
3764
3951
|
const origLen = JSON.stringify(tc.args ?? {}).length;
|
|
3765
3952
|
if (origLen < CONDENSE_MIN_CHARS) return tc;
|
|
3766
3953
|
changed = true;
|
|
3767
|
-
const
|
|
3768
|
-
const args = {
|
|
3769
|
-
[CONDENSED_ARGS_KEY]: result === "drop" || result === "pointer" ? condenseStub("args", tc.name, fired, origLen, result === "pointer") : result.replaceWith
|
|
3770
|
-
};
|
|
3954
|
+
const args = renderCondensedArgs(entry.policy.args, tc.name, fired, origLen);
|
|
3771
3955
|
if (!entry.reportedArgs) {
|
|
3772
3956
|
entry.reportedArgs = true;
|
|
3773
3957
|
const stubLen = JSON.stringify(args).length;
|
|
@@ -3793,8 +3977,13 @@ function applyCondensation(history, policies, ctx, onCondensed) {
|
|
|
3793
3977
|
const fired = entry?.policy.response ? firstFired(msg.toolResult.toolCallId, entry) : void 0;
|
|
3794
3978
|
if (entry && fired && origLen >= CONDENSE_MIN_CHARS) {
|
|
3795
3979
|
const tool = nameById.get(msg.toolResult.toolCallId) ?? msg.toolResult.toolCallId;
|
|
3796
|
-
const
|
|
3797
|
-
|
|
3980
|
+
const content = renderCondensedResult(
|
|
3981
|
+
entry.policy.response,
|
|
3982
|
+
tool,
|
|
3983
|
+
fired,
|
|
3984
|
+
textLen,
|
|
3985
|
+
images.length
|
|
3986
|
+
);
|
|
3798
3987
|
if (!entry.reportedResponse) {
|
|
3799
3988
|
entry.reportedResponse = true;
|
|
3800
3989
|
onCondensed({
|
|
@@ -3814,6 +4003,53 @@ function applyCondensation(history, policies, ctx, onCondensed) {
|
|
|
3814
4003
|
return msg;
|
|
3815
4004
|
});
|
|
3816
4005
|
}
|
|
4006
|
+
function condenseForPersistence(history, policies) {
|
|
4007
|
+
if (policies.size === 0) return history;
|
|
4008
|
+
const nameById = /* @__PURE__ */ new Map();
|
|
4009
|
+
for (const msg of history) {
|
|
4010
|
+
for (const tc of msg.toolCalls ?? []) nameById.set(tc.id, tc.name);
|
|
4011
|
+
}
|
|
4012
|
+
let changed = false;
|
|
4013
|
+
const projected = history.map((msg) => {
|
|
4014
|
+
if (msg.toolCalls?.length) {
|
|
4015
|
+
let callsChanged = false;
|
|
4016
|
+
const toolCalls = msg.toolCalls.map((tc) => {
|
|
4017
|
+
const entry = policies.get(tc.id);
|
|
4018
|
+
if (!entry?.reportedArgs || !entry.firedTrigger || !entry.policy.args) return tc;
|
|
4019
|
+
callsChanged = true;
|
|
4020
|
+
const origLen = JSON.stringify(tc.args ?? {}).length;
|
|
4021
|
+
return {
|
|
4022
|
+
...tc,
|
|
4023
|
+
args: renderCondensedArgs(entry.policy.args, tc.name, entry.firedTrigger, origLen)
|
|
4024
|
+
};
|
|
4025
|
+
});
|
|
4026
|
+
if (callsChanged) {
|
|
4027
|
+
changed = true;
|
|
4028
|
+
return { ...msg, toolCalls };
|
|
4029
|
+
}
|
|
4030
|
+
return msg;
|
|
4031
|
+
}
|
|
4032
|
+
if (msg.toolResult) {
|
|
4033
|
+
const entry = policies.get(msg.toolResult.toolCallId);
|
|
4034
|
+
if (entry?.reportedResponse && entry.firedTrigger && entry.policy.response) {
|
|
4035
|
+
changed = true;
|
|
4036
|
+
const tool = nameById.get(msg.toolResult.toolCallId) ?? msg.toolResult.toolCallId;
|
|
4037
|
+
const images = msg.toolResult.attachments ?? [];
|
|
4038
|
+
const content = renderCondensedResult(
|
|
4039
|
+
entry.policy.response,
|
|
4040
|
+
tool,
|
|
4041
|
+
entry.firedTrigger,
|
|
4042
|
+
msg.toolResult.content?.length ?? 0,
|
|
4043
|
+
images.length
|
|
4044
|
+
);
|
|
4045
|
+
const { attachments: _elided, ...restOfResult } = msg.toolResult;
|
|
4046
|
+
return { ...msg, toolResult: { ...restOfResult, content } };
|
|
4047
|
+
}
|
|
4048
|
+
}
|
|
4049
|
+
return msg;
|
|
4050
|
+
});
|
|
4051
|
+
return changed ? projected : history;
|
|
4052
|
+
}
|
|
3817
4053
|
|
|
3818
4054
|
// src/utils/history-transform.ts
|
|
3819
4055
|
function maskToolPayload(msg) {
|
|
@@ -3839,20 +4075,43 @@ function applyHistoryCap(history, cap) {
|
|
|
3839
4075
|
const cutoff = history.length - cap;
|
|
3840
4076
|
return history.map((msg, i) => i < cutoff ? maskToolPayload(msg) : msg);
|
|
3841
4077
|
}
|
|
3842
|
-
var
|
|
3843
|
-
var
|
|
3844
|
-
function
|
|
3845
|
-
if (
|
|
3846
|
-
|
|
4078
|
+
var DEFAULT_TAIL_TOKEN_BUDGET = 3e4;
|
|
4079
|
+
var DEFAULT_MIN_RECLAIM_TOKENS = 5e3;
|
|
4080
|
+
function isSafeCompactionCut(history, i) {
|
|
4081
|
+
if (i <= 0 || i >= history.length) return false;
|
|
4082
|
+
for (let j = i; j < history.length; j += 1) {
|
|
4083
|
+
if (!reachesProvider(history[j])) continue;
|
|
4084
|
+
return history[j].role !== "tool";
|
|
3847
4085
|
}
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
4086
|
+
return true;
|
|
4087
|
+
}
|
|
4088
|
+
function findCompactionCut(history, tailTokenBudget = DEFAULT_TAIL_TOKEN_BUDGET) {
|
|
4089
|
+
const estimates = estimateMessageTokens(history);
|
|
4090
|
+
let messagesTotal = 0;
|
|
4091
|
+
for (const t of estimates) messagesTotal += t;
|
|
4092
|
+
if (messagesTotal <= tailTokenBudget) return null;
|
|
4093
|
+
let tail = 0;
|
|
4094
|
+
let raw = history.length;
|
|
4095
|
+
for (let i = history.length - 1; i > 0; i -= 1) {
|
|
4096
|
+
if (tail + estimates[i] > tailTokenBudget) break;
|
|
4097
|
+
tail += estimates[i];
|
|
4098
|
+
raw = i;
|
|
4099
|
+
}
|
|
4100
|
+
for (let i = raw; i < history.length; i += 1) {
|
|
4101
|
+
if (isSafeCompactionCut(history, i)) return i;
|
|
3853
4102
|
}
|
|
3854
4103
|
return null;
|
|
3855
4104
|
}
|
|
4105
|
+
function planCompaction(history, options = {}) {
|
|
4106
|
+
const {
|
|
4107
|
+
tailTokenBudget = DEFAULT_TAIL_TOKEN_BUDGET,
|
|
4108
|
+
minReclaimTokens = DEFAULT_MIN_RECLAIM_TOKENS
|
|
4109
|
+
} = options;
|
|
4110
|
+
const cut = findCompactionCut(history, tailTokenBudget);
|
|
4111
|
+
if (cut == null) return null;
|
|
4112
|
+
const projection = projectCompaction(history, cut);
|
|
4113
|
+
return projection.reclaimed >= minReclaimTokens ? projection : null;
|
|
4114
|
+
}
|
|
3856
4115
|
function renderMessageForSummary(m) {
|
|
3857
4116
|
switch (m.role) {
|
|
3858
4117
|
case "compacted-summary":
|
|
@@ -4582,6 +4841,7 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
4582
4841
|
const status = await this.resolveStatusForProvider(resolvedName, provider);
|
|
4583
4842
|
this.lastResolvedModel = status.model;
|
|
4584
4843
|
this.lastResolvedProvider = status.provider;
|
|
4844
|
+
this.lastResolvedContextLimit = status.contextLimit;
|
|
4585
4845
|
if (resolvedName !== this.lastDispatchedProviderName) {
|
|
4586
4846
|
this.lastDispatchedProviderName = resolvedName;
|
|
4587
4847
|
recordMetaEvent(this.sessionKey, "provider.selected", {
|
|
@@ -4613,7 +4873,13 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
4613
4873
|
try {
|
|
4614
4874
|
const resolved = await provider.getStatus?.();
|
|
4615
4875
|
if (resolved) {
|
|
4616
|
-
status = {
|
|
4876
|
+
status = {
|
|
4877
|
+
model: resolved.model,
|
|
4878
|
+
provider: resolved.provider,
|
|
4879
|
+
// Carried so the mid-loop guard can measure against the window of the
|
|
4880
|
+
// provider this call actually resolved to (GENC-1567).
|
|
4881
|
+
contextLimit: resolved.contextLimit
|
|
4882
|
+
};
|
|
4617
4883
|
}
|
|
4618
4884
|
} catch {
|
|
4619
4885
|
status = {};
|
|
@@ -4770,9 +5036,48 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
4770
5036
|
return snapshot;
|
|
4771
5037
|
}
|
|
4772
5038
|
/**
|
|
4773
|
-
*
|
|
4774
|
-
*
|
|
5039
|
+
* Set the mid-loop context guard. Its margin is deliberately far smaller than
|
|
5040
|
+
* the reserve that blocks NEW turns: that reserve exists so an accepted turn can
|
|
5041
|
+
* spend it, and a guard set at the same line would kill every turn that used
|
|
5042
|
+
* the headroom it was given.
|
|
5043
|
+
*/
|
|
5044
|
+
setContextGuard(policy) {
|
|
5045
|
+
this.contextGuard = policy && policy.marginTokens > 0 ? policy : void 0;
|
|
5046
|
+
}
|
|
5047
|
+
/**
|
|
5048
|
+
* Whether issuing `requestHistory` would run the context window out.
|
|
5049
|
+
*
|
|
5050
|
+
* Inert unless a margin has been set AND the resolved provider reports a
|
|
5051
|
+
* window — with no window there is nothing to measure against, and inventing
|
|
5052
|
+
* one to refuse a request is worse than letting the provider answer.
|
|
5053
|
+
*
|
|
5054
|
+
* Stopping here is strictly better than letting the request go: the provider
|
|
5055
|
+
* would reject an oversized prompt outright, leaving a transcript still too
|
|
5056
|
+
* large to retry and no explanation the user can act on, whereas ending the
|
|
5057
|
+
* turn keeps history intact so compaction is still available and the work so
|
|
5058
|
+
* far is not lost.
|
|
4775
5059
|
*/
|
|
5060
|
+
contextExhausted(requestHistory, pendingInput) {
|
|
5061
|
+
const guard = this.contextGuard;
|
|
5062
|
+
if (!guard) return void 0;
|
|
5063
|
+
const limit = this.lastResolvedContextLimit ?? guard.fallbackLimit;
|
|
5064
|
+
if (limit == null || limit <= 0) return void 0;
|
|
5065
|
+
const threshold = Math.max(0, limit - guard.marginTokens);
|
|
5066
|
+
const request = pendingInput && (pendingInput.content || pendingInput.attachments?.length) ? [
|
|
5067
|
+
...requestHistory,
|
|
5068
|
+
{
|
|
5069
|
+
role: "user",
|
|
5070
|
+
content: pendingInput.content,
|
|
5071
|
+
attachments: pendingInput.attachments
|
|
5072
|
+
}
|
|
5073
|
+
] : requestHistory;
|
|
5074
|
+
const estimated = estimateRequestTokens(this.history, request);
|
|
5075
|
+
if (estimated < threshold) return void 0;
|
|
5076
|
+
logger2.error(
|
|
5077
|
+
`ChatDriver: ending the turn \u2014 the request would fill the context window (~${estimated} of ${limit})`
|
|
5078
|
+
);
|
|
5079
|
+
return { estimated, threshold };
|
|
5080
|
+
}
|
|
4776
5081
|
setProviderHistoryTransform(transform) {
|
|
4777
5082
|
this.providerHistoryTransform = transform;
|
|
4778
5083
|
}
|
|
@@ -4786,6 +5091,26 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
4786
5091
|
getActiveFoldNames() {
|
|
4787
5092
|
return this.foldStack.map((f) => f.foldName);
|
|
4788
5093
|
}
|
|
5094
|
+
/**
|
|
5095
|
+
* The transcript as it should be **stored**: every tool payload the model has already stopped
|
|
5096
|
+
* seeing replaced by the same stub it was sent (GENC-1566). Live history is untouched — this
|
|
5097
|
+
* returns a copy.
|
|
5098
|
+
*
|
|
5099
|
+
* Condensation deliberately spares stored history, because the live transcript and the debug
|
|
5100
|
+
* export both read it. A host writing that transcript to durable storage inherits the other
|
|
5101
|
+
* half of that decision: it stores payloads the driver has already retired, on every autosave,
|
|
5102
|
+
* for the life of the session. Measured on one small Create session — 58,187 chars, 7.4% of a
|
|
5103
|
+
* 790 KB session object, across five payloads worth 14,434 tokens of context, written 86 times.
|
|
5104
|
+
*
|
|
5105
|
+
* Only already-collapsed payloads are touched, so this can never store a stub for something the
|
|
5106
|
+
* model can still see, and it has no side effects (see `condenseForPersistence`, which is
|
|
5107
|
+
* internal). A host that wants the unredacted transcript still calls
|
|
5108
|
+
* {@link ChatDriver.getRawHistory}, and a diagnostics stream that keeps full payloads is
|
|
5109
|
+
* unaffected — which is what makes the elision recoverable.
|
|
5110
|
+
*/
|
|
5111
|
+
getHistoryForPersistence() {
|
|
5112
|
+
return condenseForPersistence([...this.history], this.condensePolicies);
|
|
5113
|
+
}
|
|
4789
5114
|
async getSuggestions(history, prompt2, count, allAgentInfo) {
|
|
4790
5115
|
const defaultProvider = this.providerRegistry.default();
|
|
4791
5116
|
if (!defaultProvider.prompt) {
|
|
@@ -4851,8 +5176,19 @@ Output format (strict):
|
|
|
4851
5176
|
* turns exists behind a clean boundary. Uses the same `history` `compact()`
|
|
4852
5177
|
* acts on, so the UI's gate can't disagree with the action (GENC-1351 follow-up).
|
|
4853
5178
|
*/
|
|
4854
|
-
canCompact() {
|
|
4855
|
-
return
|
|
5179
|
+
canCompact(options) {
|
|
5180
|
+
return this.getCompactionPlan(options) != null;
|
|
5181
|
+
}
|
|
5182
|
+
/**
|
|
5183
|
+
* {@inheritDoc AiDriver.getCompactionPlan}
|
|
5184
|
+
*
|
|
5185
|
+
* Runs against the driver's own `history` — the exact list `compact()` acts on
|
|
5186
|
+
* — so a projection and the compaction it describes can never be computed from
|
|
5187
|
+
* different transcripts (the GENC-1351 follow-up that first made `canCompact`
|
|
5188
|
+
* read `history` rather than a mirrored copy).
|
|
5189
|
+
*/
|
|
5190
|
+
getCompactionPlan(options) {
|
|
5191
|
+
return planCompaction(this.history, options);
|
|
4856
5192
|
}
|
|
4857
5193
|
/**
|
|
4858
5194
|
* Destructively compact older turns into a single `compacted-summary` message
|
|
@@ -4863,7 +5199,7 @@ Output format (strict):
|
|
|
4863
5199
|
* the new one. Returns the created summary message, or `null` when there is
|
|
4864
5200
|
* nothing worth compacting or the default provider cannot summarize.
|
|
4865
5201
|
*/
|
|
4866
|
-
async compact() {
|
|
5202
|
+
async compact(options) {
|
|
4867
5203
|
if (this.busy) return null;
|
|
4868
5204
|
const defaultProvider = this.providerRegistry.default();
|
|
4869
5205
|
if (!defaultProvider.prompt) {
|
|
@@ -4871,7 +5207,7 @@ Output format (strict):
|
|
|
4871
5207
|
return null;
|
|
4872
5208
|
}
|
|
4873
5209
|
const history = this.history;
|
|
4874
|
-
const cut =
|
|
5210
|
+
const cut = planCompaction(history, options)?.cut;
|
|
4875
5211
|
if (cut == null) return null;
|
|
4876
5212
|
const toCompact = history.slice(0, cut);
|
|
4877
5213
|
const tail = history.slice(cut);
|
|
@@ -5342,6 +5678,7 @@ Output format (strict):
|
|
|
5342
5678
|
activityBus: this.activityBus
|
|
5343
5679
|
});
|
|
5344
5680
|
child.markAsSubAgent();
|
|
5681
|
+
child.setContextGuard(this.contextGuard);
|
|
5345
5682
|
const disposeChild = () => child.dispose();
|
|
5346
5683
|
if (this.lifecycleController.signal.aborted) {
|
|
5347
5684
|
disposeChild();
|
|
@@ -5822,6 +6159,30 @@ ${tailBody}
|
|
|
5822
6159
|
if (this.lastResolvedProvider !== void 0)
|
|
5823
6160
|
turnSnapshot.provider = this.lastResolvedProvider;
|
|
5824
6161
|
if (this.lastResolvedModel !== void 0) turnSnapshot.model = this.lastResolvedModel;
|
|
6162
|
+
const contextStop = this.contextExhausted(historyForCall, {
|
|
6163
|
+
content: userInputForCall,
|
|
6164
|
+
attachments: attachmentsForCall
|
|
6165
|
+
});
|
|
6166
|
+
if (contextStop) {
|
|
6167
|
+
recordTurnError(this.sessionKey, "context-exhausted", {
|
|
6168
|
+
agent: this.activeAgentName,
|
|
6169
|
+
provider: this.lastResolvedProviderName,
|
|
6170
|
+
contextTokens: contextStop.estimated,
|
|
6171
|
+
guard: contextStop.threshold,
|
|
6172
|
+
limit: this.lastResolvedContextLimit,
|
|
6173
|
+
iterations,
|
|
6174
|
+
isSubAgent: this.isSubAgent
|
|
6175
|
+
});
|
|
6176
|
+
if (this.isSubAgent) {
|
|
6177
|
+
this.failSubAgent("context_exhausted");
|
|
6178
|
+
} else {
|
|
6179
|
+
this.appendToHistory({
|
|
6180
|
+
role: "assistant",
|
|
6181
|
+
content: "I had to stop here \u2014 this conversation has filled the available context. Compact it to free up room, then ask me to continue."
|
|
6182
|
+
});
|
|
6183
|
+
}
|
|
6184
|
+
return this.turnDone("context-exhausted");
|
|
6185
|
+
}
|
|
5825
6186
|
let response;
|
|
5826
6187
|
try {
|
|
5827
6188
|
response = await activeProvider.chat(historyForCall, userInputForCall, options);
|
|
@@ -5881,6 +6242,27 @@ ${tailBody}
|
|
|
5881
6242
|
}
|
|
5882
6243
|
return this.turnDone("response-truncated");
|
|
5883
6244
|
}
|
|
6245
|
+
if (e instanceof ContextOverflowError) {
|
|
6246
|
+
logger2.error("ChatDriver: the prompt exceeded the model context window", e);
|
|
6247
|
+
recordTurnError(this.sessionKey, "context-exhausted", {
|
|
6248
|
+
agent: this.activeAgentName,
|
|
6249
|
+
provider: this.lastResolvedProviderName,
|
|
6250
|
+
vendor: vendorTypeOfLabel(e.vendorLabel) ?? this.lastResolvedProvider,
|
|
6251
|
+
promptTokens: e.promptTokens,
|
|
6252
|
+
limitTokens: e.limitTokens,
|
|
6253
|
+
via: "provider",
|
|
6254
|
+
isSubAgent: this.isSubAgent
|
|
6255
|
+
});
|
|
6256
|
+
if (this.isSubAgent) {
|
|
6257
|
+
this.failSubAgent("context_exhausted");
|
|
6258
|
+
} else {
|
|
6259
|
+
this.appendToHistory({
|
|
6260
|
+
role: "assistant",
|
|
6261
|
+
content: DEFAULT_CONTEXT_OVERFLOW_MESSAGE
|
|
6262
|
+
});
|
|
6263
|
+
}
|
|
6264
|
+
return this.turnDone("context-exhausted");
|
|
6265
|
+
}
|
|
5884
6266
|
if (e instanceof ProviderRefusedError) {
|
|
5885
6267
|
if (this.isSubAgent) {
|
|
5886
6268
|
logger2.error("ChatDriver: provider refused the request", e);
|
|
@@ -6552,13 +6934,37 @@ var OrchestratingDriver = class extends EventTarget {
|
|
|
6552
6934
|
getRawHistory() {
|
|
6553
6935
|
return this.chatDriver.getHistory();
|
|
6554
6936
|
}
|
|
6937
|
+
/**
|
|
6938
|
+
* Delegates to the inner {@link ChatDriver} — condensation policies are registered there, so it
|
|
6939
|
+
* is the only object that knows which payloads the model has stopped seeing (GENC-1566).
|
|
6940
|
+
*
|
|
6941
|
+
* **This forward is the whole feature, for any multi-agent host.** `AiDriver` declares
|
|
6942
|
+
* `getHistoryForPersistence` optional, and `SessionPersister` calls it through `?.`, falling back
|
|
6943
|
+
* to {@link OrchestratingDriver.getRawHistory}. This class does NOT extend `ChatDriver` — it
|
|
6944
|
+
* wraps one — so without an explicit forward the optional call resolves to `undefined` and every
|
|
6945
|
+
* host on an orchestrating driver silently keeps persisting the full payloads. Measured exactly
|
|
6946
|
+
* that way on a Create run: 10 `context.condensed` events fired, and the session still stored
|
|
6947
|
+
* the two largest `read_context` results at 26,907 and 23,060 chars. The method existed, the
|
|
6948
|
+
* tests passed, and the feature did nothing.
|
|
6949
|
+
*/
|
|
6950
|
+
getHistoryForPersistence() {
|
|
6951
|
+
return this.chatDriver.getHistoryForPersistence();
|
|
6952
|
+
}
|
|
6953
|
+
/** {@inheritDoc AiDriver.setContextGuard} */
|
|
6954
|
+
setContextGuard(policy) {
|
|
6955
|
+
this.chatDriver.setContextGuard(policy);
|
|
6956
|
+
}
|
|
6555
6957
|
/** {@inheritDoc AiDriver.compact} */
|
|
6556
|
-
compact() {
|
|
6557
|
-
return this.chatDriver.compact();
|
|
6958
|
+
compact(options) {
|
|
6959
|
+
return this.chatDriver.compact(options);
|
|
6558
6960
|
}
|
|
6559
6961
|
/** {@inheritDoc AiDriver.canCompact} */
|
|
6560
|
-
canCompact() {
|
|
6561
|
-
return this.chatDriver.canCompact();
|
|
6962
|
+
canCompact(options) {
|
|
6963
|
+
return this.chatDriver.canCompact(options);
|
|
6964
|
+
}
|
|
6965
|
+
/** {@inheritDoc AiDriver.getCompactionPlan} */
|
|
6966
|
+
getCompactionPlan(options) {
|
|
6967
|
+
return this.chatDriver.getCompactionPlan(options);
|
|
6562
6968
|
}
|
|
6563
6969
|
/** Delegates to the inner {@link ChatDriver} — turns are captured there. */
|
|
6564
6970
|
getTurnSnapshots() {
|