---
title: "Chapter 05: Cost & Context Awareness"
description: "Track model pricing, usage, and context window. Display cost after every turn so you never blow your API budget."
type: lesson
order: 5
chapter: "02-core"
---

# Chapter 05: Cost & Context Awareness

## What We're Building

Right now your agent has no idea how much it's costing you. We'll add:

- **Model stats lookup** — Fetch pricing from OpenRouter on startup
- **Per-turn cost tracking** — Show cost after every response
- **Session accumulation** — Running total for the whole conversation
- **Context size awareness** — How many tokens we're using and when we're getting close to the limit

## Step 1: Add Model Stats and Cost Tracking

Paste this prompt into your agent:

> Look up the model stats on startup, including name and cost.
> Log cost stats from the responses.
> Calculate message length, context size, and current cost.
> Display status after each message.

## Step 2: The Implementation

### Model Stats Lookup

The agent will create a `fetchModelStats` function in `src/lib/api.ts`:

```typescript
export interface ModelStats {
  name: string;
  cost: {
    prompt: number; // Cost per token for input
    completion: number; // Cost per token for output
  };
}

let cachedStats: ModelStats | null = null;

export async function fetchModelStats(): Promise<ModelStats | null> {
  if (cachedStats) return cachedStats;

  const response = await fetch("https://openrouter.ai/api/v1/models");
  const data = await response.json();
  const models = (data as any).data;
  const modelInfo = models.find((m: any) => m.id === MODEL);

  if (modelInfo) {
    cachedStats = {
      name: modelInfo.name,
      cost: {
        prompt: parseFloat(modelInfo.pricing.prompt) || 0,
        completion: parseFloat(modelInfo.pricing.completion) || 0,
      },
    };
    return cachedStats;
  }
  return null;
}
```

This fetches the full model list from OpenRouter, finds your model, extracts the pricing, and caches it. The cost values are in dollars per token. You'll see prices like:

```
Model: Google Gemini 3 Pro Preview
Cost: $1.25 per 1M input tokens, $10.00 per 1M output tokens
```

### The Startup Display

In `src/index.ts`, the startup sequence shows model info:

```typescript
const stats = await fetchModelStats();
if (stats) {
  console.log(`Model: ${stats.name}`);
  console.log(
    `Cost: $${stats.cost.prompt * 1000000} per 1M input tokens, ` +
      `$${stats.cost.completion * 1000000} per 1M output tokens`,
  );
} else {
  console.log(`Model: ${MODEL} (Stats not found)`);
}
```

### Per-Turn Cost Calculation

After each agent turn, calculate and display the cost:

```typescript
let turnCost = 0;
if (stats) {
  turnCost =
    usage.prompt_tokens * stats.cost.prompt +
    usage.completion_tokens * stats.cost.completion;
  sessionUsage.cost += turnCost;
}

console.log(
  `\n[Turn Usage] Input: ${usage.prompt_tokens} | ` +
    `Output: ${usage.completion_tokens} | Cost: $${turnCost.toFixed(6)}`,
);
console.log(
  `[Session Total] Input: ${sessionUsage.prompt_tokens} | ` +
    `Output: ${sessionUsage.completion_tokens} | Cost: $${sessionUsage.cost.toFixed(6)}`,
);
```

### Context Awareness

The agent accumulates usage across the session. As the context window fills up (128K–200K tokens for most models), things get expensive and potentially dumb. The session totals help you know when it's time to compact (which we'll add in Chapter 11).

## Step 3: The Revenue Question

Paste this follow-up:

> Print the usage right after each of the responses, not before.

This ensures the cost display follows the output you care about. You'll see something like:

```
Assistant: Here's the test you asked for...

[Turn Usage] Input: 2,450 | Output: 850 | Cost: $0.011562
[Session Total] Input: 45,200 | Output: 12,400 | Cost: $0.180500
```

## Step 4: Update the Prompt to Auto-Verify

Add this improvement:

> update the prompt to always run linting and testing after each work unit, and make sure that everything currently is tested

This adds a verification step to the agent's workflow: after every code change, run `mise run check`. The `run_check` tool now becomes the default finale for any code editing cycle.

## Step 5: Test It

Start your agent and issue a few commands:

```
read src/agent.ts and explain what runTurn does
```

```
search for "cost" in the src directory
```

Check the cost display after each turn. For a typical coding session, you'll see costs like:

- Simple file reads: $0.001–0.005 per turn
- Complex edits with tool calls: $0.01–0.05 per turn
- Full-feature builds: $0.10–0.50 per session

At these rates, you can build a lot for a few dollars. But it adds up — which is why tracking is essential.

## Step 6: Understand the Numbers

| Operation                | Typical Input Tokens | Typical Output Tokens | Approximate Cost |
| ------------------------ | -------------------- | --------------------- | ---------------- |
| Simple question          | 500                  | 200                   | $0.001           |
| File read + analysis     | 1,500                | 400                   | $0.004           |
| Complex edit (3 tools)   | 3,000                | 800                   | $0.010           |
| Full feature (10+ turns) | 50,000               | 15,000                | $0.150           |
| Full session (1 hour)    | 200,000              | 60,000                | $0.600           |

> **Note:** These are estimates with Gemini 3 Pro Preview. Opus 4.5 is about 10–15× more expensive. Choose your model accordingly!

---

## Key Source File

`src/lib/api.ts` contains both the model stats fetcher and the LLM caller:

```typescript
import { TOOLS } from "../tools/index";
import type { CompletionResponse, Message } from "./types";

const API_URL = "https://openrouter.ai/api/v1/chat/completions";
export const MODEL = process.env.AGENT_MODEL || "anthropic/claude-opus-4.5";

export async function callLLM(
  messages: Message[],
  tools: any[] = TOOLS,
): Promise<CompletionResponse> {
  const apiKey = process.env.OPENROUTER_API_KEY;

  const response = await fetch(API_URL, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      model: MODEL,
      messages: messages,
      tools: tools,
      reasoning: { max_tokens: 5000 },
      include_reasoning: true,
    }),
  });

  return await response.json();
}
```

Note the `reasoning` and `include_reasoning` fields — these enable the model's thinking/reasoning output, which you can see with Gemini (OpenRouter surfaces this as `reasoning_details`).

---

[← Chapter 04](/build-ai-coding-agent/02-core/04-tools/) · [Next: Chapter 06 →](/build-ai-coding-agent/02-core/06-sessions/)
