vzcode 1.57.0 → 1.58.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.
package/dist/index.html CHANGED
@@ -20,7 +20,7 @@
20
20
  href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap"
21
21
  rel="stylesheet"
22
22
  />
23
- <script type="module" crossorigin src="/assets/index-DqIc-0Gp.js"></script>
23
+ <script type="module" crossorigin src="/assets/index-CtGus8Qk.js"></script>
24
24
  <link rel="stylesheet" crossorigin href="/assets/index-CE_xP06t.css">
25
25
  </head>
26
26
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vzcode",
3
- "version": "1.57.0",
3
+ "version": "1.58.0",
4
4
  "description": "Multiplayer code editor system",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -11,7 +11,7 @@
11
11
  ],
12
12
  "scripts": {
13
13
  "test": "vitest run",
14
- "test-interactive": "cd test/sampleDirectories/kitchenSink && node --import 'data:text/javascript,import { register } from \"node:module\"; import { pathToFileURL } from \"node:url\"; register(\"ts-node/esm\", pathToFileURL(\"./\"));' ../../../src/server/index.ts",
14
+ "test-interactive": "cd test/sampleDirectories/kitchenSink && node --import \"data:text/javascript,import { register } from 'node:module'; import { pathToFileURL } from 'node:url'; register('ts-node/esm', pathToFileURL('./'));\" ../../../src/server/index.ts",
15
15
  "prettier": "prettier {*.*,**/*.*} --write",
16
16
  "typecheck": "tsc --noEmit",
17
17
  "dev": "cross-env EDITOR_PORT=5173 concurrently \"npm run test-interactive\" \"vite\"",
@@ -186,7 +186,7 @@
186
186
  "globals": "^16.3.0",
187
187
  "npm-check-updates": "^18.0.2",
188
188
  "prettier": "^3.6.2",
189
- "sass": "^1.89.2",
189
+ "sass": "^1.90.0",
190
190
  "ts-node": "^10.9.2",
191
191
  "typescript": "^5.9.2",
192
192
  "vite": "^7.0.6",
@@ -74,6 +74,7 @@ import { getFileExtension } from '../utils/fileExtension';
74
74
  import { SparklesSVG } from '../Icons/SparklesSVG';
75
75
  import { VZCodeContext } from '../VZCodeContext';
76
76
  import { useContext, useMemo } from 'react';
77
+ import { handleAIChatMessage } from '../../server/aiChatHandler';
77
78
 
78
79
  const DEBUG = false;
79
80
 
@@ -194,6 +195,7 @@ export const getOrCreateEditor = async ({
194
195
  rainbowBracketsEnabled = true,
195
196
  setIsAIChatOpen,
196
197
  setAIChatMessage,
198
+ handleSendMessage,
197
199
  }: {
198
200
  // TODO pass this in from the outside
199
201
  paneId?: PaneId;
@@ -229,8 +231,9 @@ export const getOrCreateEditor = async ({
229
231
  view: EditorView,
230
232
  ) => Promise<readonly Diagnostic[]>;
231
233
  rainbowBracketsEnabled?: boolean; // New parameter type
232
- setIsAIChatOpen: any; // TODO fix types
233
- setAIChatMessage: any; // TODO fix types
234
+ setIsAIChatOpen: (isAIChatOpen: boolean) => void;
235
+ setAIChatMessage: (message: string) => void;
236
+ handleSendMessage: any; // TODO fix types
234
237
  }): Promise<ExtendedEditorCacheValue> => {
235
238
  // Cache hit
236
239
 
@@ -500,12 +503,9 @@ export const getOrCreateEditor = async ({
500
503
  root.render(
501
504
  <div
502
505
  onClick={() => {
503
- console.log(
504
- 'TODO set the chat prompt to "Implement the TODO"',
505
- );
506
506
  setIsAIChatOpen(true);
507
- setAIChatMessage('Implement the TODO');
508
- // submitAIChatMessage()
507
+ // setAIChatMessage('Implement the TODO');
508
+ handleSendMessage('Implement the TODO');
509
509
  }}
510
510
  >
511
511
  <SparklesSVG width={14} height={14} />
@@ -45,6 +45,7 @@ export const CodeEditor = ({
45
45
  enableAutoFollow,
46
46
  setIsAIChatOpen,
47
47
  setAIChatMessage,
48
+ handleSendMessage,
48
49
  } = useContext(VZCodeContext);
49
50
 
50
51
  // Set `doc.data.isInteracting` to `true` when the user is interacting
@@ -116,6 +117,7 @@ export const CodeEditor = ({
116
117
  esLintSource,
117
118
  setIsAIChatOpen,
118
119
  setAIChatMessage,
120
+ handleSendMessage,
119
121
  });
120
122
 
121
123
  if (isMounted) {
@@ -1,4 +1,5 @@
1
1
  import {
2
+ Context,
2
3
  createContext,
3
4
  useCallback,
4
5
  useReducer,
@@ -39,6 +40,7 @@ import { useURLSync } from './useURLSync';
39
40
  import { createInitialState, vzReducer } from './vzReducer';
40
41
  import { findPane } from './vzReducer/findPane';
41
42
  import { usePresenceAutoFollow } from './usePresenceAutoFollow';
43
+ import { v4 as uuidv4 } from 'uuid';
42
44
 
43
45
  // This context centralizes all the "smart" logic
44
46
  // to do with the application state. This includes
@@ -195,6 +197,12 @@ export type VZCodeContextValue = {
195
197
  setVoiceChatModalOpen: (state: boolean) => void;
196
198
  aiChatMessage: string;
197
199
  setAIChatMessage: (message: string) => void;
200
+ isLoading: boolean;
201
+ setIsLoading: (state: boolean) => void;
202
+ currentChatId: string;
203
+ aiErrorMessage: string | null;
204
+ setAIErrorMessage: (state: string | null) => void;
205
+ handleSendMessage: (messageToSend?: string) => void;
198
206
 
199
207
  // Auto-fork functions for VizHub integration
200
208
  autoForkAndRetryAI?: (
@@ -207,6 +215,13 @@ export type VZCodeContextValue = {
207
215
  prompt: string;
208
216
  modelName: string;
209
217
  } | null;
218
+
219
+ // Additional widgets that can be rendered in AI chat messages
220
+ additionalWidgets?: React.ComponentType<{
221
+ messageId: string;
222
+ chatId: string;
223
+ canUndo: boolean;
224
+ }>;
210
225
  };
211
226
 
212
227
  export const VZCodeProvider = ({
@@ -233,6 +248,7 @@ export const VZCodeProvider = ({
233
248
  autoForkAndRetryAI,
234
249
  clearStoredAIPrompt,
235
250
  getStoredAIPrompt,
251
+ additionalWidgets,
236
252
  }: {
237
253
  content: VizContent;
238
254
  shareDBDoc: ShareDBDoc<VizContent>;
@@ -264,6 +280,11 @@ export const VZCodeProvider = ({
264
280
  prompt: string;
265
281
  modelName: string;
266
282
  } | null;
283
+ additionalWidgets?: React.ComponentType<{
284
+ messageId: string;
285
+ chatId: string;
286
+ canUndo: boolean;
287
+ }>;
267
288
  }) => {
268
289
  // Auto-run Pretter after local changes.
269
290
  const { prettierError, runPrettierRef } = usePrettier({
@@ -324,6 +345,7 @@ export const VZCodeProvider = ({
324
345
  username,
325
346
  enableAutoFollow,
326
347
  sidebarPresenceIndicators,
348
+ aiChatMode,
327
349
  } = state;
328
350
 
329
351
  const activePane: Pane = findPane(pane, activePaneId);
@@ -479,6 +501,120 @@ export const VZCodeProvider = ({
479
501
 
480
502
  const [aiChatMessage, setAIChatMessage] = useState('');
481
503
 
504
+ const DEBUG = false;
505
+
506
+ const [isLoading, setIsLoading] = useState(false);
507
+ const [currentChatId] = useState(() => uuidv4());
508
+ const [aiErrorMessage, setAIErrorMessage] = useState<
509
+ string | null
510
+ >(null);
511
+
512
+ const handleSendMessage = useCallback(
513
+ async (messageToSend?: string) => {
514
+ DEBUG &&
515
+ console.log(
516
+ 'AIChat: handleSendMessage called with:',
517
+ messageToSend,
518
+ 'aiChatMessage:',
519
+ aiChatMessage,
520
+ );
521
+ const messageContent = messageToSend || aiChatMessage;
522
+
523
+ if (
524
+ !messageContent ||
525
+ typeof messageContent !== 'string' ||
526
+ !messageContent.trim() ||
527
+ isLoading
528
+ ) {
529
+ return;
530
+ }
531
+
532
+ const currentPrompt = aiChatMessage.trim();
533
+ setAIChatMessage('');
534
+ setIsLoading(true);
535
+ setAIErrorMessage(null); // Clear any previous errors
536
+
537
+ // Call backend endpoint for AI response
538
+ // The server will handle all ShareDB operations including adding the user message
539
+ try {
540
+ const response = await fetch(aiChatEndpoint, {
541
+ method: 'POST',
542
+ headers: {
543
+ 'Content-Type': 'application/json',
544
+ },
545
+ body: JSON.stringify({
546
+ ...aiChatOptions,
547
+ vizId: aiChatOptions.vizId,
548
+ content: messageContent.trim(),
549
+ chatId: currentChatId,
550
+ mode: aiChatMode,
551
+ }),
552
+ });
553
+
554
+ if (!response.ok) {
555
+ throw new Error(
556
+ `HTTP error! status: ${response.status}`,
557
+ );
558
+ }
559
+
560
+ // Parse the response to check for errors
561
+ const responseData = await response.json();
562
+
563
+ // Check if the response contains a VizHub error
564
+ if (
565
+ responseData.outcome === 'failure' &&
566
+ responseData.error
567
+ ) {
568
+ const errorMessage = responseData.error.message;
569
+
570
+ // Check if this is the specific permission error that should trigger auto-fork
571
+ if (
572
+ aiErrorMessage ===
573
+ 'You do not have permission to use AI chat on this visualization. Only users with edit access can use this feature. Fork the viz to edit it.'
574
+ ) {
575
+ // Trigger auto-fork instead of showing error
576
+ try {
577
+ await autoForkAndRetryAI?.(
578
+ currentPrompt,
579
+ aiChatMode,
580
+ );
581
+ // If we reach here, the fork was successful and redirect should happen
582
+ return;
583
+ } catch (forkError) {
584
+ console.error('Auto-fork failed:', forkError);
585
+ setAIErrorMessage(
586
+ 'Failed to fork visualization. Please try forking manually.',
587
+ );
588
+ return;
589
+ }
590
+ }
591
+
592
+ // For other errors, show the error message
593
+ setAIErrorMessage(aiErrorMessage);
594
+ return;
595
+ }
596
+
597
+ // The backend handles all ShareDB operations for successful responses
598
+ } catch (error) {
599
+ console.error('Error getting AI response:', error);
600
+ setAIErrorMessage(
601
+ 'Failed to send message. Please try again.',
602
+ );
603
+ } finally {
604
+ setIsLoading(false);
605
+ }
606
+ },
607
+ [
608
+ aiChatMessage,
609
+ isLoading,
610
+ aiChatEndpoint,
611
+ aiChatOptions,
612
+ currentChatId,
613
+ aiChatMode,
614
+ autoForkAndRetryAI,
615
+ ],
616
+ );
617
+
482
618
  // The value provided by this context.
483
619
  const value: VZCodeContextValue = {
484
620
  content,
@@ -589,11 +725,20 @@ export const VZCodeProvider = ({
589
725
 
590
726
  aiChatMessage,
591
727
  setAIChatMessage,
728
+ isLoading,
729
+ setIsLoading,
730
+ currentChatId,
731
+ aiErrorMessage,
732
+ setAIErrorMessage,
733
+ handleSendMessage,
592
734
 
593
735
  // Auto-fork functions for VizHub integration
594
736
  autoForkAndRetryAI,
595
737
  clearStoredAIPrompt,
596
738
  getStoredAIPrompt,
739
+
740
+ // Additional widgets that can be rendered in AI chat messages
741
+ additionalWidgets,
597
742
  };
598
743
 
599
744
  return (
@@ -1,7 +1,12 @@
1
1
  import { timestampToDate } from '@vizhub/viz-utils';
2
2
  import Markdown from 'react-markdown';
3
3
  import remarkGfm from 'remark-gfm';
4
- import { useMemo, memo, useState, useContext } from 'react';
4
+ import React, {
5
+ useMemo,
6
+ memo,
7
+ useState,
8
+ useContext,
9
+ } from 'react';
5
10
  import { DiffView } from './DiffView';
6
11
  import { UnifiedFilesDiff } from '../../../utils/fileDiff';
7
12
  import { enableDiffView } from '../../featureFlags';
@@ -31,8 +36,11 @@ const MessageComponent = ({
31
36
  canUndo,
32
37
  }: MessageProps) => {
33
38
  const [isUndoing, setIsUndoing] = useState(false);
34
- const { aiChatUndoEndpoint, aiChatOptions = {} } =
35
- useContext(VZCodeContext);
39
+ const {
40
+ aiChatUndoEndpoint,
41
+ aiChatOptions = {},
42
+ additionalWidgets,
43
+ } = useContext(VZCodeContext);
36
44
 
37
45
  // Memoize date formatting to avoid repeated computation
38
46
  const formattedTime = useMemo(() => {
@@ -102,7 +110,15 @@ const MessageComponent = ({
102
110
  Object.keys(diffData).length > 0 && (
103
111
  <DiffView diffData={diffData} />
104
112
  )}
105
- {canUndo && beforeFiles && (
113
+ {additionalWidgets &&
114
+ canUndo &&
115
+ chatId &&
116
+ React.createElement(additionalWidgets, {
117
+ messageId: id,
118
+ chatId: chatId,
119
+ canUndo: canUndo,
120
+ })}
121
+ {!additionalWidgets && canUndo && beforeFiles && (
106
122
  <div className="undo-button-container">
107
123
  <button
108
124
  className="undo-button"
@@ -15,28 +15,27 @@ const defaultAIChatEndpoint = '/api/ai-chat/';
15
15
 
16
16
  const DEBUG = false;
17
17
 
18
- const showSuggestedRequests = false;
18
+ const showSuggestedRequests = true;
19
19
 
20
20
  export const AIChat = () => {
21
- const { aiChatMessage, setAIChatMessage } =
22
- useContext(VZCodeContext);
23
-
24
- const [isLoading, setIsLoading] = useState(false);
25
- const [currentChatId] = useState(() => uuidv4());
26
- const [errorMessage, setErrorMessage] = useState<
27
- string | null
28
- >(null);
29
-
30
21
  const {
31
22
  aiChatFocused,
32
23
  content,
33
24
  aiChatEndpoint = defaultAIChatEndpoint,
34
25
  aiChatOptions = {},
35
26
  aiChatMode,
27
+ aiChatMessage,
28
+ isLoading,
29
+ currentChatId,
30
+ aiErrorMessage,
36
31
  setAIChatMode,
37
32
  autoForkAndRetryAI,
38
33
  clearStoredAIPrompt,
39
34
  getStoredAIPrompt,
35
+ setAIChatMessage,
36
+ handleSendMessage,
37
+ setIsLoading,
38
+ setAIErrorMessage,
40
39
  } = useContext(VZCodeContext);
41
40
 
42
41
  // Get current chat data from content
@@ -58,118 +57,6 @@ export const AIChat = () => {
58
57
  // Check if this is the first time opening the chat (no messages)
59
58
  const isEmptyState = rawMessages.length === 0;
60
59
 
61
- const handleSendMessage = useCallback(
62
- async (messageToSend?: string) => {
63
- DEBUG &&
64
- console.log(
65
- 'AIChat: handleSendMessage called with:',
66
- messageToSend,
67
- 'aiChatMessage:',
68
- aiChatMessage,
69
- );
70
- const messageContent = messageToSend || aiChatMessage;
71
-
72
- if (
73
- !messageContent ||
74
- typeof messageContent !== 'string' ||
75
- !messageContent.trim() ||
76
- isLoading
77
- ) {
78
- return;
79
- }
80
-
81
- const currentPrompt = aiChatMessage.trim();
82
- setAIChatMessage('');
83
- setIsLoading(true);
84
- setErrorMessage(null); // Clear any previous errors
85
-
86
- // Call backend endpoint for AI response
87
- // The server will handle all ShareDB operations including adding the user message
88
- try {
89
- const response = await fetch(aiChatEndpoint, {
90
- method: 'POST',
91
- headers: {
92
- 'Content-Type': 'application/json',
93
- },
94
- body: JSON.stringify({
95
- ...aiChatOptions,
96
- vizId: aiChatOptions.vizId,
97
- content: messageContent.trim(),
98
- chatId: currentChatId,
99
- mode: aiChatMode,
100
- }),
101
- });
102
-
103
- if (!response.ok) {
104
- throw new Error(
105
- `HTTP error! status: ${response.status}`,
106
- );
107
- }
108
-
109
- // Parse the response to check for errors
110
- const responseData = await response.json();
111
-
112
- // Check if the response contains a VizHub error
113
- if (
114
- responseData.outcome === 'failure' &&
115
- responseData.error
116
- ) {
117
- const errorMessage = responseData.error.message;
118
-
119
- // Check if this is the specific permission error that should trigger auto-fork
120
- if (
121
- errorMessage ===
122
- 'You do not have permission to use AI chat on this visualization. Only users with edit access can use this feature. Fork the viz to edit it.'
123
- ) {
124
- // Trigger auto-fork instead of showing error
125
- try {
126
- await autoForkAndRetryAI?.(
127
- currentPrompt,
128
- aiChatMode,
129
- );
130
- // If we reach here, the fork was successful and redirect should happen
131
- return;
132
- } catch (forkError) {
133
- console.error('Auto-fork failed:', forkError);
134
- setErrorMessage(
135
- 'Failed to fork visualization. Please try forking manually.',
136
- );
137
- return;
138
- }
139
- }
140
-
141
- // For other errors, show the error message
142
- setErrorMessage(errorMessage);
143
- return;
144
- }
145
-
146
- // The backend handles all ShareDB operations for successful responses
147
- } catch (error) {
148
- console.error('Error getting AI response:', error);
149
- // Fail silently in case of timeout,
150
- // which happens frequently with the longer running LLM calls.
151
- // TODO refactor this so that the network request to the server
152
- // always returns immediately, and we track the `isLoading` state
153
- // within the ShareDB document itself.
154
- //
155
- // setErrorMessage(
156
- // 'Failed to send message. Please try again.',
157
- // );
158
- } finally {
159
- setIsLoading(false);
160
- }
161
- },
162
- [
163
- aiChatMessage,
164
- isLoading,
165
- aiChatEndpoint,
166
- aiChatOptions,
167
- currentChatId,
168
- aiChatMode,
169
- autoForkAndRetryAI,
170
- ],
171
- );
172
-
173
60
  // Check for stored AI prompt on component mount (post-fork restoration)
174
61
  useEffect(() => {
175
62
  DEBUG &&
@@ -350,18 +237,18 @@ export const AIChat = () => {
350
237
  aiScratchpad={aiScratchpad}
351
238
  />
352
239
  )}
353
- {errorMessage && (
240
+ {aiErrorMessage && (
354
241
  <div className="ai-chat-error">
355
242
  <div className="ai-chat-error-content">
356
243
  <span className="ai-chat-error-icon">
357
244
  ⚠️
358
245
  </span>
359
246
  <span className="ai-chat-error-message">
360
- {errorMessage}
247
+ {aiErrorMessage}
361
248
  </span>
362
249
  <button
363
250
  className="ai-chat-error-dismiss"
364
- onClick={() => setErrorMessage(null)}
251
+ onClick={() => setAIErrorMessage(null)}
365
252
  aria-label="Dismiss error"
366
253
  >
367
254
  ×
@@ -256,7 +256,8 @@ export const addDiffToAIMessage = (
256
256
  shareDBDoc: ShareDBDoc<VizContent>,
257
257
  chatId: VizChatId,
258
258
  diffData: any,
259
- beforeFiles?: VizFiles, // Optional snapshot for undo functionality
259
+ beforeFiles?: VizFiles, // Optional snapshot for undo functionality (legacy)
260
+ beforeCommitId?: string, // Commit ID before AI changes for VizHub integration
260
261
  ) => {
261
262
  const chat = shareDBDoc.data.chats[chatId];
262
263
  const messages = [...chat.messages];
@@ -270,7 +271,8 @@ export const addDiffToAIMessage = (
270
271
  const newMessage = {
271
272
  ...messages[lastAIMessageIndex],
272
273
  diffData,
273
- ...(beforeFiles && { beforeFiles }), // Add beforeFiles only if provided
274
+ ...(beforeFiles && { beforeFiles }), // Add beforeFiles only if provided (legacy)
275
+ ...(beforeCommitId && { beforeCommitId }), // Add beforeCommitId for VizHub integration
274
276
  };
275
277
  // Use type assertion to extend the message with diffData
276
278
  (messages[lastAIMessageIndex] as any) = newMessage;
@@ -23,10 +23,12 @@ export const handleAIChatMessage =
23
23
  shareDBDoc,
24
24
  createAIEditLocalPresence,
25
25
  onCreditDeduction,
26
+ getCurrentCommitId,
26
27
  }: {
27
28
  shareDBDoc: ShareDBDoc<VizContent>;
28
29
  createAIEditLocalPresence: () => any;
29
30
  onCreditDeduction?: any;
31
+ getCurrentCommitId?: () => string | null;
30
32
  }) =>
31
33
  async (req: any, res: any) => {
32
34
  const { content, chatId, mode = 'edit' } = req.body;
@@ -52,6 +54,11 @@ export const handleAIChatMessage =
52
54
  ensureChatsExist(shareDBDoc);
53
55
  ensureChatExists(shareDBDoc, chatId);
54
56
 
57
+ // Capture the current commit ID before making changes (for VizHub integration)
58
+ const beforeCommitId = getCurrentCommitId
59
+ ? getCurrentCommitId()
60
+ : null;
61
+
55
62
  // Add user message to chat
56
63
  addUserMessage(shareDBDoc, chatId, content);
57
64
 
@@ -92,7 +99,8 @@ export const handleAIChatMessage =
92
99
  shareDBDoc,
93
100
  chatId,
94
101
  editResult.diffData,
95
- (editResult as any).beforeFiles, // Pass the beforeFiles snapshot for undo (only available for edit mode)
102
+ (editResult as any).beforeFiles, // Pass the beforeFiles snapshot for undo (legacy)
103
+ beforeCommitId, // Pass the commit ID before AI changes (for VizHub integration)
96
104
  );
97
105
  }
98
106
 
@@ -45,10 +45,15 @@ export const createLLMFunction = ({
45
45
  shareDBDoc,
46
46
  createAIEditLocalPresence,
47
47
  chatId,
48
+ // Feature flag to enable/disable reasoning tokens.
49
+ // When false, reasoning tokens are not requested from the API
50
+ // and reasoning content is not processed in the streaming response.
51
+ enableReasoningTokens = false,
48
52
  }: {
49
53
  shareDBDoc: ShareDBDoc<VizContent>;
50
54
  createAIEditLocalPresence: () => any;
51
55
  chatId: VizChatId;
56
+ enableReasoningTokens?: boolean;
52
57
  }) => {
53
58
  return async (fullPrompt: string) => {
54
59
  const localPresence = enableStreamingEditing
@@ -197,21 +202,29 @@ export const createLLMFunction = ({
197
202
  const modelName =
198
203
  process.env.VIZHUB_EDIT_WITH_AI_MODEL_NAME ||
199
204
  'anthropic/claude-3.5-sonnet';
200
- const stream = await (
201
- openRouterClient.chat.completions.create as any
202
- )({
205
+
206
+ // Configure reasoning tokens based on enableReasoningTokens flag
207
+ const requestConfig: any = {
203
208
  model: modelName,
204
209
  messages: [{ role: 'user', content: fullPrompt }],
205
- reasoning: {
206
- effort: 'low',
207
- exclude: false,
208
- },
209
210
  provider: {
210
211
  sort: 'price',
211
212
  },
212
213
  usage: { include: true },
213
214
  stream: true,
214
- });
215
+ };
216
+
217
+ // Only include reasoning configuration if reasoning tokens are enabled
218
+ if (enableReasoningTokens) {
219
+ requestConfig.reasoning = {
220
+ effort: 'low',
221
+ exclude: false,
222
+ };
223
+ }
224
+
225
+ const stream = await (
226
+ openRouterClient.chat.completions.create as any
227
+ )(requestConfig);
215
228
 
216
229
  let reasoningStarted = false;
217
230
  let contentStarted = false;
@@ -219,8 +232,8 @@ export const createLLMFunction = ({
219
232
  for await (const chunk of stream) {
220
233
  const delta = chunk.choices[0]?.delta as any; // Type assertion for OpenRouter-specific reasoning fields
221
234
 
222
- if (delta?.reasoning) {
223
- // Handle reasoning tokens (thinking)
235
+ if (delta?.reasoning && enableReasoningTokens) {
236
+ // Handle reasoning tokens (thinking) - only if enabled
224
237
  if (!reasoningStarted) {
225
238
  reasoningStarted = true;
226
239
  updateAIStatus(shareDBDoc, chatId, 'Thinking...');