| import { createSubsystemLogger } from "../logging/subsystem.js"; |
|
|
| const log = createSubsystemLogger("memory"); |
|
|
| export type QmdQueryResult = { |
| docid?: string; |
| score?: number; |
| file?: string; |
| snippet?: string; |
| body?: string; |
| }; |
|
|
| export function parseQmdQueryJson(stdout: string, stderr: string): QmdQueryResult[] { |
| const trimmedStdout = stdout.trim(); |
| const trimmedStderr = stderr.trim(); |
| const stdoutIsMarker = trimmedStdout.length > 0 && isQmdNoResultsOutput(trimmedStdout); |
| const stderrIsMarker = trimmedStderr.length > 0 && isQmdNoResultsOutput(trimmedStderr); |
| if (stdoutIsMarker || (!trimmedStdout && stderrIsMarker)) { |
| return []; |
| } |
| if (!trimmedStdout) { |
| const context = trimmedStderr ? ` (stderr: ${summarizeQmdStderr(trimmedStderr)})` : ""; |
| const message = `stdout empty${context}`; |
| log.warn(`qmd query returned invalid JSON: ${message}`); |
| throw new Error(`qmd query returned invalid JSON: ${message}`); |
| } |
| try { |
| const parsed = parseQmdQueryResultArray(trimmedStdout); |
| if (parsed !== null) { |
| return parsed; |
| } |
| const noisyPayload = extractFirstJsonArray(trimmedStdout); |
| if (!noisyPayload) { |
| throw new Error("qmd query JSON response was not an array"); |
| } |
| const fallback = parseQmdQueryResultArray(noisyPayload); |
| if (fallback !== null) { |
| return fallback; |
| } |
| throw new Error("qmd query JSON response was not an array"); |
| } catch (err) { |
| const message = err instanceof Error ? err.message : String(err); |
| log.warn(`qmd query returned invalid JSON: ${message}`); |
| throw new Error(`qmd query returned invalid JSON: ${message}`, { cause: err }); |
| } |
| } |
|
|
| function isQmdNoResultsOutput(raw: string): boolean { |
| const lines = raw |
| .split(/\r?\n/) |
| .map((line) => line.trim().toLowerCase().replace(/\s+/g, " ")) |
| .filter((line) => line.length > 0); |
| return lines.some((line) => isQmdNoResultsLine(line)); |
| } |
|
|
| function isQmdNoResultsLine(line: string): boolean { |
| if (line === "no results found" || line === "no results found.") { |
| return true; |
| } |
| return /^(?:\[[^\]]+\]\s*)?(?:(?:warn(?:ing)?|info|error|qmd)\s*:\s*)+no results found\.?$/.test( |
| line, |
| ); |
| } |
|
|
| function summarizeQmdStderr(raw: string): string { |
| return raw.length <= 120 ? raw : `${raw.slice(0, 117)}...`; |
| } |
|
|
| function parseQmdQueryResultArray(raw: string): QmdQueryResult[] | null { |
| try { |
| const parsed = JSON.parse(raw) as unknown; |
| if (!Array.isArray(parsed)) { |
| return null; |
| } |
| return parsed as QmdQueryResult[]; |
| } catch { |
| return null; |
| } |
| } |
|
|
| function extractFirstJsonArray(raw: string): string | null { |
| const start = raw.indexOf("["); |
| if (start < 0) { |
| return null; |
| } |
| let depth = 0; |
| let inString = false; |
| let escaped = false; |
| for (let i = start; i < raw.length; i += 1) { |
| const char = raw[i]; |
| if (char === undefined) { |
| break; |
| } |
| if (inString) { |
| if (escaped) { |
| escaped = false; |
| continue; |
| } |
| if (char === "\\") { |
| escaped = true; |
| } else if (char === '"') { |
| inString = false; |
| } |
| continue; |
| } |
| if (char === '"') { |
| inString = true; |
| continue; |
| } |
| if (char === "[") { |
| depth += 1; |
| } else if (char === "]") { |
| depth -= 1; |
| if (depth === 0) { |
| return raw.slice(start, i + 1); |
| } |
| } |
| } |
| return null; |
| } |
|
|