From c166c4cd01a5940cc9e0fb5fa7b4882ca0403b90 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 23 Jul 2026 10:57:13 +0000 Subject: [PATCH] fix: extract balanced JSON arrays from prose responses Previously extractBalancedJsonObject only scanned for '{...}' objects. Agents with array output schemas (s.array(...)) would fail to parse responses where the model embedded the JSON array in surrounding prose, triggering an unnecessary repair turn. Rename to extractBalancedJson and pick whichever container character ({ or [) appears first, so both objects and arrays are handled. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- skills/rig/rig.ts | 22 +++++++++++++++------- src/rig.test.ts | 11 +++++++++++ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/skills/rig/rig.ts b/skills/rig/rig.ts index f301cd4..39625fa 100644 --- a/skills/rig/rig.ts +++ b/skills/rig/rig.ts @@ -1284,7 +1284,7 @@ function parseJson(text: string): { ok: true; value: unknown } | { ok: false; er } } - const objectText = extractBalancedJsonObject(text); + const objectText = extractBalancedJson(text); if (objectText === undefined) { return { ok: false, error: "No JSON value found." }; } @@ -1329,16 +1329,24 @@ function extractFencedJson(text: string): string | undefined { return text.slice(cursor, fenceEnd); } -function extractBalancedJsonObject(text: string): string | undefined { +function extractBalancedJson(text: string): string | undefined { const objectStart = text.indexOf("{"); - if (objectStart === -1) { + const arrayStart = text.indexOf("["); + const start = + objectStart === -1 ? arrayStart : + arrayStart === -1 ? objectStart : + Math.min(objectStart, arrayStart); + if (start === -1) { return undefined; } + const openChar = text[start] as "{" | "["; + const closeChar = openChar === "{" ? "}" : "]"; + let depth = 0; let inString = false; let escaping = false; - for (let index = objectStart; index < text.length; index += 1) { + for (let index = start; index < text.length; index += 1) { const char = text[index]!; if (inString) { if (escaping) { @@ -1356,12 +1364,12 @@ function extractBalancedJsonObject(text: string): string | undefined { continue; } - if (char === "{") { + if (char === openChar) { depth += 1; - } else if (char === "}") { + } else if (char === closeChar) { depth -= 1; if (depth === 0) { - return text.slice(objectStart, index + 1); + return text.slice(start, index + 1); } } } diff --git a/src/rig.test.ts b/src/rig.test.ts index 538eb20..b59cdfe 100644 --- a/src/rig.test.ts +++ b/src/rig.test.ts @@ -422,6 +422,17 @@ describe("agent invocation", () => { await expect(reviewer("go")).resolves.toEqual({ text: "hello" }); }); + it("parses a JSON array embedded in surrounding text", async () => { + mocks.setSendAndWaitImpl(async () => 'Here are the results:\n["alpha","beta"]\nDone.'); + + const lister = agent({ + name: "lister", + output: s.array(s.string), + }); + + await expect(lister("go")).resolves.toEqual(["alpha", "beta"]); + }); + it("retries validation failures with addon-customized repair prompts", async () => { const prompts: string[] = []; let calls = 0;