UNPKG

perplexity-mcp-server

Version:

A Perplexity API Model Context Protocol (MCP) server that unlocks Perplexity's search-augmented AI capabilities for LLM agents. Features robust error handling, secure input validation, and transparent reasoning with the showThinking parameter. Built with

128 lines (127 loc) 5.82 kB
/** * @fileoverview Provides a utility class for parsing potentially partial JSON strings. * It wraps the 'partial-json' npm library and includes functionality to handle * optional <think>...</think> blocks often found at the beginning of LLM outputs. * @module src/utils/parsing/jsonParser */ import { parse as parsePartialJson, Allow as PartialJsonAllow, } from "partial-json"; import { BaseErrorCode, McpError } from "../../types-global/errors.js"; import { logger, requestContextService } from "../index.js"; /** * Enum mirroring `partial-json`'s `Allow` constants. These specify * what types of partial JSON structures are permissible during parsing. * They can be combined using bitwise OR (e.g., `Allow.STR | Allow.OBJ`). * * The available properties are: * - `STR`: Allow partial string. * - `NUM`: Allow partial number. * - `ARR`: Allow partial array. * - `OBJ`: Allow partial object. * - `NULL`: Allow partial null. * - `BOOL`: Allow partial boolean. * - `NAN`: Allow partial NaN. (Note: Standard JSON does not support NaN) * - `INFINITY`: Allow partial Infinity. (Note: Standard JSON does not support Infinity) * - `_INFINITY`: Allow partial -Infinity. (Note: Standard JSON does not support -Infinity) * - `INF`: Allow both partial Infinity and -Infinity. * - `SPECIAL`: Allow all special values (NaN, Infinity, -Infinity). * - `ATOM`: Allow all atomic values (strings, numbers, booleans, null, special values). * - `COLLECTION`: Allow all collection values (objects, arrays). * - `ALL`: Allow all value types to be partial (default for `partial-json`'s parse). * @see {@link https://github.com/promplate/partial-json-parser-js} for more details. */ export const Allow = PartialJsonAllow; /** * Regular expression to find a <think> block at the start of a string. * Captures content within <think>...</think> (Group 1) and the rest of the string (Group 2). * @private */ const thinkBlockRegex = /^<think>([\s\S]*?)<\/think>\s*([\s\S]*)$/; /** * Utility class for parsing potentially partial JSON strings. * Wraps the 'partial-json' library for robust JSON parsing, handling * incomplete structures and optional <think> blocks from LLMs. */ export class JsonParser { /** * Parses a JSON string, which may be partial or prefixed with a <think> block. * If a <think> block is present, its content is logged, and parsing proceeds on the * remainder. Uses 'partial-json' to handle incomplete JSON. * * @template T The expected type of the parsed JSON object. Defaults to `any`. * @param jsonString - The JSON string to parse. * @param allowPartial - Bitwise OR combination of `Allow` constants specifying permissible * partial JSON types. Defaults to `Allow.ALL`. * @param context - Optional `RequestContext` for logging and error correlation. * @returns The parsed JavaScript value. * @throws {McpError} If the string is empty after processing or if `partial-json` fails. */ parse(jsonString, allowPartial = Allow.ALL, context) { let stringToParse = jsonString; const match = jsonString.match(thinkBlockRegex); if (match) { const thinkContent = match[1].trim(); const restOfString = match[2]; const logContext = context || requestContextService.createRequestContext({ operation: "JsonParser.thinkBlock", }); if (thinkContent) { logger.debug("LLM <think> block detected and logged.", { ...logContext, thinkContent, }); } else { logger.debug("Empty LLM <think> block detected.", logContext); } stringToParse = restOfString; } stringToParse = stringToParse.trim(); if (!stringToParse) { throw new McpError(BaseErrorCode.VALIDATION_ERROR, "JSON string is empty after removing <think> block and trimming.", context); } try { return parsePartialJson(stringToParse, allowPartial); } catch (e) { const error = e; const errorLogContext = context || requestContextService.createRequestContext({ operation: "JsonParser.parseError", }); logger.error("Failed to parse JSON content.", { ...errorLogContext, errorDetails: error.message, contentAttempted: stringToParse.substring(0, 200), }); throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Failed to parse JSON: ${error.message}`, { ...context, originalContentSample: stringToParse.substring(0, 200) + (stringToParse.length > 200 ? "..." : ""), rawError: error instanceof Error ? error.stack : String(error), }); } } } /** * Singleton instance of the `JsonParser`. * Use this instance to parse JSON strings, with support for partial JSON and <think> blocks. * @example * ```typescript * import { jsonParser, Allow, requestContextService } from './utils'; * const context = requestContextService.createRequestContext({ operation: 'TestJsonParsing' }); * * const fullJson = '{"key": "value"}'; * const parsedFull = jsonParser.parse(fullJson, Allow.ALL, context); * console.log(parsedFull); // Output: { key: 'value' } * * const partialObject = '<think>This is a thought.</think>{"key": "value", "arr": [1,'; * try { * const parsedPartial = jsonParser.parse(partialObject, undefined, context); * console.log(parsedPartial); * } catch (e) { * console.error("Parsing partial object failed:", e); * } * ``` */ export const jsonParser = new JsonParser();