AnyBot/GROK_BOT_TOKENS.md

211 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Grok Bot 的 token 計數與 context window 用量,以及重建 harness 的對應做法
对象:`/Applications/Grok Bot.app`(package name `sand`,Electron 40.1.0,asar 1027 entries,integrity verified)
分析目標:`dist/electron-main/main-app.cjs`、`dist/local-exec-daemon/main.cjs`、`dist/node-agent-coordinator/main.cjs`
相關 finding:fnd_5、fnd_6、fnd_7、fnd_9、fnd_12、fnd_16
---
## §0 標記規則(先讀這節)
本文每條陳述都標明等級:
- **觀察**:在目標檔案裡直接讀到的欄位名、常數、字面值。**一律附掃描範圍**——「掃了哪個檔、找了哪些關鍵字」。「沒找到」只等於「在該範圍、用該關鍵字沒找到」,不等於「不存在」。
- **推論**:從觀察算出來/歸納出來的結論(例:記帳在伺服器端)。
- **推測**:最合理的猜測,沒有直接證據。
- **作者設定**:**重建 harness(`app/`)裡我自己選的數字**,與 Grok Bot 無關。**不要把這些數字當成 Grok Bot 的事實。**
引證方式:本文引 **finding id**(`fnd_*`),因為已入庫 finding 的 evidence 連結在入庫時已綁定並驗證。跨輪的 `ev_*` id 不可信任(實測會 `Unknown evidence id`),所以本文只在有**同輪** evidence 的地方列 `ev_*`:本輪的两個 id 是檔案完整性檢查(見 §11 末)。
---
## §1 核心結論:Grok Bot 客戶端**沒有** token 計數器;記帳在伺服器端
**推論**(依據下列觀察 + fnd_5/fnd_6)。
**觀察(範圍:`dist/electron-main/main-app.cjs`,找這 9 個欄位名)**:`usage`、`prompt_tokens`、`completion_tokens`、`total_tokens`、`token_count`、`context_window`、`max_tokens`、`tokens_used`、`usage_ratio` → **0 命中**;符號索引中含 `token` 的 147 筆全部是**憑證/密閉字串**(token = credential),不是計數。
**觀察**:客戶端有完整的一組 **byte 上限**與**压缩引數**(§2、§5),但**看不到把字串換算成 token 的通用估算器被使用**:
- `estimateMessagesTokenCount` 這個符號存在(約 `main-app.cjs` @2743065 附近),但它的**算術在觀察到的位址不可見**(minified、未展開)。→ 有名字、無實現細節。
**觀察(與結論一致的正面證據)**:token 用量是**從伺服器事件流裡取出來的**,不是客戶端算的:
`compactionSummaryFromEvent`、`applyCompactionSummaryToTimeline`、`applyCompactionSummaryFromEvent`、`turnUsageBreakdownFromEvent`、`usageFromEvent`、`onUsage`、`usageLabel`、`usageLabelForTurn`、`reportUsage`、`turnSteer` + `tokenBudgetsForTurn`。
**這與 fnd_5/fnd_6 完全對得上**:文字代理迴圈、prompt 組裝、模型路由與 context 管理都在遠端 box/gateway(`api2.cursor.sh` 系),客戶端只是顯示者與工具執行者(fnd_7)。
**範圍限制**:只掃了上述欄位名,且 fnd_9 已記錄字串表被截斷。所以嚴格的說法是:**「在 main-app.cjs 的 token 計數欄位掃描中未見客戶端計數器;記帳欄位一律以 event 名出現」**,不是「客戶端絕對沒有 token 數學」。
---
## §2 客戶端唯一的 token 換算:字元數 / 4(僅限語音逐字稿)
**推論**:在觀察範圍內,客戶端唯一看得見的 token 除法在語音通話路徑上:
- `voiceCallTranscriptCharsPerToken = 4.0`(**觀察**)
- 算式形如 `ceil(charLen / charsPerToken)`(**觀察**)
- 預算 `voiceCallTranscriptTokenBudget = 12000`(**觀察**)
**推論**:這是「ASCII 約 4 字元 = 1 token」的粗估,只用來控制**逐字稿長度**,不是整個對話的 context 管理。
**作者設定(重建做法)**:我的 harness 不內嵌 tokenizer(零相依,不帶 `gpt-tokenizer`/`js-tiktoken`)。規則:ASCII byte → 1 token,非 ASCII byte → 0.5 token。這是**我選的**,Grok Bot 只用 4.0 字元/token 於語音逐字稿。
---
## §3 真正的權威值:讀 provider 回來的 usage
**觀察**:Grok Bot 的用量數字來自伺服器事件(§1 的 `usageFromEvent` / `turnUsageBreakdownFromEvent` / `onUsage`)。
**作者設定(重建做法)**:不自己數,**讀 provider 回傳的 `usage`**。OpenAI-compatible 串流的最後一個 SSE chunk 帶 `usage.prompt_tokens` / `usage.completion_tokens` / `usage.total_tokens`(請求側要發 `stream_options: {"include_usage": true}`)。規則:
1. 每個 LLM 事件流結束時取**最後一個**帶 `usage` 的 chunk 當權威值;
2. 佔用率 = `usage.total_tokens / contextWindow`,寫入事件流(`tokens_used` + `source: "provider"`);
3. provider 沒給 `usage` 時才退回本地估計,並在事件上標 `est.`,UI 顯示 `~`;
4. 估計器與權威值並存,永不互相覆蓋——這樣才知道估計誤差。
---
## §4 每工具的輸出上限:用 byte 和行列卡,不用 token
**觀察(範圍:`dist/local-exec-daemon/main.cjs`,fnd_7 的工具實作)**:
| 工具 | 上限欄位 | 值 |
| --- | --- | --- |
| `read_file` | `maxBytes` | 預設 **131072**(128 KiB) |
| `readTerminalFile`(背景 shell 輸出) | `maxBytes` | **65536**(64 KiB) |
| `ripgrep` | `maxResults` | **200** |
| `ripgrep` | `timeoutMs` | **20000** |
**推論**:Grok Bot 控制 context 膨脹的主要手段是**在工具邊界卡 byte 與條數**,讓單次工具結果不可能撐爆 context,而不是把工具結果送去數 token。
**作者設定(重建做法)**:同樣策略——每個工具都有 `maxBytes` + 行/列上限,回傳時附 `truncated: true` 與「如何取下一页」的提示(offset/cursor)。**我的數值與上表相同**(131072 / 65536 / 200 / 20000 直接採用,因為它們是 Grok Bot 的觀察值,屬於可借鑑的設計常數)。
---
## §5 壓縮觸發點:Grok Bot 用「比例」,而且是 70 %
**觀察(範圍:`dist/electron-main/main-app.cjs`)**——本報告**唯一直接來自 Grok Bot 的數字組**:
```
client_speculative_summarization_config = {
enabled: true,
thresholdRatio: 0.70,
toleranceRatio: 0.05,
modelId: "grok-4.5"
}
speculativeSummarizationClientGates = { enabled, modelTier: "default", contextRatio: 0.70 }
```
**推論**:
- `thresholdRatio: 0.70` = context 用量到 **70 %** 觸發(**推論**:欄位語意由名字與 `contextRatio` 同名值一致支持,非文件證明)。
- `toleranceRatio: 0.05` = **5 %** 容差 → 觸發後压到 **65 %** 以下(`0.70 - 0.05`)。**0.70 與 0.05 是觀察;「压到 65 %」是把容差讀成遲滯目標的推論。**
- `modelId: "grok-4.5"` = 做摘要的模型由客戶端設定送出去(與 fnd_12 的「客戶端擁有設定、推送到 box」同形)。
- 「speculative」(投機式)+ 客戶端 gate(`enabled`、`modelTier`)→ **推測**:摘要是在回合進行中/之前**預先算好**,需要時直接採用,避免在關鍵時刻卡一次摘要請求。這是對命名的解讀,無行為證據。
**觀察**:压縮結果以 **`compactionSummary`** 的名字從**伺服器事件**送達,並套用到 timeline(§1)→ 摘要在事件流裡是**一等公民**(可重放、可重算 timeline),不是就地改寫訊息歷史。
**作者設定(重建做法)**:採用同一對引數——**觸發 70 %,目標 65 %**(遲滯,避免在臨界點反覆壓縮)。這是我從 Grok Bot 借鑑的**唯一**一組數值;其他比例都是我的選擇。
---
## §6 壓什麼:位元組上限講得很清楚
**觀察(範圍:`dist/electron-main/main-app.cjs`)**:
| 欄位 | 值 | 附註 |
| --- | --- | --- |
| `summarizationInputTokenLimit` | **360000** | 碼內註解寫「bytes (ASCII=1 token)」 |
| `summarizationInputByteLimit` | **360000**(預設 300000) | |
| `summarizationInputBytes` | **120000** | |
| `summarizationInputTokenLimitCap` | 0 = 停用 | 上限的上限 |
| `summarizationOutputCap` | **6000** | 摘要輸出 |
| `MAX_SUMMARY_BYTES` | **100 × 1024** | |
| `summarizeMessages` | 函式名 | |
**推論**:
1. 全鏈路都以 **byte** 為單位;「token」在客戶端只是「ASCII byte」的別名(註解自己寫了 ASCII=1 token)。
2. **輸入**有 300 000–360 000 byte 的送件上限,實際一次用 120 000 byte;**輸出**摘要 ≤ 6000(單位未標,**推測**為 token 上限)、單條摘要 ≤ 100 KiB。
3. 因為摘要由伺服器產生(§1),這些限制更像是**客戶端決定送多少歷史去摘要**。
**作者設定(重建做法)**:我的 compress 事件記錄 `beforeTokens` / `afterTokens` / `modelUsed` / `strategy`;摘要本身是 append-only 事件,timeline 由「最近 N 條原文 + 之前所有摘要」重組;保留最後一輪完整不压,避免模型忘記自己剛剛在做什麼。
---
## §7 context 爆了怎麼辦:413 的分類與一次重試
**觀察(fnd_9,範圍 `main-app.cjs`)**:provider 錯誤 taxonomy 含 `provider_413_context_too_large`,以及 `provider_400_invalid_request`、`_401_auth_rejected`、`_404_not_found`、`_429_quota_exceeded`、`_4xx_rejected`、`_5xx_terminal`、`_image_rejected`、`_user_blocked`、`NonRetryableProviderError`。
**推論**:`413` 被当成**可處理事件**(有自己的代號),不是笼統的 4xx;`NonRetryableProviderError` 與 `_5xx_terminal` 存在 → 重試策略是**依代號分類**的。
**作者設定(重建做法)**:收到 `413` → **強制壓縮一次**(不等到 70 %)→ 重發一次;第二次還失敗就帶著錯誤終止回合,並在事件流記 `provider_413_context_too_large`。`429` 讀 `retry-after`;`4xx` 不重試(對應 fnd_9 的 non-retryable 分類)。
---
## §8 別把「配額」讀成「context window」
**觀察(範圍:`dist/electron-main/main-app.cjs`)**:`token_budget = 100000`、`token_budget_per_user = 30000`、`token_budget_per_user_7d = 100000`、`token_budget_per_team`、`usage_warning_threshold = 0.8`、`usage_limit_floored`。
**推論**:`per_user` / `per_user_7d` / `per_team` 這種**按使用者/團隊/7 天視窗**的分桶是**計費與配額**語彙,context window 不會按使用者分桶。`usage_warning_threshold = 0.8` 是「用量到 80 % 要示警」的**配額**示警。
**结论**:Grok Bot 的 **context window 大小不在客戶端**(找不到 `context_window`,§1)。客戶端看到的 `token_budget*` 是配額。
**作者設定(重建做法)**:兩個數字必須分開。`--context-window`(我預設 **8000**,CLI 可蓋)控制壓縮;配額是另一個概念,我的 harness 不做配額(沒有伺服器)。`usage_warning_threshold = 0.8` 這個「80 % 示警」我沿用為 **CLI 警示條**的閾值(借用其形式,非借用其語意)。
---
## §9 用量要能對到回合
**觀察(範圍:`dist/electron-main/main-app.cjs`)**:`turnUsageBreakdownFromEvent`、`usageLabelForTurn`、`tokenBudgetsForTurn`、`turnSteer`、`reportTurnClientOutcome`、`reportRenderTtfr`。
**推論**:token 用量**按回合歸屬**(per-turn breakdown),而且客戶端會**回報自己這端的結果**(`reportTurnClientOutcome`)與**首字延遲**(`reportRenderTtfr`)→ 伺服器是記帳中心,客戶端是觀測點之一。`turnSteer` + `tokenBudgetsForTurn` 一起出現 → **推測**:每回合有 token 預算,steering(中途插入指示)時會重算。
**作者設定(重建做法)**:事件流裡每條 `llm.usage` 事件帶 `turnId`;`/usage` 指令印出 per-turn 表格(每回合 prompt/completion/total + 佔 context 百分比 + 是否 `est.`)。我的 harness 不做遠端上報。
---
## §10 讓它看得見:CLI 與 UI 的可見性
**觀察**:客戶端有 `usageLabel` / `usageLabelForTurn` → **推論**:UI 上有專用的用量字串(每回合一條)。
**作者設定(重建做法)**:
- CLI 每個回合後印一行 `tokens 1234/8000 (15%)`, provider 值直接顯示,估計值前綴 `~`。
- 超過 `--context-window` 的 **80 %** 時在同一行尾加警示條 `[████░░]`,並在 compress 事件發生時印 `compacted 4100 → 1500`。
- `--print` 模式结束時印總計。
---
## §11 數字來源對照表(最重要的一節)
| 數字 | 屬於誰 | 出處與標記 |
| --- | --- | --- |
| **0.70**(`thresholdRatio`)、**0.70**(`contextRatio`) | **Grok Bot 用戶端** | **觀察**:`client_speculative_summarization_config` / `speculativeSummarizationClientGates`(`main-app.cjs`)。**這是本報告唯一直接來自 Grok Bot 的比例數字。** |
| **0.05**(`toleranceRatio`) | **Grok Bot 用戶端** | **觀察**:同上欄位。 |
| `modelId: "grok-4.5"`(做摘要的模型) | **Grok Bot 用戶端** | **觀察**:同上欄位(模型清單由伺服器提供,見 fnd_9 / unk_dd4ee2d6bc4685b5 已解)。 |
| 360000 / 300000 / 120000 / 6000 / 100×1024 byte 上限、`MAX_SUMMARY_BYTES` | **Grok Bot 用戶端** | **觀察**:`summarizationInput*`、`summarizationOutputCap`、`MAX_SUMMARY_BYTES`(`main-app.cjs`)。單位是 **byte**(檔內註解:ASCII=1 token)。 |
| 131072 / 65536 / 200 / 20000(每工具上限) | **Grok Bot 用戶端** | **觀察**:`dist/local-exec-daemon/main.cjs`(fnd_7)。 |
| 4.0 chars/token、12000 | **Grok Bot 用戶端(僅語音逐字稿)** | **觀察**:`voiceCallTranscriptCharsPerToken`、`voiceCallTranscriptTokenBudget`。 |
| 100000 / 30000 / 100000 / 0.8 | **Grok Bot 用戶端,但是「配額」不是 context window** | **觀察**:`token_budget*`、`usage_warning_threshold`(§8 推論)。 |
| **65 %**(压縮目標) | **推論 + 作者設定** | 由 `0.70 − 0.05` 得出的**推論**;作為我 harness 的固定目標值则是**作者設定**。Grok Bot 碼內**沒有**「65 %」這個字面值。 |
| **72 000**、**100 000**(重建檔內出現的 context 尺寸範例) | **作者設定** | 我舉的例子,與 Grok Bot 無關。 |
| **8000**(`--context-window` 預設) | **作者設定** | 我的 CLI 預設值。**Grok Bot 客戶端沒有 context window 數字**(§1 掃描 0 命中)。 |
| **80 %**(CLI 警示條閾值) | **作者設定**(形式借鏡 `usage_warning_threshold = 0.8`) | 語意不同:Grok Bot 那個是配額示警。 |
| ASCII 1 token / 非 ASCII 0.5 token 的估計器 | **作者設定** | 我的 harness 規則;Grok Bot 只在語音逐字稿用 4.0 chars/token。 |
**一句話**:**只有 0.70 / 0.05 這一對壓縮引數,以及一組 byte/char 上限,是從 Grok Bot 用戶端觀察得來的**;其餘任何 token/context 數字都是我自己的設定或範例。
**本輪檔案完整性檢查(觀察,同輪 evidence)**:`GROK_BOT_TOKENS.md` 10785 B、`GROK_BOT_FEATURES.md` 20998 B,sha256 `db9f4c11b1ea18855eaac90d0eb15feea6000da92029516b28e02c285772661b` / `fd753cf232f6959e0bb63cd813bc26a8b55753ae16ac1174aa03f247df1f82d3`(`ev_c091620b0c5bc68527e432b2d02d6fe80c4d5ac7d1bd928d49f0b80db8c63174`、`ev_2f4ca1c5d683d7ad93da7316b4fd07eac9100f2bbff0f9ce166269c4de03cf58`;入庫為 fnd_16)。
---
## §12 方法與限制
**方法**:靜態分析。asar 清單(1027 entries,integrity verified)→ `js_app_map` 套裝圖 → 對 `dist/electron-main/main-app.cjs`、`dist/local-exec-daemon/main.cjs`、`dist/node-agent-coordinator/main.cjs` 做符號/欄位名掃描。**沒有執行目標**,因此本文所有陳述都只證明「碼裡寫了什麼」,不證明「跑起來會怎樣」。
**限制(逐條)**:
1. 「找不到 token 計數欄位」的範圍是 **main-app.cjs + 列出的 9 個欄位名**;fnd_9 已記錄字串表被截斷,fnd_6 已記錄 **18 個動態 fetch 點**與 connect-rpc transport 未完全解析。
2. `estimateMessagesTokenCount` 的算術不可見;`summarizationOutputCap` 的單位未標。
3. `thresholdRatio` / `toleranceRatio` 的**語意**是從命名與 `contextRatio` 一致性推來的(推論),沒有註解或文件直接說明「壓到 65 %」。
4. 伺服器端的 prompt 組裝、模型路由、真正的 context window 大小與計費邏輯**不在 asar 裡**(unk_67f6ca825e8ffa3f,high,blocked);唯一途徑是執行期擷取 box 事件流(需要使用者核准執行目標)。
5. `js_app_map` 有 25 000 節點上限(74905 個被丟)與 `paged_modules` 覆盖限制;asar 目錄清單我看過 offset 0–560。
6. 本輪無檔案內容讀取工具,`core_archive_show` 對單一文字檔只回 size/sha256、不回正文 → 本文是依分析結果重組,非讀回舊檔。
**未解(對應 unk id)**:`unk_67f6ca825e8ffa3f`(伺服器端 prompt/模型路由/工具 schema);`estimateMessagesTokenCount` 算術;`CUScrollControlPermit` 由哪個請求欄位決定(fnd_13/fnd_14)。