lonny-agent 0.1.8 → 0.2.1
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/.kilo/plans/1780293725888-quick-wizard.md +62 -0
- package/.lonny/plan-web-cwd-status.md +38 -0
- package/dist/agent/compaction.d.ts +5 -0
- package/dist/agent/compaction.d.ts.map +1 -1
- package/dist/agent/compaction.js +5 -0
- package/dist/agent/compaction.js.map +1 -1
- package/dist/agent/prompt-builder.d.ts.map +1 -1
- package/dist/agent/prompt-builder.js +39 -4
- package/dist/agent/prompt-builder.js.map +1 -1
- package/dist/agent/providers/google.js +1 -1
- package/dist/agent/providers/google.js.map +1 -1
- package/dist/agent/providers/openai.js +1 -1
- package/dist/agent/providers/openai.js.map +1 -1
- package/dist/agent/session.d.ts.map +1 -1
- package/dist/agent/session.js +18 -2
- package/dist/agent/session.js.map +1 -1
- package/dist/config/__tests__/context-window.test.d.ts +2 -0
- package/dist/config/__tests__/context-window.test.d.ts.map +1 -0
- package/dist/config/__tests__/context-window.test.js +80 -0
- package/dist/config/__tests__/context-window.test.js.map +1 -0
- package/dist/config/index.d.ts +7 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +211 -0
- package/dist/config/index.js.map +1 -1
- package/dist/pi-tui/tui.d.ts.map +1 -1
- package/dist/pi-tui/tui.js +7 -16
- package/dist/pi-tui/tui.js.map +1 -1
- package/dist/tools/__tests__/edit.test.js +139 -0
- package/dist/tools/__tests__/edit.test.js.map +1 -1
- package/dist/tools/__tests__/fetch.test.js +11 -9
- package/dist/tools/__tests__/fetch.test.js.map +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +185 -32
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/install_superpowers.d.ts +3 -0
- package/dist/tools/install_superpowers.d.ts.map +1 -0
- package/dist/tools/install_superpowers.js +207 -0
- package/dist/tools/install_superpowers.js.map +1 -0
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +25 -1
- package/dist/tui/index.js.map +1 -1
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +56 -0
- package/dist/web/index.js.map +1 -1
- package/dist/web/public/app.js +8 -0
- package/dist/web/public/index.html +6 -0
- package/dist/web/public/style.css +11 -0
- package/m.role+' +0 -0
- package/package.json +1 -1
- package/src/agent/compaction.ts +6 -0
- package/src/agent/prompt-builder.ts +40 -4
- package/src/agent/providers/google.ts +1 -1
- package/src/agent/providers/openai.ts +1 -1
- package/src/agent/session.ts +19 -2
- package/src/config/__tests__/context-window.test.ts +94 -0
- package/src/config/index.ts +242 -0
- package/src/pi-tui/tui.ts +8 -14
- package/src/tools/__tests__/edit.test.ts +155 -0
- package/src/tools/__tests__/fetch.test.ts +12 -10
- package/src/tools/edit.ts +202 -33
- package/src/tools/registry.ts +265 -265
- package/src/tui/index.ts +23 -1
- package/src/web/index.ts +71 -0
- package/src/web/public/app.js +9 -0
- package/src/web/public/index.html +6 -0
- package/src/web/public/style.css +11 -0
- package/.lonny/fix-new-command-session-cleanup.md +0 -65
- package/.lonny/tui-autocomplete-above.md +0 -62
package/src/agent/compaction.ts
CHANGED
|
@@ -6,6 +6,12 @@ import type { LLMMessage } from './llm.js'
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const DEFAULT_MAX_TOKENS = 256_000
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 获取默认的上下文窗口大小(保持向后兼容)
|
|
12
|
+
* @deprecated 请使用 config.contextWindow 代替
|
|
13
|
+
*/
|
|
14
|
+
export const DEFAULT_CONTEXT_WINDOW = DEFAULT_MAX_TOKENS
|
|
9
15
|
const COMPACTION_THRESHOLD = 0.75 // compact when usage exceeds 75% of budget
|
|
10
16
|
|
|
11
17
|
/** Rough token estimation (4 chars ~= 1 token).
|
|
@@ -67,11 +67,14 @@ ${getToolListForMode(config.mode)}`
|
|
|
67
67
|
// ── Mode-specific instructions ───────────────────────────────────────────
|
|
68
68
|
const modeInstructions =
|
|
69
69
|
config.mode === 'plan'
|
|
70
|
-
? `You are a planning agent. Your
|
|
70
|
+
? `You are a planning agent. Your ONLY job is to investigate the codebase and produce an actionable implementation plan with a todo list.
|
|
71
|
+
|
|
72
|
+
You CANNOT edit files. You do NOT have access to edit, bash (write mode), exec, or install_skill.
|
|
73
|
+
Any attempt to call these tools will FAIL — they are simply unavailable in this mode.
|
|
71
74
|
|
|
72
75
|
RULES (plan-specific):
|
|
73
76
|
1. Read first: Use read/grep/glob tools to gather all context you need before planning.
|
|
74
|
-
2. You NEVER edit source files. You ONLY use read-only tools (read/glob/grep/ls/find/git/search) and bash (read-only commands only).
|
|
77
|
+
2. You NEVER edit source files. You ONLY use read-only tools (read/glob/grep/ls/find/git/search) and bash (read-only commands only).
|
|
75
78
|
3. Use \`bash\` for investigation only — NEVER to modify files, install packages, or run write operations.
|
|
76
79
|
4. Your ONLY output is a plan file saved via \`write_plan\`. You CANNOT modify the codebase directly.
|
|
77
80
|
5. You MUST persist the final plan AND todo list to a file in \`.lonny/\` using \`write_plan\`. The \`write_plan\` content MUST include both ## Plan and ## Todo List sections.
|
|
@@ -117,7 +120,40 @@ RULES (code-specific):
|
|
|
117
120
|
7. COST OPTIMIZATION (CRITICAL): Each API call costs money. You have a hard limit of ~5 API calls per task.
|
|
118
121
|
8. TODO LIST MAINTENANCE: After completing a task item, update the corresponding plan file in \`.lonny/\` by checking off the TODO item (change \`- [ ]\` to \`- [x]\`). Use \`read\` to find the plan file, then \`edit\` to update the checkbox.`
|
|
119
122
|
|
|
120
|
-
// ──
|
|
123
|
+
// ── Built-in development methodologies ─────────────────────────────────
|
|
124
|
+
// Embedded directly from Superpowers — no skill files needed.
|
|
125
|
+
const methodologySection =
|
|
126
|
+
config.mode === 'code'
|
|
127
|
+
? `
|
|
128
|
+
|
|
129
|
+
## Development Methodology
|
|
130
|
+
|
|
131
|
+
### Systematic Debugging
|
|
132
|
+
When investigating a bug or test failure, follow this process:
|
|
133
|
+
1. **Root Cause** — Read errors carefully, reproduce consistently, check recent changes
|
|
134
|
+
2. **Pattern Analysis** — Find working examples, compare with broken code
|
|
135
|
+
3. **Hypothesis** — Form single hypothesis, test minimally (one change at a time)
|
|
136
|
+
4. **Implementation** — Create failing test first, implement minimal fix, verify
|
|
137
|
+
|
|
138
|
+
NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST.
|
|
139
|
+
If you've tried 3+ fixes without success, question the architecture — don't keep guessing.
|
|
140
|
+
|
|
141
|
+
### Verification Before Completion
|
|
142
|
+
After ANY change, you MUST verify it actually works before claiming success:
|
|
143
|
+
- Run the relevant tests and check the exit code
|
|
144
|
+
- Only then report the result
|
|
145
|
+
|
|
146
|
+
You CANNOT claim "tests pass" or "fix works" without having run the verification command in this same turn. Evidence before assertions, always.`
|
|
147
|
+
: config.mode === 'plan'
|
|
148
|
+
? `
|
|
149
|
+
|
|
150
|
+
## Design-First Planning
|
|
151
|
+
Before writing a plan, explore the user's request thoroughly:
|
|
152
|
+
1. Ask clarifying questions — understand purpose, constraints, success criteria
|
|
153
|
+
2. Explore alternatives — consider 2-3 different approaches with trade-offs
|
|
154
|
+
3. Break into bite-sized tasks (2-5 minutes each) with exact file paths and verification steps
|
|
155
|
+
4. Save the plan via \`write_plan\``
|
|
156
|
+
: ''
|
|
121
157
|
const envSection = `Environment:
|
|
122
158
|
- Platform: ${platform} ${release} (${arch})
|
|
123
159
|
- Shell: ${shell}
|
|
@@ -135,5 +171,5 @@ ${isWindows ? ' - Use `type` instead of `cat`, `dir` instead of `ls`, `where` i
|
|
|
135
171
|
${sharedRules}`
|
|
136
172
|
return `${modeInstructions}
|
|
137
173
|
|
|
138
|
-
${envSection}${rulesSection}${skillsSection}`
|
|
174
|
+
${envSection}${rulesSection}${methodologySection}${skillsSection}`
|
|
139
175
|
}
|
|
@@ -91,7 +91,7 @@ export class OpenAIProvider implements LLMProvider {
|
|
|
91
91
|
content: m.content || '',
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
|
-
if (m.role === 'assistant' && m.tool_calls) {
|
|
94
|
+
if (m.role === 'assistant' && m.tool_calls && m.tool_calls.length > 0) {
|
|
95
95
|
return {
|
|
96
96
|
role: 'assistant',
|
|
97
97
|
content: m.content,
|
package/src/agent/session.ts
CHANGED
|
@@ -388,6 +388,8 @@ export class Session {
|
|
|
388
388
|
const out = this.output
|
|
389
389
|
printUserMessage(userPrompt, out)
|
|
390
390
|
this.messages.push({ role: 'user', content: userPrompt })
|
|
391
|
+
// Save immediately after user message so page refresh doesn't lose it
|
|
392
|
+
this.save()
|
|
391
393
|
|
|
392
394
|
// Reset per-turn counters
|
|
393
395
|
this.turnInputTokens = 0
|
|
@@ -568,6 +570,12 @@ export class Session {
|
|
|
568
570
|
}
|
|
569
571
|
if (chunk.finish_reason === 'stop' || chunk.finish_reason === 'end_turn') {
|
|
570
572
|
if (toolCalls.length === 0) {
|
|
573
|
+
const finalAssistantMsg: LLMMessage = {
|
|
574
|
+
role: 'assistant',
|
|
575
|
+
content: fullResponse || null,
|
|
576
|
+
reasoning_content: reasoningContent,
|
|
577
|
+
}
|
|
578
|
+
this.messages.push(finalAssistantMsg)
|
|
571
579
|
printTokenStats(
|
|
572
580
|
this.turnInputTokens,
|
|
573
581
|
this.turnOutputTokens,
|
|
@@ -610,6 +618,12 @@ export class Session {
|
|
|
610
618
|
|
|
611
619
|
if (toolCalls.length === 0) {
|
|
612
620
|
if (fullResponse) {
|
|
621
|
+
const finalAssistantMsg: LLMMessage = {
|
|
622
|
+
role: 'assistant',
|
|
623
|
+
content: fullResponse,
|
|
624
|
+
reasoning_content: reasoningContent,
|
|
625
|
+
}
|
|
626
|
+
this.messages.push(finalAssistantMsg)
|
|
613
627
|
printTokenStats(
|
|
614
628
|
this.turnInputTokens,
|
|
615
629
|
this.turnOutputTokens,
|
|
@@ -639,6 +653,7 @@ export class Session {
|
|
|
639
653
|
reasoning_content: reasoningContent,
|
|
640
654
|
}
|
|
641
655
|
this.messages.push(assistantMsg)
|
|
656
|
+
this.save()
|
|
642
657
|
|
|
643
658
|
// ── User confirmation for write-type tool calls ──
|
|
644
659
|
if (!this.config.autoApprove && this.output?.confirmTool && toolCalls.length > 0) {
|
|
@@ -691,13 +706,15 @@ export class Session {
|
|
|
691
706
|
this.messages.push(resultMsg)
|
|
692
707
|
}
|
|
693
708
|
|
|
709
|
+
this.save()
|
|
710
|
+
|
|
694
711
|
// End the current turn before next iteration — frontend creates a new message
|
|
695
712
|
bus.emit(EventChannels.TURN_END, { iterations, toolCallCount: toolCalls.length })
|
|
696
713
|
|
|
697
714
|
// Check if compaction is needed
|
|
698
|
-
if (shouldCompact(this.messages)) {
|
|
715
|
+
if (shouldCompact(this.messages, this.config.contextWindow)) {
|
|
699
716
|
const before = this.messages.length
|
|
700
|
-
const result = compact(this.messages)
|
|
717
|
+
const result = compact(this.messages, this.config.contextWindow)
|
|
701
718
|
if (result.compressed) {
|
|
702
719
|
this.messages = result.messages
|
|
703
720
|
bus.emit(EventChannels.COMPACTION_TRIGGERED, { before, after: result.newCount })
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest'
|
|
2
|
+
import { getContextWindowForModel } from '../index.js'
|
|
3
|
+
|
|
4
|
+
describe('getContextWindowForModel', () => {
|
|
5
|
+
// 精确匹配测试
|
|
6
|
+
test('exact match with known model names', () => {
|
|
7
|
+
expect(getContextWindowForModel('doubao-seed-2.0-code')).toBe(256_000)
|
|
8
|
+
expect(getContextWindowForModel('deepseek-v4-pro')).toBe(1_000_000)
|
|
9
|
+
expect(getContextWindowForModel('qwen3.6-plus')).toBe(1_000_000)
|
|
10
|
+
expect(getContextWindowForModel('claude-sonnet-4.6')).toBe(1_000_000)
|
|
11
|
+
expect(getContextWindowForModel('gpt-5.5')).toBe(1_000_000)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
// 大小写不敏感测试
|
|
15
|
+
test('case insensitive matching', () => {
|
|
16
|
+
expect(getContextWindowForModel('DOUBAO-SEED-2.0-CODE')).toBe(256_000)
|
|
17
|
+
expect(getContextWindowForModel('DeepSeek-V4-Pro')).toBe(1_000_000)
|
|
18
|
+
expect(getContextWindowForModel('QWEN3.6-PLUS')).toBe(1_000_000)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
// 模糊匹配 - 含空格的变体
|
|
22
|
+
test('fuzzy match with space variants', () => {
|
|
23
|
+
expect(getContextWindowForModel('doubao seed 2.0 code')).toBe(256_000)
|
|
24
|
+
expect(getContextWindowForModel('deepseek v4 pro')).toBe(1_000_000)
|
|
25
|
+
expect(getContextWindowForModel('qwen 3.6 plus')).toBe(1_000_000)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// 模糊匹配 - 下划线与连字符变体
|
|
29
|
+
test('fuzzy match with underscore and hyphen variants', () => {
|
|
30
|
+
expect(getContextWindowForModel('doubao_seed_2.0_code')).toBe(256_000)
|
|
31
|
+
expect(getContextWindowForModel('deepseek_v4_pro')).toBe(1_000_000)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// 模糊匹配 - 含前缀和后缀的模型名
|
|
35
|
+
test('fuzzy match with prefixed and suffixed model names', () => {
|
|
36
|
+
expect(getContextWindowForModel('my-doubao-seed-2.0-code')).toBe(256_000)
|
|
37
|
+
expect(getContextWindowForModel('deepseek-v4-pro-variant')).toBe(1_000_000)
|
|
38
|
+
expect(getContextWindowForModel('qwen3.6-plus-prod')).toBe(1_000_000)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
// 模糊匹配 - 关键词匹配(不完整的模型名)
|
|
42
|
+
test('fuzzy match with keyword matching (partial model names)', () => {
|
|
43
|
+
// 百万上下文模型
|
|
44
|
+
expect(getContextWindowForModel('deepseek-v4')).toBe(1_000_000)
|
|
45
|
+
expect(getContextWindowForModel('qwen3.6')).toBe(1_000_000)
|
|
46
|
+
expect(getContextWindowForModel('qwen3.7')).toBe(1_000_000)
|
|
47
|
+
expect(getContextWindowForModel('claude opus 4.7')).toBe(1_000_000)
|
|
48
|
+
expect(getContextWindowForModel('gemini 2.5')).toBe(1_000_000)
|
|
49
|
+
|
|
50
|
+
// 256K 上下文模型
|
|
51
|
+
expect(getContextWindowForModel('doubao-seed-2.0')).toBe(256_000)
|
|
52
|
+
expect(getContextWindowForModel('kimi-k2.5')).toBe(262_144) // 实际值
|
|
53
|
+
expect(getContextWindowForModel('kimi-k2.6')).toBe(262_144) // 实际值
|
|
54
|
+
|
|
55
|
+
// 200K 上下文模型
|
|
56
|
+
expect(getContextWindowForModel('minimax')).toBe(200_000)
|
|
57
|
+
expect(getContextWindowForModel('glm-5')).toBe(200_000)
|
|
58
|
+
expect(getContextWindowForModel('glm-5.1')).toBe(200_000)
|
|
59
|
+
expect(getContextWindowForModel('claude-haiku')).toBe(200_000)
|
|
60
|
+
|
|
61
|
+
// 160K 上下文模型
|
|
62
|
+
expect(getContextWindowForModel('deepseek-v3')).toBe(160_000)
|
|
63
|
+
expect(getContextWindowForModel('deepseek-v3.2')).toBe(160_000)
|
|
64
|
+
|
|
65
|
+
// 128K 上下文模型
|
|
66
|
+
expect(getContextWindowForModel('gpt-4o')).toBe(128_000)
|
|
67
|
+
expect(getContextWindowForModel('gpt-4')).toBe(128_000)
|
|
68
|
+
expect(getContextWindowForModel('llama-3')).toBe(128_000)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
// 未知模型名 - 兜底默认值
|
|
72
|
+
test('unknown model names return default 128K', () => {
|
|
73
|
+
expect(getContextWindowForModel('unknown-model')).toBe(128_000)
|
|
74
|
+
expect(getContextWindowForModel('random-model-name')).toBe(128_000)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
// 用户配置中常用的模型名
|
|
78
|
+
test('common model names from user configs', () => {
|
|
79
|
+
// 豆包模型
|
|
80
|
+
expect(getContextWindowForModel('Doubao-Seed-2.0-Code')).toBe(256_000)
|
|
81
|
+
|
|
82
|
+
// DeepSeek 模型
|
|
83
|
+
expect(getContextWindowForModel('deepseek-chat')).toBe(128_000)
|
|
84
|
+
expect(getContextWindowForModel('deepseek-reasoner')).toBe(128_000)
|
|
85
|
+
|
|
86
|
+
// Kimi 模型
|
|
87
|
+
expect(getContextWindowForModel('kimi-k2.5')).toBe(262_144) // 实际值
|
|
88
|
+
expect(getContextWindowForModel('kimi-k2.6')).toBe(262_144) // 实际值
|
|
89
|
+
|
|
90
|
+
// 通义千问
|
|
91
|
+
expect(getContextWindowForModel('qwen3.6-plus')).toBe(1_000_000)
|
|
92
|
+
expect(getContextWindowForModel('qwen3.7-max')).toBe(1_000_000)
|
|
93
|
+
})
|
|
94
|
+
})
|
package/src/config/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ export interface Config {
|
|
|
8
8
|
mode: 'code' | 'plan' | 'ask'
|
|
9
9
|
provider: 'openai' | 'anthropic' | 'google' | 'ollama'
|
|
10
10
|
model: string
|
|
11
|
+
contextWindow: number
|
|
11
12
|
cwd: string
|
|
12
13
|
autoApprove: boolean
|
|
13
14
|
thinking?: boolean
|
|
@@ -19,6 +20,243 @@ export interface Config {
|
|
|
19
20
|
tavilyApiKey?: string
|
|
20
21
|
}
|
|
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
|
+
|
|
22
260
|
interface JsonConfig {
|
|
23
261
|
apiKey?: string
|
|
24
262
|
baseUrl?: string
|
|
@@ -69,6 +307,9 @@ export function loadConfig(options?: {
|
|
|
69
307
|
// Auto-enable cache for DeepSeek models unless explicitly disabled
|
|
70
308
|
const enableCache = jsonConfig.enableCache ?? (isDeepSeekModel(model, baseUrl) || undefined)
|
|
71
309
|
|
|
310
|
+
// 根据模型名称获取上下文窗口大小
|
|
311
|
+
const contextWindow = getContextWindowForModel(model)
|
|
312
|
+
|
|
72
313
|
return {
|
|
73
314
|
apiKey: jsonConfig.apiKey || '',
|
|
74
315
|
baseUrl,
|
|
@@ -79,6 +320,7 @@ export function loadConfig(options?: {
|
|
|
79
320
|
| 'ollama',
|
|
80
321
|
mode: options?.mode || 'code',
|
|
81
322
|
model,
|
|
323
|
+
contextWindow,
|
|
82
324
|
cwd: options?.cwd || process.cwd(),
|
|
83
325
|
autoApprove: options?.autoApprove ?? jsonConfig.autoApprove ?? false,
|
|
84
326
|
thinking: jsonConfig.thinking,
|
package/src/pi-tui/tui.ts
CHANGED
|
@@ -1351,26 +1351,20 @@ export class TUI extends Container {
|
|
|
1351
1351
|
const targetRow = Math.max(0, Math.min(cursorPos.row, totalLines - 1))
|
|
1352
1352
|
const targetCol = Math.max(0, cursorPos.col)
|
|
1353
1353
|
|
|
1354
|
-
//
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1354
|
+
// Use absolute cursor positioning (CUP) to avoid scroll artifacts.
|
|
1355
|
+
// Relative movement (\x1b[NB/\x1b[NA) with a stale baseline can
|
|
1356
|
+
// cause the terminal to scroll, inserting blank lines, especially
|
|
1357
|
+
// when the editor overlay cursor is far from the rendered content.
|
|
1358
|
+
let buffer = `\x1b[${targetRow + 1};${targetCol + 1}H`
|
|
1359
|
+
|
|
1360
|
+
if (this.showHardwareCursor) {
|
|
1361
|
+
buffer += '\x1b[?25h'
|
|
1361
1362
|
}
|
|
1362
|
-
// Move to absolute column (1-indexed)
|
|
1363
|
-
buffer += `\x1b[${targetCol + 1}G`
|
|
1364
1363
|
|
|
1365
1364
|
if (buffer) {
|
|
1366
1365
|
this.terminal.write(buffer)
|
|
1367
1366
|
}
|
|
1368
1367
|
|
|
1369
1368
|
this.hardwareCursorRow = targetRow
|
|
1370
|
-
if (this.showHardwareCursor) {
|
|
1371
|
-
this.terminal.showCursor()
|
|
1372
|
-
} else {
|
|
1373
|
-
this.terminal.hideCursor()
|
|
1374
|
-
}
|
|
1375
1369
|
}
|
|
1376
1370
|
}
|