thatgfsj-code 1.0.3 → 3.0.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.
- package/CHANGELOG.md +192 -0
- package/DEVELOPMENT.md +286 -0
- package/dist/app/index.d.ts +40 -3
- package/dist/app/index.d.ts.map +1 -1
- package/dist/app/index.js +87 -12
- package/dist/app/index.js.map +1 -1
- package/dist/cache/fingerprint.d.ts +49 -0
- package/dist/cache/fingerprint.d.ts.map +1 -0
- package/dist/cache/fingerprint.js +100 -0
- package/dist/cache/fingerprint.js.map +1 -0
- package/dist/cache/index.d.ts +15 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +15 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/smartModel.d.ts +40 -0
- package/dist/cache/smartModel.d.ts.map +1 -0
- package/dist/cache/smartModel.js +43 -0
- package/dist/cache/smartModel.js.map +1 -0
- package/dist/cache/stats.d.ts +93 -0
- package/dist/cache/stats.d.ts.map +1 -0
- package/dist/cache/stats.js +155 -0
- package/dist/cache/stats.js.map +1 -0
- package/dist/cache/volatile.d.ts +32 -0
- package/dist/cache/volatile.d.ts.map +1 -0
- package/dist/cache/volatile.js +44 -0
- package/dist/cache/volatile.js.map +1 -0
- package/dist/cmd/index.d.ts +21 -0
- package/dist/cmd/index.d.ts.map +1 -1
- package/dist/cmd/index.js +155 -31
- package/dist/cmd/index.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +8 -0
- package/dist/config/index.js.map +1 -1
- package/dist/config/types.d.ts +14 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/llm/anthropic.d.ts +55 -5
- package/dist/llm/anthropic.d.ts.map +1 -1
- package/dist/llm/anthropic.js +172 -26
- package/dist/llm/anthropic.js.map +1 -1
- package/dist/llm/index.d.ts +26 -8
- package/dist/llm/index.d.ts.map +1 -1
- package/dist/llm/index.js +83 -15
- package/dist/llm/index.js.map +1 -1
- package/dist/llm/openai.d.ts +28 -8
- package/dist/llm/openai.d.ts.map +1 -1
- package/dist/llm/openai.js +58 -8
- package/dist/llm/openai.js.map +1 -1
- package/dist/llm/provider.d.ts +40 -8
- package/dist/llm/provider.d.ts.map +1 -1
- package/dist/llm/provider.js +8 -1
- package/dist/llm/provider.js.map +1 -1
- package/dist/prompts/index.d.ts +32 -0
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +33 -11
- package/dist/prompts/index.js.map +1 -1
- package/dist/session/index.d.ts +43 -3
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/index.js +114 -17
- package/dist/session/index.js.map +1 -1
- package/dist/skills/brainstorming.js +33 -33
- package/dist/skills/code-review.js +35 -35
- package/dist/skills/executing-plans.js +18 -18
- package/dist/skills/frontend-design.js +25 -25
- package/dist/skills/git-workflow.js +26 -26
- package/dist/skills/improve-architecture.js +28 -28
- package/dist/skills/neuroweave.js +37 -37
- package/dist/skills/playwright.js +62 -62
- package/dist/skills/prototype.js +20 -20
- package/dist/skills/subagent.js +19 -19
- package/dist/skills/supabase.js +34 -34
- package/dist/skills/systematic-debugging.js +34 -34
- package/dist/skills/tdd.js +29 -29
- package/dist/skills/triage.js +25 -25
- package/dist/skills/verification.js +22 -22
- package/dist/skills/writing-plans.js +32 -32
- package/dist/tools/nwt.js +16 -16
- package/dist/tui/app.d.ts.map +1 -1
- package/dist/tui/app.js +9 -2
- package/dist/tui/app.js.map +1 -1
- package/dist/tui/components/ChatList.d.ts.map +1 -1
- package/dist/tui/components/ChatList.js +4 -3
- package/dist/tui/components/ChatList.js.map +1 -1
- package/dist/tui/components/Header.d.ts +8 -0
- package/dist/tui/components/Header.d.ts.map +1 -1
- package/dist/tui/components/Header.js +17 -3
- package/dist/tui/components/Header.js.map +1 -1
- package/dist/tui/components/InitWizard.d.ts +12 -1
- package/dist/tui/components/InitWizard.d.ts.map +1 -1
- package/dist/tui/components/InitWizard.js +58 -3
- package/dist/tui/components/InitWizard.js.map +1 -1
- package/dist/tui/components/ToolCall.d.ts +1 -1
- package/dist/tui/components/ToolCall.d.ts.map +1 -1
- package/dist/tui/components/ToolCall.js +4 -4
- package/dist/tui/components/ToolCall.js.map +1 -1
- package/dist/tui/hooks/useChat.d.ts +22 -0
- package/dist/tui/hooks/useChat.d.ts.map +1 -1
- package/dist/tui/hooks/useChat.js +127 -50
- package/dist/tui/hooks/useChat.js.map +1 -1
- package/dist/tui/hooks/useCommands.d.ts.map +1 -1
- package/dist/tui/hooks/useCommands.js +78 -0
- package/dist/tui/hooks/useCommands.js.map +1 -1
- package/dist/types.d.ts +79 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -1
- package/dist/utils/stableStringify.d.ts +21 -0
- package/dist/utils/stableStringify.d.ts.map +1 -0
- package/dist/utils/stableStringify.js +53 -0
- package/dist/utils/stableStringify.js.map +1 -0
- package/dist/utils/thinking.d.ts +59 -0
- package/dist/utils/thinking.d.ts.map +1 -0
- package/dist/utils/thinking.js +104 -0
- package/dist/utils/thinking.js.map +1 -0
- package/install.bat +63 -0
- package/install.ps1 +238 -0
- package/install.sh +113 -0
- package/package.json +7 -4
- package/src/app/index.ts +180 -108
- package/src/cache/fingerprint.ts +101 -0
- package/src/cache/index.ts +15 -0
- package/src/cache/smartModel.ts +52 -0
- package/src/cache/stats.ts +199 -0
- package/src/cache/volatile.ts +47 -0
- package/src/cmd/index.tsx +288 -160
- package/src/config/index.ts +156 -148
- package/src/config/providers.ts +234 -234
- package/src/config/types.ts +67 -53
- package/src/hooks/index.ts +111 -111
- package/src/llm/anthropic.ts +388 -243
- package/src/llm/gemini.ts +169 -169
- package/src/llm/index.ts +265 -200
- package/src/llm/openai.ts +243 -196
- package/src/llm/provider.ts +66 -34
- package/src/prompts/index.ts +260 -220
- package/src/session/compactor.ts +103 -103
- package/src/session/index.ts +181 -87
- package/src/session/message.ts +42 -42
- package/src/skills/brainstorming.ts +43 -43
- package/src/skills/code-review.ts +45 -45
- package/src/skills/executing-plans.ts +27 -27
- package/src/skills/frontend-design.ts +35 -35
- package/src/skills/git-workflow.ts +36 -36
- package/src/skills/improve-architecture.ts +38 -38
- package/src/skills/index.ts +136 -136
- package/src/skills/neuroweave.ts +47 -47
- package/src/skills/playwright.ts +72 -72
- package/src/skills/prototype.ts +30 -30
- package/src/skills/subagent.ts +28 -28
- package/src/skills/supabase.ts +44 -44
- package/src/skills/systematic-debugging.ts +44 -44
- package/src/skills/tdd.ts +39 -39
- package/src/skills/triage.ts +35 -35
- package/src/skills/verification.ts +31 -31
- package/src/skills/writing-plans.ts +42 -42
- package/src/tools/nwt.ts +598 -598
- package/src/tools/types.ts +122 -122
- package/src/tui/app.tsx +199 -186
- package/src/tui/components/ChatList.tsx +41 -32
- package/src/tui/components/ChatMessage.tsx +54 -54
- package/src/tui/components/Header.tsx +56 -28
- package/src/tui/components/InitWizard.tsx +217 -132
- package/src/tui/components/Markdown.tsx +35 -35
- package/src/tui/components/ModelSelector.tsx +87 -87
- package/src/tui/components/StatusBar.tsx +30 -30
- package/src/tui/components/Thinking.tsx +17 -17
- package/src/tui/components/ToolCall.tsx +102 -71
- package/src/tui/components/UserInput.tsx +131 -131
- package/src/tui/hooks/useChat.ts +287 -194
- package/src/tui/hooks/useCommands.ts +234 -154
- package/src/tui/index.ts +6 -6
- package/src/tui/welcome.ts +177 -177
- package/src/types.ts +104 -42
- package/src/utils/diff.ts +71 -71
- package/src/utils/project.ts +99 -99
- package/src/utils/stableStringify.ts +47 -0
- package/src/utils/thinking.ts +119 -0
- package/tests/cache/fingerprint.test.ts +75 -0
- package/tests/cache/stableStringify.test.ts +43 -0
- package/tests/cache/stats.test.ts +146 -0
- package/tests/cache/volatile.test.ts +75 -0
- package/tests/smoke-pollution.mjs +78 -0
- package/tests/smoke-thinking.mjs +110 -0
- package/tests/smoke-tool-stream.mjs +69 -0
- package/tests/smoke-tool.mjs +117 -0
package/src/tui/welcome.ts
CHANGED
|
@@ -1,177 +1,177 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Welcome / Setup Wizard - Clean UI
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import chalk from 'chalk';
|
|
6
|
-
import readline from 'readline';
|
|
7
|
-
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
8
|
-
import { join } from 'path';
|
|
9
|
-
import { homedir } from 'os';
|
|
10
|
-
import { PROVIDERS, getModelsForProvider, listProviders, isCustomProvider } from '../config/providers.js';
|
|
11
|
-
import type { ProviderName } from '../config/types.js';
|
|
12
|
-
|
|
13
|
-
const line = chalk.gray('─'.repeat(52));
|
|
14
|
-
|
|
15
|
-
export class WelcomeScreen {
|
|
16
|
-
|
|
17
|
-
static show(hasApiKey: boolean): void {
|
|
18
|
-
if (hasApiKey) return;
|
|
19
|
-
|
|
20
|
-
console.log();
|
|
21
|
-
console.log(chalk.cyan.bold(' ⚡ Thatgfsj Code') + chalk.gray(' v0.5.0'));
|
|
22
|
-
console.log(chalk.gray(' AI Coding Assistant'));
|
|
23
|
-
console.log(line);
|
|
24
|
-
console.log();
|
|
25
|
-
console.log(chalk.yellow(' ⚠ No API key configured'));
|
|
26
|
-
console.log();
|
|
27
|
-
console.log(chalk.gray(' Run ') + chalk.cyan.bold('gfcode init') + chalk.gray(' to set up your provider.'));
|
|
28
|
-
console.log();
|
|
29
|
-
console.log(chalk.gray(' Supported providers:'));
|
|
30
|
-
console.log(chalk.gray(' OpenAI · Claude · DeepSeek · Kimi · GLM · Gemini · 中转站'));
|
|
31
|
-
console.log(line);
|
|
32
|
-
console.log();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
static async interactiveSetup(): Promise<void> {
|
|
36
|
-
console.clear();
|
|
37
|
-
console.log();
|
|
38
|
-
console.log(chalk.cyan.bold(' ⚡ Thatgfsj Code') + chalk.gray(' - Setup'));
|
|
39
|
-
console.log(line);
|
|
40
|
-
console.log();
|
|
41
|
-
|
|
42
|
-
const rl = readline.createInterface({
|
|
43
|
-
input: process.stdin,
|
|
44
|
-
output: process.stdout,
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
const ask = (prompt: string): Promise<string> =>
|
|
48
|
-
new Promise(resolve => rl.question(prompt, answer => resolve(answer.trim())));
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
// ── Step 1: Provider ────────────────────────────────
|
|
52
|
-
console.log(chalk.bold(' 1. Choose Provider'));
|
|
53
|
-
console.log();
|
|
54
|
-
const providers = listProviders();
|
|
55
|
-
providers.forEach((p, i) => {
|
|
56
|
-
const num = chalk.cyan((i + 1).toString().padStart(2));
|
|
57
|
-
console.log(` ${num} ${p.name}`);
|
|
58
|
-
});
|
|
59
|
-
console.log();
|
|
60
|
-
|
|
61
|
-
const choice = await ask(chalk.cyan(' ❯ '));
|
|
62
|
-
const selected = providers[parseInt(choice) - 1] || providers[0];
|
|
63
|
-
const providerName = selected.key;
|
|
64
|
-
|
|
65
|
-
// ── Step 2: API Key ─────────────────────────────────
|
|
66
|
-
console.log();
|
|
67
|
-
console.log(chalk.bold(' 2. API Key'));
|
|
68
|
-
console.log();
|
|
69
|
-
|
|
70
|
-
let baseUrl: string | undefined;
|
|
71
|
-
|
|
72
|
-
if (isCustomProvider(providerName)) {
|
|
73
|
-
console.log(chalk.yellow(' Custom Provider (Relay Station)'));
|
|
74
|
-
console.log(chalk.gray(' OpenAI-compatible or Anthropic-compatible'));
|
|
75
|
-
console.log();
|
|
76
|
-
baseUrl = await ask(chalk.cyan(' Base URL ❯ '));
|
|
77
|
-
if (!baseUrl) {
|
|
78
|
-
console.log(chalk.red(' ❌ Base URL required'));
|
|
79
|
-
rl.close();
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
baseUrl = baseUrl.replace(/\/+$/, '');
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const apiKey = await ask(chalk.cyan(' API Key ❯ '));
|
|
86
|
-
|
|
87
|
-
// ── Step 3: Model ───────────────────────────────────
|
|
88
|
-
console.log();
|
|
89
|
-
console.log(chalk.bold(' 3. Choose Model'));
|
|
90
|
-
console.log();
|
|
91
|
-
|
|
92
|
-
let model: string;
|
|
93
|
-
if (isCustomProvider(providerName)) {
|
|
94
|
-
console.log(chalk.gray(' Enter model name for your relay station'));
|
|
95
|
-
console.log(chalk.gray(' e.g. gpt-4o-mini, claude-sonnet-4-20250514'));
|
|
96
|
-
console.log();
|
|
97
|
-
model = await ask(chalk.cyan(' Model ❯ '));
|
|
98
|
-
if (!model) {
|
|
99
|
-
console.log(chalk.red(' ❌ Model name required'));
|
|
100
|
-
rl.close();
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
} else {
|
|
104
|
-
const models = getModelsForProvider(providerName);
|
|
105
|
-
models.forEach((m, i) => {
|
|
106
|
-
const num = chalk.cyan((i + 1).toString().padStart(2));
|
|
107
|
-
console.log(` ${num} ${chalk.white(m.name)} ${chalk.gray(m.desc)}`);
|
|
108
|
-
});
|
|
109
|
-
console.log();
|
|
110
|
-
const modelChoice = await ask(chalk.cyan(' ❯ '));
|
|
111
|
-
const selectedModel = models[parseInt(modelChoice) - 1] || models[0];
|
|
112
|
-
model = selectedModel.id;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// ── Step 4: Context Length ───────────────────────────
|
|
116
|
-
console.log();
|
|
117
|
-
console.log(chalk.bold(' 4. Context Length'));
|
|
118
|
-
console.log();
|
|
119
|
-
console.log(chalk.gray(' Max messages kept in conversation history'));
|
|
120
|
-
console.log(chalk.gray(' Higher = more context, more tokens used'));
|
|
121
|
-
console.log();
|
|
122
|
-
console.log(chalk.cyan(' 1') + ' 20 ' + chalk.gray('(short,节省 token)'));
|
|
123
|
-
console.log(chalk.cyan(' 2') + ' 50 ' + chalk.gray('(default,推荐)'));
|
|
124
|
-
console.log(chalk.cyan(' 3') + ' 100 ' + chalk.gray('(long,长对话)'));
|
|
125
|
-
console.log(chalk.cyan(' 4') + ' 200 ' + chalk.gray('(very long,超长对话)'));
|
|
126
|
-
console.log();
|
|
127
|
-
const ctxChoice = await ask(chalk.cyan(' ❯ '));
|
|
128
|
-
const ctxMap: Record<string, number> = { '1': 20, '2': 50, '3': 100, '4': 200 };
|
|
129
|
-
const contextLength = ctxMap[ctxChoice] || 50;
|
|
130
|
-
|
|
131
|
-
// ── Save ────────────────────────────────────────────
|
|
132
|
-
this.saveConfig(providerName, model, apiKey, baseUrl, contextLength);
|
|
133
|
-
|
|
134
|
-
// ── Done ────────────────────────────────────────────
|
|
135
|
-
console.log();
|
|
136
|
-
console.log(line);
|
|
137
|
-
console.log(chalk.green.bold(' ✅ Configuration saved!'));
|
|
138
|
-
console.log();
|
|
139
|
-
console.log(chalk.gray(' Config: ') + chalk.white(join(homedir(), '.thatgfsj', 'config.json')));
|
|
140
|
-
console.log(chalk.gray(' Run ') + chalk.cyan.bold('gfcode') + chalk.gray(' to start.'));
|
|
141
|
-
console.log(line);
|
|
142
|
-
console.log();
|
|
143
|
-
|
|
144
|
-
} finally {
|
|
145
|
-
rl.close();
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
private static saveConfig(provider: ProviderName, model: string, apiKey: string, baseUrl?: string, contextLength = 50): void {
|
|
150
|
-
const configDir = join(homedir(), '.thatgfsj');
|
|
151
|
-
const configPath = join(configDir, 'config.json');
|
|
152
|
-
|
|
153
|
-
if (!existsSync(configDir)) {
|
|
154
|
-
mkdirSync(configDir, { recursive: true });
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const config: Record<string, any> = {
|
|
158
|
-
provider,
|
|
159
|
-
model,
|
|
160
|
-
apiKey,
|
|
161
|
-
temperature: 0.7,
|
|
162
|
-
maxTokens: 4096,
|
|
163
|
-
contextLength,
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
if (baseUrl) config.baseUrl = baseUrl;
|
|
167
|
-
|
|
168
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
169
|
-
|
|
170
|
-
console.log();
|
|
171
|
-
console.log(chalk.gray(' Provider: ') + chalk.white(provider));
|
|
172
|
-
console.log(chalk.gray(' Model: ') + chalk.white(model));
|
|
173
|
-
if (baseUrl) {
|
|
174
|
-
console.log(chalk.gray(' URL: ') + chalk.white(baseUrl));
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Welcome / Setup Wizard - Clean UI
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import readline from 'readline';
|
|
7
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { homedir } from 'os';
|
|
10
|
+
import { PROVIDERS, getModelsForProvider, listProviders, isCustomProvider } from '../config/providers.js';
|
|
11
|
+
import type { ProviderName } from '../config/types.js';
|
|
12
|
+
|
|
13
|
+
const line = chalk.gray('─'.repeat(52));
|
|
14
|
+
|
|
15
|
+
export class WelcomeScreen {
|
|
16
|
+
|
|
17
|
+
static show(hasApiKey: boolean): void {
|
|
18
|
+
if (hasApiKey) return;
|
|
19
|
+
|
|
20
|
+
console.log();
|
|
21
|
+
console.log(chalk.cyan.bold(' ⚡ Thatgfsj Code') + chalk.gray(' v0.5.0'));
|
|
22
|
+
console.log(chalk.gray(' AI Coding Assistant'));
|
|
23
|
+
console.log(line);
|
|
24
|
+
console.log();
|
|
25
|
+
console.log(chalk.yellow(' ⚠ No API key configured'));
|
|
26
|
+
console.log();
|
|
27
|
+
console.log(chalk.gray(' Run ') + chalk.cyan.bold('gfcode init') + chalk.gray(' to set up your provider.'));
|
|
28
|
+
console.log();
|
|
29
|
+
console.log(chalk.gray(' Supported providers:'));
|
|
30
|
+
console.log(chalk.gray(' OpenAI · Claude · DeepSeek · Kimi · GLM · Gemini · 中转站'));
|
|
31
|
+
console.log(line);
|
|
32
|
+
console.log();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static async interactiveSetup(): Promise<void> {
|
|
36
|
+
console.clear();
|
|
37
|
+
console.log();
|
|
38
|
+
console.log(chalk.cyan.bold(' ⚡ Thatgfsj Code') + chalk.gray(' - Setup'));
|
|
39
|
+
console.log(line);
|
|
40
|
+
console.log();
|
|
41
|
+
|
|
42
|
+
const rl = readline.createInterface({
|
|
43
|
+
input: process.stdin,
|
|
44
|
+
output: process.stdout,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const ask = (prompt: string): Promise<string> =>
|
|
48
|
+
new Promise(resolve => rl.question(prompt, answer => resolve(answer.trim())));
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
// ── Step 1: Provider ────────────────────────────────
|
|
52
|
+
console.log(chalk.bold(' 1. Choose Provider'));
|
|
53
|
+
console.log();
|
|
54
|
+
const providers = listProviders();
|
|
55
|
+
providers.forEach((p, i) => {
|
|
56
|
+
const num = chalk.cyan((i + 1).toString().padStart(2));
|
|
57
|
+
console.log(` ${num} ${p.name}`);
|
|
58
|
+
});
|
|
59
|
+
console.log();
|
|
60
|
+
|
|
61
|
+
const choice = await ask(chalk.cyan(' ❯ '));
|
|
62
|
+
const selected = providers[parseInt(choice) - 1] || providers[0];
|
|
63
|
+
const providerName = selected.key;
|
|
64
|
+
|
|
65
|
+
// ── Step 2: API Key ─────────────────────────────────
|
|
66
|
+
console.log();
|
|
67
|
+
console.log(chalk.bold(' 2. API Key'));
|
|
68
|
+
console.log();
|
|
69
|
+
|
|
70
|
+
let baseUrl: string | undefined;
|
|
71
|
+
|
|
72
|
+
if (isCustomProvider(providerName)) {
|
|
73
|
+
console.log(chalk.yellow(' Custom Provider (Relay Station)'));
|
|
74
|
+
console.log(chalk.gray(' OpenAI-compatible or Anthropic-compatible'));
|
|
75
|
+
console.log();
|
|
76
|
+
baseUrl = await ask(chalk.cyan(' Base URL ❯ '));
|
|
77
|
+
if (!baseUrl) {
|
|
78
|
+
console.log(chalk.red(' ❌ Base URL required'));
|
|
79
|
+
rl.close();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
baseUrl = baseUrl.replace(/\/+$/, '');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const apiKey = await ask(chalk.cyan(' API Key ❯ '));
|
|
86
|
+
|
|
87
|
+
// ── Step 3: Model ───────────────────────────────────
|
|
88
|
+
console.log();
|
|
89
|
+
console.log(chalk.bold(' 3. Choose Model'));
|
|
90
|
+
console.log();
|
|
91
|
+
|
|
92
|
+
let model: string;
|
|
93
|
+
if (isCustomProvider(providerName)) {
|
|
94
|
+
console.log(chalk.gray(' Enter model name for your relay station'));
|
|
95
|
+
console.log(chalk.gray(' e.g. gpt-4o-mini, claude-sonnet-4-20250514'));
|
|
96
|
+
console.log();
|
|
97
|
+
model = await ask(chalk.cyan(' Model ❯ '));
|
|
98
|
+
if (!model) {
|
|
99
|
+
console.log(chalk.red(' ❌ Model name required'));
|
|
100
|
+
rl.close();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
const models = getModelsForProvider(providerName);
|
|
105
|
+
models.forEach((m, i) => {
|
|
106
|
+
const num = chalk.cyan((i + 1).toString().padStart(2));
|
|
107
|
+
console.log(` ${num} ${chalk.white(m.name)} ${chalk.gray(m.desc)}`);
|
|
108
|
+
});
|
|
109
|
+
console.log();
|
|
110
|
+
const modelChoice = await ask(chalk.cyan(' ❯ '));
|
|
111
|
+
const selectedModel = models[parseInt(modelChoice) - 1] || models[0];
|
|
112
|
+
model = selectedModel.id;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ── Step 4: Context Length ───────────────────────────
|
|
116
|
+
console.log();
|
|
117
|
+
console.log(chalk.bold(' 4. Context Length'));
|
|
118
|
+
console.log();
|
|
119
|
+
console.log(chalk.gray(' Max messages kept in conversation history'));
|
|
120
|
+
console.log(chalk.gray(' Higher = more context, more tokens used'));
|
|
121
|
+
console.log();
|
|
122
|
+
console.log(chalk.cyan(' 1') + ' 20 ' + chalk.gray('(short,节省 token)'));
|
|
123
|
+
console.log(chalk.cyan(' 2') + ' 50 ' + chalk.gray('(default,推荐)'));
|
|
124
|
+
console.log(chalk.cyan(' 3') + ' 100 ' + chalk.gray('(long,长对话)'));
|
|
125
|
+
console.log(chalk.cyan(' 4') + ' 200 ' + chalk.gray('(very long,超长对话)'));
|
|
126
|
+
console.log();
|
|
127
|
+
const ctxChoice = await ask(chalk.cyan(' ❯ '));
|
|
128
|
+
const ctxMap: Record<string, number> = { '1': 20, '2': 50, '3': 100, '4': 200 };
|
|
129
|
+
const contextLength = ctxMap[ctxChoice] || 50;
|
|
130
|
+
|
|
131
|
+
// ── Save ────────────────────────────────────────────
|
|
132
|
+
this.saveConfig(providerName, model, apiKey, baseUrl, contextLength);
|
|
133
|
+
|
|
134
|
+
// ── Done ────────────────────────────────────────────
|
|
135
|
+
console.log();
|
|
136
|
+
console.log(line);
|
|
137
|
+
console.log(chalk.green.bold(' ✅ Configuration saved!'));
|
|
138
|
+
console.log();
|
|
139
|
+
console.log(chalk.gray(' Config: ') + chalk.white(join(homedir(), '.thatgfsj', 'config.json')));
|
|
140
|
+
console.log(chalk.gray(' Run ') + chalk.cyan.bold('gfcode') + chalk.gray(' to start.'));
|
|
141
|
+
console.log(line);
|
|
142
|
+
console.log();
|
|
143
|
+
|
|
144
|
+
} finally {
|
|
145
|
+
rl.close();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private static saveConfig(provider: ProviderName, model: string, apiKey: string, baseUrl?: string, contextLength = 50): void {
|
|
150
|
+
const configDir = join(homedir(), '.thatgfsj');
|
|
151
|
+
const configPath = join(configDir, 'config.json');
|
|
152
|
+
|
|
153
|
+
if (!existsSync(configDir)) {
|
|
154
|
+
mkdirSync(configDir, { recursive: true });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const config: Record<string, any> = {
|
|
158
|
+
provider,
|
|
159
|
+
model,
|
|
160
|
+
apiKey,
|
|
161
|
+
temperature: 0.7,
|
|
162
|
+
maxTokens: 4096,
|
|
163
|
+
contextLength,
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
if (baseUrl) config.baseUrl = baseUrl;
|
|
167
|
+
|
|
168
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
169
|
+
|
|
170
|
+
console.log();
|
|
171
|
+
console.log(chalk.gray(' Provider: ') + chalk.white(provider));
|
|
172
|
+
console.log(chalk.gray(' Model: ') + chalk.white(model));
|
|
173
|
+
if (baseUrl) {
|
|
174
|
+
console.log(chalk.gray(' URL: ') + chalk.white(baseUrl));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,42 +1,104 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Global shared types for Thatgfsj Code
|
|
3
|
-
* Only the most fundamental types used across modules
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Global shared types for Thatgfsj Code
|
|
3
|
+
* Only the most fundamental types used across modules
|
|
4
|
+
*
|
|
5
|
+
* v3.0.0+: Extended for Reasonix-style prompt caching:
|
|
6
|
+
* - ChatMessage.content now accepts ContentBlock[] (Anthropic-style)
|
|
7
|
+
* - cache_control field for explicit Anthropic cache breakpoints
|
|
8
|
+
* - usage extended with cache_creation_input_tokens / cache_read_input_tokens
|
|
9
|
+
* (Anthropic) and prompt_cache_hit_tokens / prompt_cache_miss_tokens (DeepSeek)
|
|
10
|
+
* - StreamChunk now has 'thinking' / 'usage' variants for structured streaming
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export type Role = 'system' | 'user' | 'assistant' | 'tool';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Anthropic-style cache control. ttl: '5m' (default) | '1h'
|
|
17
|
+
* - '5m': cheaper write cost, refreshed on each hit, expires 5 min after last hit
|
|
18
|
+
* - '1h': more expensive write cost, expires 1 hour after last hit
|
|
19
|
+
*/
|
|
20
|
+
export interface CacheControl {
|
|
21
|
+
type: 'ephemeral';
|
|
22
|
+
ttl?: '5m' | '1h';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Anthropic-style content blocks. text carries optional cache_control so that
|
|
27
|
+
* Anthropic prompt caching breakpoints can be placed inside messages.
|
|
28
|
+
*/
|
|
29
|
+
export type ContentBlock =
|
|
30
|
+
| { type: 'text'; text: string; cache_control?: CacheControl }
|
|
31
|
+
| { type: 'image'; source: { type: 'base64'; media_type: string; data: string } };
|
|
32
|
+
|
|
33
|
+
export interface ChatMessage {
|
|
34
|
+
role: Role;
|
|
35
|
+
/**
|
|
36
|
+
* For 'system' / 'user' / 'assistant' (text only): a plain string.
|
|
37
|
+
* For 'user' (multimodal) or 'assistant' (Anthropic tool_use mixed with text):
|
|
38
|
+
* an array of ContentBlock.
|
|
39
|
+
* 'tool' messages must use string content (carries tool result text).
|
|
40
|
+
*/
|
|
41
|
+
content: string | ContentBlock[];
|
|
42
|
+
name?: string;
|
|
43
|
+
tool_calls?: ToolCall[];
|
|
44
|
+
tool_call_id?: string;
|
|
45
|
+
reasoning_content?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Anthropic prompt caching marker attached to the message itself.
|
|
48
|
+
* Only honored when the underlying provider supports it (anthropic, anthropic-relay).
|
|
49
|
+
* OpenAI / Gemini / DeepSeek ignore it (DeepSeek uses automatic prefix cache).
|
|
50
|
+
*/
|
|
51
|
+
cache_control?: CacheControl;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface ToolCall {
|
|
55
|
+
id: string;
|
|
56
|
+
type: 'function';
|
|
57
|
+
function: {
|
|
58
|
+
name: string;
|
|
59
|
+
arguments: string;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface Usage {
|
|
64
|
+
prompt_tokens: number;
|
|
65
|
+
completion_tokens: number;
|
|
66
|
+
total_tokens: number;
|
|
67
|
+
// Anthropic prompt cache fields
|
|
68
|
+
cache_creation_input_tokens?: number;
|
|
69
|
+
cache_read_input_tokens?: number;
|
|
70
|
+
// DeepSeek prompt cache fields
|
|
71
|
+
prompt_cache_hit_tokens?: number;
|
|
72
|
+
prompt_cache_miss_tokens?: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface ChatResponse {
|
|
76
|
+
content: string;
|
|
77
|
+
role: 'assistant';
|
|
78
|
+
usage?: Usage;
|
|
79
|
+
tool_calls?: ToolCall[];
|
|
80
|
+
reasoning_content?: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface ChatOptions {
|
|
84
|
+
temperature?: number;
|
|
85
|
+
maxTokens?: number;
|
|
86
|
+
stream?: boolean;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Structured streaming chunk. Replaces the legacy @@TOOL@@ sentinel-string
|
|
91
|
+
* protocol that the previous version of useChat parsed by string-splitting.
|
|
92
|
+
*
|
|
93
|
+
* - 'text': regular model output
|
|
94
|
+
* - 'tool_calls': one or more tool calls ready to dispatch
|
|
95
|
+
* - 'thinking': reasoning content (stripped from final persistence; kept in
|
|
96
|
+
* VolatileScratch for the current round only)
|
|
97
|
+
* - 'usage': token usage + cache stats; emitted at end of stream if
|
|
98
|
+
* the upstream provider returned them
|
|
99
|
+
*/
|
|
100
|
+
export type StreamChunk =
|
|
101
|
+
| { type: 'text'; content: string }
|
|
102
|
+
| { type: 'tool_calls'; toolCalls: ToolCall[] }
|
|
103
|
+
| { type: 'thinking'; content: string }
|
|
104
|
+
| { type: 'usage'; usage: Usage };
|
package/src/utils/diff.ts
CHANGED
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Diff Preview - Show file changes in a readable format
|
|
3
|
-
* Migrated from old src/utils/diff-preview.ts
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export interface DiffResult {
|
|
7
|
-
original: string;
|
|
8
|
-
modified: string;
|
|
9
|
-
diff: string;
|
|
10
|
-
added: number;
|
|
11
|
-
removed: number;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export class DiffPreview {
|
|
15
|
-
/**
|
|
16
|
-
* Generate a simple line-by-line diff
|
|
17
|
-
*/
|
|
18
|
-
static diff(original: string, modified: string): DiffResult {
|
|
19
|
-
const originalLines = original.split('\n');
|
|
20
|
-
const modifiedLines = modified.split('\n');
|
|
21
|
-
const diffLines: string[] = [];
|
|
22
|
-
let added = 0;
|
|
23
|
-
let removed = 0;
|
|
24
|
-
|
|
25
|
-
// Simple diff: show removed (-) and added (+) lines
|
|
26
|
-
const maxLen = Math.max(originalLines.length, modifiedLines.length);
|
|
27
|
-
|
|
28
|
-
for (let i = 0; i < maxLen; i++) {
|
|
29
|
-
const orig = originalLines[i];
|
|
30
|
-
const mod = modifiedLines[i];
|
|
31
|
-
|
|
32
|
-
if (orig === mod) {
|
|
33
|
-
diffLines.push(` ${orig}`);
|
|
34
|
-
} else {
|
|
35
|
-
if (orig !== undefined) {
|
|
36
|
-
diffLines.push(`- ${orig}`);
|
|
37
|
-
removed++;
|
|
38
|
-
}
|
|
39
|
-
if (mod !== undefined) {
|
|
40
|
-
diffLines.push(`+ ${mod}`);
|
|
41
|
-
added++;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return {
|
|
47
|
-
original,
|
|
48
|
-
modified,
|
|
49
|
-
diff: diffLines.join('\n'),
|
|
50
|
-
added,
|
|
51
|
-
removed,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Format diff for display
|
|
57
|
-
*/
|
|
58
|
-
static format(result: DiffResult): string {
|
|
59
|
-
const lines = result.diff.split('\n').map(line => {
|
|
60
|
-
if (line.startsWith('+')) return `\x1b[32m${line}\x1b[0m`;
|
|
61
|
-
if (line.startsWith('-')) return `\x1b[31m${line}\x1b[0m`;
|
|
62
|
-
return line;
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
return [
|
|
66
|
-
`\x1b[36m--- Changes: -${result.removed} / +${result.added} ---\x1b[0m`,
|
|
67
|
-
...lines,
|
|
68
|
-
`\x1b[36m--- End ---\x1b[0m`,
|
|
69
|
-
].join('\n');
|
|
70
|
-
}
|
|
71
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Diff Preview - Show file changes in a readable format
|
|
3
|
+
* Migrated from old src/utils/diff-preview.ts
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface DiffResult {
|
|
7
|
+
original: string;
|
|
8
|
+
modified: string;
|
|
9
|
+
diff: string;
|
|
10
|
+
added: number;
|
|
11
|
+
removed: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class DiffPreview {
|
|
15
|
+
/**
|
|
16
|
+
* Generate a simple line-by-line diff
|
|
17
|
+
*/
|
|
18
|
+
static diff(original: string, modified: string): DiffResult {
|
|
19
|
+
const originalLines = original.split('\n');
|
|
20
|
+
const modifiedLines = modified.split('\n');
|
|
21
|
+
const diffLines: string[] = [];
|
|
22
|
+
let added = 0;
|
|
23
|
+
let removed = 0;
|
|
24
|
+
|
|
25
|
+
// Simple diff: show removed (-) and added (+) lines
|
|
26
|
+
const maxLen = Math.max(originalLines.length, modifiedLines.length);
|
|
27
|
+
|
|
28
|
+
for (let i = 0; i < maxLen; i++) {
|
|
29
|
+
const orig = originalLines[i];
|
|
30
|
+
const mod = modifiedLines[i];
|
|
31
|
+
|
|
32
|
+
if (orig === mod) {
|
|
33
|
+
diffLines.push(` ${orig}`);
|
|
34
|
+
} else {
|
|
35
|
+
if (orig !== undefined) {
|
|
36
|
+
diffLines.push(`- ${orig}`);
|
|
37
|
+
removed++;
|
|
38
|
+
}
|
|
39
|
+
if (mod !== undefined) {
|
|
40
|
+
diffLines.push(`+ ${mod}`);
|
|
41
|
+
added++;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
original,
|
|
48
|
+
modified,
|
|
49
|
+
diff: diffLines.join('\n'),
|
|
50
|
+
added,
|
|
51
|
+
removed,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Format diff for display
|
|
57
|
+
*/
|
|
58
|
+
static format(result: DiffResult): string {
|
|
59
|
+
const lines = result.diff.split('\n').map(line => {
|
|
60
|
+
if (line.startsWith('+')) return `\x1b[32m${line}\x1b[0m`;
|
|
61
|
+
if (line.startsWith('-')) return `\x1b[31m${line}\x1b[0m`;
|
|
62
|
+
return line;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return [
|
|
66
|
+
`\x1b[36m--- Changes: -${result.removed} / +${result.added} ---\x1b[0m`,
|
|
67
|
+
...lines,
|
|
68
|
+
`\x1b[36m--- End ---\x1b[0m`,
|
|
69
|
+
].join('\n');
|
|
70
|
+
}
|
|
71
|
+
}
|