vzcode 2.27.0 → 2.29.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.
@@ -4,6 +4,7 @@ import {
4
4
  ensureChatExists,
5
5
  addUserMessage,
6
6
  setAIStatus,
7
+ setChatAIMetadata,
7
8
  } from '../../llm-streaming-server/chatOperations.js';
8
9
  import { createLLMFunction } from '../../llm-streaming-server/llmStreaming.js';
9
10
  import { performAIEditing } from '../../llm-streaming-server/aiEditing.js';
@@ -23,15 +24,31 @@ export const handleAIChatMessage =
23
24
  ({
24
25
  shareDBDoc,
25
26
  onCreditDeduction,
27
+ onGenerationFinished,
26
28
  model,
27
29
  aiRequestOptions,
28
30
  enableReasoningTokens,
31
+ baseCommitId,
32
+ escalationLevel,
29
33
  }: {
30
34
  shareDBDoc: ShareDBDoc<VizContent>;
31
35
  onCreditDeduction?: any;
36
+ onGenerationFinished?: (result: {
37
+ success: boolean;
38
+ editResult?: any;
39
+ metrics?: any;
40
+ error?: any;
41
+ }) => Promise<void> | void;
32
42
  model?: string;
33
43
  aiRequestOptions?: any;
34
44
  enableReasoningTokens?: boolean;
45
+ // Phase 3: explicit escalation base. Persisted on the chat so that
46
+ // repeated "Try Harder" clicks are idempotent and do not depend on
47
+ // the fragile `parent(currentCommit)` heuristic.
48
+ baseCommitId?: string;
49
+ // Explicit escalation level, persisted on the chat so it survives
50
+ // reloads and is shared across clients.
51
+ escalationLevel?: number;
35
52
  }) =>
36
53
  async (req: any, res: any) => {
37
54
  const { content, chatId } = req.body;
@@ -60,6 +77,13 @@ export const handleAIChatMessage =
60
77
  // Add user message to chat
61
78
  addUserMessage(shareDBDoc, chatId, content);
62
79
 
80
+ // Persist explicit escalation metadata on the chat, so that
81
+ // retries are idempotent and the level survives reloads.
82
+ setChatAIMetadata(shareDBDoc, chatId, {
83
+ baseCommitId,
84
+ escalationLevel,
85
+ });
86
+
63
87
  // Return success immediately - AI generation continues in background
64
88
  res.status(200).json('success');
65
89
 
@@ -72,6 +96,7 @@ export const handleAIChatMessage =
72
96
  aiRequestOptions,
73
97
  enableReasoningTokens,
74
98
  onCreditDeduction,
99
+ onGenerationFinished,
75
100
  }).catch((error) => {
76
101
  console.error(
77
102
  'Background AI processing error:',
@@ -96,6 +121,7 @@ const processAIRequestAsync = async ({
96
121
  aiRequestOptions,
97
122
  enableReasoningTokens,
98
123
  onCreditDeduction,
124
+ onGenerationFinished,
99
125
  }: {
100
126
  shareDBDoc: ShareDBDoc<VizContent>;
101
127
  chatId: string;
@@ -104,6 +130,12 @@ const processAIRequestAsync = async ({
104
130
  aiRequestOptions?: any;
105
131
  enableReasoningTokens?: boolean;
106
132
  onCreditDeduction?: any;
133
+ onGenerationFinished?: (result: {
134
+ success: boolean;
135
+ editResult?: any;
136
+ metrics?: any;
137
+ error?: any;
138
+ }) => Promise<void> | void;
107
139
  }) => {
108
140
  try {
109
141
  // Create LLM function for streaming
@@ -128,23 +160,40 @@ const processAIRequestAsync = async ({
128
160
  runCode,
129
161
  });
130
162
 
131
- // Handle credit deduction if callback is provided
163
+ // Billing is best-effort. It must NEVER prevent the edit from
164
+ // being committed. Any metadata failure is logged and swallowed.
165
+ let metrics: any = null;
132
166
  if (onCreditDeduction && editResult.generationId) {
133
167
  try {
134
- await onCreditDeduction(
135
- await getGenerationMetadata({
136
- apiKey:
137
- aiRequestOptions?.apiKey ||
138
- process.env.VZCODE_EDIT_WITH_AI_API_KEY,
139
- generationId: editResult.generationId,
140
- }),
141
- );
168
+ metrics = await getGenerationMetadata({
169
+ apiKey:
170
+ aiRequestOptions?.apiKey ||
171
+ process.env.VZCODE_EDIT_WITH_AI_API_KEY,
172
+ generationId: editResult.generationId,
173
+ });
174
+ await onCreditDeduction(metrics);
142
175
  } catch (creditError) {
143
176
  console.error(
144
- 'Credit deduction error:',
177
+ 'Credit deduction error (edit will still be finalized):',
145
178
  creditError,
146
179
  );
147
- // Don't fail the request if credit deduction fails
180
+ }
181
+ }
182
+
183
+ // Always notify settlement on success, regardless of billing
184
+ // outcome, so the caller can commit the edit and release locks.
185
+ if (onGenerationFinished) {
186
+ try {
187
+ await onGenerationFinished({
188
+ success: true,
189
+ editResult,
190
+ metrics,
191
+ });
192
+ } catch (settleError) {
193
+ console.error(
194
+ 'onGenerationFinished (success) error:',
195
+ settleError,
196
+ );
148
197
  }
149
198
  }
150
199
 
@@ -153,6 +202,23 @@ const processAIRequestAsync = async ({
153
202
  } catch (error) {
154
203
  // Set error status and add error message to chat
155
204
  setAIStatus(shareDBDoc, chatId, 'error');
205
+
206
+ // Always notify settlement on failure so the caller can roll back
207
+ // the pre-restore snapshot and release locks.
208
+ if (onGenerationFinished) {
209
+ try {
210
+ await onGenerationFinished({
211
+ success: false,
212
+ error,
213
+ });
214
+ } catch (settleError) {
215
+ console.error(
216
+ 'onGenerationFinished (failure) error:',
217
+ settleError,
218
+ );
219
+ }
220
+ }
221
+
156
222
  handleBackgroundError(shareDBDoc, chatId, error);
157
223
  }
158
224
  };
package/src/types.ts CHANGED
@@ -262,6 +262,9 @@ export interface ExtendedVizChat extends VizChat {
262
262
  currentStatus?: string;
263
263
  isStreaming?: boolean;
264
264
  model?: string; // The LLM model used for this chat
265
+ // Phase 3: explicit escalation metadata persisted on the chat.
266
+ baseCommitId?: string; // Commit the current AI attempt is applied on top of
267
+ escalationLevel?: number; // How many times "Try Harder" has been pressed
265
268
  }
266
269
 
267
270
  // Extended VizChatMessage with progressive rendering support