thatgfsj-code 0.3.0 → 0.3.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 (262) hide show
  1. package/CHANGELOG.md +229 -0
  2. package/DEVELOPMENT.md +286 -0
  3. package/README.md +144 -79
  4. package/dist/agent/core.d.ts +56 -0
  5. package/dist/agent/core.d.ts.map +1 -0
  6. package/dist/agent/core.js +142 -0
  7. package/dist/agent/core.js.map +1 -0
  8. package/dist/agent/index.d.ts +8 -0
  9. package/dist/agent/index.d.ts.map +1 -0
  10. package/dist/agent/index.js +8 -0
  11. package/dist/agent/index.js.map +1 -0
  12. package/dist/agent/intent.d.ts +36 -0
  13. package/dist/agent/intent.d.ts.map +1 -0
  14. package/dist/agent/intent.js +146 -0
  15. package/dist/agent/intent.js.map +1 -0
  16. package/dist/agent/streaming.d.ts +50 -0
  17. package/dist/agent/streaming.d.ts.map +1 -0
  18. package/dist/agent/streaming.js +110 -0
  19. package/dist/agent/streaming.js.map +1 -0
  20. package/dist/core/ai-engine.d.ts +76 -0
  21. package/dist/core/ai-engine.d.ts.map +1 -0
  22. package/dist/core/ai-engine.js +394 -0
  23. package/dist/core/ai-engine.js.map +1 -0
  24. package/dist/core/cli.d.ts +21 -0
  25. package/dist/core/cli.d.ts.map +1 -0
  26. package/dist/core/cli.js +143 -0
  27. package/dist/core/cli.js.map +1 -0
  28. package/dist/core/config.d.ts +55 -0
  29. package/dist/core/config.d.ts.map +1 -0
  30. package/dist/core/config.js +162 -0
  31. package/dist/core/config.js.map +1 -0
  32. package/dist/core/context-compactor.d.ts +54 -0
  33. package/dist/core/context-compactor.d.ts.map +1 -0
  34. package/dist/core/context-compactor.js +197 -0
  35. package/dist/core/context-compactor.js.map +1 -0
  36. package/dist/core/hooks.d.ts +75 -0
  37. package/dist/core/hooks.d.ts.map +1 -0
  38. package/dist/core/hooks.js +146 -0
  39. package/dist/core/hooks.js.map +1 -0
  40. package/dist/core/permissions.d.ts +52 -0
  41. package/dist/core/permissions.d.ts.map +1 -0
  42. package/dist/core/permissions.js +237 -0
  43. package/dist/core/permissions.js.map +1 -0
  44. package/dist/core/session.d.ts +54 -0
  45. package/dist/core/session.d.ts.map +1 -0
  46. package/dist/core/session.js +147 -0
  47. package/dist/core/session.js.map +1 -0
  48. package/dist/core/skills.d.ts +60 -0
  49. package/dist/core/skills.d.ts.map +1 -0
  50. package/dist/core/skills.js +175 -0
  51. package/dist/core/skills.js.map +1 -0
  52. package/dist/core/state.d.ts +60 -0
  53. package/dist/core/state.d.ts.map +1 -0
  54. package/dist/core/state.js +106 -0
  55. package/dist/core/state.js.map +1 -0
  56. package/dist/core/subagent.d.ts +68 -0
  57. package/dist/core/subagent.d.ts.map +1 -0
  58. package/dist/core/subagent.js +142 -0
  59. package/dist/core/subagent.js.map +1 -0
  60. package/dist/core/system-prompt.d.ts +40 -0
  61. package/dist/core/system-prompt.d.ts.map +1 -0
  62. package/dist/core/system-prompt.js +138 -0
  63. package/dist/core/system-prompt.js.map +1 -0
  64. package/dist/core/tool-registry.d.ts +53 -0
  65. package/dist/core/tool-registry.d.ts.map +1 -0
  66. package/dist/core/tool-registry.js +138 -0
  67. package/dist/core/tool-registry.js.map +1 -0
  68. package/dist/core/types.d.ts +206 -0
  69. package/dist/core/types.d.ts.map +1 -0
  70. package/dist/core/types.js +127 -0
  71. package/dist/core/types.js.map +1 -0
  72. package/dist/index.d.ts +7 -0
  73. package/dist/{cmd/index.d.ts.map → index.d.ts.map} +1 -1
  74. package/dist/index.js +472 -0
  75. package/dist/index.js.map +1 -0
  76. package/dist/mcp/client.d.ts +1 -1
  77. package/dist/mcp/client.d.ts.map +1 -1
  78. package/dist/repl/index.d.ts +8 -0
  79. package/dist/repl/index.d.ts.map +1 -0
  80. package/dist/repl/index.js +8 -0
  81. package/dist/repl/index.js.map +1 -0
  82. package/dist/repl/input.d.ts +69 -0
  83. package/dist/repl/input.d.ts.map +1 -0
  84. package/dist/repl/input.js +139 -0
  85. package/dist/repl/input.js.map +1 -0
  86. package/dist/repl/loop.d.ts +184 -0
  87. package/dist/repl/loop.d.ts.map +1 -0
  88. package/dist/repl/loop.js +932 -0
  89. package/dist/repl/loop.js.map +1 -0
  90. package/dist/repl/output.d.ts +105 -0
  91. package/dist/repl/output.d.ts.map +1 -0
  92. package/dist/repl/output.js +199 -0
  93. package/dist/repl/output.js.map +1 -0
  94. package/dist/repl/welcome.d.ts +68 -0
  95. package/dist/repl/welcome.d.ts.map +1 -0
  96. package/dist/repl/welcome.js +254 -0
  97. package/dist/repl/welcome.js.map +1 -0
  98. package/dist/tools/file.d.ts +1 -1
  99. package/dist/tools/file.d.ts.map +1 -1
  100. package/dist/tools/git.d.ts +1 -1
  101. package/dist/tools/git.d.ts.map +1 -1
  102. package/dist/tools/index.d.ts +15 -35
  103. package/dist/tools/index.d.ts.map +1 -1
  104. package/dist/tools/index.js +31 -80
  105. package/dist/tools/index.js.map +1 -1
  106. package/dist/tools/search.d.ts +1 -1
  107. package/dist/tools/search.d.ts.map +1 -1
  108. package/dist/tools/shell.d.ts +11 -4
  109. package/dist/tools/shell.d.ts.map +1 -1
  110. package/dist/tools/shell.js +78 -42
  111. package/dist/tools/shell.js.map +1 -1
  112. package/dist/utils/diff-preview.d.ts +42 -0
  113. package/dist/utils/diff-preview.d.ts.map +1 -0
  114. package/dist/utils/diff-preview.js +174 -0
  115. package/dist/utils/diff-preview.js.map +1 -0
  116. package/dist/utils/index.d.ts +8 -0
  117. package/dist/utils/index.d.ts.map +1 -0
  118. package/dist/utils/index.js +8 -0
  119. package/dist/utils/index.js.map +1 -0
  120. package/dist/utils/memory.d.ts +81 -0
  121. package/dist/utils/memory.d.ts.map +1 -0
  122. package/dist/utils/memory.js +186 -0
  123. package/dist/utils/memory.js.map +1 -0
  124. package/dist/utils/project-context.d.ts +62 -0
  125. package/dist/utils/project-context.d.ts.map +1 -0
  126. package/dist/utils/project-context.js +197 -0
  127. package/dist/utils/project-context.js.map +1 -0
  128. package/docs/API_KEY_GUIDE.md +6 -0
  129. package/docs/FAQ.md +25 -3
  130. package/package.json +41 -9
  131. package/ROADMAP.md +0 -61
  132. package/dist/app/agent.d.ts +0 -31
  133. package/dist/app/agent.d.ts.map +0 -1
  134. package/dist/app/agent.js +0 -106
  135. package/dist/app/agent.js.map +0 -1
  136. package/dist/app/index.d.ts +0 -39
  137. package/dist/app/index.d.ts.map +0 -1
  138. package/dist/app/index.js +0 -80
  139. package/dist/app/index.js.map +0 -1
  140. package/dist/cmd/index.d.ts +0 -12
  141. package/dist/cmd/index.js +0 -85
  142. package/dist/cmd/index.js.map +0 -1
  143. package/dist/config/index.d.ts +0 -39
  144. package/dist/config/index.d.ts.map +0 -1
  145. package/dist/config/index.js +0 -110
  146. package/dist/config/index.js.map +0 -1
  147. package/dist/config/providers.d.ts +0 -23
  148. package/dist/config/providers.d.ts.map +0 -1
  149. package/dist/config/providers.js +0 -152
  150. package/dist/config/providers.js.map +0 -1
  151. package/dist/config/types.d.ts +0 -33
  152. package/dist/config/types.d.ts.map +0 -1
  153. package/dist/config/types.js +0 -5
  154. package/dist/config/types.js.map +0 -1
  155. package/dist/hooks/index.d.ts +0 -31
  156. package/dist/hooks/index.d.ts.map +0 -1
  157. package/dist/hooks/index.js +0 -71
  158. package/dist/hooks/index.js.map +0 -1
  159. package/dist/llm/anthropic.d.ts +0 -17
  160. package/dist/llm/anthropic.d.ts.map +0 -1
  161. package/dist/llm/anthropic.js +0 -126
  162. package/dist/llm/anthropic.js.map +0 -1
  163. package/dist/llm/gemini.d.ts +0 -16
  164. package/dist/llm/gemini.d.ts.map +0 -1
  165. package/dist/llm/gemini.js +0 -107
  166. package/dist/llm/gemini.js.map +0 -1
  167. package/dist/llm/index.d.ts +0 -48
  168. package/dist/llm/index.d.ts.map +0 -1
  169. package/dist/llm/index.js +0 -174
  170. package/dist/llm/index.js.map +0 -1
  171. package/dist/llm/openai.d.ts +0 -17
  172. package/dist/llm/openai.d.ts.map +0 -1
  173. package/dist/llm/openai.js +0 -113
  174. package/dist/llm/openai.js.map +0 -1
  175. package/dist/llm/provider.d.ts +0 -33
  176. package/dist/llm/provider.d.ts.map +0 -1
  177. package/dist/llm/provider.js +0 -6
  178. package/dist/llm/provider.js.map +0 -1
  179. package/dist/prompts/index.d.ts +0 -27
  180. package/dist/prompts/index.d.ts.map +0 -1
  181. package/dist/prompts/index.js +0 -102
  182. package/dist/prompts/index.js.map +0 -1
  183. package/dist/session/compactor.d.ts +0 -48
  184. package/dist/session/compactor.d.ts.map +0 -1
  185. package/dist/session/compactor.js +0 -77
  186. package/dist/session/compactor.js.map +0 -1
  187. package/dist/session/index.d.ts +0 -48
  188. package/dist/session/index.d.ts.map +0 -1
  189. package/dist/session/index.js +0 -69
  190. package/dist/session/index.js.map +0 -1
  191. package/dist/session/message.d.ts +0 -26
  192. package/dist/session/message.d.ts.map +0 -1
  193. package/dist/session/message.js +0 -34
  194. package/dist/session/message.js.map +0 -1
  195. package/dist/tools/types.d.ts +0 -68
  196. package/dist/tools/types.d.ts.map +0 -1
  197. package/dist/tools/types.js +0 -57
  198. package/dist/tools/types.js.map +0 -1
  199. package/dist/tui/index.d.ts +0 -8
  200. package/dist/tui/index.d.ts.map +0 -1
  201. package/dist/tui/index.js +0 -8
  202. package/dist/tui/index.js.map +0 -1
  203. package/dist/tui/input.d.ts +0 -14
  204. package/dist/tui/input.d.ts.map +0 -1
  205. package/dist/tui/input.js +0 -50
  206. package/dist/tui/input.js.map +0 -1
  207. package/dist/tui/output.d.ts +0 -20
  208. package/dist/tui/output.d.ts.map +0 -1
  209. package/dist/tui/output.js +0 -70
  210. package/dist/tui/output.js.map +0 -1
  211. package/dist/tui/repl.d.ts +0 -25
  212. package/dist/tui/repl.d.ts.map +0 -1
  213. package/dist/tui/repl.js +0 -114
  214. package/dist/tui/repl.js.map +0 -1
  215. package/dist/tui/welcome.d.ts +0 -15
  216. package/dist/tui/welcome.d.ts.map +0 -1
  217. package/dist/tui/welcome.js +0 -86
  218. package/dist/tui/welcome.js.map +0 -1
  219. package/dist/types.d.ts +0 -38
  220. package/dist/types.d.ts.map +0 -1
  221. package/dist/types.js +0 -6
  222. package/dist/types.js.map +0 -1
  223. package/dist/utils/diff.d.ts +0 -22
  224. package/dist/utils/diff.d.ts.map +0 -1
  225. package/dist/utils/diff.js +0 -60
  226. package/dist/utils/diff.js.map +0 -1
  227. package/dist/utils/project.d.ts +0 -32
  228. package/dist/utils/project.d.ts.map +0 -1
  229. package/dist/utils/project.js +0 -89
  230. package/dist/utils/project.js.map +0 -1
  231. package/src/app/agent.ts +0 -140
  232. package/src/app/index.ts +0 -101
  233. package/src/cmd/index.ts +0 -96
  234. package/src/config/index.ts +0 -130
  235. package/src/config/providers.ts +0 -160
  236. package/src/config/types.ts +0 -46
  237. package/src/hooks/index.ts +0 -111
  238. package/src/llm/anthropic.ts +0 -146
  239. package/src/llm/gemini.ts +0 -127
  240. package/src/llm/index.ts +0 -209
  241. package/src/llm/openai.ts +0 -132
  242. package/src/llm/provider.ts +0 -38
  243. package/src/mcp/client.ts +0 -330
  244. package/src/prompts/index.ts +0 -123
  245. package/src/session/compactor.ts +0 -103
  246. package/src/session/index.ts +0 -81
  247. package/src/session/message.ts +0 -42
  248. package/src/tools/file.ts +0 -117
  249. package/src/tools/git.ts +0 -132
  250. package/src/tools/index.ts +0 -108
  251. package/src/tools/search.ts +0 -263
  252. package/src/tools/shell.ts +0 -136
  253. package/src/tools/types.ts +0 -122
  254. package/src/tui/index.ts +0 -8
  255. package/src/tui/input.ts +0 -56
  256. package/src/tui/output.ts +0 -83
  257. package/src/tui/repl.ts +0 -131
  258. package/src/tui/welcome.ts +0 -101
  259. package/src/types.ts +0 -42
  260. package/src/utils/diff.ts +0 -71
  261. package/src/utils/project.ts +0 -99
  262. package/tsconfig.json +0 -19
@@ -1,146 +0,0 @@
1
- /**
2
- * Anthropic Provider
3
- * Works with: Anthropic Claude, MiniMax (Anthropic mode)
4
- */
5
-
6
- import type { ChatMessage, ChatResponse, ChatOptions } from '../types.js';
7
- import type { Tool } from '../tools/types.js';
8
- import type { LLMProvider, ProviderConfig } from './provider.js';
9
-
10
- export class AnthropicProvider implements LLMProvider {
11
- readonly name = 'anthropic';
12
- private config: ProviderConfig;
13
-
14
- constructor(config: ProviderConfig) {
15
- this.config = config;
16
- }
17
-
18
- buildTools(tools: Tool[]): any[] {
19
- return tools.map(tool => ({
20
- name: tool.name,
21
- description: tool.description,
22
- input_schema: tool.inputSchema || {
23
- type: 'object',
24
- properties: Object.fromEntries(
25
- tool.parameters.map(p => [p.name, { type: p.type, description: p.description }])
26
- ),
27
- required: tool.parameters.filter(p => p.required).map(p => p.name),
28
- },
29
- }));
30
- }
31
-
32
- async chat(messages: ChatMessage[], options?: ChatOptions): Promise<ChatResponse> {
33
- const body = this.buildRequest(messages, false, options);
34
-
35
- const response = await fetch(`${this.config.baseUrl}/messages`, {
36
- method: 'POST',
37
- headers: {
38
- 'Content-Type': 'application/json',
39
- 'x-api-key': this.config.apiKey,
40
- 'anthropic-version': '2023-06-01',
41
- },
42
- body: JSON.stringify(body),
43
- });
44
-
45
- if (!response.ok) {
46
- const text = await response.text().catch(() => '');
47
- throw new Error(`Anthropic API error ${response.status}: ${text}`);
48
- }
49
-
50
- const data = await response.json();
51
- const contentBlock = data.content?.find((b: any) => b.type === 'text');
52
- const toolUseBlocks = data.content?.filter((b: any) => b.type === 'tool_use') || [];
53
-
54
- return {
55
- content: contentBlock?.text || '',
56
- role: 'assistant',
57
- usage: data.usage ? {
58
- prompt_tokens: data.usage.input_tokens || 0,
59
- completion_tokens: data.usage.output_tokens || 0,
60
- total_tokens: (data.usage.input_tokens || 0) + (data.usage.output_tokens || 0),
61
- } : undefined,
62
- tool_calls: toolUseBlocks.length > 0 ? toolUseBlocks.map((b: any) => ({
63
- id: b.id,
64
- type: 'function' as const,
65
- function: {
66
- name: b.name,
67
- arguments: JSON.stringify(b.input),
68
- },
69
- })) : undefined,
70
- };
71
- }
72
-
73
- async *chatStream(messages: ChatMessage[], options?: ChatOptions): AsyncGenerator<string, ChatResponse> {
74
- const body = this.buildRequest(messages, true, options);
75
-
76
- const response = await fetch(`${this.config.baseUrl}/messages`, {
77
- method: 'POST',
78
- headers: {
79
- 'Content-Type': 'application/json',
80
- 'x-api-key': this.config.apiKey,
81
- 'anthropic-version': '2023-06-01',
82
- 'anthropic-dangerous-direct-browser-access': 'true',
83
- },
84
- body: JSON.stringify(body),
85
- });
86
-
87
- if (!response.ok || !response.body) {
88
- const text = await response.text().catch(() => '');
89
- throw new Error(`Anthropic API error ${response.status}: ${text}`);
90
- }
91
-
92
- const reader = response.body.getReader();
93
- const decoder = new TextDecoder();
94
- let fullContent = '';
95
- let buffer = '';
96
-
97
- try {
98
- while (true) {
99
- const { done, value } = await reader.read();
100
- if (done) break;
101
-
102
- buffer += decoder.decode(value, { stream: true });
103
- const lines = buffer.split('\n');
104
- buffer = lines.pop() || '';
105
-
106
- for (const line of lines) {
107
- const trimmed = line.trim();
108
- if (!trimmed || !trimmed.startsWith('data: ')) continue;
109
-
110
- try {
111
- const data = JSON.parse(trimmed.slice(6));
112
- if (data.type === 'content_block_delta' && data.delta?.type === 'text_delta') {
113
- const text = data.delta.text;
114
- fullContent += text;
115
- yield text;
116
- }
117
- } catch {
118
- // Skip invalid JSON
119
- }
120
- }
121
- }
122
- } finally {
123
- reader.releaseLock();
124
- }
125
-
126
- return { content: fullContent, role: 'assistant' };
127
- }
128
-
129
- private buildRequest(messages: ChatMessage[], stream: boolean, options?: ChatOptions) {
130
- // Anthropic uses separate system message
131
- const systemMsg = messages.find(m => m.role === 'system');
132
- const nonSystemMsgs = messages.filter(m => m.role !== 'system');
133
-
134
- return {
135
- model: this.config.model,
136
- max_tokens: options?.maxTokens ?? this.config.maxTokens,
137
- temperature: options?.temperature ?? this.config.temperature,
138
- ...(systemMsg && { system: systemMsg.content }),
139
- messages: nonSystemMsgs.map(m => ({
140
- role: m.role === 'assistant' ? 'assistant' : 'user',
141
- content: m.content,
142
- })),
143
- stream,
144
- };
145
- }
146
- }
package/src/llm/gemini.ts DELETED
@@ -1,127 +0,0 @@
1
- /**
2
- * Google Gemini Provider
3
- */
4
-
5
- import type { ChatMessage, ChatResponse, ChatOptions } from '../types.js';
6
- import type { Tool } from '../tools/types.js';
7
- import type { LLMProvider, ProviderConfig } from './provider.js';
8
-
9
- export class GeminiProvider implements LLMProvider {
10
- readonly name = 'gemini';
11
- private config: ProviderConfig;
12
-
13
- constructor(config: ProviderConfig) {
14
- this.config = config;
15
- }
16
-
17
- buildTools(tools: Tool[]): any[] {
18
- return tools.map(tool => ({
19
- name: tool.name,
20
- description: tool.description,
21
- parameters: tool.inputSchema || {
22
- type: 'object',
23
- properties: Object.fromEntries(
24
- tool.parameters.map(p => [p.name, { type: p.type, description: p.description }])
25
- ),
26
- required: tool.parameters.filter(p => p.required).map(p => p.name),
27
- },
28
- }));
29
- }
30
-
31
- async chat(messages: ChatMessage[], options?: ChatOptions): Promise<ChatResponse> {
32
- const body = this.buildRequest(messages, options);
33
-
34
- const url = `${this.config.baseUrl}/models/${this.config.model}:generateContent?key=${this.config.apiKey}`;
35
- const response = await fetch(url, {
36
- method: 'POST',
37
- headers: { 'Content-Type': 'application/json' },
38
- body: JSON.stringify(body),
39
- });
40
-
41
- if (!response.ok) {
42
- const text = await response.text().catch(() => '');
43
- throw new Error(`Gemini API error ${response.status}: ${text}`);
44
- }
45
-
46
- const data = await response.json();
47
- const candidate = data.candidates?.[0];
48
- const text = candidate?.content?.parts?.map((p: any) => p.text).join('') || '';
49
-
50
- return {
51
- content: text,
52
- role: 'assistant',
53
- usage: data.usageMetadata ? {
54
- prompt_tokens: data.usageMetadata.promptTokenCount || 0,
55
- completion_tokens: data.usageMetadata.candidatesTokenCount || 0,
56
- total_tokens: data.usageMetadata.totalTokenCount || 0,
57
- } : undefined,
58
- };
59
- }
60
-
61
- async *chatStream(messages: ChatMessage[], options?: ChatOptions): AsyncGenerator<string, ChatResponse> {
62
- const body = this.buildRequest(messages, options);
63
-
64
- const url = `${this.config.baseUrl}/models/${this.config.model}:streamGenerateContent?key=${this.config.apiKey}`;
65
- const response = await fetch(url, {
66
- method: 'POST',
67
- headers: { 'Content-Type': 'application/json' },
68
- body: JSON.stringify(body),
69
- });
70
-
71
- if (!response.ok || !response.body) {
72
- const text = await response.text().catch(() => '');
73
- throw new Error(`Gemini API error ${response.status}: ${text}`);
74
- }
75
-
76
- const reader = response.body.getReader();
77
- const decoder = new TextDecoder();
78
- let fullContent = '';
79
- let buffer = '';
80
-
81
- try {
82
- while (true) {
83
- const { done, value } = await reader.read();
84
- if (done) break;
85
-
86
- buffer += decoder.decode(value, { stream: true });
87
-
88
- // Gemini streams JSON array chunks
89
- const jsonMatch = buffer.match(/\{"candidates":\[.*?\]\}/);
90
- if (jsonMatch) {
91
- try {
92
- const data = JSON.parse(jsonMatch[0]);
93
- const text = data.candidates?.[0]?.content?.parts?.map((p: any) => p.text).join('') || '';
94
- if (text) {
95
- fullContent += text;
96
- yield text;
97
- }
98
- } catch {
99
- // Skip invalid JSON
100
- }
101
- buffer = buffer.slice(jsonMatch.index! + jsonMatch[0].length);
102
- }
103
- }
104
- } finally {
105
- reader.releaseLock();
106
- }
107
-
108
- return { content: fullContent, role: 'assistant' };
109
- }
110
-
111
- private buildRequest(messages: ChatMessage[], options?: ChatOptions) {
112
- const contents = messages
113
- .filter(m => m.role !== 'system')
114
- .map(m => ({
115
- role: m.role === 'assistant' ? 'model' : 'user',
116
- parts: [{ text: m.content }],
117
- }));
118
-
119
- return {
120
- contents,
121
- generationConfig: {
122
- temperature: options?.temperature ?? this.config.temperature,
123
- maxOutputTokens: options?.maxTokens ?? this.config.maxTokens,
124
- },
125
- };
126
- }
127
- }
package/src/llm/index.ts DELETED
@@ -1,209 +0,0 @@
1
- /**
2
- * LLM Service - Factory for creating providers
3
- * Replaces the monolithic AIEngine with a clean provider abstraction
4
- */
5
-
6
- import chalk from 'chalk';
7
- import type { ChatMessage, ChatResponse, ChatOptions, ToolCall } from '../types.js';
8
- import type { Tool } from '../tools/types.js';
9
- import type { LLMProvider } from './provider.js';
10
- import type { AIConfig, ProviderName } from '../config/types.js';
11
- import { PROVIDERS } from '../config/providers.js';
12
- import { OpenAIProvider } from './openai.js';
13
- import { AnthropicProvider } from './anthropic.js';
14
- import { GeminiProvider } from './gemini.js';
15
-
16
- export class LLMService {
17
- private provider: LLMProvider;
18
- private tools: Map<string, Tool> = new Map();
19
-
20
- constructor(provider: LLMProvider) {
21
- this.provider = provider;
22
- }
23
-
24
- /**
25
- * Create LLMService from AIConfig
26
- */
27
- static fromConfig(config: AIConfig): LLMService {
28
- const providerName = config.provider || 'siliconflow';
29
- const providerConfig = PROVIDERS[providerName];
30
-
31
- const providerCfg = {
32
- apiKey: config.apiKey || '',
33
- model: config.model || providerConfig.defaultModel,
34
- baseUrl: config.baseUrl || providerConfig.baseUrl,
35
- temperature: config.temperature ?? 0.7,
36
- maxTokens: config.maxTokens ?? 4096,
37
- };
38
-
39
- const format = providerConfig.format;
40
- let provider: LLMProvider;
41
-
42
- switch (format) {
43
- case 'anthropic':
44
- provider = new AnthropicProvider(providerCfg);
45
- break;
46
- case 'gemini':
47
- provider = new GeminiProvider(providerCfg);
48
- break;
49
- default:
50
- provider = new OpenAIProvider(providerCfg);
51
- }
52
-
53
- return new LLMService(provider);
54
- }
55
-
56
- /**
57
- * Register tools for function calling
58
- */
59
- registerTools(tools: Tool[]): void {
60
- for (const tool of tools) {
61
- this.tools.set(tool.name, tool);
62
- }
63
- }
64
-
65
- /**
66
- * Get provider name
67
- */
68
- getProviderName(): string {
69
- return this.provider.name;
70
- }
71
-
72
- /**
73
- * Non-streaming chat
74
- */
75
- async chat(messages: ChatMessage[], options?: ChatOptions): Promise<ChatResponse> {
76
- if (!this.hasApiKey()) {
77
- return this.getMockResponse(messages);
78
- }
79
- return this.provider.chat(messages, options);
80
- }
81
-
82
- /**
83
- * Streaming chat with agent loop (tool call support)
84
- */
85
- async *chatStream(
86
- messages: ChatMessage[],
87
- options?: ChatOptions & { maxIterations?: number }
88
- ): AsyncGenerator<string, ChatResponse> {
89
- if (!this.hasApiKey()) {
90
- const mock = this.getMockResponse(messages);
91
- for (const char of mock.content) {
92
- await new Promise(r => setTimeout(r, 10));
93
- yield char;
94
- }
95
- return mock;
96
- }
97
-
98
- const maxIterations = options?.maxIterations ?? 10;
99
- let currentMessages = [...messages];
100
- let iterations = 0;
101
-
102
- while (iterations < maxIterations) {
103
- iterations++;
104
-
105
- // Get tools in provider format
106
- const toolsArray = [...this.tools.values()];
107
- const providerTools = toolsArray.length > 0 ? this.provider.buildTools(toolsArray) : [];
108
-
109
- // Stream the response
110
- let fullContent = '';
111
- const streamOptions = { ...options };
112
-
113
- // Add tools to the request if available
114
- if (providerTools.length > 0) {
115
- // We need to pass tools differently depending on provider
116
- // For now, inject into messages context
117
- }
118
-
119
- for await (const chunk of this.provider.chatStream(currentMessages, streamOptions)) {
120
- fullContent += chunk;
121
- yield chunk;
122
- }
123
-
124
- // Check for tool calls in the response
125
- const response = await this.provider.chat(currentMessages, { ...streamOptions, stream: false });
126
- const toolCalls = response.tool_calls;
127
-
128
- if (toolCalls && toolCalls.length > 0) {
129
- // Append assistant message with tool calls
130
- currentMessages.push({
131
- role: 'assistant',
132
- content: fullContent,
133
- tool_calls: toolCalls,
134
- });
135
-
136
- // Execute each tool
137
- for (const toolCall of toolCalls) {
138
- const tool = this.tools.get(toolCall.function.name);
139
- if (!tool) {
140
- currentMessages.push({
141
- role: 'tool',
142
- content: `Tool "${toolCall.function.name}" not found`,
143
- tool_call_id: toolCall.id,
144
- name: toolCall.function.name,
145
- });
146
- continue;
147
- }
148
-
149
- try {
150
- const params = JSON.parse(toolCall.function.arguments || '{}');
151
- const result = await tool.execute(params);
152
- currentMessages.push({
153
- role: 'tool',
154
- content: result.success ? (result.output || JSON.stringify(result.data)) : (result.error || 'Tool failed'),
155
- tool_call_id: toolCall.id,
156
- name: toolCall.function.name,
157
- });
158
- yield `\n${chalk.gray(`[tool: ${toolCall.function.name}]`)}`;
159
- } catch (error: any) {
160
- currentMessages.push({
161
- role: 'tool',
162
- content: `Tool execution error: ${error.message}`,
163
- tool_call_id: toolCall.id,
164
- name: toolCall.function.name,
165
- });
166
- }
167
- }
168
-
169
- continue; // Loop back for next iteration
170
- }
171
-
172
- // No tool calls - we're done
173
- return response;
174
- }
175
-
176
- return { content: '[Agent loop exceeded maximum iterations]', role: 'assistant' };
177
- }
178
-
179
- /**
180
- * Check if API key is available
181
- */
182
- private hasApiKey(): boolean {
183
- return !!(this.provider as any).config?.apiKey;
184
- }
185
-
186
- /**
187
- * Mock response when no API key is set
188
- */
189
- private getMockResponse(messages: ChatMessage[]): ChatResponse {
190
- const last = messages[messages.length - 1]?.content || '';
191
- let content: string;
192
-
193
- if (/^(hi|hello|hey)/i.test(last)) {
194
- content = 'Hi there! How can I help you today?';
195
- } else if (last.toLowerCase().includes('who are you')) {
196
- content = 'I am Thatgfsj Code, an AI coding assistant.';
197
- } else {
198
- content = `I understand you said: "${last.slice(0, 50)}..."\n\nThis is a demo response. Configure an API key for full AI capabilities.\nRun: gfcode init`;
199
- }
200
-
201
- return { content, role: 'assistant' };
202
- }
203
- }
204
-
205
- // Re-export provider types
206
- export type { LLMProvider, ProviderConfig } from './provider.js';
207
- export { OpenAIProvider } from './openai.js';
208
- export { AnthropicProvider } from './anthropic.js';
209
- export { GeminiProvider } from './gemini.js';
package/src/llm/openai.ts DELETED
@@ -1,132 +0,0 @@
1
- /**
2
- * OpenAI-compatible Provider
3
- * Works with: OpenAI, SiliconFlow, MiniMax (OpenAI mode), DeepSeek, Kimi, ERNIE, Ollama
4
- */
5
-
6
- import type { ChatMessage, ChatResponse, ChatOptions } from '../types.js';
7
- import type { Tool } from '../tools/types.js';
8
- import type { LLMProvider, ProviderConfig } from './provider.js';
9
-
10
- export class OpenAIProvider implements LLMProvider {
11
- readonly name = 'openai';
12
- private config: ProviderConfig;
13
-
14
- constructor(config: ProviderConfig) {
15
- this.config = config;
16
- }
17
-
18
- buildTools(tools: Tool[]): any[] {
19
- return tools.map(tool => ({
20
- type: 'function' as const,
21
- function: {
22
- name: tool.name,
23
- description: tool.description,
24
- parameters: tool.inputSchema || {
25
- type: 'object',
26
- properties: Object.fromEntries(
27
- tool.parameters.map(p => [p.name, { type: p.type, description: p.description }])
28
- ),
29
- required: tool.parameters.filter(p => p.required).map(p => p.name),
30
- },
31
- },
32
- }));
33
- }
34
-
35
- async chat(messages: ChatMessage[], options?: ChatOptions): Promise<ChatResponse> {
36
- const body = this.buildRequest(messages, false, options);
37
-
38
- const response = await fetch(`${this.config.baseUrl}/chat/completions`, {
39
- method: 'POST',
40
- headers: {
41
- 'Content-Type': 'application/json',
42
- 'Authorization': `Bearer ${this.config.apiKey}`,
43
- },
44
- body: JSON.stringify(body),
45
- });
46
-
47
- if (!response.ok) {
48
- const text = await response.text().catch(() => '');
49
- throw new Error(`OpenAI API error ${response.status}: ${text}`);
50
- }
51
-
52
- const data = await response.json();
53
- const choice = data.choices?.[0];
54
-
55
- return {
56
- content: choice?.message?.content || '',
57
- role: 'assistant',
58
- usage: data.usage,
59
- tool_calls: choice?.message?.tool_calls,
60
- };
61
- }
62
-
63
- async *chatStream(messages: ChatMessage[], options?: ChatOptions): AsyncGenerator<string, ChatResponse> {
64
- const body = this.buildRequest(messages, true, options);
65
-
66
- const response = await fetch(`${this.config.baseUrl}/chat/completions`, {
67
- method: 'POST',
68
- headers: {
69
- 'Content-Type': 'application/json',
70
- 'Authorization': `Bearer ${this.config.apiKey}`,
71
- },
72
- body: JSON.stringify(body),
73
- });
74
-
75
- if (!response.ok || !response.body) {
76
- const text = await response.text().catch(() => '');
77
- throw new Error(`OpenAI API error ${response.status}: ${text}`);
78
- }
79
-
80
- const reader = response.body.getReader();
81
- const decoder = new TextDecoder();
82
- let fullContent = '';
83
- let buffer = '';
84
-
85
- try {
86
- while (true) {
87
- const { done, value } = await reader.read();
88
- if (done) break;
89
-
90
- buffer += decoder.decode(value, { stream: true });
91
- const lines = buffer.split('\n');
92
- buffer = lines.pop() || '';
93
-
94
- for (const line of lines) {
95
- const trimmed = line.trim();
96
- if (!trimmed || !trimmed.startsWith('data: ') || trimmed === 'data: [DONE]') continue;
97
-
98
- try {
99
- const data = JSON.parse(trimmed.slice(6));
100
- const content = data.choices?.[0]?.delta?.content;
101
- if (content) {
102
- fullContent += content;
103
- yield content;
104
- }
105
- } catch {
106
- // Skip invalid JSON lines
107
- }
108
- }
109
- }
110
- } finally {
111
- reader.releaseLock();
112
- }
113
-
114
- return { content: fullContent, role: 'assistant' };
115
- }
116
-
117
- private buildRequest(messages: ChatMessage[], stream: boolean, options?: ChatOptions) {
118
- return {
119
- model: this.config.model,
120
- messages: messages.map(m => ({
121
- role: m.role,
122
- content: m.content,
123
- ...(m.name && { name: m.name }),
124
- ...(m.tool_call_id && { tool_call_id: m.tool_call_id }),
125
- ...(m.tool_calls && { tool_calls: m.tool_calls }),
126
- })),
127
- temperature: options?.temperature ?? this.config.temperature,
128
- max_tokens: options?.maxTokens ?? this.config.maxTokens,
129
- stream,
130
- };
131
- }
132
- }
@@ -1,38 +0,0 @@
1
- /**
2
- * Abstract LLM Provider interface
3
- * All providers implement this interface
4
- */
5
-
6
- import type { ChatMessage, ChatResponse, ChatOptions } from '../types.js';
7
- import type { Tool } from '../tools/types.js';
8
-
9
- export interface LLMProvider {
10
- /** Provider name for logging */
11
- readonly name: string;
12
-
13
- /**
14
- * Non-streaming chat completion
15
- */
16
- chat(messages: ChatMessage[], options?: ChatOptions): Promise<ChatResponse>;
17
-
18
- /**
19
- * Streaming chat completion - yields text chunks
20
- */
21
- chatStream(messages: ChatMessage[], options?: ChatOptions): AsyncGenerator<string, ChatResponse>;
22
-
23
- /**
24
- * Convert Tool[] to provider-specific format
25
- */
26
- buildTools(tools: Tool[]): any[];
27
- }
28
-
29
- /**
30
- * Provider configuration passed to each provider
31
- */
32
- export interface ProviderConfig {
33
- apiKey: string;
34
- model: string;
35
- baseUrl: string;
36
- temperature: number;
37
- maxTokens: number;
38
- }