@juspay/neurolink
Version:
The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002
105 lines (104 loc) • 3.96 kB
JavaScript
/**
* Structured-output recovery ladder for the isolated-agent runner.
*
* Ports the production-hardened pattern the framework's consumers built by
* hand: candidates are tried in a fixed order — provider `structuredData` →
* raw JSON object → ```json fence → first/last-brace span → top-level array —
* each passed through the caller's `coerce` normalizer and validated against
* the (lenient) local schema. The first schema-valid candidate wins;
* validation errors are collected for corrective re-asks.
*/
function tryParse(text) {
try {
return JSON.parse(text);
}
catch {
return undefined;
}
}
/**
* Collect parse candidates from model text (and provider structuredData) in
* ladder order. Never throws.
*/
export function collectStructuredCandidates(content, structuredData) {
const candidates = [];
if (structuredData !== undefined && structuredData !== null) {
candidates.push({ source: "structured-data", value: structuredData });
}
const text = (content ?? "").trim();
if (!text) {
return candidates;
}
// Raw JSON object/array/scalar
const raw = tryParse(text);
if (raw !== undefined) {
candidates.push({ source: "raw-json", value: raw });
}
// ```json fenced block (first fence wins)
const fenceMatch = text.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/i);
if (fenceMatch?.[1]) {
const fenced = tryParse(fenceMatch[1].trim());
if (fenced !== undefined) {
candidates.push({ source: "json-fence", value: fenced });
}
}
// First-brace .. last-brace span
const firstBrace = text.indexOf("{");
const lastBrace = text.lastIndexOf("}");
if (firstBrace !== -1 && lastBrace > firstBrace) {
const span = tryParse(text.slice(firstBrace, lastBrace + 1));
if (span !== undefined) {
candidates.push({ source: "brace-span", value: span });
}
}
// Top-level array span
const firstBracket = text.indexOf("[");
const lastBracket = text.lastIndexOf("]");
if (firstBracket !== -1 && lastBracket > firstBracket) {
const arraySpan = tryParse(text.slice(firstBracket, lastBracket + 1));
if (arraySpan !== undefined && Array.isArray(arraySpan)) {
candidates.push({ source: "top-level-array", value: arraySpan });
}
}
return candidates;
}
/**
* Run the full ladder: candidates (through `coerce`) validated against
* `schema`; first success wins. Never throws — coerce/validation failures
* become entries in `errors`.
*/
export function recoverStructuredData(args) {
const errors = [];
const candidates = collectStructuredCandidates(args.content, args.structuredData);
for (const candidate of candidates) {
let value = candidate.value;
if (args.coerce) {
try {
value = args.coerce(value);
}
catch (coerceError) {
errors.push(`${candidate.source}: coerce failed — ${coerceError instanceof Error
? coerceError.message
: String(coerceError)}`);
continue;
}
}
const parsed = args.schema.safeParse(value);
if (parsed.success) {
return { data: parsed.data, source: candidate.source, errors };
}
errors.push(`${candidate.source}: ${summarizeZodError(parsed.error)}`);
}
if (candidates.length === 0) {
errors.push("no JSON-shaped candidates found in output");
}
return { errors };
}
/** Bounded, prompt-friendly summary of a Zod validation error. */
export function summarizeZodError(error) {
const issues = error.issues
.slice(0, 5)
.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`);
const extra = error.issues.length > 5 ? ` (+${error.issues.length - 5})` : "";
return issues.join("; ") + extra;
}