claude-code-infinite 1.0.21 → 1.0.22

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.
Files changed (55) hide show
  1. package/README.md +74 -24
  2. package/dist/claude-env.d.ts +14 -1
  3. package/dist/claude-env.d.ts.map +1 -1
  4. package/dist/claude-env.js +17 -1
  5. package/dist/claude-env.js.map +1 -1
  6. package/dist/cli-lifecycle.d.ts +4 -0
  7. package/dist/cli-lifecycle.d.ts.map +1 -1
  8. package/dist/cli-lifecycle.js +18 -0
  9. package/dist/cli-lifecycle.js.map +1 -1
  10. package/dist/cli-main.js +118 -18
  11. package/dist/cli-main.js.map +1 -1
  12. package/dist/config.d.ts +2 -0
  13. package/dist/config.d.ts.map +1 -1
  14. package/dist/config.js.map +1 -1
  15. package/dist/count-tokens.d.ts.map +1 -1
  16. package/dist/count-tokens.js +7 -1
  17. package/dist/count-tokens.js.map +1 -1
  18. package/dist/diagnostics-schema.d.ts +44 -0
  19. package/dist/diagnostics-schema.d.ts.map +1 -0
  20. package/dist/diagnostics-schema.js +162 -0
  21. package/dist/diagnostics-schema.js.map +1 -0
  22. package/dist/diagnostics.d.ts +116 -0
  23. package/dist/diagnostics.d.ts.map +1 -0
  24. package/dist/diagnostics.js +316 -0
  25. package/dist/diagnostics.js.map +1 -0
  26. package/dist/first-tree.d.ts +11 -0
  27. package/dist/first-tree.d.ts.map +1 -0
  28. package/dist/first-tree.js +45 -0
  29. package/dist/first-tree.js.map +1 -0
  30. package/dist/hooks.d.ts +37 -0
  31. package/dist/hooks.d.ts.map +1 -1
  32. package/dist/hooks.js +69 -9
  33. package/dist/hooks.js.map +1 -1
  34. package/dist/memtree-links.d.ts +2 -0
  35. package/dist/memtree-links.d.ts.map +1 -1
  36. package/dist/memtree-links.js +4 -0
  37. package/dist/memtree-links.js.map +1 -1
  38. package/dist/memtree.d.ts +7 -0
  39. package/dist/memtree.d.ts.map +1 -1
  40. package/dist/memtree.js +8 -0
  41. package/dist/memtree.js.map +1 -1
  42. package/dist/prefix-store.js +3 -0
  43. package/dist/prefix-store.js.map +1 -1
  44. package/dist/proxy.d.ts +28 -2
  45. package/dist/proxy.d.ts.map +1 -1
  46. package/dist/proxy.js +63 -7
  47. package/dist/proxy.js.map +1 -1
  48. package/dist/reqlog.d.ts +17 -1
  49. package/dist/reqlog.d.ts.map +1 -1
  50. package/dist/reqlog.js.map +1 -1
  51. package/dist/resume-hint.d.ts +25 -0
  52. package/dist/resume-hint.d.ts.map +1 -0
  53. package/dist/resume-hint.js +36 -0
  54. package/dist/resume-hint.js.map +1 -0
  55. package/package.json +3 -3
package/dist/proxy.js CHANGED
@@ -59,6 +59,7 @@ import { hyperlink } from "./payment-gate.js";
59
59
  import { PaymentNoticeThrottle, paymentNoticeText } from "./payment-notice.js";
60
60
  import { MEMTREE_LINKS_MAX_SESSIONS } from "./memtree-links.js";
61
61
  import { describeClaudeCodeRequest, inspectMonitorTranscript, knownSideRequestKind, transcriptWarning, isClaudeCodeSideRequest, memtreeClientMeta, sessionTag, } from "./cc-request.js";
62
+ import { isResumableSessionId } from "./resume-hint.js";
62
63
  import { conservativeBodyTokens } from "./token-estimate.js";
63
64
  import { approxTokensFromBytes, mergeUsageFromJsonBody, mergeUsageFromSseEvent, } from "./reqlog.js";
64
65
  const DEFAULT_UPSTREAM = "https://api.anthropic.com";
@@ -92,10 +93,23 @@ const SKIP_RESPONSE_HEADERS = new Set([
92
93
  ]);
93
94
  /**
94
95
  * Budget fallback until the server reports `model_budget_tokens`: this share
95
- * of the model's context window (800k of Opus 5.5's 1M, matching the server's
96
- * large-context threshold).
96
+ * of the model's context window. 0.8 by default; NEAR_WINDOW_FALLBACK_RATIO
97
+ * (900k of 1M) for the 1M-window models the server budgets at 900k for ccc
98
+ * (NEAR_WINDOW_MODEL_RE). Plain Claude Code 2.1.302 auto-compacts a 1M window
99
+ * at 967k, so MemTree compresses no earlier than ~70k before it would; ccc
100
+ * lifts Claude Code's own 977k refusal (claude-env.ts) so a tool turn that
101
+ * jumps past the budget reaches the proxy and is compressed here.
97
102
  */
98
103
  export const FALLBACK_BUDGET_WINDOW_RATIO = 0.8;
104
+ export const NEAR_WINDOW_FALLBACK_RATIO = 0.9;
105
+ /** Opus 5.5, Opus 5, Opus 4.8 and Fable 5 / 5.1 (polychat LARGE_CONTEXT_MODELS). */
106
+ const NEAR_WINDOW_MODEL_RE = /claude-(?:opus-(?:4-8|5-5|5)|fable-5(?:-1)?)(?![0-9])/i;
107
+ const ONE_MILLION_TOKENS = 1_000_000;
108
+ /** The fallback share of the window for `model` (see FALLBACK_BUDGET_WINDOW_RATIO). */
109
+ export function fallbackBudgetRatio(model, modelContextLimit) {
110
+ return typeof model === "string" && modelContextLimit >= ONE_MILLION_TOKENS &&
111
+ NEAR_WINDOW_MODEL_RE.test(model) ? NEAR_WINDOW_FALLBACK_RATIO : FALLBACK_BUDGET_WINDOW_RATIO;
112
+ }
99
113
  /**
100
114
  * An explicit target is capped at this share of the budget: a compaction to
101
115
  * the budget itself would be over budget again on the next turn and
@@ -269,6 +283,7 @@ export function startProxy(opts) {
269
283
  unpaidDetail: () => opts.memtree.paymentRequiredDetail,
270
284
  usageUrl: opts.memtree.usageDashboardUrl,
271
285
  sessionStartNotice: opts.sessionStartNotice,
286
+ diagnostics: opts.diagnostics,
272
287
  notices: new NoticeDeliveryQueue(),
273
288
  mainNoticePending: false,
274
289
  mainPromptGeneration: 0,
@@ -305,6 +320,7 @@ export function startProxy(opts) {
305
320
  laneSizes: new Map(),
306
321
  };
307
322
  installMemtreeLink(state, state.memtreeLinkPlacement);
323
+ state.notices.setFirstTreeIntro(opts.firstTreeIntro ?? null);
308
324
  const server = http.createServer((req, res) => {
309
325
  const accepted = { req, res };
310
326
  acceptedRequests.add(accepted);
@@ -387,6 +403,7 @@ export function startProxy(opts) {
387
403
  void beginClose();
388
404
  },
389
405
  drain,
406
+ mainSessionId: () => state.resumableMainSession,
390
407
  });
391
408
  });
392
409
  });
@@ -617,6 +634,30 @@ export function memtreePageId(pageUrl) {
617
634
  return undefined;
618
635
  }
619
636
  }
637
+ const SESSION_START_SOURCES = new Set(["startup", "resume", "clear", "compact"]);
638
+ /** A main-agent SessionStart, for diagnostics; never fails the hook. */
639
+ function recordSessionStart(state, source) {
640
+ try {
641
+ const known = source !== undefined && SESSION_START_SOURCES.has(source);
642
+ state.diagnostics?.record("session_start", { source: known ? source : "other" });
643
+ }
644
+ catch {
645
+ // Diagnostics are best effort.
646
+ }
647
+ }
648
+ /**
649
+ * A resumed or forked session can be resumed again at once; a fresh or
650
+ * cleared one has nothing to resume until its first turn. Compaction keeps
651
+ * the same session.
652
+ */
653
+ function noteMainSessionStart(state, source, sessionId) {
654
+ if (source === "resume" || source === "fork") {
655
+ state.resumableMainSession = isResumableSessionId(sessionId) ? sessionId : undefined;
656
+ }
657
+ else if (source === "startup" || source === "clear") {
658
+ state.resumableMainSession = undefined;
659
+ }
660
+ }
620
661
  /** Serve only validated Claude hook POSTs on the randomized localhost path. */
621
662
  async function handleNoticeHook(req, res, state, reqlog,
622
663
  /** A main-lane turn ended: the session may be over (its final index). */
@@ -655,6 +696,10 @@ onMainStop) {
655
696
  return;
656
697
  }
657
698
  if (parsed.hook_event_name === "SessionStart") {
699
+ if (parsed.agent_id === undefined) {
700
+ recordSessionStart(state, parsed.source);
701
+ noteMainSessionStart(state, parsed.source, parsed.session_id);
702
+ }
658
703
  // A resume waits briefly for the newest tree; a fresh session has none to wait for.
659
704
  if (parsed.agent_id === undefined && parsed.source === "resume") {
660
705
  await state.newestTrees?.settle(parsed.session_id);
@@ -1055,6 +1100,8 @@ async function handleMessages(req, res, opts, upstream, state) {
1055
1100
  // reply the session ends with (scheduleFinalIndex, on Stop).
1056
1101
  const mainSessionId = isMainRequest ? requestSessionId(req) : undefined;
1057
1102
  if (mainSessionId !== undefined) {
1103
+ if (isResumableSessionId(mainSessionId))
1104
+ state.resumableMainSession = mainSessionId;
1058
1105
  opts.memtree.noteMainConversation(mainSessionId, {
1059
1106
  messages: msgsForMemtree,
1060
1107
  modelContextLimit,
@@ -1648,7 +1695,10 @@ async function handleMessages(req, res, opts, upstream, state) {
1648
1695
  const paymentDetail = opts.memtree.paymentRequiredDetail;
1649
1696
  const mayQueueNotice = displayForThisTurn && state.mainPromptGeneration === noticePromptGeneration;
1650
1697
  if (mayQueueNotice && paymentDetail !== null && state.paymentNotice.due()) {
1651
- state.notices.queueSuffix(paymentNoticeText(paymentDetail, recapLink(state, sessionId)?.link), () => state.paymentNotice.markShown(), noticePromptId);
1698
+ state.notices.queueSuffix(paymentNoticeText(paymentDetail, recapLink(state, sessionId)?.link), () => {
1699
+ state.paymentNotice.markShown();
1700
+ state.diagnostics?.record("paused_notice", { trigger: "turn" });
1701
+ }, noticePromptId);
1652
1702
  }
1653
1703
  else if (mayQueueNotice && paymentDetail === null) {
1654
1704
  state.notices.queueSuffix(DEGRADED_NOTICE, undefined, noticePromptId);
@@ -1882,6 +1932,7 @@ function resumePaymentNotice(state, input) {
1882
1932
  if (detail === null)
1883
1933
  return undefined;
1884
1934
  state.paymentNotice.markShown();
1935
+ state.diagnostics?.record("paused_notice", { trigger: "resume" });
1885
1936
  return state.notices.warningText(paymentNoticeText(detail));
1886
1937
  }
1887
1938
  /** The caller's extra SessionStart line; a throwing provider shows nothing. */
@@ -2399,7 +2450,7 @@ function serverBudgetKey(model, modelContextLimit) {
2399
2450
  /**
2400
2451
  * The session's whole-request budget: CCC_BUDGET_TOKENS when set (tests),
2401
2452
  * else the model budget the server last reported for this model and window,
2402
- * else the window times FALLBACK_BUDGET_WINDOW_RATIO (servers that predate
2453
+ * else the window times fallbackBudgetRatio (servers that predate
2403
2454
  * `model_budget_tokens`, and the first request before any response).
2404
2455
  */
2405
2456
  function resolveBudget(opts, state, model, modelContextLimit) {
@@ -2410,7 +2461,7 @@ function resolveBudget(opts, state, model, modelContextLimit) {
2410
2461
  if (reported !== undefined)
2411
2462
  return { tokens: reported, source: "server" };
2412
2463
  return {
2413
- tokens: Math.floor(modelContextLimit * FALLBACK_BUDGET_WINDOW_RATIO),
2464
+ tokens: Math.floor(modelContextLimit * fallbackBudgetRatio(model, modelContextLimit)),
2414
2465
  source: "window-ratio",
2415
2466
  };
2416
2467
  }
@@ -3863,12 +3914,17 @@ function refuseWholeRequest(args, inputTokens, budgetTokens) {
3863
3914
  const failure = args.state.fallbackFailures.fail(args.routeKey, { awaitingIndex: args.rec.indexBehind === true });
3864
3915
  args.rec.forwardedBytes = 0;
3865
3916
  args.rec.approxInputTokens = 0;
3866
- console.error("[ccc proxy] request exceeds native context window; recovery failed", {
3917
+ args.state.diagnostics?.record("refusal", {
3918
+ status: failure.status, attempt: failure.attempt,
3919
+ input_ktokens: Math.round(inputTokens / 1000), budget_ktokens: Math.round(budgetTokens / 1000),
3920
+ });
3921
+ // To requests.jsonl, never the console: Claude Code's TUI shows stderr inline.
3922
+ args.rec.refusal = {
3867
3923
  lane: args.routeKey, attempt: failure.attempt, status: failure.status,
3868
3924
  inputTokens, outputTokens: args.body.max_tokens ?? 0,
3869
3925
  outputReservation: bodyOutputReservation(args.body), indexBehind: args.rec.indexBehind === true,
3870
3926
  budgetTokens, modelContextLimit: args.modelContextLimit,
3871
- });
3927
+ };
3872
3928
  if (args.res.headersSent || args.res.destroyed)
3873
3929
  return false;
3874
3930
  args.res.writeHead(failure.status, { "content-type": "application/json", ...failure.headers });