99 */
1010
1111import axios from "axios" ;
12+ import { CLAUDE_MODELS } from "./code-generation-service" ;
1213
1314interface StreamOptions {
1415 model ?: string ;
@@ -98,95 +99,6 @@ class ClaudeStreamParser {
9899 }
99100}
100101
101- /**
102- * Check if the generated content appears to be truncated mid-generation
103- * For Graffiticode:
104- * - All valid programs end with .. (double dots)
105- * - Code blocks must be properly closed with matching ```
106- */
107- function appearsTruncated ( content : string ) : boolean {
108- if ( ! content || content . length === 0 ) return false ;
109-
110- const trimmed = content . trim ( ) ;
111-
112- // Check for incomplete code blocks (odd number of ```)
113- const codeBlockCount = ( content . match ( / ` ` ` / g) || [ ] ) . length ;
114- if ( codeBlockCount % 2 !== 0 ) {
115- // Unclosed code block - definitely truncated
116- return true ;
117- }
118-
119- // If we have complete code blocks, check if ANY substantial code block ends with ..
120- if ( codeBlockCount > 0 ) {
121- // Extract ALL code blocks
122- const codeBlocks = content . split ( '```' ) ;
123- if ( codeBlocks . length >= 3 && codeBlocks . length % 2 === 1 ) {
124- // Check each code block (they're at even indices: 1, 3, 5, etc.)
125- for ( let i = 1 ; i < codeBlocks . length ; i += 2 ) {
126- const blockContent = codeBlocks [ i ] ;
127- // Remove language identifier if present (e.g., "javascript\n")
128- const codeContent = blockContent . replace ( / ^ [ \w ] * \n / , '' ) . trim ( ) ;
129-
130- // Skip empty or very short code blocks (likely just snippets)
131- if ( codeContent . length < 10 ) continue ;
132-
133- // Check if this looks like a complete Graffiticode program
134- // A complete program should have substantial content and end with ..
135- const hasSubstantialContent =
136- codeContent . includes ( '{' ) ||
137- codeContent . includes ( 'let' ) ||
138- codeContent . includes ( 'map' ) ||
139- codeContent . includes ( 'cells' ) ||
140- codeContent . includes ( 'columns' ) ;
141-
142- if ( hasSubstantialContent && codeContent . endsWith ( '..' ) ) {
143- // Found a complete Graffiticode program
144- return false ;
145- } else if ( hasSubstantialContent && ! codeContent . endsWith ( '..' ) ) {
146- // Found a substantial code block that doesn't end with ..
147- return true ;
148- }
149- }
150-
151- // If we only found small snippets or fragments, check the longest one
152- let longestBlock = '' ;
153- for ( let i = 1 ; i < codeBlocks . length ; i += 2 ) {
154- const blockContent = codeBlocks [ i ] . replace ( / ^ [ \w ] * \n / , '' ) . trim ( ) ;
155- if ( blockContent . length > longestBlock . length ) {
156- longestBlock = blockContent ;
157- }
158- }
159-
160- if ( longestBlock && ! longestBlock . endsWith ( '..' ) ) {
161- return true ;
162- }
163- }
164- // If all code blocks are properly terminated, consider it complete
165- return false ;
166- }
167-
168- // Only check for truncation in non-code content if there are no code blocks at all
169- // This handles cases where the response is pure text without code
170- const lastLine = trimmed . split ( '\n' ) . pop ( ) || '' ;
171- if ( lastLine . length > 200 ) {
172- return true ;
173- }
174-
175- // Check for obvious truncation indicators at the very end
176- const truncationIndicators = [
177- / [ ^ \\ ] [ " ' ] $ / , // Ends with unescaped quote
178- / [ \[ { ( , ] $ / , // Ends with opening bracket or comma
179- / [ = + \- * / < > ! & | : ] $ / , // Ends with operator or colon
180- ] ;
181-
182- for ( const pattern of truncationIndicators ) {
183- if ( pattern . test ( trimmed ) ) {
184- return true ;
185- }
186- }
187-
188- return false ;
189- }
190102
191103/**
192104 * Stream code generation from Claude with automatic continuation
@@ -219,7 +131,6 @@ export async function* streamClaudeCode({
219131 let conversationHistory = [ ...messages ] ;
220132 let fullContent = "" ;
221133 let totalUsage = { inputTokens : 0 , outputTokens : 0 } ;
222- let consecutiveShortResponses = 0 ; // Track short responses that might indicate completion
223134
224135 // Add initial user message if prompt provided
225136 if ( prompt ) {
@@ -236,7 +147,7 @@ export async function* streamClaudeCode({
236147 const response = await axios . post (
237148 "https://api.anthropic.com/v1/messages" ,
238149 {
239- model : options . model || "claude-3-5-sonnet-20241022" ,
150+ model : options . model || CLAUDE_MODELS . DEFAULT ,
240151 system : systemPrompt ,
241152 messages : conversationHistory ,
242153 max_tokens : options . maxTokens || 4096 ,
@@ -294,41 +205,37 @@ export async function* streamClaudeCode({
294205 return ;
295206 }
296207
297- // Primary check: Did the API signal completion naturally?
298- const isTruncated = appearsTruncated ( chunkContent ) ;
208+ // Check for truncation:
209+ // 1. Odd number of ``` (unclosed block) OR
210+ // 2. No .. anywhere in the code
211+ let isTruncated = false ;
212+
213+ const codeBlockCount = ( fullContent . match ( / ` ` ` / g) || [ ] ) . length ;
214+
215+ if ( codeBlockCount % 2 !== 0 ) {
216+ // Odd number of ``` means unclosed code block - definitely truncated
217+ isTruncated = true ;
218+ } else if ( codeBlockCount > 0 ) {
219+ // Even number of ``` - check if any code contains ..
220+ isTruncated = ! fullContent . includes ( '..' ) ;
221+ }
222+
299223 console . log (
300224 "streamClaudeCode()" ,
301- "isTruncated=" + isTruncated ,
225+ "fullContent=" + fullContent ,
226+ "codeBlockCount=" + codeBlockCount ,
302227 "isComplete=" + isComplete ,
228+ "chunkLength=" + chunkContent . length ,
229+ "isTruncated=" + isTruncated ,
303230 ) ;
231+
232+ // If API says complete AND code ends with .., we're done
304233 if ( isComplete && ! isTruncated ) {
305- console . log ( `Response completed naturally after ${ continuationCount + 1 } chunk(s)` ) ;
234+ console . log ( `Response completed after ${ continuationCount + 1 } chunk(s)` ) ;
306235 break ;
307236 }
308237
309- // Debug logging
310- if ( isComplete && isTruncated ) {
311- console . log ( `API signaled complete but content appears truncated` ) ;
312- }
313-
314- // Check if response was cut off (likely hit token limit)
315- const likelyHitTokenLimit = chunkContent . length >= ( options . maxTokens || 4096 ) * 3 ;
316-
317- // If response is very short and API signals complete, trust it
318- if ( chunkContent . length < 500 && isComplete && ! isTruncated ) {
319- consecutiveShortResponses ++ ;
320- if ( consecutiveShortResponses >= 2 ) {
321- console . log ( `Response appears complete (consecutive short responses)` ) ;
322- break ;
323- }
324- } else {
325- consecutiveShortResponses = 0 ;
326- }
327-
328- // Decide if we need continuation
329- // Continue if:
330- // 1. API didn't signal completion (!isComplete)
331- // 2. Content appears truncated (isTruncated), regardless of why
238+ // Continue if API says not complete OR if code doesn't end with ..
332239 const needsContinuation = ! isComplete || isTruncated ;
333240
334241 if ( needsContinuation ) {
0 commit comments