gencode-ai 0.1.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.
Files changed (274) hide show
  1. package/.env.example +11 -0
  2. package/CLAUDE.md +70 -0
  3. package/LICENSE +21 -0
  4. package/README.md +117 -0
  5. package/dist/agent/agent.d.ts +84 -0
  6. package/dist/agent/agent.d.ts.map +1 -0
  7. package/dist/agent/agent.js +233 -0
  8. package/dist/agent/agent.js.map +1 -0
  9. package/dist/agent/index.d.ts +6 -0
  10. package/dist/agent/index.d.ts.map +1 -0
  11. package/dist/agent/index.js +6 -0
  12. package/dist/agent/index.js.map +1 -0
  13. package/dist/agent/types.d.ts +47 -0
  14. package/dist/agent/types.d.ts.map +1 -0
  15. package/dist/agent/types.js +5 -0
  16. package/dist/agent/types.js.map +1 -0
  17. package/dist/cli/components/App.d.ts +14 -0
  18. package/dist/cli/components/App.d.ts.map +1 -0
  19. package/dist/cli/components/App.js +395 -0
  20. package/dist/cli/components/App.js.map +1 -0
  21. package/dist/cli/components/CommandSuggestions.d.ts +13 -0
  22. package/dist/cli/components/CommandSuggestions.d.ts.map +1 -0
  23. package/dist/cli/components/CommandSuggestions.js +32 -0
  24. package/dist/cli/components/CommandSuggestions.js.map +1 -0
  25. package/dist/cli/components/Header.d.ts +9 -0
  26. package/dist/cli/components/Header.d.ts.map +1 -0
  27. package/dist/cli/components/Header.js +13 -0
  28. package/dist/cli/components/Header.js.map +1 -0
  29. package/dist/cli/components/Input.d.ts +13 -0
  30. package/dist/cli/components/Input.d.ts.map +1 -0
  31. package/dist/cli/components/Input.js +27 -0
  32. package/dist/cli/components/Input.js.map +1 -0
  33. package/dist/cli/components/Logo.d.ts +2 -0
  34. package/dist/cli/components/Logo.d.ts.map +1 -0
  35. package/dist/cli/components/Logo.js +8 -0
  36. package/dist/cli/components/Logo.js.map +1 -0
  37. package/dist/cli/components/Messages.d.ts +37 -0
  38. package/dist/cli/components/Messages.d.ts.map +1 -0
  39. package/dist/cli/components/Messages.js +106 -0
  40. package/dist/cli/components/Messages.js.map +1 -0
  41. package/dist/cli/components/ModelSelector.d.ts +13 -0
  42. package/dist/cli/components/ModelSelector.d.ts.map +1 -0
  43. package/dist/cli/components/ModelSelector.js +72 -0
  44. package/dist/cli/components/ModelSelector.js.map +1 -0
  45. package/dist/cli/components/Spinner.d.ts +12 -0
  46. package/dist/cli/components/Spinner.d.ts.map +1 -0
  47. package/dist/cli/components/Spinner.js +45 -0
  48. package/dist/cli/components/Spinner.js.map +1 -0
  49. package/dist/cli/components/index.d.ts +12 -0
  50. package/dist/cli/components/index.d.ts.map +1 -0
  51. package/dist/cli/components/index.js +12 -0
  52. package/dist/cli/components/index.js.map +1 -0
  53. package/dist/cli/components/theme.d.ts +31 -0
  54. package/dist/cli/components/theme.d.ts.map +1 -0
  55. package/dist/cli/components/theme.js +36 -0
  56. package/dist/cli/components/theme.js.map +1 -0
  57. package/dist/cli/index-legacy.d.ts +7 -0
  58. package/dist/cli/index-legacy.d.ts.map +1 -0
  59. package/dist/cli/index-legacy.js +431 -0
  60. package/dist/cli/index-legacy.js.map +1 -0
  61. package/dist/cli/index.d.ts +7 -0
  62. package/dist/cli/index.d.ts.map +1 -0
  63. package/dist/cli/index.js +116 -0
  64. package/dist/cli/index.js.map +1 -0
  65. package/dist/cli/ink-cli.d.ts +7 -0
  66. package/dist/cli/ink-cli.d.ts.map +1 -0
  67. package/dist/cli/ink-cli.js +105 -0
  68. package/dist/cli/ink-cli.js.map +1 -0
  69. package/dist/cli/session-picker.d.ts +16 -0
  70. package/dist/cli/session-picker.d.ts.map +1 -0
  71. package/dist/cli/session-picker.js +280 -0
  72. package/dist/cli/session-picker.js.map +1 -0
  73. package/dist/cli/ui.d.ts +61 -0
  74. package/dist/cli/ui.d.ts.map +1 -0
  75. package/dist/cli/ui.js +364 -0
  76. package/dist/cli/ui.js.map +1 -0
  77. package/dist/config/index.d.ts +7 -0
  78. package/dist/config/index.d.ts.map +1 -0
  79. package/dist/config/index.js +6 -0
  80. package/dist/config/index.js.map +1 -0
  81. package/dist/config/manager.d.ts +31 -0
  82. package/dist/config/manager.d.ts.map +1 -0
  83. package/dist/config/manager.js +65 -0
  84. package/dist/config/manager.js.map +1 -0
  85. package/dist/config/types.d.ts +22 -0
  86. package/dist/config/types.d.ts.map +1 -0
  87. package/dist/config/types.js +6 -0
  88. package/dist/config/types.js.map +1 -0
  89. package/dist/index.d.ts +12 -0
  90. package/dist/index.d.ts.map +1 -0
  91. package/dist/index.js +21 -0
  92. package/dist/index.js.map +1 -0
  93. package/dist/memory/index.d.ts +10 -0
  94. package/dist/memory/index.d.ts.map +1 -0
  95. package/dist/memory/index.js +9 -0
  96. package/dist/memory/index.js.map +1 -0
  97. package/dist/memory/init.d.ts +20 -0
  98. package/dist/memory/init.d.ts.map +1 -0
  99. package/dist/memory/init.js +332 -0
  100. package/dist/memory/init.js.map +1 -0
  101. package/dist/memory/manager.d.ts +85 -0
  102. package/dist/memory/manager.d.ts.map +1 -0
  103. package/dist/memory/manager.js +234 -0
  104. package/dist/memory/manager.js.map +1 -0
  105. package/dist/memory/types.d.ts +74 -0
  106. package/dist/memory/types.d.ts.map +1 -0
  107. package/dist/memory/types.js +6 -0
  108. package/dist/memory/types.js.map +1 -0
  109. package/dist/permissions/index.d.ts +7 -0
  110. package/dist/permissions/index.d.ts.map +1 -0
  111. package/dist/permissions/index.js +6 -0
  112. package/dist/permissions/index.js.map +1 -0
  113. package/dist/permissions/manager.d.ts +32 -0
  114. package/dist/permissions/manager.d.ts.map +1 -0
  115. package/dist/permissions/manager.js +79 -0
  116. package/dist/permissions/manager.js.map +1 -0
  117. package/dist/permissions/types.d.ts +14 -0
  118. package/dist/permissions/types.d.ts.map +1 -0
  119. package/dist/permissions/types.js +17 -0
  120. package/dist/permissions/types.js.map +1 -0
  121. package/dist/providers/anthropic.d.ts +20 -0
  122. package/dist/providers/anthropic.d.ts.map +1 -0
  123. package/dist/providers/anthropic.js +185 -0
  124. package/dist/providers/anthropic.js.map +1 -0
  125. package/dist/providers/gemini.d.ts +21 -0
  126. package/dist/providers/gemini.d.ts.map +1 -0
  127. package/dist/providers/gemini.js +241 -0
  128. package/dist/providers/gemini.js.map +1 -0
  129. package/dist/providers/index.d.ts +34 -0
  130. package/dist/providers/index.d.ts.map +1 -0
  131. package/dist/providers/index.js +72 -0
  132. package/dist/providers/index.js.map +1 -0
  133. package/dist/providers/openai.d.ts +19 -0
  134. package/dist/providers/openai.d.ts.map +1 -0
  135. package/dist/providers/openai.js +221 -0
  136. package/dist/providers/openai.js.map +1 -0
  137. package/dist/providers/types.d.ts +125 -0
  138. package/dist/providers/types.d.ts.map +1 -0
  139. package/dist/providers/types.js +6 -0
  140. package/dist/providers/types.js.map +1 -0
  141. package/dist/session/index.d.ts +6 -0
  142. package/dist/session/index.d.ts.map +1 -0
  143. package/dist/session/index.js +6 -0
  144. package/dist/session/index.js.map +1 -0
  145. package/dist/session/manager.d.ts +101 -0
  146. package/dist/session/manager.d.ts.map +1 -0
  147. package/dist/session/manager.js +295 -0
  148. package/dist/session/manager.js.map +1 -0
  149. package/dist/session/types.d.ts +39 -0
  150. package/dist/session/types.d.ts.map +1 -0
  151. package/dist/session/types.js +10 -0
  152. package/dist/session/types.js.map +1 -0
  153. package/dist/tools/builtin/bash.d.ts +7 -0
  154. package/dist/tools/builtin/bash.d.ts.map +1 -0
  155. package/dist/tools/builtin/bash.js +80 -0
  156. package/dist/tools/builtin/bash.js.map +1 -0
  157. package/dist/tools/builtin/edit.d.ts +7 -0
  158. package/dist/tools/builtin/edit.d.ts.map +1 -0
  159. package/dist/tools/builtin/edit.js +32 -0
  160. package/dist/tools/builtin/edit.js.map +1 -0
  161. package/dist/tools/builtin/glob.d.ts +7 -0
  162. package/dist/tools/builtin/glob.d.ts.map +1 -0
  163. package/dist/tools/builtin/glob.js +36 -0
  164. package/dist/tools/builtin/glob.js.map +1 -0
  165. package/dist/tools/builtin/grep.d.ts +7 -0
  166. package/dist/tools/builtin/grep.d.ts.map +1 -0
  167. package/dist/tools/builtin/grep.js +59 -0
  168. package/dist/tools/builtin/grep.js.map +1 -0
  169. package/dist/tools/builtin/read.d.ts +7 -0
  170. package/dist/tools/builtin/read.d.ts.map +1 -0
  171. package/dist/tools/builtin/read.js +29 -0
  172. package/dist/tools/builtin/read.js.map +1 -0
  173. package/dist/tools/builtin/write.d.ts +7 -0
  174. package/dist/tools/builtin/write.d.ts.map +1 -0
  175. package/dist/tools/builtin/write.js +24 -0
  176. package/dist/tools/builtin/write.js.map +1 -0
  177. package/dist/tools/index.d.ts +38 -0
  178. package/dist/tools/index.d.ts.map +1 -0
  179. package/dist/tools/index.js +32 -0
  180. package/dist/tools/index.js.map +1 -0
  181. package/dist/tools/registry.d.ts +22 -0
  182. package/dist/tools/registry.d.ts.map +1 -0
  183. package/dist/tools/registry.js +71 -0
  184. package/dist/tools/registry.js.map +1 -0
  185. package/dist/tools/types.d.ts +62 -0
  186. package/dist/tools/types.d.ts.map +1 -0
  187. package/dist/tools/types.js +126 -0
  188. package/dist/tools/types.js.map +1 -0
  189. package/docs/README.md +16 -0
  190. package/docs/proposals/0001-web-fetch-tool.md +293 -0
  191. package/docs/proposals/0002-web-search-tool.md +306 -0
  192. package/docs/proposals/0003-task-subagents.md +333 -0
  193. package/docs/proposals/0004-plan-mode.md +338 -0
  194. package/docs/proposals/0005-todo-system.md +299 -0
  195. package/docs/proposals/0006-memory-system.md +539 -0
  196. package/docs/proposals/0007-context-management.md +429 -0
  197. package/docs/proposals/0008-checkpointing.md +327 -0
  198. package/docs/proposals/0009-hooks-system.md +343 -0
  199. package/docs/proposals/0010-mcp-integration.md +382 -0
  200. package/docs/proposals/0011-custom-commands.md +374 -0
  201. package/docs/proposals/0012-ask-user-question.md +317 -0
  202. package/docs/proposals/0013-multi-edit-tool.md +345 -0
  203. package/docs/proposals/0014-lsp-tool.md +478 -0
  204. package/docs/proposals/0015-ls-tool.md +407 -0
  205. package/docs/proposals/0016-kill-shell-tool.md +455 -0
  206. package/docs/proposals/0017-background-tasks.md +489 -0
  207. package/docs/proposals/0018-parallel-tool-execution.md +415 -0
  208. package/docs/proposals/0019-session-enhancements.md +462 -0
  209. package/docs/proposals/0020-session-summarization.md +447 -0
  210. package/docs/proposals/0021-skills-system.md +409 -0
  211. package/docs/proposals/0022-plugin-system.md +467 -0
  212. package/docs/proposals/0023-permission-enhancements.md +470 -0
  213. package/docs/proposals/0024-keyboard-shortcuts.md +443 -0
  214. package/docs/proposals/0025-cost-tracking.md +447 -0
  215. package/docs/proposals/0026-git-integration.md +475 -0
  216. package/docs/proposals/0027-enhanced-read-tool.md +514 -0
  217. package/docs/proposals/0028-enhanced-bash-tool.md +511 -0
  218. package/docs/proposals/0029-notebook-edit-tool.md +413 -0
  219. package/docs/proposals/0030-plugin-marketplace.md +360 -0
  220. package/docs/proposals/0031-command-suggestions.md +295 -0
  221. package/docs/proposals/0032-ide-integrations.md +328 -0
  222. package/docs/proposals/0033-enterprise-deployment.md +221 -0
  223. package/docs/proposals/0034-sandboxing.md +273 -0
  224. package/docs/proposals/0035-auto-updater.md +311 -0
  225. package/docs/proposals/0036-enhanced-glob-tool.md +267 -0
  226. package/docs/proposals/0037-enhanced-grep-tool.md +360 -0
  227. package/docs/proposals/0038-interactive-cli-ui.md +373 -0
  228. package/docs/proposals/0039-streaming-enhancements.md +359 -0
  229. package/docs/proposals/0040-multi-provider-enhancements.md +369 -0
  230. package/docs/proposals/README.md +84 -0
  231. package/docs/proposals/TEMPLATE.md +57 -0
  232. package/docs/proposals/research/claude-code-research.md +307 -0
  233. package/examples/agent-demo.ts +115 -0
  234. package/examples/basic.ts +166 -0
  235. package/package.json +50 -0
  236. package/src/agent/agent.ts +276 -0
  237. package/src/agent/index.ts +6 -0
  238. package/src/agent/types.ts +62 -0
  239. package/src/cli/components/App.tsx +565 -0
  240. package/src/cli/components/CommandSuggestions.tsx +58 -0
  241. package/src/cli/components/Header.tsx +36 -0
  242. package/src/cli/components/Input.tsx +60 -0
  243. package/src/cli/components/Logo.tsx +16 -0
  244. package/src/cli/components/Messages.tsx +210 -0
  245. package/src/cli/components/ModelSelector.tsx +135 -0
  246. package/src/cli/components/Spinner.tsx +72 -0
  247. package/src/cli/components/index.ts +21 -0
  248. package/src/cli/components/theme.ts +36 -0
  249. package/src/cli/index.tsx +136 -0
  250. package/src/config/index.ts +7 -0
  251. package/src/config/manager.ts +77 -0
  252. package/src/config/types.ts +25 -0
  253. package/src/index.ts +86 -0
  254. package/src/permissions/index.ts +7 -0
  255. package/src/permissions/manager.ts +97 -0
  256. package/src/permissions/types.ts +29 -0
  257. package/src/providers/anthropic.ts +224 -0
  258. package/src/providers/gemini.ts +295 -0
  259. package/src/providers/index.ts +97 -0
  260. package/src/providers/openai.ts +261 -0
  261. package/src/providers/types.ts +181 -0
  262. package/src/session/index.ts +6 -0
  263. package/src/session/manager.ts +354 -0
  264. package/src/session/types.ts +49 -0
  265. package/src/tools/builtin/bash.ts +92 -0
  266. package/src/tools/builtin/edit.ts +37 -0
  267. package/src/tools/builtin/glob.ts +42 -0
  268. package/src/tools/builtin/grep.ts +67 -0
  269. package/src/tools/builtin/read.ts +34 -0
  270. package/src/tools/builtin/write.ts +27 -0
  271. package/src/tools/index.ts +36 -0
  272. package/src/tools/registry.ts +83 -0
  273. package/src/tools/types.ts +172 -0
  274. package/tsconfig.json +21 -0
@@ -0,0 +1,295 @@
1
+ /**
2
+ * Google Gemini Provider Implementation
3
+ * Supports Gemini 1.5 Pro, Gemini 1.5 Flash, Gemini 2.0, etc.
4
+ */
5
+
6
+ import { GoogleGenerativeAI, SchemaType } from '@google/generative-ai';
7
+ import type { Content, Part, Tool, GenerateContentResult } from '@google/generative-ai';
8
+ import type {
9
+ LLMProvider,
10
+ CompletionOptions,
11
+ CompletionResponse,
12
+ StreamChunk,
13
+ Message,
14
+ MessageContent,
15
+ ToolDefinition,
16
+ StopReason,
17
+ GeminiConfig,
18
+ JSONSchema,
19
+ ModelInfo,
20
+ } from './types.js';
21
+
22
+ export class GeminiProvider implements LLMProvider {
23
+ readonly name = 'gemini';
24
+ private client: GoogleGenerativeAI;
25
+ private apiKey: string;
26
+
27
+ constructor(config: GeminiConfig = {}) {
28
+ const apiKey = config.apiKey ?? process.env.GOOGLE_API_KEY ?? process.env.GEMINI_API_KEY;
29
+ if (!apiKey) {
30
+ throw new Error('Gemini API key is required. Set GOOGLE_API_KEY or GEMINI_API_KEY.');
31
+ }
32
+ this.apiKey = apiKey;
33
+ this.client = new GoogleGenerativeAI(apiKey);
34
+ }
35
+
36
+ async complete(options: CompletionOptions): Promise<CompletionResponse> {
37
+ const model = this.client.getGenerativeModel({
38
+ model: options.model,
39
+ systemInstruction: options.systemPrompt,
40
+ generationConfig: {
41
+ maxOutputTokens: options.maxTokens,
42
+ temperature: options.temperature,
43
+ },
44
+ tools: options.tools ? this.convertTools(options.tools) : undefined,
45
+ });
46
+
47
+ const contents = this.convertMessages(options.messages);
48
+ const result = await model.generateContent({ contents });
49
+
50
+ return this.convertResponse(result);
51
+ }
52
+
53
+ async *stream(options: CompletionOptions): AsyncGenerator<StreamChunk, void, unknown> {
54
+ const model = this.client.getGenerativeModel({
55
+ model: options.model,
56
+ systemInstruction: options.systemPrompt,
57
+ generationConfig: {
58
+ maxOutputTokens: options.maxTokens,
59
+ temperature: options.temperature,
60
+ },
61
+ tools: options.tools ? this.convertTools(options.tools) : undefined,
62
+ });
63
+
64
+ const contents = this.convertMessages(options.messages);
65
+ const result = await model.generateContentStream({ contents });
66
+
67
+ let textContent = '';
68
+ const functionCalls: Array<{ id: string; name: string; args: Record<string, unknown> }> = [];
69
+ let callIndex = 0;
70
+
71
+ for await (const chunk of result.stream) {
72
+ const parts = chunk.candidates?.[0]?.content?.parts ?? [];
73
+
74
+ for (const part of parts) {
75
+ if ('text' in part && part.text) {
76
+ textContent += part.text;
77
+ yield { type: 'text', text: part.text };
78
+ } else if ('functionCall' in part && part.functionCall) {
79
+ const fc = part.functionCall;
80
+ const id = `call_${callIndex++}`;
81
+ functionCalls.push({
82
+ id,
83
+ name: fc.name,
84
+ args: (fc.args as Record<string, unknown>) ?? {},
85
+ });
86
+ yield { type: 'tool_start', id, name: fc.name };
87
+ yield { type: 'tool_input', id, input: JSON.stringify(fc.args) };
88
+ }
89
+ }
90
+ }
91
+
92
+ // Build final response
93
+ const content: MessageContent[] = [];
94
+ if (textContent) {
95
+ content.push({ type: 'text', text: textContent });
96
+ }
97
+ for (const fc of functionCalls) {
98
+ content.push({
99
+ type: 'tool_use',
100
+ id: fc.id,
101
+ name: fc.name,
102
+ input: fc.args,
103
+ });
104
+ }
105
+
106
+ const finalResponse = await result.response;
107
+ const stopReason = this.getStopReason(finalResponse, functionCalls.length > 0);
108
+
109
+ yield {
110
+ type: 'done',
111
+ response: {
112
+ content,
113
+ stopReason,
114
+ usage: finalResponse.usageMetadata
115
+ ? {
116
+ inputTokens: finalResponse.usageMetadata.promptTokenCount ?? 0,
117
+ outputTokens: finalResponse.usageMetadata.candidatesTokenCount ?? 0,
118
+ }
119
+ : undefined,
120
+ },
121
+ };
122
+ }
123
+
124
+ private convertMessages(messages: Message[]): Content[] {
125
+ const contents: Content[] = [];
126
+
127
+ for (const msg of messages) {
128
+ // Skip system messages - handled via systemInstruction
129
+ if (msg.role === 'system') {
130
+ continue;
131
+ }
132
+
133
+ const role = msg.role === 'assistant' ? 'model' : 'user';
134
+ const parts = this.convertToParts(msg.content, role);
135
+
136
+ if (parts.length > 0) {
137
+ contents.push({ role, parts });
138
+ }
139
+ }
140
+
141
+ return contents;
142
+ }
143
+
144
+ private convertToParts(content: string | MessageContent[], role: string): Part[] {
145
+ if (typeof content === 'string') {
146
+ return [{ text: content }];
147
+ }
148
+
149
+ const parts: Part[] = [];
150
+
151
+ for (const item of content) {
152
+ if (item.type === 'text') {
153
+ parts.push({ text: item.text });
154
+ } else if (item.type === 'tool_use' && role === 'model') {
155
+ // Function call from model
156
+ parts.push({
157
+ functionCall: {
158
+ name: item.name,
159
+ args: item.input,
160
+ },
161
+ });
162
+ } else if (item.type === 'tool_result' && role === 'user') {
163
+ // Function response
164
+ parts.push({
165
+ functionResponse: {
166
+ name: item.toolUseId, // Use toolUseId as name reference
167
+ response: { result: item.content },
168
+ },
169
+ });
170
+ }
171
+ }
172
+
173
+ return parts;
174
+ }
175
+
176
+ private convertTools(tools: ToolDefinition[]): Tool[] {
177
+ return [
178
+ {
179
+ functionDeclarations: tools.map((tool) => ({
180
+ name: tool.name,
181
+ description: tool.description,
182
+ parameters: this.convertSchema(tool.parameters),
183
+ })),
184
+ },
185
+ ];
186
+ }
187
+
188
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
189
+ private convertSchema(schema: JSONSchema): any {
190
+ const convertType = (type: string): SchemaType => {
191
+ switch (type) {
192
+ case 'string':
193
+ return SchemaType.STRING;
194
+ case 'number':
195
+ case 'integer':
196
+ return SchemaType.NUMBER;
197
+ case 'boolean':
198
+ return SchemaType.BOOLEAN;
199
+ case 'array':
200
+ return SchemaType.ARRAY;
201
+ case 'object':
202
+ default:
203
+ return SchemaType.OBJECT;
204
+ }
205
+ };
206
+
207
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
208
+ const result: any = {
209
+ type: convertType(schema.type),
210
+ description: schema.description,
211
+ };
212
+
213
+ if (schema.properties) {
214
+ result.properties = {};
215
+ for (const [key, value] of Object.entries(schema.properties)) {
216
+ result.properties[key] = this.convertSchema(value);
217
+ }
218
+ }
219
+
220
+ if (schema.required) {
221
+ result.required = schema.required;
222
+ }
223
+
224
+ return result;
225
+ }
226
+
227
+ private convertResponse(result: GenerateContentResult): CompletionResponse {
228
+ const response = result.response;
229
+ const parts = response.candidates?.[0]?.content?.parts ?? [];
230
+ const content: MessageContent[] = [];
231
+ let callIndex = 0;
232
+
233
+ for (const part of parts) {
234
+ if ('text' in part && part.text) {
235
+ content.push({ type: 'text', text: part.text });
236
+ } else if ('functionCall' in part && part.functionCall) {
237
+ content.push({
238
+ type: 'tool_use',
239
+ id: `call_${callIndex++}`,
240
+ name: part.functionCall.name,
241
+ input: (part.functionCall.args as Record<string, unknown>) ?? {},
242
+ });
243
+ }
244
+ }
245
+
246
+ const hasFunctionCalls = parts.some((p) => 'functionCall' in p);
247
+
248
+ return {
249
+ content,
250
+ stopReason: this.getStopReason(response, hasFunctionCalls),
251
+ usage: response.usageMetadata
252
+ ? {
253
+ inputTokens: response.usageMetadata.promptTokenCount ?? 0,
254
+ outputTokens: response.usageMetadata.candidatesTokenCount ?? 0,
255
+ }
256
+ : undefined,
257
+ };
258
+ }
259
+
260
+ private getStopReason(response: GenerateContentResult['response'], hasFunctionCalls: boolean): StopReason {
261
+ if (hasFunctionCalls) {
262
+ return 'tool_use';
263
+ }
264
+
265
+ const finishReason = response.candidates?.[0]?.finishReason;
266
+ switch (finishReason) {
267
+ case 'MAX_TOKENS':
268
+ return 'max_tokens';
269
+ case 'STOP':
270
+ default:
271
+ return 'end_turn';
272
+ }
273
+ }
274
+
275
+ async listModels(): Promise<ModelInfo[]> {
276
+ const response = await fetch(
277
+ `https://generativelanguage.googleapis.com/v1beta/models?key=${this.apiKey}`
278
+ );
279
+ const data = (await response.json()) as {
280
+ models?: Array<{
281
+ name: string;
282
+ displayName: string;
283
+ description?: string;
284
+ supportedGenerationMethods?: string[];
285
+ }>;
286
+ };
287
+ return (data.models || [])
288
+ .filter((m) => m.supportedGenerationMethods?.includes('generateContent'))
289
+ .map((m) => ({
290
+ id: m.name.replace('models/', ''),
291
+ name: m.displayName,
292
+ description: m.description,
293
+ }));
294
+ }
295
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * LLM Providers - Unified interface for OpenAI, Anthropic, and Gemini
3
+ */
4
+
5
+ export * from './types.js';
6
+ export { OpenAIProvider } from './openai.js';
7
+ export { AnthropicProvider } from './anthropic.js';
8
+ export { GeminiProvider } from './gemini.js';
9
+
10
+ import type { LLMProvider, OpenAIConfig, AnthropicConfig, GeminiConfig } from './types.js';
11
+ import { OpenAIProvider } from './openai.js';
12
+ import { AnthropicProvider } from './anthropic.js';
13
+ import { GeminiProvider } from './gemini.js';
14
+
15
+ export type ProviderName = 'openai' | 'anthropic' | 'gemini';
16
+ export type ProviderConfigMap = {
17
+ openai: OpenAIConfig;
18
+ anthropic: AnthropicConfig;
19
+ gemini: GeminiConfig;
20
+ };
21
+
22
+ export interface CreateProviderOptions<T extends ProviderName = ProviderName> {
23
+ provider: T;
24
+ config?: ProviderConfigMap[T];
25
+ }
26
+
27
+ /**
28
+ * Create a provider instance by name
29
+ */
30
+ export function createProvider(options: CreateProviderOptions): LLMProvider {
31
+ switch (options.provider) {
32
+ case 'openai':
33
+ return new OpenAIProvider(options.config as OpenAIConfig);
34
+ case 'anthropic':
35
+ return new AnthropicProvider(options.config as AnthropicConfig);
36
+ case 'gemini':
37
+ return new GeminiProvider(options.config as GeminiConfig);
38
+ default:
39
+ throw new Error(`Unknown provider: ${options.provider}`);
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Infer provider from model name
45
+ */
46
+ export function inferProvider(model: string): ProviderName {
47
+ const modelLower = model.toLowerCase();
48
+
49
+ // OpenAI models
50
+ if (
51
+ modelLower.includes('gpt') ||
52
+ modelLower.includes('o1') ||
53
+ modelLower.includes('o3') ||
54
+ modelLower.startsWith('text-') ||
55
+ modelLower.startsWith('davinci') ||
56
+ modelLower.startsWith('curie')
57
+ ) {
58
+ return 'openai';
59
+ }
60
+
61
+ // Anthropic models
62
+ if (modelLower.includes('claude')) {
63
+ return 'anthropic';
64
+ }
65
+
66
+ // Gemini models
67
+ if (modelLower.includes('gemini') || modelLower.includes('palm')) {
68
+ return 'gemini';
69
+ }
70
+
71
+ // Default to OpenAI (most common)
72
+ return 'openai';
73
+ }
74
+
75
+ /**
76
+ * Common model aliases
77
+ */
78
+ export const ModelAliases: Record<string, { provider: ProviderName; model: string }> = {
79
+ // OpenAI
80
+ 'gpt-4o': { provider: 'openai', model: 'gpt-4o' },
81
+ 'gpt-4o-mini': { provider: 'openai', model: 'gpt-4o-mini' },
82
+ 'gpt-4-turbo': { provider: 'openai', model: 'gpt-4-turbo' },
83
+ 'o1': { provider: 'openai', model: 'o1' },
84
+ 'o1-mini': { provider: 'openai', model: 'o1-mini' },
85
+ 'o3-mini': { provider: 'openai', model: 'o3-mini' },
86
+
87
+ // Anthropic
88
+ 'claude-opus': { provider: 'anthropic', model: 'claude-opus-4-5-20251101' },
89
+ 'claude-sonnet': { provider: 'anthropic', model: 'claude-sonnet-4-20250514' },
90
+ 'claude-haiku': { provider: 'anthropic', model: 'claude-haiku-4-20250514' },
91
+ 'claude-3.5-sonnet': { provider: 'anthropic', model: 'claude-3-5-sonnet-20241022' },
92
+
93
+ // Gemini
94
+ 'gemini-2.0-flash': { provider: 'gemini', model: 'gemini-2.0-flash' },
95
+ 'gemini-1.5-pro': { provider: 'gemini', model: 'gemini-1.5-pro' },
96
+ 'gemini-1.5-flash': { provider: 'gemini', model: 'gemini-1.5-flash' },
97
+ };
@@ -0,0 +1,261 @@
1
+ /**
2
+ * OpenAI Provider Implementation
3
+ * Supports GPT-4, GPT-4o, GPT-3.5-turbo, and other OpenAI models
4
+ */
5
+
6
+ import OpenAI from 'openai';
7
+ import type {
8
+ LLMProvider,
9
+ CompletionOptions,
10
+ CompletionResponse,
11
+ StreamChunk,
12
+ Message,
13
+ MessageContent,
14
+ ToolDefinition,
15
+ StopReason,
16
+ OpenAIConfig,
17
+ ModelInfo,
18
+ } from './types.js';
19
+
20
+ type OpenAIMessage = OpenAI.Chat.Completions.ChatCompletionMessageParam;
21
+ type OpenAITool = OpenAI.Chat.Completions.ChatCompletionTool;
22
+
23
+ export class OpenAIProvider implements LLMProvider {
24
+ readonly name = 'openai';
25
+ private client: OpenAI;
26
+
27
+ constructor(config: OpenAIConfig = {}) {
28
+ this.client = new OpenAI({
29
+ apiKey: config.apiKey ?? process.env.OPENAI_API_KEY,
30
+ baseURL: config.baseURL,
31
+ organization: config.organization,
32
+ });
33
+ }
34
+
35
+ async complete(options: CompletionOptions): Promise<CompletionResponse> {
36
+ const messages = this.convertMessages(options.messages, options.systemPrompt);
37
+ const tools = options.tools ? this.convertTools(options.tools) : undefined;
38
+
39
+ const response = await this.client.chat.completions.create({
40
+ model: options.model,
41
+ messages,
42
+ tools,
43
+ max_tokens: options.maxTokens,
44
+ temperature: options.temperature,
45
+ });
46
+
47
+ return this.convertResponse(response);
48
+ }
49
+
50
+ async *stream(options: CompletionOptions): AsyncGenerator<StreamChunk, void, unknown> {
51
+ const messages = this.convertMessages(options.messages, options.systemPrompt);
52
+ const tools = options.tools ? this.convertTools(options.tools) : undefined;
53
+
54
+ const stream = await this.client.chat.completions.create({
55
+ model: options.model,
56
+ messages,
57
+ tools,
58
+ max_tokens: options.maxTokens,
59
+ temperature: options.temperature,
60
+ stream: true,
61
+ });
62
+
63
+ const toolCalls: Map<number, { id: string; name: string; arguments: string }> = new Map();
64
+ let textContent = '';
65
+ let finishReason: string | null = null;
66
+
67
+ for await (const chunk of stream) {
68
+ const delta = chunk.choices[0]?.delta;
69
+ finishReason = chunk.choices[0]?.finish_reason ?? finishReason;
70
+
71
+ if (delta?.content) {
72
+ textContent += delta.content;
73
+ yield { type: 'text', text: delta.content };
74
+ }
75
+
76
+ if (delta?.tool_calls) {
77
+ for (const tc of delta.tool_calls) {
78
+ const existing = toolCalls.get(tc.index);
79
+ if (existing) {
80
+ if (tc.function?.arguments) {
81
+ existing.arguments += tc.function.arguments;
82
+ yield { type: 'tool_input', id: existing.id, input: tc.function.arguments };
83
+ }
84
+ } else if (tc.id && tc.function?.name) {
85
+ toolCalls.set(tc.index, {
86
+ id: tc.id,
87
+ name: tc.function.name,
88
+ arguments: tc.function.arguments ?? '',
89
+ });
90
+ yield { type: 'tool_start', id: tc.id, name: tc.function.name };
91
+ if (tc.function.arguments) {
92
+ yield { type: 'tool_input', id: tc.id, input: tc.function.arguments };
93
+ }
94
+ }
95
+ }
96
+ }
97
+ }
98
+
99
+ // Build final response
100
+ const content: MessageContent[] = [];
101
+ if (textContent) {
102
+ content.push({ type: 'text', text: textContent });
103
+ }
104
+ for (const tc of toolCalls.values()) {
105
+ content.push({
106
+ type: 'tool_use',
107
+ id: tc.id,
108
+ name: tc.name,
109
+ input: JSON.parse(tc.arguments || '{}'),
110
+ });
111
+ }
112
+
113
+ yield {
114
+ type: 'done',
115
+ response: {
116
+ content,
117
+ stopReason: this.convertStopReason(finishReason),
118
+ },
119
+ };
120
+ }
121
+
122
+ private convertMessages(messages: Message[], systemPrompt?: string): OpenAIMessage[] {
123
+ const result: OpenAIMessage[] = [];
124
+
125
+ if (systemPrompt) {
126
+ result.push({ role: 'system', content: systemPrompt });
127
+ }
128
+
129
+ for (const msg of messages) {
130
+ if (msg.role === 'system') {
131
+ result.push({ role: 'system', content: this.getTextContent(msg.content) });
132
+ } else if (msg.role === 'user') {
133
+ // Check if message contains tool results
134
+ if (Array.isArray(msg.content)) {
135
+ const toolResults = msg.content.filter((c) => c.type === 'tool_result');
136
+ if (toolResults.length > 0) {
137
+ for (const tr of toolResults) {
138
+ if (tr.type === 'tool_result') {
139
+ result.push({
140
+ role: 'tool',
141
+ tool_call_id: tr.toolUseId,
142
+ content: tr.content,
143
+ });
144
+ }
145
+ }
146
+ continue;
147
+ }
148
+ }
149
+ result.push({ role: 'user', content: this.getTextContent(msg.content) });
150
+ } else if (msg.role === 'assistant') {
151
+ const assistantMsg: OpenAI.Chat.Completions.ChatCompletionAssistantMessageParam = {
152
+ role: 'assistant',
153
+ };
154
+
155
+ if (Array.isArray(msg.content)) {
156
+ const textParts = msg.content.filter((c) => c.type === 'text');
157
+ const toolUses = msg.content.filter((c) => c.type === 'tool_use');
158
+
159
+ if (textParts.length > 0) {
160
+ assistantMsg.content = textParts.map((t) => (t as { text: string }).text).join('');
161
+ }
162
+
163
+ if (toolUses.length > 0) {
164
+ assistantMsg.tool_calls = toolUses.map((t) => {
165
+ const tu = t as { id: string; name: string; input: Record<string, unknown> };
166
+ return {
167
+ id: tu.id,
168
+ type: 'function' as const,
169
+ function: {
170
+ name: tu.name,
171
+ arguments: JSON.stringify(tu.input),
172
+ },
173
+ };
174
+ });
175
+ }
176
+ } else {
177
+ assistantMsg.content = msg.content;
178
+ }
179
+
180
+ result.push(assistantMsg);
181
+ }
182
+ }
183
+
184
+ return result;
185
+ }
186
+
187
+ private convertTools(tools: ToolDefinition[]): OpenAITool[] {
188
+ return tools.map((tool) => ({
189
+ type: 'function' as const,
190
+ function: {
191
+ name: tool.name,
192
+ description: tool.description,
193
+ parameters: tool.parameters,
194
+ },
195
+ }));
196
+ }
197
+
198
+ private convertResponse(response: OpenAI.Chat.Completions.ChatCompletion): CompletionResponse {
199
+ const choice = response.choices[0];
200
+ const content: MessageContent[] = [];
201
+
202
+ if (choice.message.content) {
203
+ content.push({ type: 'text', text: choice.message.content });
204
+ }
205
+
206
+ if (choice.message.tool_calls) {
207
+ for (const tc of choice.message.tool_calls) {
208
+ if (tc.type === 'function' && tc.function) {
209
+ content.push({
210
+ type: 'tool_use',
211
+ id: tc.id,
212
+ name: tc.function.name,
213
+ input: JSON.parse(tc.function.arguments || '{}'),
214
+ });
215
+ }
216
+ }
217
+ }
218
+
219
+ return {
220
+ content,
221
+ stopReason: this.convertStopReason(choice.finish_reason),
222
+ usage: response.usage
223
+ ? {
224
+ inputTokens: response.usage.prompt_tokens,
225
+ outputTokens: response.usage.completion_tokens,
226
+ }
227
+ : undefined,
228
+ };
229
+ }
230
+
231
+ private convertStopReason(reason: string | null): StopReason {
232
+ switch (reason) {
233
+ case 'tool_calls':
234
+ return 'tool_use';
235
+ case 'length':
236
+ return 'max_tokens';
237
+ case 'stop':
238
+ default:
239
+ return 'end_turn';
240
+ }
241
+ }
242
+
243
+ private getTextContent(content: string | MessageContent[]): string {
244
+ if (typeof content === 'string') {
245
+ return content;
246
+ }
247
+ return content
248
+ .filter((c) => c.type === 'text')
249
+ .map((c) => (c as { text: string }).text)
250
+ .join('');
251
+ }
252
+
253
+ async listModels(): Promise<ModelInfo[]> {
254
+ const list = await this.client.models.list();
255
+ const models: ModelInfo[] = [];
256
+ for await (const model of list) {
257
+ models.push({ id: model.id, name: model.id });
258
+ }
259
+ return models.sort((a, b) => a.id.localeCompare(b.id));
260
+ }
261
+ }