Skip to content
Open
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
27 changes: 18 additions & 9 deletions python/mini_claude/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
print_confirmation,
print_divider,
print_cost,
get_model_pricing,
print_retry,
print_info,
print_sub_agent_start,
Expand Down Expand Up @@ -109,6 +110,8 @@ async def _with_retry(fn, max_retries: int = 3):
"claude-opus-4-20250514": 200000,
"gpt-4o": 128000,
"gpt-4o-mini": 128000,
"MiniMax-M3": 1000000,
"MiniMax-M2.7": 204800,
}


Expand All @@ -125,12 +128,16 @@ def _model_supports_thinking(model: str) -> bool:
return False
if "claude" in m and any(x in m for x in ("opus", "sonnet", "haiku")):
return True
# MiniMax-M3 (adaptive/off) and MiniMax-M2.7 (always-on) both expose thinking.
if "minimax" in m:
return True
return False


def _model_supports_adaptive_thinking(model: str) -> bool:
m = model.lower()
return "opus-4-6" in m or "sonnet-4-6" in m
# MiniMax-M2.7 keeps thinking always-on, so only MiniMax-M3 is adaptive.
return "opus-4-6" in m or "sonnet-4-6" in m or "minimax-m3" in m


def _get_max_output_tokens(model: str) -> int:
Expand Down Expand Up @@ -514,14 +521,16 @@ def show_cost(self) -> None:
print_info(f"Tokens: {self.total_input_tokens} in / {self.total_output_tokens} out{cache_info}\n Estimated cost: ${total:.4f}{budget_info}{turn_info}")

def _get_current_cost_usd(self) -> float:
# Base input $3/Mtok. Cache read is 0.1x, cache write is 1.25x — the
# fixed multipliers Claude Code uses across every model tier.
# Per-million-token rates are resolved per model (see get_model_pricing).
# The default matches the fixed Claude multipliers (input $3, output $15,
# cache read 0.1x, cache write 1.25x); models with published rates override it.
M = 1_000_000
p = get_model_pricing(self.model)
return (
(self.total_input_tokens / M) * 3
+ (self.total_cache_read_tokens / M) * 0.3
+ (self.total_cache_creation_tokens / M) * 3.75
+ (self.total_output_tokens / M) * 15
(self.total_input_tokens / M) * p["input"]
+ (self.total_cache_read_tokens / M) * p["cache_read"]
+ (self.total_cache_creation_tokens / M) * p["cache_write"]
+ (self.total_output_tokens / M) * p["output"]
)

def _check_budget(self) -> dict:
Expand Down Expand Up @@ -1542,7 +1551,7 @@ def _on_tool_block(block: dict):

if not tool_uses:
if not self.is_sub_agent:
print_cost(self.total_input_tokens, self.total_output_tokens, self.total_cache_read_tokens, self.total_cache_creation_tokens)
print_cost(self.total_input_tokens, self.total_output_tokens, self.total_cache_read_tokens, self.total_cache_creation_tokens, self.model)
break

self.current_turns += 1
Expand Down Expand Up @@ -1763,7 +1772,7 @@ async def _chat_openai(self, user_message: str) -> None:
tool_calls = message.get("tool_calls")
if not tool_calls:
if not self.is_sub_agent:
print_cost(self.total_input_tokens, self.total_output_tokens, self.total_cache_read_tokens, self.total_cache_creation_tokens)
print_cost(self.total_input_tokens, self.total_output_tokens, self.total_cache_read_tokens, self.total_cache_creation_tokens, self.model)
break

self.current_turns += 1
Expand Down
32 changes: 26 additions & 6 deletions python/mini_claude/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,33 @@ def print_divider() -> None:
console.print(f"\n[dim] {'─' * 50}[/dim]")


def print_cost(input_tokens: int, output_tokens: int, cache_read: int = 0, cache_creation: int = 0) -> None:
# Cache read is billed 0.1x, cache write 1.25x (see agent _get_current_cost_usd).
# ─── Per-model pricing (USD per million tokens) ─────────────
# The default rates are the fixed multipliers used across Claude tiers
# (input $3, output $15, cache read 0.1x = $0.3, cache write 1.25x = $3.75).
# Models with published per-model rates override the default below so budget
# enforcement and the per-turn cost line bill at the correct rate.
DEFAULT_PRICING = {"input": 3.0, "output": 15.0, "cache_read": 0.3, "cache_write": 3.75}

MODEL_PRICING = {
# MiniMax-M3 publishes no separate cache-write rate, so cache writes are
# billed at the input rate.
"MiniMax-M3": {"input": 0.6, "output": 2.4, "cache_read": 0.12, "cache_write": 0.6},
"MiniMax-M2.7": {"input": 0.3, "output": 1.2, "cache_read": 0.06, "cache_write": 0.375},
}


def get_model_pricing(model: str) -> dict:
return MODEL_PRICING.get(model, DEFAULT_PRICING)


def print_cost(input_tokens: int, output_tokens: int, cache_read: int = 0, cache_creation: int = 0, model: str | None = None) -> None:
# Cache read/write and per-token rates are model-specific (see get_model_pricing).
p = get_model_pricing(model) if model else DEFAULT_PRICING
total = (
(input_tokens / 1_000_000) * 3
+ (cache_read / 1_000_000) * 0.3
+ (cache_creation / 1_000_000) * 3.75
+ (output_tokens / 1_000_000) * 15
(input_tokens / 1_000_000) * p["input"]
+ (cache_read / 1_000_000) * p["cache_read"]
+ (cache_creation / 1_000_000) * p["cache_write"]
+ (output_tokens / 1_000_000) * p["output"]
)
cache_str = f", {cache_read} cached" if cache_read else ""
console.print(f"\n[dim] Tokens: {input_tokens} in / {output_tokens} out{cache_str} (~${total:.4f})[/dim]")
Expand Down
28 changes: 18 additions & 10 deletions src/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
printConfirmation,
printDivider,
printCost,
getModelPricing,
printRetry,
printInfo,
printSubAgentStart,
Expand Down Expand Up @@ -79,6 +80,8 @@ const MODEL_CONTEXT: Record<string, number> = {
"claude-opus-4-20250514": 200000,
"gpt-4o": 128000,
"gpt-4o-mini": 128000,
"MiniMax-M3": 1000000,
"MiniMax-M2.7": 204800,
};

function getContextWindow(model: string): number {
Expand All @@ -93,12 +96,15 @@ function modelSupportsThinking(model: string): boolean {
// Claude 4+ models support thinking (not Claude 3.x)
if (m.includes("claude-3-") || m.includes("3-5-") || m.includes("3-7-")) return false;
if (m.includes("claude") && (m.includes("opus") || m.includes("sonnet") || m.includes("haiku"))) return true;
return false; // non-Claude models (GPT, etc.) — no thinking
// MiniMax-M3 (adaptive/off) and MiniMax-M2.7 (always-on) both expose thinking.
if (m.includes("minimax")) return true;
return false; // other non-Claude models (GPT, etc.) — no thinking
}

function modelSupportsAdaptiveThinking(model: string): boolean {
const m = model.toLowerCase();
return m.includes("opus-4-6") || m.includes("sonnet-4-6");
// MiniMax-M2.7 keeps thinking always-on, so only MiniMax-M3 is adaptive.
return m.includes("opus-4-6") || m.includes("sonnet-4-6") || m.includes("minimax-m3");
}

// Max output tokens by model (following the same caps Claude Code uses publicly)
Expand Down Expand Up @@ -553,12 +559,14 @@ export class Agent {

private getCurrentCostUsd(): number {
const M = 1_000_000;
// Base input $3/Mtok. Cache read is 0.1x, cache write is 1.25x — the fixed
// multipliers Claude Code uses across every model tier (utils/modelCost.ts).
const costIn = (this.totalInputTokens / M) * 3;
const costCacheRead = (this.totalCacheReadTokens / M) * 0.3;
const costCacheWrite = (this.totalCacheCreationTokens / M) * 3.75;
const costOut = (this.totalOutputTokens / M) * 15;
// Per-million-token rates are resolved per model (see getModelPricing).
// The default matches the fixed Claude multipliers (input $3, output $15,
// cache read 0.1x, cache write 1.25x); models with published rates override it.
const p = getModelPricing(this.model);
const costIn = (this.totalInputTokens / M) * p.input;
const costCacheRead = (this.totalCacheReadTokens / M) * p.cacheRead;
const costCacheWrite = (this.totalCacheCreationTokens / M) * p.cacheWrite;
const costOut = (this.totalOutputTokens / M) * p.output;
return costIn + costCacheRead + costCacheWrite + costOut;
}

Expand Down Expand Up @@ -1710,7 +1718,7 @@ IMPORTANT: When your plan is complete, you MUST call exit_plan_mode. Do NOT ask

if (toolUses.length === 0) {
if (!this.isSubAgent) {
printCost(this.totalInputTokens, this.totalOutputTokens, this.totalCacheReadTokens, this.totalCacheCreationTokens);
printCost(this.totalInputTokens, this.totalOutputTokens, this.totalCacheReadTokens, this.totalCacheCreationTokens, this.model);
}
break;
}
Expand Down Expand Up @@ -1949,7 +1957,7 @@ IMPORTANT: When your plan is complete, you MUST call exit_plan_mode. Do NOT ask
const toolCalls = message.tool_calls;
if (!toolCalls || toolCalls.length === 0) {
if (!this.isSubAgent) {
printCost(this.totalInputTokens, this.totalOutputTokens, this.totalCacheReadTokens, this.totalCacheCreationTokens);
printCost(this.totalInputTokens, this.totalOutputTokens, this.totalCacheReadTokens, this.totalCacheCreationTokens, this.model);
}
break;
}
Expand Down
43 changes: 37 additions & 6 deletions src/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,44 @@ export function printDivider() {
console.log(chalk.gray("\n " + "─".repeat(50)));
}

export function printCost(inputTokens: number, outputTokens: number, cacheRead = 0, cacheCreation = 0) {
// Cache read is billed 0.1x, cache write 1.25x (see agent getCurrentCostUsd).
// ─── Per-model pricing (USD per million tokens) ─────────────
// The default rates are the fixed multipliers used across Claude tiers
// (input $3, output $15, cache read 0.1x = $0.3, cache write 1.25x = $3.75).
// Models with published per-model rates override the default below so budget
// enforcement and the per-turn cost line bill at the correct rate.
export interface ModelPricing {
input: number;
output: number;
cacheRead: number;
cacheWrite: number;
}

export const DEFAULT_PRICING: ModelPricing = {
input: 3,
output: 15,
cacheRead: 0.3,
cacheWrite: 3.75,
};

export const MODEL_PRICING: Record<string, ModelPricing> = {
// MiniMax-M3 publishes no separate cache-write rate, so cache writes are
// billed at the input rate.
"MiniMax-M3": { input: 0.6, output: 2.4, cacheRead: 0.12, cacheWrite: 0.6 },
"MiniMax-M2.7": { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0.375 },
};

export function getModelPricing(model: string): ModelPricing {
return MODEL_PRICING[model] || DEFAULT_PRICING;
}

export function printCost(inputTokens: number, outputTokens: number, cacheRead = 0, cacheCreation = 0, model?: string) {
// Cache read/write and per-token rates are model-specific (see getModelPricing).
const p = model ? getModelPricing(model) : DEFAULT_PRICING;
const total =
(inputTokens / 1_000_000) * 3 +
(cacheRead / 1_000_000) * 0.3 +
(cacheCreation / 1_000_000) * 3.75 +
(outputTokens / 1_000_000) * 15;
(inputTokens / 1_000_000) * p.input +
(cacheRead / 1_000_000) * p.cacheRead +
(cacheCreation / 1_000_000) * p.cacheWrite +
(outputTokens / 1_000_000) * p.output;
const cacheStr = cacheRead ? `, ${cacheRead} cached` : "";
console.log(
chalk.gray(
Expand Down