@tpitre/story-ui 2.2.0 → 2.3.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 (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 +1157 -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,458 @@
1
+ /**
2
+ * OpenAI LLM Provider
3
+ *
4
+ * Implementation of the LLM provider interface for OpenAI's GPT models.
5
+ */
6
+ import { BaseLLMProvider } from './base-provider.js';
7
+ import { logger } from '../logger.js';
8
+ // OpenAI model definitions - Updated November 2025
9
+ // Reference: https://openai.com/index/introducing-gpt-5/
10
+ const OPENAI_MODELS = [
11
+ // GPT-5.1 Series - Latest (November 2025)
12
+ {
13
+ id: 'gpt-5.1',
14
+ name: 'GPT-5.1',
15
+ provider: 'openai',
16
+ contextWindow: 256000,
17
+ maxOutputTokens: 32768,
18
+ supportsVision: true,
19
+ supportsDocuments: true,
20
+ supportsFunctionCalling: true,
21
+ supportsStreaming: true,
22
+ supportsReasoning: true, // Adaptive reasoning with reasoning_effort parameter
23
+ inputPricePer1kTokens: 0.005,
24
+ outputPricePer1kTokens: 0.015,
25
+ description: 'Latest GPT-5 series. 76.3% on SWE-bench. Adaptive reasoning capability.',
26
+ },
27
+ {
28
+ id: 'gpt-5.1-chat-latest',
29
+ name: 'GPT-5.1 Instant',
30
+ provider: 'openai',
31
+ contextWindow: 256000,
32
+ maxOutputTokens: 32768,
33
+ supportsVision: true,
34
+ supportsDocuments: true,
35
+ supportsFunctionCalling: true,
36
+ supportsStreaming: true,
37
+ supportsReasoning: true,
38
+ inputPricePer1kTokens: 0.003,
39
+ outputPricePer1kTokens: 0.012,
40
+ description: 'More conversational GPT-5.1 with improved instruction following.',
41
+ },
42
+ {
43
+ id: 'gpt-5.1-thinking',
44
+ name: 'GPT-5.1 Thinking',
45
+ provider: 'openai',
46
+ contextWindow: 256000,
47
+ maxOutputTokens: 65536,
48
+ supportsVision: true,
49
+ supportsDocuments: true,
50
+ supportsFunctionCalling: true,
51
+ supportsStreaming: true,
52
+ supportsReasoning: true,
53
+ inputPricePer1kTokens: 0.008,
54
+ outputPricePer1kTokens: 0.024,
55
+ description: 'Extended thinking mode for complex reasoning tasks.',
56
+ },
57
+ // GPT-5 Original (August 2025)
58
+ {
59
+ id: 'gpt-5',
60
+ name: 'GPT-5',
61
+ provider: 'openai',
62
+ contextWindow: 200000,
63
+ maxOutputTokens: 32768,
64
+ supportsVision: true,
65
+ supportsDocuments: true,
66
+ supportsFunctionCalling: true,
67
+ supportsStreaming: true,
68
+ supportsReasoning: true,
69
+ inputPricePer1kTokens: 0.005,
70
+ outputPricePer1kTokens: 0.015,
71
+ description: 'Multimodal foundation model combining reasoning and general capabilities.',
72
+ },
73
+ // GPT-4o Series
74
+ {
75
+ id: 'gpt-4o',
76
+ name: 'GPT-4o',
77
+ provider: 'openai',
78
+ contextWindow: 128000,
79
+ maxOutputTokens: 16384,
80
+ supportsVision: true,
81
+ supportsDocuments: false,
82
+ supportsFunctionCalling: true,
83
+ supportsStreaming: true,
84
+ inputPricePer1kTokens: 0.0025,
85
+ outputPricePer1kTokens: 0.01,
86
+ },
87
+ {
88
+ id: 'gpt-4o-mini',
89
+ name: 'GPT-4o Mini',
90
+ provider: 'openai',
91
+ contextWindow: 128000,
92
+ maxOutputTokens: 16384,
93
+ supportsVision: true,
94
+ supportsDocuments: false,
95
+ supportsFunctionCalling: true,
96
+ supportsStreaming: true,
97
+ inputPricePer1kTokens: 0.00015,
98
+ outputPricePer1kTokens: 0.0006,
99
+ },
100
+ // o1 Reasoning Series
101
+ {
102
+ id: 'o1',
103
+ name: 'o1 (Reasoning)',
104
+ provider: 'openai',
105
+ contextWindow: 200000,
106
+ maxOutputTokens: 100000,
107
+ supportsVision: true,
108
+ supportsDocuments: false,
109
+ supportsFunctionCalling: true,
110
+ supportsStreaming: true,
111
+ inputPricePer1kTokens: 0.015,
112
+ outputPricePer1kTokens: 0.06,
113
+ },
114
+ {
115
+ id: 'o1-mini',
116
+ name: 'o1 Mini (Reasoning)',
117
+ provider: 'openai',
118
+ contextWindow: 128000,
119
+ maxOutputTokens: 65536,
120
+ supportsVision: false,
121
+ supportsDocuments: false,
122
+ supportsFunctionCalling: true,
123
+ supportsStreaming: true,
124
+ inputPricePer1kTokens: 0.003,
125
+ outputPricePer1kTokens: 0.012,
126
+ },
127
+ // Legacy GPT-4 Series
128
+ {
129
+ id: 'gpt-4-turbo',
130
+ name: 'GPT-4 Turbo',
131
+ provider: 'openai',
132
+ contextWindow: 128000,
133
+ maxOutputTokens: 4096,
134
+ supportsVision: true,
135
+ supportsDocuments: false,
136
+ supportsFunctionCalling: true,
137
+ supportsStreaming: true,
138
+ inputPricePer1kTokens: 0.01,
139
+ outputPricePer1kTokens: 0.03,
140
+ },
141
+ {
142
+ id: 'gpt-3.5-turbo',
143
+ name: 'GPT-3.5 Turbo (Legacy)',
144
+ provider: 'openai',
145
+ contextWindow: 16385,
146
+ maxOutputTokens: 4096,
147
+ supportsVision: false,
148
+ supportsDocuments: false,
149
+ supportsFunctionCalling: true,
150
+ supportsStreaming: true,
151
+ inputPricePer1kTokens: 0.0005,
152
+ outputPricePer1kTokens: 0.0015,
153
+ },
154
+ ];
155
+ // Default model
156
+ const DEFAULT_MODEL = 'gpt-4o';
157
+ // API configuration
158
+ const OPENAI_API_URL = 'https://api.openai.com/v1/chat/completions';
159
+ export class OpenAIProvider extends BaseLLMProvider {
160
+ constructor(config) {
161
+ super(config);
162
+ this.name = 'OpenAI';
163
+ this.type = 'openai';
164
+ this.supportedModels = OPENAI_MODELS;
165
+ // Set the provider type after base constructor
166
+ this.setProviderType();
167
+ // Set default model if not provided
168
+ if (!this.config.model) {
169
+ this.config.model = DEFAULT_MODEL;
170
+ }
171
+ }
172
+ async chat(messages, options) {
173
+ this.validateMessages(messages);
174
+ this.logRequest(messages, options);
175
+ const apiKey = this.config.apiKey;
176
+ if (!apiKey) {
177
+ throw new Error('OpenAI API key not configured');
178
+ }
179
+ const model = options?.model || this.config.model;
180
+ const openaiMessages = this.convertMessages(messages, options?.systemPrompt);
181
+ const requestBody = {
182
+ model,
183
+ messages: openaiMessages,
184
+ max_tokens: options?.maxTokens || this.getSelectedModel()?.maxOutputTokens || 4096,
185
+ };
186
+ // Add optional parameters
187
+ if (options?.temperature !== undefined) {
188
+ requestBody.temperature = options.temperature;
189
+ }
190
+ if (options?.topP !== undefined) {
191
+ requestBody.top_p = options.topP;
192
+ }
193
+ if (options?.stopSequences?.length) {
194
+ requestBody.stop = options.stopSequences;
195
+ }
196
+ try {
197
+ const response = await fetch(this.config.baseUrl || OPENAI_API_URL, {
198
+ method: 'POST',
199
+ headers: {
200
+ 'Content-Type': 'application/json',
201
+ 'Authorization': `Bearer ${apiKey}`,
202
+ ...(this.config.organizationId && { 'OpenAI-Organization': this.config.organizationId }),
203
+ },
204
+ body: JSON.stringify(requestBody),
205
+ signal: AbortSignal.timeout(this.config.timeout || 120000),
206
+ });
207
+ if (!response.ok) {
208
+ const errorBody = await response.text();
209
+ logger.error('OpenAI API error response', { status: response.status, body: errorBody });
210
+ throw new Error(`OpenAI API error: ${response.status} - ${errorBody}`);
211
+ }
212
+ const data = (await response.json());
213
+ const chatResponse = this.convertResponse(data);
214
+ this.logResponse(chatResponse);
215
+ return chatResponse;
216
+ }
217
+ catch (error) {
218
+ if (error instanceof Error && error.name === 'TimeoutError') {
219
+ throw new Error(`OpenAI API request timed out after ${this.config.timeout}ms`);
220
+ }
221
+ throw error;
222
+ }
223
+ }
224
+ async *chatStream(messages, options) {
225
+ this.validateMessages(messages);
226
+ this.logRequest(messages, options);
227
+ const apiKey = this.config.apiKey;
228
+ if (!apiKey) {
229
+ yield { type: 'error', error: 'OpenAI API key not configured' };
230
+ return;
231
+ }
232
+ const model = options?.model || this.config.model;
233
+ const openaiMessages = this.convertMessages(messages, options?.systemPrompt);
234
+ const requestBody = {
235
+ model,
236
+ messages: openaiMessages,
237
+ max_tokens: options?.maxTokens || this.getSelectedModel()?.maxOutputTokens || 4096,
238
+ stream: true,
239
+ };
240
+ if (options?.temperature !== undefined) {
241
+ requestBody.temperature = options.temperature;
242
+ }
243
+ try {
244
+ const response = await fetch(this.config.baseUrl || OPENAI_API_URL, {
245
+ method: 'POST',
246
+ headers: {
247
+ 'Content-Type': 'application/json',
248
+ 'Authorization': `Bearer ${apiKey}`,
249
+ ...(this.config.organizationId && { 'OpenAI-Organization': this.config.organizationId }),
250
+ },
251
+ body: JSON.stringify(requestBody),
252
+ signal: AbortSignal.timeout(this.config.timeout || 120000),
253
+ });
254
+ if (!response.ok) {
255
+ const errorBody = await response.text();
256
+ yield { type: 'error', error: `OpenAI API error: ${response.status} - ${errorBody}` };
257
+ return;
258
+ }
259
+ const reader = response.body?.getReader();
260
+ if (!reader) {
261
+ yield { type: 'error', error: 'No response body' };
262
+ return;
263
+ }
264
+ const decoder = new TextDecoder();
265
+ let buffer = '';
266
+ let promptTokens = 0;
267
+ let completionTokens = 0;
268
+ while (true) {
269
+ const { done, value } = await reader.read();
270
+ if (done)
271
+ break;
272
+ buffer += decoder.decode(value, { stream: true });
273
+ const lines = buffer.split('\n');
274
+ buffer = lines.pop() || '';
275
+ for (const line of lines) {
276
+ if (line.startsWith('data: ')) {
277
+ const data = line.slice(6);
278
+ if (data === '[DONE]')
279
+ continue;
280
+ try {
281
+ const event = JSON.parse(data);
282
+ if (event.choices?.[0]?.delta?.content) {
283
+ yield { type: 'text', content: event.choices[0].delta.content };
284
+ }
285
+ // Usage may be included in the final message
286
+ if (event.usage) {
287
+ promptTokens = event.usage.prompt_tokens || 0;
288
+ completionTokens = event.usage.completion_tokens || 0;
289
+ }
290
+ }
291
+ catch {
292
+ // Skip malformed JSON
293
+ }
294
+ }
295
+ }
296
+ }
297
+ yield {
298
+ type: 'done',
299
+ usage: {
300
+ promptTokens,
301
+ completionTokens,
302
+ totalTokens: promptTokens + completionTokens,
303
+ },
304
+ };
305
+ }
306
+ catch (error) {
307
+ yield {
308
+ type: 'error',
309
+ error: error instanceof Error ? error.message : String(error),
310
+ };
311
+ }
312
+ }
313
+ async validateApiKey(apiKey) {
314
+ try {
315
+ // Make a minimal API call to validate the key
316
+ const response = await fetch(OPENAI_API_URL, {
317
+ method: 'POST',
318
+ headers: {
319
+ 'Content-Type': 'application/json',
320
+ 'Authorization': `Bearer ${apiKey}`,
321
+ },
322
+ body: JSON.stringify({
323
+ model: 'gpt-3.5-turbo',
324
+ max_tokens: 1,
325
+ messages: [{ role: 'user', content: 'Hi' }],
326
+ }),
327
+ signal: AbortSignal.timeout(10000),
328
+ });
329
+ if (response.ok) {
330
+ return {
331
+ valid: true,
332
+ models: this.supportedModels,
333
+ };
334
+ }
335
+ const errorBody = await response.text();
336
+ // Check for specific error types
337
+ if (response.status === 401) {
338
+ return {
339
+ valid: false,
340
+ error: 'Invalid API key',
341
+ };
342
+ }
343
+ return {
344
+ valid: false,
345
+ error: `API validation failed: ${errorBody}`,
346
+ };
347
+ }
348
+ catch (error) {
349
+ return {
350
+ valid: false,
351
+ error: error instanceof Error ? error.message : 'Validation failed',
352
+ };
353
+ }
354
+ }
355
+ // Convert our message format to OpenAI format
356
+ convertMessages(messages, systemPrompt) {
357
+ const result = [];
358
+ // Add system prompt if provided
359
+ if (systemPrompt) {
360
+ result.push({ role: 'system', content: systemPrompt });
361
+ }
362
+ // Convert remaining messages
363
+ for (const msg of messages) {
364
+ if (msg.role === 'system') {
365
+ // Add system messages directly
366
+ result.push({
367
+ role: 'system',
368
+ content: typeof msg.content === 'string' ? msg.content : this.extractTextContent(msg.content),
369
+ });
370
+ }
371
+ else {
372
+ result.push({
373
+ role: msg.role,
374
+ content: this.convertContent(msg.content),
375
+ });
376
+ }
377
+ }
378
+ return result;
379
+ }
380
+ extractTextContent(content) {
381
+ return content
382
+ .filter(item => item.type === 'text')
383
+ .map(item => item.text)
384
+ .join('\n');
385
+ }
386
+ convertContent(content) {
387
+ if (typeof content === 'string') {
388
+ return content;
389
+ }
390
+ return content.map(item => {
391
+ if (item.type === 'text') {
392
+ return { type: 'text', text: item.text };
393
+ }
394
+ if (item.type === 'image') {
395
+ const imageContent = item;
396
+ // OpenAI expects either a URL or a base64 data URL
397
+ const imageUrl = imageContent.source.url ||
398
+ `data:${imageContent.source.mediaType || 'image/png'};base64,${imageContent.source.data}`;
399
+ return {
400
+ type: 'image_url',
401
+ image_url: {
402
+ url: imageUrl,
403
+ detail: 'auto',
404
+ },
405
+ };
406
+ }
407
+ // Document type - convert to text representation
408
+ if (item.type === 'document') {
409
+ return {
410
+ type: 'text',
411
+ text: `[Document: ${item.source.name || 'unnamed'}]`,
412
+ };
413
+ }
414
+ return { type: 'text', text: '' };
415
+ });
416
+ }
417
+ convertResponse(data) {
418
+ const choice = data.choices[0];
419
+ const content = choice?.message?.content || '';
420
+ return {
421
+ id: data.id,
422
+ model: data.model,
423
+ content,
424
+ finishReason: this.mapFinishReason(choice?.finish_reason),
425
+ usage: {
426
+ promptTokens: data.usage.prompt_tokens,
427
+ completionTokens: data.usage.completion_tokens,
428
+ totalTokens: data.usage.total_tokens,
429
+ },
430
+ raw: data,
431
+ };
432
+ }
433
+ mapFinishReason(finishReason) {
434
+ switch (finishReason) {
435
+ case 'stop':
436
+ return 'stop';
437
+ case 'length':
438
+ return 'length';
439
+ case 'tool_calls':
440
+ case 'function_call':
441
+ return 'tool_calls';
442
+ case 'content_filter':
443
+ return 'content_filter';
444
+ default:
445
+ return 'stop';
446
+ }
447
+ }
448
+ // Token estimation using tiktoken approximation
449
+ estimateTokens(text) {
450
+ // Rough estimate: ~4 characters per token for English text
451
+ // OpenAI uses cl100k_base encoding for newer models
452
+ return Math.ceil(text.length / 4);
453
+ }
454
+ }
455
+ // Factory function
456
+ export function createOpenAIProvider(config) {
457
+ return new OpenAIProvider(config);
458
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * LLM Settings Manager
3
+ *
4
+ * Manages hybrid configuration for LLM provider/model selection.
5
+ * Combines environment variables (DevOps guardrails) with user preferences.
6
+ *
7
+ * Environment Variables:
8
+ * - DEFAULT_PROVIDER: Default provider (claude, openai, gemini)
9
+ * - DEFAULT_MODEL: Default model ID
10
+ * - ALLOWED_MODELS: Comma-separated list of allowed model IDs (optional)
11
+ * - ALLOWED_PROVIDERS: Comma-separated list of allowed providers (optional)
12
+ * - SINGLE_PROVIDER_MODE: Hide provider selection (true/false)
13
+ */
14
+ import { ProviderType } from './types.js';
15
+ export interface UserSettings {
16
+ selectedProvider?: ProviderType;
17
+ selectedModel?: string;
18
+ }
19
+ export interface SettingsConfig {
20
+ defaultProvider: ProviderType;
21
+ defaultModel: string;
22
+ allowedProviders: ProviderType[];
23
+ allowedModels: string[];
24
+ singleProviderMode: boolean;
25
+ }
26
+ export interface AvailableOption {
27
+ id: string;
28
+ name: string;
29
+ description?: string;
30
+ isDefault?: boolean;
31
+ isRecommended?: boolean;
32
+ }
33
+ export interface SettingsResponse {
34
+ providers: AvailableOption[];
35
+ models: AvailableOption[];
36
+ currentProvider: ProviderType;
37
+ currentModel: string;
38
+ config: {
39
+ singleProviderMode: boolean;
40
+ hasRestrictions: boolean;
41
+ };
42
+ }
43
+ /**
44
+ * Load settings configuration from environment
45
+ */
46
+ export declare function loadSettingsConfig(): SettingsConfig;
47
+ /**
48
+ * Get available providers for UI selection
49
+ */
50
+ export declare function getAvailableProviders(config: SettingsConfig): AvailableOption[];
51
+ /**
52
+ * Get available models for a provider
53
+ */
54
+ export declare function getAvailableModels(provider: ProviderType, config: SettingsConfig): AvailableOption[];
55
+ /**
56
+ * Validate user's model selection against configuration
57
+ */
58
+ export declare function validateSelection(provider: ProviderType, model: string, config: SettingsConfig): {
59
+ valid: boolean;
60
+ error?: string;
61
+ };
62
+ /**
63
+ * Get complete settings response for UI
64
+ */
65
+ export declare function getSettingsForUI(currentProvider?: ProviderType, currentModel?: string): SettingsResponse;
66
+ /**
67
+ * Apply user settings and return effective provider/model
68
+ */
69
+ export declare function applyUserSettings(settings: UserSettings): {
70
+ provider: ProviderType;
71
+ model: string;
72
+ applied: boolean;
73
+ fallbackReason?: string;
74
+ };
75
+ //# sourceMappingURL=settings-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-manager.d.ts","sourceRoot":"","sources":["../../../story-generator/llm-providers/settings-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,YAAY,EAA0B,MAAM,YAAY,CAAC;AAIlE,MAAM,WAAW,YAAY;IAC3B,gBAAgB,CAAC,EAAE,YAAY,CAAC;IAChC,aAAa,CAAC,EAAE,MAAM,CAAC;CAExB;AAED,MAAM,WAAW,cAAc;IAC7B,eAAe,EAAE,YAAY,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,YAAY,EAAE,CAAC;IACjC,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,eAAe,EAAE,YAAY,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE;QACN,kBAAkB,EAAE,OAAO,CAAC;QAC5B,eAAe,EAAE,OAAO,CAAC;KAC1B,CAAC;CACH;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,cAAc,CA0BnD;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,cAAc,GAAG,eAAe,EAAE,CAyB/E;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,YAAY,EACtB,MAAM,EAAE,cAAc,GACrB,eAAe,EAAE,CAmCnB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,cAAc,GACrB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CA4BpC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,eAAe,CAAC,EAAE,YAAY,EAC9B,YAAY,CAAC,EAAE,MAAM,GACpB,gBAAgB,CAuBlB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,YAAY,GAAG;IACzD,QAAQ,EAAE,YAAY,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAuBA"}