diff --git a/extensions/codestory/src/chatState/convertStreamToMessage.ts b/extensions/codestory/src/chatState/convertStreamToMessage.ts index 4c00a177e18..0493a49b531 100644 --- a/extensions/codestory/src/chatState/convertStreamToMessage.ts +++ b/extensions/codestory/src/chatState/convertStreamToMessage.ts @@ -143,12 +143,24 @@ export const reportProcUpdateToChat = ( } }; +function safeJSONParse(text: string): any { + try { + text = text.replace(/^\uFEFF/, '').trim(); + return JSON.parse(text); + } catch (error) { + console.error('Failed to parse JSON:', error); + console.error('Raw content:', text); + throw error; + } +} + export const readJsonFile = (filePath: string): any => { const jsonString = fs.readFileSync(filePath, 'utf-8'); - return JSON.parse(jsonString); + return safeJSONParse(jsonString); }; + // const randomInt = (min: number, max: number) => // Math.floor(Math.random() * (max - min + 1)) + min; @@ -843,4 +855,4 @@ class DocumentManager { } return index + 2; } -} +} \ No newline at end of file diff --git a/extensions/codestory/src/sidecar/client.ts b/extensions/codestory/src/sidecar/client.ts index 7769ad0b613..1adbfb7216a 100644 --- a/extensions/codestory/src/sidecar/client.ts +++ b/extensions/codestory/src/sidecar/client.ts @@ -22,6 +22,22 @@ import { ConversationMessage, EditFileResponse, getSideCarModelConfiguration, Id import { sidecarUsesAgentReasoning } from '../utilities/agentConfiguration'; import { readAideRulesContent } from '../utilities/aideRules'; +function safeJSONParse(text: string): any { + try { + // Remove any potential BOM characters and whitespace + text = text.replace(/^\uFEFF/, '').trim(); + // Handle double-encoded JSON if needed + if (text.startsWith('"') && text.endsWith('"')) { + text = JSON.parse(text); + } + return JSON.parse(text); + } catch (error) { + console.error('Failed to parse JSON:', error); + console.error('Raw content:', text); + throw error; + } +} + export enum CompletionStopReason { /** * Used to signal to the completion processing code that we're still streaming. @@ -153,15 +169,15 @@ export class SideCarClient { const url = baseUrl.toString(); const asyncIterableResponse = await callServerEventStreamingBufferedGET(url); for await (const line of asyncIterableResponse) { - const lineParts = line.split('data:{'); - for (const lineSinglePart of lineParts) { - const lineSinglePartTrimmed = lineSinglePart.trim(); - if (lineSinglePartTrimmed === '') { - continue; - } - const finalString = '{' + lineSinglePartTrimmed; - const syncUpdate = JSON.parse(finalString) as SyncUpdate; + if (!line.includes('data:')) continue; + + try { + const jsonStr = line.substring(line.indexOf('data:') + 5).trim(); + const syncUpdate = safeJSONParse(jsonStr) as SyncUpdate; yield syncUpdate; + } catch (parseError) { + console.error('Failed to parse sync update:', parseError); + continue; } } } @@ -243,21 +259,15 @@ export class SideCarClient { }; const asyncIterableResponse = await callServerEventStreamingBufferedPOST(url, body); for await (const line of asyncIterableResponse) { - const lineParts = line.split('data:{'); - for (const lineSinglePart of lineParts) { - const lineSinglePartTrimmed = lineSinglePart.trim(); - if (lineSinglePartTrimmed === '') { - continue; - } - try { - // Attempt to parse JSON and yield - const editFileResponse = JSON.parse('{' + lineSinglePartTrimmed) as EditFileResponse; - yield editFileResponse; - } catch (parseError) { - // Log problematic content and error details - console.error(`Failed to parse JSON. Raw content: '{${lineSinglePartTrimmed}'`); - console.error('Parsing error details:', parseError); - } + if (!line.includes('data:')) continue; + + try { + const jsonStr = line.substring(line.indexOf('data:') + 5).trim(); + const editFileResponse = safeJSONParse(jsonStr) as EditFileResponse; + yield editFileResponse; + } catch (parseError) { + console.error('Failed to parse edit file response:', parseError); + continue; } } } @@ -671,14 +681,13 @@ export class SideCarClient { stopReason: CompletionStopReason.RequestAborted, }; } - const lineParts = line.split('data:"{'); - for (const lineSinglePart of lineParts) { - const lineSinglePartTrimmed = lineSinglePart.trim(); - if (lineSinglePartTrimmed === '') { - continue; - } - const finalString = '{' + lineSinglePartTrimmed.slice(0, -1); - const editFileResponse = JSON.parse(JSON.parse(`"${finalString}"`)) as CompletionResponse; + if (!line.includes('data:')) continue; + + try { + const jsonStr = line.substring(line.indexOf('data:') + 5).trim(); + // Handle double-encoded JSON from completion endpoint + const decodedStr = JSON.parse(jsonStr); + const editFileResponse = safeJSONParse(decodedStr) as CompletionResponse; // take the first provided completion here if (editFileResponse.completions.length > 0) { finalAnswer = editFileResponse.completions[0].insertText; @@ -1182,14 +1191,15 @@ export class SideCarClient { const asyncIterableResponse = callServerEventStreamingBufferedPOST(url, body); for await (const line of asyncIterableResponse) { - const lineParts = line.split('data:{'); - for (const lineSinglePart of lineParts) { - const lineSinglePartTrimmed = lineSinglePart.trim(); - if (lineSinglePartTrimmed === '') { - continue; - } - const conversationMessage = JSON.parse('{' + lineSinglePartTrimmed) as SideCarAgentEvent; - yield conversationMessage; + if (!line.includes('data:')) continue; + + try { + const jsonStr = line.substring(line.indexOf('data:') + 5).trim(); + const message = safeJSONParse(jsonStr) as SideCarAgentEvent; + yield message; + } catch (parseError) { + console.error('Failed to parse user feedback event:', parseError); + continue; } } } @@ -2227,4 +2237,4 @@ function getCurrentActiveWindow(): { end_line: endPosition.line + 1, language: activeWindow.document.languageId, }; -} +} \ No newline at end of file diff --git a/extensions/codestory/src/sidecar/ssestream.ts b/extensions/codestory/src/sidecar/ssestream.ts index 864f7f9f1f1..2720b599598 100644 --- a/extensions/codestory/src/sidecar/ssestream.ts +++ b/extensions/codestory/src/sidecar/ssestream.ts @@ -30,40 +30,54 @@ export async function* callServerEvent(url: string): AsyncIterableIterator 0) { + const event = this._currentEvent.join('\n'); + if (event.includes('data:')) { + finalAnswer.push(event); + } + this._currentEvent = []; + } + continue; + } + + this._currentEvent.push(line); + continue; + } + + this._buffer.push(chunk[i]); + } + + // Handle any remaining buffer at the end of chunk + if (this._buffer.length > 0) { + const line = this._buffer.join(''); + if (line.trim()) { + this._currentEvent.push(line); + } + this._buffer = []; + } + + return finalAnswer; + } } export async function* callServerEventStreamingBufferedGET(url: string): AsyncIterableIterator { @@ -138,4 +152,4 @@ export async function* callServerEventStreamingBufferedPOST(url: string, body: a } finally { reader.releaseLock(); } -} +} \ No newline at end of file diff --git a/extensions/codestory/src/storage/types.ts b/extensions/codestory/src/storage/types.ts index d3c177cbd9f..87866cbed6c 100644 --- a/extensions/codestory/src/storage/types.ts +++ b/extensions/codestory/src/storage/types.ts @@ -87,7 +87,7 @@ export const saveCodeStoryStorageObjectToStorage = async ( await ensureDirectoryExists(pathForStorage); const codeStoryStorageString = JSON.stringify(codeStoryStorage); fs.writeFileSync(pathForStorage, codeStoryStorageString); - return JSON.parse(codeStoryStorageString) as CodeStoryStorage; + return parseCodeStoryStorage(codeStoryStorageString); }; export const loadOrSaveToStorage = async ( @@ -99,4 +99,4 @@ export const loadOrSaveToStorage = async ( return storage; } return saveCodeStoryStorageToStorage(globalStorageUri, workingDirectory); -}; +}; \ No newline at end of file diff --git a/extensions/codestory/src/utilities/files.ts b/extensions/codestory/src/utilities/files.ts index c262812899d..c9e5050e463 100644 --- a/extensions/codestory/src/utilities/files.ts +++ b/extensions/codestory/src/utilities/files.ts @@ -5,9 +5,21 @@ import * as path from 'path'; import * as fs from 'fs'; +// Helper function to safely parse JSON +function safeJSONParse(text: string): any { + try { + text = text.replace(/^\uFEFF/, '').trim(); + return JSON.parse(text); + } catch (error) { + console.error('Failed to parse JSON:', error); + console.error('Raw content:', text); + throw error; + } +} + // Read and return data from sample.json file in working directory export const readJSONFromFile = () => { const filePath = path.join(__dirname, '../../sample.json'); const fileData = fs.readFileSync(filePath, 'utf-8'); - return JSON.parse(fileData); -}; + return safeJSONParse(fileData); +}; \ No newline at end of file diff --git a/extensions/codestory/src/utilities/workspaceContext.ts b/extensions/codestory/src/utilities/workspaceContext.ts index 6bfff2d736c..66f729003dc 100644 --- a/extensions/codestory/src/utilities/workspaceContext.ts +++ b/extensions/codestory/src/utilities/workspaceContext.ts @@ -92,10 +92,22 @@ export class ProjectContext { this.contentIndicators.set(fileName, indicatorFunction); } + function safeJSONParse(text: string): any { + try { + text = text.replace(/^\uFEFF/, '').trim(); + return JSON.parse(text); + } catch (error) { + console.error('Failed to parse JSON:', error); + console.error('Raw content:', text); + throw error; + } + } + collectPackageJsonIndicators(fileContent: string): string[] { const labels = []; - const parsedContent = JSON.parse(fileContent); - const dependencies = parsedContent.dependencies; + try { + const parsedContent = safeJSONParse(fileContent); + const dependencies = parsedContent.dependencies; const devDependencies = parsedContent.devDependencies; if (dependencies) { if (dependencies['@angular/core']) { @@ -122,4 +134,4 @@ export class ProjectContext { } return labels; } -} +} \ No newline at end of file