@agentforce/adk
Version:
AgentForce Agent Development Kit - A powerful framework for building AI agents and servers
253 lines (230 loc) • 8.18 kB
text/typescript
/**
* JSON utility functions for AgentForce ADK
* Provides parsing, formatting, and code block extraction for JSON data
*/
/**
* Parse JSON string to JavaScript object
* @param jsonString - The JSON string to parse
* @returns Parsed JavaScript object
* @throws Error if JSON is invalid
*/
export function parseJson(jsonString: string): any {
try {
return JSON.parse(jsonString);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to parse JSON: ${errorMessage}`);
}
}
/**
* Convert JavaScript object to JSON string
* @param data - The data to convert to JSON
* @param pretty - Whether to format with indentation
* @returns JSON string representation
* @throws Error if data cannot be converted to JSON
*/
export function stringifyJson(data: any, pretty: boolean = false): string {
try {
return JSON.stringify(data, null, pretty ? 2 : 0);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to stringify to JSON: ${errorMessage}`);
}
}
/**
* Validate if a string is valid JSON
* @param jsonString - The JSON string to validate
* @returns True if valid JSON, false otherwise
*/
export function isValidJson(jsonString: string): boolean {
try {
JSON.parse(jsonString);
return true;
} catch {
return false;
}
}
/**
* Parse JSON string safely, returning null if invalid
* @param jsonString - The JSON string to parse
* @returns Parsed object or null if invalid
*/
export function safeParseJson(jsonString: string): any | null {
try {
return JSON.parse(jsonString);
} catch {
return null;
}
}
/**
* Convert agent response to JSON format with code block extraction
* @param response - Agent response string
* @param enableCodeBlockParsing - Whether to extract JSON from code blocks (default: true). Set to false to return whole response.
* @returns JSON formatted string or original response
*/
export function formatResponseAsJson(response: string, enableCodeBlockParsing: boolean = true): string | object {
// If code block parsing is enabled, try to extract JSON from code blocks first
if (enableCodeBlockParsing) {
const jsonBlock = extractJsonCodeBlock(response);
if (jsonBlock) {
// Return the extracted JSON block as-is
return jsonBlock;
}
}
// If code block parsing is disabled, return the whole response as-is
if (!enableCodeBlockParsing) {
return response.trim();
}
// If no JSON code block found, try to parse the entire response
try {
const parsedJson = parseJson(response);
return stringifyJson(parsedJson, false);
} catch {
// If the response is not valid JSON, return it as-is
return response.trim();
}
}
/**
* Extract JSON content from code blocks in the response
* Looks for ```json ... ``` patterns (case-insensitive) and returns the content
* Handles nested code blocks and merges multiple blocks into array format
* @param response - The response text to search
* @returns Extracted JSON content or null if not found
*/
function extractJsonCodeBlock(response: string): string | null {
const jsonBlocks: string[] = [];
// Extract all JSON blocks using proper nested parsing
jsonBlocks.push(...extractNestedCodeBlocks(response, /```json\s*\n?/gi));
// If no specific JSON blocks found, try generic blocks that look like JSON
if (jsonBlocks.length === 0) {
const genericBlocks = extractNestedCodeBlocks(response, /```\s*\n?/g);
for (const block of genericBlocks) {
if (isLikelyJson(block)) {
jsonBlocks.push(block);
}
}
}
if (jsonBlocks.length === 0) {
return null;
}
// If multiple blocks, merge into array format
if (jsonBlocks.length > 1) {
try {
const parsedObjects = jsonBlocks.map(block => {
try {
return JSON.parse(block);
} catch {
return { "content": block }; // Wrap invalid JSON as content
}
});
// Format with indentation for better readability
return JSON.stringify(parsedObjects, null, 2);
} catch {
// If merging fails, try to format individual blocks
try {
const formattedBlocks = jsonBlocks.map(block => {
try {
const parsed = JSON.parse(block);
return JSON.stringify(parsed, null, 2);
} catch {
return block;
}
});
return "[\n" + formattedBlocks.map(b => " " + b.split("\n").join("\n ")).join(",\n") + "\n]";
} catch {
// Last resort: just join with commas
return "[" + jsonBlocks.join(",") + "]";
}
}
}
// Single block - format it compactly
if (jsonBlocks[0]) {
try {
const parsed = JSON.parse(jsonBlocks[0]);
return JSON.stringify(parsed, null, 0);
} catch {
// If parsing fails, return as-is
return jsonBlocks[0];
}
}
return null;
}
/**
* Extract code blocks handling nested backticks properly
* @param text - Text to search in
* @param startPattern - Regex pattern for the opening code block
* @returns Array of extracted code block contents
*/
function extractNestedCodeBlocks(text: string, startPattern: RegExp): string[] {
const blocks: string[] = [];
const lines = text.split("\n");
let i = 0;
while (i < lines.length) {
const line = lines[i];
if (!line) {
i++;
continue;
}
const match = line.match(startPattern);
if (match) {
// Found start of code block, now find the end
let blockContent: string[] = [];
i++; // Move past the opening ```
// Look for closing ``` that's not inside another code block
while (i < lines.length) {
const currentLine = lines[i] || "";
// Check if this is a closing ``` (3 or more backticks with optional leading whitespace)
if (/^\s*```\s*$/.test(currentLine)) {
// Found closing block
const content = blockContent.join("\n").trim();
if (content) {
blocks.push(content);
}
break;
}
blockContent.push(currentLine);
i++;
}
}
i++;
}
return blocks;
}
/**
* Check if content looks like JSON format
* Simple heuristic to detect JSON-like structure
* @param content - Content to check
* @returns True if content appears to be JSON
*/
function isLikelyJson(content: string): boolean {
const trimmed = content.trim();
// JSON objects start with { and end with }
// JSON arrays start with [ and end with ]
return (trimmed.startsWith("{") && trimmed.endsWith("}")) ||
(trimmed.startsWith("[") && trimmed.endsWith("]"));
}
/**
* Validate agent response and convert to JSON if possible
* @param response - Agent response string
* @returns Object with validation result and converted JSON
*/
export function validateAndFormatJson(response: string): {
isValid: boolean;
json: string | object;
error?: string;
} {
try {
const formattedJson = formatResponseAsJson(response, false);
return {
isValid: true,
json: formattedJson,
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
isValid: false,
json: response.trim(),
error: errorMessage,
};
}
}