Skip to content

Commit c166c4c

Browse files
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>
1 parent 75f579f commit c166c4c

2 files changed

Lines changed: 26 additions & 7 deletions

File tree

‎skills/rig/rig.ts‎

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1284,7 +1284,7 @@ function parseJson(text: string): { ok: true; value: unknown } | { ok: false; er
12841284
}
12851285
}
12861286

1287-
const objectText = extractBalancedJsonObject(text);
1287+
const objectText = extractBalancedJson(text);
12881288
if (objectText === undefined) {
12891289
return { ok: false, error: "No JSON value found." };
12901290
}
@@ -1329,16 +1329,24 @@ function extractFencedJson(text: string): string | undefined {
13291329
return text.slice(cursor, fenceEnd);
13301330
}
13311331

1332-
function extractBalancedJsonObject(text: string): string | undefined {
1332+
function extractBalancedJson(text: string): string | undefined {
13331333
const objectStart = text.indexOf("{");
1334-
if (objectStart === -1) {
1334+
const arrayStart = text.indexOf("[");
1335+
const start =
1336+
objectStart === -1 ? arrayStart :
1337+
arrayStart === -1 ? objectStart :
1338+
Math.min(objectStart, arrayStart);
1339+
if (start === -1) {
13351340
return undefined;
13361341
}
13371342

1343+
const openChar = text[start] as "{" | "[";
1344+
const closeChar = openChar === "{" ? "}" : "]";
1345+
13381346
let depth = 0;
13391347
let inString = false;
13401348
let escaping = false;
1341-
for (let index = objectStart; index < text.length; index += 1) {
1349+
for (let index = start; index < text.length; index += 1) {
13421350
const char = text[index]!;
13431351
if (inString) {
13441352
if (escaping) {
@@ -1356,12 +1364,12 @@ function extractBalancedJsonObject(text: string): string | undefined {
13561364
continue;
13571365
}
13581366

1359-
if (char === "{") {
1367+
if (char === openChar) {
13601368
depth += 1;
1361-
} else if (char === "}") {
1369+
} else if (char === closeChar) {
13621370
depth -= 1;
13631371
if (depth === 0) {
1364-
return text.slice(objectStart, index + 1);
1372+
return text.slice(start, index + 1);
13651373
}
13661374
}
13671375
}

‎src/rig.test.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,17 @@ describe("agent invocation", () => {
422422
await expect(reviewer("go")).resolves.toEqual({ text: "hello" });
423423
});
424424

425+
it("parses a JSON array embedded in surrounding text", async () => {
426+
mocks.setSendAndWaitImpl(async () => 'Here are the results:\n["alpha","beta"]\nDone.');
427+
428+
const lister = agent({
429+
name: "lister",
430+
output: s.array(s.string),
431+
});
432+
433+
await expect(lister("go")).resolves.toEqual(["alpha", "beta"]);
434+
});
435+
425436
it("retries validation failures with addon-customized repair prompts", async () => {
426437
const prompts: string[] = [];
427438
let calls = 0;

0 commit comments

Comments
 (0)