Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions actions/setup/js/parse_pi_log.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ function parsePiLog(logContent) {
},
duration_ms: stats.duration_ms || 0,
num_turns: stats.turns || 0,
errors: stats.errors && stats.errors.length > 0 ? stats.errors : undefined,
};
markdown += generateInformationSection(syntheticEntry);

Expand All @@ -96,6 +97,7 @@ function parsePiLog(logContent) {
type: "result",
num_turns: syntheticEntry.num_turns,
usage: syntheticEntry.usage,
errors: syntheticEntry.errors,
});
} else {
markdown += generateInformationSection(null);
Expand Down Expand Up @@ -365,13 +367,15 @@ function isPiV3ResultError(result) {
* finalized turns.
*
* @param {Array<any>} rawEntries - Raw parsed JSONL entries
* @returns {{input_tokens:number, output_tokens:number, turns:number, duration_ms:number}|null} Stats or null when unavailable
* @returns {{input_tokens:number, output_tokens:number, turns:number, duration_ms:number, errors:Array<string>}|null} Stats or null when unavailable
*/
function computePiV3Stats(rawEntries) {
let outputTokens = 0;
let inputTokens = 0;
let turns = 0;
let sawUsage = false;
/** @type {Array<string>} */
const errors = [];

for (const raw of rawEntries) {
if (raw.type !== "turn_end") {
Expand All @@ -388,6 +392,14 @@ function computePiV3Stats(rawEntries) {
inputTokens += usage.input;
}
}
// A turn can end with an empty content array and no toolCalls when the provider
// request itself failed (e.g. a 400 model_not_supported response); the only trace
// of the failure is this top-level errorMessage, which would otherwise render as a
// silent, content-free turn in the step summary.
const errorMessage = raw.message && typeof raw.message.errorMessage === "string" ? raw.message.errorMessage : undefined;
if (errorMessage) {
errors.push(errorMessage);
}
}

if (turns === 0 && !sawUsage) {
Expand All @@ -399,13 +411,14 @@ function computePiV3Stats(rawEntries) {
output_tokens: outputTokens,
turns: turns,
duration_ms: 0,
errors: errors,
};
}

/**
* Extracts stats from a legacy Pi `result` event, preserving the original flat-schema behavior.
* @param {Array<any>} rawEntries - Raw parsed JSONL entries
* @returns {{input_tokens:number, output_tokens:number, turns:number, duration_ms:number}|null} Stats or null when absent
* @returns {{input_tokens:number, output_tokens:number, turns:number, duration_ms:number, errors:Array<string>}|null} Stats or null when absent
*/
function legacyPiStats(rawEntries) {
const resultEntry = rawEntries.find(e => e.type === "result");
Expand All @@ -418,6 +431,7 @@ function legacyPiStats(rawEntries) {
output_tokens: stats.output_tokens || 0,
turns: stats.turns || 0,
duration_ms: stats.duration_ms || 0,
errors: [],
};
}

Expand Down
35 changes: 35 additions & 0 deletions actions/setup/js/parse_pi_log.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,41 @@ describe("parse_pi_log.cjs", () => {
expect(toolResult.message.content[0].is_error).toBe(true);
expect(toolResult.message.content[0].content).toContain("boom");
});

it("surfaces a provider-level errorMessage from an empty-content turn_end (e.g. model_not_supported)", () => {
// Reproduces a real failed run: the provider request itself fails (HTTP 400), so
// turn_end carries content: [] with no assistant text or tool calls — the only trace
// of the failure is the top-level errorMessage on the turn_end message.
const failedTurnLines = [
{ type: "session", version: 3, id: "s1" },
{ type: "agent_start" },
{ type: "turn_start" },
{
type: "turn_end",
message: {
role: "assistant",
model: "auto",
content: [],
stopReason: "error",
usage: { input: 0, output: 0 },
errorMessage: '400: {"message":"The requested model is not supported.","code":"model_not_supported","param":"model","type":"invalid_request_error"}',
},
toolResults: [],
},
{ type: "agent_end", messages: [], willRetry: false },
{ type: "agent_settled" },
];
const failedLog = failedTurnLines.map(l => JSON.stringify(l)).join("\n");

const stats = computePiV3Stats(failedTurnLines);
expect(stats.errors).toEqual([expect.stringContaining("model_not_supported")]);

const result = parsePiLog(failedLog);
expect(result.markdown).toContain("model_not_supported");

const resultEntry = result.logEntries.find(e => e.type === "result");
expect(resultEntry.errors).toEqual([expect.stringContaining("model_not_supported")]);
});
});

describe("normalizePiToolName", () => {
Expand Down
Loading