Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions packages/components/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,15 @@ const defaultAllowExternalDependencies = ['axios', 'moment', 'node-fetch']

export const defaultAllowBuiltInDep = ['assert', 'buffer', 'crypto', 'events', 'path', 'querystring', 'timers', 'url', 'zlib']

const safeJsonParse = (input: string) => {
if (!input || input.trim() === '') return null
try {
return JSON.parse(input)
} catch {
return null
}
}

/**
* Get base classes of components
*
Expand Down Expand Up @@ -966,13 +975,8 @@ export const convertBaseMessagetoIMessage = (messages: BaseMessage[]): IMessage[
* @returns {string[]}
*/
export const convertMultiOptionsToStringArray = (inputString: string): string[] => {
let ArrayString: string[] = []
try {
ArrayString = JSON.parse(inputString)
} catch (e) {
ArrayString = []
}
return ArrayString
const parsed = safeJsonParse(inputString)
return Array.isArray(parsed) ? parsed : []
}

/**
Expand Down Expand Up @@ -1370,7 +1374,8 @@ export const parseDocumentLoaderMetadata = (metadata: object | string): object =
if (!metadata) return {}

if (typeof metadata !== 'object') {
return JSON.parse(metadata)
const parsed = safeJsonParse(metadata)
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}
}

return metadata
Expand Down Expand Up @@ -1865,7 +1870,8 @@ export const processTemplateVariables = (state: ICommonObject, finalOutput: any)
if (match) {
try {
// Parse the response if it's JSON
const jsonResponse = typeof finalOutput === 'string' ? JSON.parse(finalOutput) : finalOutput
const jsonResponse =
typeof finalOutput === 'string' ? safeJsonParse(finalOutput) ?? finalOutput : finalOutput
// Get the value using lodash get
const path = match[1]
const value = get(jsonResponse, path)
Expand Down
24 changes: 17 additions & 7 deletions packages/server/src/controllers/chat-messages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,34 +319,44 @@ const abortChatMessage = async (req: Request, res: Response, next: NextFunction)
}
}

const safeJsonParse = (input: string) => {
if (!input || input.trim() === '') return null
try {
return JSON.parse(input)
} catch (error) {
console.warn('Unable to parse chat message JSON field', error)
return null
Comment on lines +323 to +328
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

safeJsonParse returns null on failure, and callers use ?? parsedResponse.<field>. This changes behavior for valid JSON that parses to null (e.g. the stored string 'null'): previously it would set the field to null, but now it falls back to the original string. Consider returning undefined (or a non-null sentinel) on parse failure/blank input so a successfully parsed null is preserved.

Suggested change
if (!input || input.trim() === '') return null
try {
return JSON.parse(input)
} catch (error) {
console.warn('Unable to parse chat message JSON field', error)
return null
if (!input || input.trim() === '') return undefined
try {
return JSON.parse(input)
} catch (error) {
console.warn('Unable to parse chat message JSON field', error)
return undefined

Copilot uses AI. Check for mistakes.
Comment on lines +326 to +328
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The warning log in safeJsonParse doesn’t indicate which chat message field failed to parse (sourceDocuments vs usedTools vs artifacts, etc.), which will make production troubleshooting harder and may create noisy logs. Consider passing the field name into safeJsonParse (or logging it at the call site) and/or lowering the log level if malformed JSON is expected occasionally.

Copilot uses AI. Check for mistakes.
}
}
Comment on lines +322 to +330
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This safeJsonParse function has a subtle bug when parsing a valid JSON string that evaluates to null, such as 'null'. In this case, JSON.parse('null') correctly returns the value null. However, your function also returns null on parsing errors or for empty strings. At the call sites, you use safeJsonParse(...) ?? originalValue. When safeJsonParse returns null, this expression incorrectly falls back to originalValue.

To fix this, safeJsonParse should return undefined on failure. This allows distinguishing a failed parse from a successful parse that results in null.

  • safeJsonParse('null') would return null, and null ?? 'null' correctly evaluates to null.
  • safeJsonParse('invalid') would return undefined, and undefined ?? 'invalid' correctly evaluates to 'invalid'.
Suggested change
const safeJsonParse = (input: string) => {
if (!input || input.trim() === '') return null
try {
return JSON.parse(input)
} catch (error) {
console.warn('Unable to parse chat message JSON field', error)
return null
}
}
const safeJsonParse = (input: string) => {
if (!input || input.trim() === '') return undefined
try {
return JSON.parse(input)
} catch (error) {
console.warn('Unable to parse chat message JSON field', error)
return undefined
}
}


const parseAPIResponse = (apiResponse: ChatMessage | ChatMessage[]): ChatMessage | ChatMessage[] => {
const parseResponse = (response: ChatMessage): ChatMessage => {
const parsedResponse = { ...response }

try {
if (parsedResponse.sourceDocuments) {
parsedResponse.sourceDocuments = JSON.parse(parsedResponse.sourceDocuments)
parsedResponse.sourceDocuments = safeJsonParse(parsedResponse.sourceDocuments) ?? parsedResponse.sourceDocuments
}
if (parsedResponse.usedTools) {
parsedResponse.usedTools = JSON.parse(parsedResponse.usedTools)
parsedResponse.usedTools = safeJsonParse(parsedResponse.usedTools) ?? parsedResponse.usedTools
}
if (parsedResponse.fileAnnotations) {
parsedResponse.fileAnnotations = JSON.parse(parsedResponse.fileAnnotations)
parsedResponse.fileAnnotations = safeJsonParse(parsedResponse.fileAnnotations) ?? parsedResponse.fileAnnotations
}
if (parsedResponse.agentReasoning) {
parsedResponse.agentReasoning = JSON.parse(parsedResponse.agentReasoning)
parsedResponse.agentReasoning = safeJsonParse(parsedResponse.agentReasoning) ?? parsedResponse.agentReasoning
}
if (parsedResponse.reasonContent) {
parsedResponse.reasonContent = JSON.parse(parsedResponse.reasonContent)
}
if (parsedResponse.fileUploads) {
parsedResponse.fileUploads = JSON.parse(parsedResponse.fileUploads)
parsedResponse.fileUploads = safeJsonParse(parsedResponse.fileUploads) ?? parsedResponse.fileUploads
}
if (parsedResponse.action) {
parsedResponse.action = JSON.parse(parsedResponse.action)
parsedResponse.action = safeJsonParse(parsedResponse.action) ?? parsedResponse.action
}
if (parsedResponse.artifacts) {
parsedResponse.artifacts = JSON.parse(parsedResponse.artifacts)
parsedResponse.artifacts = safeJsonParse(parsedResponse.artifacts) ?? parsedResponse.artifacts
}
} catch (e) {
console.error('Error parsing chat message response', e)
Expand Down