Tighten Claude partial tool input parsing

This commit is contained in:
Mohamed Boudra
2026-03-25 20:38:43 +07:00
parent 08f25f065a
commit 508cd524a8
3 changed files with 12 additions and 21 deletions
@@ -441,7 +441,7 @@ describe("ClaudeAgentSession redesign invariants", () => {
}
});
test("routes input_json_delta through partial parsing before buffered JSON is complete", async () => {
test("waits for complete JSON values before updating tool input from input_json_delta", async () => {
const session = await createSession();
const internal = session as unknown as {
mapPartialEvent: (event: Record<string, unknown>) => AgentTimelineItem[];
@@ -479,7 +479,7 @@ describe("ClaudeAgentSession redesign invariants", () => {
partial_json: '{"command":"echo ',
},
},
expectedCommand: "echo ",
expectedCommand: "echo seed",
},
{
event: {
@@ -520,7 +520,7 @@ describe("ClaudeAgentSession redesign invariants", () => {
}
});
test("surfaces canonical partial tool input from input_json_delta before JSON is complete", async () => {
test("does not surface incomplete string values from input_json_delta", async () => {
const session = await createSession();
const internal = session as unknown as {
mapPartialEvent: (event: Record<string, unknown>) => AgentTimelineItem[];
@@ -551,7 +551,6 @@ describe("ClaudeAgentSession redesign invariants", () => {
expect(internal.toolUseCache.get(toolUseId)?.input).toEqual({
file_path: "src/message.tsx",
old_string: "before",
});
} finally {
await session.close();
@@ -13,33 +13,25 @@ describe("parsePartialJsonObject", () => {
});
});
it("keeps partial string values without field-specific logic", () => {
it("does not emit incomplete string values", () => {
expect(parsePartialJsonObject('{"command":"echo ')).toEqual({
value: {
command: "echo ",
},
value: {},
complete: false,
});
});
it("returns parsed prefix fields from incomplete objects", () => {
it("returns only complete prefix fields from incomplete objects", () => {
expect(parsePartialJsonObject('{"file_path":"src/message.tsx","old_string":"before')).toEqual({
value: {
file_path: "src/message.tsx",
old_string: "before",
},
complete: false,
});
});
it("parses nested partial values generically", () => {
it("does not emit incomplete nested values", () => {
expect(parsePartialJsonObject('{"payload":{"path":"src/index.ts","content":"hello')).toEqual({
value: {
payload: {
path: "src/index.ts",
content: "hello",
},
},
value: {},
complete: false,
});
});
@@ -271,17 +271,17 @@ function parsePartialObject(
};
}
value[parsedKey.value] = parsedMemberValue.value;
currentIndex = skipWhitespace(input, parsedMemberValue.nextIndex);
if (!parsedMemberValue.complete) {
return {
value,
nextIndex: currentIndex,
nextIndex: skipWhitespace(input, parsedMemberValue.nextIndex),
complete: false,
};
}
value[parsedKey.value] = parsedMemberValue.value;
currentIndex = skipWhitespace(input, parsedMemberValue.nextIndex);
const delimiter = input[currentIndex];
if (delimiter === ",") {
currentIndex += 1;