diff --git a/scripts/monitor-pilot/live-runner.ts b/scripts/monitor-pilot/live-runner.ts index 9f814eb..8a5f62b 100644 --- a/scripts/monitor-pilot/live-runner.ts +++ b/scripts/monitor-pilot/live-runner.ts @@ -282,7 +282,20 @@ function parseArgs(call: { readonly id: string; readonly function: { readonly na } function toolCallMessage(response: LlmResponse): ChatMessage { - return response.message + const toolCalls = response.message.tool_calls?.map((call) => { + if (isJsonObjectArguments(call.function.arguments)) return call + return { ...call, function: { ...call.function, arguments: "{}" } } + }) + return { ...response.message, tool_calls: toolCalls } +} + +function isJsonObjectArguments(raw: string): boolean { + try { + const parsed: unknown = JSON.parse(raw) + return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) + } catch { + return false + } } function stableCallBatchSignature(calls: readonly PilotToolCall[]): string { diff --git a/src/monitoring/__tests__/live-runner.test.ts b/src/monitoring/__tests__/live-runner.test.ts index 79029cb..87d3755 100644 --- a/src/monitoring/__tests__/live-runner.test.ts +++ b/src/monitoring/__tests__/live-runner.test.ts @@ -456,6 +456,25 @@ async function testReasoningHeavyNativeOverrunIsAccepted(): Promise { } finally { await fs.rm(directory, { recursive: true, force: true }) } } +async function testMalformedArgumentsAreSanitizedOnEcho(): Promise { + const directory = await scratch() + try { + const broken = { name: "apply_diff", args: "{\"content\":\"truncated" } + const done = { name: "attempt_completion", + args: JSON.stringify({ result: "done" }) } + const client = new MockBillingClient([broken, done]) + const summary = await collectLiveDevelopment({ outputDir: directory, client }) + assert.equal(summary.abortedAttempts, 0) + assert.equal(summary.completedRuns, 32) + const history = client.requests[1]?.messages ?? [] + const calls = history.filter((message) => { + return (message.tool_calls?.length ?? 0) > 0 + }) + const echoed = calls[0]?.tool_calls?.[0]?.function.arguments + assert.equal(echoed, "{}") + } finally { await fs.rm(directory, { recursive: true, force: true }) } +} + async function testHeldoutCannotStartWithoutQualifiedDevelopment(): Promise { const directory = await scratch() try { @@ -537,6 +556,7 @@ await testCopiedGlobalLedgerEpochsDoNotBlockResume() await testKnownPerRunAdmissionStopCompletesAndSettlesTrajectory() await testCompletionOverrunIsJournaledBeforeAnyToolDispatch() await testReasoningHeavyNativeOverrunIsAccepted() +await testMalformedArgumentsAreSanitizedOnEcho() await testHeldoutCannotStartWithoutQualifiedDevelopment() await testHeldoutRejectsDifferentFrozenOutputCapBeforeInference() await testStorageRootSymlinkIsRejectedBeforeInference()