zelari-code 1.38.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.
Files changed (38) hide show
  1. package/dist/cli/budget/llmCompact.js +97 -83
  2. package/dist/cli/budget/llmCompact.js.map +1 -1
  3. package/dist/cli/budget/requestMeter.js +138 -0
  4. package/dist/cli/budget/requestMeter.js.map +1 -0
  5. package/dist/cli/budget/requestSnapshotStore.js +55 -0
  6. package/dist/cli/budget/requestSnapshotStore.js.map +1 -0
  7. package/dist/cli/budget/tokenBudget.js +147 -15
  8. package/dist/cli/budget/tokenBudget.js.map +1 -1
  9. package/dist/cli/desktopConfig.js +8 -2
  10. package/dist/cli/desktopConfig.js.map +1 -1
  11. package/dist/cli/hooks/conversationContext.js +4 -0
  12. package/dist/cli/hooks/conversationContext.js.map +1 -1
  13. package/dist/cli/hooks/historyCompaction.js +76 -21
  14. package/dist/cli/hooks/historyCompaction.js.map +1 -1
  15. package/dist/cli/hooks/useChatTurn.js +90 -23
  16. package/dist/cli/hooks/useChatTurn.js.map +1 -1
  17. package/dist/cli/main.bundled.js +718 -297
  18. package/dist/cli/main.bundled.js.map +4 -4
  19. package/dist/cli/modelDiscovery.js +22 -33
  20. package/dist/cli/modelDiscovery.js.map +1 -1
  21. package/dist/cli/oauthDesktop.js +3 -3
  22. package/dist/cli/provider/anthropic.js +1 -1
  23. package/dist/cli/provider/anthropic.js.map +1 -1
  24. package/dist/cli/provider/chatgpt.js +1 -1
  25. package/dist/cli/provider/chatgpt.js.map +1 -1
  26. package/dist/cli/provider/openai-compatible.js +12 -2
  27. package/dist/cli/provider/openai-compatible.js.map +1 -1
  28. package/dist/cli/providerConfig.js +5 -5
  29. package/dist/cli/providerConfig.js.map +1 -1
  30. package/dist/cli/slashHandlers/provider.js +3 -3
  31. package/dist/cli/slashHandlers/provider.js.map +1 -1
  32. package/dist/cli/thinking.js +102 -43
  33. package/dist/cli/thinking.js.map +1 -1
  34. package/dist/cli/thinking.test.js +125 -4
  35. package/dist/cli/thinking.test.js.map +1 -1
  36. package/dist/cli/thinkingCapability.js +160 -0
  37. package/dist/cli/thinkingCapability.js.map +1 -0
  38. package/package.json +2 -2
@@ -1468,27 +1468,27 @@ function readStore() {
1468
1468
  }
1469
1469
  return { providers: {} };
1470
1470
  }
1471
- function writeStore(store3) {
1471
+ function writeStore(store4) {
1472
1472
  const file2 = getKeyStorePath();
1473
1473
  mkdirSync2(path3.dirname(file2), { recursive: true });
1474
- writeFileSync2(file2, JSON.stringify(store3, null, 2), { encoding: "utf-8", mode: 384 });
1474
+ writeFileSync2(file2, JSON.stringify(store4, null, 2), { encoding: "utf-8", mode: 384 });
1475
1475
  }
1476
1476
  function setApiKey(providerId, key) {
1477
- const store3 = readStore();
1478
- store3.providers[providerId] = { apiKey: key };
1479
- writeStore(store3);
1477
+ const store4 = readStore();
1478
+ store4.providers[providerId] = { apiKey: key };
1479
+ writeStore(store4);
1480
1480
  }
1481
1481
  function clearApiKey(providerId) {
1482
- const store3 = readStore();
1483
- delete store3.providers[providerId];
1484
- writeStore(store3);
1482
+ const store4 = readStore();
1483
+ delete store4.providers[providerId];
1484
+ writeStore(store4);
1485
1485
  }
1486
1486
  function getStoredApiKey(providerId) {
1487
- const store3 = readStore();
1488
- return store3.providers[providerId]?.apiKey ?? null;
1487
+ const store4 = readStore();
1488
+ return store4.providers[providerId]?.apiKey ?? null;
1489
1489
  }
1490
1490
  function setOAuthToken(providerId, token) {
1491
- const store3 = readStore();
1491
+ const store4 = readStore();
1492
1492
  const entry = { apiKey: token.apiKey };
1493
1493
  if (typeof token.expiresAt === "number" && Number.isFinite(token.expiresAt)) {
1494
1494
  entry.expiresAt = token.expiresAt;
@@ -1502,12 +1502,12 @@ function setOAuthToken(providerId, token) {
1502
1502
  if (typeof token.idToken === "string" && token.idToken.length > 0) {
1503
1503
  entry.idToken = token.idToken;
1504
1504
  }
1505
- store3.providers[providerId] = entry;
1506
- writeStore(store3);
1505
+ store4.providers[providerId] = entry;
1506
+ writeStore(store4);
1507
1507
  }
1508
1508
  function getOAuthToken(providerId) {
1509
- const store3 = readStore();
1510
- return store3.providers[providerId] ?? null;
1509
+ const store4 = readStore();
1510
+ return store4.providers[providerId] ?? null;
1511
1511
  }
1512
1512
  function resolveApiKey(providerId) {
1513
1513
  const spec = getProviderSpec(providerId);
@@ -1566,8 +1566,8 @@ async function forceRefreshOAuth(providerId, options = {}) {
1566
1566
  function readStoreDirect() {
1567
1567
  return readStore();
1568
1568
  }
1569
- function writeStoreDirect(store3) {
1570
- writeStore(store3);
1569
+ function writeStoreDirect(store4) {
1570
+ writeStore(store4);
1571
1571
  }
1572
1572
  function maskKey(key) {
1573
1573
  if (key.length <= 12) return "****";
@@ -1620,9 +1620,151 @@ var init_keyStore = __esm({
1620
1620
  }
1621
1621
  });
1622
1622
 
1623
+ // src/cli/thinkingCapability.ts
1624
+ function thinkingCapabilityFor(id, model) {
1625
+ const base = PROVIDER_THINKING_CAPABILITY[id] ?? {};
1626
+ const efforts = effortLevelsFor(id, model);
1627
+ const budget = supportsBudget(id, model);
1628
+ return {
1629
+ ...base,
1630
+ effort: efforts.length > 0 || Boolean(base.effort),
1631
+ budget,
1632
+ efforts: efforts.length > 0 ? efforts : void 0
1633
+ };
1634
+ }
1635
+ function effortLevelsFor(id, model) {
1636
+ const m = (model ?? "").trim();
1637
+ switch (id) {
1638
+ case "grok":
1639
+ case "openai-compatible":
1640
+ case "custom":
1641
+ if (grokHasXhigh(m)) return [...BASE_EFFORTS, "xhigh"];
1642
+ return [...BASE_EFFORTS];
1643
+ case "chatgpt":
1644
+ if (gptHasMax(m)) return [...BASE_EFFORTS, "xhigh", "max"];
1645
+ if (gptHasXhigh(m)) return [...BASE_EFFORTS, "xhigh"];
1646
+ return [...BASE_EFFORTS];
1647
+ case "deepseek":
1648
+ return ["high", "max"];
1649
+ case "minimax":
1650
+ return [...BASE_EFFORTS];
1651
+ case "glm":
1652
+ if (glmHasEffortScale(m)) return ["low", "high", "max"];
1653
+ return [];
1654
+ case "anthropic":
1655
+ if (claudeHasXhigh(m)) return ["high", "xhigh", "max"];
1656
+ if (claudeHasMax(m)) return ["high", "max"];
1657
+ return [];
1658
+ default:
1659
+ return [];
1660
+ }
1661
+ }
1662
+ function supportsBudget(id, model) {
1663
+ if (id === "anthropic") return true;
1664
+ if (id === "glm") return !glmHasEffortScale(model);
1665
+ return Boolean(PROVIDER_THINKING_CAPABILITY[id]?.budget);
1666
+ }
1667
+ function grokHasXhigh(model) {
1668
+ const v = parseDottedVersion(model, /grok[-_]?(\d+)(?:[.-](\d+))?/i);
1669
+ if (!v) return false;
1670
+ return v.major > 4 || v.major === 4 && v.minor >= 6;
1671
+ }
1672
+ function gptHasXhigh(model) {
1673
+ const v = parseDottedVersion(model, /gpt[-_]?(\d+)(?:[.-](\d+))?/i);
1674
+ if (!v) return false;
1675
+ return v.major > 5 || v.major === 5 && v.minor >= 4;
1676
+ }
1677
+ function gptHasMax(model) {
1678
+ const v = parseDottedVersion(model, /gpt[-_]?(\d+)(?:[.-](\d+))?/i);
1679
+ if (!v) return false;
1680
+ return v.major > 5 || v.major === 5 && v.minor >= 6;
1681
+ }
1682
+ function claudeHasMax(model) {
1683
+ const v = parseClaudeVersion(model);
1684
+ if (!v) return false;
1685
+ return v.major > 4 || v.major === 4 && v.minor >= 6;
1686
+ }
1687
+ function claudeHasXhigh(model) {
1688
+ const v = parseClaudeVersion(model);
1689
+ if (!v) return false;
1690
+ if (v.major >= 5) return true;
1691
+ return v.major === 4 && v.minor >= 7;
1692
+ }
1693
+ function glmHasEffortScale(model) {
1694
+ const v = parseDottedVersion(model ?? "", /glm[-_]?(\d+)(?:[.-](\d+))?/i);
1695
+ if (!v) return false;
1696
+ return v.major >= 5;
1697
+ }
1698
+ function parseDottedVersion(model, re) {
1699
+ const m = re.exec(model);
1700
+ if (!m) return null;
1701
+ return {
1702
+ major: Number.parseInt(m[1], 10),
1703
+ minor: m[2] ? Number.parseInt(m[2], 10) : 0
1704
+ };
1705
+ }
1706
+ function parseClaudeVersion(model) {
1707
+ const m = /claude-(?:sonnet|opus|haiku)[-_]?(\d+)(?:[.-](\d+))?/i.exec(model);
1708
+ if (!m) return null;
1709
+ return {
1710
+ major: Number.parseInt(m[1], 10),
1711
+ minor: m[2] ? Number.parseInt(m[2], 10) : 0
1712
+ };
1713
+ }
1714
+ var THINKING_EFFORTS, BASE_EFFORTS, PROVIDER_THINKING_CAPABILITY;
1715
+ var init_thinkingCapability = __esm({
1716
+ "src/cli/thinkingCapability.ts"() {
1717
+ "use strict";
1718
+ THINKING_EFFORTS = [
1719
+ "low",
1720
+ "medium",
1721
+ "high",
1722
+ "xhigh",
1723
+ "max"
1724
+ ];
1725
+ BASE_EFFORTS = ["low", "medium", "high"];
1726
+ PROVIDER_THINKING_CAPABILITY = {
1727
+ "openai-compatible": { effort: true },
1728
+ grok: { effort: true },
1729
+ chatgpt: { effort: true },
1730
+ anthropic: { budget: true },
1731
+ glm: { budget: true },
1732
+ deepseek: { effort: true },
1733
+ minimax: { effort: true },
1734
+ custom: { effort: true }
1735
+ };
1736
+ }
1737
+ });
1738
+
1623
1739
  // src/cli/thinking.ts
1624
- function thinkingCapabilityFor(id) {
1625
- return PROVIDER_THINKING_CAPABILITY[id] ?? {};
1740
+ function clampEffort(id, model, requested) {
1741
+ const native = effortLevelsFor(id, model);
1742
+ if (native.includes(requested)) {
1743
+ return { effort: requested, clamped: false };
1744
+ }
1745
+ if (native.length === 0) {
1746
+ return {
1747
+ effort: requested,
1748
+ clamped: true,
1749
+ note: `thinking '${requested}' is not supported for provider "${id}"`
1750
+ };
1751
+ }
1752
+ const want = EFFORT_RANK[requested];
1753
+ let best = native[0];
1754
+ let bestDist = Math.abs(EFFORT_RANK[best] - want);
1755
+ for (const level of native) {
1756
+ const dist = Math.abs(EFFORT_RANK[level] - want);
1757
+ if (dist < bestDist || dist === bestDist && EFFORT_RANK[level] > EFFORT_RANK[best]) {
1758
+ best = level;
1759
+ bestDist = dist;
1760
+ }
1761
+ }
1762
+ const label = model ? `${id}/${model}` : id;
1763
+ return {
1764
+ effort: best,
1765
+ clamped: true,
1766
+ note: `'${requested}' is not native on ${label} \u2014 using '${best}'`
1767
+ };
1626
1768
  }
1627
1769
  function stringifyThinkingSpec(spec) {
1628
1770
  if (spec === "auto") return "auto";
@@ -1634,7 +1776,9 @@ function parseThinkingSpec(raw) {
1634
1776
  const s = (raw ?? "").trim().toLowerCase();
1635
1777
  if (!s || s === "auto") return "auto";
1636
1778
  if (s === "off") return { kind: "off" };
1637
- if (s === "low" || s === "medium" || s === "high") return { kind: "effort", effort: s };
1779
+ if (THINKING_EFFORTS.includes(s)) {
1780
+ return { kind: "effort", effort: s };
1781
+ }
1638
1782
  const m = /^budget:(\d+)$/.exec(s);
1639
1783
  if (m) {
1640
1784
  const n = Number.parseInt(m[1], 10);
@@ -1644,15 +1788,19 @@ function parseThinkingSpec(raw) {
1644
1788
  }
1645
1789
  function isValidThinkingInput(raw) {
1646
1790
  const s = raw.trim().toLowerCase();
1647
- if (s === "auto" || s === "off" || s === "low" || s === "medium" || s === "high") return true;
1791
+ if (s === "auto" || s === "off") return true;
1792
+ if (THINKING_EFFORTS.includes(s)) return true;
1648
1793
  return /^budget:\d+$/.test(s) && Number.parseInt(s.slice(7), 10) > 0;
1649
1794
  }
1650
1795
  function degrade(note) {
1651
1796
  return { patch: {}, degraded: true, note };
1652
1797
  }
1653
- function translateOpenAiCompatibleThinking(providerId, spec) {
1798
+ function withClampNote(patch, clamped, note) {
1799
+ return { patch, degraded: false, note: clamped ? note : void 0 };
1800
+ }
1801
+ function translateOpenAiCompatibleThinking(providerId, spec, model) {
1654
1802
  if (spec === "auto") return { patch: {}, degraded: false };
1655
- const cap3 = thinkingCapabilityFor(providerId);
1803
+ const cap3 = thinkingCapabilityFor(providerId, model);
1656
1804
  switch (spec.kind) {
1657
1805
  case "off":
1658
1806
  if (providerId === "deepseek" || providerId === "glm") {
@@ -1660,20 +1808,40 @@ function translateOpenAiCompatibleThinking(providerId, spec) {
1660
1808
  }
1661
1809
  if (cap3.effort) return { patch: { reasoning_effort: "low" }, degraded: false };
1662
1810
  return degrade(`thinking 'off' is not supported for provider "${providerId}"`);
1663
- case "effort":
1664
- if (!cap3.effort) {
1811
+ case "effort": {
1812
+ if (!cap3.effort && !cap3.efforts?.length) {
1665
1813
  return degrade(`thinking 'effort' is not supported for provider "${providerId}"`);
1666
1814
  }
1815
+ const resolved = clampEffort(providerId, model, spec.effort);
1667
1816
  if (providerId === "deepseek") {
1668
- return {
1669
- patch: {
1817
+ return withClampNote(
1818
+ {
1670
1819
  thinking: { type: "enabled" },
1671
- reasoning_effort: spec.effort === "high" ? "max" : "high"
1820
+ reasoning_effort: resolved.effort === "max" ? "max" : "high"
1672
1821
  },
1673
- degraded: false
1674
- };
1822
+ resolved.clamped,
1823
+ resolved.note
1824
+ );
1825
+ }
1826
+ if (providerId === "glm") {
1827
+ if (!glmHasEffortScale(model)) {
1828
+ return degrade(`thinking 'effort' is not supported for GLM ${model || "4.x"} \u2014 use budget:N`);
1829
+ }
1830
+ return withClampNote(
1831
+ {
1832
+ thinking: { type: "enabled" },
1833
+ reasoning_effort: resolved.effort
1834
+ },
1835
+ resolved.clamped,
1836
+ resolved.note
1837
+ );
1675
1838
  }
1676
- return { patch: { reasoning_effort: spec.effort }, degraded: false };
1839
+ return withClampNote(
1840
+ { reasoning_effort: resolved.effort },
1841
+ resolved.clamped,
1842
+ resolved.note
1843
+ );
1844
+ }
1677
1845
  case "budget":
1678
1846
  if (!cap3.budget) {
1679
1847
  return degrade(`thinking 'budget' is not supported for provider "${providerId}"`);
@@ -1684,18 +1852,24 @@ function translateOpenAiCompatibleThinking(providerId, spec) {
1684
1852
  };
1685
1853
  }
1686
1854
  }
1687
- function translateResponsesThinking(spec) {
1855
+ function translateResponsesThinking(spec, model) {
1688
1856
  if (spec === "auto") return { patch: {}, degraded: false };
1689
1857
  switch (spec.kind) {
1690
1858
  case "off":
1691
1859
  return { patch: { reasoning: { effort: "minimal" } }, degraded: false };
1692
- case "effort":
1693
- return { patch: { reasoning: { effort: spec.effort } }, degraded: false };
1860
+ case "effort": {
1861
+ const resolved = clampEffort("chatgpt", model, spec.effort);
1862
+ return withClampNote(
1863
+ { reasoning: { effort: resolved.effort } },
1864
+ resolved.clamped,
1865
+ resolved.note
1866
+ );
1867
+ }
1694
1868
  case "budget":
1695
- return degrade('thinking "budget" is not supported for chatgpt \u2014 use low/medium/high');
1869
+ return degrade('thinking "budget" is not supported for chatgpt \u2014 use low/medium/high/xhigh/max');
1696
1870
  }
1697
1871
  }
1698
- function translateAnthropicThinking(spec) {
1872
+ function translateAnthropicThinking(spec, model) {
1699
1873
  if (spec === "auto") return { patch: {}, degraded: false };
1700
1874
  switch (spec.kind) {
1701
1875
  case "off":
@@ -1705,23 +1879,31 @@ function translateAnthropicThinking(spec) {
1705
1879
  patch: { thinking: { type: "enabled", budget_tokens: spec.budgetTokens } },
1706
1880
  degraded: false
1707
1881
  };
1708
- case "effort":
1709
- return degrade('thinking "effort" is not supported for anthropic \u2014 use budget:N');
1882
+ case "effort": {
1883
+ const levels = effortLevelsFor("anthropic", model);
1884
+ if (levels.length === 0) {
1885
+ return degrade('thinking "effort" is not supported for this Claude model \u2014 use budget:N');
1886
+ }
1887
+ const resolved = clampEffort("anthropic", model, spec.effort);
1888
+ return withClampNote(
1889
+ { output_config: { effort: resolved.effort } },
1890
+ resolved.clamped,
1891
+ resolved.note
1892
+ );
1893
+ }
1710
1894
  }
1711
1895
  }
1712
- var PROVIDER_THINKING_CAPABILITY;
1896
+ var EFFORT_RANK;
1713
1897
  var init_thinking = __esm({
1714
1898
  "src/cli/thinking.ts"() {
1715
1899
  "use strict";
1716
- PROVIDER_THINKING_CAPABILITY = {
1717
- "openai-compatible": { effort: true },
1718
- "grok": { effort: true },
1719
- "chatgpt": { effort: true },
1720
- "anthropic": { budget: true },
1721
- "glm": { budget: true },
1722
- "deepseek": { effort: true },
1723
- "minimax": { effort: true },
1724
- "custom": { effort: true }
1900
+ init_thinkingCapability();
1901
+ EFFORT_RANK = {
1902
+ low: 1,
1903
+ medium: 2,
1904
+ high: 3,
1905
+ xhigh: 4,
1906
+ max: 5
1725
1907
  };
1726
1908
  }
1727
1909
  });
@@ -1914,14 +2096,14 @@ var init_providerConfig = __esm({
1914
2096
  DEFAULTS = {
1915
2097
  activeProviderId: "openai-compatible",
1916
2098
  modelByProvider: {
1917
- // grok-4.5: flagship; reasoning_effort defaults to "high" on the xAI API
1918
- "openai-compatible": "grok-4.5",
2099
+ // grok-4.6: flagship; native reasoning_effort includes xhigh
2100
+ "openai-compatible": "grok-4.6",
1919
2101
  "minimax": "MiniMax-M2.5",
1920
2102
  "glm": "glm-4.6",
1921
- "grok": "grok-4.5",
2103
+ "grok": "grok-4.6",
1922
2104
  "deepseek": "deepseek-v4-pro",
1923
- "chatgpt": "gpt-5.2-codex",
1924
- "anthropic": "claude-sonnet-4-5",
2105
+ "chatgpt": "gpt-5.6-codex",
2106
+ "anthropic": "claude-sonnet-4-6",
1925
2107
  "custom": ""
1926
2108
  },
1927
2109
  thinkingByProvider: {
@@ -1948,6 +2130,7 @@ __export(modelDiscovery_exports, {
1948
2130
  getCachedModels: () => getCachedModels,
1949
2131
  getDiscoveredModelIds: () => getDiscoveredModelIds,
1950
2132
  getModelsFilePath: () => getModelsFilePath,
2133
+ getStaticFallbackModels: () => getStaticFallbackModels,
1951
2134
  isModelsCacheStale: () => isModelsCacheStale,
1952
2135
  loadModelsRegistry: () => loadModelsRegistry,
1953
2136
  pickDefaultModel: () => pickDefaultModel
@@ -1955,6 +2138,9 @@ __export(modelDiscovery_exports, {
1955
2138
  import { promises as fs3, existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
1956
2139
  import { homedir } from "node:os";
1957
2140
  import path5 from "node:path";
2141
+ function getStaticFallbackModels(provider) {
2142
+ return STATIC_FALLBACKS[provider] ? [...STATIC_FALLBACKS[provider]] : [];
2143
+ }
1958
2144
  async function resolveDiscoveryBaseUrl(provider, options) {
1959
2145
  if (options.baseUrl) return options.baseUrl;
1960
2146
  const { getCustomEndpoint: getCustomEndpoint2 } = await Promise.resolve().then(() => (init_providerConfig(), providerConfig_exports));
@@ -2090,10 +2276,6 @@ async function discoverModelsForProvider(provider, options = {}) {
2090
2276
  const headers2 = await resolveDiscoveryHeaders(provider, authToken);
2091
2277
  response = await fetchImpl(url2, { method: "GET", headers: headers2 });
2092
2278
  } catch (err) {
2093
- const fallback = STATIC_FALLBACKS[provider];
2094
- if (fallback) {
2095
- return cacheFallback(provider, baseUrl, fallback, options, `network: ${err instanceof Error ? err.message : String(err)}`);
2096
- }
2097
2279
  throw new ModelDiscoveryError(
2098
2280
  `Network error contacting ${url2}: ${err instanceof Error ? err.message : String(err)}`,
2099
2281
  "network_error"
@@ -2101,10 +2283,6 @@ async function discoverModelsForProvider(provider, options = {}) {
2101
2283
  }
2102
2284
  if (!response.ok) {
2103
2285
  const body = await response.text().catch(() => "");
2104
- const fallback = STATIC_FALLBACKS[provider];
2105
- if (fallback) {
2106
- return cacheFallback(provider, baseUrl, fallback, options, `HTTP ${response.status}: ${body.slice(0, 80)}`);
2107
- }
2108
2286
  throw new ModelDiscoveryError(
2109
2287
  `HTTP ${response.status} from ${url2}: ${body.slice(0, 200)}`,
2110
2288
  `http_${response.status}`
@@ -2114,10 +2292,6 @@ async function discoverModelsForProvider(provider, options = {}) {
2114
2292
  try {
2115
2293
  json2 = await response.json();
2116
2294
  } catch (err) {
2117
- const fallback = STATIC_FALLBACKS[provider];
2118
- if (fallback) {
2119
- return cacheFallback(provider, baseUrl, fallback, options, "invalid_json");
2120
- }
2121
2295
  throw new ModelDiscoveryError(
2122
2296
  `Invalid JSON from ${url2}: ${err instanceof Error ? err.message : String(err)}`,
2123
2297
  "invalid_json"
@@ -2125,10 +2299,6 @@ async function discoverModelsForProvider(provider, options = {}) {
2125
2299
  }
2126
2300
  const models = provider === "anthropic" ? parseAnthropicModelsResponse(json2) : parseOpenAIModelsResponse(json2, baseUrl);
2127
2301
  if (models.length === 0) {
2128
- const fallback = STATIC_FALLBACKS[provider];
2129
- if (fallback) {
2130
- return cacheFallback(provider, baseUrl, fallback, options, "empty_response");
2131
- }
2132
2302
  throw new ModelDiscoveryError(
2133
2303
  `Provider ${provider} returned 0 models \u2014 refusing to overwrite cache`,
2134
2304
  "empty_response"
@@ -2148,22 +2318,6 @@ async function discoverModelsForProvider(provider, options = {}) {
2148
2318
  }
2149
2319
  return entry;
2150
2320
  }
2151
- async function cacheFallback(provider, baseUrl, models, options, lastError) {
2152
- const entry = {
2153
- models,
2154
- fetchedAt: Date.now(),
2155
- baseUrl,
2156
- lastError
2157
- };
2158
- if (!options.skipCacheWrite) {
2159
- const file2 = getModelsFilePath();
2160
- await readModifyWriteRegistry((current) => {
2161
- current[provider] = entry;
2162
- return current;
2163
- }, file2);
2164
- }
2165
- return entry;
2166
- }
2167
2321
  function discoverModelsInBackground(provider, options = {}) {
2168
2322
  discoverModelsForProvider(provider, options).catch((err) => {
2169
2323
  if (err instanceof ModelDiscoveryError && options.onError) {
@@ -2201,6 +2355,9 @@ var init_modelDiscovery = __esm({
2201
2355
  };
2202
2356
  STATIC_FALLBACKS = {
2203
2357
  chatgpt: [
2358
+ { id: "gpt-5.6-codex", displayName: "GPT-5.6 Codex" },
2359
+ { id: "gpt-5.6", displayName: "GPT-5.6" },
2360
+ { id: "gpt-5.4", displayName: "GPT-5.4" },
2204
2361
  { id: "gpt-5.2-codex", displayName: "GPT-5.2 Codex" },
2205
2362
  { id: "gpt-5.2", displayName: "GPT-5.2" },
2206
2363
  { id: "gpt-5.1-codex", displayName: "GPT-5.1 Codex" },
@@ -2209,9 +2366,19 @@ var init_modelDiscovery = __esm({
2209
2366
  { id: "o4-mini", displayName: "o4-mini" }
2210
2367
  ],
2211
2368
  anthropic: [
2369
+ { id: "claude-opus-4-7", displayName: "Claude Opus 4.7" },
2212
2370
  { id: "claude-opus-4-6", displayName: "Claude Opus 4.6" },
2371
+ { id: "claude-sonnet-4-6", displayName: "Claude Sonnet 4.6" },
2213
2372
  { id: "claude-sonnet-4-5", displayName: "Claude Sonnet 4.5" },
2214
2373
  { id: "claude-haiku-4-5", displayName: "Claude Haiku 4.5" }
2374
+ ],
2375
+ grok: [
2376
+ { id: "grok-4.6", displayName: "Grok 4.6" },
2377
+ { id: "grok-4.5", displayName: "Grok 4.5" }
2378
+ ],
2379
+ glm: [
2380
+ { id: "glm-5.3", displayName: "GLM-5.3" },
2381
+ { id: "glm-4.6", displayName: "GLM-4.6" }
2215
2382
  ]
2216
2383
  };
2217
2384
  writeChain = Promise.resolve();
@@ -21047,6 +21214,79 @@ var init_types = __esm({
21047
21214
  }
21048
21215
  });
21049
21216
 
21217
+ // packages/core/dist/core/requestSnapshot.js
21218
+ import { createHash as createHash2 } from "node:crypto";
21219
+ function stableStringify(value) {
21220
+ if (value === null || typeof value !== "object")
21221
+ return JSON.stringify(value) ?? "null";
21222
+ if (Array.isArray(value)) {
21223
+ const items = value.map((v) => stableStringify(v));
21224
+ return `[${items.join(",")}]`;
21225
+ }
21226
+ const obj = value;
21227
+ const keys = Object.keys(obj).filter((k) => obj[k] !== void 0).sort();
21228
+ const parts = keys.map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`);
21229
+ return `{${parts.join(",")}}`;
21230
+ }
21231
+ function sha256Hex(input) {
21232
+ return createHash2("sha256").update(input, "utf8").digest("hex").slice(0, 32);
21233
+ }
21234
+ function cloneMessages(messages) {
21235
+ return structuredClone(messages);
21236
+ }
21237
+ function canonicalTools(tools) {
21238
+ return [...tools].sort((a, b) => a.name.localeCompare(b.name));
21239
+ }
21240
+ function createRoutedRequestSnapshot(params) {
21241
+ let split = 0;
21242
+ while (split < params.messages.length && params.messages[split].role === "system") {
21243
+ split++;
21244
+ }
21245
+ const systemMessages = cloneMessages(params.messages.slice(0, split));
21246
+ const conversation = cloneMessages(params.messages.slice(split));
21247
+ const tools = canonicalTools(params.tools).map((t) => structuredClone(t));
21248
+ const header = stableStringify({
21249
+ provider: params.provider,
21250
+ model: params.model,
21251
+ systemMessages,
21252
+ tools
21253
+ });
21254
+ const request = stableStringify({
21255
+ provider: params.provider,
21256
+ model: params.model,
21257
+ systemMessages,
21258
+ tools,
21259
+ conversation
21260
+ });
21261
+ return {
21262
+ provider: params.provider,
21263
+ model: params.model,
21264
+ systemMessages,
21265
+ conversation,
21266
+ tools,
21267
+ headerFingerprint: sha256Hex(header),
21268
+ requestFingerprint: sha256Hex(request),
21269
+ createdAt: Date.now()
21270
+ };
21271
+ }
21272
+ function compareReplayPrefix(snapshot, messages) {
21273
+ const base = snapshot.conversation;
21274
+ const n = Math.min(base.length, messages.length);
21275
+ let matching = 0;
21276
+ for (let i = 0; i < n; i++) {
21277
+ if (stableStringify(base[i]) !== stableStringify(messages[i])) {
21278
+ return { exact: false, matchingMessages: matching, mismatchIndex: i };
21279
+ }
21280
+ matching++;
21281
+ }
21282
+ return { exact: true, matchingMessages: matching };
21283
+ }
21284
+ var init_requestSnapshot = __esm({
21285
+ "packages/core/dist/core/requestSnapshot.js"() {
21286
+ "use strict";
21287
+ }
21288
+ });
21289
+
21050
21290
  // packages/core/dist/core/textLoopDetect.js
21051
21291
  function isStatusTheaterUnit(unit) {
21052
21292
  const u = normalizeLoopUnit(unit).toLowerCase();
@@ -21245,10 +21485,10 @@ var init_textLoopDetect = __esm({
21245
21485
 
21246
21486
  // packages/core/dist/core/AgentHarness.js
21247
21487
  function hashToolCall(toolName, args) {
21248
- const canonical = stableStringify(args);
21488
+ const canonical = stableStringify2(args);
21249
21489
  return `${toolName}::${canonical}`;
21250
21490
  }
21251
- function stableStringify(value) {
21491
+ function stableStringify2(value) {
21252
21492
  return JSON.stringify(value, (_k, v) => {
21253
21493
  if (v && typeof v === "object" && !Array.isArray(v)) {
21254
21494
  const sorted = {};
@@ -21481,6 +21721,7 @@ var init_AgentHarness = __esm({
21481
21721
  "packages/core/dist/core/AgentHarness.js"() {
21482
21722
  "use strict";
21483
21723
  init_events();
21724
+ init_requestSnapshot();
21484
21725
  init_textLoopDetect();
21485
21726
  init_textLoopDetect();
21486
21727
  AgentHarness = class {
@@ -21917,6 +22158,23 @@ ${shared2.content}`,
21917
22158
  yield agentEnd;
21918
22159
  this.activeController = null;
21919
22160
  }
22161
+ /**
22162
+ * v1.36.0: capture a deterministic snapshot of the routed request just
22163
+ * before it goes out. Never throws into the request path.
22164
+ */
22165
+ emitSnapshot(tools, generation) {
22166
+ if (!this.config.onRequestSnapshot)
22167
+ return;
22168
+ try {
22169
+ this.config.onRequestSnapshot(createRoutedRequestSnapshot({
22170
+ messages: this.config.messages,
22171
+ model: this.config.model,
22172
+ provider: this.config.provider,
22173
+ tools
22174
+ }), generation);
22175
+ } catch {
22176
+ }
22177
+ }
21920
22178
  /**
21921
22179
  * Run a single provider turn for the current message buffer.
21922
22180
  * Streams from the provider, dispatches deltas to events, executes
@@ -21934,6 +22192,7 @@ ${shared2.content}`,
21934
22192
  */
21935
22193
  async *runSingleTurn(messageId, finishRef, usageRef) {
21936
22194
  try {
22195
+ this.emitSnapshot(this.config.tools);
21937
22196
  const stream = this.config.providerStream({
21938
22197
  messages: this.config.messages,
21939
22198
  model: this.config.model,
@@ -22224,6 +22483,7 @@ ${cached2}`
22224
22483
  const finishRef = { value: "stop" };
22225
22484
  const usageRef = { value: null };
22226
22485
  try {
22486
+ this.emitSnapshot([]);
22227
22487
  const stream = this.config.providerStream({
22228
22488
  messages: this.config.messages,
22229
22489
  model: this.config.model,
@@ -22804,7 +23064,10 @@ __export(harness_exports, {
22804
23064
  SessionJsonlWriter: () => SessionJsonlWriter,
22805
23065
  TEXT_LOOP_RECOVERY_SYSTEM: () => TEXT_LOOP_RECOVERY_SYSTEM,
22806
23066
  TEXT_LOOP_RECOVERY_USER_PROMPT: () => TEXT_LOOP_RECOVERY_USER_PROMPT,
23067
+ canonicalTools: () => canonicalTools,
22807
23068
  collapseLoopedAssistantText: () => collapseLoopedAssistantText,
23069
+ compareReplayPrefix: () => compareReplayPrefix,
23070
+ createRoutedRequestSnapshot: () => createRoutedRequestSnapshot,
22808
23071
  detectAssistantTextLoop: () => detectAssistantTextLoop,
22809
23072
  detectAssistantTextLoopWindow: () => detectAssistantTextLoopWindow,
22810
23073
  hashToolCall: () => hashToolCall,
@@ -22816,6 +23079,8 @@ __export(harness_exports, {
22816
23079
  parseMinimaxStyleToolCalls: () => parseMinimaxStyleToolCalls,
22817
23080
  parseTextToolCalls: () => parseTextToolCalls,
22818
23081
  readSession: () => readSession,
23082
+ sha256Hex: () => sha256Hex,
23083
+ stableStringify: () => stableStringify,
22819
23084
  toolMatches: () => toolMatches,
22820
23085
  wrapLegacyStream: () => wrapLegacyStream
22821
23086
  });
@@ -22824,6 +23089,7 @@ var init_harness = __esm({
22824
23089
  "use strict";
22825
23090
  init_AgentHarness();
22826
23091
  init_providerStream();
23092
+ init_requestSnapshot();
22827
23093
  init_sessionJsonl();
22828
23094
  init_hooks();
22829
23095
  }
@@ -27735,6 +28001,7 @@ __export(dist_exports, {
27735
28001
  buildSystemPrompt: () => buildSystemPrompt,
27736
28002
  buildSystemPromptSplit: () => buildSystemPromptSplit,
27737
28003
  canRunParallel: () => canRunParallel,
28004
+ canonicalTools: () => canonicalTools,
27738
28005
  captureFailure: () => captureFailure,
27739
28006
  checkImplementationCompletion: () => checkImplementationCompletion,
27740
28007
  checkImplementationDelivery: () => checkImplementationDelivery,
@@ -27746,6 +28013,7 @@ __export(dist_exports, {
27746
28013
  clearCustomTools: () => clearCustomTools,
27747
28014
  cliToolToEnhanced: () => cliToolToEnhanced,
27748
28015
  collapseLoopedAssistantText: () => collapseLoopedAssistantText,
28016
+ compareReplayPrefix: () => compareReplayPrefix,
27749
28017
  computeAgentSkills: () => computeAgentSkills,
27750
28018
  computeAgentTools: () => computeAgentTools,
27751
28019
  councilModeBanner: () => councilModeBanner,
@@ -27755,6 +28023,7 @@ __export(dist_exports, {
27755
28023
  createBrainEvent: () => createBrainEvent,
27756
28024
  createDefaultSystemPromptConfig: () => createDefaultSystemPromptConfig,
27757
28025
  createGraph: () => createGraph,
28026
+ createRoutedRequestSnapshot: () => createRoutedRequestSnapshot,
27758
28027
  defaultPersonaParse: () => defaultPersonaParse,
27759
28028
  detectAssistantTextLoop: () => detectAssistantTextLoop,
27760
28029
  detectAssistantTextLoopWindow: () => detectAssistantTextLoopWindow,
@@ -27871,9 +28140,11 @@ __export(dist_exports, {
27871
28140
  scrubProprietaryLeak: () => scrubProprietaryLeak,
27872
28141
  selectParallelWave: () => selectParallelWave,
27873
28142
  setWorkspaceStubs: () => setWorkspaceStubs,
28143
+ sha256Hex: () => sha256Hex,
27874
28144
  shouldRetryMember: () => shouldRetryMember,
27875
28145
  slugify: () => slugify2,
27876
28146
  specificityFromAssumptions: () => specificityFromAssumptions,
28147
+ stableStringify: () => stableStringify,
27877
28148
  stripClarificationProtocol: () => stripClarificationProtocol,
27878
28149
  swapMembers: () => swapMembers,
27879
28150
  systemMessagesFromSplit: () => systemMessagesFromSplit,
@@ -28236,6 +28507,7 @@ function openaiCompatibleProvider(config2) {
28236
28507
  if (cacheable) messageMappingCache.set(m, mapped);
28237
28508
  return mapped;
28238
28509
  });
28510
+ const generation = params.generation;
28239
28511
  const body = {
28240
28512
  // Use `params.model` (per-call override from AgentHarness, e.g. for
28241
28513
  // `agentModels` config) rather than the closed-over `config.model`
@@ -28243,7 +28515,7 @@ function openaiCompatibleProvider(config2) {
28243
28515
  model: params.model,
28244
28516
  messages,
28245
28517
  stream: true,
28246
- temperature: 0.7,
28518
+ temperature: generation?.temperature ?? 0.7,
28247
28519
  // Task G.4.2 — request the provider to send real token usage in
28248
28520
  // the final chunk (gated by `stream_options.include_usage` on the
28249
28521
  // OpenAI-compatible API). Providers that don't honor this (some
@@ -28251,13 +28523,16 @@ function openaiCompatibleProvider(config2) {
28251
28523
  // the harness will fall back to the ~4-char/token approximation.
28252
28524
  stream_options: { include_usage: true }
28253
28525
  };
28526
+ if (typeof generation?.maxTokens === "number" && generation.maxTokens > 0) {
28527
+ body.max_tokens = generation.maxTokens;
28528
+ }
28254
28529
  const thinkingSpec = config2.thinking ?? "auto";
28255
28530
  if (config2.providerId === "deepseek" && thinkingSpec === "auto") {
28256
28531
  const thinking = resolveDeepSeekThinking();
28257
28532
  if (thinking.thinking) body.thinking = { type: thinking.thinking };
28258
28533
  if (thinking.reasoningEffort) body.reasoning_effort = thinking.reasoningEffort;
28259
28534
  } else if (thinkingSpec !== "auto") {
28260
- const t = translateOpenAiCompatibleThinking(config2.providerId, thinkingSpec);
28535
+ const t = translateOpenAiCompatibleThinking(config2.providerId, thinkingSpec, config2.model);
28261
28536
  if (t.degraded) {
28262
28537
  console.warn(`[thinking] ${t.note ?? "unsupported"} \u2014 falling back to provider default.`);
28263
28538
  } else {
@@ -28791,7 +29066,7 @@ function anthropicMessagesProvider(config2) {
28791
29066
  }
28792
29067
  const thinkingSpec = config2.thinking ?? "auto";
28793
29068
  if (thinkingSpec !== "auto") {
28794
- const t = translateAnthropicThinking(thinkingSpec);
29069
+ const t = translateAnthropicThinking(thinkingSpec, config2.model);
28795
29070
  if (t.degraded) console.warn(`[thinking] ${t.note ?? "unsupported"} \u2014 falling back to provider default.`);
28796
29071
  else Object.assign(body, t.patch);
28797
29072
  }
@@ -29003,7 +29278,7 @@ function chatgptResponsesProvider(config2) {
29003
29278
  }
29004
29279
  const thinkingSpec = config2.thinking ?? "auto";
29005
29280
  if (thinkingSpec !== "auto") {
29006
- const t = translateResponsesThinking(thinkingSpec);
29281
+ const t = translateResponsesThinking(thinkingSpec, config2.model);
29007
29282
  if (t.degraded) console.warn(`[thinking] ${t.note ?? "unsupported"} \u2014 falling back to provider default.`);
29008
29283
  else Object.assign(body, t.patch);
29009
29284
  }
@@ -29145,7 +29420,7 @@ var init_resolveStream = __esm({
29145
29420
  });
29146
29421
 
29147
29422
  // packages/core/dist/core/tools/toolOutputSpill.js
29148
- import { createHash as createHash2, randomBytes as randomBytes2 } from "node:crypto";
29423
+ import { createHash as createHash3, randomBytes as randomBytes2 } from "node:crypto";
29149
29424
  import { existsSync as existsSync12, mkdirSync as mkdirSync7, writeFileSync as writeFileSync11 } from "node:fs";
29150
29425
  import { homedir as homedir3, tmpdir } from "node:os";
29151
29426
  import { join as join11 } from "node:path";
@@ -29175,7 +29450,7 @@ function spillToolOutput(fullText, meta3) {
29175
29450
  if (!existsSync12(dir)) {
29176
29451
  mkdirSync7(dir, { recursive: true });
29177
29452
  }
29178
- const hash3 = createHash2("sha256").update(fullText).digest("hex").slice(0, 12);
29453
+ const hash3 = createHash3("sha256").update(fullText).digest("hex").slice(0, 12);
29179
29454
  const stamp = Date.now().toString(36);
29180
29455
  const rnd = randomBytes2(3).toString("hex");
29181
29456
  const safeTool = (meta3?.toolName ?? "tool").replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 32);
@@ -31582,9 +31857,9 @@ var init_store = __esm({
31582
31857
  import { promises as fs12, existsSync as existsSync17, readFileSync as readFileSync17 } from "node:fs";
31583
31858
  import { homedir as homedir5 } from "node:os";
31584
31859
  import path23 from "node:path";
31585
- import { createHash as createHash3 } from "node:crypto";
31860
+ import { createHash as createHash4 } from "node:crypto";
31586
31861
  function getIndexPath(root) {
31587
- const hash3 = createHash3("sha1").update(path23.resolve(root)).digest("hex").slice(0, 16);
31862
+ const hash3 = createHash4("sha1").update(path23.resolve(root)).digest("hex").slice(0, 16);
31588
31863
  return process.env.ZELARI_SEMANTIC_FILE ?? path23.join(homedir5(), ".tmp", "zelari-code", "semantic", `${hash3}.json`);
31589
31864
  }
31590
31865
  async function collectSourceFiles(root, maxFiles = 1500) {
@@ -33065,11 +33340,11 @@ function readStore3() {
33065
33340
  return DEFAULT_STORE;
33066
33341
  }
33067
33342
  }
33068
- function writeStore3(store3) {
33343
+ function writeStore3(store4) {
33069
33344
  const p3 = trustStorePath();
33070
33345
  try {
33071
33346
  mkdirSync11(path28.dirname(p3), { recursive: true });
33072
- writeFileSync13(p3, JSON.stringify(store3, null, 2), "utf8");
33347
+ writeFileSync13(p3, JSON.stringify(store4, null, 2), "utf8");
33073
33348
  } catch (err) {
33074
33349
  throw new Error(
33075
33350
  `failed to persist trust store ${p3}: ${err instanceof Error ? err.message : String(err)}`
@@ -33093,21 +33368,21 @@ function isFolderTrusted(folderPath) {
33093
33368
  return readStore3().folders.some((f) => normalize(f.path) === target);
33094
33369
  }
33095
33370
  function trustFolder(folderPath) {
33096
- const store3 = readStore3();
33371
+ const store4 = readStore3();
33097
33372
  const normalized = path28.resolve(folderPath);
33098
- if (!store3.folders.some((f) => normalize(f.path) === normalize(normalized))) {
33099
- store3.folders.push({ path: normalized, trustedAt: (/* @__PURE__ */ new Date()).toISOString() });
33100
- writeStore3(store3);
33373
+ if (!store4.folders.some((f) => normalize(f.path) === normalize(normalized))) {
33374
+ store4.folders.push({ path: normalized, trustedAt: (/* @__PURE__ */ new Date()).toISOString() });
33375
+ writeStore3(store4);
33101
33376
  }
33102
33377
  return { ok: true, path: normalized };
33103
33378
  }
33104
33379
  function untrustFolder(folderPath) {
33105
- const store3 = readStore3();
33380
+ const store4 = readStore3();
33106
33381
  const target = normalize(folderPath);
33107
- const before = store3.folders.length;
33108
- store3.folders = store3.folders.filter((f) => normalize(f.path) !== target);
33109
- if (store3.folders.length === before) return { ok: true, removed: false };
33110
- writeStore3(store3);
33382
+ const before = store4.folders.length;
33383
+ store4.folders = store4.folders.filter((f) => normalize(f.path) !== target);
33384
+ if (store4.folders.length === before) return { ok: true, removed: false };
33385
+ writeStore3(store4);
33111
33386
  return { ok: true, removed: true };
33112
33387
  }
33113
33388
  function listTrustedFolders() {
@@ -33208,7 +33483,7 @@ var init_lifecycleHooks = __esm({
33208
33483
  });
33209
33484
 
33210
33485
  // src/cli/toolResultCache.ts
33211
- import { createHash as createHash4 } from "node:crypto";
33486
+ import { createHash as createHash5 } from "node:crypto";
33212
33487
  import { promises as fs14 } from "node:fs";
33213
33488
  import path29 from "node:path";
33214
33489
  function isToolCacheEnabled() {
@@ -33221,7 +33496,7 @@ function resolveToolCacheTtlMs() {
33221
33496
  return Number.isFinite(n) && n >= 0 ? n : TOOL_CACHE_DEFAULT_TTL_MS;
33222
33497
  }
33223
33498
  function hashKey(parts) {
33224
- return createHash4("sha256").update(JSON.stringify(parts), "utf8").digest("hex");
33499
+ return createHash5("sha256").update(JSON.stringify(parts), "utf8").digest("hex");
33225
33500
  }
33226
33501
  function resultBytes(result) {
33227
33502
  try {
@@ -33902,7 +34177,7 @@ var init_toolRegistry = __esm({
33902
34177
  });
33903
34178
 
33904
34179
  // src/cli/state/fileStateStore.ts
33905
- import { createHash as createHash5, randomUUID as randomUUID2 } from "node:crypto";
34180
+ import { createHash as createHash6, randomUUID as randomUUID2 } from "node:crypto";
33906
34181
  import { promises as fs15 } from "node:fs";
33907
34182
  import * as path30 from "node:path";
33908
34183
  function shortId() {
@@ -33945,16 +34220,16 @@ function isStateEnabled(env = process.env) {
33945
34220
  }
33946
34221
  async function getStateStore(projectRoot, env = process.env) {
33947
34222
  if (!isStateEnabled(env)) return new NoopDurableStateStore();
33948
- const store3 = new FileDurableStateStore();
34223
+ const store4 = new FileDurableStateStore();
33949
34224
  try {
33950
- await store3.init(projectRoot);
33951
- return store3;
34225
+ await store4.init(projectRoot);
34226
+ return store4;
33952
34227
  } catch {
33953
34228
  return new NoopDurableStateStore();
33954
34229
  }
33955
34230
  }
33956
34231
  function hashStablePrompt(stable) {
33957
- return createHash5("sha256").update(stable, "utf8").digest("hex").slice(0, 16);
34232
+ return createHash6("sha256").update(stable, "utf8").digest("hex").slice(0, 16);
33958
34233
  }
33959
34234
  var DEFAULT_MATERIALIZE_CHARS, FileDurableStateStore, NoopDurableStateStore;
33960
34235
  var init_fileStateStore = __esm({
@@ -34285,29 +34560,6 @@ function extractiveHistorySummary(dropped, opts) {
34285
34560
  }
34286
34561
  return out;
34287
34562
  }
34288
- function formatDroppedForLlm(dropped) {
34289
- const lines = [];
34290
- for (const m of dropped) {
34291
- if (m.role === "user") {
34292
- lines.push(`USER: ${oneLine(m.content, 400)}`);
34293
- } else if (m.role === "assistant") {
34294
- const tools = m.toolCalls?.map((t) => t.name).join(",") || "";
34295
- const body2 = oneLine(m.content, 300);
34296
- lines.push(
34297
- tools ? `ASSISTANT(tools=${tools}): ${body2}` : `ASSISTANT: ${body2}`
34298
- );
34299
- } else if (m.role === "tool") {
34300
- lines.push(`TOOL(${m.toolCallId ?? "?"}): ${oneLine(m.content, 160)}`);
34301
- } else if (m.role === "system") {
34302
- lines.push(`SYSTEM: ${oneLine(m.content, 200)}`);
34303
- }
34304
- }
34305
- let body = lines.join("\n");
34306
- if (body.length > MAX_LLM_INPUT_CHARS) {
34307
- body = body.slice(body.length - MAX_LLM_INPUT_CHARS);
34308
- }
34309
- return body;
34310
- }
34311
34563
  function oneLine(s, max) {
34312
34564
  const t = s.replace(/\s+/g, " ").trim();
34313
34565
  if (t.length <= max) return t;
@@ -34333,12 +34585,11 @@ function collectPathsFromText(text, out) {
34333
34585
  n += 1;
34334
34586
  }
34335
34587
  }
34336
- var MAX_SUMMARY_CHARS, MAX_LLM_INPUT_CHARS;
34588
+ var MAX_SUMMARY_CHARS;
34337
34589
  var init_historySummary = __esm({
34338
34590
  "src/cli/budget/historySummary.ts"() {
34339
34591
  "use strict";
34340
34592
  MAX_SUMMARY_CHARS = 3500;
34341
- MAX_LLM_INPUT_CHARS = 24e3;
34342
34593
  }
34343
34594
  });
34344
34595
 
@@ -34348,92 +34599,87 @@ function isLlmCompactEnabled() {
34348
34599
  if (v === "0" || v === "false" || v === "off" || v === "no") return false;
34349
34600
  return true;
34350
34601
  }
34351
- async function llmSummarizeHistory(input) {
34352
- if (!isLlmCompactEnabled()) return null;
34353
- if (!input.droppedTranscript.trim()) return null;
34354
- let config2 = null;
34355
- try {
34356
- config2 = await resolveCompactProviderConfig();
34357
- } catch {
34358
- return null;
34602
+ function compactModelOverride() {
34603
+ const v = process.env.ZELARI_COMPACT_MODEL?.trim();
34604
+ return v ? v : void 0;
34605
+ }
34606
+ async function llmSummarizeHistoryReplay(input) {
34607
+ const override = input.overrideModel ?? compactModelOverride();
34608
+ const model = override ?? input.model;
34609
+ const cacheReuseExpected = !override;
34610
+ if (!isLlmCompactEnabled()) return { summary: null, model, cacheReuseExpected };
34611
+ if (input.droppedMessages.length === 0) {
34612
+ return { summary: null, model, cacheReuseExpected };
34359
34613
  }
34360
- if (!config2) return null;
34361
- const model = process.env.ZELARI_COMPACT_MODEL?.trim() || config2.model;
34614
+ const messages = [
34615
+ ...input.systemMessages,
34616
+ ...input.droppedMessages,
34617
+ {
34618
+ role: "user",
34619
+ content: COMPACTION_INSTRUCTION
34620
+ }
34621
+ ];
34362
34622
  const controller = new AbortController();
34363
- const timeout = setTimeout(() => controller.abort(), 45e3);
34623
+ const timeout = setTimeout(() => controller.abort(), REPLAY_TIMEOUT_MS);
34364
34624
  const onOuterAbort = () => controller.abort();
34365
34625
  input.signal?.addEventListener("abort", onOuterAbort, { once: true });
34366
34626
  try {
34367
- const url2 = `${config2.baseUrl.replace(/\/$/, "")}/chat/completions`;
34368
- const res = await fetch(url2, {
34369
- method: "POST",
34627
+ let text = "";
34628
+ let emittedToolCall = false;
34629
+ for await (const delta of input.providerStream({
34630
+ provider: input.provider,
34631
+ model,
34632
+ messages,
34633
+ // Tools stay advertised: dropping them would change the prefix token
34634
+ // sequence and destroy cache reuse (explicit DSH decision). They are
34635
+ // sorted canonically (same discipline as the live routed request and
34636
+ // the snapshot fingerprints) so the replay prefix is byte-identical.
34637
+ tools: [...input.tools].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0),
34370
34638
  signal: controller.signal,
34371
- headers: {
34372
- "content-type": "application/json",
34373
- authorization: `Bearer ${config2.apiKey}`
34374
- },
34375
- body: JSON.stringify({
34376
- model,
34639
+ generation: {
34640
+ purpose: "compaction",
34377
34641
  temperature: 0.1,
34378
- max_tokens: 900,
34379
- stream: false,
34380
- messages: [
34381
- { role: "system", content: COMPACT_SYSTEM },
34382
- {
34383
- role: "user",
34384
- content: `Extractive sketch (may be incomplete):
34385
- ${input.extractive.slice(0, 2500)}
34386
-
34387
- Transcript of dropped turns:
34388
- ${input.droppedTranscript}`
34389
- }
34390
- ]
34391
- })
34392
- });
34393
- if (!res.ok) return null;
34394
- const json2 = await res.json();
34395
- const text = json2.choices?.[0]?.message?.content?.trim();
34396
- if (!text) return null;
34397
- return "[history-summary \xB7 llm]\n" + text + "\n\nContinue from the recent messages below; honor decisions already made above.";
34642
+ maxTokens: 900
34643
+ }
34644
+ })) {
34645
+ if (delta.kind === "text") text += delta.delta;
34646
+ if (delta.kind === "tool_call") emittedToolCall = true;
34647
+ }
34648
+ if (emittedToolCall) return { summary: null, model, cacheReuseExpected };
34649
+ if (!text.trim()) return { summary: null, model, cacheReuseExpected };
34650
+ return { summary: text.trim(), model, cacheReuseExpected };
34398
34651
  } catch {
34399
- return null;
34652
+ return { summary: null, model, cacheReuseExpected };
34400
34653
  } finally {
34401
34654
  clearTimeout(timeout);
34402
34655
  input.signal?.removeEventListener("abort", onOuterAbort);
34403
34656
  }
34404
34657
  }
34405
- async function resolveCompactProviderConfig() {
34406
- const active = getProviderConfig().activeProviderId;
34407
- const meta3 = await resolveApiKeyWithMeta(active);
34408
- const apiKey = meta3?.apiKey;
34409
- if (!apiKey) return null;
34410
- const custom2 = getCustomEndpoint(active);
34411
- let baseUrl = custom2 || (active === "openai-compatible" || active === "custom" ? process.env.OPENAI_BASE_URL ?? PROVIDER_ENDPOINTS[active] : PROVIDER_ENDPOINTS[active]);
34412
- if (!baseUrl) return null;
34413
- const model = getModelForProvider(active);
34414
- return {
34415
- apiKey,
34416
- baseUrl,
34417
- model,
34418
- providerId: active
34419
- };
34420
- }
34421
- var COMPACT_SYSTEM;
34658
+ var COMPACTION_INSTRUCTION, REPLAY_TIMEOUT_MS;
34422
34659
  var init_llmCompact = __esm({
34423
34660
  "src/cli/budget/llmCompact.ts"() {
34424
34661
  "use strict";
34425
- init_providerConfig();
34426
- init_keyStore();
34427
- init_openai_compatible();
34428
- COMPACT_SYSTEM = `You compress earlier turns of a coding-agent session into a dense continuity brief.
34429
- Output plain text (no markdown fences) with these sections:
34430
- 1) Goal \u2014 what the user wants
34431
- 2) Decisions \u2014 choices already made
34432
- 3) Done \u2014 completed work / files changed
34433
- 4) Open \u2014 remaining tasks / blockers
34434
- 5) Constraints \u2014 important rules the agent must keep
34662
+ COMPACTION_INSTRUCTION = `
34663
+ You are now acting as a compaction engine for this coding-agent session.
34435
34664
 
34436
- Be factual and concise. Max ~400 words. Do not invent work that was not present.`;
34665
+ Condense the conversation ABOVE into a compact checkpoint sufficient to continue the task.
34666
+
34667
+ Preserve:
34668
+ - user's goal and evolving intent
34669
+ - decisions already made
34670
+ - exact file paths and identifiers
34671
+ - code changes already completed
34672
+ - commands/errors that still matter
34673
+ - constraints
34674
+ - unfinished work
34675
+ - the single most likely next action
34676
+
34677
+ Do not call tools.
34678
+ Do not mention this summarization request.
34679
+ Output only the checkpoint.
34680
+ Be concise.
34681
+ `.trim();
34682
+ REPLAY_TIMEOUT_MS = 6e4;
34437
34683
  }
34438
34684
  });
34439
34685
 
@@ -34472,6 +34718,7 @@ function resolveMaxMessages(opts) {
34472
34718
  turns = Math.min(turns, 3);
34473
34719
  }
34474
34720
  if (turns <= 0) return 0;
34721
+ if (opts?.force && opts?.maxMessages) return Math.max(1, opts.maxMessages);
34475
34722
  return turns * 4;
34476
34723
  }
34477
34724
  function findValidCutIndex(messages, naiveCut) {
@@ -34537,12 +34784,18 @@ function pruneToolResultsDetailed(messages, opts) {
34537
34784
  function compactHistory(messages, opts) {
34538
34785
  return compactHistoryDetailed(messages, opts).messages;
34539
34786
  }
34787
+ function buildCheckpointMessage(summaryText) {
34788
+ return {
34789
+ role: "user",
34790
+ content: CHECKPOINT_WRAPPER_PREFIX + "\n\n<compacted-summary>\n" + summaryText + "\n</compacted-summary>"
34791
+ };
34792
+ }
34540
34793
  function compactHistoryDetailed(messages, opts) {
34541
34794
  const maxMessages = resolveMaxMessages(opts);
34542
34795
  if (maxMessages === 0) {
34543
34796
  return { messages: [], compacted: true, messagesRemoved: messages.length, summary: "" };
34544
34797
  }
34545
- if (messages.length <= maxMessages * 2) {
34798
+ if (messages.length <= maxMessages * 2 && !opts?.force) {
34546
34799
  return {
34547
34800
  messages,
34548
34801
  compacted: false,
@@ -34550,7 +34803,7 @@ function compactHistoryDetailed(messages, opts) {
34550
34803
  summary: ""
34551
34804
  };
34552
34805
  }
34553
- const naiveCut = messages.length - maxMessages;
34806
+ const naiveCut = Math.max(0, messages.length - maxMessages);
34554
34807
  const cut = findValidCutIndex(messages, naiveCut);
34555
34808
  if (cut === 0) {
34556
34809
  return {
@@ -34564,10 +34817,9 @@ function compactHistoryDetailed(messages, opts) {
34564
34817
  const pruned = pruneToolResultsDetailed(messages.slice(cut));
34565
34818
  const kept = pruned.messages;
34566
34819
  const summaryText = extractiveHistorySummary(droppedMsgs);
34567
- const summary = {
34568
- role: "system",
34569
- content: summaryText || `${COMPACT_MARKER} ${cut} earlier message(s) dropped.`
34570
- };
34820
+ const summary = buildCheckpointMessage(
34821
+ summaryText || `${COMPACT_MARKER} ${cut} earlier message(s) dropped.`
34822
+ );
34571
34823
  return {
34572
34824
  messages: [summary, ...kept],
34573
34825
  compacted: true,
@@ -34582,29 +34834,58 @@ async function compactHistoryAsync(messages, opts) {
34582
34834
  const cut = base.messagesRemoved;
34583
34835
  const droppedMsgs = messages.slice(0, cut);
34584
34836
  const extractive = extractiveHistorySummary(droppedMsgs);
34585
- const droppedTranscript = formatDroppedForLlm(droppedMsgs);
34586
34837
  let summaryText = extractive;
34587
- try {
34588
- const llm = await llmSummarizeHistory({
34589
- extractive,
34590
- droppedTranscript,
34591
- signal: opts?.signal
34592
- });
34593
- if (llm && llm.trim().length > 40) summaryText = llm.trim();
34594
- } catch {
34838
+ let cacheReuseExpected;
34839
+ let replayExactPrefix;
34840
+ const canReplay = !!(opts?.providerStream && opts?.requestSnapshot);
34841
+ if (canReplay) {
34842
+ try {
34843
+ const replay = await llmSummarizeHistoryReplay({
34844
+ providerStream: opts.providerStream,
34845
+ provider: opts.requestSnapshot.provider,
34846
+ model: opts.requestSnapshot.model,
34847
+ systemMessages: opts.requestSnapshot.systemMessages,
34848
+ tools: opts.requestSnapshot.tools,
34849
+ droppedMessages: droppedMsgs,
34850
+ signal: opts?.signal
34851
+ });
34852
+ cacheReuseExpected = replay.cacheReuseExpected;
34853
+ if (replay.summary && replay.summary.trim().length > 40) {
34854
+ const sourceTokens = roughTokens(droppedMsgs);
34855
+ const summaryTok = Math.ceil(replay.summary.length / 4);
34856
+ if (summaryTok < sourceTokens) {
34857
+ summaryText = replay.summary.trim();
34858
+ }
34859
+ }
34860
+ } catch {
34861
+ }
34595
34862
  }
34596
34863
  const pruned = pruneToolResultsDetailed(messages.slice(cut));
34597
34864
  const kept = pruned.messages;
34598
- const summary = { role: "system", content: summaryText };
34865
+ const summary = buildCheckpointMessage(summaryText);
34599
34866
  return {
34600
34867
  messages: [summary, ...kept],
34601
34868
  compacted: true,
34602
34869
  messagesRemoved: cut,
34603
34870
  summary: summaryText,
34604
- prunedToolResults: pruned.stats.pruned
34871
+ prunedToolResults: pruned.stats.pruned,
34872
+ cacheReuseExpected,
34873
+ replayExactPrefix
34605
34874
  };
34606
34875
  }
34607
- var COMPACT_MARKER;
34876
+ function roughTokens(msgs) {
34877
+ let n = 0;
34878
+ for (const m of msgs) {
34879
+ n += Math.ceil((m.content ?? "").length / 4);
34880
+ if (m.toolCalls) {
34881
+ for (const tc of m.toolCalls) {
34882
+ n += Math.ceil(JSON.stringify(tc.args ?? {}).length / 4);
34883
+ }
34884
+ }
34885
+ }
34886
+ return Math.max(1, n);
34887
+ }
34888
+ var COMPACT_MARKER, CHECKPOINT_WRAPPER_PREFIX;
34608
34889
  var init_historyCompaction = __esm({
34609
34890
  "src/cli/hooks/historyCompaction.ts"() {
34610
34891
  "use strict";
@@ -34612,6 +34893,30 @@ var init_historyCompaction = __esm({
34612
34893
  init_llmCompact();
34613
34894
  init_envNumber();
34614
34895
  COMPACT_MARKER = "[history] Earlier turns were compacted to stay within the context budget.";
34896
+ CHECKPOINT_WRAPPER_PREFIX = "This is an automatically generated checkpoint of earlier conversation. Treat it as established context and continue directly.";
34897
+ }
34898
+ });
34899
+
34900
+ // src/cli/budget/requestSnapshotStore.ts
34901
+ function recordRequestSnapshot(sessionId, snapshot) {
34902
+ store3.set(sessionId, { snapshot });
34903
+ }
34904
+ function recordRequestUsage(sessionId, usage) {
34905
+ const entry = store3.get(sessionId);
34906
+ if (!entry) return;
34907
+ entry.usage = usage;
34908
+ }
34909
+ function getRequestSnapshotWithUsage(sessionId) {
34910
+ return store3.get(sessionId) ?? null;
34911
+ }
34912
+ function clearAllRequestSnapshots() {
34913
+ store3.clear();
34914
+ }
34915
+ var store3;
34916
+ var init_requestSnapshotStore = __esm({
34917
+ "src/cli/budget/requestSnapshotStore.ts"() {
34918
+ "use strict";
34919
+ store3 = /* @__PURE__ */ new Map();
34615
34920
  }
34616
34921
  });
34617
34922
 
@@ -34656,6 +34961,7 @@ function appendMessages(msgs) {
34656
34961
  function clearHistory() {
34657
34962
  history = [];
34658
34963
  lastClarification = null;
34964
+ clearAllRequestSnapshots();
34659
34965
  clearSessionTodos();
34660
34966
  clearSessionPermissionGrants();
34661
34967
  }
@@ -34814,6 +35120,7 @@ var init_conversationContext = __esm({
34814
35120
  init_toolPermissions();
34815
35121
  init_sessionTodos();
34816
35122
  init_historyCompaction();
35123
+ init_requestSnapshotStore();
34817
35124
  history = [];
34818
35125
  lastClarification = null;
34819
35126
  SHORT_CONTINUE = /^(procedi|continua|continue|go\s*ahead|go|ok|okay|sì|si|yes|vai|avanti|next|proceed|conferma|confermo|applica|fai|scrivi|esegui|implementa|vai pure|fai pure|ok procedi|sì procedi|si procedi)$/i;
@@ -35212,7 +35519,7 @@ import {
35212
35519
  } from "node:fs";
35213
35520
  import { join as join18, basename } from "node:path";
35214
35521
  import { homedir as homedir9 } from "node:os";
35215
- import { createHash as createHash6 } from "node:crypto";
35522
+ import { createHash as createHash7 } from "node:crypto";
35216
35523
  function resolveWorkspaceRoot(projectRoot = process.cwd()) {
35217
35524
  const candidates = [
35218
35525
  join18(projectRoot, ".zelari"),
@@ -35228,7 +35535,7 @@ function resolveWorkspaceRoot(projectRoot = process.cwd()) {
35228
35535
  return candidates[0];
35229
35536
  }
35230
35537
  function hashProject(projectPath) {
35231
- return createHash6("sha1").update(realpathSync(projectPath)).digest("hex").slice(0, 12);
35538
+ return createHash7("sha1").update(realpathSync(projectPath)).digest("hex").slice(0, 12);
35232
35539
  }
35233
35540
  function isWritableDir(dir) {
35234
35541
  try {
@@ -35819,8 +36126,8 @@ async function loadDurableContext(projectRoot, opts) {
35819
36126
  return cache.text;
35820
36127
  }
35821
36128
  try {
35822
- const store3 = await getStateStore(projectRoot, env);
35823
- const text = await store3.materializeContext(void 0, opts?.maxChars);
36129
+ const store4 = await getStateStore(projectRoot, env);
36130
+ const text = await store4.materializeContext(void 0, opts?.maxChars);
35824
36131
  cache = { text: text || "", at: now, projectRoot };
35825
36132
  return cache.text;
35826
36133
  } catch {
@@ -37740,7 +38047,7 @@ __export(agentsMd_exports, {
37740
38047
  updateAgentsMd: () => updateAgentsMd
37741
38048
  });
37742
38049
  import { existsSync as existsSync32, readFileSync as readFileSync28, writeFileSync as writeFileSync18 } from "node:fs";
37743
- import { createHash as createHash7 } from "node:crypto";
38050
+ import { createHash as createHash8 } from "node:crypto";
37744
38051
  import { join as join27 } from "node:path";
37745
38052
  import { readFile as readFile3 } from "node:fs/promises";
37746
38053
  async function readPackageJson2(projectRoot) {
@@ -37955,7 +38262,7 @@ async function updateAgentsMd(ctx, projectRoot) {
37955
38262
  return { changed: true, sections: changedSections };
37956
38263
  }
37957
38264
  function hash2(s) {
37958
- return createHash7("sha256").update(s).digest("hex").slice(0, 16);
38265
+ return createHash8("sha256").update(s).digest("hex").slice(0, 16);
37959
38266
  }
37960
38267
  var AUTO_SECTIONS, MARKER_OPEN, MARKER_CLOSE, GENERATORS;
37961
38268
  var init_agentsMd = __esm({
@@ -38766,7 +39073,7 @@ __export(commitHelpers_exports, {
38766
39073
  });
38767
39074
  async function tryStateCommit(args) {
38768
39075
  try {
38769
- const store3 = args.store ?? await getStateStore(args.projectRoot, args.env);
39076
+ const store4 = args.store ?? await getStateStore(args.projectRoot, args.env);
38770
39077
  let workspaceCheckpointId = args.workspaceCheckpointId;
38771
39078
  if (!workspaceCheckpointId && args.withCheckpoint && (args.env ?? process.env).ZELARI_CHECKPOINT !== "0") {
38772
39079
  const cp = await createCheckpoint(
@@ -38775,7 +39082,7 @@ async function tryStateCommit(args) {
38775
39082
  );
38776
39083
  if (cp.ok) workspaceCheckpointId = cp.value.id;
38777
39084
  }
38778
- const meta3 = await store3.commit({
39085
+ const meta3 = await store4.commit({
38779
39086
  mode: args.mode,
38780
39087
  label: args.label,
38781
39088
  layer: args.layer,
@@ -42783,9 +43090,9 @@ var init_oauthDesktop = __esm({
42783
43090
  init_chatgptOAuth();
42784
43091
  init_anthropicOAuth();
42785
43092
  DEFAULT_MODELS = {
42786
- grok: "grok-4.5",
42787
- chatgpt: "gpt-5.2-codex",
42788
- anthropic: "claude-sonnet-4-5"
43093
+ grok: "grok-4.6",
43094
+ chatgpt: "gpt-5.6-codex",
43095
+ anthropic: "claude-sonnet-4-6"
42789
43096
  };
42790
43097
  }
42791
43098
  });
@@ -43045,12 +43352,12 @@ function handleModelSet(ctx, model) {
43045
43352
  }
43046
43353
  function handleEffortShow(ctx) {
43047
43354
  const id = ctx.activeProviderSpec.id;
43048
- const cap3 = thinkingCapabilityFor(id);
43355
+ const cap3 = thinkingCapabilityFor(id, ctx.activeModel);
43049
43356
  const current = stringifyThinkingSpec(getThinkingForProvider(id));
43050
43357
  const options = [
43051
43358
  "auto",
43052
43359
  "off",
43053
- ...cap3.effort ? ["low", "medium", "high"] : [],
43360
+ ...cap3.efforts ?? (cap3.effort ? ["low", "medium", "high"] : []),
43054
43361
  ...cap3.budget ? ["budget:<tokens>"] : []
43055
43362
  ];
43056
43363
  appendSystem(
@@ -43063,7 +43370,7 @@ function handleEffortSet(ctx, raw) {
43063
43370
  if (!isValidThinkingInput(raw)) {
43064
43371
  appendSystem(
43065
43372
  ctx.setMessages,
43066
- `[effort] invalid spec "${raw}" \u2014 use auto | off | low | medium | high | budget:<tokens>`
43373
+ `[effort] invalid spec "${raw}" \u2014 use auto | off | low | medium | high | xhigh | max | budget:<tokens>`
43067
43374
  );
43068
43375
  return;
43069
43376
  }
@@ -43624,6 +43931,11 @@ function buildDesktopConfigSnapshot() {
43624
43931
  const providers = PROVIDERS.map((p3) => {
43625
43932
  const cached2 = getCachedModels(p3.id);
43626
43933
  const models = cached2?.models.map((m) => m.id) ?? [];
43934
+ if (models.length === 0) {
43935
+ for (const m of getStaticFallbackModels(p3.id)) {
43936
+ if (!models.includes(m.id)) models.push(m.id);
43937
+ }
43938
+ }
43627
43939
  const defaultModel = config2.modelByProvider[p3.id] ?? "";
43628
43940
  if (defaultModel && !models.includes(defaultModel)) {
43629
43941
  models.unshift(defaultModel);
@@ -43647,7 +43959,7 @@ function buildDesktopConfigSnapshot() {
43647
43959
  hasRefreshToken: Boolean(stored?.refreshToken),
43648
43960
  oauthSupported: isOAuthProvider(p3.id),
43649
43961
  thinking: config2.thinkingByProvider[p3.id] ?? "auto",
43650
- thinkingCapability: thinkingCapabilityFor(p3.id)
43962
+ thinkingCapability: thinkingCapabilityFor(p3.id, defaultModel)
43651
43963
  };
43652
43964
  });
43653
43965
  return {
@@ -44319,7 +44631,7 @@ import {
44319
44631
  } from "node:fs";
44320
44632
  import { join as join36 } from "node:path";
44321
44633
  import { homedir as homedir13 } from "node:os";
44322
- import { createHash as createHash8, randomBytes as randomBytes5, timingSafeEqual } from "node:crypto";
44634
+ import { createHash as createHash9, randomBytes as randomBytes5, timingSafeEqual } from "node:crypto";
44323
44635
  function getZelariHome() {
44324
44636
  return join36(homedir13(), ".zelari-code");
44325
44637
  }
@@ -44395,8 +44707,8 @@ function loadOrCreateToken(explicit) {
44395
44707
  }
44396
44708
  function tokenMatches(expected, provided) {
44397
44709
  if (!provided) return false;
44398
- const a = createHash8("sha256").update(expected).digest();
44399
- const b = createHash8("sha256").update(provided).digest();
44710
+ const a = createHash9("sha256").update(expected).digest();
44711
+ const b = createHash9("sha256").update(provided).digest();
44400
44712
  try {
44401
44713
  return timingSafeEqual(a, b);
44402
44714
  } catch {
@@ -49063,34 +49375,58 @@ function phaseKnobs(phase2) {
49063
49375
  })
49064
49376
  };
49065
49377
  }
49066
- function occupancyOf(hist, sessionExtra, contextLimit) {
49067
- const estimated = estimateHistoryTokens(hist);
49068
- const occupancy = Math.min(1, (estimated + sessionExtra) / contextLimit);
49069
- return { estimated, occupancy };
49070
- }
49378
+ var RESERVED_OUTPUT_TOKENS = 8192;
49071
49379
  async function applyBudgetPolicyAsync(history2, phase2, opts) {
49072
49380
  const contextLimit = resolveContextLimit(opts?.model);
49073
49381
  const sessionExtra = opts?.sessionTokens ?? 0;
49074
49382
  const warnings = [];
49075
49383
  let { historyTurns, maxToolLoopIterations } = phaseKnobs(phase2);
49384
+ const envelope = opts?.requestSnapshot ?? null;
49385
+ const replayBase = envelope ? {
49386
+ provider: envelope.snapshot.provider,
49387
+ model: envelope.snapshot.model,
49388
+ systemMessages: envelope.snapshot.systemMessages,
49389
+ tools: envelope.snapshot.tools
49390
+ } : opts?.providerStream ? { provider: "local", model: opts?.model ?? "unknown", systemMessages: [], tools: [] } : null;
49391
+ const headerTokens = envelope ? estimateSystemTokensLite(envelope.snapshot.systemMessages) + estimateToolSchemaTokensLite(envelope.snapshot.tools) : 0;
49392
+ const convTokensOf = (h) => estimateConversationTokensLite(h);
49076
49393
  let hist = history2;
49077
- let { estimated, occupancy } = occupancyOf(hist, sessionExtra, contextLimit);
49394
+ let estimated = envelope ? headerTokens + convTokensOf(hist) + RESERVED_OUTPUT_TOKENS : estimateHistoryTokens(hist) + sessionExtra;
49395
+ let occupancy = Math.min(1, estimated / contextLimit);
49078
49396
  let compactSummary = "";
49079
49397
  let messagesRemoved = 0;
49398
+ let cacheReuseExpected;
49399
+ let prunedTotal = 0;
49080
49400
  if (occupancy >= 0.7 && occupancy < 0.85) {
49081
49401
  warnings.push(
49082
- `[budget] context ~${Math.round(occupancy * 100)}% full (${estimated + sessionExtra}/${contextLimit} tok est.) \u2014 consider /compact or shorter replies.`
49402
+ `[budget] context ~${Math.round(occupancy * 100)}% full (${estimated}/${contextLimit} tok full-request est.) \u2014 consider /compact or shorter replies.`
49083
49403
  );
49084
49404
  }
49405
+ if (occupancy >= 0.8) {
49406
+ const pruned = pruneToolResultsDetailed(hist);
49407
+ if (pruned.stats.pruned > 0) {
49408
+ hist = pruned.messages;
49409
+ prunedTotal += pruned.stats.pruned;
49410
+ estimated = envelope ? headerTokens + convTokensOf(hist) + RESERVED_OUTPUT_TOKENS : estimateHistoryTokens(hist) + sessionExtra;
49411
+ occupancy = Math.min(1, estimated / contextLimit);
49412
+ warnings.push(
49413
+ `[budget] pruned ${pruned.stats.pruned} oversized tool result(s) \u2192 ${Math.round(occupancy * 100)}% (${estimated} tok).`
49414
+ );
49415
+ }
49416
+ }
49085
49417
  const fold = (r, label, forcedTurns) => {
49086
49418
  hist = r.messages;
49087
49419
  if (r.compacted) {
49088
49420
  messagesRemoved += r.messagesRemoved;
49089
49421
  if (r.summary) compactSummary = r.summary;
49422
+ if (r.cacheReuseExpected !== void 0) {
49423
+ cacheReuseExpected = r.cacheReuseExpected;
49424
+ }
49090
49425
  }
49091
- ({ estimated, occupancy } = occupancyOf(hist, sessionExtra, contextLimit));
49426
+ estimated = envelope ? headerTokens + convTokensOf(hist) + RESERVED_OUTPUT_TOKENS : estimateHistoryTokens(hist) + sessionExtra;
49427
+ occupancy = Math.min(1, estimated / contextLimit);
49092
49428
  warnings.push(
49093
- `[budget] ${label} at ${forcedTurns === 2 ? "95%" : "85%"} \u2014 kept ~${forcedTurns} turns (${estimated} tok history est.` + (r.messagesRemoved ? `, removed ${r.messagesRemoved} msgs` : "") + `).`
49429
+ `[budget] ${label} \u2014 kept ~${forcedTurns} turns (${estimated} tok est.` + (r.messagesRemoved ? `, removed ${r.messagesRemoved} msgs` : "") + (r.cacheReuseExpected === false ? ", cache reuse NOT expected (model override)" : "") + ")."
49094
49430
  );
49095
49431
  };
49096
49432
  if (occupancy >= 0.85) {
@@ -49100,39 +49436,91 @@ async function applyBudgetPolicyAsync(history2, phase2, opts) {
49100
49436
  maxToolLoopIterations,
49101
49437
  phase2 === "plan" ? 24 : 40
49102
49438
  );
49103
- const r = await compactHistoryAsync(hist, {
49104
- maxMessages: forcedTurns * 4,
49105
- signal: opts?.signal
49106
- });
49107
- const label = r.summary.includes("\xB7 llm") ? "llm-compact" : "auto-compact";
49439
+ let r = await compactHistoryAsync(hist, {
49440
+ maxMessages: Math.max(2, forcedTurns * 4),
49441
+ force: true,
49442
+ signal: opts?.signal,
49443
+ ...replayBase ? { requestSnapshot: replayBase, providerStream: opts?.providerStream } : {}
49444
+ });
49445
+ if (!r.compacted) {
49446
+ r = await compactHistoryAsync(hist, {
49447
+ maxMessages: 2,
49448
+ force: true,
49449
+ signal: opts?.signal,
49450
+ ...replayBase ? { requestSnapshot: replayBase, providerStream: opts?.providerStream } : {}
49451
+ });
49452
+ }
49453
+ const label = r.cacheReuseExpected === false ? "llm-compact (model override)" : "auto-compact";
49108
49454
  fold(r, label, forcedTurns);
49109
49455
  }
49110
49456
  if (occupancy >= 0.95) {
49111
49457
  const hard = await compactHistoryAsync(hist, {
49112
- maxMessages: 8,
49458
+ maxMessages: 2,
49459
+ force: true,
49113
49460
  signal: opts?.signal
49114
49461
  });
49115
- fold(hard, hard.summary.includes("\xB7 llm") ? "llm-compact" : "auto-compact", 2);
49462
+ fold(hard, hard.summary.includes("\xB7 llm") ? "llm-compact" : "HARD trim", 2);
49116
49463
  historyTurns = 2;
49117
49464
  maxToolLoopIterations = Math.min(maxToolLoopIterations, 16);
49118
49465
  warnings.push(
49119
- `[budget] HARD context pressure (\u226595%) \u2014 prefer /clear or a new session if quality drops.`
49466
+ "[budget] HARD context pressure (\u226595%) \u2014 prefer /clear or a new session if quality drops."
49120
49467
  );
49121
49468
  }
49469
+ const cacheMetricsLine = envelope ? [
49470
+ "compaction meter:",
49471
+ `provider/model: ${envelope.snapshot.provider}/${envelope.snapshot.model}`,
49472
+ `headerFingerprint: ${envelope.snapshot.headerFingerprint.slice(0, 12)}`,
49473
+ `occupancy: ${Math.round(occupancy * 100)}% (${estimated}/${contextLimit})`,
49474
+ ...envelope.usage?.cachedPromptTokens !== void 0 ? [`cachedPromptTokens: ${envelope.usage.cachedPromptTokens}`] : [],
49475
+ ...cacheReuseExpected !== void 0 ? [`cacheReuseExpected: ${cacheReuseExpected}`] : []
49476
+ ].join(" | ") : void 0;
49122
49477
  return {
49123
49478
  history: hist,
49124
49479
  warnings,
49125
49480
  maxToolLoopIterations,
49126
49481
  historyTurns,
49127
- estimatedHistoryTokens: estimated,
49482
+ estimatedHistoryTokens: envelope ? convTokensOf(hist) : estimated,
49128
49483
  contextLimit,
49129
49484
  occupancy,
49130
49485
  compactSummary: compactSummary || void 0,
49131
- messagesRemoved: messagesRemoved || void 0
49486
+ messagesRemoved: messagesRemoved || void 0,
49487
+ ...envelope ? { contextPressureTokens: estimated } : {},
49488
+ ...cacheReuseExpected !== void 0 ? { cacheReuseExpected } : {},
49489
+ ...cacheMetricsLine ? { cacheMetricsLine } : {}
49132
49490
  };
49133
49491
  }
49492
+ function estimateSystemTokensLite(systemMessages) {
49493
+ let n = 0;
49494
+ for (const m of systemMessages) n += 4 + Math.ceil((m.content ?? "").length / 4);
49495
+ return n;
49496
+ }
49497
+ function estimateToolSchemaTokensLite(tools) {
49498
+ let n = 0;
49499
+ for (const t of tools) {
49500
+ n += Math.ceil((t.name ?? "").length / 4);
49501
+ n += Math.ceil((t.description ?? "").length / 4);
49502
+ n += Math.ceil(JSON.stringify(t.parameters ?? {}).length / 4);
49503
+ }
49504
+ return n + tools.length * 4;
49505
+ }
49506
+ function estimateConversationTokensLite(messages) {
49507
+ let n = 0;
49508
+ for (const m of messages) {
49509
+ n += 4 + Math.ceil((m.content ?? "").length / 4);
49510
+ if (m.toolCalls) {
49511
+ for (const tc of m.toolCalls) {
49512
+ n += Math.ceil(tc.name.length / 4) + Math.ceil(tc.id.length / 4);
49513
+ n += Math.ceil(JSON.stringify(tc.args ?? {}).length / 4);
49514
+ }
49515
+ }
49516
+ if (m.reasoningContent) n += Math.ceil(m.reasoningContent.length / 4);
49517
+ if (m.toolCallId) n += Math.ceil(m.toolCallId.length / 4);
49518
+ }
49519
+ return n;
49520
+ }
49134
49521
 
49135
49522
  // src/cli/hooks/useChatTurn.ts
49523
+ init_requestSnapshotStore();
49136
49524
  function useChatTurn(params) {
49137
49525
  const {
49138
49526
  sessionId,
@@ -49159,17 +49547,9 @@ function useChatTurn(params) {
49159
49547
  let envConfig;
49160
49548
  let harness;
49161
49549
  let historySeedLen = 0;
49550
+ let systemPrefixLen = 0;
49162
49551
  let turnSucceeded = false;
49163
49552
  try {
49164
- compactInPlace();
49165
- const budget = await applyBudgetPolicyAsync(getHistory(), getPhase(), {
49166
- model: getActiveModel()
49167
- });
49168
- setHistory(budget.history);
49169
- for (const w of budget.warnings) {
49170
- appendSystem(setMessages, w, Date.now());
49171
- }
49172
- historySeedLen = getHistory().length;
49173
49553
  const anchored = maybeAnchorShortAnswer(userText);
49174
49554
  const effectiveUserText = anchored ?? userText;
49175
49555
  const localCli = (process.env.ZELARI_LOCAL_CLI ?? "").trim();
@@ -49264,6 +49644,33 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
49264
49644
  });
49265
49645
  }
49266
49646
  const cwd = process.cwd();
49647
+ const budget = await applyBudgetPolicyAsync(getHistory(), getPhase(), {
49648
+ model: getActiveModel(),
49649
+ sessionId,
49650
+ // v1.36.0: envelope for full-request metering + cache-aware
49651
+ // compaction replay (last warm prefix + provider usage anchor).
49652
+ requestSnapshot: getRequestSnapshotWithUsage(sessionId),
49653
+ providerStream
49654
+ });
49655
+ setHistory(budget.history);
49656
+ for (const w of budget.warnings) {
49657
+ appendSystem(setMessages, w, Date.now());
49658
+ }
49659
+ if ((budget.messagesRemoved ?? 0) > 0) {
49660
+ const envelope = getRequestSnapshotWithUsage(sessionId);
49661
+ const compactionEvent = createBrainEvent("session_compacted", sessionId, {
49662
+ summary: budget.compactSummary ?? "",
49663
+ messagesRemoved: budget.messagesRemoved ?? 0,
49664
+ ...envelope ? {
49665
+ sourceRequestFingerprint: envelope.snapshot.requestFingerprint,
49666
+ headerFingerprint: envelope.snapshot.headerFingerprint
49667
+ } : {},
49668
+ ...budget.contextPressureTokens !== void 0 ? { sourceEstimatedTokens: budget.contextPressureTokens } : {},
49669
+ ...budget.cacheReuseExpected !== void 0 ? { cacheReuseExpected: budget.cacheReuseExpected } : {}
49670
+ });
49671
+ void writerRef.current?.append(compactionEvent);
49672
+ }
49673
+ historySeedLen = getHistory().length;
49267
49674
  let composedWorkspace = "";
49268
49675
  let composedInstructions = "";
49269
49676
  let hasPlan = false;
@@ -49421,6 +49828,7 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
49421
49828
  lastStableHash = hashStablePrompt(fallback);
49422
49829
  systemMessages = [{ role: "system", content: fallback }];
49423
49830
  }
49831
+ systemPrefixLen = systemMessages.length;
49424
49832
  const maxToolCallsPerTurn = envNumber(process.env.ZELARI_MAX_TOOL_CALLS, {
49425
49833
  default: 25,
49426
49834
  min: 1
@@ -49433,7 +49841,10 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
49433
49841
  });
49434
49842
  const harness2 = new AgentHarness({
49435
49843
  model: envConfig.model,
49436
- provider: "openai-compatible",
49844
+ // v1.36.0 (P0.2): real provider identity — the harness used to
49845
+ // hardcode "openai-compatible" (the transport family) so snapshots
49846
+ // and telemetry mislabeled deepseek/glm/minimax routing.
49847
+ provider: envConfig.providerId,
49437
49848
  messages: [
49438
49849
  ...systemMessages,
49439
49850
  // v1.8.0: shared rolling history (agent/council/zelari) so short
@@ -49452,6 +49863,9 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
49452
49863
  cwd,
49453
49864
  maxToolCallsPerTurn,
49454
49865
  maxToolLoopIterations,
49866
+ // v1.36.0: routed-request snapshots feed the meter (occupancy) and
49867
+ // the cache-aware compaction replay (last warm prefix).
49868
+ onRequestSnapshot: (snap) => recordRequestSnapshot(sessionId, snap),
49455
49869
  ...maxToolLoopHardCap > 0 ? { maxToolLoopHardCap } : {}
49456
49870
  });
49457
49871
  harnessRef.current = harness2;
@@ -49466,6 +49880,14 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
49466
49880
  for await (const event of harness2.run()) {
49467
49881
  if (event.type === "message_end") {
49468
49882
  if (event.usage) realUsage = event.usage;
49883
+ if (event.usage) {
49884
+ recordRequestUsage(sessionId, {
49885
+ promptTokens: event.usage.promptTokens,
49886
+ completionTokens: event.usage.completionTokens,
49887
+ totalTokens: event.usage.totalTokens,
49888
+ cachedPromptTokens: event.usage.cachedPromptTokens
49889
+ });
49890
+ }
49469
49891
  if (streamContent) {
49470
49892
  const sealed = streamScrub.finalize(streamContent);
49471
49893
  if (useLiveModel) setStreaming(commitStreaming, sealed, event.ts);
@@ -49625,18 +50047,17 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
49625
50047
  const h = harnessRef.current;
49626
50048
  if (h && turnSucceeded) {
49627
50049
  const all = h.getMessages();
49628
- const seedLen = 1 + historySeedLen + 1;
50050
+ const seedLen = systemPrefixLen + historySeedLen + 1;
49629
50051
  if (all.length > seedLen) {
49630
50052
  appendMessages(
49631
- all.slice(seedLen).map(
49632
- (m) => m.role === "assistant" && m.content ? {
49633
- ...m,
49634
- content: cleanAgentContent(m.content, {
49635
- stripQuestion: false,
49636
- stripThink: false
49637
- })
49638
- } : m
49639
- )
50053
+ all.slice(seedLen).map((m) => {
50054
+ if (m.role !== "assistant" || !m.content) return m;
50055
+ const cleaned = cleanAgentContent(m.content, {
50056
+ stripQuestion: false,
50057
+ stripThink: false
50058
+ });
50059
+ return cleaned === m.content ? m : { ...m, content: cleaned };
50060
+ })
49640
50061
  );
49641
50062
  }
49642
50063
  }
@@ -51579,12 +52000,12 @@ init_fileStateStore();
51579
52000
  async function restoreDurableState(opts) {
51580
52001
  const restoreTree = opts.restoreTree !== false;
51581
52002
  try {
51582
- const store3 = opts.store ?? await getStateStore(opts.projectRoot);
52003
+ const store4 = opts.store ?? await getStateStore(opts.projectRoot);
51583
52004
  let meta3;
51584
52005
  if (opts.commitId) {
51585
- meta3 = await store3.setHead(opts.commitId);
52006
+ meta3 = await store4.setHead(opts.commitId);
51586
52007
  } else {
51587
- meta3 = await store3.head();
52008
+ meta3 = await store4.head();
51588
52009
  if (!meta3) {
51589
52010
  return {
51590
52011
  ok: false,
@@ -51637,8 +52058,8 @@ function ago2(ms) {
51637
52058
  return `${Math.round(s / 3600)}h ago`;
51638
52059
  }
51639
52060
  async function handleStateStatus(ctx) {
51640
- const store3 = await getStateStore(ctx.cwd);
51641
- const head = await store3.head();
52061
+ const store4 = await getStateStore(ctx.cwd);
52062
+ const head = await store4.head();
51642
52063
  if (!head) {
51643
52064
  appendSystem(
51644
52065
  ctx.setMessages,
@@ -51646,9 +52067,9 @@ async function handleStateStatus(ctx) {
51646
52067
  );
51647
52068
  return;
51648
52069
  }
51649
- const discoveries = await store3.loadDiscoveries(head.id);
52070
+ const discoveries = await store4.loadDiscoveries(head.id);
51650
52071
  const reusable = discoveries.filter((d) => d.reusable).length;
51651
- const recent = await store3.list(8);
52072
+ const recent = await store4.list(8);
51652
52073
  const lines = recent.map((c, i) => {
51653
52074
  const ver2 = c.verification.ran ? c.verification.ok ? "ok" : "fail" : "n/a";
51654
52075
  return ` ${i === 0 ? "\u2192" : " "} ${c.id} ${ago2(c.createdAt)} ${c.label} ver=${ver2}` + (c.layer ? ` [${c.layer}]` : "") + (c.stablePromptHash ? ` hash=${c.stablePromptHash.slice(0, 8)}` : "");
@@ -51667,9 +52088,9 @@ async function handleStateStatus(ctx) {
51667
52088
  );
51668
52089
  }
51669
52090
  async function handleStateCommit(ctx, label) {
51670
- const store3 = await getStateStore(ctx.cwd);
52091
+ const store4 = await getStateStore(ctx.cwd);
51671
52092
  try {
51672
- const meta3 = await store3.commit({
52093
+ const meta3 = await store4.commit({
51673
52094
  mode: "agent",
51674
52095
  label: label?.trim() || "manual state commit",
51675
52096
  layer: "manual",
@@ -51696,8 +52117,8 @@ async function handleStateCommit(ctx, label) {
51696
52117
  }
51697
52118
  }
51698
52119
  async function handleStateShow(ctx, id) {
51699
- const store3 = await getStateStore(ctx.cwd);
51700
- const meta3 = id ? await store3.get(id) : await store3.head();
52120
+ const store4 = await getStateStore(ctx.cwd);
52121
+ const meta3 = id ? await store4.get(id) : await store4.head();
51701
52122
  if (!meta3) {
51702
52123
  appendSystem(
51703
52124
  ctx.setMessages,
@@ -51705,7 +52126,7 @@ async function handleStateShow(ctx, id) {
51705
52126
  );
51706
52127
  return;
51707
52128
  }
51708
- const text = await store3.materializeContext(meta3.id, 6e3);
52129
+ const text = await store4.materializeContext(meta3.id, 6e3);
51709
52130
  appendSystem(ctx.setMessages, `[state] show ${meta3.id}
51710
52131
  ${text}`);
51711
52132
  }
@@ -53214,14 +53635,14 @@ async function handleSkillCompare(ctx, ids, fallbackMessage) {
53214
53635
  }
53215
53636
  function handleCouncilFeedback(ctx, memberId, score, note) {
53216
53637
  try {
53217
- const store3 = new FeedbackStore();
53218
- const entry = store3.record({
53638
+ const store4 = new FeedbackStore();
53639
+ const entry = store4.record({
53219
53640
  memberId,
53220
53641
  score,
53221
53642
  ...note ? { note } : {},
53222
53643
  ...ctx.sessionId ? { sessionId: ctx.sessionId } : {}
53223
53644
  });
53224
- const stats = store3.getStats(memberId);
53645
+ const stats = store4.getStats(memberId);
53225
53646
  appendSystem(
53226
53647
  ctx.setMessages,
53227
53648
  `[council-feedback] ${memberId} rated ${entry.score}/5 \u2014 running avg ${stats.avg.toFixed(2)} over ${stats.count} rating(s).`