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/dist/tui/welcome.js
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
|
import chalk from 'chalk';
|
|
6
5
|
import readline from 'readline';
|
|
@@ -8,103 +7,108 @@ import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
|
8
7
|
import { join } from 'path';
|
|
9
8
|
import { homedir } from 'os';
|
|
10
9
|
import { getModelsForProvider, listProviders, isCustomProvider } from '../config/providers.js';
|
|
10
|
+
const line = chalk.gray('─'.repeat(52));
|
|
11
11
|
export class WelcomeScreen {
|
|
12
|
-
/**
|
|
13
|
-
* Show welcome if no API key is configured
|
|
14
|
-
*/
|
|
15
12
|
static show(hasApiKey) {
|
|
16
13
|
if (hasApiKey)
|
|
17
14
|
return;
|
|
18
|
-
|
|
19
|
-
console.log(chalk.cyan
|
|
20
|
-
console.log(chalk.
|
|
21
|
-
console.log(
|
|
22
|
-
console.log(
|
|
23
|
-
console.log(chalk.
|
|
24
|
-
console.log(
|
|
15
|
+
console.log();
|
|
16
|
+
console.log(chalk.cyan.bold(' ⚡ Thatgfsj Code') + chalk.gray(' v0.5.0'));
|
|
17
|
+
console.log(chalk.gray(' AI Coding Assistant'));
|
|
18
|
+
console.log(line);
|
|
19
|
+
console.log();
|
|
20
|
+
console.log(chalk.yellow(' ⚠ No API key configured'));
|
|
21
|
+
console.log();
|
|
22
|
+
console.log(chalk.gray(' Run ') + chalk.cyan.bold('gfcode init') + chalk.gray(' to set up your provider.'));
|
|
23
|
+
console.log();
|
|
24
|
+
console.log(chalk.gray(' Supported providers:'));
|
|
25
|
+
console.log(chalk.gray(' OpenAI · Claude · DeepSeek · Kimi · GLM · Gemini · 中转站'));
|
|
26
|
+
console.log(line);
|
|
25
27
|
console.log();
|
|
26
28
|
}
|
|
27
|
-
/**
|
|
28
|
-
* Interactive setup wizard
|
|
29
|
-
*/
|
|
30
29
|
static async interactiveSetup() {
|
|
31
30
|
console.clear();
|
|
32
|
-
console.log(
|
|
31
|
+
console.log();
|
|
32
|
+
console.log(chalk.cyan.bold(' ⚡ Thatgfsj Code') + chalk.gray(' - Setup'));
|
|
33
|
+
console.log(line);
|
|
34
|
+
console.log();
|
|
33
35
|
const rl = readline.createInterface({
|
|
34
36
|
input: process.stdin,
|
|
35
37
|
output: process.stdout,
|
|
36
38
|
});
|
|
37
|
-
const
|
|
39
|
+
const ask = (prompt) => new Promise(resolve => rl.question(prompt, answer => resolve(answer.trim())));
|
|
38
40
|
try {
|
|
39
|
-
// Step 1:
|
|
40
|
-
console.log(chalk.
|
|
41
|
+
// ── Step 1: Provider ────────────────────────────────
|
|
42
|
+
console.log(chalk.bold(' 1. Choose Provider'));
|
|
43
|
+
console.log();
|
|
41
44
|
const providers = listProviders();
|
|
42
45
|
providers.forEach((p, i) => {
|
|
43
|
-
const num = (i + 1).toString().padStart(2);
|
|
44
|
-
console.log(
|
|
46
|
+
const num = chalk.cyan((i + 1).toString().padStart(2));
|
|
47
|
+
console.log(` ${num} ${p.name}`);
|
|
45
48
|
});
|
|
46
49
|
console.log();
|
|
47
|
-
const choice = await
|
|
50
|
+
const choice = await ask(chalk.cyan(' ❯ '));
|
|
48
51
|
const selected = providers[parseInt(choice) - 1] || providers[0];
|
|
49
52
|
const providerName = selected.key;
|
|
50
|
-
// Step 2:
|
|
51
|
-
console.log(
|
|
53
|
+
// ── Step 2: API Key ─────────────────────────────────
|
|
54
|
+
console.log();
|
|
55
|
+
console.log(chalk.bold(' 2. API Key'));
|
|
56
|
+
console.log();
|
|
52
57
|
let baseUrl;
|
|
53
58
|
if (isCustomProvider(providerName)) {
|
|
54
|
-
|
|
55
|
-
console.log(chalk.
|
|
56
|
-
console.log(chalk.gray(' 支持 OpenAI 兼容 或 Anthropic 兼容 的中转站'));
|
|
59
|
+
console.log(chalk.yellow(' Custom Provider (Relay Station)'));
|
|
60
|
+
console.log(chalk.gray(' OpenAI-compatible or Anthropic-compatible'));
|
|
57
61
|
console.log();
|
|
58
|
-
baseUrl = await
|
|
62
|
+
baseUrl = await ask(chalk.cyan(' Base URL ❯ '));
|
|
59
63
|
if (!baseUrl) {
|
|
60
|
-
console.log(chalk.red('
|
|
64
|
+
console.log(chalk.red(' ❌ Base URL required'));
|
|
61
65
|
rl.close();
|
|
62
66
|
return;
|
|
63
67
|
}
|
|
64
|
-
// Remove trailing slash
|
|
65
68
|
baseUrl = baseUrl.replace(/\/+$/, '');
|
|
66
|
-
// Remove /v1 suffix if present (we'll add it in the provider)
|
|
67
|
-
// Actually keep it as-is since the user knows their endpoint
|
|
68
69
|
}
|
|
69
|
-
const apiKey = await
|
|
70
|
-
// Step 3:
|
|
71
|
-
console.log(
|
|
70
|
+
const apiKey = await ask(chalk.cyan(' API Key ❯ '));
|
|
71
|
+
// ── Step 3: Model ───────────────────────────────────
|
|
72
|
+
console.log();
|
|
73
|
+
console.log(chalk.bold(' 3. Choose Model'));
|
|
74
|
+
console.log();
|
|
72
75
|
if (isCustomProvider(providerName)) {
|
|
73
|
-
|
|
74
|
-
console.log(chalk.gray('
|
|
75
|
-
console.log(
|
|
76
|
-
const model = await
|
|
76
|
+
console.log(chalk.gray(' Enter model name for your relay station'));
|
|
77
|
+
console.log(chalk.gray(' e.g. gpt-4o-mini, claude-sonnet-4-20250514'));
|
|
78
|
+
console.log();
|
|
79
|
+
const model = await ask(chalk.cyan(' Model ❯ '));
|
|
77
80
|
if (!model) {
|
|
78
|
-
console.log(chalk.red('
|
|
81
|
+
console.log(chalk.red(' ❌ Model name required'));
|
|
79
82
|
rl.close();
|
|
80
83
|
return;
|
|
81
84
|
}
|
|
82
|
-
// Save config
|
|
83
85
|
this.saveConfig(providerName, model, apiKey, baseUrl);
|
|
84
86
|
}
|
|
85
87
|
else {
|
|
86
|
-
// Standard provider: show model list
|
|
87
88
|
const models = getModelsForProvider(providerName);
|
|
88
89
|
models.forEach((m, i) => {
|
|
89
|
-
const num = (i + 1).toString().padStart(2);
|
|
90
|
-
console.log(
|
|
90
|
+
const num = chalk.cyan((i + 1).toString().padStart(2));
|
|
91
|
+
console.log(` ${num} ${chalk.white(m.name)} ${chalk.gray(m.desc)}`);
|
|
91
92
|
});
|
|
92
93
|
console.log();
|
|
93
|
-
const modelChoice = await
|
|
94
|
+
const modelChoice = await ask(chalk.cyan(' ❯ '));
|
|
94
95
|
const selectedModel = models[parseInt(modelChoice) - 1] || models[0];
|
|
95
96
|
this.saveConfig(providerName, selectedModel.id, apiKey, baseUrl);
|
|
96
97
|
}
|
|
97
|
-
|
|
98
|
-
console.log(
|
|
99
|
-
console.log(
|
|
98
|
+
// ── Done ────────────────────────────────────────────
|
|
99
|
+
console.log();
|
|
100
|
+
console.log(line);
|
|
101
|
+
console.log(chalk.green.bold(' ✅ Configuration saved!'));
|
|
102
|
+
console.log();
|
|
103
|
+
console.log(chalk.gray(' Config: ') + chalk.white(join(homedir(), '.thatgfsj', 'config.json')));
|
|
104
|
+
console.log(chalk.gray(' Run ') + chalk.cyan.bold('gfcode') + chalk.gray(' to start.'));
|
|
105
|
+
console.log(line);
|
|
106
|
+
console.log();
|
|
100
107
|
}
|
|
101
108
|
finally {
|
|
102
109
|
rl.close();
|
|
103
110
|
}
|
|
104
111
|
}
|
|
105
|
-
/**
|
|
106
|
-
* Save config to file
|
|
107
|
-
*/
|
|
108
112
|
static saveConfig(provider, model, apiKey, baseUrl) {
|
|
109
113
|
const configDir = join(homedir(), '.thatgfsj');
|
|
110
114
|
const configPath = join(configDir, 'config.json');
|
|
@@ -118,14 +122,14 @@ export class WelcomeScreen {
|
|
|
118
122
|
temperature: 0.7,
|
|
119
123
|
maxTokens: 4096,
|
|
120
124
|
};
|
|
121
|
-
if (baseUrl)
|
|
125
|
+
if (baseUrl)
|
|
122
126
|
config.baseUrl = baseUrl;
|
|
123
|
-
}
|
|
124
127
|
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
125
|
-
console.log(
|
|
126
|
-
console.log(chalk.gray(
|
|
128
|
+
console.log();
|
|
129
|
+
console.log(chalk.gray(' Provider: ') + chalk.white(provider));
|
|
130
|
+
console.log(chalk.gray(' Model: ') + chalk.white(model));
|
|
127
131
|
if (baseUrl) {
|
|
128
|
-
console.log(chalk.gray(
|
|
132
|
+
console.log(chalk.gray(' URL: ') + chalk.white(baseUrl));
|
|
129
133
|
}
|
|
130
134
|
}
|
|
131
135
|
}
|
package/dist/tui/welcome.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"welcome.js","sourceRoot":"","sources":["../../src/tui/welcome.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"welcome.js","sourceRoot":"","sources":["../../src/tui/welcome.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/app/index.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* App - Core application singleton
|
|
3
|
-
*
|
|
4
|
-
* Replaces the scattered initialization in old src/index.ts
|
|
3
|
+
* Simplified: directly uses LLMService (which has built-in agent loop)
|
|
5
4
|
*/
|
|
6
5
|
|
|
7
6
|
import { ConfigManager } from '../config/index.js';
|
|
@@ -10,8 +9,7 @@ import { SessionManager } from '../session/index.js';
|
|
|
10
9
|
import { ToolRegistry } from '../tools/index.js';
|
|
11
10
|
import { HookManager } from '../hooks/index.js';
|
|
12
11
|
import { SystemPromptBuilder } from '../prompts/index.js';
|
|
13
|
-
import {
|
|
14
|
-
import type { ChatMessage } from '../types.js';
|
|
12
|
+
import type { ChatMessage, ChatResponse } from '../types.js';
|
|
15
13
|
|
|
16
14
|
export class App {
|
|
17
15
|
config: ConfigManager;
|
|
@@ -20,7 +18,6 @@ export class App {
|
|
|
20
18
|
tools: ToolRegistry;
|
|
21
19
|
hooks: HookManager;
|
|
22
20
|
prompts: SystemPromptBuilder;
|
|
23
|
-
private agent: Agent;
|
|
24
21
|
|
|
25
22
|
private constructor(
|
|
26
23
|
config: ConfigManager,
|
|
@@ -29,7 +26,6 @@ export class App {
|
|
|
29
26
|
tools: ToolRegistry,
|
|
30
27
|
hooks: HookManager,
|
|
31
28
|
prompts: SystemPromptBuilder,
|
|
32
|
-
agent: Agent,
|
|
33
29
|
) {
|
|
34
30
|
this.config = config;
|
|
35
31
|
this.llm = llm;
|
|
@@ -37,18 +33,12 @@ export class App {
|
|
|
37
33
|
this.tools = tools;
|
|
38
34
|
this.hooks = hooks;
|
|
39
35
|
this.prompts = prompts;
|
|
40
|
-
this.agent = agent;
|
|
41
36
|
}
|
|
42
37
|
|
|
43
|
-
/**
|
|
44
|
-
* Create and initialize the App
|
|
45
|
-
*/
|
|
46
38
|
static async create(): Promise<App> {
|
|
47
|
-
// Load config
|
|
48
39
|
const config = await ConfigManager.load();
|
|
49
40
|
const aiConfig = config.getAIConfig();
|
|
50
41
|
|
|
51
|
-
// Create services
|
|
52
42
|
const llm = LLMService.fromConfig(aiConfig);
|
|
53
43
|
const session = new SessionManager();
|
|
54
44
|
const tools = new ToolRegistry();
|
|
@@ -59,13 +49,24 @@ export class App {
|
|
|
59
49
|
permissionMode: 'ask',
|
|
60
50
|
});
|
|
61
51
|
|
|
62
|
-
//
|
|
63
|
-
|
|
52
|
+
// Register tools with LLM service
|
|
53
|
+
llm.registerTools(tools.list());
|
|
64
54
|
|
|
65
55
|
// Set up system prompt
|
|
66
56
|
session.addMessage('system', prompts.build());
|
|
67
57
|
|
|
68
|
-
return new App(config, llm, session, tools, hooks, prompts
|
|
58
|
+
return new App(config, llm, session, tools, hooks, prompts);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Stream a response for the current session messages.
|
|
63
|
+
* The LLMService handles the full agent loop internally.
|
|
64
|
+
*/
|
|
65
|
+
async *streamResponse(messages?: ChatMessage[]): AsyncGenerator<string, void> {
|
|
66
|
+
const msgs = messages || this.session.getMessages();
|
|
67
|
+
for await (const chunk of this.llm.chatStream(msgs)) {
|
|
68
|
+
yield chunk;
|
|
69
|
+
}
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
/**
|
|
@@ -75,27 +76,21 @@ export class App {
|
|
|
75
76
|
this.session.addMessage('user', prompt);
|
|
76
77
|
|
|
77
78
|
let fullResponse = '';
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
try {
|
|
80
|
+
for await (const chunk of this.streamResponse()) {
|
|
81
|
+
process.stdout.write(chunk);
|
|
82
|
+
fullResponse += chunk;
|
|
83
|
+
}
|
|
84
|
+
} catch (err) {
|
|
85
|
+
// Re-throw after saving partial response
|
|
86
|
+
if (fullResponse) {
|
|
87
|
+
this.session.addMessage('assistant', fullResponse);
|
|
88
|
+
}
|
|
89
|
+
throw err;
|
|
81
90
|
}
|
|
82
91
|
|
|
83
|
-
console.log();
|
|
92
|
+
console.log();
|
|
84
93
|
this.session.addMessage('assistant', fullResponse);
|
|
85
94
|
return fullResponse;
|
|
86
95
|
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Get the agent for streaming control
|
|
90
|
-
*/
|
|
91
|
-
getAgent(): Agent {
|
|
92
|
-
return this.agent;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Get current config
|
|
97
|
-
*/
|
|
98
|
-
getConfig(): ConfigManager {
|
|
99
|
-
return this.config;
|
|
100
|
-
}
|
|
101
96
|
}
|
package/src/cmd/index.ts
CHANGED
|
@@ -22,7 +22,6 @@ import chalk from 'chalk';
|
|
|
22
22
|
import { App } from '../app/index.js';
|
|
23
23
|
import { REPL } from '../tui/repl.js';
|
|
24
24
|
import { WelcomeScreen } from '../tui/welcome.js';
|
|
25
|
-
import { ProjectContext } from '../utils/project.js';
|
|
26
25
|
|
|
27
26
|
// ==================== Error Handling ====================
|
|
28
27
|
|
|
@@ -41,7 +40,7 @@ process.on('unhandledRejection', (reason) => {
|
|
|
41
40
|
program
|
|
42
41
|
.name('gfcode')
|
|
43
42
|
.description('🤖 Thatgfsj Code - AI Coding Assistant')
|
|
44
|
-
.version('0.
|
|
43
|
+
.version('0.5.0')
|
|
45
44
|
.argument('[prompt]', 'Task to execute (omit to start interactive mode)')
|
|
46
45
|
.option('-m, --model <model>', 'Specify model')
|
|
47
46
|
.option('-i, --interactive', 'Force interactive mode')
|
|
@@ -70,13 +69,59 @@ program
|
|
|
70
69
|
const repl = new REPL(app);
|
|
71
70
|
await repl.start();
|
|
72
71
|
} else {
|
|
73
|
-
// Single prompt mode
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
console.log(chalk.gray('─'.repeat(40)));
|
|
77
|
-
console.log(chalk.cyan('\n> ') + prompt + '\n');
|
|
72
|
+
// Single prompt mode - use same UI as REPL
|
|
73
|
+
const { REPLOutput } = await import('../tui/output.js');
|
|
74
|
+
const out = new REPLOutput();
|
|
78
75
|
|
|
79
|
-
|
|
76
|
+
out.printBanner();
|
|
77
|
+
out.printUserInput(prompt);
|
|
78
|
+
out.startThinking();
|
|
79
|
+
|
|
80
|
+
app.session.addMessage('user', prompt);
|
|
81
|
+
let fullResponse = '';
|
|
82
|
+
let hasStarted = false;
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const stream = app.streamResponse();
|
|
86
|
+
for await (const chunk of stream) {
|
|
87
|
+
out.stopThinking();
|
|
88
|
+
|
|
89
|
+
if (chunk.includes('@@TOOL@@')) {
|
|
90
|
+
const parts = chunk.split('\n');
|
|
91
|
+
for (const part of parts) {
|
|
92
|
+
if (part.startsWith('@@TOOL@@')) {
|
|
93
|
+
try {
|
|
94
|
+
const data = JSON.parse(part.slice(8));
|
|
95
|
+
if (data.action === 'call') {
|
|
96
|
+
if (hasStarted) { out.endAssistant(); hasStarted = false; }
|
|
97
|
+
out.printToolCall(data.name, data.args || '');
|
|
98
|
+
out.startExecuting(data.name);
|
|
99
|
+
} else if (data.action === 'result') {
|
|
100
|
+
out.stopThinking();
|
|
101
|
+
out.printToolResult(data.output || data.error || '', !!data.error);
|
|
102
|
+
out.printToolEnd();
|
|
103
|
+
}
|
|
104
|
+
} catch {}
|
|
105
|
+
} else if (part) {
|
|
106
|
+
if (!hasStarted) { out.beginAssistant(); hasStarted = true; }
|
|
107
|
+
out.writeChunk(part + '\n');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
if (!hasStarted) { out.beginAssistant(); hasStarted = true; }
|
|
112
|
+
out.writeChunk(chunk);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
fullResponse += chunk;
|
|
116
|
+
}
|
|
117
|
+
if (hasStarted) out.endAssistant();
|
|
118
|
+
} catch (error: any) {
|
|
119
|
+
if (hasStarted) out.endAssistant();
|
|
120
|
+
out.stopThinkingFail(error.message);
|
|
121
|
+
out.error(error.message);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
app.session.addMessage('assistant', fullResponse);
|
|
80
125
|
}
|
|
81
126
|
} catch (error: any) {
|
|
82
127
|
console.error(chalk.red(`\n❌ ${error.message}`));
|
package/src/llm/anthropic.ts
CHANGED
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
* - Tool results use role "tool_result" (not "tool")
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import type { ChatMessage, ChatResponse, ChatOptions } from '../types.js';
|
|
17
|
+
import type { ChatMessage, ChatResponse, ChatOptions, ToolCall } from '../types.js';
|
|
18
18
|
import type { Tool } from '../tools/types.js';
|
|
19
|
-
import type { LLMProvider, ProviderConfig } from './provider.js';
|
|
19
|
+
import type { LLMProvider, StreamChunk, ProviderConfig } from './provider.js';
|
|
20
20
|
|
|
21
21
|
export class AnthropicProvider implements LLMProvider {
|
|
22
22
|
readonly name = 'anthropic';
|
|
@@ -40,8 +40,8 @@ export class AnthropicProvider implements LLMProvider {
|
|
|
40
40
|
}));
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
async chat(messages: ChatMessage[], options?: ChatOptions): Promise<ChatResponse> {
|
|
44
|
-
const body = this.buildRequest(messages, false, options);
|
|
43
|
+
async chat(messages: ChatMessage[], options?: ChatOptions, tools?: Tool[]): Promise<ChatResponse> {
|
|
44
|
+
const body = this.buildRequest(messages, false, options, tools);
|
|
45
45
|
const response = await this.doRequest(body);
|
|
46
46
|
|
|
47
47
|
if (!response.ok) {
|
|
@@ -78,8 +78,8 @@ export class AnthropicProvider implements LLMProvider {
|
|
|
78
78
|
};
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
async *chatStream(messages: ChatMessage[], options?: ChatOptions): AsyncGenerator<
|
|
82
|
-
const body = this.buildRequest(messages, true, options);
|
|
81
|
+
async *chatStream(messages: ChatMessage[], options?: ChatOptions, tools?: Tool[]): AsyncGenerator<StreamChunk, ChatResponse> {
|
|
82
|
+
const body = this.buildRequest(messages, true, options, tools);
|
|
83
83
|
const response = await this.doRequest(body);
|
|
84
84
|
|
|
85
85
|
if (!response.ok || !response.body) {
|
|
@@ -91,6 +91,10 @@ export class AnthropicProvider implements LLMProvider {
|
|
|
91
91
|
const decoder = new TextDecoder();
|
|
92
92
|
let fullContent = '';
|
|
93
93
|
let buffer = '';
|
|
94
|
+
// Track tool use blocks
|
|
95
|
+
const toolUseBlocks: Map<number, { id: string; name: string; input: string }> = new Map();
|
|
96
|
+
let currentBlockIndex = -1;
|
|
97
|
+
let currentBlockType = '';
|
|
94
98
|
|
|
95
99
|
try {
|
|
96
100
|
while (true) {
|
|
@@ -108,11 +112,31 @@ export class AnthropicProvider implements LLMProvider {
|
|
|
108
112
|
try {
|
|
109
113
|
const data = JSON.parse(trimmed.slice(6));
|
|
110
114
|
|
|
111
|
-
//
|
|
112
|
-
if (data.type === '
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
115
|
+
// content_block_start: track block types
|
|
116
|
+
if (data.type === 'content_block_start') {
|
|
117
|
+
currentBlockIndex = data.index ?? 0;
|
|
118
|
+
currentBlockType = data.content_block?.type || '';
|
|
119
|
+
if (currentBlockType === 'tool_use') {
|
|
120
|
+
toolUseBlocks.set(currentBlockIndex, {
|
|
121
|
+
id: data.content_block.id || '',
|
|
122
|
+
name: data.content_block.name || '',
|
|
123
|
+
input: '',
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// content_block_delta: incremental text or tool input
|
|
129
|
+
if (data.type === 'content_block_delta') {
|
|
130
|
+
if (data.delta?.type === 'text_delta') {
|
|
131
|
+
const text = data.delta.text;
|
|
132
|
+
fullContent += text;
|
|
133
|
+
yield { type: 'text', content: text };
|
|
134
|
+
} else if (data.delta?.type === 'input_json_delta') {
|
|
135
|
+
const buf = toolUseBlocks.get(currentBlockIndex);
|
|
136
|
+
if (buf) {
|
|
137
|
+
buf.input += data.delta.partial_json || '';
|
|
138
|
+
}
|
|
139
|
+
}
|
|
116
140
|
}
|
|
117
141
|
} catch {
|
|
118
142
|
// Skip invalid JSON
|
|
@@ -123,13 +147,29 @@ export class AnthropicProvider implements LLMProvider {
|
|
|
123
147
|
reader.releaseLock();
|
|
124
148
|
}
|
|
125
149
|
|
|
126
|
-
|
|
150
|
+
// Convert tool use blocks to ToolCall[]
|
|
151
|
+
const toolCalls: ToolCall[] = [];
|
|
152
|
+
for (const [, buf] of toolUseBlocks) {
|
|
153
|
+
if (buf.id && buf.name) {
|
|
154
|
+
toolCalls.push({
|
|
155
|
+
id: buf.id,
|
|
156
|
+
type: 'function',
|
|
157
|
+
function: { name: buf.name, arguments: buf.input },
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (toolCalls.length > 0) {
|
|
163
|
+
yield { type: 'tool_calls', toolCalls };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return { content: fullContent, role: 'assistant', tool_calls: toolCalls.length > 0 ? toolCalls : undefined };
|
|
127
167
|
}
|
|
128
168
|
|
|
129
169
|
/**
|
|
130
170
|
* Build request body for Anthropic API
|
|
131
171
|
*/
|
|
132
|
-
protected buildRequest(messages: ChatMessage[], stream: boolean, options?: ChatOptions) {
|
|
172
|
+
protected buildRequest(messages: ChatMessage[], stream: boolean, options?: ChatOptions, tools?: Tool[]) {
|
|
133
173
|
// Anthropic uses separate system message
|
|
134
174
|
const systemMsg = messages.find(m => m.role === 'system');
|
|
135
175
|
const nonSystemMsgs = messages.filter(m => m.role !== 'system');
|
|
@@ -169,7 +209,7 @@ export class AnthropicProvider implements LLMProvider {
|
|
|
169
209
|
};
|
|
170
210
|
});
|
|
171
211
|
|
|
172
|
-
|
|
212
|
+
const body: any = {
|
|
173
213
|
model: this.config.model,
|
|
174
214
|
max_tokens: options?.maxTokens ?? this.config.maxTokens ?? 4096,
|
|
175
215
|
temperature: options?.temperature ?? this.config.temperature,
|
|
@@ -177,6 +217,12 @@ export class AnthropicProvider implements LLMProvider {
|
|
|
177
217
|
messages: anthropicMessages,
|
|
178
218
|
stream,
|
|
179
219
|
};
|
|
220
|
+
|
|
221
|
+
if (tools && tools.length > 0) {
|
|
222
|
+
body.tools = this.buildTools(tools);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return body;
|
|
180
226
|
}
|
|
181
227
|
|
|
182
228
|
/**
|