@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.cjs
CHANGED
|
@@ -1553,6 +1553,61 @@ function abortableDelay(ms, signal) {
|
|
|
1553
1553
|
});
|
|
1554
1554
|
}
|
|
1555
1555
|
|
|
1556
|
+
// ../../foundation-ai/dist/esm/transports/context-overflow-error.js
|
|
1557
|
+
var CONTEXT_OVERFLOW_PHRASES = Object.freeze([
|
|
1558
|
+
/prompt is too long/i,
|
|
1559
|
+
/input token count .* exceeds/i,
|
|
1560
|
+
/maximum context length/i,
|
|
1561
|
+
/context[_ ]length[_ ]exceeded/i,
|
|
1562
|
+
/too many (input )?tokens/i
|
|
1563
|
+
]);
|
|
1564
|
+
var TOKEN_FIGURES = /(\d[\d,]{2,})\D{0,40}?(?:>|exceeds)\D{0,80}?(\d[\d,]{2,})/i;
|
|
1565
|
+
var OVERFLOW_STATUS = 400;
|
|
1566
|
+
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.";
|
|
1567
|
+
var ContextOverflowError = class extends Error {
|
|
1568
|
+
constructor(vendorLabel, promptTokens, limitTokens, detail) {
|
|
1569
|
+
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.");
|
|
1570
|
+
this.vendorLabel = vendorLabel;
|
|
1571
|
+
this.promptTokens = promptTokens;
|
|
1572
|
+
this.limitTokens = limitTokens;
|
|
1573
|
+
this.detail = detail;
|
|
1574
|
+
this.name = "ContextOverflowError";
|
|
1575
|
+
}
|
|
1576
|
+
};
|
|
1577
|
+
var MAX_ENVELOPE_DEPTH = 4;
|
|
1578
|
+
function messagesIn(payload) {
|
|
1579
|
+
if (!payload || typeof payload !== "object")
|
|
1580
|
+
return [];
|
|
1581
|
+
const found = [];
|
|
1582
|
+
const visit = (node, depth) => {
|
|
1583
|
+
if (depth > MAX_ENVELOPE_DEPTH || !node || typeof node !== "object")
|
|
1584
|
+
return;
|
|
1585
|
+
for (const value of Object.values(node)) {
|
|
1586
|
+
if (typeof value === "string")
|
|
1587
|
+
found.push(value);
|
|
1588
|
+
else if (typeof value === "object")
|
|
1589
|
+
visit(value, depth + 1);
|
|
1590
|
+
}
|
|
1591
|
+
};
|
|
1592
|
+
visit(payload, 0);
|
|
1593
|
+
return found;
|
|
1594
|
+
}
|
|
1595
|
+
function contextOverflowOf(_vendor, status, payload) {
|
|
1596
|
+
if (status !== OVERFLOW_STATUS)
|
|
1597
|
+
return void 0;
|
|
1598
|
+
for (const message of messagesIn(payload)) {
|
|
1599
|
+
if (!CONTEXT_OVERFLOW_PHRASES.some((phrase) => phrase.test(message)))
|
|
1600
|
+
continue;
|
|
1601
|
+
const figures = TOKEN_FIGURES.exec(message);
|
|
1602
|
+
return {
|
|
1603
|
+
promptTokens: figures ? Number(figures[1].replace(/,/g, "")) : void 0,
|
|
1604
|
+
limitTokens: figures ? Number(figures[2].replace(/,/g, "")) : void 0,
|
|
1605
|
+
message
|
|
1606
|
+
};
|
|
1607
|
+
}
|
|
1608
|
+
return void 0;
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1556
1611
|
// ../../foundation-ai/dist/esm/transports/ndjson-frames.js
|
|
1557
1612
|
var MALFORMED_FRAME_PREVIEW_CHARS = 120;
|
|
1558
1613
|
function readFramedBody(stream, onChunk, vendorLabel, stallTimeout) {
|
|
@@ -1681,7 +1736,7 @@ function serverBackoffSpentMs(resolvedMs, ladderMs) {
|
|
|
1681
1736
|
var MAX_RETRIES = 5;
|
|
1682
1737
|
function postWithRetry(options) {
|
|
1683
1738
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1684
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
1739
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
1685
1740
|
const { url, headers, body, credentials, vendorLabel, retryableStatuses, timeout, stallTimeout, backoffBaseMs, signal } = options;
|
|
1686
1741
|
const vendor = vendorOfTransportLabel(vendorLabel);
|
|
1687
1742
|
let serverBackoffSpent = 0;
|
|
@@ -1734,6 +1789,10 @@ function postWithRetry(options) {
|
|
|
1734
1789
|
if (refusal) {
|
|
1735
1790
|
throw providerRefusedFrom(vendorLabel, refusal, response.status, parsed, errText || void 0);
|
|
1736
1791
|
}
|
|
1792
|
+
const overflow = contextOverflowOf(vendor, response.status, parsed);
|
|
1793
|
+
if (overflow) {
|
|
1794
|
+
throw new ContextOverflowError(vendorLabel, overflow.promptTokens, overflow.limitTokens, (_d = overflow.message) !== null && _d !== void 0 ? _d : errText || void 0);
|
|
1795
|
+
}
|
|
1737
1796
|
if (isBudgetRejection(response.status, codeOf(parsed))) {
|
|
1738
1797
|
throw budgetExhaustedFrom(vendorLabel, parsed, errText || void 0);
|
|
1739
1798
|
}
|
|
@@ -1746,18 +1805,22 @@ function postWithRetry(options) {
|
|
|
1746
1805
|
}
|
|
1747
1806
|
throw new Error(`${vendorLabel} request error ${response.status}: ${errText}`);
|
|
1748
1807
|
}
|
|
1749
|
-
if (((
|
|
1808
|
+
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) {
|
|
1750
1809
|
rearmStallTimer();
|
|
1751
1810
|
const frame = yield readFramedBody(response.body, rearmStallTimer, vendorLabel, stallTimeout);
|
|
1752
1811
|
if (frame.t === "ok")
|
|
1753
1812
|
return frame.body;
|
|
1754
1813
|
if (frame.code === "UPSTREAM_STALLED") {
|
|
1755
|
-
throw new DOMException(`${vendorLabel} request stalled: ${(
|
|
1814
|
+
throw new DOMException(`${vendorLabel} request stalled: ${(_g = frame.error) !== null && _g !== void 0 ? _g : "no upstream data"}`, "TimeoutError");
|
|
1756
1815
|
}
|
|
1757
1816
|
const framedRefusal = providerRefusalOf(vendor, frame.code, frame);
|
|
1758
1817
|
if (framedRefusal) {
|
|
1759
1818
|
throw providerRefusedFrom(vendorLabel, framedRefusal, frame.status, frame, frame.error);
|
|
1760
1819
|
}
|
|
1820
|
+
const framedOverflow = contextOverflowOf(vendor, frame.status, frame);
|
|
1821
|
+
if (framedOverflow) {
|
|
1822
|
+
throw new ContextOverflowError(vendorLabel, framedOverflow.promptTokens, framedOverflow.limitTokens, (_h = framedOverflow.message) !== null && _h !== void 0 ? _h : frame.error);
|
|
1823
|
+
}
|
|
1761
1824
|
if (isBudgetRejection(frame.status, frame.code)) {
|
|
1762
1825
|
throw budgetExhaustedFrom(vendorLabel, frame);
|
|
1763
1826
|
}
|
|
@@ -1767,7 +1830,7 @@ function postWithRetry(options) {
|
|
|
1767
1830
|
yield backoff(attempt, retryInfoMs(frame));
|
|
1768
1831
|
continue;
|
|
1769
1832
|
}
|
|
1770
|
-
throw new Error(`${vendorLabel} request error ${frame.status}: ${(
|
|
1833
|
+
throw new Error(`${vendorLabel} request error ${frame.status}: ${(_j = frame.error) !== null && _j !== void 0 ? _j : JSON.stringify(frame.details)}`);
|
|
1771
1834
|
}
|
|
1772
1835
|
return yield response.json();
|
|
1773
1836
|
} catch (e) {
|
|
@@ -3732,10 +3795,126 @@ function createInteractionContext(interactionId) {
|
|
|
3732
3795
|
};
|
|
3733
3796
|
}
|
|
3734
3797
|
|
|
3798
|
+
// src/utils/context-tokens.ts
|
|
3799
|
+
var APPROX_CHARS_PER_TOKEN = 4;
|
|
3800
|
+
var IMAGE_TOKEN_ESTIMATE = 1600;
|
|
3801
|
+
var PER_MESSAGE_FRAMING_TOKENS = 4;
|
|
3802
|
+
var COMPACTION_SUMMARY_TOKEN_ESTIMATE = 1200;
|
|
3803
|
+
function reachesProvider(m) {
|
|
3804
|
+
if (m.role === "system-event" || m.role === "synthetic-user") return false;
|
|
3805
|
+
if (m.category === "reasoning" || m.category === "narration") return false;
|
|
3806
|
+
return !m.thinking;
|
|
3807
|
+
}
|
|
3808
|
+
function imageCount(m) {
|
|
3809
|
+
const onMessage = m.attachments?.filter((a) => a.kind === "image").length ?? 0;
|
|
3810
|
+
const onResult = m.toolResult?.attachments?.filter((a) => a.kind === "image").length ?? 0;
|
|
3811
|
+
return onMessage + onResult;
|
|
3812
|
+
}
|
|
3813
|
+
var TEXT_ATTACHMENT_FRAMING_CHARS = 9;
|
|
3814
|
+
function wireChars(m) {
|
|
3815
|
+
let chars = m.content?.length ?? 0;
|
|
3816
|
+
for (const attachment of m.attachments ?? []) {
|
|
3817
|
+
if (attachment.kind === "image") continue;
|
|
3818
|
+
chars += TEXT_ATTACHMENT_FRAMING_CHARS + attachment.name.length + (attachment.content?.length ?? 0);
|
|
3819
|
+
}
|
|
3820
|
+
for (const tc of m.toolCalls ?? []) {
|
|
3821
|
+
chars += tc.name.length;
|
|
3822
|
+
try {
|
|
3823
|
+
chars += JSON.stringify(tc.args ?? {}).length;
|
|
3824
|
+
} catch {
|
|
3825
|
+
}
|
|
3826
|
+
}
|
|
3827
|
+
chars += m.toolResult?.content?.length ?? 0;
|
|
3828
|
+
return chars;
|
|
3829
|
+
}
|
|
3830
|
+
function baselineTokens(m) {
|
|
3831
|
+
if (!reachesProvider(m)) return 0;
|
|
3832
|
+
return Math.ceil(wireChars(m) / APPROX_CHARS_PER_TOKEN) + imageCount(m) * IMAGE_TOKEN_ESTIMATE + PER_MESSAGE_FRAMING_TOKENS;
|
|
3833
|
+
}
|
|
3834
|
+
function compactedAtOf(history) {
|
|
3835
|
+
return history.find((m) => m.role === "compacted-summary")?.compaction?.createdAt;
|
|
3836
|
+
}
|
|
3837
|
+
function usableAnchor(m, compactedAt) {
|
|
3838
|
+
if (m.inputTokens == null) return false;
|
|
3839
|
+
if (!compactedAt) return true;
|
|
3840
|
+
return !!m.timestamp && m.timestamp >= compactedAt;
|
|
3841
|
+
}
|
|
3842
|
+
function firstAnchor(history, compactedAt) {
|
|
3843
|
+
return history.findIndex((m) => usableAnchor(m, compactedAt));
|
|
3844
|
+
}
|
|
3845
|
+
function estimateMessageTokens(history) {
|
|
3846
|
+
const estimates = history.map(baselineTokens);
|
|
3847
|
+
const compactedAt = compactedAtOf(history);
|
|
3848
|
+
const anchors = [];
|
|
3849
|
+
history.forEach((m, i) => {
|
|
3850
|
+
if (usableAnchor(m, compactedAt)) anchors.push(i);
|
|
3851
|
+
});
|
|
3852
|
+
for (let a = 0; a < anchors.length - 1; a += 1) {
|
|
3853
|
+
const from = anchors[a];
|
|
3854
|
+
const to = anchors[a + 1];
|
|
3855
|
+
const measured = history[to].inputTokens - history[from].inputTokens;
|
|
3856
|
+
if (measured <= 0) continue;
|
|
3857
|
+
let baseline = 0;
|
|
3858
|
+
for (let i = from; i < to; i += 1) baseline += estimates[i];
|
|
3859
|
+
if (baseline <= 0) {
|
|
3860
|
+
estimates[from] = measured;
|
|
3861
|
+
continue;
|
|
3862
|
+
}
|
|
3863
|
+
const scale = measured / baseline;
|
|
3864
|
+
let assigned = 0;
|
|
3865
|
+
for (let i = from; i < to - 1; i += 1) {
|
|
3866
|
+
estimates[i] = Math.round(estimates[i] * scale);
|
|
3867
|
+
assigned += estimates[i];
|
|
3868
|
+
}
|
|
3869
|
+
estimates[to - 1] = measured - assigned;
|
|
3870
|
+
}
|
|
3871
|
+
return estimates;
|
|
3872
|
+
}
|
|
3873
|
+
function estimateSystemOverhead(history) {
|
|
3874
|
+
const first = firstAnchor(history, compactedAtOf(history));
|
|
3875
|
+
if (first < 0) return 0;
|
|
3876
|
+
const estimates = estimateMessageTokens(history);
|
|
3877
|
+
let messages = 0;
|
|
3878
|
+
for (let i = 0; i < first; i += 1) messages += estimates[i];
|
|
3879
|
+
return Math.max(0, history[first].inputTokens - messages);
|
|
3880
|
+
}
|
|
3881
|
+
function estimateContextTokens(history) {
|
|
3882
|
+
const estimates = estimateMessageTokens(history);
|
|
3883
|
+
let total = estimateSystemOverhead(history);
|
|
3884
|
+
for (const t of estimates) total += t;
|
|
3885
|
+
return total;
|
|
3886
|
+
}
|
|
3887
|
+
function estimateRequestTokens(storedHistory, requestHistory) {
|
|
3888
|
+
const overhead = estimateSystemOverhead(storedHistory);
|
|
3889
|
+
const calibratedMessages = Math.max(0, estimateContextTokens(storedHistory) - overhead);
|
|
3890
|
+
let storedBaseline = 0;
|
|
3891
|
+
for (const m of storedHistory) storedBaseline += baselineTokens(m);
|
|
3892
|
+
let requestBaseline = 0;
|
|
3893
|
+
for (const m of requestHistory) requestBaseline += baselineTokens(m);
|
|
3894
|
+
if (storedBaseline <= 0) return overhead + requestBaseline;
|
|
3895
|
+
return Math.round(overhead + calibratedMessages * (requestBaseline / storedBaseline));
|
|
3896
|
+
}
|
|
3897
|
+
function projectCompaction(history, cut, summaryTokens = COMPACTION_SUMMARY_TOKEN_ESTIMATE) {
|
|
3898
|
+
const estimates = estimateMessageTokens(history);
|
|
3899
|
+
const overhead = estimateSystemOverhead(history);
|
|
3900
|
+
let tail = 0;
|
|
3901
|
+
for (let i = cut; i < estimates.length; i += 1) tail += estimates[i];
|
|
3902
|
+
let all = 0;
|
|
3903
|
+
for (const t of estimates) all += t;
|
|
3904
|
+
const tokensBefore = overhead + all;
|
|
3905
|
+
const tokensAfter = overhead + summaryTokens + tail;
|
|
3906
|
+
return {
|
|
3907
|
+
cut,
|
|
3908
|
+
compactedCount: cut,
|
|
3909
|
+
tokensBefore,
|
|
3910
|
+
tokensAfter,
|
|
3911
|
+
reclaimed: Math.max(0, tokensBefore - tokensAfter)
|
|
3912
|
+
};
|
|
3913
|
+
}
|
|
3914
|
+
|
|
3735
3915
|
// src/utils/condense-history.ts
|
|
3736
3916
|
var CONDENSE_MIN_CHARS = 1e3;
|
|
3737
3917
|
var CONDENSED_ARGS_KEY = "condensed";
|
|
3738
|
-
var APPROX_CHARS_PER_TOKEN = 4;
|
|
3739
3918
|
function triggerReason(trigger) {
|
|
3740
3919
|
switch (trigger.kind) {
|
|
3741
3920
|
case "age":
|
|
@@ -3750,13 +3929,21 @@ function triggerReason(trigger) {
|
|
|
3750
3929
|
return "superseded";
|
|
3751
3930
|
}
|
|
3752
3931
|
}
|
|
3753
|
-
function condenseStub(target, tool, trigger, origLen, restorable,
|
|
3932
|
+
function condenseStub(target, tool, trigger, origLen, restorable, imageCount2 = 0) {
|
|
3754
3933
|
const what = target === "args" ? "args" : "result";
|
|
3755
3934
|
const key = trigger.kind === "superseded" ? ` ${trigger.by}` : "";
|
|
3756
3935
|
const restore = restorable ? "; re-call to restore" : "";
|
|
3757
|
-
const images =
|
|
3936
|
+
const images = imageCount2 > 0 ? ` + ${imageCount2} image${imageCount2 === 1 ? "" : "s"}` : "";
|
|
3758
3937
|
return `[${tool}${key} \u2014 ${what} elided, ~${origLen} chars${images} (${triggerReason(trigger)})${restore}]`;
|
|
3759
3938
|
}
|
|
3939
|
+
function renderCondensedArgs(policy, tool, fired, origLen) {
|
|
3940
|
+
return {
|
|
3941
|
+
[CONDENSED_ARGS_KEY]: policy === "drop" || policy === "pointer" ? condenseStub("args", tool, fired, origLen, policy === "pointer") : policy.replaceWith
|
|
3942
|
+
};
|
|
3943
|
+
}
|
|
3944
|
+
function renderCondensedResult(policy, tool, fired, textLen, imageCount2) {
|
|
3945
|
+
return policy === "drop" || policy === "pointer" ? condenseStub("response", tool, fired, textLen, policy === "pointer", imageCount2) : policy.replaceWith;
|
|
3946
|
+
}
|
|
3760
3947
|
function triggerLabel(trigger) {
|
|
3761
3948
|
switch (trigger.kind) {
|
|
3762
3949
|
case "age":
|
|
@@ -3831,10 +4018,7 @@ function applyCondensation(history, policies, ctx, onCondensed) {
|
|
|
3831
4018
|
const origLen = JSON.stringify(tc.args ?? {}).length;
|
|
3832
4019
|
if (origLen < CONDENSE_MIN_CHARS) return tc;
|
|
3833
4020
|
changed = true;
|
|
3834
|
-
const
|
|
3835
|
-
const args = {
|
|
3836
|
-
[CONDENSED_ARGS_KEY]: result === "drop" || result === "pointer" ? condenseStub("args", tc.name, fired, origLen, result === "pointer") : result.replaceWith
|
|
3837
|
-
};
|
|
4021
|
+
const args = renderCondensedArgs(entry.policy.args, tc.name, fired, origLen);
|
|
3838
4022
|
if (!entry.reportedArgs) {
|
|
3839
4023
|
entry.reportedArgs = true;
|
|
3840
4024
|
const stubLen = JSON.stringify(args).length;
|
|
@@ -3860,8 +4044,13 @@ function applyCondensation(history, policies, ctx, onCondensed) {
|
|
|
3860
4044
|
const fired = entry?.policy.response ? firstFired(msg.toolResult.toolCallId, entry) : void 0;
|
|
3861
4045
|
if (entry && fired && origLen >= CONDENSE_MIN_CHARS) {
|
|
3862
4046
|
const tool = nameById.get(msg.toolResult.toolCallId) ?? msg.toolResult.toolCallId;
|
|
3863
|
-
const
|
|
3864
|
-
|
|
4047
|
+
const content = renderCondensedResult(
|
|
4048
|
+
entry.policy.response,
|
|
4049
|
+
tool,
|
|
4050
|
+
fired,
|
|
4051
|
+
textLen,
|
|
4052
|
+
images.length
|
|
4053
|
+
);
|
|
3865
4054
|
if (!entry.reportedResponse) {
|
|
3866
4055
|
entry.reportedResponse = true;
|
|
3867
4056
|
onCondensed({
|
|
@@ -3881,6 +4070,53 @@ function applyCondensation(history, policies, ctx, onCondensed) {
|
|
|
3881
4070
|
return msg;
|
|
3882
4071
|
});
|
|
3883
4072
|
}
|
|
4073
|
+
function condenseForPersistence(history, policies) {
|
|
4074
|
+
if (policies.size === 0) return history;
|
|
4075
|
+
const nameById = /* @__PURE__ */ new Map();
|
|
4076
|
+
for (const msg of history) {
|
|
4077
|
+
for (const tc of msg.toolCalls ?? []) nameById.set(tc.id, tc.name);
|
|
4078
|
+
}
|
|
4079
|
+
let changed = false;
|
|
4080
|
+
const projected = history.map((msg) => {
|
|
4081
|
+
if (msg.toolCalls?.length) {
|
|
4082
|
+
let callsChanged = false;
|
|
4083
|
+
const toolCalls = msg.toolCalls.map((tc) => {
|
|
4084
|
+
const entry = policies.get(tc.id);
|
|
4085
|
+
if (!entry?.reportedArgs || !entry.firedTrigger || !entry.policy.args) return tc;
|
|
4086
|
+
callsChanged = true;
|
|
4087
|
+
const origLen = JSON.stringify(tc.args ?? {}).length;
|
|
4088
|
+
return {
|
|
4089
|
+
...tc,
|
|
4090
|
+
args: renderCondensedArgs(entry.policy.args, tc.name, entry.firedTrigger, origLen)
|
|
4091
|
+
};
|
|
4092
|
+
});
|
|
4093
|
+
if (callsChanged) {
|
|
4094
|
+
changed = true;
|
|
4095
|
+
return { ...msg, toolCalls };
|
|
4096
|
+
}
|
|
4097
|
+
return msg;
|
|
4098
|
+
}
|
|
4099
|
+
if (msg.toolResult) {
|
|
4100
|
+
const entry = policies.get(msg.toolResult.toolCallId);
|
|
4101
|
+
if (entry?.reportedResponse && entry.firedTrigger && entry.policy.response) {
|
|
4102
|
+
changed = true;
|
|
4103
|
+
const tool = nameById.get(msg.toolResult.toolCallId) ?? msg.toolResult.toolCallId;
|
|
4104
|
+
const images = msg.toolResult.attachments ?? [];
|
|
4105
|
+
const content = renderCondensedResult(
|
|
4106
|
+
entry.policy.response,
|
|
4107
|
+
tool,
|
|
4108
|
+
entry.firedTrigger,
|
|
4109
|
+
msg.toolResult.content?.length ?? 0,
|
|
4110
|
+
images.length
|
|
4111
|
+
);
|
|
4112
|
+
const { attachments: _elided, ...restOfResult } = msg.toolResult;
|
|
4113
|
+
return { ...msg, toolResult: { ...restOfResult, content } };
|
|
4114
|
+
}
|
|
4115
|
+
}
|
|
4116
|
+
return msg;
|
|
4117
|
+
});
|
|
4118
|
+
return changed ? projected : history;
|
|
4119
|
+
}
|
|
3884
4120
|
|
|
3885
4121
|
// src/utils/history-transform.ts
|
|
3886
4122
|
function maskToolPayload(msg) {
|
|
@@ -3906,20 +4142,43 @@ function applyHistoryCap(history, cap) {
|
|
|
3906
4142
|
const cutoff = history.length - cap;
|
|
3907
4143
|
return history.map((msg, i) => i < cutoff ? maskToolPayload(msg) : msg);
|
|
3908
4144
|
}
|
|
3909
|
-
var
|
|
3910
|
-
var
|
|
3911
|
-
function
|
|
3912
|
-
if (
|
|
3913
|
-
|
|
4145
|
+
var DEFAULT_TAIL_TOKEN_BUDGET = 3e4;
|
|
4146
|
+
var DEFAULT_MIN_RECLAIM_TOKENS = 5e3;
|
|
4147
|
+
function isSafeCompactionCut(history, i) {
|
|
4148
|
+
if (i <= 0 || i >= history.length) return false;
|
|
4149
|
+
for (let j = i; j < history.length; j += 1) {
|
|
4150
|
+
if (!reachesProvider(history[j])) continue;
|
|
4151
|
+
return history[j].role !== "tool";
|
|
3914
4152
|
}
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
4153
|
+
return true;
|
|
4154
|
+
}
|
|
4155
|
+
function findCompactionCut(history, tailTokenBudget = DEFAULT_TAIL_TOKEN_BUDGET) {
|
|
4156
|
+
const estimates = estimateMessageTokens(history);
|
|
4157
|
+
let messagesTotal = 0;
|
|
4158
|
+
for (const t of estimates) messagesTotal += t;
|
|
4159
|
+
if (messagesTotal <= tailTokenBudget) return null;
|
|
4160
|
+
let tail = 0;
|
|
4161
|
+
let raw = history.length;
|
|
4162
|
+
for (let i = history.length - 1; i > 0; i -= 1) {
|
|
4163
|
+
if (tail + estimates[i] > tailTokenBudget) break;
|
|
4164
|
+
tail += estimates[i];
|
|
4165
|
+
raw = i;
|
|
4166
|
+
}
|
|
4167
|
+
for (let i = raw; i < history.length; i += 1) {
|
|
4168
|
+
if (isSafeCompactionCut(history, i)) return i;
|
|
3920
4169
|
}
|
|
3921
4170
|
return null;
|
|
3922
4171
|
}
|
|
4172
|
+
function planCompaction(history, options = {}) {
|
|
4173
|
+
const {
|
|
4174
|
+
tailTokenBudget = DEFAULT_TAIL_TOKEN_BUDGET,
|
|
4175
|
+
minReclaimTokens = DEFAULT_MIN_RECLAIM_TOKENS
|
|
4176
|
+
} = options;
|
|
4177
|
+
const cut = findCompactionCut(history, tailTokenBudget);
|
|
4178
|
+
if (cut == null) return null;
|
|
4179
|
+
const projection = projectCompaction(history, cut);
|
|
4180
|
+
return projection.reclaimed >= minReclaimTokens ? projection : null;
|
|
4181
|
+
}
|
|
3923
4182
|
function renderMessageForSummary(m) {
|
|
3924
4183
|
switch (m.role) {
|
|
3925
4184
|
case "compacted-summary":
|
|
@@ -4649,6 +4908,7 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
4649
4908
|
const status = await this.resolveStatusForProvider(resolvedName, provider);
|
|
4650
4909
|
this.lastResolvedModel = status.model;
|
|
4651
4910
|
this.lastResolvedProvider = status.provider;
|
|
4911
|
+
this.lastResolvedContextLimit = status.contextLimit;
|
|
4652
4912
|
if (resolvedName !== this.lastDispatchedProviderName) {
|
|
4653
4913
|
this.lastDispatchedProviderName = resolvedName;
|
|
4654
4914
|
recordMetaEvent(this.sessionKey, "provider.selected", {
|
|
@@ -4680,7 +4940,13 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
4680
4940
|
try {
|
|
4681
4941
|
const resolved = await provider.getStatus?.();
|
|
4682
4942
|
if (resolved) {
|
|
4683
|
-
status = {
|
|
4943
|
+
status = {
|
|
4944
|
+
model: resolved.model,
|
|
4945
|
+
provider: resolved.provider,
|
|
4946
|
+
// Carried so the mid-loop guard can measure against the window of the
|
|
4947
|
+
// provider this call actually resolved to (GENC-1567).
|
|
4948
|
+
contextLimit: resolved.contextLimit
|
|
4949
|
+
};
|
|
4684
4950
|
}
|
|
4685
4951
|
} catch {
|
|
4686
4952
|
status = {};
|
|
@@ -4837,9 +5103,48 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
4837
5103
|
return snapshot;
|
|
4838
5104
|
}
|
|
4839
5105
|
/**
|
|
4840
|
-
*
|
|
4841
|
-
*
|
|
5106
|
+
* Set the mid-loop context guard. Its margin is deliberately far smaller than
|
|
5107
|
+
* the reserve that blocks NEW turns: that reserve exists so an accepted turn can
|
|
5108
|
+
* spend it, and a guard set at the same line would kill every turn that used
|
|
5109
|
+
* the headroom it was given.
|
|
5110
|
+
*/
|
|
5111
|
+
setContextGuard(policy) {
|
|
5112
|
+
this.contextGuard = policy && policy.marginTokens > 0 ? policy : void 0;
|
|
5113
|
+
}
|
|
5114
|
+
/**
|
|
5115
|
+
* Whether issuing `requestHistory` would run the context window out.
|
|
5116
|
+
*
|
|
5117
|
+
* Inert unless a margin has been set AND the resolved provider reports a
|
|
5118
|
+
* window — with no window there is nothing to measure against, and inventing
|
|
5119
|
+
* one to refuse a request is worse than letting the provider answer.
|
|
5120
|
+
*
|
|
5121
|
+
* Stopping here is strictly better than letting the request go: the provider
|
|
5122
|
+
* would reject an oversized prompt outright, leaving a transcript still too
|
|
5123
|
+
* large to retry and no explanation the user can act on, whereas ending the
|
|
5124
|
+
* turn keeps history intact so compaction is still available and the work so
|
|
5125
|
+
* far is not lost.
|
|
4842
5126
|
*/
|
|
5127
|
+
contextExhausted(requestHistory, pendingInput) {
|
|
5128
|
+
const guard = this.contextGuard;
|
|
5129
|
+
if (!guard) return void 0;
|
|
5130
|
+
const limit = this.lastResolvedContextLimit ?? guard.fallbackLimit;
|
|
5131
|
+
if (limit == null || limit <= 0) return void 0;
|
|
5132
|
+
const threshold = Math.max(0, limit - guard.marginTokens);
|
|
5133
|
+
const request = pendingInput && (pendingInput.content || pendingInput.attachments?.length) ? [
|
|
5134
|
+
...requestHistory,
|
|
5135
|
+
{
|
|
5136
|
+
role: "user",
|
|
5137
|
+
content: pendingInput.content,
|
|
5138
|
+
attachments: pendingInput.attachments
|
|
5139
|
+
}
|
|
5140
|
+
] : requestHistory;
|
|
5141
|
+
const estimated = estimateRequestTokens(this.history, request);
|
|
5142
|
+
if (estimated < threshold) return void 0;
|
|
5143
|
+
logger2.error(
|
|
5144
|
+
`ChatDriver: ending the turn \u2014 the request would fill the context window (~${estimated} of ${limit})`
|
|
5145
|
+
);
|
|
5146
|
+
return { estimated, threshold };
|
|
5147
|
+
}
|
|
4843
5148
|
setProviderHistoryTransform(transform) {
|
|
4844
5149
|
this.providerHistoryTransform = transform;
|
|
4845
5150
|
}
|
|
@@ -4853,6 +5158,26 @@ var ChatDriver = class _ChatDriver extends EventTarget {
|
|
|
4853
5158
|
getActiveFoldNames() {
|
|
4854
5159
|
return this.foldStack.map((f) => f.foldName);
|
|
4855
5160
|
}
|
|
5161
|
+
/**
|
|
5162
|
+
* The transcript as it should be **stored**: every tool payload the model has already stopped
|
|
5163
|
+
* seeing replaced by the same stub it was sent (GENC-1566). Live history is untouched — this
|
|
5164
|
+
* returns a copy.
|
|
5165
|
+
*
|
|
5166
|
+
* Condensation deliberately spares stored history, because the live transcript and the debug
|
|
5167
|
+
* export both read it. A host writing that transcript to durable storage inherits the other
|
|
5168
|
+
* half of that decision: it stores payloads the driver has already retired, on every autosave,
|
|
5169
|
+
* for the life of the session. Measured on one small Create session — 58,187 chars, 7.4% of a
|
|
5170
|
+
* 790 KB session object, across five payloads worth 14,434 tokens of context, written 86 times.
|
|
5171
|
+
*
|
|
5172
|
+
* Only already-collapsed payloads are touched, so this can never store a stub for something the
|
|
5173
|
+
* model can still see, and it has no side effects (see `condenseForPersistence`, which is
|
|
5174
|
+
* internal). A host that wants the unredacted transcript still calls
|
|
5175
|
+
* {@link ChatDriver.getRawHistory}, and a diagnostics stream that keeps full payloads is
|
|
5176
|
+
* unaffected — which is what makes the elision recoverable.
|
|
5177
|
+
*/
|
|
5178
|
+
getHistoryForPersistence() {
|
|
5179
|
+
return condenseForPersistence([...this.history], this.condensePolicies);
|
|
5180
|
+
}
|
|
4856
5181
|
async getSuggestions(history, prompt2, count, allAgentInfo) {
|
|
4857
5182
|
const defaultProvider = this.providerRegistry.default();
|
|
4858
5183
|
if (!defaultProvider.prompt) {
|
|
@@ -4918,8 +5243,19 @@ Output format (strict):
|
|
|
4918
5243
|
* turns exists behind a clean boundary. Uses the same `history` `compact()`
|
|
4919
5244
|
* acts on, so the UI's gate can't disagree with the action (GENC-1351 follow-up).
|
|
4920
5245
|
*/
|
|
4921
|
-
canCompact() {
|
|
4922
|
-
return
|
|
5246
|
+
canCompact(options) {
|
|
5247
|
+
return this.getCompactionPlan(options) != null;
|
|
5248
|
+
}
|
|
5249
|
+
/**
|
|
5250
|
+
* {@inheritDoc AiDriver.getCompactionPlan}
|
|
5251
|
+
*
|
|
5252
|
+
* Runs against the driver's own `history` — the exact list `compact()` acts on
|
|
5253
|
+
* — so a projection and the compaction it describes can never be computed from
|
|
5254
|
+
* different transcripts (the GENC-1351 follow-up that first made `canCompact`
|
|
5255
|
+
* read `history` rather than a mirrored copy).
|
|
5256
|
+
*/
|
|
5257
|
+
getCompactionPlan(options) {
|
|
5258
|
+
return planCompaction(this.history, options);
|
|
4923
5259
|
}
|
|
4924
5260
|
/**
|
|
4925
5261
|
* Destructively compact older turns into a single `compacted-summary` message
|
|
@@ -4930,7 +5266,7 @@ Output format (strict):
|
|
|
4930
5266
|
* the new one. Returns the created summary message, or `null` when there is
|
|
4931
5267
|
* nothing worth compacting or the default provider cannot summarize.
|
|
4932
5268
|
*/
|
|
4933
|
-
async compact() {
|
|
5269
|
+
async compact(options) {
|
|
4934
5270
|
if (this.busy) return null;
|
|
4935
5271
|
const defaultProvider = this.providerRegistry.default();
|
|
4936
5272
|
if (!defaultProvider.prompt) {
|
|
@@ -4938,7 +5274,7 @@ Output format (strict):
|
|
|
4938
5274
|
return null;
|
|
4939
5275
|
}
|
|
4940
5276
|
const history = this.history;
|
|
4941
|
-
const cut =
|
|
5277
|
+
const cut = planCompaction(history, options)?.cut;
|
|
4942
5278
|
if (cut == null) return null;
|
|
4943
5279
|
const toCompact = history.slice(0, cut);
|
|
4944
5280
|
const tail = history.slice(cut);
|
|
@@ -5409,6 +5745,7 @@ Output format (strict):
|
|
|
5409
5745
|
activityBus: this.activityBus
|
|
5410
5746
|
});
|
|
5411
5747
|
child.markAsSubAgent();
|
|
5748
|
+
child.setContextGuard(this.contextGuard);
|
|
5412
5749
|
const disposeChild = () => child.dispose();
|
|
5413
5750
|
if (this.lifecycleController.signal.aborted) {
|
|
5414
5751
|
disposeChild();
|
|
@@ -5889,6 +6226,30 @@ ${tailBody}
|
|
|
5889
6226
|
if (this.lastResolvedProvider !== void 0)
|
|
5890
6227
|
turnSnapshot.provider = this.lastResolvedProvider;
|
|
5891
6228
|
if (this.lastResolvedModel !== void 0) turnSnapshot.model = this.lastResolvedModel;
|
|
6229
|
+
const contextStop = this.contextExhausted(historyForCall, {
|
|
6230
|
+
content: userInputForCall,
|
|
6231
|
+
attachments: attachmentsForCall
|
|
6232
|
+
});
|
|
6233
|
+
if (contextStop) {
|
|
6234
|
+
recordTurnError(this.sessionKey, "context-exhausted", {
|
|
6235
|
+
agent: this.activeAgentName,
|
|
6236
|
+
provider: this.lastResolvedProviderName,
|
|
6237
|
+
contextTokens: contextStop.estimated,
|
|
6238
|
+
guard: contextStop.threshold,
|
|
6239
|
+
limit: this.lastResolvedContextLimit,
|
|
6240
|
+
iterations,
|
|
6241
|
+
isSubAgent: this.isSubAgent
|
|
6242
|
+
});
|
|
6243
|
+
if (this.isSubAgent) {
|
|
6244
|
+
this.failSubAgent("context_exhausted");
|
|
6245
|
+
} else {
|
|
6246
|
+
this.appendToHistory({
|
|
6247
|
+
role: "assistant",
|
|
6248
|
+
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."
|
|
6249
|
+
});
|
|
6250
|
+
}
|
|
6251
|
+
return this.turnDone("context-exhausted");
|
|
6252
|
+
}
|
|
5892
6253
|
let response;
|
|
5893
6254
|
try {
|
|
5894
6255
|
response = await activeProvider.chat(historyForCall, userInputForCall, options);
|
|
@@ -5948,6 +6309,27 @@ ${tailBody}
|
|
|
5948
6309
|
}
|
|
5949
6310
|
return this.turnDone("response-truncated");
|
|
5950
6311
|
}
|
|
6312
|
+
if (e instanceof ContextOverflowError) {
|
|
6313
|
+
logger2.error("ChatDriver: the prompt exceeded the model context window", e);
|
|
6314
|
+
recordTurnError(this.sessionKey, "context-exhausted", {
|
|
6315
|
+
agent: this.activeAgentName,
|
|
6316
|
+
provider: this.lastResolvedProviderName,
|
|
6317
|
+
vendor: vendorTypeOfLabel(e.vendorLabel) ?? this.lastResolvedProvider,
|
|
6318
|
+
promptTokens: e.promptTokens,
|
|
6319
|
+
limitTokens: e.limitTokens,
|
|
6320
|
+
via: "provider",
|
|
6321
|
+
isSubAgent: this.isSubAgent
|
|
6322
|
+
});
|
|
6323
|
+
if (this.isSubAgent) {
|
|
6324
|
+
this.failSubAgent("context_exhausted");
|
|
6325
|
+
} else {
|
|
6326
|
+
this.appendToHistory({
|
|
6327
|
+
role: "assistant",
|
|
6328
|
+
content: DEFAULT_CONTEXT_OVERFLOW_MESSAGE
|
|
6329
|
+
});
|
|
6330
|
+
}
|
|
6331
|
+
return this.turnDone("context-exhausted");
|
|
6332
|
+
}
|
|
5951
6333
|
if (e instanceof ProviderRefusedError) {
|
|
5952
6334
|
if (this.isSubAgent) {
|
|
5953
6335
|
logger2.error("ChatDriver: provider refused the request", e);
|
|
@@ -6619,13 +7001,37 @@ var OrchestratingDriver = class extends EventTarget {
|
|
|
6619
7001
|
getRawHistory() {
|
|
6620
7002
|
return this.chatDriver.getHistory();
|
|
6621
7003
|
}
|
|
7004
|
+
/**
|
|
7005
|
+
* Delegates to the inner {@link ChatDriver} — condensation policies are registered there, so it
|
|
7006
|
+
* is the only object that knows which payloads the model has stopped seeing (GENC-1566).
|
|
7007
|
+
*
|
|
7008
|
+
* **This forward is the whole feature, for any multi-agent host.** `AiDriver` declares
|
|
7009
|
+
* `getHistoryForPersistence` optional, and `SessionPersister` calls it through `?.`, falling back
|
|
7010
|
+
* to {@link OrchestratingDriver.getRawHistory}. This class does NOT extend `ChatDriver` — it
|
|
7011
|
+
* wraps one — so without an explicit forward the optional call resolves to `undefined` and every
|
|
7012
|
+
* host on an orchestrating driver silently keeps persisting the full payloads. Measured exactly
|
|
7013
|
+
* that way on a Create run: 10 `context.condensed` events fired, and the session still stored
|
|
7014
|
+
* the two largest `read_context` results at 26,907 and 23,060 chars. The method existed, the
|
|
7015
|
+
* tests passed, and the feature did nothing.
|
|
7016
|
+
*/
|
|
7017
|
+
getHistoryForPersistence() {
|
|
7018
|
+
return this.chatDriver.getHistoryForPersistence();
|
|
7019
|
+
}
|
|
7020
|
+
/** {@inheritDoc AiDriver.setContextGuard} */
|
|
7021
|
+
setContextGuard(policy) {
|
|
7022
|
+
this.chatDriver.setContextGuard(policy);
|
|
7023
|
+
}
|
|
6622
7024
|
/** {@inheritDoc AiDriver.compact} */
|
|
6623
|
-
compact() {
|
|
6624
|
-
return this.chatDriver.compact();
|
|
7025
|
+
compact(options) {
|
|
7026
|
+
return this.chatDriver.compact(options);
|
|
6625
7027
|
}
|
|
6626
7028
|
/** {@inheritDoc AiDriver.canCompact} */
|
|
6627
|
-
canCompact() {
|
|
6628
|
-
return this.chatDriver.canCompact();
|
|
7029
|
+
canCompact(options) {
|
|
7030
|
+
return this.chatDriver.canCompact(options);
|
|
7031
|
+
}
|
|
7032
|
+
/** {@inheritDoc AiDriver.getCompactionPlan} */
|
|
7033
|
+
getCompactionPlan(options) {
|
|
7034
|
+
return this.chatDriver.getCompactionPlan(options);
|
|
6629
7035
|
}
|
|
6630
7036
|
/** Delegates to the inner {@link ChatDriver} — turns are captured there. */
|
|
6631
7037
|
getTurnSnapshots() {
|