zelari-code 1.41.0 → 1.42.0
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/cli/budget/llmCompact.js +97 -83
- package/dist/cli/budget/llmCompact.js.map +1 -1
- package/dist/cli/budget/requestMeter.js +138 -0
- package/dist/cli/budget/requestMeter.js.map +1 -0
- package/dist/cli/budget/requestSnapshotStore.js +55 -0
- package/dist/cli/budget/requestSnapshotStore.js.map +1 -0
- package/dist/cli/budget/tokenBudget.js +147 -15
- package/dist/cli/budget/tokenBudget.js.map +1 -1
- package/dist/cli/hooks/conversationContext.js +4 -0
- package/dist/cli/hooks/conversationContext.js.map +1 -1
- package/dist/cli/hooks/historyCompaction.js +76 -21
- package/dist/cli/hooks/historyCompaction.js.map +1 -1
- package/dist/cli/hooks/useChatTurn.js +90 -23
- package/dist/cli/hooks/useChatTurn.js.map +1 -1
- package/dist/cli/main.bundled.js +468 -219
- package/dist/cli/main.bundled.js.map +4 -4
- package/dist/cli/provider/openai-compatible.js +11 -1
- package/dist/cli/provider/openai-compatible.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,23 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Falls back to null on any failure so callers use extractive summary.
|
|
2
|
+
* LLM compaction via CACHE-AWARE PREFIX REPLAY (v1.36.0, P9).
|
|
4
3
|
*
|
|
5
|
-
*
|
|
4
|
+
* Pre-1.36 this module built a COLD request: its own COMPACT_SYSTEM, a
|
|
5
|
+
* flattened transcript, and a raw fetch to the chat-completions endpoint.
|
|
6
|
+
* That request diverged from the live conversation from token 0 — zero
|
|
7
|
+
* prompt-cache reuse — and the rewritten history invalidated the warm
|
|
8
|
+
* prefix for the NEXT conversation turn too.
|
|
6
9
|
*
|
|
7
|
-
*
|
|
10
|
+
* The replay approach sends:
|
|
11
|
+
*
|
|
12
|
+
* SYSTEM(original) + TOOLS(original) + DROPPED PREFIX + COMPACTION_INSTRUCTION
|
|
13
|
+
*
|
|
14
|
+
* so everything up to the trailing instruction is byte-identical to the
|
|
15
|
+
* previous routed request and hits the provider prefix cache (DeepSeek/
|
|
16
|
+
* OpenAI/GLM bill cached tokens at ~1/10). Tools stay advertised even
|
|
17
|
+
* though the summarizer must not call them: removing them would change
|
|
18
|
+
* the prefix token sequence and kill cache reuse.
|
|
19
|
+
*
|
|
20
|
+
* Fallback: any failure → null → caller uses the extractive summary.
|
|
21
|
+
* Disable entirely with ZELARI_LLM_COMPACT=0.
|
|
22
|
+
*
|
|
23
|
+
* @since v1.21.0 (cold-request version)
|
|
24
|
+
* @updated v1.36.0 — replay-based, providerStream-routed
|
|
8
25
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import { PROVIDER_ENDPOINTS, } from '../provider/openai-compatible.js';
|
|
12
|
-
const COMPACT_SYSTEM = `You compress earlier turns of a coding-agent session into a dense continuity brief.
|
|
13
|
-
Output plain text (no markdown fences) with these sections:
|
|
14
|
-
1) Goal — what the user wants
|
|
15
|
-
2) Decisions — choices already made
|
|
16
|
-
3) Done — completed work / files changed
|
|
17
|
-
4) Open — remaining tasks / blockers
|
|
18
|
-
5) Constraints — important rules the agent must keep
|
|
26
|
+
export const COMPACTION_INSTRUCTION = `
|
|
27
|
+
You are now acting as a compaction engine for this coding-agent session.
|
|
19
28
|
|
|
20
|
-
|
|
29
|
+
Condense the conversation ABOVE into a compact checkpoint sufficient to continue the task.
|
|
30
|
+
|
|
31
|
+
Preserve:
|
|
32
|
+
- user's goal and evolving intent
|
|
33
|
+
- decisions already made
|
|
34
|
+
- exact file paths and identifiers
|
|
35
|
+
- code changes already completed
|
|
36
|
+
- commands/errors that still matter
|
|
37
|
+
- constraints
|
|
38
|
+
- unfinished work
|
|
39
|
+
- the single most likely next action
|
|
40
|
+
|
|
41
|
+
Do not call tools.
|
|
42
|
+
Do not mention this summarization request.
|
|
43
|
+
Output only the checkpoint.
|
|
44
|
+
Be concise.
|
|
45
|
+
`.trim();
|
|
21
46
|
export function isLlmCompactEnabled() {
|
|
22
47
|
const v = process.env.ZELARI_LLM_COMPACT?.trim().toLowerCase();
|
|
23
48
|
if (v === '0' || v === 'false' || v === 'off' || v === 'no')
|
|
@@ -25,90 +50,79 @@ export function isLlmCompactEnabled() {
|
|
|
25
50
|
// default on when env unset
|
|
26
51
|
return true;
|
|
27
52
|
}
|
|
53
|
+
/** Explicit model override for the summarizer (ZELARI_COMPACT_MODEL). */
|
|
54
|
+
export function compactModelOverride() {
|
|
55
|
+
const v = process.env.ZELARI_COMPACT_MODEL?.trim();
|
|
56
|
+
return v ? v : undefined;
|
|
57
|
+
}
|
|
58
|
+
/** Hard ceiling so a stuck summarizer can't hang the dispatch loop. */
|
|
59
|
+
const REPLAY_TIMEOUT_MS = 60_000;
|
|
28
60
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
61
|
+
* Summarize `droppedMessages` by replaying the ORIGINAL request prefix and
|
|
62
|
+
* appending the compaction instruction as the final user message.
|
|
63
|
+
*
|
|
64
|
+
* Returns `summary: null` when disabled, empty, failed, or when the model
|
|
65
|
+
* emitted a tool call (the summarizer must never act — only condense).
|
|
31
66
|
*/
|
|
32
|
-
export async function
|
|
67
|
+
export async function llmSummarizeHistoryReplay(input) {
|
|
68
|
+
const override = input.overrideModel ?? compactModelOverride();
|
|
69
|
+
const model = override ?? input.model;
|
|
70
|
+
const cacheReuseExpected = !override;
|
|
33
71
|
if (!isLlmCompactEnabled())
|
|
34
|
-
return null;
|
|
35
|
-
if (
|
|
36
|
-
return null;
|
|
37
|
-
let config = null;
|
|
38
|
-
try {
|
|
39
|
-
config = await resolveCompactProviderConfig();
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
return null;
|
|
72
|
+
return { summary: null, model, cacheReuseExpected };
|
|
73
|
+
if (input.droppedMessages.length === 0) {
|
|
74
|
+
return { summary: null, model, cacheReuseExpected };
|
|
43
75
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
76
|
+
// Replay = original prefix + instruction tail. The prefix must be
|
|
77
|
+
// byte-identical to the previous routed request for cache reuse.
|
|
78
|
+
const messages = [
|
|
79
|
+
...input.systemMessages,
|
|
80
|
+
...input.droppedMessages,
|
|
81
|
+
{
|
|
82
|
+
role: 'user',
|
|
83
|
+
content: COMPACTION_INSTRUCTION,
|
|
84
|
+
},
|
|
85
|
+
];
|
|
47
86
|
const controller = new AbortController();
|
|
48
|
-
const timeout = setTimeout(() => controller.abort(),
|
|
87
|
+
const timeout = setTimeout(() => controller.abort(), REPLAY_TIMEOUT_MS);
|
|
49
88
|
const onOuterAbort = () => controller.abort();
|
|
50
89
|
input.signal?.addEventListener('abort', onOuterAbort, { once: true });
|
|
51
90
|
try {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
91
|
+
let text = '';
|
|
92
|
+
let emittedToolCall = false;
|
|
93
|
+
for await (const delta of input.providerStream({
|
|
94
|
+
provider: input.provider,
|
|
95
|
+
model,
|
|
96
|
+
messages,
|
|
97
|
+
// Tools stay advertised: dropping them would change the prefix token
|
|
98
|
+
// sequence and destroy cache reuse (explicit DSH decision). They are
|
|
99
|
+
// sorted canonically (same discipline as the live routed request and
|
|
100
|
+
// the snapshot fingerprints) so the replay prefix is byte-identical.
|
|
101
|
+
tools: [...input.tools].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)),
|
|
55
102
|
signal: controller.signal,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
authorization: `Bearer ${config.apiKey}`,
|
|
59
|
-
},
|
|
60
|
-
body: JSON.stringify({
|
|
61
|
-
model,
|
|
103
|
+
generation: {
|
|
104
|
+
purpose: 'compaction',
|
|
62
105
|
temperature: 0.1,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return null;
|
|
77
|
-
const json = (await res.json());
|
|
78
|
-
const text = json.choices?.[0]?.message?.content?.trim();
|
|
79
|
-
if (!text)
|
|
80
|
-
return null;
|
|
81
|
-
return ('[history-summary · llm]\n' +
|
|
82
|
-
text +
|
|
83
|
-
'\n\nContinue from the recent messages below; honor decisions already made above.');
|
|
106
|
+
maxTokens: 900,
|
|
107
|
+
},
|
|
108
|
+
})) {
|
|
109
|
+
if (delta.kind === 'text')
|
|
110
|
+
text += delta.delta;
|
|
111
|
+
if (delta.kind === 'tool_call')
|
|
112
|
+
emittedToolCall = true;
|
|
113
|
+
}
|
|
114
|
+
if (emittedToolCall)
|
|
115
|
+
return { summary: null, model, cacheReuseExpected };
|
|
116
|
+
if (!text.trim())
|
|
117
|
+
return { summary: null, model, cacheReuseExpected };
|
|
118
|
+
return { summary: text.trim(), model, cacheReuseExpected };
|
|
84
119
|
}
|
|
85
120
|
catch {
|
|
86
|
-
return null;
|
|
121
|
+
return { summary: null, model, cacheReuseExpected };
|
|
87
122
|
}
|
|
88
123
|
finally {
|
|
89
124
|
clearTimeout(timeout);
|
|
90
125
|
input.signal?.removeEventListener('abort', onOuterAbort);
|
|
91
126
|
}
|
|
92
127
|
}
|
|
93
|
-
async function resolveCompactProviderConfig() {
|
|
94
|
-
const active = getProviderConfig().activeProviderId;
|
|
95
|
-
const meta = await resolveApiKeyWithMeta(active);
|
|
96
|
-
const apiKey = meta?.apiKey;
|
|
97
|
-
if (!apiKey)
|
|
98
|
-
return null;
|
|
99
|
-
const custom = getCustomEndpoint(active);
|
|
100
|
-
let baseUrl = custom ||
|
|
101
|
-
(active === 'openai-compatible' || active === 'custom'
|
|
102
|
-
? process.env.OPENAI_BASE_URL ?? PROVIDER_ENDPOINTS[active]
|
|
103
|
-
: PROVIDER_ENDPOINTS[active]);
|
|
104
|
-
if (!baseUrl)
|
|
105
|
-
return null;
|
|
106
|
-
const model = getModelForProvider(active);
|
|
107
|
-
return {
|
|
108
|
-
apiKey,
|
|
109
|
-
baseUrl,
|
|
110
|
-
model,
|
|
111
|
-
providerId: active,
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
128
|
//# sourceMappingURL=llmCompact.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"llmCompact.js","sourceRoot":"","sources":["../../../src/cli/budget/llmCompact.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"llmCompact.js","sourceRoot":"","sources":["../../../src/cli/budget/llmCompact.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* requestMeter — full-request occupancy estimation with provider-usage
|
|
3
|
+
* anchoring (v1.36.0 context/cache upgrade; P4/P5).
|
|
4
|
+
*
|
|
5
|
+
* The legacy `estimateHistoryTokens` measured ONLY the rolling history
|
|
6
|
+
* (`content` + `toolCalls` args). It ignored the system prompt, tool
|
|
7
|
+
* schemas, `reasoningContent`, and per-message role overhead — tens of
|
|
8
|
+
* thousands of tokens in tool-heavy sessions — so occupancy read low and
|
|
9
|
+
* compaction fired too late (or never).
|
|
10
|
+
*
|
|
11
|
+
* This meter measures the WHOLE request surface:
|
|
12
|
+
*
|
|
13
|
+
* estimatedPromptTokens ≈ system + tools-schema + conversation
|
|
14
|
+
*
|
|
15
|
+
* and anchors to provider-reported usage when the header fingerprint
|
|
16
|
+
* matches the last routed request: provider usage is ground truth for
|
|
17
|
+
* the stable header, so we only re-estimate the conversation DELTA.
|
|
18
|
+
*
|
|
19
|
+
* `contextPressureTokens` NEVER subtracts cached tokens: the provider
|
|
20
|
+
* must still HOLD the whole prefix (cached or not) in the context window.
|
|
21
|
+
*
|
|
22
|
+
* @since v1.36.0
|
|
23
|
+
*/
|
|
24
|
+
import { stableStringify, sha256Hex, canonicalTools, } from '@zelari/core/harness';
|
|
25
|
+
/** Local chars→tokens estimate (same heuristic as tokenBudget; local copy to
|
|
26
|
+
* avoid a tokenBudget ⇄ requestMeter import cycle). */
|
|
27
|
+
function estimateTokensLocal(text) {
|
|
28
|
+
if (!text)
|
|
29
|
+
return 0;
|
|
30
|
+
return Math.max(1, Math.ceil(text.length / 4));
|
|
31
|
+
}
|
|
32
|
+
/** ~4 tokens of wire overhead per message (role/id framing). */
|
|
33
|
+
const MESSAGE_OVERHEAD_TOKENS = 4;
|
|
34
|
+
/** Estimate one message across ALL its token-bearing fields. */
|
|
35
|
+
export function estimateMessageTokens(m) {
|
|
36
|
+
let n = MESSAGE_OVERHEAD_TOKENS + estimateTokensLocal(m.content ?? '');
|
|
37
|
+
if (m.toolCalls) {
|
|
38
|
+
for (const tc of m.toolCalls) {
|
|
39
|
+
n += estimateTokensLocal(tc.name) + estimateTokensLocal(tc.id);
|
|
40
|
+
n += estimateTokensLocal(JSON.stringify(tc.args ?? {}));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (m.reasoningContent)
|
|
44
|
+
n += estimateTokensLocal(m.reasoningContent);
|
|
45
|
+
if (m.toolCallId)
|
|
46
|
+
n += estimateTokensLocal(m.toolCallId);
|
|
47
|
+
return n;
|
|
48
|
+
}
|
|
49
|
+
/** Estimate the tool-schema surface (name + description + parameters). */
|
|
50
|
+
export function estimateToolSchemaTokens(tools) {
|
|
51
|
+
let n = 0;
|
|
52
|
+
for (const t of tools) {
|
|
53
|
+
n += estimateTokensLocal(t.name) + estimateTokensLocal(t.description ?? '');
|
|
54
|
+
n += estimateTokensLocal(JSON.stringify(t.parameters ?? {}));
|
|
55
|
+
}
|
|
56
|
+
// Wire framing per advertised function.
|
|
57
|
+
return n + tools.length * MESSAGE_OVERHEAD_TOKENS;
|
|
58
|
+
}
|
|
59
|
+
/** Estimate the system-prompt surface. */
|
|
60
|
+
export function estimateSystemTokens(systemMessages) {
|
|
61
|
+
let n = 0;
|
|
62
|
+
for (const m of systemMessages)
|
|
63
|
+
n += estimateMessageTokens(m);
|
|
64
|
+
return n;
|
|
65
|
+
}
|
|
66
|
+
/** Estimated tokens of the conversation tail. */
|
|
67
|
+
export function estimateConversationTokens(conversation) {
|
|
68
|
+
let n = 0;
|
|
69
|
+
for (const m of conversation)
|
|
70
|
+
n += estimateMessageTokens(m);
|
|
71
|
+
return n;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Same fingerprint discipline as requestSnapshot: provider + model + system
|
|
75
|
+
* + canonical tools. Cheap enough to recompute per turn.
|
|
76
|
+
*/
|
|
77
|
+
export function headerFingerprintOf(input) {
|
|
78
|
+
return createFingerprintOnly(input);
|
|
79
|
+
}
|
|
80
|
+
function createFingerprintOnly(input) {
|
|
81
|
+
return sha256Hex(stableStringify({
|
|
82
|
+
provider: input.provider,
|
|
83
|
+
model: input.model,
|
|
84
|
+
systemMessages: input.systemMessages,
|
|
85
|
+
tools: canonicalTools(input.tools),
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Measure the full request surface.
|
|
90
|
+
*
|
|
91
|
+
* Anchoring: when `anchor.snapshot`'s headerFingerprint equals the current
|
|
92
|
+
* header fingerprint AND its usage arrived, the stable header is measured
|
|
93
|
+
* as `usage.promptTokens - estimatedConversationTokens(anchor.conversation)`
|
|
94
|
+
* (clamped to ≥0) — provider ground truth beats chars/4. The current
|
|
95
|
+
* conversation is always freshly estimated.
|
|
96
|
+
*/
|
|
97
|
+
export function measureRequest(input) {
|
|
98
|
+
const estimatedHeaderTokens = estimateSystemTokens(input.systemMessages) + estimateToolSchemaTokens(input.tools);
|
|
99
|
+
const currentConversationTokens = estimateConversationTokens(input.conversation);
|
|
100
|
+
let headerAnchored = false;
|
|
101
|
+
let headerTokens = estimatedHeaderTokens;
|
|
102
|
+
const anchorUsage = input.anchor?.usage;
|
|
103
|
+
const anchorSnapshot = input.anchor?.snapshot;
|
|
104
|
+
if (anchorUsage && anchorSnapshot) {
|
|
105
|
+
const currentHeaderFp = createFingerprintOnly({
|
|
106
|
+
provider: anchorSnapshot.provider,
|
|
107
|
+
model: anchorSnapshot.model,
|
|
108
|
+
systemMessages: input.systemMessages,
|
|
109
|
+
tools: input.tools,
|
|
110
|
+
});
|
|
111
|
+
if (currentHeaderFp === anchorSnapshot.headerFingerprint) {
|
|
112
|
+
// Provider saw header + its conversation; subtract the conversation
|
|
113
|
+
// estimate to isolate the header ground truth.
|
|
114
|
+
const anchorConv = estimateConversationTokens(anchorSnapshot.conversation);
|
|
115
|
+
const headerFromUsage = Math.max(0, anchorUsage.promptTokens - anchorConv);
|
|
116
|
+
// v1.36.0 (case 9): the provider truth is EXPECTED to dwarf the
|
|
117
|
+
// chars/4 estimate (chat templating, tool marshalling, hidden
|
|
118
|
+
// framing). Anchoring exists precisely for that gap — so only a
|
|
119
|
+
// non-positive (garbage/empty) value is rejected, never a large one.
|
|
120
|
+
if (headerFromUsage > 0) {
|
|
121
|
+
headerTokens = headerFromUsage;
|
|
122
|
+
headerAnchored = true;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const estimatedPromptTokens = headerTokens + currentConversationTokens;
|
|
127
|
+
const reservedOutput = input.reservedOutputTokens ?? 0;
|
|
128
|
+
const contextPressureTokens = estimatedPromptTokens + reservedOutput;
|
|
129
|
+
return {
|
|
130
|
+
estimatedPromptTokens,
|
|
131
|
+
estimatedHeaderTokens,
|
|
132
|
+
headerAnchored,
|
|
133
|
+
contextPressureTokens,
|
|
134
|
+
occupancy: Math.min(1, contextPressureTokens / input.contextLimit),
|
|
135
|
+
purpose: input.purpose ?? 'conversation',
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=requestMeter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requestMeter.js","sourceRoot":"","sources":["../../../src/cli/budget/requestMeter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* requestSnapshotStore — in-memory per-session store of routed request
|
|
3
|
+
* snapshots (v1.36.0 context/cache upgrade).
|
|
4
|
+
*
|
|
5
|
+
* Keeps the LAST snapshot per session (that's all cache-aware compaction
|
|
6
|
+
* needs: the most recent routed request = the warmest provider prefix)
|
|
7
|
+
* plus the provider-reported usage that answered it.
|
|
8
|
+
*
|
|
9
|
+
* Usage flow:
|
|
10
|
+
* - AgentHarness `onRequestSnapshot` → `recordRequestSnapshot(sessionId, s)`
|
|
11
|
+
* - `message_end` usage in useChatTurn → `recordRequestUsage(sessionId, usage)`
|
|
12
|
+
* (associated to the snapshot that is currently pending for that session)
|
|
13
|
+
* - `applyBudgetPolicyAsync` → `getRequestSnapshot(sessionId)` → replay base
|
|
14
|
+
* - `/clear` | `/new` → `clearRequestSnapshots(sessionId)` /
|
|
15
|
+
* `clearAllRequestSnapshots()` (CLI is single-session per process).
|
|
16
|
+
*
|
|
17
|
+
* @since v1.36.0
|
|
18
|
+
*/
|
|
19
|
+
const store = new Map();
|
|
20
|
+
/** Track the most recently routed request for a session. */
|
|
21
|
+
export function recordRequestSnapshot(sessionId, snapshot) {
|
|
22
|
+
// Keep the pending slot: usage recorded next will bind to this snapshot.
|
|
23
|
+
store.set(sessionId, { snapshot });
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Attach provider-reported usage to the session's latest snapshot.
|
|
27
|
+
* Called when the `message_end` usage delta lands — after the snapshot.
|
|
28
|
+
*/
|
|
29
|
+
export function recordRequestUsage(sessionId, usage) {
|
|
30
|
+
const entry = store.get(sessionId);
|
|
31
|
+
if (!entry)
|
|
32
|
+
return;
|
|
33
|
+
entry.usage = usage;
|
|
34
|
+
}
|
|
35
|
+
/** Last routed snapshot (+ usage, when reported). Null when none yet. */
|
|
36
|
+
export function getRequestSnapshot(sessionId) {
|
|
37
|
+
return store.get(sessionId)?.snapshot ?? null;
|
|
38
|
+
}
|
|
39
|
+
/** Last snapshot together with its provider-reported usage. */
|
|
40
|
+
export function getRequestSnapshotWithUsage(sessionId) {
|
|
41
|
+
return store.get(sessionId) ?? null;
|
|
42
|
+
}
|
|
43
|
+
/** Drop one session's snapshot (session switch). */
|
|
44
|
+
export function clearRequestSnapshots(sessionId) {
|
|
45
|
+
store.delete(sessionId);
|
|
46
|
+
}
|
|
47
|
+
/** Drop everything (/clear, /new — the CLI is mono-session per process). */
|
|
48
|
+
export function clearAllRequestSnapshots() {
|
|
49
|
+
store.clear();
|
|
50
|
+
}
|
|
51
|
+
/** Test-only reset. */
|
|
52
|
+
export function _resetRequestSnapshotStoreForTests() {
|
|
53
|
+
store.clear();
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=requestSnapshotStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requestSnapshotStore.js","sourceRoot":"","sources":["../../../src/cli/budget/requestSnapshotStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAkBH,MAAM,KAAK,GAAG,IAAI,GAAG,EAAwB,CAAC;AAE9C,4DAA4D;AAC5D,MAAM,UAAU,qBAAqB,CACnC,SAAiB,EACjB,QAA+B;IAE/B,yEAAyE;IACzE,KAAK,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;AACrC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAChC,SAAiB,EACjB,KAAyB;IAEzB,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACnC,IAAI,CAAC,KAAK;QAAE,OAAO;IACnB,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC;AACtB,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,kBAAkB,CAAC,SAAiB;IAClD,OAAO,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,QAAQ,IAAI,IAAI,CAAC;AAChD,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,2BAA2B,CACzC,SAAiB;IAEjB,OAAO,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC;AACtC,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,qBAAqB,CAAC,SAAiB;IACrD,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;AAC1B,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,wBAAwB;IACtC,KAAK,CAAC,KAAK,EAAE,CAAC;AAChB,CAAC;AAED,uBAAuB;AACvB,MAAM,UAAU,kCAAkC;IAChD,KAAK,CAAC,KAAK,EAAE,CAAC;AAChB,CAAC"}
|