thatgfsj-code 0.4.0 → 0.4.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/dist/cmd/index.js +56 -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 +87 -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 +27 -12
- package/dist/tui/output.d.ts.map +1 -1
- package/dist/tui/output.js +176 -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 +76 -42
- 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/cmd/index.ts +49 -8
- package/src/llm/anthropic.ts +60 -14
- package/src/llm/gemini.ts +31 -24
- package/src/llm/index.ts +94 -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 +193 -40
- package/src/tui/repl.ts +82 -43
- package/src/tui/welcome.ts +62 -59
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.4.1'));
|
|
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/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.4.
|
|
43
|
+
.version('0.4.1')
|
|
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,55 @@ 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.getAgent().run(app.session.getMessages());
|
|
86
|
+
for await (const chunk of stream) {
|
|
87
|
+
out.stopThinking();
|
|
88
|
+
|
|
89
|
+
const parts = chunk.split('\n');
|
|
90
|
+
for (const part of parts) {
|
|
91
|
+
if (part.startsWith('@@TOOL@@')) {
|
|
92
|
+
try {
|
|
93
|
+
const data = JSON.parse(part.slice(8));
|
|
94
|
+
if (data.action === 'call') {
|
|
95
|
+
if (hasStarted) { out.endAssistant(); hasStarted = false; }
|
|
96
|
+
out.printToolCall(data.name, data.args || '');
|
|
97
|
+
out.startExecuting(data.name);
|
|
98
|
+
} else if (data.action === 'result') {
|
|
99
|
+
out.stopThinking();
|
|
100
|
+
out.printToolResult(data.output || data.error || '', !!data.error);
|
|
101
|
+
out.printToolEnd();
|
|
102
|
+
}
|
|
103
|
+
} catch {}
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (part) {
|
|
107
|
+
if (!hasStarted) { out.beginAssistant(); hasStarted = true; }
|
|
108
|
+
out.writeChunk(part + '\n');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
fullResponse += chunk;
|
|
112
|
+
}
|
|
113
|
+
if (hasStarted) out.endAssistant();
|
|
114
|
+
} catch (error: any) {
|
|
115
|
+
if (hasStarted) out.endAssistant();
|
|
116
|
+
out.stopThinkingFail(error.message);
|
|
117
|
+
out.error(error.message);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
app.session.addMessage('assistant', fullResponse);
|
|
80
121
|
}
|
|
81
122
|
} catch (error: any) {
|
|
82
123
|
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
|
/**
|
package/src/llm/gemini.ts
CHANGED
|
@@ -1,20 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Google Gemini Provider
|
|
3
|
-
*
|
|
4
|
-
* API: POST {baseUrl}/models/{model}:streamGenerateContent?key={apiKey}
|
|
5
|
-
* Auth: API key in URL query parameter
|
|
6
|
-
* Streaming: JSON array chunks (not SSE)
|
|
7
|
-
*
|
|
8
|
-
* Key differences:
|
|
9
|
-
* - No "system" role in contents, use "systemInstruction" field
|
|
10
|
-
* - Roles are "user" and "model" (not "assistant")
|
|
11
|
-
* - Content is array of "parts" with "text" field
|
|
12
|
-
* - Auth via URL query param, not header
|
|
13
3
|
*/
|
|
14
4
|
|
|
15
|
-
import type { ChatMessage, ChatResponse, ChatOptions } from '../types.js';
|
|
5
|
+
import type { ChatMessage, ChatResponse, ChatOptions, ToolCall } from '../types.js';
|
|
16
6
|
import type { Tool } from '../tools/types.js';
|
|
17
|
-
import type { LLMProvider, ProviderConfig } from './provider.js';
|
|
7
|
+
import type { LLMProvider, StreamChunk, ProviderConfig } from './provider.js';
|
|
18
8
|
|
|
19
9
|
export class GeminiProvider implements LLMProvider {
|
|
20
10
|
readonly name = 'gemini';
|
|
@@ -40,8 +30,8 @@ export class GeminiProvider implements LLMProvider {
|
|
|
40
30
|
}];
|
|
41
31
|
}
|
|
42
32
|
|
|
43
|
-
async chat(messages: ChatMessage[], options?: ChatOptions): Promise<ChatResponse> {
|
|
44
|
-
const body = this.buildRequest(messages, options);
|
|
33
|
+
async chat(messages: ChatMessage[], options?: ChatOptions, tools?: Tool[]): Promise<ChatResponse> {
|
|
34
|
+
const body = this.buildRequest(messages, options, tools);
|
|
45
35
|
const url = `${this.config.baseUrl}/models/${this.config.model}:generateContent?key=${this.config.apiKey}`;
|
|
46
36
|
|
|
47
37
|
const response = await fetch(url, {
|
|
@@ -60,7 +50,6 @@ export class GeminiProvider implements LLMProvider {
|
|
|
60
50
|
const parts = candidate?.content?.parts || [];
|
|
61
51
|
const text = parts.map((p: any) => p.text).filter(Boolean).join('');
|
|
62
52
|
|
|
63
|
-
// Extract function calls
|
|
64
53
|
const functionCalls = parts.filter((p: any) => p.functionCall).map((p: any) => ({
|
|
65
54
|
id: `call_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
66
55
|
type: 'function' as const,
|
|
@@ -82,8 +71,8 @@ export class GeminiProvider implements LLMProvider {
|
|
|
82
71
|
};
|
|
83
72
|
}
|
|
84
73
|
|
|
85
|
-
async *chatStream(messages: ChatMessage[], options?: ChatOptions): AsyncGenerator<
|
|
86
|
-
const body = this.buildRequest(messages, options);
|
|
74
|
+
async *chatStream(messages: ChatMessage[], options?: ChatOptions, tools?: Tool[]): AsyncGenerator<StreamChunk, ChatResponse> {
|
|
75
|
+
const body = this.buildRequest(messages, options, tools);
|
|
87
76
|
const url = `${this.config.baseUrl}/models/${this.config.model}:streamGenerateContent?alt=sse&key=${this.config.apiKey}`;
|
|
88
77
|
|
|
89
78
|
const response = await fetch(url, {
|
|
@@ -101,6 +90,7 @@ export class GeminiProvider implements LLMProvider {
|
|
|
101
90
|
const decoder = new TextDecoder();
|
|
102
91
|
let fullContent = '';
|
|
103
92
|
let buffer = '';
|
|
93
|
+
const functionCalls: ToolCall[] = [];
|
|
104
94
|
|
|
105
95
|
try {
|
|
106
96
|
while (true) {
|
|
@@ -121,7 +111,17 @@ export class GeminiProvider implements LLMProvider {
|
|
|
121
111
|
for (const part of parts) {
|
|
122
112
|
if (part.text) {
|
|
123
113
|
fullContent += part.text;
|
|
124
|
-
yield part.text;
|
|
114
|
+
yield { type: 'text', content: part.text };
|
|
115
|
+
}
|
|
116
|
+
if (part.functionCall) {
|
|
117
|
+
functionCalls.push({
|
|
118
|
+
id: `call_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
119
|
+
type: 'function',
|
|
120
|
+
function: {
|
|
121
|
+
name: part.functionCall.name,
|
|
122
|
+
arguments: JSON.stringify(part.functionCall.args || {}),
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
} catch {
|
|
@@ -133,13 +133,14 @@ export class GeminiProvider implements LLMProvider {
|
|
|
133
133
|
reader.releaseLock();
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
|
|
136
|
+
if (functionCalls.length > 0) {
|
|
137
|
+
yield { type: 'tool_calls', toolCalls: functionCalls };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return { content: fullContent, role: 'assistant', tool_calls: functionCalls.length > 0 ? functionCalls : undefined };
|
|
137
141
|
}
|
|
138
142
|
|
|
139
|
-
|
|
140
|
-
* Build request body for Gemini API
|
|
141
|
-
*/
|
|
142
|
-
private buildRequest(messages: ChatMessage[], options?: ChatOptions) {
|
|
143
|
+
private buildRequest(messages: ChatMessage[], options?: ChatOptions, tools?: Tool[]) {
|
|
143
144
|
const systemMsg = messages.find(m => m.role === 'system');
|
|
144
145
|
const nonSystemMsgs = messages.filter(m => m.role !== 'system');
|
|
145
146
|
|
|
@@ -148,7 +149,7 @@ export class GeminiProvider implements LLMProvider {
|
|
|
148
149
|
parts: [{ text: m.content }],
|
|
149
150
|
}));
|
|
150
151
|
|
|
151
|
-
|
|
152
|
+
const body: any = {
|
|
152
153
|
contents,
|
|
153
154
|
...(systemMsg && {
|
|
154
155
|
systemInstruction: { parts: [{ text: systemMsg.content }] },
|
|
@@ -158,5 +159,11 @@ export class GeminiProvider implements LLMProvider {
|
|
|
158
159
|
maxOutputTokens: options?.maxTokens ?? this.config.maxTokens,
|
|
159
160
|
},
|
|
160
161
|
};
|
|
162
|
+
|
|
163
|
+
if (tools && tools.length > 0) {
|
|
164
|
+
body.tools = this.buildTools(tools);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return body;
|
|
161
168
|
}
|
|
162
169
|
}
|