TheFocus.AI TheFocus.AI
02 core Lesson 5

Chapter 05: Cost & Context Awareness

Track model pricing, usage, and context window. Display cost after every turn so you never blow your API budget.

TUTOR WITH THEFOCUS.AI

Agent Integration

Copy this prompt into Claude, ChatGPT, or any external AI assistant. It points the assistant to the course instructions and links it to your student profile to track your progress and customize observations.

Please tutor me in this lesson using the following context. First, read the instructions at: https://courses.thefocus.ai/llms.txt My Student ID is: <none> The lesson markdown source is at: https://courses.thefocus.ai/build-ai-coding-agent/02-core/05-cost-context.md

You are not enrolled yet. Enroll to generate a Student ID to track lesson completions and store learning notes.

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:

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:

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:

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

OperationTypical Input TokensTypical Output TokensApproximate Cost
Simple question500200$0.001
File read + analysis1,500400$0.004
Complex edit (3 tools)3,000800$0.010
Full feature (10+ turns)50,00015,000$0.150
Full session (1 hour)200,00060,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:

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 · Next: Chapter 06 →

Previous Lesson 5 of 11 Next