lonny-agent 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +326 -326
- package/dist/agent/llm.d.ts +4 -0
- package/dist/agent/llm.d.ts.map +1 -1
- package/dist/agent/prompt-builder.d.ts.map +1 -1
- package/dist/agent/prompt-builder.js +13 -9
- package/dist/agent/prompt-builder.js.map +1 -1
- package/dist/agent/providers/google.d.ts.map +1 -1
- package/dist/agent/providers/google.js.map +1 -1
- package/dist/agent/providers/openai.d.ts.map +1 -1
- package/dist/agent/providers/openai.js +43 -3
- package/dist/agent/providers/openai.js.map +1 -1
- package/dist/agent/session.d.ts +4 -0
- package/dist/agent/session.d.ts.map +1 -1
- package/dist/agent/session.js +45 -7
- package/dist/agent/session.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +7 -1
- package/dist/config/index.js.map +1 -1
- package/dist/tools/__tests__/bash.test.js +1 -1
- package/dist/tools/__tests__/bash.test.js.map +1 -1
- package/dist/tools/__tests__/edit.test.js +875 -371
- package/dist/tools/__tests__/edit.test.js.map +1 -1
- package/dist/tools/__tests__/grep.test.js +32 -0
- package/dist/tools/__tests__/grep.test.js.map +1 -1
- package/dist/tools/__tests__/sed.test.d.ts +2 -0
- package/dist/tools/__tests__/sed.test.d.ts.map +1 -0
- package/dist/tools/__tests__/sed.test.js +228 -0
- package/dist/tools/__tests__/sed.test.js.map +1 -0
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +83 -10
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit.d.ts +50 -9
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +328 -158
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +54 -30
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/registry.d.ts +0 -6
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +2 -29
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/sed.d.ts +4 -0
- package/dist/tools/sed.d.ts.map +1 -0
- package/dist/tools/sed.js +121 -0
- package/dist/tools/sed.js.map +1 -0
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +26 -3
- package/dist/web/index.js.map +1 -1
- package/dist/web/public/app.js +5 -855
- package/dist/web/public/confirm.js +70 -0
- package/dist/web/public/index.html +121 -120
- package/dist/web/public/input.js +107 -0
- package/dist/web/public/messages.js +395 -0
- package/dist/web/public/sidebar.js +82 -0
- package/dist/web/public/state.js +72 -0
- package/dist/web/public/style.css +996 -949
- package/dist/web/public/utils.js +37 -0
- package/dist/web/public/websocket.js +267 -0
- package/dist/web/public/ws.js +19 -0
- package/dist/web/session-bridge.d.ts.map +1 -1
- package/dist/web/session-bridge.js +3 -7
- package/dist/web/session-bridge.js.map +1 -1
- package/package.json +55 -54
- package/scripts/copy-native.mjs +24 -0
- package/scripts/copy-web.mjs +15 -0
- package/src/agent/index.ts +25 -25
- package/src/agent/llm.ts +4 -0
- package/src/agent/prompt-builder.ts +13 -9
- package/src/agent/providers/anthropic.ts +179 -179
- package/src/agent/providers/google.ts +210 -211
- package/src/agent/providers/ollama.ts +186 -186
- package/src/agent/providers/openai.ts +61 -5
- package/src/agent/session.ts +59 -3
- package/src/config/index.ts +342 -335
- package/src/tools/__tests__/bash.test.ts +1 -1
- package/src/tools/__tests__/edit.test.ts +981 -385
- package/src/tools/__tests__/grep.test.ts +35 -0
- package/src/tools/bash.ts +97 -11
- package/src/tools/edit.ts +362 -169
- package/src/tools/git.ts +76 -76
- package/src/tools/grep.ts +57 -30
- package/src/tools/registry.ts +2 -31
- package/src/tui/index.ts +786 -786
- package/src/web/index.ts +25 -3
- package/src/web/public/app.js +5 -855
- package/src/web/public/confirm.js +70 -0
- package/src/web/public/index.html +121 -120
- package/src/web/public/input.js +107 -0
- package/src/web/public/messages.js +395 -0
- package/src/web/public/sidebar.js +82 -0
- package/src/web/public/state.js +72 -0
- package/src/web/public/style.css +996 -949
- package/src/web/public/utils.js +37 -0
- package/src/web/public/websocket.js +267 -0
- package/src/web/public/ws.js +19 -0
- package/src/web/session-bridge.ts +193 -194
- package/.claude/settings.local.json +0 -14
- package/.kilo/plans/1780064105789-mighty-pixel.md +0 -171
- package/.kilo/plans/1780293725888-quick-wizard.md +0 -62
- package/.lonny/plan-web-cwd-status.md +0 -38
- package/src/tools/exec.ts +0 -348
package/src/config/index.ts
CHANGED
|
@@ -1,335 +1,342 @@
|
|
|
1
|
-
import * as fs from 'node:fs'
|
|
2
|
-
import * as os from 'node:os'
|
|
3
|
-
import * as path from 'node:path'
|
|
4
|
-
|
|
5
|
-
export interface Config {
|
|
6
|
-
apiKey: string
|
|
7
|
-
baseUrl?: string
|
|
8
|
-
mode: 'code' | 'plan' | 'ask'
|
|
9
|
-
provider: 'openai' | 'anthropic' | 'google' | 'ollama'
|
|
10
|
-
model: string
|
|
11
|
-
contextWindow: number
|
|
12
|
-
cwd: string
|
|
13
|
-
autoApprove: boolean
|
|
14
|
-
thinking?: boolean
|
|
15
|
-
reasoningEffort?: string
|
|
16
|
-
enableCache?: boolean
|
|
17
|
-
strictTools?: boolean
|
|
18
|
-
temperature?: number
|
|
19
|
-
maxTokens?: number
|
|
20
|
-
tavilyApiKey?: string
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* 模型上下文窗口大小映射表(基于 2026 年最新数据)
|
|
25
|
-
* 单位:tokens
|
|
26
|
-
*/
|
|
27
|
-
const MODEL_CONTEXT_WINDOWS: Record<string, number> = {
|
|
28
|
-
// Doubao 系列
|
|
29
|
-
'doubao-seed-2.0-code': 256_000,
|
|
30
|
-
'doubao-seed-2.0-pro': 256_000,
|
|
31
|
-
'doubao-seed-2.0-lite': 256_000,
|
|
32
|
-
'doubao-seed-2.0-mini': 256_000,
|
|
33
|
-
'doubao-seed-2.0-code-preview-260215': 256_000,
|
|
34
|
-
'doubao-seed-1.8': 256_000,
|
|
35
|
-
'doubao-seed-1.6': 256_000,
|
|
36
|
-
|
|
37
|
-
// DeepSeek 系列
|
|
38
|
-
'deepseek-v4-pro': 1_000_000,
|
|
39
|
-
'deepseek-v4-flash': 256_000,
|
|
40
|
-
'deepseek-v3.2': 160_000,
|
|
41
|
-
'deepseek-r1': 128_000,
|
|
42
|
-
'deepseek-chat': 128_000,
|
|
43
|
-
'deepseek-reasoner': 128_000,
|
|
44
|
-
|
|
45
|
-
// Kimi 系列
|
|
46
|
-
'kimi-k2.5': 262_144,
|
|
47
|
-
'kimi-k2.6': 262_144,
|
|
48
|
-
'kimi-k2-thinking': 256_000,
|
|
49
|
-
|
|
50
|
-
// MiniMax 系列
|
|
51
|
-
'minimax-m2.5': 200_000,
|
|
52
|
-
'minimax-m2.7': 200_000,
|
|
53
|
-
'minimax-m2': 205_000,
|
|
54
|
-
|
|
55
|
-
// GLM 系列
|
|
56
|
-
'glm-5': 200_000,
|
|
57
|
-
'glm-5.1': 200_000,
|
|
58
|
-
'glm-4.7-flashx': 200_000,
|
|
59
|
-
'glm-4-flash': 128_000,
|
|
60
|
-
'glm-4': 128_000,
|
|
61
|
-
|
|
62
|
-
// Qwen 系列
|
|
63
|
-
'qwen3.6-plus': 1_000_000,
|
|
64
|
-
'qwen3.7-max': 1_000_000,
|
|
65
|
-
'qwen3-max': 256_000,
|
|
66
|
-
'qwen3.5-plus': 1_000_000,
|
|
67
|
-
'qwen-flash': 1_000_000,
|
|
68
|
-
|
|
69
|
-
// Claude 系列
|
|
70
|
-
'claude-opus-4.7': 1_000_000,
|
|
71
|
-
'claude-opus-4.6': 1_000_000,
|
|
72
|
-
'claude-sonnet-4.6': 1_000_000,
|
|
73
|
-
'claude-haiku-4.5': 200_000,
|
|
74
|
-
|
|
75
|
-
// GPT 系列
|
|
76
|
-
'gpt-5.5': 1_000_000,
|
|
77
|
-
'gpt-5.4': 1_000_000,
|
|
78
|
-
'gpt-5.1': 1_000_000,
|
|
79
|
-
'gpt-5.2-codex': 512_000,
|
|
80
|
-
'gpt-4.1': 1_000_000,
|
|
81
|
-
'gpt-4.1-mini': 1_000_000,
|
|
82
|
-
'gpt-4o': 128_000,
|
|
83
|
-
'gpt-4': 128_000,
|
|
84
|
-
|
|
85
|
-
// Gemini 系列
|
|
86
|
-
'gemini-2.5-pro': 1_000_000,
|
|
87
|
-
'gemini-2.5-flash': 1_000_000,
|
|
88
|
-
'gemini-3.1-pro': 1_000_000,
|
|
89
|
-
'gemini-3.1-flash': 1_000_000,
|
|
90
|
-
|
|
91
|
-
// Llama 系列
|
|
92
|
-
'llama-4-maverick': 1_000_000,
|
|
93
|
-
'llama-4-scout': 10_000_000,
|
|
94
|
-
'llama-3.1': 128_000,
|
|
95
|
-
'llama-3': 128_000,
|
|
96
|
-
|
|
97
|
-
// Mistral 系列
|
|
98
|
-
'mistral-small-3.1': 128_000,
|
|
99
|
-
'mistral-small-3.2': 128_000,
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* 模糊匹配规则
|
|
104
|
-
*/
|
|
105
|
-
interface FuzzyMatchRule {
|
|
106
|
-
keywords: string[]
|
|
107
|
-
windowSize: number
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* 模糊匹配规则列表(从高优先级到低优先级)
|
|
112
|
-
*/
|
|
113
|
-
const fuzzyMatchRules: FuzzyMatchRule[] = [
|
|
114
|
-
// 首先匹配 128K 模型(防止 gpt-4 匹配到 gpt-5 规则)
|
|
115
|
-
{
|
|
116
|
-
keywords: [
|
|
117
|
-
'gpt-4o',
|
|
118
|
-
'gpt 4o',
|
|
119
|
-
'gpt-4',
|
|
120
|
-
'gpt 4',
|
|
121
|
-
'llama-3',
|
|
122
|
-
'llama 3',
|
|
123
|
-
'mistral-small',
|
|
124
|
-
'mistral small',
|
|
125
|
-
'glm-4-flash',
|
|
126
|
-
'glm 4 flash',
|
|
127
|
-
'glm-4',
|
|
128
|
-
],
|
|
129
|
-
windowSize: 128_000,
|
|
130
|
-
},
|
|
131
|
-
// 百万上下文模型 - 更高优先级(更精确的关键词)
|
|
132
|
-
{
|
|
133
|
-
keywords: [
|
|
134
|
-
'deepseek-v4',
|
|
135
|
-
'deepseek v4',
|
|
136
|
-
'qwen3.6',
|
|
137
|
-
'qwen 3.6',
|
|
138
|
-
'qwen3.7',
|
|
139
|
-
'qwen 3.7',
|
|
140
|
-
'qwen3.5',
|
|
141
|
-
'qwen 3.5',
|
|
142
|
-
'qwen-flash',
|
|
143
|
-
'qwen flash',
|
|
144
|
-
],
|
|
145
|
-
windowSize: 1_000_000,
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
keywords: [
|
|
149
|
-
'claude-opus',
|
|
150
|
-
'claude sonnet',
|
|
151
|
-
'claude-sonnet',
|
|
152
|
-
'gpt-5.',
|
|
153
|
-
'gpt 5',
|
|
154
|
-
'gpt-4.1',
|
|
155
|
-
'gpt 4.1',
|
|
156
|
-
'gemini-2.5',
|
|
157
|
-
'gemini 2.5',
|
|
158
|
-
'gemini-3.1',
|
|
159
|
-
'gemini 3.1',
|
|
160
|
-
'llama-4',
|
|
161
|
-
'llama 4',
|
|
162
|
-
],
|
|
163
|
-
windowSize: 1_000_000,
|
|
164
|
-
},
|
|
165
|
-
// 256K 上下文模型
|
|
166
|
-
{
|
|
167
|
-
keywords: [
|
|
168
|
-
'doubao-seed-2.0',
|
|
169
|
-
'doubao seed 2.0',
|
|
170
|
-
'doubao-seed-1',
|
|
171
|
-
'doubao seed 1',
|
|
172
|
-
'kimi-k2',
|
|
173
|
-
'kimi k2',
|
|
174
|
-
'kimi-k2.5',
|
|
175
|
-
'kimi k2.5',
|
|
176
|
-
'kimi-k2.6',
|
|
177
|
-
'kimi k2.6',
|
|
178
|
-
'qwen3',
|
|
179
|
-
'qwen 3',
|
|
180
|
-
'qwen3-max',
|
|
181
|
-
'qwen3 max',
|
|
182
|
-
],
|
|
183
|
-
windowSize: 256_000,
|
|
184
|
-
},
|
|
185
|
-
// 200K 上下文模型
|
|
186
|
-
{
|
|
187
|
-
keywords: [
|
|
188
|
-
'minimax',
|
|
189
|
-
'glm-5',
|
|
190
|
-
'glm 5',
|
|
191
|
-
'glm-5.1',
|
|
192
|
-
'glm 5.1',
|
|
193
|
-
'glm-4.7',
|
|
194
|
-
'glm 4.7',
|
|
195
|
-
'claude-haiku',
|
|
196
|
-
'claude haiku',
|
|
197
|
-
],
|
|
198
|
-
windowSize: 200_000,
|
|
199
|
-
},
|
|
200
|
-
// 160K 上下文模型
|
|
201
|
-
{
|
|
202
|
-
keywords: ['deepseek-v3', 'deepseek v3', 'deepseek-v3.2', 'deepseek v3.2'],
|
|
203
|
-
windowSize: 160_000,
|
|
204
|
-
},
|
|
205
|
-
// 最后匹配通用关键词(deepseek)
|
|
206
|
-
{
|
|
207
|
-
keywords: ['deepseek'],
|
|
208
|
-
windowSize: 128_000,
|
|
209
|
-
},
|
|
210
|
-
]
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* 根据模型名称获取上下文窗口大小(改进的模糊匹配)
|
|
214
|
-
* @param modelName 模型名称(不区分大小写)
|
|
215
|
-
* @returns 上下文窗口大小(tokens)
|
|
216
|
-
*/
|
|
217
|
-
export function getContextWindowForModel(modelName: string): number {
|
|
218
|
-
// 处理空字符串
|
|
219
|
-
if (!modelName) {
|
|
220
|
-
return 128_000
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const normalizedModelName = modelName.toLowerCase().replace(/[-_]/g, ' ')
|
|
224
|
-
|
|
225
|
-
// 1. 精确匹配(原始键名)
|
|
226
|
-
if (MODEL_CONTEXT_WINDOWS[modelName]) {
|
|
227
|
-
return MODEL_CONTEXT_WINDOWS[modelName]
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// 2. 精确匹配(小写键名)
|
|
231
|
-
if (MODEL_CONTEXT_WINDOWS[modelName.toLowerCase()]) {
|
|
232
|
-
return MODEL_CONTEXT_WINDOWS[modelName.toLowerCase()]
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// 3. 前缀匹配(支持部分匹配)
|
|
236
|
-
for (const [pattern, size] of Object.entries(MODEL_CONTEXT_WINDOWS)) {
|
|
237
|
-
const normalizedPattern = pattern.toLowerCase().replace(/[-_]/g, ' ')
|
|
238
|
-
if (
|
|
239
|
-
normalizedModelName.includes(normalizedPattern) ||
|
|
240
|
-
normalizedPattern.includes(normalizedModelName)
|
|
241
|
-
) {
|
|
242
|
-
return size
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// 4. 模糊匹配(关键词匹配)- 按照规则顺序(优先级)处理
|
|
247
|
-
for (const rule of fuzzyMatchRules) {
|
|
248
|
-
for (const keyword of rule.keywords) {
|
|
249
|
-
const normalizedKeyword = keyword.toLowerCase().replace(/[-_]/g, ' ')
|
|
250
|
-
if (normalizedModelName.includes(normalizedKeyword)) {
|
|
251
|
-
return rule.windowSize
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// 5. 兜底默认值
|
|
257
|
-
return 128_000
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
interface JsonConfig {
|
|
261
|
-
apiKey?: string
|
|
262
|
-
baseUrl?: string
|
|
263
|
-
provider?: string
|
|
264
|
-
model?: string
|
|
265
|
-
thinking?: boolean
|
|
266
|
-
reasoningEffort?: string
|
|
267
|
-
autoApprove?: boolean
|
|
268
|
-
enableCache?: boolean
|
|
269
|
-
strictTools?: boolean
|
|
270
|
-
contextWindow?: number
|
|
271
|
-
temperature?: number
|
|
272
|
-
maxTokens?: number
|
|
273
|
-
tavilyApiKey?: string
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
let cachedJsonConfig: JsonConfig | null = null
|
|
277
|
-
|
|
278
|
-
function loadJsonConfig(): JsonConfig {
|
|
279
|
-
if (cachedJsonConfig) return cachedJsonConfig
|
|
280
|
-
const configPath = path.join(os.homedir(), '.lonny', 'config.json')
|
|
281
|
-
try {
|
|
282
|
-
const raw = fs.readFileSync(configPath, 'utf-8')
|
|
283
|
-
cachedJsonConfig = JSON.parse(raw) as JsonConfig
|
|
284
|
-
return cachedJsonConfig
|
|
285
|
-
} catch {
|
|
286
|
-
cachedJsonConfig = {}
|
|
287
|
-
return cachedJsonConfig
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
1
|
+
import * as fs from 'node:fs'
|
|
2
|
+
import * as os from 'node:os'
|
|
3
|
+
import * as path from 'node:path'
|
|
4
|
+
|
|
5
|
+
export interface Config {
|
|
6
|
+
apiKey: string
|
|
7
|
+
baseUrl?: string
|
|
8
|
+
mode: 'code' | 'plan' | 'ask'
|
|
9
|
+
provider: 'openai' | 'anthropic' | 'google' | 'ollama'
|
|
10
|
+
model: string
|
|
11
|
+
contextWindow: number
|
|
12
|
+
cwd: string
|
|
13
|
+
autoApprove: boolean
|
|
14
|
+
thinking?: boolean
|
|
15
|
+
reasoningEffort?: string
|
|
16
|
+
enableCache?: boolean
|
|
17
|
+
strictTools?: boolean
|
|
18
|
+
temperature?: number
|
|
19
|
+
maxTokens?: number
|
|
20
|
+
tavilyApiKey?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 模型上下文窗口大小映射表(基于 2026 年最新数据)
|
|
25
|
+
* 单位:tokens
|
|
26
|
+
*/
|
|
27
|
+
const MODEL_CONTEXT_WINDOWS: Record<string, number> = {
|
|
28
|
+
// Doubao 系列
|
|
29
|
+
'doubao-seed-2.0-code': 256_000,
|
|
30
|
+
'doubao-seed-2.0-pro': 256_000,
|
|
31
|
+
'doubao-seed-2.0-lite': 256_000,
|
|
32
|
+
'doubao-seed-2.0-mini': 256_000,
|
|
33
|
+
'doubao-seed-2.0-code-preview-260215': 256_000,
|
|
34
|
+
'doubao-seed-1.8': 256_000,
|
|
35
|
+
'doubao-seed-1.6': 256_000,
|
|
36
|
+
|
|
37
|
+
// DeepSeek 系列
|
|
38
|
+
'deepseek-v4-pro': 1_000_000,
|
|
39
|
+
'deepseek-v4-flash': 256_000,
|
|
40
|
+
'deepseek-v3.2': 160_000,
|
|
41
|
+
'deepseek-r1': 128_000,
|
|
42
|
+
'deepseek-chat': 128_000,
|
|
43
|
+
'deepseek-reasoner': 128_000,
|
|
44
|
+
|
|
45
|
+
// Kimi 系列
|
|
46
|
+
'kimi-k2.5': 262_144,
|
|
47
|
+
'kimi-k2.6': 262_144,
|
|
48
|
+
'kimi-k2-thinking': 256_000,
|
|
49
|
+
|
|
50
|
+
// MiniMax 系列
|
|
51
|
+
'minimax-m2.5': 200_000,
|
|
52
|
+
'minimax-m2.7': 200_000,
|
|
53
|
+
'minimax-m2': 205_000,
|
|
54
|
+
|
|
55
|
+
// GLM 系列
|
|
56
|
+
'glm-5': 200_000,
|
|
57
|
+
'glm-5.1': 200_000,
|
|
58
|
+
'glm-4.7-flashx': 200_000,
|
|
59
|
+
'glm-4-flash': 128_000,
|
|
60
|
+
'glm-4': 128_000,
|
|
61
|
+
|
|
62
|
+
// Qwen 系列
|
|
63
|
+
'qwen3.6-plus': 1_000_000,
|
|
64
|
+
'qwen3.7-max': 1_000_000,
|
|
65
|
+
'qwen3-max': 256_000,
|
|
66
|
+
'qwen3.5-plus': 1_000_000,
|
|
67
|
+
'qwen-flash': 1_000_000,
|
|
68
|
+
|
|
69
|
+
// Claude 系列
|
|
70
|
+
'claude-opus-4.7': 1_000_000,
|
|
71
|
+
'claude-opus-4.6': 1_000_000,
|
|
72
|
+
'claude-sonnet-4.6': 1_000_000,
|
|
73
|
+
'claude-haiku-4.5': 200_000,
|
|
74
|
+
|
|
75
|
+
// GPT 系列
|
|
76
|
+
'gpt-5.5': 1_000_000,
|
|
77
|
+
'gpt-5.4': 1_000_000,
|
|
78
|
+
'gpt-5.1': 1_000_000,
|
|
79
|
+
'gpt-5.2-codex': 512_000,
|
|
80
|
+
'gpt-4.1': 1_000_000,
|
|
81
|
+
'gpt-4.1-mini': 1_000_000,
|
|
82
|
+
'gpt-4o': 128_000,
|
|
83
|
+
'gpt-4': 128_000,
|
|
84
|
+
|
|
85
|
+
// Gemini 系列
|
|
86
|
+
'gemini-2.5-pro': 1_000_000,
|
|
87
|
+
'gemini-2.5-flash': 1_000_000,
|
|
88
|
+
'gemini-3.1-pro': 1_000_000,
|
|
89
|
+
'gemini-3.1-flash': 1_000_000,
|
|
90
|
+
|
|
91
|
+
// Llama 系列
|
|
92
|
+
'llama-4-maverick': 1_000_000,
|
|
93
|
+
'llama-4-scout': 10_000_000,
|
|
94
|
+
'llama-3.1': 128_000,
|
|
95
|
+
'llama-3': 128_000,
|
|
96
|
+
|
|
97
|
+
// Mistral 系列
|
|
98
|
+
'mistral-small-3.1': 128_000,
|
|
99
|
+
'mistral-small-3.2': 128_000,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 模糊匹配规则
|
|
104
|
+
*/
|
|
105
|
+
interface FuzzyMatchRule {
|
|
106
|
+
keywords: string[]
|
|
107
|
+
windowSize: number
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 模糊匹配规则列表(从高优先级到低优先级)
|
|
112
|
+
*/
|
|
113
|
+
const fuzzyMatchRules: FuzzyMatchRule[] = [
|
|
114
|
+
// 首先匹配 128K 模型(防止 gpt-4 匹配到 gpt-5 规则)
|
|
115
|
+
{
|
|
116
|
+
keywords: [
|
|
117
|
+
'gpt-4o',
|
|
118
|
+
'gpt 4o',
|
|
119
|
+
'gpt-4',
|
|
120
|
+
'gpt 4',
|
|
121
|
+
'llama-3',
|
|
122
|
+
'llama 3',
|
|
123
|
+
'mistral-small',
|
|
124
|
+
'mistral small',
|
|
125
|
+
'glm-4-flash',
|
|
126
|
+
'glm 4 flash',
|
|
127
|
+
'glm-4',
|
|
128
|
+
],
|
|
129
|
+
windowSize: 128_000,
|
|
130
|
+
},
|
|
131
|
+
// 百万上下文模型 - 更高优先级(更精确的关键词)
|
|
132
|
+
{
|
|
133
|
+
keywords: [
|
|
134
|
+
'deepseek-v4',
|
|
135
|
+
'deepseek v4',
|
|
136
|
+
'qwen3.6',
|
|
137
|
+
'qwen 3.6',
|
|
138
|
+
'qwen3.7',
|
|
139
|
+
'qwen 3.7',
|
|
140
|
+
'qwen3.5',
|
|
141
|
+
'qwen 3.5',
|
|
142
|
+
'qwen-flash',
|
|
143
|
+
'qwen flash',
|
|
144
|
+
],
|
|
145
|
+
windowSize: 1_000_000,
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
keywords: [
|
|
149
|
+
'claude-opus',
|
|
150
|
+
'claude sonnet',
|
|
151
|
+
'claude-sonnet',
|
|
152
|
+
'gpt-5.',
|
|
153
|
+
'gpt 5',
|
|
154
|
+
'gpt-4.1',
|
|
155
|
+
'gpt 4.1',
|
|
156
|
+
'gemini-2.5',
|
|
157
|
+
'gemini 2.5',
|
|
158
|
+
'gemini-3.1',
|
|
159
|
+
'gemini 3.1',
|
|
160
|
+
'llama-4',
|
|
161
|
+
'llama 4',
|
|
162
|
+
],
|
|
163
|
+
windowSize: 1_000_000,
|
|
164
|
+
},
|
|
165
|
+
// 256K 上下文模型
|
|
166
|
+
{
|
|
167
|
+
keywords: [
|
|
168
|
+
'doubao-seed-2.0',
|
|
169
|
+
'doubao seed 2.0',
|
|
170
|
+
'doubao-seed-1',
|
|
171
|
+
'doubao seed 1',
|
|
172
|
+
'kimi-k2',
|
|
173
|
+
'kimi k2',
|
|
174
|
+
'kimi-k2.5',
|
|
175
|
+
'kimi k2.5',
|
|
176
|
+
'kimi-k2.6',
|
|
177
|
+
'kimi k2.6',
|
|
178
|
+
'qwen3',
|
|
179
|
+
'qwen 3',
|
|
180
|
+
'qwen3-max',
|
|
181
|
+
'qwen3 max',
|
|
182
|
+
],
|
|
183
|
+
windowSize: 256_000,
|
|
184
|
+
},
|
|
185
|
+
// 200K 上下文模型
|
|
186
|
+
{
|
|
187
|
+
keywords: [
|
|
188
|
+
'minimax',
|
|
189
|
+
'glm-5',
|
|
190
|
+
'glm 5',
|
|
191
|
+
'glm-5.1',
|
|
192
|
+
'glm 5.1',
|
|
193
|
+
'glm-4.7',
|
|
194
|
+
'glm 4.7',
|
|
195
|
+
'claude-haiku',
|
|
196
|
+
'claude haiku',
|
|
197
|
+
],
|
|
198
|
+
windowSize: 200_000,
|
|
199
|
+
},
|
|
200
|
+
// 160K 上下文模型
|
|
201
|
+
{
|
|
202
|
+
keywords: ['deepseek-v3', 'deepseek v3', 'deepseek-v3.2', 'deepseek v3.2'],
|
|
203
|
+
windowSize: 160_000,
|
|
204
|
+
},
|
|
205
|
+
// 最后匹配通用关键词(deepseek)
|
|
206
|
+
{
|
|
207
|
+
keywords: ['deepseek'],
|
|
208
|
+
windowSize: 128_000,
|
|
209
|
+
},
|
|
210
|
+
]
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* 根据模型名称获取上下文窗口大小(改进的模糊匹配)
|
|
214
|
+
* @param modelName 模型名称(不区分大小写)
|
|
215
|
+
* @returns 上下文窗口大小(tokens)
|
|
216
|
+
*/
|
|
217
|
+
export function getContextWindowForModel(modelName: string): number {
|
|
218
|
+
// 处理空字符串
|
|
219
|
+
if (!modelName) {
|
|
220
|
+
return 128_000
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const normalizedModelName = modelName.toLowerCase().replace(/[-_]/g, ' ')
|
|
224
|
+
|
|
225
|
+
// 1. 精确匹配(原始键名)
|
|
226
|
+
if (MODEL_CONTEXT_WINDOWS[modelName]) {
|
|
227
|
+
return MODEL_CONTEXT_WINDOWS[modelName]
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// 2. 精确匹配(小写键名)
|
|
231
|
+
if (MODEL_CONTEXT_WINDOWS[modelName.toLowerCase()]) {
|
|
232
|
+
return MODEL_CONTEXT_WINDOWS[modelName.toLowerCase()]
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// 3. 前缀匹配(支持部分匹配)
|
|
236
|
+
for (const [pattern, size] of Object.entries(MODEL_CONTEXT_WINDOWS)) {
|
|
237
|
+
const normalizedPattern = pattern.toLowerCase().replace(/[-_]/g, ' ')
|
|
238
|
+
if (
|
|
239
|
+
normalizedModelName.includes(normalizedPattern) ||
|
|
240
|
+
normalizedPattern.includes(normalizedModelName)
|
|
241
|
+
) {
|
|
242
|
+
return size
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// 4. 模糊匹配(关键词匹配)- 按照规则顺序(优先级)处理
|
|
247
|
+
for (const rule of fuzzyMatchRules) {
|
|
248
|
+
for (const keyword of rule.keywords) {
|
|
249
|
+
const normalizedKeyword = keyword.toLowerCase().replace(/[-_]/g, ' ')
|
|
250
|
+
if (normalizedModelName.includes(normalizedKeyword)) {
|
|
251
|
+
return rule.windowSize
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// 5. 兜底默认值
|
|
257
|
+
return 128_000
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
interface JsonConfig {
|
|
261
|
+
apiKey?: string
|
|
262
|
+
baseUrl?: string
|
|
263
|
+
provider?: string
|
|
264
|
+
model?: string
|
|
265
|
+
thinking?: boolean
|
|
266
|
+
reasoningEffort?: string
|
|
267
|
+
autoApprove?: boolean
|
|
268
|
+
enableCache?: boolean
|
|
269
|
+
strictTools?: boolean
|
|
270
|
+
contextWindow?: number
|
|
271
|
+
temperature?: number
|
|
272
|
+
maxTokens?: number
|
|
273
|
+
tavilyApiKey?: string
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
let cachedJsonConfig: JsonConfig | null = null
|
|
277
|
+
|
|
278
|
+
function loadJsonConfig(): JsonConfig {
|
|
279
|
+
if (cachedJsonConfig) return cachedJsonConfig
|
|
280
|
+
const configPath = path.join(os.homedir(), '.lonny', 'config.json')
|
|
281
|
+
try {
|
|
282
|
+
const raw = fs.readFileSync(configPath, 'utf-8')
|
|
283
|
+
cachedJsonConfig = JSON.parse(raw) as JsonConfig
|
|
284
|
+
return cachedJsonConfig
|
|
285
|
+
} catch {
|
|
286
|
+
cachedJsonConfig = {}
|
|
287
|
+
return cachedJsonConfig
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function saveJsonConfig(config: JsonConfig): void {
|
|
292
|
+
const configPath = path.join(os.homedir(), '.lonny', 'config.json')
|
|
293
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8')
|
|
294
|
+
cachedJsonConfig = config
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** Check if the model is a DeepSeek model (supports enable_cache). */
|
|
298
|
+
function isDeepSeekModel(model: string, baseUrl?: string): boolean {
|
|
299
|
+
if (/deepseek/i.test(model)) return true
|
|
300
|
+
if (baseUrl && /deepseek/i.test(baseUrl)) return true
|
|
301
|
+
return false
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function loadConfig(options?: {
|
|
305
|
+
mode?: 'code' | 'plan' | 'ask'
|
|
306
|
+
autoApprove?: boolean
|
|
307
|
+
cwd?: string
|
|
308
|
+
}): Config {
|
|
309
|
+
const jsonConfig = loadJsonConfig()
|
|
310
|
+
|
|
311
|
+
const model = jsonConfig.model || 'deepseek-v4-flash'
|
|
312
|
+
const baseUrl = process.env.LONNY_BASE_URL || jsonConfig.baseUrl || undefined
|
|
313
|
+
const provider = (process.env.LONNY_PROVIDER || jsonConfig.provider || 'openai') as
|
|
314
|
+
| 'openai'
|
|
315
|
+
| 'anthropic'
|
|
316
|
+
| 'google'
|
|
317
|
+
| 'ollama'
|
|
318
|
+
|
|
319
|
+
// Auto-enable cache for DeepSeek models unless explicitly disabled
|
|
320
|
+
const enableCache = jsonConfig.enableCache ?? (isDeepSeekModel(model, baseUrl) || undefined)
|
|
321
|
+
|
|
322
|
+
// 根据模型名称获取上下文窗口大小,如果配置中有则优先使用配置值
|
|
323
|
+
const contextWindow = jsonConfig.contextWindow || getContextWindowForModel(model)
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
apiKey: jsonConfig.apiKey || '',
|
|
327
|
+
baseUrl,
|
|
328
|
+
provider,
|
|
329
|
+
mode: options?.mode || 'code',
|
|
330
|
+
model,
|
|
331
|
+
contextWindow,
|
|
332
|
+
cwd: options?.cwd || process.cwd(),
|
|
333
|
+
autoApprove: options?.autoApprove ?? jsonConfig.autoApprove ?? false,
|
|
334
|
+
thinking: jsonConfig.thinking,
|
|
335
|
+
reasoningEffort: jsonConfig.reasoningEffort,
|
|
336
|
+
enableCache,
|
|
337
|
+
strictTools: jsonConfig.strictTools,
|
|
338
|
+
temperature: jsonConfig.temperature,
|
|
339
|
+
maxTokens: jsonConfig.maxTokens,
|
|
340
|
+
tavilyApiKey: jsonConfig.tavilyApiKey,
|
|
341
|
+
}
|
|
342
|
+
}
|