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.
Files changed (47) hide show
  1. package/dist/cmd/index.js +56 -8
  2. package/dist/cmd/index.js.map +1 -1
  3. package/dist/llm/anthropic.d.ts +4 -17
  4. package/dist/llm/anthropic.d.ts.map +1 -1
  5. package/dist/llm/anthropic.js +54 -12
  6. package/dist/llm/anthropic.js.map +1 -1
  7. package/dist/llm/gemini.d.ts +3 -16
  8. package/dist/llm/gemini.d.ts.map +1 -1
  9. package/dist/llm/gemini.js +26 -22
  10. package/dist/llm/gemini.js.map +1 -1
  11. package/dist/llm/index.d.ts +5 -19
  12. package/dist/llm/index.d.ts.map +1 -1
  13. package/dist/llm/index.js +87 -87
  14. package/dist/llm/index.js.map +1 -1
  15. package/dist/llm/openai.d.ts +9 -23
  16. package/dist/llm/openai.d.ts.map +1 -1
  17. package/dist/llm/openai.js +48 -14
  18. package/dist/llm/openai.js.map +1 -1
  19. package/dist/llm/provider.d.ts +11 -16
  20. package/dist/llm/provider.d.ts.map +1 -1
  21. package/dist/tui/input.d.ts +0 -1
  22. package/dist/tui/input.d.ts.map +1 -1
  23. package/dist/tui/input.js +2 -5
  24. package/dist/tui/input.js.map +1 -1
  25. package/dist/tui/output.d.ts +27 -12
  26. package/dist/tui/output.d.ts.map +1 -1
  27. package/dist/tui/output.js +176 -40
  28. package/dist/tui/output.js.map +1 -1
  29. package/dist/tui/repl.d.ts +0 -10
  30. package/dist/tui/repl.d.ts.map +1 -1
  31. package/dist/tui/repl.js +76 -42
  32. package/dist/tui/repl.js.map +1 -1
  33. package/dist/tui/welcome.d.ts +1 -11
  34. package/dist/tui/welcome.d.ts.map +1 -1
  35. package/dist/tui/welcome.js +60 -56
  36. package/dist/tui/welcome.js.map +1 -1
  37. package/package.json +1 -1
  38. package/src/cmd/index.ts +49 -8
  39. package/src/llm/anthropic.ts +60 -14
  40. package/src/llm/gemini.ts +31 -24
  41. package/src/llm/index.ts +94 -92
  42. package/src/llm/openai.ts +58 -15
  43. package/src/llm/provider.ts +12 -16
  44. package/src/tui/input.ts +2 -5
  45. package/src/tui/output.ts +193 -40
  46. package/src/tui/repl.ts +82 -43
  47. package/src/tui/welcome.ts +62 -59
@@ -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
- const w = 62;
19
- console.log(chalk.cyan('+') + chalk.white.bold(' Thatgfsj Code ') + chalk.cyan('-'.repeat(w - 16)) + '+');
20
- console.log(chalk.cyan('|') + chalk.yellow(' 快速开始指南').padEnd(w) + chalk.cyan('|'));
21
- console.log(chalk.cyan('|') + ' '.repeat(w) + chalk.cyan('|'));
22
- console.log(chalk.cyan('|') + chalk.gray(' 运行 gfcode init 配置你的 API Key').padEnd(w) + chalk.cyan('|'));
23
- console.log(chalk.cyan('|') + chalk.gray(' 支持: OpenAI / Claude / DeepSeek / Kimi / 智谱 / 中转站').padEnd(w) + chalk.cyan('|'));
24
- console.log(chalk.cyan('+') + '-'.repeat(w) + '+');
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(chalk.cyan('\n 🤖 Thatgfsj Code 配置向导\n'));
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 question = (prompt) => new Promise(resolve => rl.question(prompt, answer => resolve(answer.trim())));
39
+ const ask = (prompt) => new Promise(resolve => rl.question(prompt, answer => resolve(answer.trim())));
38
40
  try {
39
- // Step 1: Choose provider
40
- console.log(chalk.white(' 步骤 1/3: 选择 AI 提供商\n'));
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(chalk.green(` ${num}. ${p.name}`));
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 question(chalk.green(' 请选择 (1-' + providers.length + '): '));
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: Enter API Key (and base URL for custom providers)
51
- console.log(chalk.white('\n 步骤 2/3: 配置连接信息\n'));
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
- // Custom provider needs base URL
55
- console.log(chalk.yellow(' 自定义 Provider (中转站) 模式'));
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 question(chalk.green(' 请输入中转站 Base URL: '));
62
+ baseUrl = await ask(chalk.cyan(' Base URL '));
59
63
  if (!baseUrl) {
60
- console.log(chalk.red(' 中转站必须提供 Base URL'));
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 question(chalk.green(' 请输入 API Key: '));
70
- // Step 3: Choose model
71
- console.log(chalk.white('\n 步骤 3/3: 选择模型\n'));
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
- // Custom provider: let user type model name
74
- console.log(chalk.gray(' 输入中转站支持的模型名称'));
75
- console.log(chalk.gray(' 例如: gpt-4o-mini, claude-sonnet-4-20250514, deepseek-chat'));
76
- const model = await question(chalk.green(' 模型名称: '));
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(chalk.green(` ${num}. ${m.name}`) + chalk.gray(` - ${m.desc}`));
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 question(chalk.green(' 请选择模型 (1-' + models.length + '): '));
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
- console.log(chalk.green('\n ✅ 配置已保存!'));
98
- console.log(chalk.gray(` 配置文件: ${join(homedir(), '.thatgfsj', 'config.json')}`));
99
- console.log(chalk.cyan('\n 运行 gfcode 开始使用\n'));
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(chalk.gray(` 提供商: ${provider}`));
126
- console.log(chalk.gray(` 模型: ${model}`));
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(` Base URL: ${baseUrl}`));
132
+ console.log(chalk.gray(' URL: ') + chalk.white(baseUrl));
129
133
  }
130
134
  }
131
135
  }
@@ -1 +1 @@
1
- {"version":3,"file":"welcome.js","sourceRoot":"","sources":["../../src/tui/welcome.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"welcome.js","sourceRoot":"","sources":["../../src/tui/welcome.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thatgfsj-code",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Thatgfsj Code - AI Coding Assistant",
5
5
  "main": "dist/cmd/index.js",
6
6
  "type": "module",
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.0')
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
- console.log(chalk.cyan('\n🤖 Thatgfsj Code\n'));
75
- console.log(chalk.gray(ProjectContext.getSummary()));
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
- await app.runPrompt(prompt);
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}`));
@@ -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<string, ChatResponse> {
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
- // content_block_delta: incremental text
112
- if (data.type === 'content_block_delta' && data.delta?.type === 'text_delta') {
113
- const text = data.delta.text;
114
- fullContent += text;
115
- yield text;
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
- return { content: fullContent, role: 'assistant' };
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
- return {
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<string, ChatResponse> {
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
- return { content: fullContent, role: 'assistant' };
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
- return {
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
  }