Fix NaN cost display and show request count in usage summary

Amolith and Shelley created

When model cost config is zero (local/free models), the cost math
produced NaN. Now cost is only shown when it's a valid positive number.

Usage summary now includes the number of LLM requests (assistant
messages) for better observability: 'usage: N tokens across M requests'.

Co-authored-by: Shelley <shelley@exe.dev>

Change summary

src/agent/runner.ts      |  4 ++++
src/cli/commands/repo.ts |  2 +-
src/cli/commands/web.ts  |  2 +-
src/cli/output.ts        | 18 +++++++++++++++---
4 files changed, 21 insertions(+), 5 deletions(-)

Detailed changes

src/agent/runner.ts 🔗

@@ -16,6 +16,7 @@ export interface AgentRunOptions {
 export interface AgentRunResult {
   message: string;
   usage?: unknown;
+  requestCount: number;
 }
 
 /**
@@ -82,8 +83,11 @@ export async function runAgent(query: string, options: AgentRunOptions): Promise
     throw new AgentError("Agent returned no text response");
   }
 
+  const requestCount = agent.state.messages.filter((msg) => msg.role === "assistant").length;
+
   return {
     message: text,
     usage: last?.usage,
+    requestCount,
   };
 }

src/cli/commands/repo.ts 🔗

@@ -90,7 +90,7 @@ export async function runRepoCommand(options: RepoCommandOptions): Promise<void>
     });
 
     process.stdout.write(result.message + "\n");
-    printUsageSummary(result.usage as any);
+    printUsageSummary(result.usage as any, result.requestCount);
   } finally {
     await workspace.cleanup();
   }

src/cli/commands/web.ts 🔗

@@ -97,7 +97,7 @@ export async function runWebCommand(options: WebCommandOptions): Promise<void> {
     });
 
     process.stdout.write(result.message + "\n");
-    printUsageSummary(result.usage as any);
+    printUsageSummary(result.usage as any, result.requestCount);
   } finally {
     await workspace.cleanup();
   }

src/cli/output.ts 🔗

@@ -44,10 +44,22 @@ export function createEventLogger(options: OutputOptions) {
   };
 }
 
-export function printUsageSummary(usage: { cost?: { total?: number }; totalTokens?: number; output?: number; input?: number } | undefined) {
+export function printUsageSummary(
+  usage: { cost?: { total?: number }; totalTokens?: number; output?: number; input?: number } | undefined,
+  requestCount?: number,
+) {
   if (!usage) return;
 
-  const cost = usage.cost?.total ?? 0;
   const tokens = usage.totalTokens ?? (usage.output ?? 0) + (usage.input ?? 0);
-  console.error(`\nusage: ${tokens} tokens, cost $${cost.toFixed(4)}`);
+  const rawCost = usage.cost?.total;
+  const cost = typeof rawCost === "number" && !isNaN(rawCost) && rawCost > 0 ? rawCost : undefined;
+
+  let line = `\nusage: ${tokens} tokens`;
+  if (requestCount !== undefined && requestCount > 0) {
+    line += ` across ${requestCount} ${requestCount === 1 ? "request" : "requests"}`;
+  }
+  if (cost !== undefined) {
+    line += `, cost $${cost.toFixed(4)}`;
+  }
+  console.error(line);
 }