@tpitre/story-ui 4.15.0 → 4.16.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 (55) hide show
  1. package/dist/cli/setup.js +3 -3
  2. package/dist/mcp-server/index.js +0 -6
  3. package/dist/mcp-server/routes/canvasGenerate.d.ts +23 -1
  4. package/dist/mcp-server/routes/canvasGenerate.d.ts.map +1 -1
  5. package/dist/mcp-server/routes/canvasGenerate.js +156 -26
  6. package/dist/mcp-server/routes/canvasSave.d.ts +7 -0
  7. package/dist/mcp-server/routes/canvasSave.d.ts.map +1 -1
  8. package/dist/mcp-server/routes/canvasSave.js +21 -7
  9. package/dist/story-generator/llm-providers/claude-provider.d.ts.map +1 -1
  10. package/dist/story-generator/llm-providers/claude-provider.js +0 -39
  11. package/dist/story-generator/llm-providers/gemini-provider.d.ts.map +1 -1
  12. package/dist/story-generator/llm-providers/gemini-provider.js +12 -36
  13. package/dist/story-generator/llm-providers/index.js +3 -3
  14. package/dist/story-generator/llm-providers/openai-provider.d.ts.map +1 -1
  15. package/dist/story-generator/llm-providers/openai-provider.js +10 -50
  16. package/dist/story-generator/llm-providers/settings-manager.d.ts.map +1 -1
  17. package/dist/story-generator/llm-providers/settings-manager.js +4 -7
  18. package/dist/templates/StoryUI/StoryUIPanel.css +30 -1
  19. package/dist/templates/StoryUI/StoryUIPanel.d.ts.map +1 -1
  20. package/dist/templates/StoryUI/StoryUIPanel.js +7 -16
  21. package/dist/templates/StoryUI/StoryUIPanel.tsx +7 -16
  22. package/dist/templates/StoryUI/voice/VoiceCanvas.d.ts +1 -1
  23. package/dist/templates/StoryUI/voice/VoiceCanvas.d.ts.map +1 -1
  24. package/dist/templates/StoryUI/voice/VoiceCanvas.js +37 -9
  25. package/dist/templates/StoryUI/voice/VoiceCanvas.tsx +87 -64
  26. package/package.json +5 -3
  27. package/templates/StoryUI/StoryUIPanel.css +30 -1
  28. package/templates/StoryUI/StoryUIPanel.tsx +7 -16
  29. package/templates/StoryUI/voice/VoiceCanvas.tsx +87 -64
  30. package/dist/mcp-server/routes/canvasIntent.d.ts +0 -15
  31. package/dist/mcp-server/routes/canvasIntent.d.ts.map +0 -1
  32. package/dist/mcp-server/routes/canvasIntent.js +0 -553
  33. package/dist/mcp-server/routes/canvasPreview.d.ts +0 -14
  34. package/dist/mcp-server/routes/canvasPreview.d.ts.map +0 -1
  35. package/dist/mcp-server/routes/canvasPreview.js +0 -25
  36. package/dist/templates/StoryUI/voice/canvas/ComponentRenderer.d.ts +0 -14
  37. package/dist/templates/StoryUI/voice/canvas/ComponentRenderer.d.ts.map +0 -1
  38. package/dist/templates/StoryUI/voice/canvas/ComponentRenderer.js +0 -90
  39. package/dist/templates/StoryUI/voice/canvas/ComponentRenderer.tsx +0 -168
  40. package/dist/templates/StoryUI/voice/canvas/componentRegistry.d.ts +0 -21
  41. package/dist/templates/StoryUI/voice/canvas/componentRegistry.d.ts.map +0 -1
  42. package/dist/templates/StoryUI/voice/canvas/componentRegistry.js +0 -24
  43. package/dist/templates/StoryUI/voice/canvas/componentRegistry.ts +0 -30
  44. package/dist/templates/StoryUI/voice/canvas/operations.d.ts +0 -8
  45. package/dist/templates/StoryUI/voice/canvas/operations.d.ts.map +0 -1
  46. package/dist/templates/StoryUI/voice/canvas/operations.js +0 -189
  47. package/dist/templates/StoryUI/voice/canvas/operations.ts +0 -233
  48. package/dist/templates/StoryUI/voice/canvas/types.d.ts +0 -89
  49. package/dist/templates/StoryUI/voice/canvas/types.d.ts.map +0 -1
  50. package/dist/templates/StoryUI/voice/canvas/types.js +0 -2
  51. package/dist/templates/StoryUI/voice/canvas/types.ts +0 -106
  52. package/templates/StoryUI/voice/canvas/ComponentRenderer.tsx +0 -168
  53. package/templates/StoryUI/voice/canvas/componentRegistry.ts +0 -30
  54. package/templates/StoryUI/voice/canvas/operations.ts +0 -233
  55. package/templates/StoryUI/voice/canvas/types.ts +0 -106
package/dist/cli/setup.js CHANGED
@@ -206,21 +206,21 @@ const LLM_PROVIDERS = {
206
206
  claude: {
207
207
  name: 'Claude (Anthropic)',
208
208
  envKey: 'ANTHROPIC_API_KEY',
209
- models: ['claude-opus-4-6', 'claude-sonnet-4-6', 'claude-opus-4-20250514', 'claude-sonnet-4-5-20250929', 'claude-haiku-4-5-20251001'],
209
+ models: ['claude-opus-4-6', 'claude-sonnet-4-6', 'claude-haiku-4-5-20251001'],
210
210
  docsUrl: 'https://console.anthropic.com/',
211
211
  description: 'Recommended - Best for complex reasoning and code quality'
212
212
  },
213
213
  openai: {
214
214
  name: 'OpenAI (GPT)',
215
215
  envKey: 'OPENAI_API_KEY',
216
- models: ['gpt-4.1', 'gpt-4.1-mini', 'o3', 'o4-mini', 'gpt-4o', 'gpt-4o-mini'],
216
+ models: ['gpt-5.4', 'gpt-5.4-mini', 'o4-mini'],
217
217
  docsUrl: 'https://platform.openai.com/api-keys',
218
218
  description: 'Versatile and fast'
219
219
  },
220
220
  gemini: {
221
221
  name: 'Google Gemini',
222
222
  envKey: 'GEMINI_API_KEY',
223
- models: ['gemini-3.1-pro-preview', 'gemini-2.5-pro', 'gemini-2.5-flash', 'gemini-2.5-flash-lite', 'gemini-3-flash-preview'],
223
+ models: ['gemini-3.1-pro-preview', 'gemini-3-flash-preview', 'gemini-2.5-flash'],
224
224
  docsUrl: 'https://aistudio.google.com/app/apikey',
225
225
  description: 'Cost-effective with good performance'
226
226
  }
@@ -23,10 +23,8 @@ import { getProviders, getModels, configureProviderRoute, validateApiKey, setDef
23
23
  import { listFrameworks, detectCurrentFramework, getFrameworkDetails, validateStoryForFramework, postProcessStoryForFramework, } from './routes/frameworks.js';
24
24
  import mcpRemoteRouter from './routes/mcpRemote.js';
25
25
  // Voice Canvas endpoints
26
- import { canvasIntentHandler, warmCanvasComponentCache } from './routes/canvasIntent.js';
27
26
  import { canvasSaveHandler } from './routes/canvasSave.js';
28
27
  import { canvasGenerateHandler, ensureVoiceCanvasStory } from './routes/canvasGenerate.js';
29
- import { canvasPreviewHandler } from './routes/canvasPreview.js';
30
28
  import { getAdapterRegistry } from '../story-generator/framework-adapters/index.js';
31
29
  // Manifest — story ↔ chat source of truth
32
30
  import { manifestGetHandler, manifestPatchHandler, manifestDeleteHandler, manifestReconcileHandler, manifestPollHandler, } from './routes/manifest.js';
@@ -110,9 +108,7 @@ app.post('/mcp/generate-story', generateStoryFromPrompt);
110
108
  app.post('/mcp/generate-story-stream', generateStoryFromPromptStream);
111
109
  // Voice Canvas endpoints
112
110
  app.post('/mcp/canvas-generate', canvasGenerateHandler); // generate + write voice-canvas.stories.tsx
113
- app.post('/mcp/canvas-preview', canvasPreviewHandler); // undo/redo: rewrite voice-canvas.stories.tsx
114
111
  app.post('/mcp/canvas-save', canvasSaveHandler); // save canvas to named .stories.tsx
115
- app.post('/mcp/canvas-intent', canvasIntentHandler); // legacy (kept for compatibility)
116
112
  // Manifest — story ↔ chat source of truth
117
113
  // NOTE: /reconcile must be registered BEFORE /:fileName to avoid route conflict
118
114
  app.get('/story-ui/manifest/poll', manifestPollHandler);
@@ -883,8 +879,6 @@ if (storybookProxyEnabled) {
883
879
  app.listen(PORT, () => {
884
880
  console.error(`MCP server running on port ${PORT}`);
885
881
  console.error(`Stories will be generated to: ${config.generatedStoriesPath}`);
886
- // Pre-warm canvas component cache in background so first voice request is fast
887
- warmCanvasComponentCache().catch(() => { });
888
882
  // Ensure voice-canvas scratchpad story file exists before client polling starts.
889
883
  // If it's missing, the first canvas generate creates it, triggering a false-positive
890
884
  // "externally generated story" detection which reloads the page and kills Voice Canvas.
@@ -15,11 +15,33 @@
15
15
  */
16
16
  import { Request, Response } from 'express';
17
17
  export declare const VOICE_CANVAS_STORY_ID = "generated-voice-canvas--default";
18
- export declare function ensureReactLive(): void;
18
+ export declare function ensureReactLive(): Promise<void>;
19
19
  /**
20
20
  * Write the static voice-canvas story template if it doesn't exist yet.
21
21
  * Subsequent calls are no-ops — the file never changes after initial creation.
22
22
  */
23
23
  export declare function ensureVoiceCanvasStory(storiesDir: string): void;
24
+ /**
25
+ * If the LLM forgot to add a render() call (required by react-live noInline mode),
26
+ * detect the last defined PascalCase component and append render(<ComponentName />).
27
+ * This prevents the "No-Inline evaluations must call render" error when voice input
28
+ * is ambiguous or short and the LLM skips the final line.
29
+ */
30
+ export declare function ensureRenderCall(code: string): string;
31
+ /**
32
+ * Extract the canvas component code from the LLM response.
33
+ * Handles markdown code fences and stray text.
34
+ */
35
+ export declare function extractCanvasCode(response: string): string;
36
+ /**
37
+ * Scan LLM-generated canvas code for dangerous patterns and neutralize them.
38
+ *
39
+ * Instead of rejecting the entire response, each dangerous token is replaced
40
+ * with a safe alternative (typically a comment + `undefined` or `void(`) so
41
+ * the rest of the generated JSX remains functional.
42
+ *
43
+ * Returns the sanitized code string. Logs a warning for every pattern found.
44
+ */
45
+ export declare function sanitizeCanvasCode(code: string): string;
24
46
  export declare function canvasGenerateHandler(req: Request, res: Response): Promise<Response<any, Record<string, any>>>;
25
47
  //# sourceMappingURL=canvasGenerate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"canvasGenerate.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/canvasGenerate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAW5C,eAAO,MAAM,qBAAqB,oCAAoC,CAAC;AAmIvE,wBAAgB,eAAe,IAAI,IAAI,CAuBtC;AAID;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAU/D;AAyCD,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,+CAwEtE"}
1
+ {"version":3,"file":"canvasGenerate.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/canvasGenerate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAkB5C,eAAO,MAAM,qBAAqB,oCAAoC,CAAC;AAuIvE,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAmCrD;AAID;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAU/D;AAID;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOrD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAc1D;AAgED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAsBvD;AAID,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,+CA0GtE"}
@@ -15,12 +15,17 @@
15
15
  */
16
16
  import fs from 'fs';
17
17
  import path from 'path';
18
- import { execSync } from 'child_process';
18
+ import { exec } from 'child_process';
19
+ import { promisify } from 'util';
20
+ const execAsync = promisify(exec);
19
21
  import { loadUserConfig } from '../../story-generator/configLoader.js';
20
22
  import { EnhancedComponentDiscovery } from '../../story-generator/enhancedComponentDiscovery.js';
21
23
  import { buildClaudePrompt } from '../../story-generator/promptGenerator.js';
22
24
  import { chatCompletion } from '../../story-generator/llm-providers/story-llm-service.js';
23
25
  import { logger } from '../../story-generator/logger.js';
26
+ // ── Component discovery cache ─────────────────────────────────
27
+ let _componentCache = null;
28
+ const COMPONENT_CACHE_TTL = 300000; // 5 minutes
24
29
  // ── Constants ─────────────────────────────────────────────────
25
30
  export const VOICE_CANVAS_STORY_ID = 'generated-voice-canvas--default';
26
31
  const VOICE_CANVAS_STORY_FILE = 'voice-canvas.stories.tsx';
@@ -122,6 +127,8 @@ export const Default: StoryObj = {
122
127
 
123
128
  useEffect(() => {
124
129
  const handler = (e: MessageEvent) => {
130
+ // Only accept messages from same origin to prevent cross-origin code injection
131
+ if (e.origin !== window.location.origin) return;
125
132
  if (e.data?.type === 'VOICE_CANVAS_UPDATE' && typeof e.data.code === 'string') {
126
133
  setCode(e.data.code);
127
134
  try { localStorage.setItem('${LS_KEY}', e.data.code); } catch {}
@@ -149,31 +156,43 @@ export const Default: StoryObj = {
149
156
  * Detects pnpm / yarn / npm automatically.
150
157
  */
151
158
  let reactLiveChecked = false;
152
- export function ensureReactLive() {
159
+ let reactLiveInstalling = null;
160
+ export async function ensureReactLive() {
153
161
  if (reactLiveChecked)
154
162
  return;
155
- reactLiveChecked = true;
163
+ // If another request is already installing, wait for it
164
+ if (reactLiveInstalling)
165
+ return reactLiveInstalling;
156
166
  const cwd = process.cwd();
157
167
  const reactLiveDir = path.join(cwd, 'node_modules', 'react-live');
158
- if (fs.existsSync(reactLiveDir))
168
+ if (fs.existsSync(reactLiveDir)) {
169
+ reactLiveChecked = true;
159
170
  return;
171
+ }
160
172
  logger.log('[canvas-generate] react-live not found — installing...');
161
- try {
162
- let cmd = 'npm install react-live --save';
163
- if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'))) {
164
- cmd = 'pnpm add react-live';
173
+ reactLiveInstalling = (async () => {
174
+ try {
175
+ let cmd = 'npm install react-live --save';
176
+ if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'))) {
177
+ cmd = 'pnpm add react-live';
178
+ }
179
+ else if (fs.existsSync(path.join(cwd, 'yarn.lock'))) {
180
+ cmd = 'yarn add react-live';
181
+ }
182
+ await execAsync(cmd, { cwd });
183
+ reactLiveChecked = true;
184
+ logger.log('[canvas-generate] react-live installed successfully');
165
185
  }
166
- else if (fs.existsSync(path.join(cwd, 'yarn.lock'))) {
167
- cmd = 'yarn add react-live';
186
+ catch (err) {
187
+ const msg = err instanceof Error ? err.message : String(err);
188
+ logger.error('[canvas-generate] Could not auto-install react-live', { error: msg });
189
+ logger.log('[canvas-generate] Run manually: npm install react-live');
168
190
  }
169
- execSync(cmd, { cwd, stdio: 'pipe' });
170
- logger.log('[canvas-generate] react-live installed successfully');
171
- }
172
- catch (err) {
173
- const msg = err instanceof Error ? err.message : String(err);
174
- logger.error('[canvas-generate] Could not auto-install react-live', { error: msg });
175
- logger.log('[canvas-generate] Run manually: npm install react-live');
176
- }
191
+ finally {
192
+ reactLiveInstalling = null;
193
+ }
194
+ })();
195
+ return reactLiveInstalling;
177
196
  }
178
197
  // ── Write story to disk (once) ────────────────────────────────
179
198
  /**
@@ -198,7 +217,7 @@ export function ensureVoiceCanvasStory(storiesDir) {
198
217
  * This prevents the "No-Inline evaluations must call render" error when voice input
199
218
  * is ambiguous or short and the LLM skips the final line.
200
219
  */
201
- function ensureRenderCall(code) {
220
+ export function ensureRenderCall(code) {
202
221
  if (/\brender\s*\(/.test(code))
203
222
  return code;
204
223
  // Find the last PascalCase component/const defined in the code
@@ -210,7 +229,7 @@ function ensureRenderCall(code) {
210
229
  * Extract the canvas component code from the LLM response.
211
230
  * Handles markdown code fences and stray text.
212
231
  */
213
- function extractCanvasCode(response) {
232
+ export function extractCanvasCode(response) {
214
233
  let code;
215
234
  // Prefer explicit code fence
216
235
  const fenceMatch = response.match(/```(?:jsx|tsx|js|ts)?\n([\s\S]+?)\n```/);
@@ -224,13 +243,112 @@ function extractCanvasCode(response) {
224
243
  }
225
244
  return ensureRenderCall(code);
226
245
  }
246
+ // ── Security sanitization ─────────────────────────────────────
247
+ /**
248
+ * Dangerous patterns that must be neutralized in LLM-generated canvas code.
249
+ *
250
+ * Each entry defines a regex (applied with the global flag) and a replacement
251
+ * string. The replacement comments out the dangerous call so the surrounding
252
+ * code still parses — this avoids rejecting an entire response because the LLM
253
+ * happened to mention one of these tokens inside a string literal or comment.
254
+ *
255
+ * Categories covered:
256
+ * - Arbitrary code execution (eval, Function constructor)
257
+ * - Cookie / domain access
258
+ * - Storage APIs (localStorage, sessionStorage)
259
+ * - Network requests (fetch, XMLHttpRequest, WebSocket)
260
+ * - Location manipulation
261
+ * - Script injection
262
+ * - Unsafe React patterns (dangerouslySetInnerHTML)
263
+ * - Prototype pollution (__proto__, constructor.prototype)
264
+ * - Dynamic / CommonJS imports
265
+ */
266
+ const DANGEROUS_PATTERNS = [
267
+ // Arbitrary code execution
268
+ { pattern: /\beval\s*\(/g, label: 'eval()', replacement: '/* [sanitized: eval] */void(' },
269
+ { pattern: /\bnew\s+Function\s*\(/g, label: 'new Function()', replacement: '/* [sanitized: new Function] */void(' },
270
+ { pattern: /\bFunction\s*\(/g, label: 'Function()', replacement: '/* [sanitized: Function] */void(' },
271
+ // Cookie / domain access
272
+ { pattern: /\bdocument\.cookie\b/g, label: 'document.cookie', replacement: '/* [sanitized: document.cookie] */undefined' },
273
+ { pattern: /\bdocument\.domain\b/g, label: 'document.domain', replacement: '/* [sanitized: document.domain] */undefined' },
274
+ // Storage APIs
275
+ { pattern: /\blocalStorage\b/g, label: 'localStorage', replacement: '/* [sanitized: localStorage] */undefined' },
276
+ { pattern: /\bsessionStorage\b/g, label: 'sessionStorage', replacement: '/* [sanitized: sessionStorage] */undefined' },
277
+ // Network requests
278
+ { pattern: /\bfetch\s*\(/g, label: 'fetch()', replacement: '/* [sanitized: fetch] */void(' },
279
+ { pattern: /\bnew\s+XMLHttpRequest\b/g, label: 'XMLHttpRequest', replacement: '/* [sanitized: XMLHttpRequest] */undefined' },
280
+ { pattern: /\bXMLHttpRequest\b/g, label: 'XMLHttpRequest', replacement: '/* [sanitized: XMLHttpRequest] */undefined' },
281
+ { pattern: /\bnew\s+WebSocket\s*\(/g, label: 'WebSocket', replacement: '/* [sanitized: WebSocket] */void(' },
282
+ { pattern: /\bWebSocket\s*\(/g, label: 'WebSocket', replacement: '/* [sanitized: WebSocket] */void(' },
283
+ // Location manipulation
284
+ { pattern: /\bwindow\.location\b/g, label: 'window.location', replacement: '/* [sanitized: window.location] */undefined' },
285
+ // Script injection
286
+ { pattern: /<script\b/gi, label: '<script>', replacement: '/* [sanitized: script tag] */undefined' },
287
+ // Unsafe React patterns
288
+ { pattern: /\bdangerouslySetInnerHTML\b/g, label: 'dangerouslySetInnerHTML', replacement: '/* [sanitized: dangerouslySetInnerHTML] */undefined' },
289
+ // Prototype pollution
290
+ { pattern: /__proto__/g, label: '__proto__', replacement: '/* [sanitized: __proto__] */undefined' },
291
+ { pattern: /\bconstructor\.prototype\b/g, label: 'constructor.prototype', replacement: '/* [sanitized: constructor.prototype] */undefined' },
292
+ // Dynamic imports
293
+ { pattern: /\bimport\s*\(/g, label: 'dynamic import()', replacement: '/* [sanitized: dynamic import] */void(' },
294
+ // CommonJS require
295
+ { pattern: /\brequire\s*\(/g, label: 'require()', replacement: '/* [sanitized: require] */void(' },
296
+ ];
297
+ /**
298
+ * Scan LLM-generated canvas code for dangerous patterns and neutralize them.
299
+ *
300
+ * Instead of rejecting the entire response, each dangerous token is replaced
301
+ * with a safe alternative (typically a comment + `undefined` or `void(`) so
302
+ * the rest of the generated JSX remains functional.
303
+ *
304
+ * Returns the sanitized code string. Logs a warning for every pattern found.
305
+ */
306
+ export function sanitizeCanvasCode(code) {
307
+ let sanitized = code;
308
+ const found = [];
309
+ for (const { pattern, label, replacement } of DANGEROUS_PATTERNS) {
310
+ // Reset lastIndex in case the regex was used before (global flag)
311
+ pattern.lastIndex = 0;
312
+ if (pattern.test(sanitized)) {
313
+ found.push(label);
314
+ // Reset again before replace — .test() advances lastIndex
315
+ pattern.lastIndex = 0;
316
+ sanitized = sanitized.replace(pattern, replacement);
317
+ }
318
+ }
319
+ if (found.length > 0) {
320
+ logger.warn(`[canvas-generate] Sanitized ${found.length} dangerous pattern(s) from LLM output: ${found.join(', ')}`);
321
+ }
322
+ return sanitized;
323
+ }
227
324
  // ── Handler ───────────────────────────────────────────────────
228
325
  export async function canvasGenerateHandler(req, res) {
229
326
  try {
230
- const { prompt, canvasCode, provider, model, conversationHistory = [], } = req.body;
327
+ let { prompt, canvasCode, provider, model, conversationHistory = [], } = req.body;
231
328
  if (!prompt || typeof prompt !== 'string') {
232
329
  return res.status(400).json({ error: 'prompt is required' });
233
330
  }
331
+ // ── Request body size limits (truncate, don't reject) ──────
332
+ const MAX_PROMPT = 5000;
333
+ const MAX_CANVAS_CODE = 50000;
334
+ const MAX_HISTORY_ENTRIES = 50;
335
+ const MAX_HISTORY_CONTENT = 10000;
336
+ if (prompt.length > MAX_PROMPT) {
337
+ prompt = prompt.slice(0, MAX_PROMPT);
338
+ }
339
+ if (canvasCode && typeof canvasCode === 'string' && canvasCode.length > MAX_CANVAS_CODE) {
340
+ canvasCode = canvasCode.slice(0, MAX_CANVAS_CODE);
341
+ }
342
+ if (Array.isArray(conversationHistory)) {
343
+ if (conversationHistory.length > MAX_HISTORY_ENTRIES) {
344
+ conversationHistory = conversationHistory.slice(-MAX_HISTORY_ENTRIES);
345
+ }
346
+ for (const entry of conversationHistory) {
347
+ if (entry && typeof entry.content === 'string' && entry.content.length > MAX_HISTORY_CONTENT) {
348
+ entry.content = entry.content.slice(0, MAX_HISTORY_CONTENT);
349
+ }
350
+ }
351
+ }
234
352
  // Load config + discover components — same quality context as standard generation
235
353
  const config = loadUserConfig();
236
354
  // Voice Canvas requires React — it uses react-live to render JSX in the browser.
@@ -239,8 +357,16 @@ export async function canvasGenerateHandler(req, res) {
239
357
  error: `Voice Canvas is only available for React-based Storybook projects. Current framework: ${config.componentFramework}`,
240
358
  });
241
359
  }
242
- const discovery = new EnhancedComponentDiscovery(config);
243
- const components = await discovery.discoverAll();
360
+ let components;
361
+ const now = Date.now();
362
+ if (_componentCache && now - _componentCache.timestamp < COMPONENT_CACHE_TTL) {
363
+ components = _componentCache.components;
364
+ }
365
+ else {
366
+ const discovery = new EnhancedComponentDiscovery(config);
367
+ components = await discovery.discoverAll();
368
+ _componentCache = { components, timestamp: now };
369
+ }
244
370
  // Build the system prompt using the standard prompt pipeline
245
371
  const baseSystemPrompt = await buildClaudePrompt(prompt, config, components);
246
372
  const systemPrompt = baseSystemPrompt + '\n' + CANVAS_MODE_SUFFIX;
@@ -264,10 +390,14 @@ export async function canvasGenerateHandler(req, res) {
264
390
  maxTokens: 4096,
265
391
  temperature: 0.3,
266
392
  });
267
- // Extract the canvas code from the LLM response
268
- const result = extractCanvasCode(response);
393
+ // Extract the canvas code from the LLM response and sanitize it.
394
+ // sanitizeCanvasCode neutralizes dangerous patterns (eval, fetch, script
395
+ // injection, prototype pollution, etc.) before the code reaches the
396
+ // client where react-live would execute it as arbitrary JS.
397
+ const rawCode = extractCanvasCode(response);
398
+ const result = sanitizeCanvasCode(rawCode);
269
399
  // Ensure react-live is installed and story template exists (no-ops after first run)
270
- ensureReactLive();
400
+ await ensureReactLive();
271
401
  const storiesDir = config.generatedStoriesPath || './src/stories/generated/';
272
402
  ensureVoiceCanvasStory(storiesDir);
273
403
  logger.log(`[canvas-generate] Generated ${result.split('\n').length} lines for: "${prompt.slice(0, 60)}"`);
@@ -9,5 +9,12 @@
9
9
  * Returns: { fileName, filePath, code }
10
10
  */
11
11
  import { Request, Response } from 'express';
12
+ /**
13
+ * Convert a react-live canvas component (JSX string) to a proper .stories.tsx file.
14
+ * The input is in the format: `const Canvas = () => { ... }; render(<Canvas />);`
15
+ */
16
+ export declare function jsxCodeToStory(jsxCode: string, title: string, importPath: string): string;
17
+ /** Derive a readable title from the last voice/text prompt. */
18
+ export declare function titleFromPrompt(prompt: string): string;
12
19
  export declare function canvasSaveHandler(req: Request, res: Response): Promise<Response<any, Record<string, any>>>;
13
20
  //# sourceMappingURL=canvasSave.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"canvasSave.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/canvasSave.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AA0M5C,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,+CAgGlE"}
1
+ {"version":3,"file":"canvasSave.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/canvasSave.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AA0I5C;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAwCzF;AAID,+DAA+D;AAC/D,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAatD;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,+CAiHlE"}
@@ -125,7 +125,7 @@ ${jsx}
125
125
  * Convert a react-live canvas component (JSX string) to a proper .stories.tsx file.
126
126
  * The input is in the format: `const Canvas = () => { ... }; render(<Canvas />);`
127
127
  */
128
- function jsxCodeToStory(jsxCode, title, importPath) {
128
+ export function jsxCodeToStory(jsxCode, title, importPath) {
129
129
  // Remove the render(<Canvas />) call at the end
130
130
  const cleanCode = jsxCode.replace(/\nrender\s*\(<Canvas\s*\/>\);?\s*$/, '').trim();
131
131
  // Extract component names used in JSX (uppercase identifiers after '<')
@@ -166,7 +166,7 @@ function jsxCodeToStory(jsxCode, title, importPath) {
166
166
  }
167
167
  // ── Express handler ─────────────────────────────────────────
168
168
  /** Derive a readable title from the last voice/text prompt. */
169
- function titleFromPrompt(prompt) {
169
+ export function titleFromPrompt(prompt) {
170
170
  // Strip filler words, take first ~6 meaningful words, title-case
171
171
  const stop = new Set(['a', 'an', 'the', 'with', 'and', 'for', 'of', 'to', 'in', 'on', 'at', 'by']);
172
172
  const words = prompt
@@ -183,15 +183,29 @@ function titleFromPrompt(prompt) {
183
183
  }
184
184
  export async function canvasSaveHandler(req, res) {
185
185
  try {
186
- const { tree, jsxCode, title: rawTitle, lastPrompt } = req.body;
186
+ const { tree, jsxCode, title: rawTitle, lastPrompt: rawLastPrompt } = req.body;
187
+ // ── Request body size limits ───────────────────────────────
188
+ const MAX_JSX_CODE = 100000;
189
+ const MAX_TITLE = 200;
190
+ const MAX_LAST_PROMPT = 5000;
191
+ if (jsxCode && typeof jsxCode === 'string' && jsxCode.length > MAX_JSX_CODE) {
192
+ return res.status(400).json({ error: `jsxCode exceeds maximum length of ${MAX_JSX_CODE} characters` });
193
+ }
194
+ // Truncate title and lastPrompt if needed (safe to trim these)
195
+ const safeTitle = (rawTitle && typeof rawTitle === 'string')
196
+ ? rawTitle.slice(0, MAX_TITLE)
197
+ : rawTitle;
198
+ const lastPrompt = (rawLastPrompt && typeof rawLastPrompt === 'string' && rawLastPrompt.length > MAX_LAST_PROMPT)
199
+ ? rawLastPrompt.slice(0, MAX_LAST_PROMPT)
200
+ : rawLastPrompt;
187
201
  // Auto-generate title from last prompt if not provided
188
- const title = (rawTitle && typeof rawTitle === 'string' && rawTitle.trim())
189
- ? rawTitle.trim()
202
+ const title = (safeTitle && typeof safeTitle === 'string' && safeTitle.trim())
203
+ ? safeTitle.trim()
190
204
  : (lastPrompt && typeof lastPrompt === 'string' && lastPrompt.trim())
191
205
  ? titleFromPrompt(lastPrompt.trim())
192
- : 'Voice Canvas';
206
+ : `Canvas ${new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' })}`;
193
207
  const config = loadUserConfig();
194
- const importPath = config.importPath || '@mantine/core';
208
+ const importPath = config.importPath || '';
195
209
  const storiesDir = config.generatedStoriesPath || './src/stories/generated/';
196
210
  let code;
197
211
  if (jsxCode && typeof jsxCode === 'string' && jsxCode.trim()) {
@@ -1 +1 @@
1
- {"version":3,"file":"claude-provider.d.ts","sourceRoot":"","sources":["../../../story-generator/llm-providers/claude-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;AA0HrD,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;IAkE1E,UAAU,CACf,QAAQ,EAAE,WAAW,EAAE,EACvB,OAAO,CAAC,EAAE,WAAW,GACpB,aAAa,CAAC,WAAW,CAAC;IAuGvB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAgD/D,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,cAAc;IAgCtB,OAAO,CAAC,eAAe;IAoBvB,OAAO,CAAC,aAAa;IAiBrB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAMrC;AAGD,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,cAAc,CAErF"}
1
+ {"version":3,"file":"claude-provider.d.ts","sourceRoot":"","sources":["../../../story-generator/llm-providers/claude-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;AAmFrD,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;IAkE1E,UAAU,CACf,QAAQ,EAAE,WAAW,EAAE,EACvB,OAAO,CAAC,EAAE,WAAW,GACpB,aAAa,CAAC,WAAW,CAAC;IAuGvB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAgD/D,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,cAAc;IAgCtB,OAAO,CAAC,eAAe;IAoBvB,OAAO,CAAC,aAAa;IAiBrB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAMrC;AAGD,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,cAAc,CAErF"}
@@ -33,32 +33,6 @@ const CLAUDE_MODELS = [
33
33
  inputPricePer1kTokens: 0.003,
34
34
  outputPricePer1kTokens: 0.015,
35
35
  },
36
- {
37
- id: 'claude-opus-4-20250514',
38
- name: 'Claude Opus 4',
39
- provider: 'claude',
40
- contextWindow: 200000,
41
- maxOutputTokens: 32000,
42
- supportsVision: true,
43
- supportsDocuments: true,
44
- supportsFunctionCalling: true,
45
- supportsStreaming: true,
46
- inputPricePer1kTokens: 0.015,
47
- outputPricePer1kTokens: 0.075,
48
- },
49
- {
50
- id: 'claude-sonnet-4-5-20250929',
51
- name: 'Claude Sonnet 4.5',
52
- provider: 'claude',
53
- contextWindow: 200000,
54
- maxOutputTokens: 16000,
55
- supportsVision: true,
56
- supportsDocuments: true,
57
- supportsFunctionCalling: true,
58
- supportsStreaming: true,
59
- inputPricePer1kTokens: 0.003,
60
- outputPricePer1kTokens: 0.015,
61
- },
62
36
  {
63
37
  id: 'claude-haiku-4-5-20251001',
64
38
  name: 'Claude Haiku 4.5',
@@ -70,19 +44,6 @@ const CLAUDE_MODELS = [
70
44
  supportsFunctionCalling: true,
71
45
  supportsStreaming: true,
72
46
  inputPricePer1kTokens: 0.0008,
73
- outputPricePer1kTokens: 0.004,
74
- },
75
- {
76
- id: 'claude-sonnet-4-20250514',
77
- name: 'Claude Sonnet 4',
78
- provider: 'claude',
79
- contextWindow: 200000,
80
- maxOutputTokens: 16000,
81
- supportsVision: true,
82
- supportsDocuments: true,
83
- supportsFunctionCalling: true,
84
- supportsStreaming: true,
85
- inputPricePer1kTokens: 0.003,
86
47
  outputPricePer1kTokens: 0.015,
87
48
  },
88
49
  ];
@@ -1 +1 @@
1
- {"version":3,"file":"gemini-provider.d.ts","sourceRoot":"","sources":["../../../story-generator/llm-providers/gemini-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;AAkHrD,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;IAU5C,OAAO,CAAC,SAAS;IAKX,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IA6D1E,UAAU,CACf,QAAQ,EAAE,WAAW,EAAE,EACvB,OAAO,CAAC,EAAE,WAAW,GACpB,aAAa,CAAC,WAAW,CAAC;IA+GvB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA8C/D,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,cAAc;IAqCtB,OAAO,CAAC,eAAe;IAmBvB,OAAO,CAAC,eAAe;IAiBvB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAKrC;AAGD,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,cAAc,CAErF"}
1
+ {"version":3,"file":"gemini-provider.d.ts","sourceRoot":"","sources":["../../../story-generator/llm-providers/gemini-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;AAuFrD,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;IAU5C,OAAO,CAAC,SAAS;IAKX,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IA8D1E,UAAU,CACf,QAAQ,EAAE,WAAW,EAAE,EACvB,OAAO,CAAC,EAAE,WAAW,GACpB,aAAa,CAAC,WAAW,CAAC;IAgHvB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA+C/D,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,cAAc;IAqCtB,OAAO,CAAC,eAAe;IAmBvB,OAAO,CAAC,eAAe;IAiBvB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAKrC;AAGD,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,cAAc,CAErF"}
@@ -23,8 +23,8 @@ const GEMINI_MODELS = [
23
23
  outputPricePer1kTokens: 0.012,
24
24
  },
25
25
  {
26
- id: 'gemini-2.5-pro',
27
- name: 'Gemini 2.5 Pro',
26
+ id: 'gemini-3-flash-preview',
27
+ name: 'Gemini 3 Flash Preview',
28
28
  provider: 'gemini',
29
29
  contextWindow: 1048576,
30
30
  maxOutputTokens: 65536,
@@ -32,9 +32,8 @@ const GEMINI_MODELS = [
32
32
  supportsDocuments: true,
33
33
  supportsFunctionCalling: true,
34
34
  supportsStreaming: true,
35
- supportsReasoning: true,
36
- inputPricePer1kTokens: 0.00125,
37
- outputPricePer1kTokens: 0.01,
35
+ inputPricePer1kTokens: 0.00015,
36
+ outputPricePer1kTokens: 0.0006,
38
37
  },
39
38
  {
40
39
  id: 'gemini-2.5-flash',
@@ -50,35 +49,9 @@ const GEMINI_MODELS = [
50
49
  inputPricePer1kTokens: 0.00015,
51
50
  outputPricePer1kTokens: 0.0006,
52
51
  },
53
- {
54
- id: 'gemini-2.5-flash-lite',
55
- name: 'Gemini 2.5 Flash Lite',
56
- provider: 'gemini',
57
- contextWindow: 1048576,
58
- maxOutputTokens: 65536,
59
- supportsVision: true,
60
- supportsDocuments: true,
61
- supportsFunctionCalling: true,
62
- supportsStreaming: true,
63
- inputPricePer1kTokens: 0.00008,
64
- outputPricePer1kTokens: 0.0003,
65
- },
66
- {
67
- id: 'gemini-3-flash-preview',
68
- name: 'Gemini 3 Flash Preview',
69
- provider: 'gemini',
70
- contextWindow: 1048576,
71
- maxOutputTokens: 65536,
72
- supportsVision: true,
73
- supportsDocuments: true,
74
- supportsFunctionCalling: true,
75
- supportsStreaming: true,
76
- inputPricePer1kTokens: 0.00015,
77
- outputPricePer1kTokens: 0.0006,
78
- },
79
52
  ];
80
- // Default model - Gemini 2.5 Pro (stable flagship, March 2026)
81
- const DEFAULT_MODEL = 'gemini-2.5-pro';
53
+ // Default model - Gemini 3.1 Pro Preview (flagship, March 2026)
54
+ const DEFAULT_MODEL = 'gemini-3.1-pro-preview';
82
55
  // API configuration
83
56
  const GEMINI_API_BASE = 'https://generativelanguage.googleapis.com/v1beta/models';
84
57
  export class GeminiProvider extends BaseLLMProvider {
@@ -124,12 +97,13 @@ export class GeminiProvider extends BaseLLMProvider {
124
97
  parts: [{ text: systemPrompt }],
125
98
  };
126
99
  }
127
- const url = `${this.getApiUrl(model)}?key=${apiKey}`;
100
+ const url = this.getApiUrl(model);
128
101
  try {
129
102
  const response = await fetch(url, {
130
103
  method: 'POST',
131
104
  headers: {
132
105
  'Content-Type': 'application/json',
106
+ 'x-goog-api-key': apiKey,
133
107
  },
134
108
  body: JSON.stringify(requestBody),
135
109
  signal: AbortSignal.timeout(this.config.timeout || 120000),
@@ -174,12 +148,13 @@ export class GeminiProvider extends BaseLLMProvider {
174
148
  parts: [{ text: systemPrompt }],
175
149
  };
176
150
  }
177
- const url = `${this.getApiUrl(model, true)}?key=${apiKey}&alt=sse`;
151
+ const url = `${this.getApiUrl(model, true)}?alt=sse`;
178
152
  try {
179
153
  const response = await fetch(url, {
180
154
  method: 'POST',
181
155
  headers: {
182
156
  'Content-Type': 'application/json',
157
+ 'x-goog-api-key': apiKey,
183
158
  },
184
159
  body: JSON.stringify(requestBody),
185
160
  signal: AbortSignal.timeout(this.config.timeout || 120000),
@@ -251,11 +226,12 @@ export class GeminiProvider extends BaseLLMProvider {
251
226
  async validateApiKey(apiKey) {
252
227
  try {
253
228
  // Make a minimal API call to validate the key
254
- const url = `${this.getApiUrl('gemini-2.0-flash')}?key=${apiKey}`;
229
+ const url = this.getApiUrl('gemini-2.5-flash');
255
230
  const response = await fetch(url, {
256
231
  method: 'POST',
257
232
  headers: {
258
233
  'Content-Type': 'application/json',
234
+ 'x-goog-api-key': apiKey,
259
235
  },
260
236
  body: JSON.stringify({
261
237
  contents: [{ parts: [{ text: 'Hi' }] }],
@@ -143,7 +143,7 @@ export function initializeFromEnv() {
143
143
  if (claudeKey) {
144
144
  registry.configureProvider('claude', {
145
145
  apiKey: claudeKey,
146
- model: process.env.CLAUDE_MODEL || 'claude-sonnet-4-5-20250929',
146
+ model: process.env.CLAUDE_MODEL || 'claude-sonnet-4-6',
147
147
  });
148
148
  logger.info('Claude provider configured from environment');
149
149
  }
@@ -152,7 +152,7 @@ export function initializeFromEnv() {
152
152
  if (openaiKey) {
153
153
  registry.configureProvider('openai', {
154
154
  apiKey: openaiKey,
155
- model: process.env.OPENAI_MODEL || 'gpt-4o',
155
+ model: process.env.OPENAI_MODEL || 'gpt-5.4',
156
156
  organizationId: process.env.OPENAI_ORG_ID,
157
157
  });
158
158
  logger.info('OpenAI provider configured from environment');
@@ -162,7 +162,7 @@ export function initializeFromEnv() {
162
162
  if (geminiKey) {
163
163
  registry.configureProvider('gemini', {
164
164
  apiKey: geminiKey,
165
- model: process.env.GEMINI_MODEL || 'gemini-2.0-flash',
165
+ model: process.env.GEMINI_MODEL || 'gemini-3.1-pro-preview',
166
166
  });
167
167
  logger.info('Gemini provider configured from environment');
168
168
  }
@@ -1 +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;AAgIrD,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;IAmE1E,UAAU,CACf,QAAQ,EAAE,WAAW,EAAE,EACvB,OAAO,CAAC,EAAE,WAAW,GACpB,aAAa,CAAC,WAAW,CAAC;IA6GvB,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"}
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;AAwFrD,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;IAmE1E,UAAU,CACf,QAAQ,EAAE,WAAW,EAAE,EACvB,OAAO,CAAC,EAAE,WAAW,GACpB,aAAa,CAAC,WAAW,CAAC;IA6GvB,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"}