@royalcat/opencode-dcp-rc 4.0.0 → 4.0.2
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/README.md +21 -1
- package/dist/index.js +1402 -27
- package/dist/index.js.map +1 -1
- package/dist/lib/commands/stats.d.ts +3 -1
- package/dist/lib/commands/stats.d.ts.map +1 -1
- package/dist/lib/compress/rc.d.ts.map +1 -1
- package/dist/lib/compress/summary.d.ts +11 -1
- package/dist/lib/compress/summary.d.ts.map +1 -1
- package/dist/lib/compress/types.d.ts +10 -1
- package/dist/lib/compress/types.d.ts.map +1 -1
- package/dist/lib/compress/usage.d.ts +45 -0
- package/dist/lib/compress/usage.d.ts.map +1 -0
- package/dist/lib/config.d.ts +2 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/prompts/context-limit-nudge.d.ts +1 -1
- package/dist/lib/prompts/context-limit-nudge.d.ts.map +1 -1
- package/dist/lib/prompts/extensions/system.d.ts +2 -2
- package/dist/lib/prompts/extensions/system.d.ts.map +1 -1
- package/dist/lib/prompts/iteration-nudge.d.ts +1 -1
- package/dist/lib/prompts/iteration-nudge.d.ts.map +1 -1
- package/dist/lib/prompts/turn-nudge.d.ts +1 -1
- package/dist/lib/prompts/turn-nudge.d.ts.map +1 -1
- package/dist/lib/state/persistence.d.ts +2 -0
- package/dist/lib/state/persistence.d.ts.map +1 -1
- package/dist/lib/state/state.d.ts.map +1 -1
- package/dist/lib/state/types.d.ts +3 -0
- package/dist/lib/state/types.d.ts.map +1 -1
- package/dist/lib/tui/dialogs.d.ts.map +1 -1
- package/dist/lib/v2/index.d.ts.map +1 -1
- package/dist/lib/v2/rpc.d.ts +20 -0
- package/dist/lib/v2/rpc.d.ts.map +1 -1
- package/dist/lib/v2/scrub.d.ts +94 -0
- package/dist/lib/v2/scrub.d.ts.map +1 -0
- package/dist/lib/v2/usage.d.ts +45 -0
- package/dist/lib/v2/usage.d.ts.map +1 -0
- package/lib/commands/stats.ts +21 -0
- package/lib/compress/rc.ts +43 -7
- package/lib/compress/summary.ts +28 -1
- package/lib/compress/types.ts +14 -1
- package/lib/compress/usage.ts +168 -0
- package/lib/config.ts +30 -0
- package/lib/prompts/context-limit-nudge.ts +2 -0
- package/lib/prompts/extensions/system.ts +4 -0
- package/lib/prompts/iteration-nudge.ts +2 -0
- package/lib/prompts/system.ts +1 -1
- package/lib/prompts/turn-nudge.ts +2 -0
- package/lib/state/persistence.ts +18 -2
- package/lib/state/state.ts +4 -0
- package/lib/state/types.ts +3 -0
- package/lib/tui/dialogs.tsx +24 -0
- package/lib/v2/index.ts +58 -5
- package/lib/v2/rpc.ts +12 -0
- package/lib/v2/scrub.ts +954 -0
- package/lib/v2/usage.ts +450 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -913,6 +913,8 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
913
913
|
"compress.protectedTools",
|
|
914
914
|
"compress.protectTags",
|
|
915
915
|
"compress.protectUserMessages",
|
|
916
|
+
"compress.scrubModelOutput",
|
|
917
|
+
"compress.scrubMessageIds",
|
|
916
918
|
"strategies",
|
|
917
919
|
"strategies.deduplication",
|
|
918
920
|
"strategies.deduplication.enabled",
|
|
@@ -1146,6 +1148,20 @@ function validateConfigTypes(config) {
|
|
|
1146
1148
|
actual: typeof compress.protectUserMessages
|
|
1147
1149
|
});
|
|
1148
1150
|
}
|
|
1151
|
+
if (compress.scrubModelOutput !== void 0 && typeof compress.scrubModelOutput !== "boolean") {
|
|
1152
|
+
errors.push({
|
|
1153
|
+
key: "compress.scrubModelOutput",
|
|
1154
|
+
expected: "boolean",
|
|
1155
|
+
actual: typeof compress.scrubModelOutput
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
if (compress.scrubMessageIds !== void 0 && typeof compress.scrubMessageIds !== "boolean") {
|
|
1159
|
+
errors.push({
|
|
1160
|
+
key: "compress.scrubMessageIds",
|
|
1161
|
+
expected: "boolean",
|
|
1162
|
+
actual: typeof compress.scrubMessageIds
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1149
1165
|
if (typeof compress.iterationNudgeThreshold === "number" && compress.iterationNudgeThreshold < 1) {
|
|
1150
1166
|
errors.push({
|
|
1151
1167
|
key: "compress.iterationNudgeThreshold",
|
|
@@ -1331,7 +1347,9 @@ var defaultConfig = {
|
|
|
1331
1347
|
nudgeForce: "soft",
|
|
1332
1348
|
protectedTools: [...COMPRESS_DEFAULT_PROTECTED_TOOLS],
|
|
1333
1349
|
protectTags: false,
|
|
1334
|
-
protectUserMessages: false
|
|
1350
|
+
protectUserMessages: false,
|
|
1351
|
+
scrubModelOutput: true,
|
|
1352
|
+
scrubMessageIds: true
|
|
1335
1353
|
},
|
|
1336
1354
|
strategies: {
|
|
1337
1355
|
deduplication: {
|
|
@@ -1456,7 +1474,9 @@ function mergeCompress(base, override) {
|
|
|
1456
1474
|
nudgeForce: override.nudgeForce ?? base.nudgeForce,
|
|
1457
1475
|
protectedTools: [.../* @__PURE__ */ new Set([...base.protectedTools, ...override.protectedTools ?? []])],
|
|
1458
1476
|
protectTags: override.protectTags ?? base.protectTags,
|
|
1459
|
-
protectUserMessages: override.protectUserMessages ?? base.protectUserMessages
|
|
1477
|
+
protectUserMessages: override.protectUserMessages ?? base.protectUserMessages,
|
|
1478
|
+
scrubModelOutput: override.scrubModelOutput ?? base.scrubModelOutput,
|
|
1479
|
+
scrubMessageIds: override.scrubMessageIds ?? base.scrubMessageIds
|
|
1460
1480
|
};
|
|
1461
1481
|
}
|
|
1462
1482
|
function mergeCommands(base, override) {
|
|
@@ -1791,7 +1811,7 @@ You operate in a context-constrained environment. Manage context continuously to
|
|
|
1791
1811
|
|
|
1792
1812
|
The ONLY tool you have for context management is \`compress\`. It replaces older conversation content with compact technical summaries.
|
|
1793
1813
|
|
|
1794
|
-
${markers} and \`<dcp-system-reminder>\` tags are environment-injected metadata. Do not output them.
|
|
1814
|
+
${markers} and \`<dcp-system-reminder>\` tags are environment-injected metadata. Do not output, quote, or mention them in your replies or reasoning.
|
|
1795
1815
|
|
|
1796
1816
|
THE PHILOSOPHY OF COMPRESS
|
|
1797
1817
|
\`compress\` transforms conversation content into dense, high-fidelity summaries. This is not cleanup - it is crystallization. Your summary becomes the authoritative record of what transpired.
|
|
@@ -1863,6 +1883,8 @@ Avoid the newest active working messages unless it is clearly closed.
|
|
|
1863
1883
|
SUMMARY REQUIREMENTS
|
|
1864
1884
|
Your summary MUST cover all essential details from the selected messages so work can continue.
|
|
1865
1885
|
If the compressed range includes user messages, preserve user intent exactly. Prefer direct quotes for short user messages to avoid semantic drift.
|
|
1886
|
+
|
|
1887
|
+
Never repeat or quote this reminder in your reply.
|
|
1866
1888
|
</dcp-system-reminder>
|
|
1867
1889
|
`;
|
|
1868
1890
|
|
|
@@ -1875,6 +1897,8 @@ If direction has shifted, compress earlier ranges that are now less relevant.
|
|
|
1875
1897
|
|
|
1876
1898
|
The goal is to filter noise and distill key information so context accumulation stays under control.
|
|
1877
1899
|
Keep active context uncompressed.
|
|
1900
|
+
|
|
1901
|
+
Never repeat or quote this reminder in your reply.
|
|
1878
1902
|
</dcp-system-reminder>
|
|
1879
1903
|
`;
|
|
1880
1904
|
|
|
@@ -1883,6 +1907,8 @@ var ITERATION_NUDGE = `<dcp-system-reminder>
|
|
|
1883
1907
|
You've been iterating for a while after the last user message.
|
|
1884
1908
|
|
|
1885
1909
|
If there is a closed portion that is unlikely to be referenced immediately (for example, finished research before implementation), use the compress tool on it now.
|
|
1910
|
+
|
|
1911
|
+
Never repeat or quote this reminder in your reply.
|
|
1886
1912
|
</dcp-system-reminder>
|
|
1887
1913
|
`;
|
|
1888
1914
|
|
|
@@ -1895,6 +1921,8 @@ Only use the compress tool after seeing \`<compress triggered manually>\` in the
|
|
|
1895
1921
|
Issue exactly ONE compress tool per manual trigger. Do NOT launch multiple compress tools in parallel. Each trigger grants a single compression; after it completes, wait for the next trigger.
|
|
1896
1922
|
|
|
1897
1923
|
After completing a manually triggered context-management action, STOP IMMEDIATELY. Do NOT continue with any task execution. End your response right after the tool use completes and wait for the next user input.
|
|
1924
|
+
|
|
1925
|
+
Never repeat this notice in your reply.
|
|
1898
1926
|
</dcp-system-reminder>
|
|
1899
1927
|
`;
|
|
1900
1928
|
var SUBAGENT_SYSTEM_EXTENSION = `<dcp-system-reminder>
|
|
@@ -1903,6 +1931,8 @@ You are operating in a subagent environment.
|
|
|
1903
1931
|
The initial subagent instruction is imperative and must be followed exactly.
|
|
1904
1932
|
It is the only user message intentionally not assigned a message ID, and therefore is not eligible for compression.
|
|
1905
1933
|
All subsequent messages in the session will have IDs.
|
|
1934
|
+
|
|
1935
|
+
Never repeat this notice in your reply.
|
|
1906
1936
|
</dcp-system-reminder>
|
|
1907
1937
|
`;
|
|
1908
1938
|
function buildProtectedToolsExtension(protectedTools) {
|
|
@@ -2250,6 +2280,102 @@ import { existsSync as existsSync4 } from "fs";
|
|
|
2250
2280
|
import { homedir as homedir4 } from "os";
|
|
2251
2281
|
import { join as join4 } from "path";
|
|
2252
2282
|
|
|
2283
|
+
// lib/compress/usage.ts
|
|
2284
|
+
function emptyCompressionUsage() {
|
|
2285
|
+
return {
|
|
2286
|
+
calls: 0,
|
|
2287
|
+
providerCalls: 0,
|
|
2288
|
+
estimatedCalls: 0,
|
|
2289
|
+
inputTokens: 0,
|
|
2290
|
+
outputTokens: 0,
|
|
2291
|
+
cacheReadTokens: 0,
|
|
2292
|
+
cacheWriteTokens: 0,
|
|
2293
|
+
reasoningTokens: 0
|
|
2294
|
+
};
|
|
2295
|
+
}
|
|
2296
|
+
function toCount(value, fallback = 0) {
|
|
2297
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
2298
|
+
return fallback;
|
|
2299
|
+
}
|
|
2300
|
+
return Math.round(value);
|
|
2301
|
+
}
|
|
2302
|
+
function normalizeProviderUsage(raw) {
|
|
2303
|
+
if (!raw || typeof raw !== "object") {
|
|
2304
|
+
return void 0;
|
|
2305
|
+
}
|
|
2306
|
+
const value = raw;
|
|
2307
|
+
const input = value.inputTokens && typeof value.inputTokens === "object" ? value.inputTokens : {};
|
|
2308
|
+
const output2 = value.outputTokens && typeof value.outputTokens === "object" ? value.outputTokens : {};
|
|
2309
|
+
const cacheRead = toCount(input.cacheRead);
|
|
2310
|
+
const cacheWrite = toCount(input.cacheWrite);
|
|
2311
|
+
const uncached = toCount(input.noCache);
|
|
2312
|
+
const inputFromParts = uncached + cacheRead + cacheWrite;
|
|
2313
|
+
const inputTotal = Math.max(toCount(input.total, inputFromParts), inputFromParts);
|
|
2314
|
+
const reasoning = toCount(output2.reasoning);
|
|
2315
|
+
const outputTotal = Math.max(toCount(output2.total, toCount(output2.text) + reasoning), reasoning);
|
|
2316
|
+
if (inputTotal === 0 && outputTotal === 0 && cacheRead === 0 && cacheWrite === 0 && reasoning === 0) {
|
|
2317
|
+
return void 0;
|
|
2318
|
+
}
|
|
2319
|
+
return {
|
|
2320
|
+
inputTokens: inputTotal,
|
|
2321
|
+
outputTokens: outputTotal,
|
|
2322
|
+
cacheReadTokens: cacheRead,
|
|
2323
|
+
cacheWriteTokens: cacheWrite,
|
|
2324
|
+
reasoningTokens: reasoning,
|
|
2325
|
+
source: "provider"
|
|
2326
|
+
};
|
|
2327
|
+
}
|
|
2328
|
+
function estimatedCompressionUsage(inputTokens, outputTokens) {
|
|
2329
|
+
return {
|
|
2330
|
+
inputTokens: toCount(inputTokens),
|
|
2331
|
+
outputTokens: toCount(outputTokens),
|
|
2332
|
+
cacheReadTokens: 0,
|
|
2333
|
+
cacheWriteTokens: 0,
|
|
2334
|
+
reasoningTokens: 0,
|
|
2335
|
+
source: "estimated"
|
|
2336
|
+
};
|
|
2337
|
+
}
|
|
2338
|
+
function addCompressionUsage(totals, usage) {
|
|
2339
|
+
return {
|
|
2340
|
+
calls: totals.calls + 1,
|
|
2341
|
+
providerCalls: totals.providerCalls + (usage.source === "provider" ? 1 : 0),
|
|
2342
|
+
estimatedCalls: totals.estimatedCalls + (usage.source === "estimated" ? 1 : 0),
|
|
2343
|
+
inputTokens: totals.inputTokens + usage.inputTokens,
|
|
2344
|
+
outputTokens: totals.outputTokens + usage.outputTokens,
|
|
2345
|
+
cacheReadTokens: totals.cacheReadTokens + usage.cacheReadTokens,
|
|
2346
|
+
cacheWriteTokens: totals.cacheWriteTokens + usage.cacheWriteTokens,
|
|
2347
|
+
reasoningTokens: totals.reasoningTokens + usage.reasoningTokens
|
|
2348
|
+
};
|
|
2349
|
+
}
|
|
2350
|
+
function addCompressionUsageTotals(totals, other) {
|
|
2351
|
+
return {
|
|
2352
|
+
calls: totals.calls + other.calls,
|
|
2353
|
+
providerCalls: totals.providerCalls + other.providerCalls,
|
|
2354
|
+
estimatedCalls: totals.estimatedCalls + other.estimatedCalls,
|
|
2355
|
+
inputTokens: totals.inputTokens + other.inputTokens,
|
|
2356
|
+
outputTokens: totals.outputTokens + other.outputTokens,
|
|
2357
|
+
cacheReadTokens: totals.cacheReadTokens + other.cacheReadTokens,
|
|
2358
|
+
cacheWriteTokens: totals.cacheWriteTokens + other.cacheWriteTokens,
|
|
2359
|
+
reasoningTokens: totals.reasoningTokens + other.reasoningTokens
|
|
2360
|
+
};
|
|
2361
|
+
}
|
|
2362
|
+
function normalizeCompressionUsageTotals(value) {
|
|
2363
|
+
if (!value || typeof value !== "object") {
|
|
2364
|
+
return emptyCompressionUsage();
|
|
2365
|
+
}
|
|
2366
|
+
const raw = value;
|
|
2367
|
+
return {
|
|
2368
|
+
calls: toCount(raw.calls),
|
|
2369
|
+
providerCalls: toCount(raw.providerCalls),
|
|
2370
|
+
estimatedCalls: toCount(raw.estimatedCalls),
|
|
2371
|
+
inputTokens: toCount(raw.inputTokens),
|
|
2372
|
+
outputTokens: toCount(raw.outputTokens),
|
|
2373
|
+
cacheReadTokens: toCount(raw.cacheReadTokens),
|
|
2374
|
+
cacheWriteTokens: toCount(raw.cacheWriteTokens),
|
|
2375
|
+
reasoningTokens: toCount(raw.reasoningTokens)
|
|
2376
|
+
};
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2253
2379
|
// lib/messages/shape.ts
|
|
2254
2380
|
function isMessageWithInfo(message) {
|
|
2255
2381
|
if (!message || typeof message !== "object") {
|
|
@@ -2824,7 +2950,8 @@ function emptyPersistedState(manualMode) {
|
|
|
2824
2950
|
},
|
|
2825
2951
|
stats: {
|
|
2826
2952
|
pruneTokenCounter: 0,
|
|
2827
|
-
totalPruneTokens: 0
|
|
2953
|
+
totalPruneTokens: 0,
|
|
2954
|
+
compressionUsage: emptyCompressionUsage()
|
|
2828
2955
|
},
|
|
2829
2956
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
2830
2957
|
};
|
|
@@ -2845,7 +2972,8 @@ async function loadAllSessionStats(logger) {
|
|
|
2845
2972
|
totalTokens: 0,
|
|
2846
2973
|
totalTools: 0,
|
|
2847
2974
|
totalMessages: 0,
|
|
2848
|
-
sessionCount: 0
|
|
2975
|
+
sessionCount: 0,
|
|
2976
|
+
compressionUsage: emptyCompressionUsage()
|
|
2849
2977
|
};
|
|
2850
2978
|
try {
|
|
2851
2979
|
if (!existsSync4(STORAGE_DIR)) {
|
|
@@ -2858,10 +2986,16 @@ async function loadAllSessionStats(logger) {
|
|
|
2858
2986
|
const filePath = join4(STORAGE_DIR, file);
|
|
2859
2987
|
const content = await fs.readFile(filePath, "utf-8");
|
|
2860
2988
|
const state = JSON.parse(content);
|
|
2861
|
-
|
|
2862
|
-
|
|
2989
|
+
const usage = normalizeCompressionUsageTotals(state?.stats?.compressionUsage);
|
|
2990
|
+
const hasUsage = usage.calls > 0;
|
|
2991
|
+
if (state?.prune && (state?.stats?.totalPruneTokens || hasUsage)) {
|
|
2992
|
+
result.totalTokens += state.stats?.totalPruneTokens || 0;
|
|
2863
2993
|
result.totalTools += state.prune.tools ? Object.keys(state.prune.tools).length : 0;
|
|
2864
2994
|
result.totalMessages += state.prune.messages?.byMessageId ? Object.keys(state.prune.messages.byMessageId).length : 0;
|
|
2995
|
+
result.compressionUsage = addCompressionUsageTotals(
|
|
2996
|
+
result.compressionUsage,
|
|
2997
|
+
usage
|
|
2998
|
+
);
|
|
2865
2999
|
result.sessionCount++;
|
|
2866
3000
|
}
|
|
2867
3001
|
} catch {
|
|
@@ -3302,7 +3436,8 @@ function createSessionState(idFormat = "xml") {
|
|
|
3302
3436
|
},
|
|
3303
3437
|
stats: {
|
|
3304
3438
|
pruneTokenCounter: 0,
|
|
3305
|
-
totalPruneTokens: 0
|
|
3439
|
+
totalPruneTokens: 0,
|
|
3440
|
+
compressionUsage: emptyCompressionUsage()
|
|
3306
3441
|
},
|
|
3307
3442
|
compressionTiming: {
|
|
3308
3443
|
startsByCallId: /* @__PURE__ */ new Map(),
|
|
@@ -3339,7 +3474,8 @@ function resetSessionState(state) {
|
|
|
3339
3474
|
};
|
|
3340
3475
|
state.stats = {
|
|
3341
3476
|
pruneTokenCounter: 0,
|
|
3342
|
-
totalPruneTokens: 0
|
|
3477
|
+
totalPruneTokens: 0,
|
|
3478
|
+
compressionUsage: emptyCompressionUsage()
|
|
3343
3479
|
};
|
|
3344
3480
|
state.toolParameters.clear();
|
|
3345
3481
|
state.subAgentResultCache.clear();
|
|
@@ -3385,7 +3521,8 @@ async function ensureSessionInitialized(client, state, sessionId, logger, messag
|
|
|
3385
3521
|
);
|
|
3386
3522
|
state.stats = {
|
|
3387
3523
|
pruneTokenCounter: persisted.stats?.pruneTokenCounter || 0,
|
|
3388
|
-
totalPruneTokens: persisted.stats?.totalPruneTokens || 0
|
|
3524
|
+
totalPruneTokens: persisted.stats?.totalPruneTokens || 0,
|
|
3525
|
+
compressionUsage: normalizeCompressionUsageTotals(persisted.stats?.compressionUsage)
|
|
3389
3526
|
};
|
|
3390
3527
|
const applied = applyPendingCompressionDurations(state);
|
|
3391
3528
|
if (applied > 0) {
|
|
@@ -4562,6 +4699,22 @@ ${output2}`);
|
|
|
4562
4699
|
|
|
4563
4700
|
// lib/compress/summary.ts
|
|
4564
4701
|
var RC_SUMMARY_MARKER = "[[DCP-RC-SUMMARY]]";
|
|
4702
|
+
var RC_SUMMARY_MARKER_PREFIX = "[[DCP-RC-SUMMARY";
|
|
4703
|
+
function sanitizeCallId(callId) {
|
|
4704
|
+
return callId.replace(/[^A-Za-z0-9_-]/g, "-");
|
|
4705
|
+
}
|
|
4706
|
+
function buildSummaryMarker(callId) {
|
|
4707
|
+
if (!callId) {
|
|
4708
|
+
return RC_SUMMARY_MARKER;
|
|
4709
|
+
}
|
|
4710
|
+
return `${RC_SUMMARY_MARKER_PREFIX}:${sanitizeCallId(callId)}]]`;
|
|
4711
|
+
}
|
|
4712
|
+
function hasSummaryMarker(text) {
|
|
4713
|
+
return text.includes(RC_SUMMARY_MARKER_PREFIX);
|
|
4714
|
+
}
|
|
4715
|
+
function extractSummaryCallId(text) {
|
|
4716
|
+
return text.match(/\[\[DCP-RC-SUMMARY:([A-Za-z0-9_-]+)\]\]/)?.[1];
|
|
4717
|
+
}
|
|
4565
4718
|
function stringify(value) {
|
|
4566
4719
|
if (typeof value === "string") {
|
|
4567
4720
|
return value;
|
|
@@ -4641,7 +4794,7 @@ var SUMMARY_RULES = `RULES
|
|
|
4641
4794
|
- Preserve every high-signal detail: user intent (quote short user messages exactly), decisions, constraints, file paths, function and symbol names, commands run, key findings, errors, verification results, and any subagent task IDs with their scope and completion state.
|
|
4642
4795
|
- Drop noise: failed attempts that led nowhere, repeated tool output, exploration that produced no durable result.
|
|
4643
4796
|
- Be dense and precise. Prefer terse technical wording over narrative.`;
|
|
4644
|
-
function buildSummaryPrompt(selectors, idFormat = "xml") {
|
|
4797
|
+
function buildSummaryPrompt(selectors, idFormat = "xml", callId) {
|
|
4645
4798
|
const selectorList = selectors.map((segment) => segment.selector);
|
|
4646
4799
|
const example = selectorList[0] ?? (idFormat === "compact" ? "" : "m0004");
|
|
4647
4800
|
const body = selectors.map((segment) => {
|
|
@@ -4652,7 +4805,7 @@ ${prior.body.trim()}`
|
|
|
4652
4805
|
).join("\n\n");
|
|
4653
4806
|
return [messageBody, priorBody].filter((part) => part.length > 0).join("\n\n");
|
|
4654
4807
|
}).join("\n\n");
|
|
4655
|
-
return `${
|
|
4808
|
+
return `${buildSummaryMarker(callId)}
|
|
4656
4809
|
|
|
4657
4810
|
You generate compressed-context entries for a coding session. Summarize ONLY the conversation segments listed below. Do not summarize anything else and do not ask questions.
|
|
4658
4811
|
|
|
@@ -4759,6 +4912,11 @@ function parseSummarySections(text, expected, idFormat = "xml") {
|
|
|
4759
4912
|
}
|
|
4760
4913
|
|
|
4761
4914
|
// lib/compress/rc.ts
|
|
4915
|
+
var summaryCallCounter = 0;
|
|
4916
|
+
function nextSummaryCallId() {
|
|
4917
|
+
summaryCallCounter += 1;
|
|
4918
|
+
return `c${summaryCallCounter.toString(36)}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
4919
|
+
}
|
|
4762
4920
|
function validateArgs(args) {
|
|
4763
4921
|
if (!args || typeof args !== "object") {
|
|
4764
4922
|
throw new Error("compress requires an object argument with an `ids` array");
|
|
@@ -4905,7 +5063,12 @@ function createRcCompressTool(ctx) {
|
|
|
4905
5063
|
messages: plan.messages,
|
|
4906
5064
|
priorSummaries: plan.priorSummaries
|
|
4907
5065
|
}));
|
|
4908
|
-
const text = await generateSummaries(ctx, sessionID, segments);
|
|
5066
|
+
const { text, usage } = await generateSummaries(ctx, sessionID, segments);
|
|
5067
|
+
ctx.state.stats.compressionUsage = addCompressionUsageTotals(
|
|
5068
|
+
ctx.state.stats.compressionUsage,
|
|
5069
|
+
usage
|
|
5070
|
+
);
|
|
5071
|
+
await saveSessionState(ctx.state, ctx.logger);
|
|
4909
5072
|
const accepted = await resolveAcceptedSections(
|
|
4910
5073
|
ctx,
|
|
4911
5074
|
plans.map((plan) => plan.selector.selector),
|
|
@@ -4944,8 +5107,19 @@ function createRcCompressTool(ctx) {
|
|
|
4944
5107
|
});
|
|
4945
5108
|
}
|
|
4946
5109
|
async function generateSummaries(ctx, sessionID, segments) {
|
|
4947
|
-
|
|
4948
|
-
|
|
5110
|
+
let usage = emptyCompressionUsage();
|
|
5111
|
+
const summarize = async (promptSegments) => {
|
|
5112
|
+
const callId = nextSummaryCallId();
|
|
5113
|
+
const prompt = buildSummaryPrompt(promptSegments, ctx.state.idFormat, callId);
|
|
5114
|
+
const result = await ctx.generate({ sessionID, prompt, callId });
|
|
5115
|
+
const text2 = result.text ?? "";
|
|
5116
|
+
usage = addCompressionUsage(
|
|
5117
|
+
usage,
|
|
5118
|
+
result.usage ?? estimatedCompressionUsage(countTokens2(prompt), countTokens2(text2))
|
|
5119
|
+
);
|
|
5120
|
+
return text2;
|
|
5121
|
+
};
|
|
5122
|
+
let text = await summarize(segments);
|
|
4949
5123
|
for (let retry = 0; retry < 2; retry++) {
|
|
4950
5124
|
const parsed = parseSummarySections(
|
|
4951
5125
|
text,
|
|
@@ -4962,14 +5136,13 @@ async function generateSummaries(ctx, sessionID, segments) {
|
|
|
4962
5136
|
if (retrySegments.length === 0) {
|
|
4963
5137
|
break;
|
|
4964
5138
|
}
|
|
4965
|
-
const retryPrompt = buildSummaryPrompt(retrySegments, ctx.state.idFormat);
|
|
4966
5139
|
try {
|
|
4967
|
-
text += "\n\n" + await
|
|
5140
|
+
text += "\n\n" + await summarize(retrySegments);
|
|
4968
5141
|
} catch {
|
|
4969
5142
|
break;
|
|
4970
5143
|
}
|
|
4971
5144
|
}
|
|
4972
|
-
return text;
|
|
5145
|
+
return { text, usage };
|
|
4973
5146
|
}
|
|
4974
5147
|
async function resolveAcceptedSections(ctx, selectors, text) {
|
|
4975
5148
|
const parsed = parseSummarySections(text, selectors, ctx.state.idFormat);
|
|
@@ -5081,6 +5254,1140 @@ async function resetManualMode(ctx, sessionID) {
|
|
|
5081
5254
|
await saveSessionState(ctx.state, ctx.logger);
|
|
5082
5255
|
}
|
|
5083
5256
|
|
|
5257
|
+
// lib/v2/scrub.ts
|
|
5258
|
+
function outputScrubEnabled(config) {
|
|
5259
|
+
return config.modelOutput || config.messageIds;
|
|
5260
|
+
}
|
|
5261
|
+
var DCP_PAIRED_TAG_REGEX = /<dcp[^>]*>[\s\S]*?<\/dcp[^>]*>/gi;
|
|
5262
|
+
var DCP_UNPAIRED_TAG_REGEX = /<\/?dcp[^>]*>/gi;
|
|
5263
|
+
var ID_LINE_REGEX = /^[ \t]*@(?:[1-9]\d*|b[1-9]\d*|blocked)@(?:[ \t]+\[(?:low|medium|high)\])?[ \t]*$/;
|
|
5264
|
+
function isIdLine(line) {
|
|
5265
|
+
const value = line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
5266
|
+
return ID_LINE_REGEX.test(value);
|
|
5267
|
+
}
|
|
5268
|
+
function isIdLinePrefix(text) {
|
|
5269
|
+
let index = 0;
|
|
5270
|
+
while (index < text.length && (text[index] === " " || text[index] === " ")) {
|
|
5271
|
+
index += 1;
|
|
5272
|
+
}
|
|
5273
|
+
if (index === text.length) {
|
|
5274
|
+
return true;
|
|
5275
|
+
}
|
|
5276
|
+
if (text[index] !== "@") {
|
|
5277
|
+
return false;
|
|
5278
|
+
}
|
|
5279
|
+
index += 1;
|
|
5280
|
+
if (index === text.length) {
|
|
5281
|
+
return true;
|
|
5282
|
+
}
|
|
5283
|
+
if (text.startsWith("blocked", index)) {
|
|
5284
|
+
index += 7;
|
|
5285
|
+
} else if (text[index] === "b") {
|
|
5286
|
+
if (index + 1 === text.length) {
|
|
5287
|
+
return true;
|
|
5288
|
+
}
|
|
5289
|
+
if (!/\d/.test(text[index + 1])) {
|
|
5290
|
+
return false;
|
|
5291
|
+
}
|
|
5292
|
+
index += 2;
|
|
5293
|
+
while (index < text.length && /\d/.test(text[index])) {
|
|
5294
|
+
index += 1;
|
|
5295
|
+
}
|
|
5296
|
+
} else if (/[1-9]/.test(text[index])) {
|
|
5297
|
+
index += 1;
|
|
5298
|
+
while (index < text.length && /\d/.test(text[index])) {
|
|
5299
|
+
index += 1;
|
|
5300
|
+
}
|
|
5301
|
+
} else {
|
|
5302
|
+
return false;
|
|
5303
|
+
}
|
|
5304
|
+
if (index === text.length) {
|
|
5305
|
+
return true;
|
|
5306
|
+
}
|
|
5307
|
+
if (text[index] !== "@") {
|
|
5308
|
+
return false;
|
|
5309
|
+
}
|
|
5310
|
+
return isIdPriorityPrefix(text.slice(index + 1));
|
|
5311
|
+
}
|
|
5312
|
+
function isIdPriorityPrefix(text) {
|
|
5313
|
+
if (text.length === 0 || /^[ \t]+$/.test(text)) {
|
|
5314
|
+
return true;
|
|
5315
|
+
}
|
|
5316
|
+
return /^[ \t]*\[(?:l(?:o(?:w)?)?|m(?:e(?:d(?:i(?:u(?:m)?)?)?)?)?|h(?:i(?:g(?:h)?)?)?)?\]?[ \t]*$/.test(
|
|
5317
|
+
text
|
|
5318
|
+
);
|
|
5319
|
+
}
|
|
5320
|
+
function stripDcpArtifacts(text, config) {
|
|
5321
|
+
let out = text;
|
|
5322
|
+
if (config.modelOutput) {
|
|
5323
|
+
out = out.replace(DCP_PAIRED_TAG_REGEX, "").replace(DCP_UNPAIRED_TAG_REGEX, "");
|
|
5324
|
+
}
|
|
5325
|
+
if (config.messageIds && out.includes("\n")) {
|
|
5326
|
+
out = out.split("\n").filter((line) => !isIdLine(line)).join("\n");
|
|
5327
|
+
} else if (config.messageIds && isIdLine(out)) {
|
|
5328
|
+
out = "";
|
|
5329
|
+
}
|
|
5330
|
+
return out;
|
|
5331
|
+
}
|
|
5332
|
+
var TAG_OPEN = "<dcp";
|
|
5333
|
+
var TAG_CLOSE = "</dcp";
|
|
5334
|
+
var MAX_TAG_LENGTH = 512;
|
|
5335
|
+
var MAX_BLOCK_LENGTH = 8192;
|
|
5336
|
+
function heldTagSuffix(text) {
|
|
5337
|
+
const max = Math.min(4, text.length);
|
|
5338
|
+
for (let length = max; length >= 1; length -= 1) {
|
|
5339
|
+
const suffix = text.slice(text.length - length).toLowerCase();
|
|
5340
|
+
if (TAG_OPEN.startsWith(suffix) || TAG_CLOSE.startsWith(suffix)) {
|
|
5341
|
+
return text.slice(text.length - length);
|
|
5342
|
+
}
|
|
5343
|
+
}
|
|
5344
|
+
return "";
|
|
5345
|
+
}
|
|
5346
|
+
var DcpTagStripper = class {
|
|
5347
|
+
mode = "text";
|
|
5348
|
+
carry = "";
|
|
5349
|
+
push(chunk) {
|
|
5350
|
+
this.carry += chunk;
|
|
5351
|
+
let out = "";
|
|
5352
|
+
for (; ; ) {
|
|
5353
|
+
if (this.mode === "text") {
|
|
5354
|
+
const lower = this.carry.toLowerCase();
|
|
5355
|
+
const open = lower.indexOf(TAG_OPEN);
|
|
5356
|
+
const close2 = lower.indexOf(TAG_CLOSE);
|
|
5357
|
+
let index = -1;
|
|
5358
|
+
let next = "open";
|
|
5359
|
+
if (open === -1 && close2 === -1) {
|
|
5360
|
+
index = -1;
|
|
5361
|
+
} else if (open === -1) {
|
|
5362
|
+
index = close2;
|
|
5363
|
+
next = "close";
|
|
5364
|
+
} else if (close2 === -1) {
|
|
5365
|
+
index = open;
|
|
5366
|
+
} else if (open <= close2) {
|
|
5367
|
+
index = open;
|
|
5368
|
+
} else {
|
|
5369
|
+
index = close2;
|
|
5370
|
+
next = "close";
|
|
5371
|
+
}
|
|
5372
|
+
if (index === -1) {
|
|
5373
|
+
const held = heldTagSuffix(this.carry);
|
|
5374
|
+
out += held ? this.carry.slice(0, -held.length) : this.carry;
|
|
5375
|
+
this.carry = held;
|
|
5376
|
+
break;
|
|
5377
|
+
}
|
|
5378
|
+
out += this.carry.slice(0, index);
|
|
5379
|
+
this.carry = this.carry.slice(index);
|
|
5380
|
+
this.mode = next;
|
|
5381
|
+
continue;
|
|
5382
|
+
}
|
|
5383
|
+
if (this.mode === "open" || this.mode === "close") {
|
|
5384
|
+
const end = this.carry.indexOf(">");
|
|
5385
|
+
if (end === -1) {
|
|
5386
|
+
if (this.carry.length > MAX_TAG_LENGTH) {
|
|
5387
|
+
out += this.carry;
|
|
5388
|
+
this.carry = "";
|
|
5389
|
+
this.mode = "text";
|
|
5390
|
+
}
|
|
5391
|
+
break;
|
|
5392
|
+
}
|
|
5393
|
+
const wasOpen = this.mode === "open";
|
|
5394
|
+
this.carry = this.carry.slice(end + 1);
|
|
5395
|
+
this.mode = wasOpen ? "block" : "text";
|
|
5396
|
+
continue;
|
|
5397
|
+
}
|
|
5398
|
+
const close = this.carry.toLowerCase().indexOf(TAG_CLOSE);
|
|
5399
|
+
if (close !== -1) {
|
|
5400
|
+
const end = this.carry.indexOf(">", close);
|
|
5401
|
+
if (end !== -1) {
|
|
5402
|
+
this.carry = this.carry.slice(end + 1);
|
|
5403
|
+
this.mode = "text";
|
|
5404
|
+
continue;
|
|
5405
|
+
}
|
|
5406
|
+
}
|
|
5407
|
+
if (this.carry.length > MAX_BLOCK_LENGTH) {
|
|
5408
|
+
out += this.carry;
|
|
5409
|
+
this.carry = "";
|
|
5410
|
+
this.mode = "text";
|
|
5411
|
+
}
|
|
5412
|
+
break;
|
|
5413
|
+
}
|
|
5414
|
+
return out;
|
|
5415
|
+
}
|
|
5416
|
+
flush() {
|
|
5417
|
+
const out = this.mode === "block" ? this.carry : "";
|
|
5418
|
+
this.carry = "";
|
|
5419
|
+
this.mode = "text";
|
|
5420
|
+
return out;
|
|
5421
|
+
}
|
|
5422
|
+
reset() {
|
|
5423
|
+
this.carry = "";
|
|
5424
|
+
this.mode = "text";
|
|
5425
|
+
}
|
|
5426
|
+
};
|
|
5427
|
+
var IdLineStripper = class {
|
|
5428
|
+
carry = "";
|
|
5429
|
+
push(chunk) {
|
|
5430
|
+
this.carry += chunk;
|
|
5431
|
+
let out = "";
|
|
5432
|
+
for (; ; ) {
|
|
5433
|
+
const newline = this.carry.indexOf("\n");
|
|
5434
|
+
if (newline === -1) {
|
|
5435
|
+
if (isIdLinePrefix(this.carry)) {
|
|
5436
|
+
break;
|
|
5437
|
+
}
|
|
5438
|
+
out += this.carry;
|
|
5439
|
+
this.carry = "";
|
|
5440
|
+
break;
|
|
5441
|
+
}
|
|
5442
|
+
const line = this.carry.slice(0, newline);
|
|
5443
|
+
this.carry = this.carry.slice(newline + 1);
|
|
5444
|
+
if (!isIdLine(line)) {
|
|
5445
|
+
out += line + "\n";
|
|
5446
|
+
}
|
|
5447
|
+
}
|
|
5448
|
+
return out;
|
|
5449
|
+
}
|
|
5450
|
+
flush() {
|
|
5451
|
+
const held = this.carry;
|
|
5452
|
+
this.carry = "";
|
|
5453
|
+
return isIdLine(held) ? "" : held;
|
|
5454
|
+
}
|
|
5455
|
+
reset() {
|
|
5456
|
+
this.carry = "";
|
|
5457
|
+
}
|
|
5458
|
+
};
|
|
5459
|
+
var DcpStreamScrubber = class {
|
|
5460
|
+
constructor(config) {
|
|
5461
|
+
this.config = config;
|
|
5462
|
+
}
|
|
5463
|
+
config;
|
|
5464
|
+
tags = new DcpTagStripper();
|
|
5465
|
+
ids = new IdLineStripper();
|
|
5466
|
+
push(chunk) {
|
|
5467
|
+
let out = chunk;
|
|
5468
|
+
if (this.config.modelOutput) {
|
|
5469
|
+
out = this.tags.push(out);
|
|
5470
|
+
}
|
|
5471
|
+
if (this.config.messageIds) {
|
|
5472
|
+
out = this.ids.push(out);
|
|
5473
|
+
}
|
|
5474
|
+
return out;
|
|
5475
|
+
}
|
|
5476
|
+
flush() {
|
|
5477
|
+
let out = this.config.modelOutput ? this.tags.flush() : "";
|
|
5478
|
+
if (out && this.config.messageIds) {
|
|
5479
|
+
out = this.ids.push(out);
|
|
5480
|
+
}
|
|
5481
|
+
if (this.config.messageIds) {
|
|
5482
|
+
out += this.ids.flush();
|
|
5483
|
+
}
|
|
5484
|
+
return out;
|
|
5485
|
+
}
|
|
5486
|
+
reset() {
|
|
5487
|
+
this.tags.reset();
|
|
5488
|
+
this.ids.reset();
|
|
5489
|
+
}
|
|
5490
|
+
};
|
|
5491
|
+
function assignText(target, key, value) {
|
|
5492
|
+
if (target[key] === value) {
|
|
5493
|
+
return false;
|
|
5494
|
+
}
|
|
5495
|
+
target[key] = value;
|
|
5496
|
+
return true;
|
|
5497
|
+
}
|
|
5498
|
+
var TEXT_PART_TYPES = /* @__PURE__ */ new Set(["text", "output_text", "reasoning_text", "summary_text"]);
|
|
5499
|
+
var ProviderPayloadScrubber = class {
|
|
5500
|
+
constructor(config) {
|
|
5501
|
+
this.config = config;
|
|
5502
|
+
}
|
|
5503
|
+
config;
|
|
5504
|
+
streams = /* @__PURE__ */ new Map();
|
|
5505
|
+
/** Scrub one streamed provider event. Returns true when it was modified. */
|
|
5506
|
+
scrub(payload) {
|
|
5507
|
+
try {
|
|
5508
|
+
if (!payload || typeof payload !== "object") {
|
|
5509
|
+
return false;
|
|
5510
|
+
}
|
|
5511
|
+
const record = payload;
|
|
5512
|
+
const type = typeof record.type === "string" ? record.type : "";
|
|
5513
|
+
if (Array.isArray(record.choices)) {
|
|
5514
|
+
return this.scrubChat(record, false);
|
|
5515
|
+
}
|
|
5516
|
+
if (Array.isArray(record.candidates)) {
|
|
5517
|
+
return this.scrubGoogle(record, false);
|
|
5518
|
+
}
|
|
5519
|
+
if (type.startsWith("response.")) {
|
|
5520
|
+
return this.scrubResponses(record);
|
|
5521
|
+
}
|
|
5522
|
+
if (type.startsWith("content_block") || type === "message" || type === "message_delta") {
|
|
5523
|
+
return this.scrubAnthropic(record);
|
|
5524
|
+
}
|
|
5525
|
+
return false;
|
|
5526
|
+
} catch {
|
|
5527
|
+
return false;
|
|
5528
|
+
}
|
|
5529
|
+
}
|
|
5530
|
+
/** Scrub a complete (non-streamed) JSON payload. */
|
|
5531
|
+
scrubComplete(payload) {
|
|
5532
|
+
try {
|
|
5533
|
+
if (Array.isArray(payload)) {
|
|
5534
|
+
let changed = false;
|
|
5535
|
+
for (const item of payload) {
|
|
5536
|
+
changed = this.scrubComplete(item) || changed;
|
|
5537
|
+
}
|
|
5538
|
+
return changed;
|
|
5539
|
+
}
|
|
5540
|
+
if (!payload || typeof payload !== "object") {
|
|
5541
|
+
return false;
|
|
5542
|
+
}
|
|
5543
|
+
const record = payload;
|
|
5544
|
+
if (Array.isArray(record.choices)) {
|
|
5545
|
+
return this.scrubChat(record, true);
|
|
5546
|
+
}
|
|
5547
|
+
if (Array.isArray(record.candidates)) {
|
|
5548
|
+
return this.scrubGoogle(record, true);
|
|
5549
|
+
}
|
|
5550
|
+
if (record.type === "message" && Array.isArray(record.content)) {
|
|
5551
|
+
return this.scrubAnthropic(record);
|
|
5552
|
+
}
|
|
5553
|
+
if (Array.isArray(record.output)) {
|
|
5554
|
+
return this.scrubItems(record.output);
|
|
5555
|
+
}
|
|
5556
|
+
if (typeof record.output_text === "string") {
|
|
5557
|
+
return assignText(record, "output_text", this.complete(record.output_text));
|
|
5558
|
+
}
|
|
5559
|
+
return false;
|
|
5560
|
+
} catch {
|
|
5561
|
+
return false;
|
|
5562
|
+
}
|
|
5563
|
+
}
|
|
5564
|
+
complete(text) {
|
|
5565
|
+
return stripDcpArtifacts(text, this.config);
|
|
5566
|
+
}
|
|
5567
|
+
stream(key) {
|
|
5568
|
+
let scrubber = this.streams.get(key);
|
|
5569
|
+
if (!scrubber) {
|
|
5570
|
+
scrubber = new DcpStreamScrubber(this.config);
|
|
5571
|
+
this.streams.set(key, scrubber);
|
|
5572
|
+
}
|
|
5573
|
+
return scrubber;
|
|
5574
|
+
}
|
|
5575
|
+
textFor(value, key, complete) {
|
|
5576
|
+
if (complete || !key) {
|
|
5577
|
+
return this.complete(value);
|
|
5578
|
+
}
|
|
5579
|
+
return this.stream(key).push(value);
|
|
5580
|
+
}
|
|
5581
|
+
scrubField(target, key, streamKey, complete = false) {
|
|
5582
|
+
if (!target || typeof target !== "object" || typeof target[key] !== "string") {
|
|
5583
|
+
return false;
|
|
5584
|
+
}
|
|
5585
|
+
return assignText(target, key, this.textFor(target[key], streamKey, complete));
|
|
5586
|
+
}
|
|
5587
|
+
scrubParts(parts, key, complete = false) {
|
|
5588
|
+
if (!Array.isArray(parts)) {
|
|
5589
|
+
return false;
|
|
5590
|
+
}
|
|
5591
|
+
let changed = false;
|
|
5592
|
+
parts.forEach((part, index) => {
|
|
5593
|
+
if (!part || typeof part !== "object" || typeof part.text !== "string") {
|
|
5594
|
+
return;
|
|
5595
|
+
}
|
|
5596
|
+
if (part.type && !TEXT_PART_TYPES.has(part.type)) {
|
|
5597
|
+
return;
|
|
5598
|
+
}
|
|
5599
|
+
changed = assignText(
|
|
5600
|
+
part,
|
|
5601
|
+
"text",
|
|
5602
|
+
this.textFor(part.text, key ? `${key}:${index}` : void 0, complete)
|
|
5603
|
+
) || changed;
|
|
5604
|
+
});
|
|
5605
|
+
return changed;
|
|
5606
|
+
}
|
|
5607
|
+
scrubChat(record, complete) {
|
|
5608
|
+
let changed = false;
|
|
5609
|
+
const choices = Array.isArray(record.choices) ? record.choices : [];
|
|
5610
|
+
choices.forEach((choice, index) => {
|
|
5611
|
+
const delta = choice?.delta;
|
|
5612
|
+
if (delta && typeof delta === "object") {
|
|
5613
|
+
changed = this.scrubField(delta, "content", `chat:content:${index}`) || changed;
|
|
5614
|
+
changed = this.scrubField(delta, "reasoning_content", `chat:reasoning:${index}`) || changed;
|
|
5615
|
+
changed = this.scrubField(delta, "reasoning", `chat:reasoning:${index}`) || changed;
|
|
5616
|
+
changed = this.scrubParts(delta.content, `chat:parts:${index}`) || changed;
|
|
5617
|
+
if (choice?.finish_reason) {
|
|
5618
|
+
this.streams.delete(`chat:content:${index}`);
|
|
5619
|
+
this.streams.delete(`chat:reasoning:${index}`);
|
|
5620
|
+
this.streams.delete(`chat:parts:${index}`);
|
|
5621
|
+
}
|
|
5622
|
+
}
|
|
5623
|
+
const message = choice?.message;
|
|
5624
|
+
if (message && typeof message === "object") {
|
|
5625
|
+
changed = this.scrubField(message, "content", void 0, true) || changed;
|
|
5626
|
+
changed = this.scrubField(message, "reasoning_content", void 0, true) || changed;
|
|
5627
|
+
changed = this.scrubField(message, "reasoning", void 0, true) || changed;
|
|
5628
|
+
changed = this.scrubParts(message.content, void 0, true) || changed;
|
|
5629
|
+
}
|
|
5630
|
+
});
|
|
5631
|
+
return changed;
|
|
5632
|
+
}
|
|
5633
|
+
scrubResponses(record) {
|
|
5634
|
+
const type = record.type;
|
|
5635
|
+
const itemId = record.item_id ?? "";
|
|
5636
|
+
const outputIndex = record.output_index ?? "";
|
|
5637
|
+
const contentIndex = record.content_index ?? "";
|
|
5638
|
+
const key = (family) => `resp:${family}:${itemId}:${outputIndex}:${contentIndex}`;
|
|
5639
|
+
switch (type) {
|
|
5640
|
+
case "response.output_text.delta":
|
|
5641
|
+
return this.scrubField(record, "delta", key("text"));
|
|
5642
|
+
case "response.reasoning_text.delta":
|
|
5643
|
+
return this.scrubField(record, "delta", key("reasoning"));
|
|
5644
|
+
case "response.reasoning_summary_text.delta":
|
|
5645
|
+
return this.scrubField(record, "delta", key("summary"));
|
|
5646
|
+
case "response.output_text.done":
|
|
5647
|
+
case "response.reasoning_text.done":
|
|
5648
|
+
case "response.reasoning_summary_text.done":
|
|
5649
|
+
return this.scrubField(record, "text", void 0, true);
|
|
5650
|
+
case "response.content_part.added":
|
|
5651
|
+
case "response.content_part.done":
|
|
5652
|
+
case "response.reasoning_summary_part.added":
|
|
5653
|
+
case "response.reasoning_summary_part.done":
|
|
5654
|
+
return this.scrubParts(record.part ? [record.part] : void 0, void 0, true);
|
|
5655
|
+
case "response.output_item.added":
|
|
5656
|
+
case "response.output_item.done":
|
|
5657
|
+
return this.scrubItems(record.item ? [record.item] : void 0);
|
|
5658
|
+
case "response.completed": {
|
|
5659
|
+
const changed = this.scrubResponseObject(record.response);
|
|
5660
|
+
this.streams.clear();
|
|
5661
|
+
return changed;
|
|
5662
|
+
}
|
|
5663
|
+
default:
|
|
5664
|
+
return false;
|
|
5665
|
+
}
|
|
5666
|
+
}
|
|
5667
|
+
scrubResponseObject(response) {
|
|
5668
|
+
if (!response || typeof response !== "object") {
|
|
5669
|
+
return false;
|
|
5670
|
+
}
|
|
5671
|
+
let changed = this.scrubItems(response.output);
|
|
5672
|
+
if (typeof response.output_text === "string") {
|
|
5673
|
+
changed = assignText(response, "output_text", this.complete(response.output_text)) || changed;
|
|
5674
|
+
}
|
|
5675
|
+
return changed;
|
|
5676
|
+
}
|
|
5677
|
+
scrubItems(items) {
|
|
5678
|
+
if (!Array.isArray(items)) {
|
|
5679
|
+
return false;
|
|
5680
|
+
}
|
|
5681
|
+
let changed = false;
|
|
5682
|
+
items.forEach((entry) => {
|
|
5683
|
+
if (!entry || typeof entry !== "object") {
|
|
5684
|
+
return;
|
|
5685
|
+
}
|
|
5686
|
+
changed = this.scrubParts(entry.content, void 0, true) || changed;
|
|
5687
|
+
changed = this.scrubParts(entry.summary, void 0, true) || changed;
|
|
5688
|
+
});
|
|
5689
|
+
return changed;
|
|
5690
|
+
}
|
|
5691
|
+
scrubAnthropic(record) {
|
|
5692
|
+
const type = record.type;
|
|
5693
|
+
if (type === "content_block_start") {
|
|
5694
|
+
const block = record.content_block;
|
|
5695
|
+
if (!block || typeof block !== "object") {
|
|
5696
|
+
return false;
|
|
5697
|
+
}
|
|
5698
|
+
if (block.type === "text") {
|
|
5699
|
+
return this.scrubField(block, "text", `anthropic:text:${record.index}`);
|
|
5700
|
+
}
|
|
5701
|
+
if (block.type === "thinking") {
|
|
5702
|
+
return this.scrubField(block, "thinking", `anthropic:thinking:${record.index}`);
|
|
5703
|
+
}
|
|
5704
|
+
return false;
|
|
5705
|
+
}
|
|
5706
|
+
if (type === "content_block_delta") {
|
|
5707
|
+
const delta = record.delta;
|
|
5708
|
+
if (!delta || typeof delta !== "object") {
|
|
5709
|
+
return false;
|
|
5710
|
+
}
|
|
5711
|
+
if (delta.type === "text_delta") {
|
|
5712
|
+
return this.scrubField(delta, "text", `anthropic:text:${record.index}`);
|
|
5713
|
+
}
|
|
5714
|
+
if (delta.type === "thinking_delta") {
|
|
5715
|
+
return this.scrubField(delta, "thinking", `anthropic:thinking:${record.index}`);
|
|
5716
|
+
}
|
|
5717
|
+
return false;
|
|
5718
|
+
}
|
|
5719
|
+
if (type === "content_block_stop") {
|
|
5720
|
+
this.streams.delete(`anthropic:text:${record.index}`);
|
|
5721
|
+
this.streams.delete(`anthropic:thinking:${record.index}`);
|
|
5722
|
+
return false;
|
|
5723
|
+
}
|
|
5724
|
+
if (type === "message" && Array.isArray(record.content)) {
|
|
5725
|
+
let changed = false;
|
|
5726
|
+
record.content.forEach((block) => {
|
|
5727
|
+
if (block?.type === "text") {
|
|
5728
|
+
changed = this.scrubField(block, "text", void 0, true) || changed;
|
|
5729
|
+
} else if (block?.type === "thinking") {
|
|
5730
|
+
changed = this.scrubField(block, "thinking", void 0, true) || changed;
|
|
5731
|
+
}
|
|
5732
|
+
});
|
|
5733
|
+
return changed;
|
|
5734
|
+
}
|
|
5735
|
+
return false;
|
|
5736
|
+
}
|
|
5737
|
+
scrubGoogle(record, complete) {
|
|
5738
|
+
const candidates = Array.isArray(record.candidates) ? record.candidates : [];
|
|
5739
|
+
let changed = false;
|
|
5740
|
+
candidates.forEach((candidate, candidateIndex) => {
|
|
5741
|
+
const parts = candidate?.content?.parts;
|
|
5742
|
+
if (Array.isArray(parts)) {
|
|
5743
|
+
parts.forEach((part, partIndex) => {
|
|
5744
|
+
if (!part || typeof part !== "object" || typeof part.text !== "string") {
|
|
5745
|
+
return;
|
|
5746
|
+
}
|
|
5747
|
+
changed = assignText(
|
|
5748
|
+
part,
|
|
5749
|
+
"text",
|
|
5750
|
+
this.textFor(
|
|
5751
|
+
part.text,
|
|
5752
|
+
complete ? void 0 : `google:${candidateIndex}:${partIndex}`,
|
|
5753
|
+
complete
|
|
5754
|
+
)
|
|
5755
|
+
) || changed;
|
|
5756
|
+
});
|
|
5757
|
+
}
|
|
5758
|
+
if (candidate?.finishReason) {
|
|
5759
|
+
this.streams.clear();
|
|
5760
|
+
}
|
|
5761
|
+
});
|
|
5762
|
+
return changed;
|
|
5763
|
+
}
|
|
5764
|
+
};
|
|
5765
|
+
function findBoundary(buffer) {
|
|
5766
|
+
const lf = buffer.indexOf("\n\n");
|
|
5767
|
+
const crlf = buffer.indexOf("\r\n\r\n");
|
|
5768
|
+
if (lf === -1 && crlf === -1) {
|
|
5769
|
+
return null;
|
|
5770
|
+
}
|
|
5771
|
+
if (crlf !== -1 && (lf === -1 || crlf <= lf)) {
|
|
5772
|
+
return { start: crlf, end: crlf + 4, separator: "\r\n\r\n" };
|
|
5773
|
+
}
|
|
5774
|
+
return { start: lf, end: lf + 2, separator: "\n\n" };
|
|
5775
|
+
}
|
|
5776
|
+
function scrubJsonBody(body, config, scrubber) {
|
|
5777
|
+
const trimmed = body.trim();
|
|
5778
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
|
|
5779
|
+
return body;
|
|
5780
|
+
}
|
|
5781
|
+
try {
|
|
5782
|
+
const payload = JSON.parse(trimmed);
|
|
5783
|
+
const worker = scrubber ?? new ProviderPayloadScrubber(config);
|
|
5784
|
+
if (worker.scrubComplete(payload)) {
|
|
5785
|
+
return JSON.stringify(payload);
|
|
5786
|
+
}
|
|
5787
|
+
} catch {
|
|
5788
|
+
}
|
|
5789
|
+
return body;
|
|
5790
|
+
}
|
|
5791
|
+
function createSseScrubTransform(config) {
|
|
5792
|
+
const TransformStreamImpl = globalThis.TransformStream;
|
|
5793
|
+
const decoder = new TextDecoder();
|
|
5794
|
+
const encoder = new TextEncoder();
|
|
5795
|
+
const scrubber = new ProviderPayloadScrubber(config);
|
|
5796
|
+
let buffer = "";
|
|
5797
|
+
const processBlock = (block) => {
|
|
5798
|
+
try {
|
|
5799
|
+
const lines = block.split("\n");
|
|
5800
|
+
const dataLines = [];
|
|
5801
|
+
lines.forEach((raw, index) => {
|
|
5802
|
+
const line = raw.endsWith("\r") ? raw.slice(0, -1) : raw;
|
|
5803
|
+
const match = /^data:( ?)(.*)$/.exec(line);
|
|
5804
|
+
if (match) {
|
|
5805
|
+
dataLines.push({ index, space: match[1] ?? "", value: match[2] ?? "" });
|
|
5806
|
+
}
|
|
5807
|
+
});
|
|
5808
|
+
if (dataLines.length === 0) {
|
|
5809
|
+
return block;
|
|
5810
|
+
}
|
|
5811
|
+
const data = dataLines.map((entry) => entry.value).join("\n");
|
|
5812
|
+
const payloadText = data.trim();
|
|
5813
|
+
if (!payloadText || payloadText === "[DONE]") {
|
|
5814
|
+
return block;
|
|
5815
|
+
}
|
|
5816
|
+
let payload;
|
|
5817
|
+
try {
|
|
5818
|
+
payload = JSON.parse(data);
|
|
5819
|
+
} catch {
|
|
5820
|
+
return block;
|
|
5821
|
+
}
|
|
5822
|
+
if (!scrubber.scrub(payload)) {
|
|
5823
|
+
return block;
|
|
5824
|
+
}
|
|
5825
|
+
const first = dataLines[0];
|
|
5826
|
+
const drop = new Set(dataLines.slice(1).map((entry) => entry.index));
|
|
5827
|
+
const rebuilt = [];
|
|
5828
|
+
lines.forEach((raw, index) => {
|
|
5829
|
+
if (drop.has(index)) {
|
|
5830
|
+
return;
|
|
5831
|
+
}
|
|
5832
|
+
if (index === first.index) {
|
|
5833
|
+
rebuilt.push(`data:${first.space}${JSON.stringify(payload)}`);
|
|
5834
|
+
return;
|
|
5835
|
+
}
|
|
5836
|
+
rebuilt.push(raw);
|
|
5837
|
+
});
|
|
5838
|
+
return rebuilt.join("\n");
|
|
5839
|
+
} catch {
|
|
5840
|
+
return block;
|
|
5841
|
+
}
|
|
5842
|
+
};
|
|
5843
|
+
return new TransformStreamImpl({
|
|
5844
|
+
transform(chunk, controller) {
|
|
5845
|
+
let enqueued = false;
|
|
5846
|
+
try {
|
|
5847
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
5848
|
+
let boundary = findBoundary(buffer);
|
|
5849
|
+
while (boundary) {
|
|
5850
|
+
const block = buffer.slice(0, boundary.start);
|
|
5851
|
+
buffer = buffer.slice(boundary.end);
|
|
5852
|
+
controller.enqueue(encoder.encode(processBlock(block) + boundary.separator));
|
|
5853
|
+
enqueued = true;
|
|
5854
|
+
boundary = findBoundary(buffer);
|
|
5855
|
+
}
|
|
5856
|
+
} catch {
|
|
5857
|
+
if (!enqueued) {
|
|
5858
|
+
try {
|
|
5859
|
+
controller.enqueue(chunk);
|
|
5860
|
+
} catch {
|
|
5861
|
+
}
|
|
5862
|
+
}
|
|
5863
|
+
}
|
|
5864
|
+
},
|
|
5865
|
+
flush(controller) {
|
|
5866
|
+
try {
|
|
5867
|
+
buffer += decoder.decode();
|
|
5868
|
+
if (buffer) {
|
|
5869
|
+
controller.enqueue(encoder.encode(processBlock(buffer)));
|
|
5870
|
+
}
|
|
5871
|
+
buffer = "";
|
|
5872
|
+
} catch {
|
|
5873
|
+
}
|
|
5874
|
+
}
|
|
5875
|
+
});
|
|
5876
|
+
}
|
|
5877
|
+
function createJsonScrubTransform(config) {
|
|
5878
|
+
const TransformStreamImpl = globalThis.TransformStream;
|
|
5879
|
+
const decoder = new TextDecoder();
|
|
5880
|
+
const encoder = new TextEncoder();
|
|
5881
|
+
const scrubber = new ProviderPayloadScrubber(config);
|
|
5882
|
+
let buffer = "";
|
|
5883
|
+
return new TransformStreamImpl({
|
|
5884
|
+
transform(chunk, _controller) {
|
|
5885
|
+
try {
|
|
5886
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
5887
|
+
} catch {
|
|
5888
|
+
}
|
|
5889
|
+
},
|
|
5890
|
+
flush(controller) {
|
|
5891
|
+
try {
|
|
5892
|
+
buffer += decoder.decode();
|
|
5893
|
+
controller.enqueue(encoder.encode(scrubJsonBody(buffer, config, scrubber)));
|
|
5894
|
+
buffer = "";
|
|
5895
|
+
} catch {
|
|
5896
|
+
try {
|
|
5897
|
+
controller.enqueue(encoder.encode(buffer));
|
|
5898
|
+
buffer = "";
|
|
5899
|
+
} catch {
|
|
5900
|
+
}
|
|
5901
|
+
}
|
|
5902
|
+
}
|
|
5903
|
+
});
|
|
5904
|
+
}
|
|
5905
|
+
function wrapScrubResponse(response, config) {
|
|
5906
|
+
try {
|
|
5907
|
+
if (!response || typeof response !== "object" || !response.body) {
|
|
5908
|
+
return response;
|
|
5909
|
+
}
|
|
5910
|
+
const headers = new Headers(response.headers);
|
|
5911
|
+
const contentType = headers.get("content-type") ?? "";
|
|
5912
|
+
let body;
|
|
5913
|
+
if (/text\/event-stream/i.test(contentType)) {
|
|
5914
|
+
body = response.body.pipeThrough(createSseScrubTransform(config));
|
|
5915
|
+
} else if (/json/i.test(contentType)) {
|
|
5916
|
+
body = response.body.pipeThrough(createJsonScrubTransform(config));
|
|
5917
|
+
} else {
|
|
5918
|
+
return response;
|
|
5919
|
+
}
|
|
5920
|
+
headers.delete("content-length");
|
|
5921
|
+
return new Response(body, {
|
|
5922
|
+
status: response.status,
|
|
5923
|
+
statusText: response.statusText,
|
|
5924
|
+
headers
|
|
5925
|
+
});
|
|
5926
|
+
} catch {
|
|
5927
|
+
return response;
|
|
5928
|
+
}
|
|
5929
|
+
}
|
|
5930
|
+
async function installResponseScrubHook(ctx, config, logger) {
|
|
5931
|
+
if (!outputScrubEnabled(config)) {
|
|
5932
|
+
return;
|
|
5933
|
+
}
|
|
5934
|
+
const session2 = ctx?.session;
|
|
5935
|
+
if (!session2 || typeof session2.hook !== "function") {
|
|
5936
|
+
return;
|
|
5937
|
+
}
|
|
5938
|
+
try {
|
|
5939
|
+
await session2.hook("http.response", async (event) => {
|
|
5940
|
+
try {
|
|
5941
|
+
const response = event?.response;
|
|
5942
|
+
if (!response || typeof response !== "object") {
|
|
5943
|
+
return;
|
|
5944
|
+
}
|
|
5945
|
+
const wrapped = wrapScrubResponse(response, config);
|
|
5946
|
+
if (wrapped !== response) {
|
|
5947
|
+
event.response = wrapped;
|
|
5948
|
+
}
|
|
5949
|
+
} catch (error) {
|
|
5950
|
+
logger.debug("DCP output scrub failed", { error: error?.message });
|
|
5951
|
+
}
|
|
5952
|
+
});
|
|
5953
|
+
logger.debug("DCP output scrub hook installed");
|
|
5954
|
+
} catch (error) {
|
|
5955
|
+
logger.warn("DCP output scrub hook registration failed", { error: error?.message });
|
|
5956
|
+
}
|
|
5957
|
+
}
|
|
5958
|
+
function isTextDelta(part) {
|
|
5959
|
+
if (part?.type === "text-delta") {
|
|
5960
|
+
return { family: "text-delta", field: deltaField(part) };
|
|
5961
|
+
}
|
|
5962
|
+
if (part?.type === "reasoning-delta" || part?.type === "reasoning") {
|
|
5963
|
+
return { family: "reasoning-delta", field: deltaField(part) };
|
|
5964
|
+
}
|
|
5965
|
+
return void 0;
|
|
5966
|
+
}
|
|
5967
|
+
function deltaField(part) {
|
|
5968
|
+
if (typeof part.text === "string") {
|
|
5969
|
+
return "text";
|
|
5970
|
+
}
|
|
5971
|
+
if (typeof part.delta === "string") {
|
|
5972
|
+
return "delta";
|
|
5973
|
+
}
|
|
5974
|
+
return "textDelta";
|
|
5975
|
+
}
|
|
5976
|
+
var AisdkPartScrubber = class {
|
|
5977
|
+
constructor(config) {
|
|
5978
|
+
this.config = config;
|
|
5979
|
+
}
|
|
5980
|
+
config;
|
|
5981
|
+
streams = /* @__PURE__ */ new Map();
|
|
5982
|
+
process(part) {
|
|
5983
|
+
try {
|
|
5984
|
+
if (!part || typeof part !== "object") {
|
|
5985
|
+
return [part];
|
|
5986
|
+
}
|
|
5987
|
+
const delta = isTextDelta(part);
|
|
5988
|
+
if (delta) {
|
|
5989
|
+
const key = `${delta.family}:${part.id ?? ""}`;
|
|
5990
|
+
return [{ ...part, [delta.field]: this.stream(key).push(part[delta.field]) }];
|
|
5991
|
+
}
|
|
5992
|
+
if (part.type === "text-start" || part.type === "reasoning-start") {
|
|
5993
|
+
const family = part.type === "text-start" ? "text-delta" : "reasoning-delta";
|
|
5994
|
+
this.streams.delete(`${family}:${part.id ?? ""}`);
|
|
5995
|
+
return [part];
|
|
5996
|
+
}
|
|
5997
|
+
if (part.type === "text-end" || part.type === "reasoning-end") {
|
|
5998
|
+
const family = part.type === "text-end" ? "text-delta" : "reasoning-delta";
|
|
5999
|
+
const out = [];
|
|
6000
|
+
const leftover = this.take(`${family}:${part.id ?? ""}`);
|
|
6001
|
+
if (leftover) {
|
|
6002
|
+
out.push({ type: family, id: part.id, text: leftover });
|
|
6003
|
+
}
|
|
6004
|
+
const field = deltaField(part);
|
|
6005
|
+
if (typeof part[field] === "string") {
|
|
6006
|
+
out.push({ ...part, [field]: stripDcpArtifacts(part[field], this.config) });
|
|
6007
|
+
} else {
|
|
6008
|
+
out.push(part);
|
|
6009
|
+
}
|
|
6010
|
+
return out;
|
|
6011
|
+
}
|
|
6012
|
+
if (part.type === "finish") {
|
|
6013
|
+
return [...this.flushStreams(), part];
|
|
6014
|
+
}
|
|
6015
|
+
return [part];
|
|
6016
|
+
} catch {
|
|
6017
|
+
return [part];
|
|
6018
|
+
}
|
|
6019
|
+
}
|
|
6020
|
+
stream(key) {
|
|
6021
|
+
let scrubber = this.streams.get(key);
|
|
6022
|
+
if (!scrubber) {
|
|
6023
|
+
scrubber = new DcpStreamScrubber(this.config);
|
|
6024
|
+
this.streams.set(key, scrubber);
|
|
6025
|
+
}
|
|
6026
|
+
return scrubber;
|
|
6027
|
+
}
|
|
6028
|
+
take(key) {
|
|
6029
|
+
const scrubber = this.streams.get(key);
|
|
6030
|
+
if (!scrubber) {
|
|
6031
|
+
return "";
|
|
6032
|
+
}
|
|
6033
|
+
this.streams.delete(key);
|
|
6034
|
+
return scrubber.flush();
|
|
6035
|
+
}
|
|
6036
|
+
flushStreams() {
|
|
6037
|
+
const out = [];
|
|
6038
|
+
for (const [key, scrubber] of this.streams) {
|
|
6039
|
+
const leftover = scrubber.flush();
|
|
6040
|
+
if (!leftover) {
|
|
6041
|
+
continue;
|
|
6042
|
+
}
|
|
6043
|
+
const separator = key.indexOf(":");
|
|
6044
|
+
out.push({
|
|
6045
|
+
type: separator === -1 ? "text-delta" : key.slice(0, separator),
|
|
6046
|
+
id: separator === -1 ? "" : key.slice(separator + 1),
|
|
6047
|
+
text: leftover
|
|
6048
|
+
});
|
|
6049
|
+
}
|
|
6050
|
+
this.streams.clear();
|
|
6051
|
+
return out;
|
|
6052
|
+
}
|
|
6053
|
+
};
|
|
6054
|
+
function stripAisdkResultContent(result, config) {
|
|
6055
|
+
if (!result || !Array.isArray(result.content)) {
|
|
6056
|
+
return;
|
|
6057
|
+
}
|
|
6058
|
+
for (const part of result.content) {
|
|
6059
|
+
if (!part || typeof part !== "object") {
|
|
6060
|
+
continue;
|
|
6061
|
+
}
|
|
6062
|
+
if ((part.type === "text" || part.type === "reasoning") && typeof part.text === "string") {
|
|
6063
|
+
part.text = stripDcpArtifacts(part.text, config);
|
|
6064
|
+
}
|
|
6065
|
+
}
|
|
6066
|
+
}
|
|
6067
|
+
|
|
6068
|
+
// lib/v2/usage.ts
|
|
6069
|
+
function createCompressionUsageTracker() {
|
|
6070
|
+
const pending = /* @__PURE__ */ new Map();
|
|
6071
|
+
const entry = (callId) => {
|
|
6072
|
+
let current = pending.get(callId);
|
|
6073
|
+
if (!current) {
|
|
6074
|
+
current = {};
|
|
6075
|
+
pending.set(callId, current);
|
|
6076
|
+
}
|
|
6077
|
+
return current;
|
|
6078
|
+
};
|
|
6079
|
+
return {
|
|
6080
|
+
recordProvider(callId, usage) {
|
|
6081
|
+
entry(callId).provider = usage;
|
|
6082
|
+
},
|
|
6083
|
+
recordEstimatedInput(callId, inputTokens) {
|
|
6084
|
+
if (Number.isFinite(inputTokens) && inputTokens > 0) {
|
|
6085
|
+
entry(callId).estimatedInput = Math.round(inputTokens);
|
|
6086
|
+
}
|
|
6087
|
+
},
|
|
6088
|
+
resolve(callId, outputText) {
|
|
6089
|
+
const current = pending.get(callId);
|
|
6090
|
+
if (!current) {
|
|
6091
|
+
return void 0;
|
|
6092
|
+
}
|
|
6093
|
+
if (current.provider) {
|
|
6094
|
+
return current.provider;
|
|
6095
|
+
}
|
|
6096
|
+
if (current.estimatedInput === void 0) {
|
|
6097
|
+
return void 0;
|
|
6098
|
+
}
|
|
6099
|
+
return estimatedCompressionUsage(current.estimatedInput, countTokens2(outputText));
|
|
6100
|
+
},
|
|
6101
|
+
discard(callId) {
|
|
6102
|
+
pending.delete(callId);
|
|
6103
|
+
}
|
|
6104
|
+
};
|
|
6105
|
+
}
|
|
6106
|
+
function findSummaryCallId(options) {
|
|
6107
|
+
const texts = [];
|
|
6108
|
+
collectTexts(options?.prompt, texts);
|
|
6109
|
+
for (const text of texts) {
|
|
6110
|
+
const callId = extractSummaryCallId(text);
|
|
6111
|
+
if (callId) {
|
|
6112
|
+
return callId;
|
|
6113
|
+
}
|
|
6114
|
+
}
|
|
6115
|
+
return void 0;
|
|
6116
|
+
}
|
|
6117
|
+
function collectTexts(value, out, depth = 0) {
|
|
6118
|
+
if (depth > 8 || out.length > 200) {
|
|
6119
|
+
return;
|
|
6120
|
+
}
|
|
6121
|
+
if (typeof value === "string") {
|
|
6122
|
+
out.push(value);
|
|
6123
|
+
return;
|
|
6124
|
+
}
|
|
6125
|
+
if (Array.isArray(value)) {
|
|
6126
|
+
for (const item of value) {
|
|
6127
|
+
collectTexts(item, out, depth + 1);
|
|
6128
|
+
}
|
|
6129
|
+
return;
|
|
6130
|
+
}
|
|
6131
|
+
if (value && typeof value === "object") {
|
|
6132
|
+
const record = value;
|
|
6133
|
+
if (typeof record.text === "string") {
|
|
6134
|
+
out.push(record.text);
|
|
6135
|
+
}
|
|
6136
|
+
if (typeof record.content === "string") {
|
|
6137
|
+
out.push(record.content);
|
|
6138
|
+
} else if (Array.isArray(record.content)) {
|
|
6139
|
+
collectTexts(record.content, out, depth + 1);
|
|
6140
|
+
}
|
|
6141
|
+
}
|
|
6142
|
+
}
|
|
6143
|
+
function wrapLanguageModel(model, tracker, logger, scrub) {
|
|
6144
|
+
return new Proxy(model, {
|
|
6145
|
+
get(target, property, receiver) {
|
|
6146
|
+
if (property === "doStream") {
|
|
6147
|
+
return (options) => trackedStream(target, options, tracker, logger, scrub);
|
|
6148
|
+
}
|
|
6149
|
+
if (property === "doGenerate") {
|
|
6150
|
+
return (options) => trackedGenerate(target, options, tracker, logger, scrub);
|
|
6151
|
+
}
|
|
6152
|
+
return Reflect.get(target, property, receiver);
|
|
6153
|
+
}
|
|
6154
|
+
});
|
|
6155
|
+
}
|
|
6156
|
+
async function trackedStream(target, options, tracker, logger, scrub) {
|
|
6157
|
+
const result = await target.doStream(options);
|
|
6158
|
+
const callId = findSummaryCallId(options);
|
|
6159
|
+
const scrubEnabled = !!scrub && outputScrubEnabled(scrub);
|
|
6160
|
+
logger.debug("Compression usage stream call", { callId: callId ?? null });
|
|
6161
|
+
const TransformStreamImpl = globalThis.TransformStream;
|
|
6162
|
+
if (!callId && !scrubEnabled || typeof result?.stream?.pipeThrough !== "function") {
|
|
6163
|
+
return result;
|
|
6164
|
+
}
|
|
6165
|
+
if (typeof TransformStreamImpl !== "function") {
|
|
6166
|
+
return result;
|
|
6167
|
+
}
|
|
6168
|
+
const partScrubber = scrubEnabled ? new AisdkPartScrubber(scrub) : void 0;
|
|
6169
|
+
let recorded = false;
|
|
6170
|
+
const transform = new TransformStreamImpl({
|
|
6171
|
+
transform(part, controller) {
|
|
6172
|
+
try {
|
|
6173
|
+
if (callId && !recorded && part?.type === "finish" && part.usage) {
|
|
6174
|
+
const usage = normalizeProviderUsage(part.usage);
|
|
6175
|
+
if (usage) {
|
|
6176
|
+
recorded = true;
|
|
6177
|
+
tracker.recordProvider(callId, usage);
|
|
6178
|
+
}
|
|
6179
|
+
}
|
|
6180
|
+
} catch {
|
|
6181
|
+
}
|
|
6182
|
+
const parts = partScrubber ? partScrubber.process(part) : [part];
|
|
6183
|
+
for (const next of parts) {
|
|
6184
|
+
controller.enqueue(next);
|
|
6185
|
+
}
|
|
6186
|
+
}
|
|
6187
|
+
});
|
|
6188
|
+
return { ...result, stream: result.stream.pipeThrough(transform) };
|
|
6189
|
+
}
|
|
6190
|
+
async function trackedGenerate(target, options, tracker, logger, scrub) {
|
|
6191
|
+
const result = await target.doGenerate(options);
|
|
6192
|
+
const callId = findSummaryCallId(options);
|
|
6193
|
+
logger.debug("Compression usage generate call", { callId: callId ?? null });
|
|
6194
|
+
if (scrub && outputScrubEnabled(scrub)) {
|
|
6195
|
+
try {
|
|
6196
|
+
stripAisdkResultContent(result, scrub);
|
|
6197
|
+
} catch {
|
|
6198
|
+
}
|
|
6199
|
+
}
|
|
6200
|
+
if (!callId) {
|
|
6201
|
+
return result;
|
|
6202
|
+
}
|
|
6203
|
+
const usage = normalizeProviderUsage(result?.usage);
|
|
6204
|
+
if (usage) {
|
|
6205
|
+
tracker.recordProvider(callId, usage);
|
|
6206
|
+
}
|
|
6207
|
+
return result;
|
|
6208
|
+
}
|
|
6209
|
+
async function installCompressionUsageHook(ctx, tracker, logger, scrub) {
|
|
6210
|
+
const aisdk = ctx?.aisdk;
|
|
6211
|
+
if (!aisdk || typeof aisdk.hook !== "function") {
|
|
6212
|
+
logger.debug("aisdk hook unavailable; compression usage will be estimated");
|
|
6213
|
+
return;
|
|
6214
|
+
}
|
|
6215
|
+
try {
|
|
6216
|
+
await aisdk.hook("language", (event) => {
|
|
6217
|
+
try {
|
|
6218
|
+
const isModel = (candidate) => !!candidate && typeof candidate.doStream === "function";
|
|
6219
|
+
logger.debug("Compression usage hook event", {
|
|
6220
|
+
modelID: event?.model?.modelID ?? event?.model?.id ?? null,
|
|
6221
|
+
hasLanguage: isModel(event?.language),
|
|
6222
|
+
hasFactory: typeof event?.sdk?.languageModel === "function"
|
|
6223
|
+
});
|
|
6224
|
+
let model = isModel(event?.language) ? event.language : void 0;
|
|
6225
|
+
if (!model && typeof event?.sdk?.languageModel === "function") {
|
|
6226
|
+
model = event.sdk.languageModel(
|
|
6227
|
+
event?.model?.modelID ?? event?.model?.id
|
|
6228
|
+
);
|
|
6229
|
+
}
|
|
6230
|
+
if (!isModel(model)) {
|
|
6231
|
+
return;
|
|
6232
|
+
}
|
|
6233
|
+
event.language = wrapLanguageModel(model, tracker, logger, scrub);
|
|
6234
|
+
} catch (error) {
|
|
6235
|
+
logger.debug("Compression usage hook could not wrap language model", {
|
|
6236
|
+
error: error?.message
|
|
6237
|
+
});
|
|
6238
|
+
}
|
|
6239
|
+
});
|
|
6240
|
+
logger.debug("Compression usage hook installed");
|
|
6241
|
+
} catch (error) {
|
|
6242
|
+
logger.warn("Compression usage hook registration failed", {
|
|
6243
|
+
error: error?.message
|
|
6244
|
+
});
|
|
6245
|
+
}
|
|
6246
|
+
}
|
|
6247
|
+
function emptyUsageAccumulator() {
|
|
6248
|
+
return {
|
|
6249
|
+
input: 0,
|
|
6250
|
+
output: 0,
|
|
6251
|
+
cacheRead: 0,
|
|
6252
|
+
cacheWrite: 0,
|
|
6253
|
+
reasoning: 0,
|
|
6254
|
+
exclusiveInput: false
|
|
6255
|
+
};
|
|
6256
|
+
}
|
|
6257
|
+
function usageField(key) {
|
|
6258
|
+
switch (key) {
|
|
6259
|
+
case "input_tokens":
|
|
6260
|
+
case "prompt_tokens":
|
|
6261
|
+
case "promptTokenCount":
|
|
6262
|
+
return "input";
|
|
6263
|
+
case "output_tokens":
|
|
6264
|
+
case "completion_tokens":
|
|
6265
|
+
case "candidatesTokenCount":
|
|
6266
|
+
return "output";
|
|
6267
|
+
case "cache_read_input_tokens":
|
|
6268
|
+
case "cache_creation_input_tokens":
|
|
6269
|
+
case "cached_tokens":
|
|
6270
|
+
case "cachedContentTokenCount":
|
|
6271
|
+
return key === "cache_creation_input_tokens" ? "cacheWrite" : "cacheRead";
|
|
6272
|
+
case "reasoning_tokens":
|
|
6273
|
+
case "thoughtsTokenCount":
|
|
6274
|
+
return "reasoning";
|
|
6275
|
+
default:
|
|
6276
|
+
return void 0;
|
|
6277
|
+
}
|
|
6278
|
+
}
|
|
6279
|
+
function scanUsage(value, acc, depth) {
|
|
6280
|
+
if (depth > 8 || !value || typeof value !== "object") {
|
|
6281
|
+
return;
|
|
6282
|
+
}
|
|
6283
|
+
if (Array.isArray(value)) {
|
|
6284
|
+
for (const item of value) {
|
|
6285
|
+
scanUsage(item, acc, depth + 1);
|
|
6286
|
+
}
|
|
6287
|
+
return;
|
|
6288
|
+
}
|
|
6289
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
6290
|
+
const field = usageField(key);
|
|
6291
|
+
if (field && typeof raw === "number" && Number.isFinite(raw)) {
|
|
6292
|
+
acc[field] = Math.max(acc[field], raw);
|
|
6293
|
+
if (key === "cache_read_input_tokens" || key === "cache_creation_input_tokens") {
|
|
6294
|
+
acc.exclusiveInput = true;
|
|
6295
|
+
}
|
|
6296
|
+
continue;
|
|
6297
|
+
}
|
|
6298
|
+
if (raw && typeof raw === "object") {
|
|
6299
|
+
scanUsage(raw, acc, depth + 1);
|
|
6300
|
+
}
|
|
6301
|
+
}
|
|
6302
|
+
}
|
|
6303
|
+
function usageFromAccumulator(acc) {
|
|
6304
|
+
const input = acc.exclusiveInput ? acc.input + acc.cacheRead + acc.cacheWrite : acc.input;
|
|
6305
|
+
const usage = {
|
|
6306
|
+
inputTokens: Math.max(0, Math.round(input)),
|
|
6307
|
+
outputTokens: Math.max(0, Math.round(acc.output)),
|
|
6308
|
+
cacheReadTokens: Math.max(0, Math.round(acc.cacheRead)),
|
|
6309
|
+
cacheWriteTokens: Math.max(0, Math.round(acc.cacheWrite)),
|
|
6310
|
+
reasoningTokens: Math.max(0, Math.round(acc.reasoning)),
|
|
6311
|
+
source: "provider"
|
|
6312
|
+
};
|
|
6313
|
+
const total = usage.inputTokens + usage.outputTokens + usage.cacheReadTokens + usage.cacheWriteTokens + usage.reasoningTokens;
|
|
6314
|
+
return total > 0 ? usage : void 0;
|
|
6315
|
+
}
|
|
6316
|
+
function parseProviderUsageFromBody(body) {
|
|
6317
|
+
const acc = emptyUsageAccumulator();
|
|
6318
|
+
const trimmed = body.trim();
|
|
6319
|
+
let parsedJson = false;
|
|
6320
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
6321
|
+
try {
|
|
6322
|
+
scanUsage(JSON.parse(trimmed), acc, 0);
|
|
6323
|
+
parsedJson = true;
|
|
6324
|
+
} catch {
|
|
6325
|
+
}
|
|
6326
|
+
}
|
|
6327
|
+
if (!parsedJson) {
|
|
6328
|
+
for (const line of body.split(/\r?\n/)) {
|
|
6329
|
+
const match = /^\s*data:\s*(.*)$/.exec(line);
|
|
6330
|
+
if (!match) {
|
|
6331
|
+
continue;
|
|
6332
|
+
}
|
|
6333
|
+
const payload = match[1].trim();
|
|
6334
|
+
if (!payload || payload === "[DONE]") {
|
|
6335
|
+
continue;
|
|
6336
|
+
}
|
|
6337
|
+
try {
|
|
6338
|
+
scanUsage(JSON.parse(payload), acc, 0);
|
|
6339
|
+
} catch {
|
|
6340
|
+
}
|
|
6341
|
+
}
|
|
6342
|
+
}
|
|
6343
|
+
return usageFromAccumulator(acc);
|
|
6344
|
+
}
|
|
6345
|
+
async function installHttpUsageHook(ctx, tracker, logger) {
|
|
6346
|
+
const session2 = ctx?.session;
|
|
6347
|
+
if (!session2 || typeof session2.hook !== "function") {
|
|
6348
|
+
return;
|
|
6349
|
+
}
|
|
6350
|
+
try {
|
|
6351
|
+
await session2.hook("http.response", async (event) => {
|
|
6352
|
+
if (event?.kind !== "generate") {
|
|
6353
|
+
return;
|
|
6354
|
+
}
|
|
6355
|
+
try {
|
|
6356
|
+
const request = event.request;
|
|
6357
|
+
const response = event.response;
|
|
6358
|
+
if (!request || typeof request.clone !== "function") {
|
|
6359
|
+
return;
|
|
6360
|
+
}
|
|
6361
|
+
const callId = extractSummaryCallId(await request.clone().text());
|
|
6362
|
+
if (!callId) {
|
|
6363
|
+
return;
|
|
6364
|
+
}
|
|
6365
|
+
if (!response || typeof response.clone !== "function") {
|
|
6366
|
+
return;
|
|
6367
|
+
}
|
|
6368
|
+
const usage = parseProviderUsageFromBody(await response.clone().text());
|
|
6369
|
+
if (usage) {
|
|
6370
|
+
tracker.recordProvider(callId, usage);
|
|
6371
|
+
logger.debug("Compression usage captured from http response", {
|
|
6372
|
+
callId,
|
|
6373
|
+
inputTokens: usage.inputTokens,
|
|
6374
|
+
outputTokens: usage.outputTokens
|
|
6375
|
+
});
|
|
6376
|
+
}
|
|
6377
|
+
} catch (error) {
|
|
6378
|
+
logger.debug("Compression usage http capture failed", {
|
|
6379
|
+
error: error?.message
|
|
6380
|
+
});
|
|
6381
|
+
}
|
|
6382
|
+
});
|
|
6383
|
+
logger.debug("Compression usage http hook installed");
|
|
6384
|
+
} catch (error) {
|
|
6385
|
+
logger.warn("Compression usage http hook registration failed", {
|
|
6386
|
+
error: error?.message
|
|
6387
|
+
});
|
|
6388
|
+
}
|
|
6389
|
+
}
|
|
6390
|
+
|
|
5084
6391
|
// lib/prompts/index.ts
|
|
5085
6392
|
function renderSystemPrompt(prompts, protectedToolsExtension, manual, subagent) {
|
|
5086
6393
|
const extensions = [];
|
|
@@ -5285,8 +6592,8 @@ async function handleContextCommand(ctx) {
|
|
|
5285
6592
|
// lib/messages/utils.ts
|
|
5286
6593
|
import { createHash } from "crypto";
|
|
5287
6594
|
var SUMMARY_ID_HASH_LENGTH = 16;
|
|
5288
|
-
var
|
|
5289
|
-
var
|
|
6595
|
+
var DCP_PAIRED_TAG_REGEX2 = /<dcp[^>]*>[\s\S]*?<\/dcp[^>]*>/gi;
|
|
6596
|
+
var DCP_UNPAIRED_TAG_REGEX2 = /<\/?dcp[^>]*>/gi;
|
|
5290
6597
|
var INJECTED_MESSAGE_ID_SUFFIX_REGEX = /(?<=\n)<dcp-message-id[^>]*>m\d+<\/dcp-message-id>\s*$/;
|
|
5291
6598
|
var HALLUCINATED_PARAMETER_SUFFIX_REGEX = /(?<=\n)m\d+<\/parameter>\s*$/;
|
|
5292
6599
|
var COMPACT_TAG_REGEX = /@(?:[1-9]\d*|b[1-9]\d*|blocked)@(?:[ \t]+\[(?:low|medium|high)\])?/gi;
|
|
@@ -5410,7 +6717,7 @@ function buildToolIdList(state, messages) {
|
|
|
5410
6717
|
var stripHallucinationsFromString = (text, format2 = "xml") => {
|
|
5411
6718
|
if (format2 === "compact") text = text.replace(COMPACT_TAG_REGEX, "");
|
|
5412
6719
|
const withoutKnownSuffixes = text.replace(INJECTED_MESSAGE_ID_SUFFIX_REGEX, "").replace(HALLUCINATED_PARAMETER_SUFFIX_REGEX, "");
|
|
5413
|
-
return withoutKnownSuffixes.replace(
|
|
6720
|
+
return withoutKnownSuffixes.replace(DCP_PAIRED_TAG_REGEX2, "").replace(DCP_UNPAIRED_TAG_REGEX2, "");
|
|
5414
6721
|
};
|
|
5415
6722
|
var stripHallucinations = (messages, format2 = "xml") => {
|
|
5416
6723
|
for (const message of messages) {
|
|
@@ -6592,7 +7899,7 @@ async function handleRecompressCommand(ctx) {
|
|
|
6592
7899
|
}
|
|
6593
7900
|
|
|
6594
7901
|
// lib/commands/stats.ts
|
|
6595
|
-
function formatStatsMessage(sessionTokens, sessionSummaryTokens, sessionTools, sessionMessages, sessionDurationMs, allTime) {
|
|
7902
|
+
function formatStatsMessage(sessionTokens, sessionSummaryTokens, sessionTools, sessionMessages, sessionDurationMs, sessionUsage, allTime) {
|
|
6596
7903
|
const lines = [];
|
|
6597
7904
|
lines.push("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E");
|
|
6598
7905
|
lines.push("\u2502 DCP Statistics \u2502");
|
|
@@ -6607,6 +7914,16 @@ function formatStatsMessage(sessionTokens, sessionSummaryTokens, sessionTools, s
|
|
|
6607
7914
|
lines.push(` Time: ${formatCompressionTime(sessionDurationMs)}`);
|
|
6608
7915
|
lines.push(` Messages: ${sessionMessages}`);
|
|
6609
7916
|
lines.push(` Tools: ${sessionTools}`);
|
|
7917
|
+
lines.push(
|
|
7918
|
+
` Summary requests: ${sessionUsage.calls} (${sessionUsage.providerCalls} provider, ${sessionUsage.estimatedCalls} estimated)`
|
|
7919
|
+
);
|
|
7920
|
+
lines.push(
|
|
7921
|
+
` Request in|out: ~${formatTokenCount(sessionUsage.inputTokens)} | ~${formatTokenCount(sessionUsage.outputTokens)}`
|
|
7922
|
+
);
|
|
7923
|
+
lines.push(
|
|
7924
|
+
` Request cache: ~${formatTokenCount(sessionUsage.cacheReadTokens)} read | ~${formatTokenCount(sessionUsage.cacheWriteTokens)} write`
|
|
7925
|
+
);
|
|
7926
|
+
lines.push(` Request reason: ~${formatTokenCount(sessionUsage.reasoningTokens)}`);
|
|
6610
7927
|
lines.push("");
|
|
6611
7928
|
lines.push("All-time:");
|
|
6612
7929
|
lines.push("\u2500".repeat(60));
|
|
@@ -6614,6 +7931,9 @@ function formatStatsMessage(sessionTokens, sessionSummaryTokens, sessionTools, s
|
|
|
6614
7931
|
lines.push(` Tools pruned: ${allTime.totalTools}`);
|
|
6615
7932
|
lines.push(` Messages pruned: ${allTime.totalMessages}`);
|
|
6616
7933
|
lines.push(` Sessions: ${allTime.sessionCount}`);
|
|
7934
|
+
lines.push(
|
|
7935
|
+
` Summary requests: ${allTime.compressionUsage.calls} (~${formatTokenCount(allTime.compressionUsage.inputTokens)} in | ~${formatTokenCount(allTime.compressionUsage.outputTokens)} out)`
|
|
7936
|
+
);
|
|
6617
7937
|
return lines.join("\n");
|
|
6618
7938
|
}
|
|
6619
7939
|
function formatCompressionRatio(inputTokens, outputTokens) {
|
|
@@ -6653,6 +7973,7 @@ async function handleStatsCommand(ctx) {
|
|
|
6653
7973
|
report2.sessionTools,
|
|
6654
7974
|
report2.sessionMessages,
|
|
6655
7975
|
report2.sessionDurationMs,
|
|
7976
|
+
report2.sessionCompressionUsage,
|
|
6656
7977
|
report2.allTime
|
|
6657
7978
|
);
|
|
6658
7979
|
const params = getCurrentParams(state, messages, logger);
|
|
@@ -6663,9 +7984,11 @@ async function handleStatsCommand(ctx) {
|
|
|
6663
7984
|
sessionTools: report2.sessionTools,
|
|
6664
7985
|
sessionMessages: report2.sessionMessages,
|
|
6665
7986
|
sessionDurationMs: report2.sessionDurationMs,
|
|
7987
|
+
sessionCompressionUsage: report2.sessionCompressionUsage,
|
|
6666
7988
|
allTimeTokens: report2.allTime.totalTokens,
|
|
6667
7989
|
allTimeTools: report2.allTime.totalTools,
|
|
6668
|
-
allTimeMessages: report2.allTime.totalMessages
|
|
7990
|
+
allTimeMessages: report2.allTime.totalMessages,
|
|
7991
|
+
allTimeCompressionUsage: report2.allTime.compressionUsage
|
|
6669
7992
|
});
|
|
6670
7993
|
}
|
|
6671
7994
|
async function buildStatsReport(state, logger) {
|
|
@@ -6687,6 +8010,7 @@ async function buildStatsReport(state, logger) {
|
|
|
6687
8010
|
}
|
|
6688
8011
|
}
|
|
6689
8012
|
const sessionTools = prunedToolIds.size;
|
|
8013
|
+
const sessionCompressionUsage = normalizeCompressionUsageTotals(state.stats.compressionUsage);
|
|
6690
8014
|
let sessionMessages = 0;
|
|
6691
8015
|
for (const entry of state.prune.messages.byMessageId.values()) {
|
|
6692
8016
|
if (entry.activeBlockIds.length > 0) {
|
|
@@ -6700,6 +8024,7 @@ async function buildStatsReport(state, logger) {
|
|
|
6700
8024
|
sessionTools,
|
|
6701
8025
|
sessionMessages,
|
|
6702
8026
|
sessionDurationMs,
|
|
8027
|
+
sessionCompressionUsage,
|
|
6703
8028
|
allTime
|
|
6704
8029
|
};
|
|
6705
8030
|
}
|
|
@@ -7235,17 +8560,29 @@ function project(native, entries, session2) {
|
|
|
7235
8560
|
import { tool as tool2 } from "@opencode-ai/plugin";
|
|
7236
8561
|
var z = tool2.schema;
|
|
7237
8562
|
var session = z.object({ sessionID: z.string() });
|
|
8563
|
+
var compressionUsage = z.object({
|
|
8564
|
+
calls: z.number(),
|
|
8565
|
+
providerCalls: z.number(),
|
|
8566
|
+
estimatedCalls: z.number(),
|
|
8567
|
+
inputTokens: z.number(),
|
|
8568
|
+
outputTokens: z.number(),
|
|
8569
|
+
cacheReadTokens: z.number(),
|
|
8570
|
+
cacheWriteTokens: z.number(),
|
|
8571
|
+
reasoningTokens: z.number()
|
|
8572
|
+
});
|
|
7238
8573
|
var stats = z.object({
|
|
7239
8574
|
sessionTokens: z.number(),
|
|
7240
8575
|
sessionSummaryTokens: z.number(),
|
|
7241
8576
|
sessionDurationMs: z.number(),
|
|
7242
8577
|
sessionTools: z.number(),
|
|
7243
8578
|
sessionMessages: z.number(),
|
|
8579
|
+
sessionCompressionUsage: compressionUsage,
|
|
7244
8580
|
allTime: z.object({
|
|
7245
8581
|
totalTokens: z.number(),
|
|
7246
8582
|
totalTools: z.number(),
|
|
7247
8583
|
totalMessages: z.number(),
|
|
7248
|
-
sessionCount: z.number()
|
|
8584
|
+
sessionCount: z.number(),
|
|
8585
|
+
compressionUsage
|
|
7249
8586
|
})
|
|
7250
8587
|
});
|
|
7251
8588
|
var context = z.object({
|
|
@@ -7300,6 +8637,14 @@ async function setup(ctx) {
|
|
|
7300
8637
|
return;
|
|
7301
8638
|
}
|
|
7302
8639
|
const logger = new Logger(config.debug);
|
|
8640
|
+
const usageTracker = createCompressionUsageTracker();
|
|
8641
|
+
const scrub = {
|
|
8642
|
+
modelOutput: config.compress.scrubModelOutput,
|
|
8643
|
+
messageIds: config.compress.scrubMessageIds
|
|
8644
|
+
};
|
|
8645
|
+
await installCompressionUsageHook(ctx, usageTracker, logger, scrub);
|
|
8646
|
+
await installHttpUsageHook(ctx, usageTracker, logger);
|
|
8647
|
+
await installResponseScrubHook(ctx, scrub, logger);
|
|
7303
8648
|
const prompts = new PromptStore(
|
|
7304
8649
|
logger,
|
|
7305
8650
|
ctx.location.directory,
|
|
@@ -7465,7 +8810,7 @@ async function setup(ctx) {
|
|
|
7465
8810
|
await ctx.session.hook("generate", (event) => {
|
|
7466
8811
|
const own = event.messages.filter(
|
|
7467
8812
|
(message) => Array.isArray(message.content) && message.content.some(
|
|
7468
|
-
(part) => part?.type === "text" && typeof part.text === "string" && part.text
|
|
8813
|
+
(part) => part?.type === "text" && typeof part.text === "string" && hasSummaryMarker(part.text)
|
|
7469
8814
|
)
|
|
7470
8815
|
);
|
|
7471
8816
|
if (own.length === 0) {
|
|
@@ -7475,6 +8820,25 @@ async function setup(ctx) {
|
|
|
7475
8820
|
for (const name of Object.keys(event.tools ?? {})) {
|
|
7476
8821
|
delete event.tools[name];
|
|
7477
8822
|
}
|
|
8823
|
+
const callId = own.flatMap(
|
|
8824
|
+
(message) => Array.isArray(message.content) ? message.content : []
|
|
8825
|
+
).filter((part) => part?.type === "text" && typeof part.text === "string").map((part) => extractSummaryCallId(part.text)).find((id) => !!id);
|
|
8826
|
+
if (callId) {
|
|
8827
|
+
const texts = [];
|
|
8828
|
+
for (const part of event.system ?? []) {
|
|
8829
|
+
if (typeof part?.text === "string") {
|
|
8830
|
+
texts.push(part.text);
|
|
8831
|
+
}
|
|
8832
|
+
}
|
|
8833
|
+
for (const message of own) {
|
|
8834
|
+
for (const part of message.content ?? []) {
|
|
8835
|
+
if (part?.type === "text" && typeof part.text === "string") {
|
|
8836
|
+
texts.push(part.text);
|
|
8837
|
+
}
|
|
8838
|
+
}
|
|
8839
|
+
}
|
|
8840
|
+
usageTracker.recordEstimatedInput(callId, countTokens2(texts.join("\n")));
|
|
8841
|
+
}
|
|
7478
8842
|
});
|
|
7479
8843
|
if (config.compress.permission !== "deny") {
|
|
7480
8844
|
const define = (state) => {
|
|
@@ -7485,8 +8849,19 @@ async function setup(ctx) {
|
|
|
7485
8849
|
config,
|
|
7486
8850
|
prompts,
|
|
7487
8851
|
generate: async (input) => {
|
|
7488
|
-
|
|
7489
|
-
|
|
8852
|
+
try {
|
|
8853
|
+
const result = await ctx.session.generate({
|
|
8854
|
+
sessionID: input.sessionID,
|
|
8855
|
+
prompt: input.prompt
|
|
8856
|
+
});
|
|
8857
|
+
const text = result?.text ?? "";
|
|
8858
|
+
const usage = input.callId ? usageTracker.resolve(input.callId, text) : void 0;
|
|
8859
|
+
return { text, usage };
|
|
8860
|
+
} finally {
|
|
8861
|
+
if (input.callId) {
|
|
8862
|
+
usageTracker.discard(input.callId);
|
|
8863
|
+
}
|
|
8864
|
+
}
|
|
7490
8865
|
}
|
|
7491
8866
|
};
|
|
7492
8867
|
return createRcCompressTool(context2);
|