@tpitre/story-ui 2.2.0 → 2.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 (188) hide show
  1. package/.env.sample +82 -11
  2. package/README.md +89 -0
  3. package/dist/cli/deploy.d.ts +17 -0
  4. package/dist/cli/deploy.d.ts.map +1 -0
  5. package/dist/cli/deploy.js +696 -0
  6. package/dist/cli/index.d.ts +3 -0
  7. package/dist/cli/index.d.ts.map +1 -0
  8. package/dist/cli/index.js +26 -2
  9. package/dist/cli/setup.d.ts +11 -0
  10. package/dist/cli/setup.d.ts.map +1 -0
  11. package/dist/cli/setup.js +437 -110
  12. package/dist/mcp-server/index.d.ts +2 -0
  13. package/dist/mcp-server/index.d.ts.map +1 -0
  14. package/dist/mcp-server/index.js +120 -2
  15. package/dist/mcp-server/mcp-stdio-server.d.ts +3 -0
  16. package/dist/mcp-server/mcp-stdio-server.d.ts.map +1 -0
  17. package/dist/mcp-server/mcp-stdio-server.js +8 -1
  18. package/dist/mcp-server/routes/claude.d.ts +3 -0
  19. package/dist/mcp-server/routes/claude.d.ts.map +1 -0
  20. package/dist/mcp-server/routes/claude.js +60 -23
  21. package/dist/mcp-server/routes/components.d.ts +4 -0
  22. package/dist/mcp-server/routes/components.d.ts.map +1 -0
  23. package/dist/mcp-server/routes/frameworks.d.ts +38 -0
  24. package/dist/mcp-server/routes/frameworks.d.ts.map +1 -0
  25. package/dist/mcp-server/routes/frameworks.js +183 -0
  26. package/dist/mcp-server/routes/generateStory.d.ts +3 -0
  27. package/dist/mcp-server/routes/generateStory.d.ts.map +1 -0
  28. package/dist/mcp-server/routes/generateStory.js +160 -76
  29. package/dist/mcp-server/routes/generateStoryStream.d.ts +12 -0
  30. package/dist/mcp-server/routes/generateStoryStream.d.ts.map +1 -0
  31. package/dist/mcp-server/routes/generateStoryStream.js +947 -0
  32. package/dist/mcp-server/routes/hybridStories.d.ts +18 -0
  33. package/dist/mcp-server/routes/hybridStories.d.ts.map +1 -0
  34. package/dist/mcp-server/routes/mcpRemote.d.ts +14 -0
  35. package/dist/mcp-server/routes/mcpRemote.d.ts.map +1 -0
  36. package/dist/mcp-server/routes/mcpRemote.js +489 -0
  37. package/dist/mcp-server/routes/memoryStories.d.ts +26 -0
  38. package/dist/mcp-server/routes/memoryStories.d.ts.map +1 -0
  39. package/dist/mcp-server/routes/providers.d.ts +89 -0
  40. package/dist/mcp-server/routes/providers.d.ts.map +1 -0
  41. package/dist/mcp-server/routes/providers.js +369 -0
  42. package/dist/mcp-server/routes/storySync.d.ts +26 -0
  43. package/dist/mcp-server/routes/storySync.d.ts.map +1 -0
  44. package/dist/mcp-server/routes/streamTypes.d.ts +110 -0
  45. package/dist/mcp-server/routes/streamTypes.d.ts.map +1 -0
  46. package/dist/mcp-server/routes/streamTypes.js +18 -0
  47. package/dist/mcp-server/sessionManager.d.ts +50 -0
  48. package/dist/mcp-server/sessionManager.d.ts.map +1 -0
  49. package/dist/story-generator/componentBlacklist.d.ts +21 -0
  50. package/dist/story-generator/componentBlacklist.d.ts.map +1 -0
  51. package/dist/story-generator/componentDiscovery.d.ts +28 -0
  52. package/dist/story-generator/componentDiscovery.d.ts.map +1 -0
  53. package/dist/story-generator/componentRegistryGenerator.d.ts +49 -0
  54. package/dist/story-generator/componentRegistryGenerator.d.ts.map +1 -0
  55. package/dist/story-generator/componentRegistryGenerator.js +205 -0
  56. package/dist/story-generator/configLoader.d.ts +33 -0
  57. package/dist/story-generator/configLoader.d.ts.map +1 -0
  58. package/dist/story-generator/considerationsLoader.d.ts +32 -0
  59. package/dist/story-generator/considerationsLoader.d.ts.map +1 -0
  60. package/dist/story-generator/documentation-sources.d.ts +28 -0
  61. package/dist/story-generator/documentation-sources.d.ts.map +1 -0
  62. package/dist/story-generator/documentationLoader.d.ts +64 -0
  63. package/dist/story-generator/documentationLoader.d.ts.map +1 -0
  64. package/dist/story-generator/dynamicPackageDiscovery.d.ts +97 -0
  65. package/dist/story-generator/dynamicPackageDiscovery.d.ts.map +1 -0
  66. package/dist/story-generator/enhancedComponentDiscovery.d.ts +125 -0
  67. package/dist/story-generator/enhancedComponentDiscovery.d.ts.map +1 -0
  68. package/dist/story-generator/enhancedComponentDiscovery.js +111 -11
  69. package/dist/story-generator/framework-adapters/angular-adapter.d.ts +40 -0
  70. package/dist/story-generator/framework-adapters/angular-adapter.d.ts.map +1 -0
  71. package/dist/story-generator/framework-adapters/angular-adapter.js +427 -0
  72. package/dist/story-generator/framework-adapters/base-adapter.d.ts +75 -0
  73. package/dist/story-generator/framework-adapters/base-adapter.d.ts.map +1 -0
  74. package/dist/story-generator/framework-adapters/base-adapter.js +147 -0
  75. package/dist/story-generator/framework-adapters/framework-detector.d.ts +55 -0
  76. package/dist/story-generator/framework-adapters/framework-detector.d.ts.map +1 -0
  77. package/dist/story-generator/framework-adapters/framework-detector.js +323 -0
  78. package/dist/story-generator/framework-adapters/index.d.ts +97 -0
  79. package/dist/story-generator/framework-adapters/index.d.ts.map +1 -0
  80. package/dist/story-generator/framework-adapters/index.js +198 -0
  81. package/dist/story-generator/framework-adapters/react-adapter.d.ts +40 -0
  82. package/dist/story-generator/framework-adapters/react-adapter.d.ts.map +1 -0
  83. package/dist/story-generator/framework-adapters/react-adapter.js +316 -0
  84. package/dist/story-generator/framework-adapters/svelte-adapter.d.ts +40 -0
  85. package/dist/story-generator/framework-adapters/svelte-adapter.d.ts.map +1 -0
  86. package/dist/story-generator/framework-adapters/svelte-adapter.js +372 -0
  87. package/dist/story-generator/framework-adapters/types.d.ts +182 -0
  88. package/dist/story-generator/framework-adapters/types.d.ts.map +1 -0
  89. package/dist/story-generator/framework-adapters/types.js +8 -0
  90. package/dist/story-generator/framework-adapters/vue-adapter.d.ts +36 -0
  91. package/dist/story-generator/framework-adapters/vue-adapter.d.ts.map +1 -0
  92. package/dist/story-generator/framework-adapters/vue-adapter.js +336 -0
  93. package/dist/story-generator/framework-adapters/web-components-adapter.d.ts +54 -0
  94. package/dist/story-generator/framework-adapters/web-components-adapter.d.ts.map +1 -0
  95. package/dist/story-generator/framework-adapters/web-components-adapter.js +387 -0
  96. package/dist/story-generator/generateStory.d.ts +7 -0
  97. package/dist/story-generator/generateStory.d.ts.map +1 -0
  98. package/dist/story-generator/gitignoreManager.d.ts +50 -0
  99. package/dist/story-generator/gitignoreManager.d.ts.map +1 -0
  100. package/dist/story-generator/imageProcessor.d.ts +80 -0
  101. package/dist/story-generator/imageProcessor.d.ts.map +1 -0
  102. package/dist/story-generator/imageProcessor.js +391 -0
  103. package/dist/story-generator/inMemoryStoryService.d.ts +89 -0
  104. package/dist/story-generator/inMemoryStoryService.d.ts.map +1 -0
  105. package/dist/story-generator/llm-providers/base-provider.d.ts +36 -0
  106. package/dist/story-generator/llm-providers/base-provider.d.ts.map +1 -0
  107. package/dist/story-generator/llm-providers/base-provider.js +135 -0
  108. package/dist/story-generator/llm-providers/claude-provider.d.ts +23 -0
  109. package/dist/story-generator/llm-providers/claude-provider.d.ts.map +1 -0
  110. package/dist/story-generator/llm-providers/claude-provider.js +414 -0
  111. package/dist/story-generator/llm-providers/gemini-provider.d.ts +24 -0
  112. package/dist/story-generator/llm-providers/gemini-provider.d.ts.map +1 -0
  113. package/dist/story-generator/llm-providers/gemini-provider.js +406 -0
  114. package/dist/story-generator/llm-providers/index.d.ts +63 -0
  115. package/dist/story-generator/llm-providers/index.d.ts.map +1 -0
  116. package/dist/story-generator/llm-providers/index.js +169 -0
  117. package/dist/story-generator/llm-providers/openai-provider.d.ts +24 -0
  118. package/dist/story-generator/llm-providers/openai-provider.d.ts.map +1 -0
  119. package/dist/story-generator/llm-providers/openai-provider.js +458 -0
  120. package/dist/story-generator/llm-providers/settings-manager.d.ts +75 -0
  121. package/dist/story-generator/llm-providers/settings-manager.d.ts.map +1 -0
  122. package/dist/story-generator/llm-providers/settings-manager.js +173 -0
  123. package/dist/story-generator/llm-providers/story-llm-service.d.ts +79 -0
  124. package/dist/story-generator/llm-providers/story-llm-service.d.ts.map +1 -0
  125. package/dist/story-generator/llm-providers/story-llm-service.js +240 -0
  126. package/dist/story-generator/llm-providers/types.d.ts +153 -0
  127. package/dist/story-generator/llm-providers/types.d.ts.map +1 -0
  128. package/dist/story-generator/llm-providers/types.js +8 -0
  129. package/dist/story-generator/logger.d.ts +14 -0
  130. package/dist/story-generator/logger.d.ts.map +1 -0
  131. package/dist/story-generator/logger.js +96 -29
  132. package/dist/story-generator/postProcessStory.d.ts +6 -0
  133. package/dist/story-generator/postProcessStory.d.ts.map +1 -0
  134. package/dist/story-generator/productionGitignoreManager.d.ts +91 -0
  135. package/dist/story-generator/productionGitignoreManager.d.ts.map +1 -0
  136. package/dist/story-generator/promptGenerator.d.ts +48 -0
  137. package/dist/story-generator/promptGenerator.d.ts.map +1 -0
  138. package/dist/story-generator/promptGenerator.js +186 -1
  139. package/dist/story-generator/storyHistory.d.ts +44 -0
  140. package/dist/story-generator/storyHistory.d.ts.map +1 -0
  141. package/dist/story-generator/storySync.d.ts +68 -0
  142. package/dist/story-generator/storySync.d.ts.map +1 -0
  143. package/dist/story-generator/storyTracker.d.ts +48 -0
  144. package/dist/story-generator/storyTracker.d.ts.map +1 -0
  145. package/dist/story-generator/storyValidator.d.ts +6 -0
  146. package/dist/story-generator/storyValidator.d.ts.map +1 -0
  147. package/dist/story-generator/universalDesignSystemAdapter.d.ts +68 -0
  148. package/dist/story-generator/universalDesignSystemAdapter.d.ts.map +1 -0
  149. package/dist/story-generator/universalDesignSystemAdapter.js +138 -1
  150. package/dist/story-generator/urlRedirectService.d.ts +21 -0
  151. package/dist/story-generator/urlRedirectService.d.ts.map +1 -0
  152. package/dist/story-generator/validateStory.d.ts +19 -0
  153. package/dist/story-generator/validateStory.d.ts.map +1 -0
  154. package/dist/story-generator/validateStory.js +6 -2
  155. package/dist/story-generator/visionPrompts.d.ts +88 -0
  156. package/dist/story-generator/visionPrompts.d.ts.map +1 -0
  157. package/dist/story-generator/visionPrompts.js +462 -0
  158. package/dist/story-ui.config.d.ts +78 -0
  159. package/dist/story-ui.config.d.ts.map +1 -0
  160. package/dist/templates/StoryUI/StoryUIPanel.d.ts +4 -0
  161. package/dist/templates/StoryUI/StoryUIPanel.d.ts.map +1 -0
  162. package/dist/templates/StoryUI/StoryUIPanel.js +1874 -0
  163. package/dist/templates/StoryUI/StoryUIPanel.stories.d.ts +18 -0
  164. package/dist/templates/StoryUI/StoryUIPanel.stories.d.ts.map +1 -0
  165. package/dist/templates/StoryUI/StoryUIPanel.stories.js +37 -0
  166. package/dist/templates/StoryUI/index.d.ts +3 -0
  167. package/dist/templates/StoryUI/index.d.ts.map +1 -0
  168. package/dist/templates/StoryUI/index.js +2 -0
  169. package/package.json +17 -3
  170. package/templates/StoryUI/StoryUIPanel.tsx +1960 -384
  171. package/templates/StoryUI/index.tsx +1 -1
  172. package/templates/StoryUI/manager.tsx +264 -0
  173. package/templates/production-app/.env.example +11 -0
  174. package/templates/production-app/index.html +66 -0
  175. package/templates/production-app/package.json +30 -0
  176. package/templates/production-app/public/favicon.svg +5 -0
  177. package/templates/production-app/src/App.tsx +1560 -0
  178. package/templates/production-app/src/LivePreviewRenderer.tsx +420 -0
  179. package/templates/production-app/src/componentRegistry.ts +315 -0
  180. package/templates/production-app/src/considerations.ts +16 -0
  181. package/templates/production-app/src/index.css +284 -0
  182. package/templates/production-app/src/main.tsx +25 -0
  183. package/templates/production-app/tsconfig.json +32 -0
  184. package/templates/production-app/tsconfig.node.json +11 -0
  185. package/templates/production-app/vite.config.ts +83 -0
  186. package/templates/react-import-rule.json +2 -2
  187. package/dist/index.js +0 -12
  188. package/dist/story-ui.config.loader.js +0 -205
@@ -0,0 +1,406 @@
1
+ /**
2
+ * Gemini LLM Provider
3
+ *
4
+ * Implementation of the LLM provider interface for Google's Gemini models.
5
+ */
6
+ import { BaseLLMProvider } from './base-provider.js';
7
+ import { logger } from '../logger.js';
8
+ // Gemini model definitions - Updated November 2025
9
+ // Reference: https://ai.google.dev/gemini-api/docs/gemini-3
10
+ const GEMINI_MODELS = [
11
+ // Gemini 3 Series - Latest (November 2025)
12
+ {
13
+ id: 'gemini-3-pro',
14
+ name: 'Gemini 3 Pro',
15
+ provider: 'gemini',
16
+ contextWindow: 1000000, // 1M tokens input
17
+ maxOutputTokens: 64000,
18
+ supportsVision: true,
19
+ supportsDocuments: true,
20
+ supportsFunctionCalling: true,
21
+ supportsStreaming: true,
22
+ supportsReasoning: true, // Native reasoning with thinking_level parameter
23
+ inputPricePer1kTokens: 0.002, // $2/million for <=200k context
24
+ outputPricePer1kTokens: 0.012, // $12/million output
25
+ description: 'Most intelligent Gemini model. PhD-level reasoning, 1501 Elo on LMArena.',
26
+ },
27
+ {
28
+ id: 'gemini-3-pro-preview',
29
+ name: 'Gemini 3 Pro (Preview)',
30
+ provider: 'gemini',
31
+ contextWindow: 1000000,
32
+ maxOutputTokens: 64000,
33
+ supportsVision: true,
34
+ supportsDocuments: true,
35
+ supportsFunctionCalling: true,
36
+ supportsStreaming: true,
37
+ supportsReasoning: true,
38
+ inputPricePer1kTokens: 0.002,
39
+ outputPricePer1kTokens: 0.012,
40
+ description: 'Preview version of Gemini 3 Pro with latest experimental features.',
41
+ },
42
+ // Gemini 2.0 Series
43
+ {
44
+ id: 'gemini-2.0-flash-exp',
45
+ name: 'Gemini 2.0 Flash Experimental',
46
+ provider: 'gemini',
47
+ contextWindow: 1048576,
48
+ maxOutputTokens: 8192,
49
+ supportsVision: true,
50
+ supportsDocuments: true,
51
+ supportsFunctionCalling: true,
52
+ supportsStreaming: true,
53
+ inputPricePer1kTokens: 0.00,
54
+ outputPricePer1kTokens: 0.00,
55
+ },
56
+ {
57
+ id: 'gemini-2.0-flash',
58
+ name: 'Gemini 2.0 Flash',
59
+ provider: 'gemini',
60
+ contextWindow: 1048576,
61
+ maxOutputTokens: 8192,
62
+ supportsVision: true,
63
+ supportsDocuments: true,
64
+ supportsFunctionCalling: true,
65
+ supportsStreaming: true,
66
+ inputPricePer1kTokens: 0.00,
67
+ outputPricePer1kTokens: 0.00,
68
+ },
69
+ // Gemini 1.5 Series (Legacy)
70
+ {
71
+ id: 'gemini-1.5-pro',
72
+ name: 'Gemini 1.5 Pro',
73
+ provider: 'gemini',
74
+ contextWindow: 2097152,
75
+ maxOutputTokens: 8192,
76
+ supportsVision: true,
77
+ supportsDocuments: true,
78
+ supportsFunctionCalling: true,
79
+ supportsStreaming: true,
80
+ inputPricePer1kTokens: 0.00125,
81
+ outputPricePer1kTokens: 0.005,
82
+ },
83
+ {
84
+ id: 'gemini-1.5-flash',
85
+ name: 'Gemini 1.5 Flash',
86
+ provider: 'gemini',
87
+ contextWindow: 1048576,
88
+ maxOutputTokens: 8192,
89
+ supportsVision: true,
90
+ supportsDocuments: true,
91
+ supportsFunctionCalling: true,
92
+ supportsStreaming: true,
93
+ inputPricePer1kTokens: 0.000075,
94
+ outputPricePer1kTokens: 0.0003,
95
+ },
96
+ {
97
+ id: 'gemini-1.5-flash-8b',
98
+ name: 'Gemini 1.5 Flash 8B',
99
+ provider: 'gemini',
100
+ contextWindow: 1048576,
101
+ maxOutputTokens: 8192,
102
+ supportsVision: true,
103
+ supportsDocuments: true,
104
+ supportsFunctionCalling: true,
105
+ supportsStreaming: true,
106
+ inputPricePer1kTokens: 0.0000375,
107
+ outputPricePer1kTokens: 0.00015,
108
+ },
109
+ ];
110
+ // Default model
111
+ const DEFAULT_MODEL = 'gemini-2.0-flash';
112
+ // API configuration
113
+ const GEMINI_API_BASE = 'https://generativelanguage.googleapis.com/v1beta/models';
114
+ export class GeminiProvider extends BaseLLMProvider {
115
+ constructor(config) {
116
+ super(config);
117
+ this.name = 'Gemini';
118
+ this.type = 'gemini';
119
+ this.supportedModels = GEMINI_MODELS;
120
+ // Set the provider type after base constructor
121
+ this.setProviderType();
122
+ // Set default model if not provided
123
+ if (!this.config.model) {
124
+ this.config.model = DEFAULT_MODEL;
125
+ }
126
+ }
127
+ getApiUrl(model, stream = false) {
128
+ const method = stream ? 'streamGenerateContent' : 'generateContent';
129
+ return `${GEMINI_API_BASE}/${model}:${method}`;
130
+ }
131
+ async chat(messages, options) {
132
+ this.validateMessages(messages);
133
+ this.logRequest(messages, options);
134
+ const apiKey = this.config.apiKey;
135
+ if (!apiKey) {
136
+ throw new Error('Gemini API key not configured');
137
+ }
138
+ const model = options?.model || this.config.model;
139
+ const geminiContents = this.convertMessages(messages);
140
+ const systemPrompt = this.buildSystemPrompt(options);
141
+ const requestBody = {
142
+ contents: geminiContents,
143
+ generationConfig: {
144
+ maxOutputTokens: options?.maxTokens || this.getSelectedModel()?.maxOutputTokens || 8192,
145
+ ...(options?.temperature !== undefined && { temperature: options.temperature }),
146
+ ...(options?.topP !== undefined && { topP: options.topP }),
147
+ ...(options?.topK !== undefined && { topK: options.topK }),
148
+ ...(options?.stopSequences?.length && { stopSequences: options.stopSequences }),
149
+ },
150
+ };
151
+ // Add system instruction if provided
152
+ if (systemPrompt) {
153
+ requestBody.systemInstruction = {
154
+ parts: [{ text: systemPrompt }],
155
+ };
156
+ }
157
+ const url = `${this.getApiUrl(model)}?key=${apiKey}`;
158
+ try {
159
+ const response = await fetch(url, {
160
+ method: 'POST',
161
+ headers: {
162
+ 'Content-Type': 'application/json',
163
+ },
164
+ body: JSON.stringify(requestBody),
165
+ signal: AbortSignal.timeout(this.config.timeout || 120000),
166
+ });
167
+ if (!response.ok) {
168
+ const errorBody = await response.text();
169
+ logger.error('Gemini API error response', { status: response.status, body: errorBody });
170
+ throw new Error(`Gemini API error: ${response.status} - ${errorBody}`);
171
+ }
172
+ const data = (await response.json());
173
+ const chatResponse = this.convertResponse(data, model);
174
+ this.logResponse(chatResponse);
175
+ return chatResponse;
176
+ }
177
+ catch (error) {
178
+ if (error instanceof Error && error.name === 'TimeoutError') {
179
+ throw new Error(`Gemini API request timed out after ${this.config.timeout}ms`);
180
+ }
181
+ throw error;
182
+ }
183
+ }
184
+ async *chatStream(messages, options) {
185
+ this.validateMessages(messages);
186
+ this.logRequest(messages, options);
187
+ const apiKey = this.config.apiKey;
188
+ if (!apiKey) {
189
+ yield { type: 'error', error: 'Gemini API key not configured' };
190
+ return;
191
+ }
192
+ const model = options?.model || this.config.model;
193
+ const geminiContents = this.convertMessages(messages);
194
+ const systemPrompt = this.buildSystemPrompt(options);
195
+ const requestBody = {
196
+ contents: geminiContents,
197
+ generationConfig: {
198
+ maxOutputTokens: options?.maxTokens || this.getSelectedModel()?.maxOutputTokens || 8192,
199
+ ...(options?.temperature !== undefined && { temperature: options.temperature }),
200
+ },
201
+ };
202
+ if (systemPrompt) {
203
+ requestBody.systemInstruction = {
204
+ parts: [{ text: systemPrompt }],
205
+ };
206
+ }
207
+ const url = `${this.getApiUrl(model, true)}?key=${apiKey}&alt=sse`;
208
+ try {
209
+ const response = await fetch(url, {
210
+ method: 'POST',
211
+ headers: {
212
+ 'Content-Type': 'application/json',
213
+ },
214
+ body: JSON.stringify(requestBody),
215
+ signal: AbortSignal.timeout(this.config.timeout || 120000),
216
+ });
217
+ if (!response.ok) {
218
+ const errorBody = await response.text();
219
+ yield { type: 'error', error: `Gemini API error: ${response.status} - ${errorBody}` };
220
+ return;
221
+ }
222
+ const reader = response.body?.getReader();
223
+ if (!reader) {
224
+ yield { type: 'error', error: 'No response body' };
225
+ return;
226
+ }
227
+ const decoder = new TextDecoder();
228
+ let buffer = '';
229
+ let promptTokens = 0;
230
+ let completionTokens = 0;
231
+ while (true) {
232
+ const { done, value } = await reader.read();
233
+ if (done)
234
+ break;
235
+ buffer += decoder.decode(value, { stream: true });
236
+ const lines = buffer.split('\n');
237
+ buffer = lines.pop() || '';
238
+ for (const line of lines) {
239
+ if (line.startsWith('data: ')) {
240
+ const data = line.slice(6);
241
+ if (!data || data === '[DONE]')
242
+ continue;
243
+ try {
244
+ const event = JSON.parse(data);
245
+ // Extract text from candidates
246
+ const text = event.candidates?.[0]?.content?.parts
247
+ ?.map(p => p.text)
248
+ .filter(Boolean)
249
+ .join('');
250
+ if (text) {
251
+ yield { type: 'text', content: text };
252
+ }
253
+ // Extract usage metadata
254
+ if (event.usageMetadata) {
255
+ promptTokens = event.usageMetadata.promptTokenCount || 0;
256
+ completionTokens = event.usageMetadata.candidatesTokenCount || 0;
257
+ }
258
+ }
259
+ catch {
260
+ // Skip malformed JSON
261
+ }
262
+ }
263
+ }
264
+ }
265
+ yield {
266
+ type: 'done',
267
+ usage: {
268
+ promptTokens,
269
+ completionTokens,
270
+ totalTokens: promptTokens + completionTokens,
271
+ },
272
+ };
273
+ }
274
+ catch (error) {
275
+ yield {
276
+ type: 'error',
277
+ error: error instanceof Error ? error.message : String(error),
278
+ };
279
+ }
280
+ }
281
+ async validateApiKey(apiKey) {
282
+ try {
283
+ // Make a minimal API call to validate the key
284
+ const url = `${this.getApiUrl('gemini-1.5-flash')}?key=${apiKey}`;
285
+ const response = await fetch(url, {
286
+ method: 'POST',
287
+ headers: {
288
+ 'Content-Type': 'application/json',
289
+ },
290
+ body: JSON.stringify({
291
+ contents: [{ parts: [{ text: 'Hi' }] }],
292
+ generationConfig: { maxOutputTokens: 1 },
293
+ }),
294
+ signal: AbortSignal.timeout(10000),
295
+ });
296
+ if (response.ok) {
297
+ return {
298
+ valid: true,
299
+ models: this.supportedModels,
300
+ };
301
+ }
302
+ const errorBody = await response.text();
303
+ // Check for specific error types
304
+ if (response.status === 400 && errorBody.includes('API_KEY_INVALID')) {
305
+ return {
306
+ valid: false,
307
+ error: 'Invalid API key',
308
+ };
309
+ }
310
+ return {
311
+ valid: false,
312
+ error: `API validation failed: ${errorBody}`,
313
+ };
314
+ }
315
+ catch (error) {
316
+ return {
317
+ valid: false,
318
+ error: error instanceof Error ? error.message : 'Validation failed',
319
+ };
320
+ }
321
+ }
322
+ // Convert our message format to Gemini format
323
+ convertMessages(messages) {
324
+ return messages
325
+ .filter(msg => msg.role !== 'system') // System messages handled separately
326
+ .map(msg => ({
327
+ role: msg.role === 'assistant' ? 'model' : 'user',
328
+ parts: this.convertContent(msg.content),
329
+ }));
330
+ }
331
+ convertContent(content) {
332
+ if (typeof content === 'string') {
333
+ return [{ text: content }];
334
+ }
335
+ return content.map(item => {
336
+ if (item.type === 'text') {
337
+ return { text: item.text };
338
+ }
339
+ if (item.type === 'image') {
340
+ const imageContent = item;
341
+ if (imageContent.source.type === 'base64' && imageContent.source.data) {
342
+ return {
343
+ inlineData: {
344
+ mimeType: imageContent.source.mediaType || 'image/png',
345
+ data: imageContent.source.data,
346
+ },
347
+ };
348
+ }
349
+ // URL images - Gemini requires base64, so we store for async processing
350
+ // NOTE: For Gemini, URL images should be pre-converted to base64 using imageProcessor.ts
351
+ // If a URL image reaches here, it means it wasn't pre-processed
352
+ if (imageContent.source.url) {
353
+ logger.warn(`Gemini provider received URL image that wasn't pre-converted to base64. URL: ${imageContent.source.url}`);
354
+ logger.warn('For best results, use imageProcessor.processImageInputs() to convert URL images before sending to Gemini.');
355
+ return { text: `[Image URL - requires base64 conversion: ${imageContent.source.url}]` };
356
+ }
357
+ return { text: '[Invalid image content]' };
358
+ }
359
+ // Document type
360
+ if (item.type === 'document') {
361
+ return { text: `[Document: ${item.source.name || 'unnamed'}]` };
362
+ }
363
+ return { text: '' };
364
+ });
365
+ }
366
+ convertResponse(data, model) {
367
+ const candidate = data.candidates?.[0];
368
+ const textParts = candidate?.content?.parts || [];
369
+ const content = textParts.map(p => p.text || '').join('');
370
+ return {
371
+ id: `gemini-${Date.now()}`, // Gemini doesn't return an ID
372
+ model,
373
+ content,
374
+ finishReason: this.mapFinishReason(candidate?.finishReason),
375
+ usage: {
376
+ promptTokens: data.usageMetadata?.promptTokenCount || 0,
377
+ completionTokens: data.usageMetadata?.candidatesTokenCount || 0,
378
+ totalTokens: data.usageMetadata?.totalTokenCount || 0,
379
+ },
380
+ raw: data,
381
+ };
382
+ }
383
+ mapFinishReason(finishReason) {
384
+ switch (finishReason) {
385
+ case 'STOP':
386
+ return 'stop';
387
+ case 'MAX_TOKENS':
388
+ return 'length';
389
+ case 'SAFETY':
390
+ case 'RECITATION':
391
+ return 'content_filter';
392
+ default:
393
+ return 'stop';
394
+ }
395
+ }
396
+ // Token estimation for Gemini
397
+ estimateTokens(text) {
398
+ // Gemini uses a similar tokenization to other LLMs
399
+ // Rough estimate: ~4 characters per token
400
+ return Math.ceil(text.length / 4);
401
+ }
402
+ }
403
+ // Factory function
404
+ export function createGeminiProvider(config) {
405
+ return new GeminiProvider(config);
406
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * LLM Providers Module
3
+ *
4
+ * Exports all LLM providers and the provider registry for managing them.
5
+ */
6
+ export * from './types.js';
7
+ export { BaseLLMProvider } from './base-provider.js';
8
+ export { ClaudeProvider, createClaudeProvider } from './claude-provider.js';
9
+ export { OpenAIProvider, createOpenAIProvider } from './openai-provider.js';
10
+ export { GeminiProvider, createGeminiProvider } from './gemini-provider.js';
11
+ import { LLMProvider, ProviderType, ProviderRegistry, ModelInfo, ProviderConfig } from './types.js';
12
+ /**
13
+ * Default Provider Registry Implementation
14
+ */
15
+ declare class DefaultProviderRegistry implements ProviderRegistry {
16
+ private providers;
17
+ private defaultProviderType;
18
+ constructor();
19
+ private registerBuiltInProviders;
20
+ register(provider: LLMProvider): void;
21
+ get(type: ProviderType): LLMProvider | undefined;
22
+ getAll(): LLMProvider[];
23
+ getAvailableModels(): ModelInfo[];
24
+ getDefault(): LLMProvider | undefined;
25
+ setDefault(type: ProviderType): void;
26
+ /**
27
+ * Configure a provider with API key and settings
28
+ */
29
+ configureProvider(type: ProviderType, config: Partial<ProviderConfig>): void;
30
+ /**
31
+ * Get a provider that supports a specific model
32
+ */
33
+ getProviderForModel(modelId: string): LLMProvider | undefined;
34
+ /**
35
+ * Check if any provider is configured and ready to use
36
+ */
37
+ hasConfiguredProvider(): boolean;
38
+ /**
39
+ * Get all configured providers
40
+ */
41
+ getConfiguredProviders(): LLMProvider[];
42
+ }
43
+ /**
44
+ * Get the global provider registry instance
45
+ */
46
+ export declare function getProviderRegistry(): DefaultProviderRegistry;
47
+ /**
48
+ * Convenience function to get a provider by type
49
+ */
50
+ export declare function getProvider(type: ProviderType): LLMProvider | undefined;
51
+ /**
52
+ * Convenience function to get the default provider
53
+ */
54
+ export declare function getDefaultProvider(): LLMProvider | undefined;
55
+ /**
56
+ * Convenience function to configure a provider
57
+ */
58
+ export declare function configureProvider(type: ProviderType, config: Partial<ProviderConfig>): void;
59
+ /**
60
+ * Initialize providers from environment variables
61
+ */
62
+ export declare function initializeFromEnv(): void;
63
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../story-generator/llm-providers/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,cAAc,YAAY,CAAC;AAG3B,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAGrD,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5E,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5E,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAG5E,OAAO,EACL,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,SAAS,EACT,cAAc,EACf,MAAM,YAAY,CAAC;AAMpB;;GAEG;AACH,cAAM,uBAAwB,YAAW,gBAAgB;IACvD,OAAO,CAAC,SAAS,CAA6C;IAC9D,OAAO,CAAC,mBAAmB,CAA6B;;IAOxD,OAAO,CAAC,wBAAwB;IAQhC,QAAQ,CAAC,QAAQ,EAAE,WAAW,GAAG,IAAI;IAUrC,GAAG,CAAC,IAAI,EAAE,YAAY,GAAG,WAAW,GAAG,SAAS;IAIhD,MAAM,IAAI,WAAW,EAAE;IAIvB,kBAAkB,IAAI,SAAS,EAAE;IAQjC,UAAU,IAAI,WAAW,GAAG,SAAS;IAKrC,UAAU,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAQpC;;OAEG;IACH,iBAAiB,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,IAAI;IAQ5E;;OAEG;IACH,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAS7D;;OAEG;IACH,qBAAqB,IAAI,OAAO;IAShC;;OAEG;IACH,sBAAsB,IAAI,WAAW,EAAE;CAGxC;AAKD;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,uBAAuB,CAK7D;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,WAAW,GAAG,SAAS,CAEvE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,WAAW,GAAG,SAAS,CAE5D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,OAAO,CAAC,cAAc,CAAC,GAC9B,IAAI,CAEN;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAiCxC"}
@@ -0,0 +1,169 @@
1
+ /**
2
+ * LLM Providers Module
3
+ *
4
+ * Exports all LLM providers and the provider registry for managing them.
5
+ */
6
+ // Types
7
+ export * from './types.js';
8
+ // Base provider
9
+ export { BaseLLMProvider } from './base-provider.js';
10
+ // Provider implementations
11
+ export { ClaudeProvider, createClaudeProvider } from './claude-provider.js';
12
+ export { OpenAIProvider, createOpenAIProvider } from './openai-provider.js';
13
+ export { GeminiProvider, createGeminiProvider } from './gemini-provider.js';
14
+ import { ClaudeProvider } from './claude-provider.js';
15
+ import { OpenAIProvider } from './openai-provider.js';
16
+ import { GeminiProvider } from './gemini-provider.js';
17
+ import { logger } from '../logger.js';
18
+ /**
19
+ * Default Provider Registry Implementation
20
+ */
21
+ class DefaultProviderRegistry {
22
+ constructor() {
23
+ this.providers = new Map();
24
+ this.defaultProviderType = null;
25
+ // Register built-in providers
26
+ this.registerBuiltInProviders();
27
+ }
28
+ registerBuiltInProviders() {
29
+ // Register all built-in providers
30
+ this.register(new ClaudeProvider());
31
+ this.register(new OpenAIProvider());
32
+ this.register(new GeminiProvider());
33
+ logger.debug('Registered built-in providers: Claude, OpenAI, Gemini');
34
+ }
35
+ register(provider) {
36
+ this.providers.set(provider.type, provider);
37
+ logger.debug(`Registered provider: ${provider.name} (${provider.type})`);
38
+ // Set as default if it's the first provider
39
+ if (!this.defaultProviderType) {
40
+ this.defaultProviderType = provider.type;
41
+ }
42
+ }
43
+ get(type) {
44
+ return this.providers.get(type);
45
+ }
46
+ getAll() {
47
+ return Array.from(this.providers.values());
48
+ }
49
+ getAvailableModels() {
50
+ const models = [];
51
+ for (const provider of this.providers.values()) {
52
+ models.push(...provider.supportedModels);
53
+ }
54
+ return models;
55
+ }
56
+ getDefault() {
57
+ if (!this.defaultProviderType)
58
+ return undefined;
59
+ return this.providers.get(this.defaultProviderType);
60
+ }
61
+ setDefault(type) {
62
+ if (!this.providers.has(type)) {
63
+ throw new Error(`Provider type '${type}' is not registered`);
64
+ }
65
+ this.defaultProviderType = type;
66
+ logger.debug(`Set default provider to: ${type}`);
67
+ }
68
+ /**
69
+ * Configure a provider with API key and settings
70
+ */
71
+ configureProvider(type, config) {
72
+ const provider = this.providers.get(type);
73
+ if (!provider) {
74
+ throw new Error(`Provider type '${type}' is not registered`);
75
+ }
76
+ provider.configure({ ...config, provider: type });
77
+ }
78
+ /**
79
+ * Get a provider that supports a specific model
80
+ */
81
+ getProviderForModel(modelId) {
82
+ for (const provider of this.providers.values()) {
83
+ if (provider.supportedModels.some(m => m.id === modelId)) {
84
+ return provider;
85
+ }
86
+ }
87
+ return undefined;
88
+ }
89
+ /**
90
+ * Check if any provider is configured and ready to use
91
+ */
92
+ hasConfiguredProvider() {
93
+ for (const provider of this.providers.values()) {
94
+ if (provider.isConfigured()) {
95
+ return true;
96
+ }
97
+ }
98
+ return false;
99
+ }
100
+ /**
101
+ * Get all configured providers
102
+ */
103
+ getConfiguredProviders() {
104
+ return this.getAll().filter(p => p.isConfigured());
105
+ }
106
+ }
107
+ // Singleton registry instance
108
+ let registryInstance = null;
109
+ /**
110
+ * Get the global provider registry instance
111
+ */
112
+ export function getProviderRegistry() {
113
+ if (!registryInstance) {
114
+ registryInstance = new DefaultProviderRegistry();
115
+ }
116
+ return registryInstance;
117
+ }
118
+ /**
119
+ * Convenience function to get a provider by type
120
+ */
121
+ export function getProvider(type) {
122
+ return getProviderRegistry().get(type);
123
+ }
124
+ /**
125
+ * Convenience function to get the default provider
126
+ */
127
+ export function getDefaultProvider() {
128
+ return getProviderRegistry().getDefault();
129
+ }
130
+ /**
131
+ * Convenience function to configure a provider
132
+ */
133
+ export function configureProvider(type, config) {
134
+ getProviderRegistry().configureProvider(type, config);
135
+ }
136
+ /**
137
+ * Initialize providers from environment variables
138
+ */
139
+ export function initializeFromEnv() {
140
+ const registry = getProviderRegistry();
141
+ // Configure Claude if API key is present
142
+ const claudeKey = process.env.CLAUDE_API_KEY || process.env.ANTHROPIC_API_KEY;
143
+ if (claudeKey) {
144
+ registry.configureProvider('claude', {
145
+ apiKey: claudeKey,
146
+ model: process.env.CLAUDE_MODEL || 'claude-sonnet-4-5-20250929',
147
+ });
148
+ logger.info('Claude provider configured from environment');
149
+ }
150
+ // Configure OpenAI if API key is present
151
+ const openaiKey = process.env.OPENAI_API_KEY;
152
+ if (openaiKey) {
153
+ registry.configureProvider('openai', {
154
+ apiKey: openaiKey,
155
+ model: process.env.OPENAI_MODEL || 'gpt-4o',
156
+ organizationId: process.env.OPENAI_ORG_ID,
157
+ });
158
+ logger.info('OpenAI provider configured from environment');
159
+ }
160
+ // Configure Gemini if API key is present
161
+ const geminiKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY;
162
+ if (geminiKey) {
163
+ registry.configureProvider('gemini', {
164
+ apiKey: geminiKey,
165
+ model: process.env.GEMINI_MODEL || 'gemini-2.0-flash',
166
+ });
167
+ logger.info('Gemini provider configured from environment');
168
+ }
169
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * OpenAI LLM Provider
3
+ *
4
+ * Implementation of the LLM provider interface for OpenAI's GPT models.
5
+ */
6
+ import { ProviderType, ProviderConfig, ModelInfo, ChatMessage, ChatOptions, ChatResponse, StreamChunk, ValidationResult } from './types.js';
7
+ import { BaseLLMProvider } from './base-provider.js';
8
+ export declare class OpenAIProvider extends BaseLLMProvider {
9
+ readonly name = "OpenAI";
10
+ readonly type: ProviderType;
11
+ readonly supportedModels: ModelInfo[];
12
+ constructor(config?: Partial<ProviderConfig>);
13
+ chat(messages: ChatMessage[], options?: ChatOptions): Promise<ChatResponse>;
14
+ chatStream(messages: ChatMessage[], options?: ChatOptions): AsyncIterable<StreamChunk>;
15
+ validateApiKey(apiKey: string): Promise<ValidationResult>;
16
+ private convertMessages;
17
+ private extractTextContent;
18
+ private convertContent;
19
+ private convertResponse;
20
+ private mapFinishReason;
21
+ estimateTokens(text: string): number;
22
+ }
23
+ export declare function createOpenAIProvider(config?: Partial<ProviderConfig>): OpenAIProvider;
24
+ //# sourceMappingURL=openai-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-provider.d.ts","sourceRoot":"","sources":["../../../story-generator/llm-providers/openai-provider.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,YAAY,EACZ,cAAc,EACd,SAAS,EACT,WAAW,EACX,WAAW,EACX,YAAY,EACZ,WAAW,EACX,gBAAgB,EAGjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AA+LrD,qBAAa,cAAe,SAAQ,eAAe;IACjD,QAAQ,CAAC,IAAI,YAAY;IACzB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAY;IACvC,QAAQ,CAAC,eAAe,cAAiB;gBAE7B,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC;IAUtC,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IA2D1E,UAAU,CACf,QAAQ,EAAE,WAAW,EAAE,EACvB,OAAO,CAAC,EAAE,WAAW,GACpB,aAAa,CAAC,WAAW,CAAC;IAqGvB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA+C/D,OAAO,CAAC,eAAe;IA8BvB,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,cAAc;IAiCtB,OAAO,CAAC,eAAe;IAkBvB,OAAO,CAAC,eAAe;IAmBvB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAKrC;AAGD,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,cAAc,CAErF"}