gencode-ai 0.1.3 → 0.2.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 (151) hide show
  1. package/README.md +2 -1
  2. package/dist/agent/agent.d.ts +35 -0
  3. package/dist/agent/agent.d.ts.map +1 -1
  4. package/dist/agent/agent.js +93 -3
  5. package/dist/agent/agent.js.map +1 -1
  6. package/dist/agent/types.d.ts +6 -0
  7. package/dist/agent/types.d.ts.map +1 -1
  8. package/dist/checkpointing/checkpoint-manager.d.ts +87 -0
  9. package/dist/checkpointing/checkpoint-manager.d.ts.map +1 -0
  10. package/dist/checkpointing/checkpoint-manager.js +281 -0
  11. package/dist/checkpointing/checkpoint-manager.js.map +1 -0
  12. package/dist/checkpointing/index.d.ts +29 -0
  13. package/dist/checkpointing/index.d.ts.map +1 -0
  14. package/dist/checkpointing/index.js +29 -0
  15. package/dist/checkpointing/index.js.map +1 -0
  16. package/dist/checkpointing/types.d.ts +98 -0
  17. package/dist/checkpointing/types.d.ts.map +1 -0
  18. package/dist/checkpointing/types.js +7 -0
  19. package/dist/checkpointing/types.js.map +1 -0
  20. package/dist/cli/components/App.d.ts.map +1 -1
  21. package/dist/cli/components/App.js +157 -6
  22. package/dist/cli/components/App.js.map +1 -1
  23. package/dist/cli/components/CommandSuggestions.d.ts.map +1 -1
  24. package/dist/cli/components/CommandSuggestions.js +5 -0
  25. package/dist/cli/components/CommandSuggestions.js.map +1 -1
  26. package/dist/cli/components/Messages.d.ts +7 -1
  27. package/dist/cli/components/Messages.d.ts.map +1 -1
  28. package/dist/cli/components/Messages.js +11 -2
  29. package/dist/cli/components/Messages.js.map +1 -1
  30. package/dist/cli/components/ModeIndicator.d.ts +42 -0
  31. package/dist/cli/components/ModeIndicator.d.ts.map +1 -0
  32. package/dist/cli/components/ModeIndicator.js +52 -0
  33. package/dist/cli/components/ModeIndicator.js.map +1 -0
  34. package/dist/cli/components/PlanApproval.d.ts +36 -0
  35. package/dist/cli/components/PlanApproval.d.ts.map +1 -0
  36. package/dist/cli/components/PlanApproval.js +154 -0
  37. package/dist/cli/components/PlanApproval.js.map +1 -0
  38. package/dist/cli/components/theme.d.ts +2 -0
  39. package/dist/cli/components/theme.d.ts.map +1 -1
  40. package/dist/cli/components/theme.js +3 -0
  41. package/dist/cli/components/theme.js.map +1 -1
  42. package/dist/index.d.ts +1 -0
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +2 -0
  45. package/dist/index.js.map +1 -1
  46. package/dist/planning/index.d.ts +13 -0
  47. package/dist/planning/index.d.ts.map +1 -0
  48. package/dist/planning/index.js +15 -0
  49. package/dist/planning/index.js.map +1 -0
  50. package/dist/planning/plan-file.d.ts +59 -0
  51. package/dist/planning/plan-file.d.ts.map +1 -0
  52. package/dist/planning/plan-file.js +278 -0
  53. package/dist/planning/plan-file.js.map +1 -0
  54. package/dist/planning/state.d.ts +127 -0
  55. package/dist/planning/state.d.ts.map +1 -0
  56. package/dist/planning/state.js +261 -0
  57. package/dist/planning/state.js.map +1 -0
  58. package/dist/planning/tools/enter-plan-mode.d.ts +25 -0
  59. package/dist/planning/tools/enter-plan-mode.d.ts.map +1 -0
  60. package/dist/planning/tools/enter-plan-mode.js +98 -0
  61. package/dist/planning/tools/enter-plan-mode.js.map +1 -0
  62. package/dist/planning/tools/exit-plan-mode.d.ts +24 -0
  63. package/dist/planning/tools/exit-plan-mode.d.ts.map +1 -0
  64. package/dist/planning/tools/exit-plan-mode.js +149 -0
  65. package/dist/planning/tools/exit-plan-mode.js.map +1 -0
  66. package/dist/planning/types.d.ts +100 -0
  67. package/dist/planning/types.d.ts.map +1 -0
  68. package/dist/planning/types.js +28 -0
  69. package/dist/planning/types.js.map +1 -0
  70. package/dist/pricing/calculator.d.ts +21 -0
  71. package/dist/pricing/calculator.d.ts.map +1 -0
  72. package/dist/pricing/calculator.js +59 -0
  73. package/dist/pricing/calculator.js.map +1 -0
  74. package/dist/pricing/index.d.ts +7 -0
  75. package/dist/pricing/index.d.ts.map +1 -0
  76. package/dist/pricing/index.js +7 -0
  77. package/dist/pricing/index.js.map +1 -0
  78. package/dist/pricing/models.d.ts +20 -0
  79. package/dist/pricing/models.d.ts.map +1 -0
  80. package/dist/pricing/models.js +322 -0
  81. package/dist/pricing/models.js.map +1 -0
  82. package/dist/pricing/types.d.ts +30 -0
  83. package/dist/pricing/types.d.ts.map +1 -0
  84. package/dist/pricing/types.js +5 -0
  85. package/dist/pricing/types.js.map +1 -0
  86. package/dist/providers/anthropic.d.ts.map +1 -1
  87. package/dist/providers/anthropic.js +17 -10
  88. package/dist/providers/anthropic.js.map +1 -1
  89. package/dist/providers/gemini.d.ts.map +1 -1
  90. package/dist/providers/gemini.js +21 -14
  91. package/dist/providers/gemini.js.map +1 -1
  92. package/dist/providers/openai.d.ts.map +1 -1
  93. package/dist/providers/openai.js +12 -8
  94. package/dist/providers/openai.js.map +1 -1
  95. package/dist/providers/types.d.ts +2 -0
  96. package/dist/providers/types.d.ts.map +1 -1
  97. package/dist/providers/vertex-ai.d.ts.map +1 -1
  98. package/dist/providers/vertex-ai.js +17 -10
  99. package/dist/providers/vertex-ai.js.map +1 -1
  100. package/dist/session/manager.d.ts +4 -0
  101. package/dist/session/manager.d.ts.map +1 -1
  102. package/dist/session/manager.js +8 -0
  103. package/dist/session/manager.js.map +1 -1
  104. package/dist/tools/index.d.ts +7 -1
  105. package/dist/tools/index.d.ts.map +1 -1
  106. package/dist/tools/index.js +7 -0
  107. package/dist/tools/index.js.map +1 -1
  108. package/dist/tools/registry.d.ts +13 -0
  109. package/dist/tools/registry.d.ts.map +1 -1
  110. package/dist/tools/registry.js +79 -2
  111. package/dist/tools/registry.js.map +1 -1
  112. package/docs/cost-tracking-comparison.md +904 -0
  113. package/docs/operating-modes.md +96 -0
  114. package/docs/proposals/0025-cost-tracking.md +60 -2
  115. package/docs/proposals/README.md +1 -1
  116. package/examples/test-checkpointing.ts +121 -0
  117. package/examples/test-cost-tracking.ts +77 -0
  118. package/examples/test-interrupt-cleanup.ts +94 -0
  119. package/package.json +1 -1
  120. package/src/agent/agent.ts +110 -3
  121. package/src/agent/types.ts +6 -0
  122. package/src/checkpointing/checkpoint-manager.ts +327 -0
  123. package/src/checkpointing/index.ts +45 -0
  124. package/src/checkpointing/types.ts +104 -0
  125. package/src/cli/components/App.tsx +204 -5
  126. package/src/cli/components/CommandSuggestions.tsx +5 -0
  127. package/src/cli/components/Messages.tsx +23 -4
  128. package/src/cli/components/ModeIndicator.tsx +174 -0
  129. package/src/cli/components/PlanApproval.tsx +327 -0
  130. package/src/cli/components/theme.ts +3 -0
  131. package/src/index.ts +15 -0
  132. package/src/planning/index.ts +53 -0
  133. package/src/planning/plan-file.ts +326 -0
  134. package/src/planning/state.ts +305 -0
  135. package/src/planning/tools/enter-plan-mode.ts +111 -0
  136. package/src/planning/tools/exit-plan-mode.ts +170 -0
  137. package/src/planning/types.ts +150 -0
  138. package/src/pricing/calculator.ts +71 -0
  139. package/src/pricing/index.ts +7 -0
  140. package/src/pricing/models.ts +334 -0
  141. package/src/pricing/types.ts +32 -0
  142. package/src/providers/anthropic.ts +21 -10
  143. package/src/providers/gemini.ts +25 -14
  144. package/src/providers/openai.ts +17 -8
  145. package/src/providers/types.ts +3 -0
  146. package/src/providers/vertex-ai.ts +21 -10
  147. package/src/session/manager.ts +9 -0
  148. package/src/tools/index.ts +8 -0
  149. package/src/tools/registry.ts +95 -2
  150. package/.gencode/settings.local.json +0 -7
  151. package/CLAUDE.md +0 -86
@@ -0,0 +1,904 @@
1
+ # 成本跟踪实现对比:Claude Code vs OpenCode vs GenCode
2
+
3
+ ## 一、三个工具的成本跟踪实现对比
4
+
5
+ ### 1.1 OpenCode 的实现(生产级别)
6
+
7
+ OpenCode 是用 TypeScript/Go 实现的 AI 编码助手,有**完整的成本跟踪系统**。
8
+
9
+ #### 核心特点
10
+ - ✅ 支持多种 token 类型(输入/输出/推理/缓存读/缓存写)
11
+ - ✅ 使用 Decimal.js 确保财务计算精度
12
+ - ✅ 支持 Prompt Cache(Anthropic 的缓存功能)
13
+ - ✅ 支持 200k+ 上下文的分阶段定价
14
+ - ✅ 实时 UI 显示成本
15
+
16
+ #### 数据结构
17
+ ```typescript
18
+ // 每个步骤完成时记录的成本信息
19
+ StepFinishPart {
20
+ type: "step-finish",
21
+ cost: 0.0234, // USD 成本
22
+ tokens: {
23
+ input: 1234, // 输入 token
24
+ output: 567, // 输出 token
25
+ reasoning: 50, // 推理 token(O1 模型)
26
+ cache: {
27
+ read: 100, // 缓存命中读取
28
+ write: 20, // 缓存写入
29
+ }
30
+ }
31
+ }
32
+
33
+ // 每条助手消息的累积成本
34
+ AssistantMessage {
35
+ cost: 0.0456, // 这条消息的总成本
36
+ tokens: { ... }, // 累积的 token 使用
37
+ // ... 其他字段
38
+ }
39
+ ```
40
+
41
+ #### 定价表存储方式
42
+ 定价直接嵌入在 Model 对象中:
43
+ ```typescript
44
+ Model {
45
+ id: "claude-3-5-sonnet-20241022",
46
+ cost: {
47
+ input: 3.00, // 每 1M tokens 价格(美元)
48
+ output: 15.00,
49
+ cache: {
50
+ read: 0.30, // 缓存读取便宜 10 倍
51
+ write: 3.75, // 缓存写入略贵
52
+ },
53
+ experimentalOver200K: { // 超过 200k 上下文的特殊定价
54
+ input: 6.00, // 价格翻倍
55
+ output: 30.00,
56
+ cache: { ... }
57
+ }
58
+ }
59
+ }
60
+ ```
61
+
62
+ ---
63
+
64
+ ### 1.2 GenCode 当前状态(基础设施已有)
65
+
66
+ GenCode 是我们正在开发的工具,**已有 token 跟踪,但缺少成本计算**。
67
+
68
+ #### 已有功能
69
+ - ✅ 所有 Provider 都返回 token usage
70
+ - ✅ Session 存储 token 使用量
71
+ - ✅ Example 中已展示如何显示 token
72
+
73
+ #### 缺失功能
74
+ - ❌ 没有成本计算逻辑
75
+ - ❌ 没有定价表
76
+ - ❌ CLI 不显示成本
77
+ - ❌ 不支持高级 token 类型(推理、缓存)
78
+ - ❌ 没有预算系统
79
+
80
+ #### 当前数据结构
81
+ ```typescript
82
+ // Provider 返回的响应
83
+ CompletionResponse {
84
+ content: [...],
85
+ stopReason: "end_turn",
86
+ usage?: {
87
+ inputTokens: 1234,
88
+ outputTokens: 567,
89
+ // 缺少:reasoningTokens, cacheReadTokens, cacheWriteTokens
90
+ }
91
+ }
92
+
93
+ // Session 存储
94
+ SessionMetadata {
95
+ tokenUsage?: {
96
+ input: 1234,
97
+ output: 567,
98
+ // 缺少:成本信息
99
+ }
100
+ }
101
+ ```
102
+
103
+ ---
104
+
105
+ ### 1.3 GenCode Proposal 0025 的设计(完整方案)
106
+
107
+ Proposal 设计了一套**完整的成本跟踪系统**。
108
+
109
+ #### 设计特点
110
+ - ✅ 完整的 token 类型支持(包括推理、缓存)
111
+ - ✅ 成本追踪和聚合
112
+ - ✅ 预算系统(每消息/每会话/每日/每月)
113
+ - ✅ 成本报告和对比
114
+ - ✅ 多 Provider 成本对比
115
+
116
+ #### 核心类型定义
117
+ ```typescript
118
+ // Token 使用详情
119
+ interface TokenUsage {
120
+ inputTokens: number;
121
+ outputTokens: number;
122
+ totalTokens: number;
123
+ reasoningTokens?: number; // 新增:推理 token
124
+ cacheReadTokens?: number; // 新增:缓存读取
125
+ cacheWriteTokens?: number; // 新增:缓存写入
126
+ }
127
+
128
+ // 成本估算
129
+ interface CostEstimate {
130
+ inputCost: number; // 输入成本
131
+ outputCost: number; // 输出成本
132
+ totalCost: number; // 总成本
133
+ currency: string; // "USD"
134
+ }
135
+
136
+ // 每条消息的成本记录
137
+ interface MessageCost {
138
+ messageId: string;
139
+ timestamp: Date;
140
+ model: string; // "claude-sonnet-4"
141
+ provider: string; // "anthropic"
142
+ tokens: TokenUsage;
143
+ cost: CostEstimate;
144
+ }
145
+
146
+ // 会话级别的成本聚合
147
+ interface SessionCost {
148
+ sessionId: string;
149
+ messages: MessageCost[];
150
+ totals: {
151
+ tokens: TokenUsage, // 累积 token
152
+ cost: CostEstimate, // 累积成本
153
+ }
154
+ }
155
+ ```
156
+
157
+ #### 定价表设计
158
+ ```typescript
159
+ interface ProviderPricing {
160
+ provider: string; // "anthropic"
161
+ model: string; // "claude-sonnet-4"
162
+ inputPer1M: number; // 3.00 USD
163
+ outputPer1M: number; // 15.00 USD
164
+ effectiveDate: string; // "2025-01-01"
165
+ }
166
+
167
+ // 2025 年最新定价
168
+ const pricing = [
169
+ // Anthropic
170
+ { provider: 'anthropic', model: 'claude-opus-4-5',
171
+ inputPer1M: 15, outputPer1M: 75 },
172
+ { provider: 'anthropic', model: 'claude-sonnet-4',
173
+ inputPer1M: 3, outputPer1M: 15 },
174
+ { provider: 'anthropic', model: 'claude-haiku-3-5',
175
+ inputPer1M: 0.25, outputPer1M: 1.25 },
176
+
177
+ // OpenAI
178
+ { provider: 'openai', model: 'gpt-4o',
179
+ inputPer1M: 2.5, outputPer1M: 10 },
180
+ { provider: 'openai', model: 'o1',
181
+ inputPer1M: 15, outputPer1M: 60 },
182
+
183
+ // Google Gemini
184
+ { provider: 'gemini', model: 'gemini-2.0-flash',
185
+ inputPer1M: 0.075, outputPer1M: 0.30 },
186
+ { provider: 'gemini', model: 'gemini-1.5-pro',
187
+ inputPer1M: 1.25, outputPer1M: 5 },
188
+ ];
189
+ ```
190
+
191
+ #### 预算系统设计
192
+ ```typescript
193
+ interface CostConfig {
194
+ displayMode: 'always' | 'summary' | 'never';
195
+ currency: string;
196
+ budgets?: {
197
+ perMessage?: number; // 单条消息最大成本
198
+ perSession?: number; // 会话最大成本
199
+ daily?: number; // 每日预算
200
+ monthly?: number; // 每月预算
201
+ };
202
+ alerts?: {
203
+ threshold: number; // 告警阈值(如 0.8 = 80%)
204
+ action: 'warn' | 'confirm' | 'block';
205
+ };
206
+ }
207
+ ```
208
+
209
+ ---
210
+
211
+ ## 二、数据流程图对比
212
+
213
+ ### 2.1 OpenCode 的完整数据流
214
+
215
+ ```
216
+ ┌─────────────────────────────────────────────────────────────────────┐
217
+ │ Step 1: LLM API 返回 │
218
+ │ ┌─────────────────────────────────────────────────────────────┐ │
219
+ │ │ Anthropic/OpenAI/Gemini API Response │ │
220
+ │ │ { │ │
221
+ │ │ id: "msg_123", │ │
222
+ │ │ content: [...], │ │
223
+ │ │ usage: { │ │
224
+ │ │ input_tokens: 1234, │ │
225
+ │ │ output_tokens: 567, │ │
226
+ │ │ cache_read_input_tokens: 100, // Anthropic 特有 │ │
227
+ │ │ cache_creation_input_tokens: 20, // Anthropic 特有 │ │
228
+ │ │ } │ │
229
+ │ │ } │ │
230
+ │ └─────────────────────────────────────────────────────────────┘ │
231
+ └─────────────────────────────────────────────────────────────────────┘
232
+
233
+ ┌─────────────────────────────────────────────────────────────────────┐
234
+ │ Step 2: 提取和标准化 Token │
235
+ │ ┌─────────────────────────────────────────────────────────────┐ │
236
+ │ │ Session.getUsage() │ │
237
+ │ │ │ │
238
+ │ │ 1. 处理 Prompt Cache │ │
239
+ │ │ - Anthropic: 缓存 token 已从 input_tokens 中扣除 │ │
240
+ │ │ - 其他: 需要手动扣除 │ │
241
+ │ │ │ │
242
+ │ │ 2. 标准化输出 │ │
243
+ │ │ tokens = { │ │
244
+ │ │ input: 1234, │ │
245
+ │ │ output: 567, │ │
246
+ │ │ reasoning: 50, // O1 模型 │ │
247
+ │ │ cache: { │ │
248
+ │ │ read: 100, │ │
249
+ │ │ write: 20, │ │
250
+ │ │ } │ │
251
+ │ │ } │ │
252
+ │ └─────────────────────────────────────────────────────────────┘ │
253
+ └─────────────────────────────────────────────────────────────────────┘
254
+
255
+ ┌─────────────────────────────────────────────────────────────────────┐
256
+ │ Step 3: 选择定价表 │
257
+ │ ┌─────────────────────────────────────────────────────────────┐ │
258
+ │ │ if (input + cache.read > 200,000) { │ │
259
+ │ │ // 使用 200k+ 特殊定价(更贵) │ │
260
+ │ │ costInfo = model.cost.experimentalOver200K │ │
261
+ │ │ } else { │ │
262
+ │ │ // 标准定价 │ │
263
+ │ │ costInfo = model.cost │ │
264
+ │ │ } │ │
265
+ │ │ │ │
266
+ │ │ costInfo = { │ │
267
+ │ │ input: 3.00, // USD per 1M tokens │ │
268
+ │ │ output: 15.00, │ │
269
+ │ │ cache.read: 0.30, │ │
270
+ │ │ cache.write: 3.75 │ │
271
+ │ │ } │ │
272
+ │ └─────────────────────────────────────────────────────────────┘ │
273
+ └─────────────────────────────────────────────────────────────────────┘
274
+
275
+ ┌─────────────────────────────────────────────────────────────────────┐
276
+ │ Step 4: 计算成本(使用 Decimal.js 确保精度) │
277
+ │ ┌─────────────────────────────────────────────────────────────┐ │
278
+ │ │ cost = new Decimal(0) │ │
279
+ │ │ .add((tokens.input / 1,000,000) * costInfo.input) │ │
280
+ │ │ .add((tokens.output / 1,000,000) * costInfo.output) │ │
281
+ │ │ .add((tokens.cache.read / 1,000,000) * costInfo.cache.read)│ │
282
+ │ │ .add((tokens.cache.write / 1,000,000) * costInfo.cache.write) │
283
+ │ │ .add((tokens.reasoning / 1,000,000) * costInfo.output) │ │
284
+ │ │ .toNumber() │ │
285
+ │ │ │ │
286
+ │ │ 例如: │ │
287
+ │ │ input: (1234 / 1,000,000) * 3.00 = $0.003702 │ │
288
+ │ │ output: (567 / 1,000,000) * 15.00 = $0.008505 │ │
289
+ │ │ cache.read: (100 / 1,000,000) * 0.30 = $0.000030 │ │
290
+ │ │ cache.write: (20 / 1,000,000) * 3.75 = $0.000075 │ │
291
+ │ │ ──────────────────────────────────────────────────── │ │
292
+ │ │ 总计: $0.012312 │ │
293
+ │ └─────────────────────────────────────────────────────────────┘ │
294
+ └─────────────────────────────────────────────────────────────────────┘
295
+
296
+ ┌─────────────────────────────────────────────────────────────────────┐
297
+ │ Step 5: 存储到数据库 │
298
+ │ ┌─────────────────────────────────────────────────────────────┐ │
299
+ │ │ SessionProcessor.process() │ │
300
+ │ │ │ │
301
+ │ │ 1. 累加到消息 │ │
302
+ │ │ assistantMessage.cost += 0.012312 │ │
303
+ │ │ assistantMessage.tokens = { input: 1234, ... } │ │
304
+ │ │ │ │
305
+ │ │ 2. 创建 StepFinishPart(每个步骤记录) │ │
306
+ │ │ new StepFinishPart({ │ │
307
+ │ │ type: "step-finish", │ │
308
+ │ │ cost: 0.012312, │ │
309
+ │ │ tokens: { input: 1234, ... } │ │
310
+ │ │ }) │ │
311
+ │ │ │ │
312
+ │ │ 3. 持久化到存储 │ │
313
+ │ │ - Message 表记录消息级别成本 │ │
314
+ │ │ - Parts 表记录步骤级别成本 │ │
315
+ │ └─────────────────────────────────────────────────────────────┘ │
316
+ └─────────────────────────────────────────────────────────────────────┘
317
+
318
+ ┌─────────────────────────────────────────────────────────────────────┐
319
+ │ Step 6: UI 实时显示 │
320
+ │ ┌─────────────────────────────────────────────────────────────┐ │
321
+ │ │ Header Component (header.tsx) │ │
322
+ │ │ │ │
323
+ │ │ 会话总成本: │ │
324
+ │ │ cost = messages │ │
325
+ │ │ .filter(m => m.role === "assistant") │ │
326
+ │ │ .reduce((sum, m) => sum + m.cost, 0) │ │
327
+ │ │ │ │
328
+ │ │ 显示: $0.45 │ │
329
+ │ │ │ │
330
+ │ │ 最后一条消息的上下文使用率: │ │
331
+ │ │ totalTokens = input + output + reasoning + │ │
332
+ │ │ cache.read + cache.write │ │
333
+ │ │ percentage = (totalTokens / model.limit.context) * 100 │ │
334
+ │ │ │ │
335
+ │ │ 显示: 1,234 tokens (45%) │ │
336
+ │ └─────────────────────────────────────────────────────────────┘ │
337
+ │ │
338
+ │ ┌────────────────────────────────────────────────┐ │
339
+ │ │ OpenCode $0.45 ⚡ │ │
340
+ │ │ ───────────────────────────────────────── │ │
341
+ │ │ Session: my-session │ │
342
+ │ │ Context: 1,234 tokens (45%) │ │
343
+ │ └────────────────────────────────────────────────┘ │
344
+ └─────────────────────────────────────────────────────────────────────┘
345
+ ```
346
+
347
+ ---
348
+
349
+ ### 2.2 GenCode 当前数据流(仅 Token)
350
+
351
+ ```
352
+ ┌─────────────────────────────────────────────────────────────────────┐
353
+ │ Step 1: LLM API 返回 │
354
+ │ ┌─────────────────────────────────────────────────────────────┐ │
355
+ │ │ Anthropic/OpenAI/Gemini API Response │ │
356
+ │ │ { │ │
357
+ │ │ // ... 内容 ... │ │
358
+ │ │ usage: { │ │
359
+ │ │ input_tokens: 1234, │ │
360
+ │ │ output_tokens: 567, │ │
361
+ │ │ } │ │
362
+ │ │ } │ │
363
+ │ └─────────────────────────────────────────────────────────────┘ │
364
+ └─────────────────────────────────────────────────────────────────────┘
365
+
366
+ ┌─────────────────────────────────────────────────────────────────────┐
367
+ │ Step 2: Provider 标准化 │
368
+ │ ┌─────────────────────────────────────────────────────────────┐ │
369
+ │ │ AnthropicProvider.complete() │ │
370
+ │ │ OpenAIProvider.complete() │ │
371
+ │ │ GeminiProvider.complete() │ │
372
+ │ │ │ │
373
+ │ │ return { │ │
374
+ │ │ content: [...], │ │
375
+ │ │ stopReason: "end_turn", │ │
376
+ │ │ usage: { │ │
377
+ │ │ inputTokens: 1234, │ │
378
+ │ │ outputTokens: 567, │ │
379
+ │ │ } │ │
380
+ │ │ } │ │
381
+ │ └─────────────────────────────────────────────────────────────┘ │
382
+ └─────────────────────────────────────────────────────────────────────┘
383
+
384
+ ┌─────────────────────────────────────────────────────────────────────┐
385
+ │ Step 3: Session 存储(可选) │
386
+ │ ┌─────────────────────────────────────────────────────────────┐ │
387
+ │ │ SessionManager.addMessage() │ │
388
+ │ │ │ │
389
+ │ │ session.tokenUsage = { │ │
390
+ │ │ input: session.tokenUsage.input + 1234, │ │
391
+ │ │ output: session.tokenUsage.output + 567, │ │
392
+ │ │ } │ │
393
+ │ │ │ │
394
+ │ │ 存储到: ~/.gencode/sessions/{id}.json │ │
395
+ │ └─────────────────────────────────────────────────────────────┘ │
396
+ └─────────────────────────────────────────────────────────────────────┘
397
+
398
+ ┌─────────────────────────────────────────────────────────────────────┐
399
+ │ Step 4: 基础显示(仅在 example 中) │
400
+ │ ┌─────────────────────────────────────────────────────────────┐ │
401
+ │ │ examples/basic.ts │ │
402
+ │ │ │ │
403
+ │ │ if (response.usage) { │ │
404
+ │ │ console.log( │ │
405
+ │ │ `Usage: ${response.usage.inputTokens} input, ` + │ │
406
+ │ │ `${response.usage.outputTokens} output` │ │
407
+ │ │ ); │ │
408
+ │ │ } │ │
409
+ │ │ │ │
410
+ │ │ 输出: Usage: 1234 input, 567 output │ │
411
+ │ └─────────────────────────────────────────────────────────────┘ │
412
+ └─────────────────────────────────────────────────────────────────────┘
413
+
414
+ ❌ 缺少:成本计算
415
+ ❌ 缺少:定价表
416
+ ❌ 缺少:CLI 中的成本显示
417
+ ❌ 缺少:会话级别的成本聚合
418
+ ❌ 缺少:预算系统
419
+ ```
420
+
421
+ ---
422
+
423
+ ### 2.3 GenCode Proposal 设计的数据流
424
+
425
+ ```
426
+ ┌─────────────────────────────────────────────────────────────────────┐
427
+ │ Step 1: LLM API 返回(同当前) │
428
+ │ [同当前流程] │
429
+ └─────────────────────────────────────────────────────────────────────┘
430
+
431
+ ┌─────────────────────────────────────────────────────────────────────┐
432
+ │ Step 2: Provider 标准化并计算成本(新增) │
433
+ │ ┌─────────────────────────────────────────────────────────────┐ │
434
+ │ │ import { calculateCost } from '../pricing/calculator'; │ │
435
+ │ │ │ │
436
+ │ │ AnthropicProvider.complete() { │ │
437
+ │ │ const response = await this.client.messages.create(...); │ │
438
+ │ │ │ │
439
+ │ │ const tokens = { │ │
440
+ │ │ inputTokens: response.usage.input_tokens, │ │
441
+ │ │ outputTokens: response.usage.output_tokens, │ │
442
+ │ │ }; │ │
443
+ │ │ │ │
444
+ │ │ // 新增:计算成本 │ │
445
+ │ │ const cost = calculateCost({ │ │
446
+ │ │ provider: 'anthropic', │ │
447
+ │ │ model: this.model, │ │
448
+ │ │ tokens, │ │
449
+ │ │ }); │ │
450
+ │ │ │ │
451
+ │ │ return { │ │
452
+ │ │ content: [...], │ │
453
+ │ │ stopReason: "end_turn", │ │
454
+ │ │ usage: tokens, │ │
455
+ │ │ cost, // 新增字段 │ │
456
+ │ │ }; │ │
457
+ │ │ } │ │
458
+ │ └─────────────────────────────────────────────────────────────┘ │
459
+ └─────────────────────────────────────────────────────────────────────┘
460
+
461
+ ┌─────────────────────────────────────────────────────────────────────┐
462
+ │ Step 3: CostTracker 记录(新增) │
463
+ │ ┌─────────────────────────────────────────────────────────────┐ │
464
+ │ │ Agent.run() { │ │
465
+ │ │ const response = await this.provider.complete(...); │ │
466
+ │ │ │ │
467
+ │ │ // 新增:记录成本 │ │
468
+ │ │ if (response.usage && response.cost) { │ │
469
+ │ │ costTracker.recordUsage( │ │
470
+ │ │ this.sessionId, │ │
471
+ │ │ this.model, │ │
472
+ │ │ this.provider.name, │ │
473
+ │ │ response.usage, │ │
474
+ │ │ response.cost │ │
475
+ │ │ ); │ │
476
+ │ │ │ │
477
+ │ │ // 检查预算 │ │
478
+ │ │ costTracker.checkBudget(this.sessionId); │ │
479
+ │ │ } │ │
480
+ │ │ } │ │
481
+ │ └─────────────────────────────────────────────────────────────┘ │
482
+ └─────────────────────────────────────────────────────────────────────┘
483
+
484
+ ┌─────────────────────────────────────────────────────────────────────┐
485
+ │ Step 4: Session 存储扩展 │
486
+ │ ┌─────────────────────────────────────────────────────────────┐ │
487
+ │ │ SessionManager.addMessage() │ │
488
+ │ │ │ │
489
+ │ │ session.tokenUsage = { │ │
490
+ │ │ input: ..., │ │
491
+ │ │ output: ..., │ │
492
+ │ │ }; │ │
493
+ │ │ │ │
494
+ │ │ // 新增:成本跟踪 │ │
495
+ │ │ session.totalCost = (session.totalCost || 0) + │ │
496
+ │ │ response.cost.totalCost; │ │
497
+ │ │ │ │
498
+ │ │ 存储到: ~/.gencode/sessions/{id}.json │ │
499
+ │ │ { │ │
500
+ │ │ "id": "...", │ │
501
+ │ │ "tokenUsage": { "input": 1234, "output": 567 }, │ │
502
+ │ │ "totalCost": 0.0234, // 新增 │ │
503
+ │ │ "messages": [...] │ │
504
+ │ │ } │ │
505
+ │ └─────────────────────────────────────────────────────────────┘ │
506
+ └─────────────────────────────────────────────────────────────────────┘
507
+
508
+ ┌─────────────────────────────────────────────────────────────────────┐
509
+ │ Step 5: CLI 显示(新增) │
510
+ │ ┌─────────────────────────────────────────────────────────────┐ │
511
+ │ │ App.tsx - 在每次响应后显示 │ │
512
+ │ │ │ │
513
+ │ │ {event.type === 'done' && event.usage && event.cost && ( │ │
514
+ │ │ <Box marginTop={1}> │ │
515
+ │ │ <Text dimColor> │ │
516
+ │ │ Tokens: {formatTokens(event.usage.inputTokens)} in / │ │
517
+ │ │ {formatTokens(event.usage.outputTokens)} out │ │
518
+ │ │ (~{formatCost(event.cost.totalCost)}) │ │
519
+ │ │ </Text> │ │
520
+ │ │ </Box> │ │
521
+ │ │ )} │ │
522
+ │ │ │ │
523
+ │ │ 显示效果: │ │
524
+ │ │ Tokens: 1.2K in / 567 out (~$0.02) │ │
525
+ │ │ Session total: $0.45 │ │
526
+ │ └─────────────────────────────────────────────────────────────┘ │
527
+ │ │
528
+ │ ┌────────────────────────────────────────────────┐ │
529
+ │ │ > How can I help you? │ │
530
+ │ │ │ │
531
+ │ │ [AI 响应内容...] │ │
532
+ │ │ │ │
533
+ │ │ Tokens: 1.2K in / 567 out (~$0.02) │ │
534
+ │ │ Session total: $0.45 │ │
535
+ │ └────────────────────────────────────────────────┘ │
536
+ └─────────────────────────────────────────────────────────────────────┘
537
+
538
+ ┌─────────────────────────────────────────────────────────────────────┐
539
+ │ Step 6: 成本报告命令(新增) │
540
+ │ ┌─────────────────────────────────────────────────────────────┐ │
541
+ │ │ /costs - 显示详细报告 │ │
542
+ │ │ │ │
543
+ │ │ Cost Report - Current Session: │ │
544
+ │ │ ┌──────────────────────────────────────────────────────┐ │ │
545
+ │ │ │ Provider Model Messages Tokens Cost │ │ │
546
+ │ │ ├──────────────────────────────────────────────────────┤ │ │
547
+ │ │ │ anthropic claude-sonnet 12 45.2K $0.23 │ │ │
548
+ │ │ │ anthropic claude-haiku 3 8.1K $0.01 │ │ │
549
+ │ │ ├──────────────────────────────────────────────────────┤ │ │
550
+ │ │ │ Total 15 53.3K $0.24 │ │ │
551
+ │ │ └──────────────────────────────────────────────────────┘ │ │
552
+ │ │ │ │
553
+ │ │ Daily: $1.45 / $10.00 budget (14.5%) │ │
554
+ │ │ Monthly: $23.67 / $100.00 budget (23.7%) │ │
555
+ │ └─────────────────────────────────────────────────────────────┘ │
556
+ └─────────────────────────────────────────────────────────────────────┘
557
+ ```
558
+
559
+ ---
560
+
561
+ ## 三、具体例子对比
562
+
563
+ ### 3.1 场景:用户发送一个简单问题
564
+
565
+ **用户输入**:
566
+ ```
567
+ "请帮我写一个 TypeScript 函数,用于计算斐波那契数列"
568
+ ```
569
+
570
+ #### OpenCode 的处理流程
571
+
572
+ ```
573
+ 1. 用户输入 → Agent
574
+ 输入: "请帮我写一个..." (约 20 tokens)
575
+
576
+ 2. Agent → Anthropic API
577
+ Request: {
578
+ model: "claude-3-5-sonnet-20241022",
579
+ messages: [
580
+ { role: "system", content: "..." }, // 系统提示 ~500 tokens
581
+ { role: "user", content: "请帮我写..." } // 20 tokens
582
+ ]
583
+ }
584
+
585
+ 3. Anthropic API → Response
586
+ Response: {
587
+ content: [{
588
+ type: "text",
589
+ text: "当然可以!下面是一个计算斐波那契数列的 TypeScript 函数:\n\n```typescript\nfunction fibonacci(n: number): number {\n if (n <= 1) return n;\n return fibonacci(n - 1) + fibonacci(n - 2);\n}\n```"
590
+ }],
591
+ usage: {
592
+ input_tokens: 520, // 系统 + 用户输入
593
+ output_tokens: 85, // 生成的代码和说明
594
+ cache_creation_input_tokens: 500, // 系统提示写入缓存
595
+ cache_read_input_tokens: 0 // 首次调用,无缓存
596
+ }
597
+ }
598
+
599
+ 4. Session.getUsage() 计算成本
600
+ tokens = {
601
+ input: 520,
602
+ output: 85,
603
+ cache: {
604
+ write: 500, // 首次缓存写入
605
+ read: 0
606
+ }
607
+ }
608
+
609
+ 定价(claude-3-5-sonnet):
610
+ - input: $3.00 / 1M tokens
611
+ - output: $15.00 / 1M tokens
612
+ - cache.write: $3.75 / 1M tokens
613
+ - cache.read: $0.30 / 1M tokens
614
+
615
+ cost = (520 / 1,000,000) * 3.00 = $0.001560
616
+ + (85 / 1,000,000) * 15.00 = $0.001275
617
+ + (500 / 1,000,000) * 3.75 = $0.001875
618
+ + (0 / 1,000,000) * 0.30 = $0.000000
619
+ ─────────────────────────────────────────────
620
+ 总计 = $0.00471
621
+
622
+ 5. 存储
623
+ StepFinishPart: { cost: 0.00471, tokens: {...} }
624
+ AssistantMessage.cost = 0.00471
625
+
626
+ 6. UI 显示
627
+ ┌──────────────────────────────────────────────┐
628
+ │ OpenCode $0.00 ⚡ │
629
+ │ ─────────────────────────────────────── │
630
+ │ > 请帮我写一个 TypeScript 函数... │
631
+ │ │
632
+ │ 当然可以!下面是一个计算斐波那契数列的... │
633
+ │ [代码块显示] │
634
+ │ │
635
+ │ Session: $0.00 | Context: 605 tokens (3%) │
636
+ └──────────────────────────────────────────────┘
637
+ ```
638
+
639
+ **第二次调用(Prompt Cache 生效)**:
640
+ ```
641
+ 用户: "能否优化这个函数,使用动态规划?"
642
+
643
+ Anthropic API Response:
644
+ usage: {
645
+ input_tokens: 20, // 新用户输入
646
+ output_tokens: 120, // 新输出
647
+ cache_creation_input_tokens: 0, // 无新缓存
648
+ cache_read_input_tokens: 585 // 缓存命中!(系统提示 + 之前对话)
649
+ }
650
+
651
+ 成本计算:
652
+ cost = (20 / 1,000,000) * 3.00 = $0.000060
653
+ + (120 / 1,000,000) * 15.00 = $0.001800
654
+ + (0 / 1,000,000) * 3.75 = $0.000000
655
+ + (585 / 1,000,000) * 0.30 = $0.000176 ← 缓存命中便宜 10 倍!
656
+ ─────────────────────────────────────────────
657
+ 总计 = $0.002036
658
+
659
+ 会话总成本: $0.00471 + $0.002036 = $0.006746
660
+
661
+ UI 显示:
662
+ Session: $0.01 | Context: 725 tokens (4%)
663
+ ```
664
+
665
+ ---
666
+
667
+ #### GenCode 当前的处理(无成本计算)
668
+
669
+ ```
670
+ 1. 用户输入 → Agent
671
+ [同上]
672
+
673
+ 2. Agent → Anthropic API
674
+ [同上]
675
+
676
+ 3. Anthropic API → Response
677
+ [同上,包含 usage 数据]
678
+
679
+ 4. Provider 标准化
680
+ return {
681
+ content: [...],
682
+ stopReason: "end_turn",
683
+ usage: {
684
+ inputTokens: 520,
685
+ outputTokens: 85,
686
+ }
687
+ }
688
+
689
+ ❌ 未计算成本
690
+ ❌ 未跟踪缓存 token
691
+
692
+ 5. Session 存储(可选)
693
+ session.tokenUsage = {
694
+ input: 520,
695
+ output: 85,
696
+ }
697
+
698
+ ❌ 无成本信息
699
+
700
+ 6. CLI 显示
701
+ ┌──────────────────────────────────────────────┐
702
+ │ > 请帮我写一个 TypeScript 函数... │
703
+ │ │
704
+ │ 当然可以!下面是一个计算斐波那契数列的... │
705
+ │ [代码块显示] │
706
+ │ │
707
+ │ ❌ 无成本显示 │
708
+ │ ❌ 无 token 显示 │
709
+ └──────────────────────────────────────────────┘
710
+
711
+ 仅在 examples/basic.ts 中有基础显示:
712
+ > Usage: 520 input, 85 output
713
+ ```
714
+
715
+ ---
716
+
717
+ #### GenCode Proposal 的处理(完整成本跟踪)
718
+
719
+ ```
720
+ 1-3. [同当前流程]
721
+
722
+ 4. Provider 计算成本(新增)
723
+ import { calculateCost } from '../pricing/calculator';
724
+
725
+ const cost = calculateCost({
726
+ provider: 'anthropic',
727
+ model: 'claude-sonnet-4',
728
+ tokens: {
729
+ inputTokens: 520,
730
+ outputTokens: 85,
731
+ }
732
+ });
733
+
734
+ // 定价表查询
735
+ const pricing = {
736
+ provider: 'anthropic',
737
+ model: 'claude-sonnet-4',
738
+ inputPer1M: 3.00,
739
+ outputPer1M: 15.00,
740
+ };
741
+
742
+ // 成本计算
743
+ cost = {
744
+ inputCost: (520 / 1_000_000) * 3.00 = 0.00156,
745
+ outputCost: (85 / 1_000_000) * 15.00 = 0.001275,
746
+ totalCost: 0.002835,
747
+ currency: 'USD'
748
+ };
749
+
750
+ return {
751
+ content: [...],
752
+ usage: { inputTokens: 520, outputTokens: 85 },
753
+ cost: cost, // ✅ 新增
754
+ };
755
+
756
+ 5. CostTracker 记录
757
+ costTracker.recordUsage(
758
+ sessionId: "session-123",
759
+ model: "claude-sonnet-4",
760
+ provider: "anthropic",
761
+ tokens: { inputTokens: 520, outputTokens: 85 },
762
+ cost: { totalCost: 0.002835, ... }
763
+ );
764
+
765
+ // 检查预算
766
+ if (config.budgets?.perMessage && cost.totalCost > config.budgets.perMessage) {
767
+ ui.warn("Message cost exceeds budget!");
768
+ }
769
+
770
+ 6. Session 存储
771
+ session.tokenUsage = { input: 520, output: 85 };
772
+ session.totalCost = 0.002835; // ✅ 新增
773
+
774
+ 7. CLI 显示
775
+ ┌──────────────────────────────────────────────┐
776
+ │ > 请帮我写一个 TypeScript 函数... │
777
+ │ │
778
+ │ 当然可以!下面是一个计算斐波那契数列的... │
779
+ │ [代码块显示] │
780
+ │ │
781
+ │ ✅ Tokens: 520 in / 85 out (~$0.00) │
782
+ │ ✅ Session total: $0.00 │
783
+ └──────────────────────────────────────────────┘
784
+
785
+ 8. 成本报告(/costs 命令)
786
+ > /costs
787
+
788
+ Cost Report - Current Session:
789
+ ┌──────────────────────────────────────────────┐
790
+ │ Provider Model Messages Cost │
791
+ ├──────────────────────────────────────────────┤
792
+ │ anthropic claude-sonnet 1 $0.00 │
793
+ ├──────────────────────────────────────────────┤
794
+ │ Total 1 $0.00 │
795
+ └──────────────────────────────────────────────┘
796
+ ```
797
+
798
+ ---
799
+
800
+ ### 3.2 成本对比场景:多轮对话
801
+
802
+ **场景**:一个 5 轮对话的编码会话
803
+
804
+ | 轮次 | 输入 Token | 输出 Token | OpenCode 成本 | GenCode Proposal 成本 | 差异原因 |
805
+ |-----|-----------|-----------|--------------|---------------------|---------|
806
+ | 1 | 520 | 85 | $0.00471 | $0.00284 | OpenCode 包含缓存写入成本 |
807
+ | 2 | 20 + 585(cache) | 120 | $0.00204 | $0.00184 | OpenCode 缓存命中折扣 |
808
+ | 3 | 30 + 705(cache) | 200 | $0.00331 | $0.00309 | 同上 |
809
+ | 4 | 50 + 935(cache) | 150 | $0.00271 | $0.00255 | 同上 |
810
+ | 5 | 25 + 1085(cache) | 180 | $0.00303 | $0.00278 | 同上 |
811
+ | **总计** | **2955** | **735** | **$0.0158** | **$0.01310** | **Proposal 少 17%** |
812
+
813
+ **差异分析**:
814
+ - OpenCode:完整跟踪缓存 token,首次写入有额外成本,后续命中有折扣
815
+ - GenCode Proposal:简化版本,不跟踪缓存(初期实现),所以成本估算偏低
816
+
817
+ **如果 GenCode 实现缓存跟踪**(Phase 2):
818
+ - 成本将与 OpenCode 一致
819
+ - 需要从 Anthropic API 提取 `cache_creation_input_tokens` 和 `cache_read_input_tokens`
820
+
821
+ ---
822
+
823
+ ## 四、关键技术对比总结
824
+
825
+ | 特性 | OpenCode | GenCode 当前 | GenCode Proposal |
826
+ |-----|----------|-------------|----------------|
827
+ | **Token 类型** | input/output/reasoning/cache.read/cache.write | input/output | 计划支持全部 |
828
+ | **精度保证** | Decimal.js | 标准 number | 标准 number |
829
+ | **定价表** | 内嵌 Model 对象 | 无 | 独立 ProviderPricing[] |
830
+ | **成本存储** | 每 Step + 每 Message | 无 | 每 Message |
831
+ | **UI 显示** | Header 实时显示 | 无 | 计划显示 |
832
+ | **预算系统** | 无(应用层) | 无 | 设计中 |
833
+ | **特殊定价** | 200k+ 分阶段 | 无 | 无(可扩展) |
834
+ | **缓存优化** | 完整支持 | 不支持 | Phase 2 |
835
+ | **实现复杂度** | 高 | - | 中 |
836
+
837
+ ---
838
+
839
+ ## 五、实现建议
840
+
841
+ 基于对比分析,GenCode 应该分阶段实现:
842
+
843
+ ### Phase 1: 基础成本跟踪(最小可行产品)
844
+ ✅ 创建定价表(`src/pricing/models.ts`)
845
+ ✅ 实现成本计算器(`src/pricing/calculator.ts`)
846
+ ✅ Provider 返回成本(修改 `CompletionResponse`)
847
+ ✅ CLI 显示成本(修改 `App.tsx`)
848
+
849
+ **工作量**:低
850
+ **价值**:高
851
+ **参考**:Proposal 0025 Phase 1
852
+
853
+ ### Phase 2: 高级 Token 支持
854
+ - 扩展 `TokenUsage` 支持 reasoning, cacheRead, cacheWrite
855
+ - 更新所有 Provider 提取这些数据
856
+ - 更新定价计算逻辑
857
+
858
+ **工作量**:中
859
+ **价值**:中(Anthropic 用户受益大)
860
+ **参考**:OpenCode 的 getUsage() 实现
861
+
862
+ ### Phase 3: 预算和告警
863
+ - 实现 `CostConfig` 和 `CostTracker`
864
+ - 会话/每日/每月预算
865
+ - 超预算告警
866
+
867
+ **工作量**:中
868
+ **价值**:高(企业用户必需)
869
+ **参考**:Proposal 0025 Budget System
870
+
871
+ ### Phase 4: 高级特性
872
+ - 成本报告(/costs 命令)
873
+ - 多 Provider 成本对比
874
+ - 成本优化建议
875
+ - Decimal.js 精度保证(如需要)
876
+
877
+ **工作量**:高
878
+ **价值**:中(锦上添花)
879
+
880
+ ---
881
+
882
+ ## 六、总结
883
+
884
+ **OpenCode**:
885
+ - ✅ 生产级实现,功能完整
886
+ - ✅ 支持所有 token 类型和缓存优化
887
+ - ✅ 财务精度保证(Decimal.js)
888
+ - ⚠️ 实现复杂度高
889
+
890
+ **GenCode 当前**:
891
+ - ✅ Token 基础设施已完备
892
+ - ❌ 缺少成本计算层
893
+ - ❌ 缺少 UI 显示
894
+
895
+ **GenCode Proposal**:
896
+ - ✅ 设计合理,分阶段实现
897
+ - ✅ 覆盖核心用例
898
+ - ✅ 可扩展到高级特性
899
+ - ✅ 实现成本适中
900
+
901
+ **推荐路径**:
902
+ 1. 先实现 Phase 1(基础成本显示)→ 快速交付价值
903
+ 2. 根据用户反馈决定是否实现 Phase 2-4
904
+ 3. 参考 OpenCode 的精确实现,但保持 GenCode 的简洁性