fix(runtime): classify run usage status

This commit is contained in:
ZacharyZhang-NY committed 2026-04-22 15:41:19 -04:00
1 parent d971ca7929
commit eefb515aa2
17 files changed
+308 -26

No files matched your search

+1
View File
@@ -107,6 +107,7 @@ function cancelRun(runId: string) {
exitCode: null,
signal: null,
model: null,
usageStatus: "aborted_before_usage",
unattributedCost: true,
inputTokens: null,
outputTokens: null,
+3 -1
View File
@@ -1,12 +1,13 @@
import { z } from "zod";
import type { AppDatabase } from "../../database";
import type { DesktopEvent, RunRecord } from "@shared/types";
import { RUN_USAGE_STATUSES, type DesktopEvent, type RunRecord } from "@shared/types";
import type { NotificationOptions } from "../../notification-service";
import type { createBudgetEnforcementService } from "../../budget-enforcement-service";
import type { createSessionManagementService } from "../../session-management-service";
import type { RuntimeBridge, RuntimeMessageInjector } from "../../agent-runtime";
const runStatusSchema = z.enum(["queued", "running", "succeeded", "failed", "cancelled", "timed_out", "interrupted"]);
const runUsageStatusSchema = z.enum(RUN_USAGE_STATUSES);
/**
* Single source of truth for worker → main RPC method names.
@@ -51,6 +52,7 @@ export const workerMessageSchema = z.union([
exitCode: z.number().int().nullable(),
signal: z.string().nullable(),
model: z.string().nullable(),
usageStatus: runUsageStatusSchema,
unattributedCost: z.boolean(),
inputTokens: z.number().nullable(),
outputTokens: z.number().nullable(),
@@ -47,6 +47,7 @@ export function finalizeRun(
exitCode: event.exitCode,
signal: event.signal,
model: event.model,
usageStatus: event.usageStatus,
unattributedCost: event.unattributedCost,
inputTokens: event.inputTokens,
outputTokens: event.outputTokens,
+2 -1
View File
@@ -1,5 +1,5 @@
import type { AgentMessage } from "@mariozechner/pi-agent-core";
import type { RunStatus } from "@shared/types";
import type { RunStatus, RunUsageStatus } from "@shared/types";
export interface RunJob {
runId: string;
@@ -64,6 +64,7 @@ export type ChildMessage =
exitCode: number | null;
signal: string | null;
model: string | null;
usageStatus: RunUsageStatus;
unattributedCost: boolean;
inputTokens: number | null;
outputTokens: number | null;
+21 -11
View File
@@ -8,6 +8,12 @@ import { createPiAgent, resolveThinkingLevel } from "./pi-agent-factory";
import type { WorkerRpcClient } from "../entry/rpc";
import { createHireAgentTool, HIRE_AGENT_TOOL_NAME } from "./internal-tools/hire-agent-tool";
import { createFileApprovalTool, FILE_APPROVAL_TOOL_NAME } from "./internal-tools/file-approval-tool";
import {
classifyRunUsage,
hasAttributableAssistantUsage,
isTerminalAssistantStopReason,
type TerminalAssistantStopReason,
} from "../../run-usage-status";
interface PrepareRunResult {
provider: string;
@@ -100,6 +106,8 @@ export class WorkerRunner {
inputTokens: null,
outputTokens: null,
};
let sawAttributableUsage = false;
let terminalStopReason: TerminalAssistantStopReason | null = null;
let errorMessage: string | null = null;
// Inject the AgentCompany job env (AGENT_COMPANY_API_URL / API_KEY /
@@ -217,12 +225,12 @@ export class WorkerRunner {
errorMessage?: string;
usage?: { input?: number; output?: number; cost?: { total?: number } };
};
if (
maybeAssistant.role === "assistant"
&& (maybeAssistant.stopReason === "error" || maybeAssistant.stopReason === "aborted")
) {
if (maybeAssistant.role === "assistant" && isTerminalAssistantStopReason(maybeAssistant.stopReason)) {
terminalStopReason = maybeAssistant.stopReason;
errorMessage = maybeAssistant.errorMessage ?? `Agent stopped with ${maybeAssistant.stopReason}.`;
} else if (maybeAssistant.role === "assistant" && maybeAssistant.usage) {
}
if (hasAttributableAssistantUsage(maybeAssistant)) {
sawAttributableUsage = true;
finalUsage = {
inputTokens: typeof maybeAssistant.usage.input === "number" ? maybeAssistant.usage.input : finalUsage.inputTokens,
outputTokens: typeof maybeAssistant.usage.output === "number" ? maybeAssistant.usage.output : finalUsage.outputTokens,
@@ -265,6 +273,12 @@ export class WorkerRunner {
: errorMessage
? "failed"
: "succeeded";
const usageStatus = classifyRunUsage({
runStatus: status,
errorMessage,
sawAttributableUsage,
terminalStopReason,
});
post({
type: "run-finished",
@@ -275,12 +289,8 @@ export class WorkerRunner {
exitCode: null,
signal: null,
model: resolvedModelId,
// Worker no longer owns cost attribution — CostWriter (main-side)
// writes per-message rows, finishRun sums them. This flag stays true
// only for run paths that never had a chance to emit usage (errors
// before the first message_end); per-message drift detection belongs
// elsewhere.
unattributedCost: finalUsage.inputTokens === null,
usageStatus,
unattributedCost: usageStatus !== "ok",
inputTokens: finalUsage.inputTokens,
outputTokens: finalUsage.outputTokens,
});