thatgfsj-code 0.4.0 → 0.5.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/dist/app/index.d.ts +6 -14
- package/dist/app/index.d.ts.map +1 -1
- package/dist/app/index.js +28 -30
- package/dist/app/index.js.map +1 -1
- package/dist/cmd/index.js +64 -8
- package/dist/cmd/index.js.map +1 -1
- package/dist/llm/anthropic.d.ts +4 -17
- package/dist/llm/anthropic.d.ts.map +1 -1
- package/dist/llm/anthropic.js +54 -12
- package/dist/llm/anthropic.js.map +1 -1
- package/dist/llm/gemini.d.ts +3 -16
- package/dist/llm/gemini.d.ts.map +1 -1
- package/dist/llm/gemini.js +26 -22
- package/dist/llm/gemini.js.map +1 -1
- package/dist/llm/index.d.ts +5 -19
- package/dist/llm/index.d.ts.map +1 -1
- package/dist/llm/index.js +86 -87
- package/dist/llm/index.js.map +1 -1
- package/dist/llm/openai.d.ts +9 -23
- package/dist/llm/openai.d.ts.map +1 -1
- package/dist/llm/openai.js +48 -14
- package/dist/llm/openai.js.map +1 -1
- package/dist/llm/provider.d.ts +11 -16
- package/dist/llm/provider.d.ts.map +1 -1
- package/dist/tui/input.d.ts +0 -1
- package/dist/tui/input.d.ts.map +1 -1
- package/dist/tui/input.js +2 -5
- package/dist/tui/input.js.map +1 -1
- package/dist/tui/output.d.ts +36 -12
- package/dist/tui/output.d.ts.map +1 -1
- package/dist/tui/output.js +172 -40
- package/dist/tui/output.js.map +1 -1
- package/dist/tui/repl.d.ts +0 -10
- package/dist/tui/repl.d.ts.map +1 -1
- package/dist/tui/repl.js +91 -43
- package/dist/tui/repl.js.map +1 -1
- package/dist/tui/welcome.d.ts +1 -11
- package/dist/tui/welcome.d.ts.map +1 -1
- package/dist/tui/welcome.js +60 -56
- package/dist/tui/welcome.js.map +1 -1
- package/package.json +1 -1
- package/src/app/index.ts +28 -33
- package/src/cmd/index.ts +53 -8
- package/src/llm/anthropic.ts +60 -14
- package/src/llm/gemini.ts +31 -24
- package/src/llm/index.ts +93 -92
- package/src/llm/openai.ts +58 -15
- package/src/llm/provider.ts +12 -16
- package/src/tui/input.ts +2 -5
- package/src/tui/output.ts +192 -40
- package/src/tui/repl.ts +94 -44
- package/src/tui/welcome.ts +62 -59
- package/src/app/agent.ts +0 -140
package/src/tui/welcome.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Welcome / Setup Wizard
|
|
3
|
-
* Supports all providers + custom relay stations (中转站)
|
|
2
|
+
* Welcome / Setup Wizard - Clean UI
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
import chalk from 'chalk';
|
|
@@ -11,119 +10,124 @@ import { homedir } from 'os';
|
|
|
11
10
|
import { PROVIDERS, getModelsForProvider, listProviders, isCustomProvider } from '../config/providers.js';
|
|
12
11
|
import type { ProviderName } from '../config/types.js';
|
|
13
12
|
|
|
13
|
+
const line = chalk.gray('─'.repeat(52));
|
|
14
|
+
|
|
14
15
|
export class WelcomeScreen {
|
|
15
|
-
|
|
16
|
-
* Show welcome if no API key is configured
|
|
17
|
-
*/
|
|
16
|
+
|
|
18
17
|
static show(hasApiKey: boolean): void {
|
|
19
18
|
if (hasApiKey) return;
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
console.log(chalk.cyan
|
|
23
|
-
console.log(chalk.
|
|
24
|
-
console.log(
|
|
25
|
-
console.log(
|
|
26
|
-
console.log(chalk.
|
|
27
|
-
console.log(
|
|
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);
|
|
28
32
|
console.log();
|
|
29
33
|
}
|
|
30
34
|
|
|
31
|
-
/**
|
|
32
|
-
* Interactive setup wizard
|
|
33
|
-
*/
|
|
34
35
|
static async interactiveSetup(): Promise<void> {
|
|
35
36
|
console.clear();
|
|
36
|
-
console.log(
|
|
37
|
+
console.log();
|
|
38
|
+
console.log(chalk.cyan.bold(' ⚡ Thatgfsj Code') + chalk.gray(' - Setup'));
|
|
39
|
+
console.log(line);
|
|
40
|
+
console.log();
|
|
37
41
|
|
|
38
42
|
const rl = readline.createInterface({
|
|
39
43
|
input: process.stdin,
|
|
40
44
|
output: process.stdout,
|
|
41
45
|
});
|
|
42
46
|
|
|
43
|
-
const
|
|
47
|
+
const ask = (prompt: string): Promise<string> =>
|
|
44
48
|
new Promise(resolve => rl.question(prompt, answer => resolve(answer.trim())));
|
|
45
49
|
|
|
46
50
|
try {
|
|
47
|
-
// Step 1:
|
|
48
|
-
console.log(chalk.
|
|
51
|
+
// ── Step 1: Provider ────────────────────────────────
|
|
52
|
+
console.log(chalk.bold(' 1. Choose Provider'));
|
|
53
|
+
console.log();
|
|
49
54
|
const providers = listProviders();
|
|
50
55
|
providers.forEach((p, i) => {
|
|
51
|
-
const num = (i + 1).toString().padStart(2);
|
|
52
|
-
console.log(
|
|
56
|
+
const num = chalk.cyan((i + 1).toString().padStart(2));
|
|
57
|
+
console.log(` ${num} ${p.name}`);
|
|
53
58
|
});
|
|
54
59
|
console.log();
|
|
55
60
|
|
|
56
|
-
const choice = await
|
|
61
|
+
const choice = await ask(chalk.cyan(' ❯ '));
|
|
57
62
|
const selected = providers[parseInt(choice) - 1] || providers[0];
|
|
58
63
|
const providerName = selected.key;
|
|
59
64
|
|
|
60
|
-
// Step 2:
|
|
61
|
-
console.log(
|
|
65
|
+
// ── Step 2: API Key ─────────────────────────────────
|
|
66
|
+
console.log();
|
|
67
|
+
console.log(chalk.bold(' 2. API Key'));
|
|
68
|
+
console.log();
|
|
62
69
|
|
|
63
70
|
let baseUrl: string | undefined;
|
|
64
71
|
|
|
65
72
|
if (isCustomProvider(providerName)) {
|
|
66
|
-
|
|
67
|
-
console.log(chalk.
|
|
68
|
-
console.log(chalk.gray(' 支持 OpenAI 兼容 或 Anthropic 兼容 的中转站'));
|
|
73
|
+
console.log(chalk.yellow(' Custom Provider (Relay Station)'));
|
|
74
|
+
console.log(chalk.gray(' OpenAI-compatible or Anthropic-compatible'));
|
|
69
75
|
console.log();
|
|
70
|
-
baseUrl = await
|
|
76
|
+
baseUrl = await ask(chalk.cyan(' Base URL ❯ '));
|
|
71
77
|
if (!baseUrl) {
|
|
72
|
-
console.log(chalk.red('
|
|
78
|
+
console.log(chalk.red(' ❌ Base URL required'));
|
|
73
79
|
rl.close();
|
|
74
80
|
return;
|
|
75
81
|
}
|
|
76
|
-
// Remove trailing slash
|
|
77
82
|
baseUrl = baseUrl.replace(/\/+$/, '');
|
|
78
|
-
// Remove /v1 suffix if present (we'll add it in the provider)
|
|
79
|
-
// Actually keep it as-is since the user knows their endpoint
|
|
80
83
|
}
|
|
81
84
|
|
|
82
|
-
const apiKey = await
|
|
85
|
+
const apiKey = await ask(chalk.cyan(' API Key ❯ '));
|
|
83
86
|
|
|
84
|
-
// Step 3:
|
|
85
|
-
console.log(
|
|
87
|
+
// ── Step 3: Model ───────────────────────────────────
|
|
88
|
+
console.log();
|
|
89
|
+
console.log(chalk.bold(' 3. Choose Model'));
|
|
90
|
+
console.log();
|
|
86
91
|
|
|
87
92
|
if (isCustomProvider(providerName)) {
|
|
88
|
-
|
|
89
|
-
console.log(chalk.gray('
|
|
90
|
-
console.log(
|
|
91
|
-
const model = await
|
|
93
|
+
console.log(chalk.gray(' Enter model name for your relay station'));
|
|
94
|
+
console.log(chalk.gray(' e.g. gpt-4o-mini, claude-sonnet-4-20250514'));
|
|
95
|
+
console.log();
|
|
96
|
+
const model = await ask(chalk.cyan(' Model ❯ '));
|
|
92
97
|
if (!model) {
|
|
93
|
-
console.log(chalk.red('
|
|
98
|
+
console.log(chalk.red(' ❌ Model name required'));
|
|
94
99
|
rl.close();
|
|
95
100
|
return;
|
|
96
101
|
}
|
|
97
|
-
|
|
98
|
-
// Save config
|
|
99
102
|
this.saveConfig(providerName, model, apiKey, baseUrl);
|
|
100
103
|
} else {
|
|
101
|
-
// Standard provider: show model list
|
|
102
104
|
const models = getModelsForProvider(providerName);
|
|
103
105
|
models.forEach((m, i) => {
|
|
104
|
-
const num = (i + 1).toString().padStart(2);
|
|
105
|
-
console.log(
|
|
106
|
+
const num = chalk.cyan((i + 1).toString().padStart(2));
|
|
107
|
+
console.log(` ${num} ${chalk.white(m.name)} ${chalk.gray(m.desc)}`);
|
|
106
108
|
});
|
|
107
109
|
console.log();
|
|
108
110
|
|
|
109
|
-
const modelChoice = await
|
|
111
|
+
const modelChoice = await ask(chalk.cyan(' ❯ '));
|
|
110
112
|
const selectedModel = models[parseInt(modelChoice) - 1] || models[0];
|
|
111
|
-
|
|
112
113
|
this.saveConfig(providerName, selectedModel.id, apiKey, baseUrl);
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
|
|
116
|
-
console.log(
|
|
117
|
-
console.log(
|
|
116
|
+
// ── Done ────────────────────────────────────────────
|
|
117
|
+
console.log();
|
|
118
|
+
console.log(line);
|
|
119
|
+
console.log(chalk.green.bold(' ✅ Configuration saved!'));
|
|
120
|
+
console.log();
|
|
121
|
+
console.log(chalk.gray(' Config: ') + chalk.white(join(homedir(), '.thatgfsj', 'config.json')));
|
|
122
|
+
console.log(chalk.gray(' Run ') + chalk.cyan.bold('gfcode') + chalk.gray(' to start.'));
|
|
123
|
+
console.log(line);
|
|
124
|
+
console.log();
|
|
118
125
|
|
|
119
126
|
} finally {
|
|
120
127
|
rl.close();
|
|
121
128
|
}
|
|
122
129
|
}
|
|
123
130
|
|
|
124
|
-
/**
|
|
125
|
-
* Save config to file
|
|
126
|
-
*/
|
|
127
131
|
private static saveConfig(provider: ProviderName, model: string, apiKey: string, baseUrl?: string): void {
|
|
128
132
|
const configDir = join(homedir(), '.thatgfsj');
|
|
129
133
|
const configPath = join(configDir, 'config.json');
|
|
@@ -140,16 +144,15 @@ export class WelcomeScreen {
|
|
|
140
144
|
maxTokens: 4096,
|
|
141
145
|
};
|
|
142
146
|
|
|
143
|
-
if (baseUrl)
|
|
144
|
-
config.baseUrl = baseUrl;
|
|
145
|
-
}
|
|
147
|
+
if (baseUrl) config.baseUrl = baseUrl;
|
|
146
148
|
|
|
147
149
|
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
148
150
|
|
|
149
|
-
console.log(
|
|
150
|
-
console.log(chalk.gray(
|
|
151
|
+
console.log();
|
|
152
|
+
console.log(chalk.gray(' Provider: ') + chalk.white(provider));
|
|
153
|
+
console.log(chalk.gray(' Model: ') + chalk.white(model));
|
|
151
154
|
if (baseUrl) {
|
|
152
|
-
console.log(chalk.gray(
|
|
155
|
+
console.log(chalk.gray(' URL: ') + chalk.white(baseUrl));
|
|
153
156
|
}
|
|
154
157
|
}
|
|
155
158
|
}
|
package/src/app/agent.ts
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agent Loop - Core agent logic with streaming and tool execution
|
|
3
|
-
* Extracted from old src/core/ai-engine.ts
|
|
4
|
-
*
|
|
5
|
-
* The agent loop: while(true) { stream response → check tool calls → execute → repeat }
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import chalk from 'chalk';
|
|
9
|
-
import type { ChatMessage, ChatResponse } from '../types.js';
|
|
10
|
-
import type { Tool } from '../tools/types.js';
|
|
11
|
-
import type { HookManager } from '../hooks/index.js';
|
|
12
|
-
import { LLMService } from '../llm/index.js';
|
|
13
|
-
import { ToolRegistry } from '../tools/index.js';
|
|
14
|
-
|
|
15
|
-
export interface AgentConfig {
|
|
16
|
-
maxIterations?: number;
|
|
17
|
-
hooks?: HookManager;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export class Agent {
|
|
21
|
-
private llm: LLMService;
|
|
22
|
-
private registry: ToolRegistry;
|
|
23
|
-
private hooks?: HookManager;
|
|
24
|
-
private maxIterations: number;
|
|
25
|
-
|
|
26
|
-
constructor(llm: LLMService, registry: ToolRegistry, config: AgentConfig = {}) {
|
|
27
|
-
this.llm = llm;
|
|
28
|
-
this.registry = registry;
|
|
29
|
-
this.hooks = config.hooks;
|
|
30
|
-
this.maxIterations = config.maxIterations ?? 10;
|
|
31
|
-
|
|
32
|
-
// Register tools with LLM service
|
|
33
|
-
this.llm.registerTools(registry.list());
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Run agent loop with streaming output
|
|
38
|
-
* Returns the final response
|
|
39
|
-
*/
|
|
40
|
-
async *run(messages: ChatMessage[]): AsyncGenerator<string, ChatResponse> {
|
|
41
|
-
let currentMessages = [...messages];
|
|
42
|
-
let iterations = 0;
|
|
43
|
-
|
|
44
|
-
while (iterations < this.maxIterations) {
|
|
45
|
-
iterations++;
|
|
46
|
-
|
|
47
|
-
// Hook: beforeAgentLoop
|
|
48
|
-
if (this.hooks) {
|
|
49
|
-
await this.hooks.emit('beforeAgentLoop', { messages: currentMessages, iteration: iterations });
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Stream the response
|
|
53
|
-
let fullContent = '';
|
|
54
|
-
|
|
55
|
-
for await (const chunk of this.llm.chatStream(currentMessages, { maxIterations: 1 })) {
|
|
56
|
-
fullContent += chunk;
|
|
57
|
-
yield chunk;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Get the full response to check for tool calls
|
|
61
|
-
const response: ChatResponse = {
|
|
62
|
-
content: fullContent,
|
|
63
|
-
role: 'assistant',
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
// Check if the response contains tool calls
|
|
67
|
-
// For now, we rely on the LLM service to handle tool calls internally
|
|
68
|
-
// If the LLM service returned tool_calls, we need to process them
|
|
69
|
-
if (response.tool_calls && response.tool_calls.length > 0) {
|
|
70
|
-
currentMessages.push({
|
|
71
|
-
role: 'assistant',
|
|
72
|
-
content: response.content,
|
|
73
|
-
tool_calls: response.tool_calls,
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
for (const toolCall of response.tool_calls) {
|
|
77
|
-
// Hook: beforeToolCall
|
|
78
|
-
if (this.hooks) {
|
|
79
|
-
await this.hooks.emit('beforeToolCall', {
|
|
80
|
-
toolName: toolCall.function.name,
|
|
81
|
-
toolParams: JSON.parse(toolCall.function.arguments || '{}'),
|
|
82
|
-
toolCallId: toolCall.id,
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const result = await this.registry.execute(
|
|
87
|
-
toolCall.function.name,
|
|
88
|
-
JSON.parse(toolCall.function.arguments || '{}')
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
// Hook: afterToolCall
|
|
92
|
-
if (this.hooks) {
|
|
93
|
-
await this.hooks.emit('afterToolCall', {
|
|
94
|
-
toolName: toolCall.function.name,
|
|
95
|
-
toolParams: JSON.parse(toolCall.function.arguments || '{}'),
|
|
96
|
-
toolResult: result,
|
|
97
|
-
toolCallId: toolCall.id,
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
currentMessages.push({
|
|
102
|
-
role: 'tool',
|
|
103
|
-
content: result.success ? (result.output || JSON.stringify(result.data)) : (result.error || 'Tool failed'),
|
|
104
|
-
tool_call_id: toolCall.id,
|
|
105
|
-
name: toolCall.function.name,
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
yield `\n${chalk.gray(`[tool: ${toolCall.function.name}]`)}`;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
continue; // Next iteration
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// No tool calls - done
|
|
115
|
-
if (response.content) {
|
|
116
|
-
currentMessages.push({ role: 'assistant', content: response.content });
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Hook: afterAgentLoop
|
|
120
|
-
if (this.hooks) {
|
|
121
|
-
await this.hooks.emit('afterAgentLoop', { messages: currentMessages, iteration: iterations });
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return response;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return { content: '[Agent loop exceeded maximum iterations]', role: 'assistant' };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Non-streaming chat (convenience method)
|
|
132
|
-
*/
|
|
133
|
-
async chat(messages: ChatMessage[]): Promise<ChatResponse> {
|
|
134
|
-
let fullContent = '';
|
|
135
|
-
for await (const chunk of this.run(messages)) {
|
|
136
|
-
fullContent += chunk;
|
|
137
|
-
}
|
|
138
|
-
return { content: fullContent, role: 'assistant' };
|
|
139
|
-
}
|
|
140
|
-
}
|