claude-code-infinite 1.0.11 → 1.0.16
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 +214 -47
- package/dist/cc-request.d.ts +94 -0
- package/dist/cc-request.d.ts.map +1 -0
- package/dist/cc-request.js +194 -0
- package/dist/cc-request.js.map +1 -0
- package/dist/claude-env.d.ts +30 -0
- package/dist/claude-env.d.ts.map +1 -0
- package/dist/claude-env.js +55 -0
- package/dist/claude-env.js.map +1 -0
- package/dist/cli-args.d.ts +32 -0
- package/dist/cli-args.d.ts.map +1 -0
- package/dist/cli-args.js +72 -0
- package/dist/cli-args.js.map +1 -0
- package/dist/cli-lifecycle.d.ts +17 -0
- package/dist/cli-lifecycle.d.ts.map +1 -0
- package/dist/cli-lifecycle.js +30 -0
- package/dist/cli-lifecycle.js.map +1 -0
- package/dist/cli-main.d.ts +15 -0
- package/dist/cli-main.d.ts.map +1 -0
- package/dist/cli-main.js +464 -0
- package/dist/cli-main.js.map +1 -0
- package/dist/cli.js +13 -178
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +29 -3
- package/dist/config.js.map +1 -1
- package/dist/count-tokens.d.ts +78 -0
- package/dist/count-tokens.d.ts.map +1 -0
- package/dist/count-tokens.js +277 -0
- package/dist/count-tokens.js.map +1 -0
- package/dist/final-index.d.ts +10 -0
- package/dist/final-index.d.ts.map +1 -0
- package/dist/final-index.js +37 -0
- package/dist/final-index.js.map +1 -0
- package/dist/hooks.d.ts +239 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +618 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/memtree-fetch.d.ts +24 -0
- package/dist/memtree-fetch.d.ts.map +1 -0
- package/dist/memtree-fetch.js +121 -0
- package/dist/memtree-fetch.js.map +1 -0
- package/dist/memtree-finder.d.ts +99 -0
- package/dist/memtree-finder.d.ts.map +1 -0
- package/dist/memtree-finder.js +235 -0
- package/dist/memtree-finder.js.map +1 -0
- package/dist/memtree-links.d.ts +16 -0
- package/dist/memtree-links.d.ts.map +1 -0
- package/dist/memtree-links.js +66 -0
- package/dist/memtree-links.js.map +1 -0
- package/dist/memtree-mcp-config.d.ts +28 -0
- package/dist/memtree-mcp-config.d.ts.map +1 -0
- package/dist/memtree-mcp-config.js +121 -0
- package/dist/memtree-mcp-config.js.map +1 -0
- package/dist/memtree-mcp.d.ts +265 -0
- package/dist/memtree-mcp.d.ts.map +1 -0
- package/dist/memtree-mcp.js +667 -0
- package/dist/memtree-mcp.js.map +1 -0
- package/dist/memtree-newest.d.ts +74 -0
- package/dist/memtree-newest.d.ts.map +1 -0
- package/dist/memtree-newest.js +217 -0
- package/dist/memtree-newest.js.map +1 -0
- package/dist/memtree-tools.d.ts +105 -0
- package/dist/memtree-tools.d.ts.map +1 -0
- package/dist/memtree-tools.js +408 -0
- package/dist/memtree-tools.js.map +1 -0
- package/dist/memtree.d.ts +430 -0
- package/dist/memtree.d.ts.map +1 -0
- package/dist/memtree.js +1286 -0
- package/dist/memtree.js.map +1 -0
- package/dist/notices.d.ts +177 -0
- package/dist/notices.d.ts.map +1 -0
- package/dist/notices.js +469 -0
- package/dist/notices.js.map +1 -0
- package/dist/payment-gate.d.ts +53 -0
- package/dist/payment-gate.d.ts.map +1 -0
- package/dist/payment-gate.js +97 -0
- package/dist/payment-gate.js.map +1 -0
- package/dist/project-meta.d.ts +15 -0
- package/dist/project-meta.d.ts.map +1 -0
- package/dist/project-meta.js +80 -0
- package/dist/project-meta.js.map +1 -0
- package/dist/prompt-accounting.d.ts +12 -0
- package/dist/prompt-accounting.d.ts.map +1 -0
- package/dist/prompt-accounting.js +70 -0
- package/dist/prompt-accounting.js.map +1 -0
- package/dist/proxy.d.ts +204 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +4299 -0
- package/dist/proxy.js.map +1 -0
- package/dist/reqlog.d.ts +427 -0
- package/dist/reqlog.d.ts.map +1 -0
- package/dist/reqlog.js +146 -0
- package/dist/reqlog.js.map +1 -0
- package/dist/request-sizing.d.ts +74 -0
- package/dist/request-sizing.d.ts.map +1 -0
- package/dist/request-sizing.js +121 -0
- package/dist/request-sizing.js.map +1 -0
- package/dist/resume-repair-file.d.ts +17 -0
- package/dist/resume-repair-file.d.ts.map +1 -0
- package/dist/resume-repair-file.js +100 -0
- package/dist/resume-repair-file.js.map +1 -0
- package/dist/resume-repair.d.ts +27 -0
- package/dist/resume-repair.d.ts.map +1 -0
- package/dist/resume-repair.js +198 -0
- package/dist/resume-repair.js.map +1 -0
- package/dist/route-cache.d.ts +5 -0
- package/dist/route-cache.d.ts.map +1 -0
- package/dist/route-cache.js +63 -0
- package/dist/route-cache.js.map +1 -0
- package/dist/route-fallback.d.ts +67 -0
- package/dist/route-fallback.d.ts.map +1 -0
- package/dist/route-fallback.js +126 -0
- package/dist/route-fallback.js.map +1 -0
- package/dist/route-identity.d.ts +6 -0
- package/dist/route-identity.d.ts.map +1 -0
- package/dist/route-identity.js +100 -0
- package/dist/route-identity.js.map +1 -0
- package/dist/transcript-usage.d.ts +57 -0
- package/dist/transcript-usage.d.ts.map +1 -0
- package/dist/transcript-usage.js +570 -0
- package/dist/transcript-usage.js.map +1 -0
- package/dist/turns.d.ts +77 -0
- package/dist/turns.d.ts.map +1 -0
- package/dist/turns.js +330 -0
- package/dist/turns.js.map +1 -0
- package/dist/update-check.d.ts +38 -0
- package/dist/update-check.d.ts.map +1 -0
- package/dist/update-check.js +67 -0
- package/dist/update-check.js.map +1 -0
- package/package.json +6 -2
package/dist/reqlog.d.ts
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Always-on request/timing log (append-only JSONL) for post-hoc incident
|
|
3
|
+
* reconstruction — when a turn stalls for minutes we want a client-side
|
|
4
|
+
* record without asking the user to relaunch with --debug.
|
|
5
|
+
*
|
|
6
|
+
* Hard constraints:
|
|
7
|
+
* - NEVER throw and NEVER block the proxy path: every filesystem touch is
|
|
8
|
+
* wrapped in try/catch and writes are fire-and-forget (async appendFile
|
|
9
|
+
* with an ignored error callback). A broken log path degrades to silence.
|
|
10
|
+
* - Append-only single file at ~/.claude-code-infinite/logs/requests.jsonl,
|
|
11
|
+
* with one rotation slot: if the file exceeds ~20MB at proxy startup it is
|
|
12
|
+
* renamed to requests.jsonl.1 (overwriting any previous .1).
|
|
13
|
+
*
|
|
14
|
+
* One JSON object per line. `ts` (ISO 8601) and `pid` (the ccc process, which
|
|
15
|
+
* tells apart concurrent ccc sessions sharing this file) are stamped here so
|
|
16
|
+
* callers only supply event fields. Token counts under `approxInputTokens` are a rough
|
|
17
|
+
* bytes/4 chars→tokens proxy, NOT real tokenizer output — exact usage, when
|
|
18
|
+
* the response format lets us extract it cheaply, lands under `usage`.
|
|
19
|
+
*/
|
|
20
|
+
import type { ClaudeCodeRequestInfo, TranscriptShape } from "./cc-request.js";
|
|
21
|
+
/** Rough bytes→tokens estimate (bytes/4). Label the result approximate. */
|
|
22
|
+
export declare function approxTokensFromBytes(bytes: number): number;
|
|
23
|
+
export declare function defaultLogPath(): string;
|
|
24
|
+
export type TurnType = "first-user" | "tool"
|
|
25
|
+
/** A tool turn sent on its lane's memory route (compressed prefix + suffix). */
|
|
26
|
+
| "tool-memory"
|
|
27
|
+
/**
|
|
28
|
+
* A main-thread tool turn sent on the session's stable prefix directly (no
|
|
29
|
+
* lane route to ride, e.g. after a tool-turn compaction made while a typed
|
|
30
|
+
* prompt was pending): the prefix bytes, then every later message verbatim.
|
|
31
|
+
*/
|
|
32
|
+
| "tool-prefix"
|
|
33
|
+
/**
|
|
34
|
+
* A tool turn that compressed: its estimated size reached the budget (see
|
|
35
|
+
* `compaction`, reason "budget"), or its lane's route no longer fitted the
|
|
36
|
+
* context window. Validated compressed bytes were sent to Anthropic; on the
|
|
37
|
+
* main thread the result is also the session's new stable prefix.
|
|
38
|
+
*/
|
|
39
|
+
| "tool-recompressed" | "followup-compressed"
|
|
40
|
+
/**
|
|
41
|
+
* A main-thread human turn sent on the session's stable compressed prefix:
|
|
42
|
+
* the prefix bytes stored at the last compaction, then every message after
|
|
43
|
+
* the part it covers, verbatim. No compress call (the history still goes
|
|
44
|
+
* to MemTree as a background index_only call), and the prefix is a prompt
|
|
45
|
+
* cache read. See `compaction` for the size check that allowed it.
|
|
46
|
+
*/
|
|
47
|
+
| "followup-prefix" | "followup-noop"
|
|
48
|
+
/** Indexed response that carried no prior conversation; history forwarded. */
|
|
49
|
+
| "followup-empty-memory"
|
|
50
|
+
/**
|
|
51
|
+
* Compressed result carried no server `flattened_messages` (pre-flatten
|
|
52
|
+
* server or malformed field); history forwarded.
|
|
53
|
+
*/
|
|
54
|
+
| "followup-no-flatten" | "followup-degraded"
|
|
55
|
+
/**
|
|
56
|
+
* A fork of the main conversation (the away recap) rode the main thread's
|
|
57
|
+
* last compressed prefix: same bytes the main thread sent, so Anthropic's
|
|
58
|
+
* prompt cache hits, and no MemTree call was made.
|
|
59
|
+
*/
|
|
60
|
+
| "fork-memory"
|
|
61
|
+
/**
|
|
62
|
+
* Claude Code's own side request (the security monitor): forwarded
|
|
63
|
+
* untouched, no MemTree call, main-thread route state left alone.
|
|
64
|
+
*/
|
|
65
|
+
| "side-request"
|
|
66
|
+
/**
|
|
67
|
+
* Not from Claude Code (no X-Claude-Code-Session-Id): another program that
|
|
68
|
+
* inherited ccc's ANTHROPIC_BASE_URL. Forwarded untouched, no MemTree.
|
|
69
|
+
*/
|
|
70
|
+
| "foreign" | "followup-client-closed" | "unparseable";
|
|
71
|
+
/** Real token usage parsed from Anthropic's response, when available. */
|
|
72
|
+
export interface UsageRecord {
|
|
73
|
+
input_tokens?: number;
|
|
74
|
+
cache_read_input_tokens?: number;
|
|
75
|
+
cache_creation_input_tokens?: number;
|
|
76
|
+
output_tokens?: number;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* One /v1/messages request through the proxy. Built up mutably as the request
|
|
80
|
+
* flows through the forward path (the forwarders fill in upstream timing) and
|
|
81
|
+
* written once when the response finishes. Fields that a given path can't
|
|
82
|
+
* derive cheaply are simply omitted.
|
|
83
|
+
*/
|
|
84
|
+
/** One actual Count Tokens HTTP attempt; never includes body, credentials or token counts. */
|
|
85
|
+
export interface CountTokensRecord {
|
|
86
|
+
statusClass: string;
|
|
87
|
+
ms: number;
|
|
88
|
+
}
|
|
89
|
+
export interface MessagesRecord {
|
|
90
|
+
kind: "messages";
|
|
91
|
+
turnType: TurnType;
|
|
92
|
+
/** Body bytes as received from Claude Code. */
|
|
93
|
+
requestBytes: number;
|
|
94
|
+
model?: string;
|
|
95
|
+
stream?: boolean;
|
|
96
|
+
/** For a foreign request: the opening of its User-Agent, to name the program. */
|
|
97
|
+
userAgent?: string;
|
|
98
|
+
/**
|
|
99
|
+
* What Claude Code's billing header says about this request (see
|
|
100
|
+
* cc-request.ts). Logged to measure which request kinds lack
|
|
101
|
+
* `cc_turn_origin` before any of them are kept away from MemTree.
|
|
102
|
+
*/
|
|
103
|
+
client?: ClaudeCodeRequestInfo;
|
|
104
|
+
/**
|
|
105
|
+
* For a request shaped like Claude Code's security monitor (billing header,
|
|
106
|
+
* no turn origin, no tools): whether its `<transcript>` block has the known
|
|
107
|
+
* format. `ok: false` means it was handled as an ordinary request, and is
|
|
108
|
+
* the signal that Claude Code changed the format.
|
|
109
|
+
*/
|
|
110
|
+
transcript?: TranscriptShape;
|
|
111
|
+
/** The request's max_tokens (output reservation counted against the window). */
|
|
112
|
+
maxTokens?: number;
|
|
113
|
+
/**
|
|
114
|
+
* The compress result showed MemTree's index trailing the conversation
|
|
115
|
+
* (indexLagEvidence: the server's index_pending, or a cold pass-through with
|
|
116
|
+
* its page). If the request then cannot fit, the refusal waits for indexing.
|
|
117
|
+
*/
|
|
118
|
+
indexBehind?: true;
|
|
119
|
+
/** Body bytes actually sent to Anthropic (after notice strip / compression). */
|
|
120
|
+
forwardedBytes?: number;
|
|
121
|
+
/** forwardedBytes/4 — rough chars→tokens proxy, not a tokenizer count. */
|
|
122
|
+
approxInputTokens?: number;
|
|
123
|
+
/** Present when a blocking MemTree compress was attempted for this turn. */
|
|
124
|
+
compress?: {
|
|
125
|
+
/** Overall wall time of the compress step (all concurrent legs). */
|
|
126
|
+
ms: number;
|
|
127
|
+
/**
|
|
128
|
+
* The compress call returned a result. NOT a
|
|
129
|
+
* "forwarded compressed history" flag: the proxy can still discard the
|
|
130
|
+
* result afterwards (no-op response, or indexed-but-empty memory) and
|
|
131
|
+
* forward the full history — `turnType` (followup-noop /
|
|
132
|
+
* followup-empty-memory vs followup-compressed) and `history.usable`
|
|
133
|
+
* record what actually happened.
|
|
134
|
+
*/
|
|
135
|
+
ok: boolean;
|
|
136
|
+
/**
|
|
137
|
+
* The compress call consumed (roughly) the whole compress budget and
|
|
138
|
+
* returned nothing.
|
|
139
|
+
*/
|
|
140
|
+
timedOut: boolean;
|
|
141
|
+
};
|
|
142
|
+
/**
|
|
143
|
+
* How much conversation the compressed response actually carried. Recorded
|
|
144
|
+
* separately from `compress.ok` because a fully indexed response can still
|
|
145
|
+
* return an empty conversation; this is the field that shows it.
|
|
146
|
+
*/
|
|
147
|
+
history?: {
|
|
148
|
+
retainedChars: number;
|
|
149
|
+
priorHistoryChars: number;
|
|
150
|
+
usable: boolean;
|
|
151
|
+
};
|
|
152
|
+
/**
|
|
153
|
+
* Which memory-route lane this request keys to: the away-summary side
|
|
154
|
+
* channel, a request carrying agent attribution, or the main thread. This
|
|
155
|
+
* is what splits verbatim `tool` rows into main-miss vs subagent traffic
|
|
156
|
+
* when evaluating recovery behaviour after the fact.
|
|
157
|
+
*/
|
|
158
|
+
routeLane?: "main" | "away" | "agent";
|
|
159
|
+
/**
|
|
160
|
+
* Tool turns that missed their lane's route: "missing" (empty lane),
|
|
161
|
+
* "rejected" (a route was present in this lane but unusable for this
|
|
162
|
+
* request), or "replay" (the body was an exact replay of the request that
|
|
163
|
+
* installed the route — a client retry after a pre-flush socket death —
|
|
164
|
+
* forwarded verbatim with the route retained and no recovery attempt).
|
|
165
|
+
* "rejected" covers every OTHER reason memoryRoutedToolBody refuses:
|
|
166
|
+
* a changed system/prefix hash, a conversation that shrank below the
|
|
167
|
+
* stored prefix, an unexpected tool suffix shape, or an assembled route
|
|
168
|
+
* estimated to exceed the destination context window. (An epoch-stale
|
|
169
|
+
* route never reaches rejection: getMemoryRoute drops it on lookup, so
|
|
170
|
+
* that case logs "missing".) "superseded": a main-thread route built on
|
|
171
|
+
* an older stable prefix than the session's current one (a later tool-turn
|
|
172
|
+
* compaction replaced the prefix without owning the route); dropped, and
|
|
173
|
+
* the turn rides the current prefix instead.
|
|
174
|
+
* Absent means there was no applicable miss — hits and away-summary turns
|
|
175
|
+
* never emit it. "none" is deliberately not a value.
|
|
176
|
+
*/
|
|
177
|
+
routeMiss?: "missing" | "rejected" | "replay" | "superseded";
|
|
178
|
+
/**
|
|
179
|
+
* The request's last conversation message was neither a user prompt nor a
|
|
180
|
+
* tool result: e.g. a background task notification, which Claude Code
|
|
181
|
+
* sends as a trailing role=system block after the assistant's last reply.
|
|
182
|
+
* Handled like a tool turn (stable-prefix ride, budget compaction) without
|
|
183
|
+
* consulting the lane's route. Absent on every other request.
|
|
184
|
+
*/
|
|
185
|
+
continuation?: true;
|
|
186
|
+
/**
|
|
187
|
+
* Why a fork of the main conversation (away recap) did not ride the main
|
|
188
|
+
* thread's last prefix and fell back to its own compression; absent when it
|
|
189
|
+
* rode (turnType "fork-memory") or was not a fork.
|
|
190
|
+
*/
|
|
191
|
+
forkMiss?: "no-route" | "session" | "system" | "prefix" | "too-large";
|
|
192
|
+
/**
|
|
193
|
+
* Stable-prefix (edge) compaction: the budget the turn was measured
|
|
194
|
+
* against, the size estimate, and, when it compacted, why. Recorded on
|
|
195
|
+
* main-thread human turns and on every lane's tool turns (a tool turn under
|
|
196
|
+
* the budget forwards with no compress call; at the budget it compresses
|
|
197
|
+
* once, reason "budget"). Absent on other requests, and on tool turns under
|
|
198
|
+
* the CCC_TOOL_ROUTE_RECOVERY=0 kill switch.
|
|
199
|
+
*/
|
|
200
|
+
compaction?: CompactionRecord;
|
|
201
|
+
/** Every actual counting attempt, in order; cached results add no entry. */
|
|
202
|
+
countTokens?: CountTokensRecord[];
|
|
203
|
+
/** One bounded compression after an assembled ride was counted above the soft budget. */
|
|
204
|
+
lateCountRecovery?: {
|
|
205
|
+
outcome: "compressed" | "forwarded" | "refused";
|
|
206
|
+
};
|
|
207
|
+
/** Searchable flag explaining a forward above the advisory compression budget. */
|
|
208
|
+
overBudgetForward?: {
|
|
209
|
+
reason: "exact-count-fits-window" | "estimate-fits-window" | "bytes-fallback-fits-window";
|
|
210
|
+
};
|
|
211
|
+
/**
|
|
212
|
+
* Outcome of a tool-turn compaction attempt (a tool turn whose estimated
|
|
213
|
+
* size reached the budget), or why none was made although one was due.
|
|
214
|
+
* Absent on tool turns under the budget: they forward with no attempt.
|
|
215
|
+
*/
|
|
216
|
+
routeRecovery?: {
|
|
217
|
+
/**
|
|
218
|
+
* Serialized non-system conversation bytes, measured once per attempt.
|
|
219
|
+
* Absent when no attempt was made (kill switch, cooldown, in-flight,
|
|
220
|
+
* backoff, awaiting-index).
|
|
221
|
+
*/
|
|
222
|
+
conversationBytes?: number;
|
|
223
|
+
outcome: "compressed" | "failed"
|
|
224
|
+
/** The server returned the conversation uncompressed (index warming). */
|
|
225
|
+
| "noop" | "unusable"
|
|
226
|
+
/**
|
|
227
|
+
* Compressed result carried no server `flattened_messages`
|
|
228
|
+
* (pre-flatten server or malformed field). Forwarded the original.
|
|
229
|
+
*/
|
|
230
|
+
| "no-flatten" | "no-gain" | "client-closed"
|
|
231
|
+
/**
|
|
232
|
+
* CCC_TOOL_ROUTE_RECOVERY=0: a route miss that got no size check and
|
|
233
|
+
* no attempt.
|
|
234
|
+
*/
|
|
235
|
+
| "disabled"
|
|
236
|
+
/** A recent attempt returned null; the blocking wait was skipped. */
|
|
237
|
+
| "cooldown"
|
|
238
|
+
/** Another attempt for this lane is still in flight. */
|
|
239
|
+
| "in-flight"
|
|
240
|
+
/**
|
|
241
|
+
* This lane's last attempt this human turn produced no prefix to ride
|
|
242
|
+
* (no-op, no gain, failure, a forward that installed nothing, or a
|
|
243
|
+
* result still over the window from a complete index); the next one
|
|
244
|
+
* waits until the conversation has grown by a twentieth of the budget, so a lane that
|
|
245
|
+
* cannot compress does not pay a blocking call on every tool turn.
|
|
246
|
+
*/
|
|
247
|
+
| "backoff"
|
|
248
|
+
/**
|
|
249
|
+
* This lane's last attempt found no finished tree for the conversation
|
|
250
|
+
* (no-op, nothing indexed), so a compress call could only pass it
|
|
251
|
+
* through again. None is made until that attempt's MemTree page
|
|
252
|
+
* reports built (checked in the background, one fetch at a time).
|
|
253
|
+
*/
|
|
254
|
+
| "awaiting-index"
|
|
255
|
+
/**
|
|
256
|
+
* The compressed result could not be serialized (e.g. V8 string-length
|
|
257
|
+
* limit on a multi-megabyte body). Forwarded the original.
|
|
258
|
+
*/
|
|
259
|
+
| "build-failed";
|
|
260
|
+
/**
|
|
261
|
+
* Main-thread compactions with a session: whether the result became the
|
|
262
|
+
* session's stable prefix ("not-installed": the forward never completed,
|
|
263
|
+
* or a newer prefix replaced the one this compaction started from).
|
|
264
|
+
*/
|
|
265
|
+
prefix?: "installed" | "not-installed";
|
|
266
|
+
/** A no-op that found no tree: the lane now waits for one ("awaiting-index"). */
|
|
267
|
+
awaitingIndex?: boolean;
|
|
268
|
+
/** Route candidate fate; only "compressed" outcomes carry it. */
|
|
269
|
+
install?: "installed" | "stale" | "prompt-pending" | "no-session"
|
|
270
|
+
/**
|
|
271
|
+
* The recovered forward never reached protocol-complete and the
|
|
272
|
+
* downstream client had NOT aborted. Typically an upstream 5xx/529
|
|
273
|
+
* or a truncated upstream stream, but any non-2xx of the compressed
|
|
274
|
+
* forward lands here — including a plain 4xx, which is deliberately
|
|
275
|
+
* non-arming for the fuse — as does proxy-shutdown teardown of an
|
|
276
|
+
* in-flight recovery. Lifts the lane's backoff so the identical-body
|
|
277
|
+
* retry compresses again.
|
|
278
|
+
*/
|
|
279
|
+
| "upstream-failed"
|
|
280
|
+
/**
|
|
281
|
+
* The upstream served the turn to protocol-complete but route
|
|
282
|
+
* bookkeeping threw at an activation attempt (the protocol-complete
|
|
283
|
+
* attempt, the delivered-settle retry, or both), so no route exists.
|
|
284
|
+
* Releases the lane's reservation but keeps its backoff. In the common
|
|
285
|
+
* sub-case the client got its complete answer — a fast-tool abort
|
|
286
|
+
* after message_stop lands here, not in "client-aborted" — so no
|
|
287
|
+
* identical-body retry is coming, and the throw is not
|
|
288
|
+
* upstream-health evidence. Rarer closes land here too: a socket
|
|
289
|
+
* that dies before the accepted message_stop bytes flush (which
|
|
290
|
+
* DOES retry the identical body) and an upstream-owned error
|
|
291
|
+
* arriving after the data chunk that carried message_stop. A retry
|
|
292
|
+
* finds its lane backed off and forwards uncompressed until the
|
|
293
|
+
* history grows past the retry size or the next human turn — a
|
|
294
|
+
* bounded degradation accepted because the closes are
|
|
295
|
+
* indistinguishable at settle time and lifting the backoff would
|
|
296
|
+
* fund one blocking recompress per tool turn under a deterministic
|
|
297
|
+
* activation throw.
|
|
298
|
+
*/
|
|
299
|
+
| "activation-error"
|
|
300
|
+
/**
|
|
301
|
+
* The downstream client aborted mid-stream before protocol-complete.
|
|
302
|
+
* Lifts the backoff exactly like upstream-failed (no route exists
|
|
303
|
+
* and the client's identical-body retry is imminent) but is split
|
|
304
|
+
* out so attempt-rate tripwires can tell client behavior from
|
|
305
|
+
* upstream health.
|
|
306
|
+
*/
|
|
307
|
+
| "client-aborted";
|
|
308
|
+
};
|
|
309
|
+
upstreamStatus?: number;
|
|
310
|
+
/** The downstream client aborted before the response finished. */
|
|
311
|
+
clientAborted?: true;
|
|
312
|
+
/** Forward start → first response byte from Anthropic. */
|
|
313
|
+
ttfbMs?: number;
|
|
314
|
+
/** Forward start → first upstream content_block_delta (SSE only). */
|
|
315
|
+
firstContentMs?: number;
|
|
316
|
+
/** Request received → response fully sent to Claude Code. */
|
|
317
|
+
totalMs?: number;
|
|
318
|
+
/** Legacy schema field; live response fabrication is disabled. */
|
|
319
|
+
preludeFired?: boolean;
|
|
320
|
+
usage?: UsageRecord;
|
|
321
|
+
}
|
|
322
|
+
/** Why a turn compressed instead of riding its prefix (or passing through). */
|
|
323
|
+
export type RecompressReason =
|
|
324
|
+
/** The prefix plus the turns after it reached the budget. */
|
|
325
|
+
"budget"
|
|
326
|
+
/** The messages the prefix covers changed: rewind, edit, fork, /clear. */
|
|
327
|
+
| "prefix-mismatch"
|
|
328
|
+
/** `/memtree-compact [N]` asked for a compaction now. */
|
|
329
|
+
| "manual"
|
|
330
|
+
/** The session's target or context window changed since the prefix was built. */
|
|
331
|
+
| "target-change";
|
|
332
|
+
export interface CompactionRecord {
|
|
333
|
+
/** "off" after `/memtree-compact off`; "explicit" for an N or CCC_COMPACT_TARGET. */
|
|
334
|
+
mode: "auto" | "explicit" | "off";
|
|
335
|
+
/** The whole-request budget the turn was measured against. */
|
|
336
|
+
budgetTokens: number;
|
|
337
|
+
/**
|
|
338
|
+
* Where the budget came from: CCC_BUDGET_TOKENS ("override"), the
|
|
339
|
+
* server's `model_budget_tokens` ("server"), or the model's context window
|
|
340
|
+
* times FALLBACK_BUDGET_WINDOW_RATIO until the server reports one.
|
|
341
|
+
*/
|
|
342
|
+
budgetSource: "override" | "server" | "window-ratio";
|
|
343
|
+
/** What a compaction on this turn aims at (budget/2, or the explicit N). */
|
|
344
|
+
targetTokens?: number;
|
|
345
|
+
/** Sent as compression_threshold_tokens (servers that report a budget). */
|
|
346
|
+
thresholdTokens?: number;
|
|
347
|
+
/** Size estimate of what would be sent (prefix ride or full history). */
|
|
348
|
+
estimatedTokens?: number;
|
|
349
|
+
/** Bytes of the request underlying estimatedTokens, including a reused prefix. */
|
|
350
|
+
estimatedBytes?: number;
|
|
351
|
+
/**
|
|
352
|
+
* "reported": Anthropic's input+cache_read+cache_creation for the previous
|
|
353
|
+
* request of the same shape, plus bytes/4 for what was added since.
|
|
354
|
+
* "bytes": bytes/4 of the whole body (no earlier usage to start from).
|
|
355
|
+
*/
|
|
356
|
+
sizeSource?: "reported" | "bytes";
|
|
357
|
+
/** Set when the turn compressed (or tried to) for one of these reasons. */
|
|
358
|
+
reason?: RecompressReason;
|
|
359
|
+
/** For "prefix-mismatch": which check failed. */
|
|
360
|
+
prefixMiss?: "session" | "system" | "prefix";
|
|
361
|
+
/**
|
|
362
|
+
* A compaction was attempted but did not produce a new prefix (MemTree
|
|
363
|
+
* down, no-op, unusable) and the turn rode the old prefix instead.
|
|
364
|
+
*/
|
|
365
|
+
keptPrefix?: true;
|
|
366
|
+
}
|
|
367
|
+
/** One MemTree API call (blocking compress or background index). */
|
|
368
|
+
export interface MemtreeRecord {
|
|
369
|
+
kind: "memtree";
|
|
370
|
+
indexOnly: boolean;
|
|
371
|
+
ms: number;
|
|
372
|
+
ok: boolean;
|
|
373
|
+
/** HTTP status; absent when the call died before a response (network/timeout). */
|
|
374
|
+
status?: number;
|
|
375
|
+
requestBytes: number;
|
|
376
|
+
/** Model sent for server-side budget resolution (compression calls only). */
|
|
377
|
+
model?: string;
|
|
378
|
+
/**
|
|
379
|
+
* Response diagnostics, present on successful calls that reported usage.
|
|
380
|
+
* `indexedTokens` (cached_tokens) is the prompt coverage of the index and
|
|
381
|
+
* drives the success notice: flat coverage across turns means MemTree
|
|
382
|
+
* indexed nothing new. `memoryChars` moves independently of it because the
|
|
383
|
+
* index is unfolded per question.
|
|
384
|
+
*/
|
|
385
|
+
indexedTokens?: number;
|
|
386
|
+
rawPromptTokens?: number;
|
|
387
|
+
memoryChars?: number;
|
|
388
|
+
/** The per-request MemTree page the server stamped on the response, if any. */
|
|
389
|
+
memtreeUrl?: string;
|
|
390
|
+
/** The completed index the turn was compressed against, if the server said. */
|
|
391
|
+
memtreeIndex?: string;
|
|
392
|
+
/** The server's model budget (`model_budget_tokens`), when it reported one. */
|
|
393
|
+
modelBudgetTokens?: number;
|
|
394
|
+
}
|
|
395
|
+
/** A display-only notice was atomically claimed by one Claude Code hook. */
|
|
396
|
+
export interface NoticeRecord {
|
|
397
|
+
kind: "notice";
|
|
398
|
+
event: "claimed";
|
|
399
|
+
via: "MessageDisplay" | "Stop";
|
|
400
|
+
}
|
|
401
|
+
export type RequestRecord = MessagesRecord | MemtreeRecord | NoticeRecord;
|
|
402
|
+
/** Structural logging seam retained for embedders and focused tests. */
|
|
403
|
+
export interface RequestLogSink {
|
|
404
|
+
log(record: RequestRecord): void;
|
|
405
|
+
}
|
|
406
|
+
export declare class RequestLogger implements RequestLogSink {
|
|
407
|
+
readonly path: string;
|
|
408
|
+
private readonly pendingWrites;
|
|
409
|
+
constructor(filePath?: string);
|
|
410
|
+
/** Fire-and-forget append of one JSONL line. Never throws, never blocks. */
|
|
411
|
+
log(record: RequestRecord): void;
|
|
412
|
+
/**
|
|
413
|
+
* Shutdown-only durability seam. Wait for already-scheduled appends without
|
|
414
|
+
* making normal proxy logging synchronous; false means the bounded wait
|
|
415
|
+
* expired. Callers should stop request producers before invoking this.
|
|
416
|
+
*/
|
|
417
|
+
flush(timeoutMs?: number): Promise<boolean>;
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Merge token usage out of a parsed SSE event into a record. Anthropic sends
|
|
421
|
+
* input/cache counts on message_start and the final output count (plus
|
|
422
|
+
* occasionally refreshed input counts) on message_delta.
|
|
423
|
+
*/
|
|
424
|
+
export declare function mergeUsageFromSseEvent(data: any, rec: MessagesRecord): void;
|
|
425
|
+
/** Merge usage from a non-streaming /v1/messages JSON response body. */
|
|
426
|
+
export declare function mergeUsageFromJsonBody(body: Buffer, rec: MessagesRecord): void;
|
|
427
|
+
//# sourceMappingURL=reqlog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reqlog.d.ts","sourceRoot":"","sources":["../src/reqlog.ts"],"names":[],"mappings":"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"}
|
package/dist/reqlog.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Always-on request/timing log (append-only JSONL) for post-hoc incident
|
|
3
|
+
* reconstruction — when a turn stalls for minutes we want a client-side
|
|
4
|
+
* record without asking the user to relaunch with --debug.
|
|
5
|
+
*
|
|
6
|
+
* Hard constraints:
|
|
7
|
+
* - NEVER throw and NEVER block the proxy path: every filesystem touch is
|
|
8
|
+
* wrapped in try/catch and writes are fire-and-forget (async appendFile
|
|
9
|
+
* with an ignored error callback). A broken log path degrades to silence.
|
|
10
|
+
* - Append-only single file at ~/.claude-code-infinite/logs/requests.jsonl,
|
|
11
|
+
* with one rotation slot: if the file exceeds ~20MB at proxy startup it is
|
|
12
|
+
* renamed to requests.jsonl.1 (overwriting any previous .1).
|
|
13
|
+
*
|
|
14
|
+
* One JSON object per line. `ts` (ISO 8601) and `pid` (the ccc process, which
|
|
15
|
+
* tells apart concurrent ccc sessions sharing this file) are stamped here so
|
|
16
|
+
* callers only supply event fields. Token counts under `approxInputTokens` are a rough
|
|
17
|
+
* bytes/4 chars→tokens proxy, NOT real tokenizer output — exact usage, when
|
|
18
|
+
* the response format lets us extract it cheaply, lands under `usage`.
|
|
19
|
+
*/
|
|
20
|
+
import { appendFile, mkdirSync, renameSync, statSync } from "node:fs";
|
|
21
|
+
import { dirname, join } from "node:path";
|
|
22
|
+
import { getConfigDir } from "./config.js";
|
|
23
|
+
const MAX_LOG_BYTES = 20 * 1024 * 1024; // rotate above ~20MB at startup
|
|
24
|
+
const APPROX_CHARS_PER_TOKEN = 4;
|
|
25
|
+
/** Rough bytes→tokens estimate (bytes/4). Label the result approximate. */
|
|
26
|
+
export function approxTokensFromBytes(bytes) {
|
|
27
|
+
return Math.round(bytes / APPROX_CHARS_PER_TOKEN);
|
|
28
|
+
}
|
|
29
|
+
export function defaultLogPath() {
|
|
30
|
+
return join(getConfigDir(), "logs", "requests.jsonl");
|
|
31
|
+
}
|
|
32
|
+
export class RequestLogger {
|
|
33
|
+
path;
|
|
34
|
+
pendingWrites = new Set();
|
|
35
|
+
constructor(filePath) {
|
|
36
|
+
this.path = filePath ?? defaultLogPath();
|
|
37
|
+
try {
|
|
38
|
+
mkdirSync(dirname(this.path), { recursive: true });
|
|
39
|
+
rotateIfLarge(this.path);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Unwritable/odd log location: run silent, never break startup.
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Fire-and-forget append of one JSONL line. Never throws, never blocks. */
|
|
46
|
+
log(record) {
|
|
47
|
+
try {
|
|
48
|
+
const line = JSON.stringify({ ts: new Date().toISOString(), pid: process.pid, ...record }) + "\n";
|
|
49
|
+
let finish;
|
|
50
|
+
const pending = new Promise((resolve) => {
|
|
51
|
+
finish = resolve;
|
|
52
|
+
});
|
|
53
|
+
const settle = () => {
|
|
54
|
+
this.pendingWrites.delete(pending);
|
|
55
|
+
finish();
|
|
56
|
+
};
|
|
57
|
+
this.pendingWrites.add(pending);
|
|
58
|
+
try {
|
|
59
|
+
appendFile(this.path, line, settle);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
settle();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// Serialization/scheduling failure — same policy.
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Shutdown-only durability seam. Wait for already-scheduled appends without
|
|
71
|
+
* making normal proxy logging synchronous; false means the bounded wait
|
|
72
|
+
* expired. Callers should stop request producers before invoking this.
|
|
73
|
+
*/
|
|
74
|
+
async flush(timeoutMs = 2_000) {
|
|
75
|
+
const boundedMs = Number.isFinite(timeoutMs) && timeoutMs >= 0
|
|
76
|
+
? Math.floor(timeoutMs)
|
|
77
|
+
: 2_000;
|
|
78
|
+
const deadline = Date.now() + boundedMs;
|
|
79
|
+
while (this.pendingWrites.size > 0) {
|
|
80
|
+
const remaining = deadline - Date.now();
|
|
81
|
+
if (remaining <= 0)
|
|
82
|
+
return false;
|
|
83
|
+
let timer;
|
|
84
|
+
const completed = await Promise.race([
|
|
85
|
+
Promise.allSettled([...this.pendingWrites]).then(() => true),
|
|
86
|
+
new Promise((resolve) => {
|
|
87
|
+
timer = setTimeout(() => resolve(false), remaining);
|
|
88
|
+
}),
|
|
89
|
+
]);
|
|
90
|
+
if (timer)
|
|
91
|
+
clearTimeout(timer);
|
|
92
|
+
if (!completed)
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Merge token usage out of a parsed SSE event into a record. Anthropic sends
|
|
100
|
+
* input/cache counts on message_start and the final output count (plus
|
|
101
|
+
* occasionally refreshed input counts) on message_delta.
|
|
102
|
+
*/
|
|
103
|
+
export function mergeUsageFromSseEvent(data, rec) {
|
|
104
|
+
const type = data?.type;
|
|
105
|
+
if (type === "message_start")
|
|
106
|
+
mergeUsage(data?.message?.usage, rec);
|
|
107
|
+
else if (type === "message_delta")
|
|
108
|
+
mergeUsage(data?.usage, rec);
|
|
109
|
+
}
|
|
110
|
+
/** Merge usage from a non-streaming /v1/messages JSON response body. */
|
|
111
|
+
export function mergeUsageFromJsonBody(body, rec) {
|
|
112
|
+
try {
|
|
113
|
+
mergeUsage(JSON.parse(body.toString("utf-8"))?.usage, rec);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// Not the shape we expected — usage just stays absent.
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const USAGE_FIELDS = [
|
|
120
|
+
"input_tokens",
|
|
121
|
+
"cache_read_input_tokens",
|
|
122
|
+
"cache_creation_input_tokens",
|
|
123
|
+
"output_tokens",
|
|
124
|
+
];
|
|
125
|
+
function mergeUsage(usage, rec) {
|
|
126
|
+
if (!usage || typeof usage !== "object")
|
|
127
|
+
return;
|
|
128
|
+
for (const field of USAGE_FIELDS) {
|
|
129
|
+
const value = usage[field];
|
|
130
|
+
if (typeof value !== "number")
|
|
131
|
+
continue;
|
|
132
|
+
rec.usage = rec.usage ?? {};
|
|
133
|
+
rec.usage[field] = value;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/** Startup size protection: one rotation slot, old .1 is overwritten. */
|
|
137
|
+
function rotateIfLarge(path) {
|
|
138
|
+
try {
|
|
139
|
+
if (statSync(path).size > MAX_LOG_BYTES)
|
|
140
|
+
renameSync(path, `${path}.1`);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
// Missing file (fresh install) or unreadable — nothing to rotate.
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=reqlog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reqlog.js","sourceRoot":"","sources":["../src/reqlog.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { type CountTokensSession, type CountTokensResult, type SizeSample } from "./count-tokens.js";
|
|
2
|
+
/** Output room kept free on models that accept input + max_tokens over the
|
|
3
|
+
* window, so the model can still answer a request that barely fits. */
|
|
4
|
+
export declare const OUTPUT_FLOOR_TOKENS = 16000;
|
|
5
|
+
export type CandidateKind = "original" | "ride" | "replacement";
|
|
6
|
+
export interface SizingLimits {
|
|
7
|
+
budget: number;
|
|
8
|
+
context: number;
|
|
9
|
+
/** Output room an exact count must leave (see outputReservation). */
|
|
10
|
+
output: number;
|
|
11
|
+
/** Full max_tokens, reserved for every estimate: an estimate that would not
|
|
12
|
+
* fit with it is counted before delivery, and only an exact count may use
|
|
13
|
+
* the relaxed `output` floor. */
|
|
14
|
+
recountOutput?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface PlannedSize {
|
|
17
|
+
tokens: number;
|
|
18
|
+
source: "reported" | "bytes";
|
|
19
|
+
}
|
|
20
|
+
export interface CandidateMeasurement {
|
|
21
|
+
tokens: number;
|
|
22
|
+
source: "exact" | "reported" | "bytes";
|
|
23
|
+
/** Advisory estimate retained for compression and stale-estimate diagnostics. */
|
|
24
|
+
estimatedTokens: number;
|
|
25
|
+
count?: CountTokensResult;
|
|
26
|
+
}
|
|
27
|
+
export interface RequestCandidate {
|
|
28
|
+
readonly key: string;
|
|
29
|
+
/** Owned copy: callers must not mutate these bytes. */
|
|
30
|
+
readonly body: Buffer;
|
|
31
|
+
readonly kind: CandidateKind;
|
|
32
|
+
readonly sample?: Readonly<SizeSample>;
|
|
33
|
+
readonly estimate: Readonly<PlannedSize>;
|
|
34
|
+
}
|
|
35
|
+
export declare class RequestSizing {
|
|
36
|
+
private readonly counts;
|
|
37
|
+
private readonly enabled;
|
|
38
|
+
private readonly candidates;
|
|
39
|
+
private readonly excludedFallbacks;
|
|
40
|
+
constructor(counts: CountTokensSession, enabled: boolean);
|
|
41
|
+
/** The first registration fixes both bytes and learned sample for this request. */
|
|
42
|
+
register(body: Buffer, kind: CandidateKind, sample?: SizeSample): RequestCandidate;
|
|
43
|
+
/** Eligibility belongs to route planning, not to a later byte-size guess. */
|
|
44
|
+
excludeFallback(candidate: RequestCandidate): void;
|
|
45
|
+
/** Planning and metadata consult the same exact count, else the snapshotted estimate. */
|
|
46
|
+
plan(candidate: RequestCandidate): PlannedSize;
|
|
47
|
+
/** Whether this body's size is an exact Count Tokens result. */
|
|
48
|
+
isExact(candidate: RequestCandidate): boolean;
|
|
49
|
+
clientInputTokens(candidate: RequestCandidate): number | undefined;
|
|
50
|
+
/** Count only when a size decision needs it; all views reuse the same result. */
|
|
51
|
+
measure(candidate: RequestCandidate, limits: SizingLimits, phase?: "plan" | "delivery"): Promise<CandidateMeasurement>;
|
|
52
|
+
/** The preferred body gets first chance; only validated registered candidates
|
|
53
|
+
* are considered afterward. Exact overflow never degrades to a byte guess. */
|
|
54
|
+
select(preferred: RequestCandidate, limits: SizingLimits): Promise<{
|
|
55
|
+
candidate: RequestCandidate;
|
|
56
|
+
measurement: CandidateMeasurement;
|
|
57
|
+
} | undefined>;
|
|
58
|
+
}
|
|
59
|
+
/** Tokens a fit check must leave free for output. Claude 4.5+ rejects a
|
|
60
|
+
* request only when its input alone exceeds the window (platform.claude.com/
|
|
61
|
+
* docs/en/build-with-claude/context-windows, "Context window overflow
|
|
62
|
+
* behavior"), so those models reserve min(max_tokens, OUTPUT_FLOOR_TOKENS);
|
|
63
|
+
* older and unknown models reserve the full max_tokens. */
|
|
64
|
+
export declare function outputReservation(model: string | undefined, maxTokens: unknown): number;
|
|
65
|
+
/** Admission: the relaxed output floor applies only to an exact count of this
|
|
66
|
+
* body; estimates and fallbacks (failed or disabled counting) keep max_tokens. */
|
|
67
|
+
export declare function fitsMeasured(measurement: Pick<CandidateMeasurement, "tokens" | "source">, limits: SizingLimits): boolean;
|
|
68
|
+
/** The limits an estimate is judged by: the full max_tokens reservation. */
|
|
69
|
+
export declare function estimateLimits(limits: SizingLimits): SizingLimits;
|
|
70
|
+
/** `limits.output` is the reservation from outputReservation, not raw max_tokens. */
|
|
71
|
+
export declare function fitsNative(input: number, limits: SizingLimits): boolean;
|
|
72
|
+
/** One shared estimator for planner telemetry and request-local snapshots. */
|
|
73
|
+
export declare function estimateRequestTokens(sample: SizeSample | undefined, bytes: number): PlannedSize;
|
|
74
|
+
//# sourceMappingURL=request-sizing.d.ts.map
|