April 21, 2026

[OpenRouter-Kostenverfolgung gibt $0 wegen Typfehler zurück] - OpenRouter cost tracking returns $0 due to type mismatch in extractCostBreakdown()

Die extractCostBreakdown()-Funktion erwartet usage.cost als Objekt, aber OpenRouter gibt es als einfache Zahl zurück, was zu stillen Kostenverfolgungsfehlern führt.

🔍 Symptome

Primäre Manifestation: OpenRouter-API-Anfragen geben usage.cost als skalaren numerischen Wert zurück (z.B. 0.0045), aber die Kostenverfolgungslogik versucht darauf als verschachtelte Objekteigenschaft zuzugreifen.

Fehlerausgabe:

// In pi-embedded-*.js extractCostBreakdown() function:
const total = toFiniteNumber(cost.total); // cost ist number 0.0045
// Ergebnis: total = NaN (da cost.total === undefined)
// Kosten protokolliert als: $0.00

// Konsolenausgabe Beispiel:
[OpenClaw] OpenRouter Request Complete
  Model: anthropic/claude-3.5-sonnet
  Cost: $0.00    ← FALSCH (sollte ~$0.0045 sein)
  Tokens: 2453 input, 892 output

Technische Erkennung:

// Debug-Inspektion der OpenRouter-Antwort
console.log(response.usage.cost);
// Ausgabe: 0.0045  (number)

// Debug-Inspektion der internen Kostenextraktion
console.log(typeof response.usage.cost);
// Ausgabe: "number"

// Erwartete Struktur gemäß anderen Providern
console.log(response.usage.cost);
// Ausgabe: { total: 0.0045, input: 0.001, output: 0.0035 }  (object)

🧠 Ursache

Architektonische Abweichung: Die Funktion extractCostBreakdown() wurde für OpenAI-kompatible Kostenstrukturen konzipiert, bei denen usage.cost immer ein Objekt mit total, input und output Schlüsseln ist.

Fehlersequenz:

  1. OpenRouter API gibt usage.cost = 0.0045 zurück (skalare Zahl)
  2. extractCostBreakdown() führt aus: const total = toFiniteNumber(cost.total)
  3. Wenn cost eine Zahl ist, ergibt cost.total undefined
  4. toFiniteNumber(undefined) gibt NaN zurück
  5. Die Kostenaggregationslogik behandelt NaN für Anzeigezwecke als $0
  6. Kein Fehler wird ausgelöst – der Fehler ist still

Betroffener Codepfad:

// Datei: dist/pi-embedded-*.js
// Position: extractCostBreakdown() Funktion

function extractCostBreakdown(cost) {
  const total = toFiniteNumber(cost.total);  // ← SCHLÄGT FEHL wenn cost eine Zahl ist
  const input = toFiniteNumber(cost.input);
  const output = toFiniteNumber(cost.output);
  
  return { total, input, output };
}

Provider-Vergleich:

Providerusage.cost TypStruktur
OpenAIObject{ total, input, output }
Azure OpenAIObject{ total, input, output }
AnthropicObject{ total, input, output }
OpenRouterNumber0.0045 (flacher Wert)

🛠️ Schritt-für-Schritt-Lösung

Zieldatei: dist/pi-embedded-*.js (oder Quelläquivalent)

Erforderliche Änderung: Aktualisieren Sie die Funktion extractCostBreakdown(), um sowohl skalare Zahlen- als auch Objekttypen für usage.cost zu behandeln.

Vorher:

function extractCostBreakdown(cost) {
  const total = toFiniteNumber(cost.total);
  const input = toFiniteNumber(cost.input);
  const output = toFiniteNumber(cost.output);
  
  return { total, input, output };
}

Nachher:

function extractCostBreakdown(cost) {
  // Handle OpenRouter flat number vs OpenAI-compatible object
  const total = toFiniteNumber(typeof cost === 'number' ? cost : cost.total);
  const input = toFiniteNumber(cost.input);
  const output = toFiniteNumber(cost.output);
  
  return { total, input, output };
}

Alternative robuste Implementierung:

function extractCostBreakdown(cost) {
  // Defensive: normalize cost to object shape regardless of input type
  const costObj = typeof cost === 'number' 
    ? { total: cost, input: 0, output: 0 } 
    : cost;
    
  const total = toFiniteNumber(costObj.total);
  const input = toFiniteNumber(costObj.input);
  const output = toFiniteNumber(costObj.output);
  
  return { total, input, output };
}

Quelldatei-Fix (falls verfügbar):

Navigieren Sie zur Quelldatei, die extractCostBreakdown() enthält:

# Assuming standard project structure
find . -name "*.ts" -o -name "*.js" | xargs grep -l "extractCostBreakdown"
# Output: src/providers/openrouter.ts or src/utils/cost.ts

Wenden Sie die Typprüfung direkt in der Quelldatei an, bevor Sie das Verteilungs-Bundle neu erstellen.

🧪 Verifizierung

Testfall 1: OpenRouter flache Zahl Kosten

// Simulate OpenRouter response
const mockOpenRouterCost = 0.0045;
const result = extractCostBreakdown(mockOpenRouterCost);

console.log(result);
// Expected: { total: 0.0045, input: NaN or 0, output: NaN or 0 }

// Verify total is correctly extracted
console.log(Number.isFinite(result.total));
// Expected: true

Testfall 2: OpenAI-kompatible Objektkosten

// Simulate standard provider response
const mockStandardCost = { total: 0.012, input: 0.006, output: 0.006 };
const result = extractCostBreakdown(mockStandardCost);

console.log(result);
// Expected: { total: 0.012, input: 0.006, output: 0.006 }

Integrationsverifizierung:

# Run cost tracking test suite
npm test -- --grep "cost"

# Or specific to OpenRouter
npm test -- --grep "OpenRouter"

# Expected: All cost extraction tests pass

Manuelle End-to-End-Verifizierung:

# 1. Execute a simple OpenRouter request
curl -X POST https://openrouter.ai/api/v1/chat/completions \
  -H "Authorization: Bearer $OPENROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "anthropic/claude-3.5-sonnet",
    "messages": [{"role": "user", "content": "Hello"}]
  }'

# 2. Check extracted cost in OpenClaw logs
# Should show actual cost value, not $0.00

Erwartete Protokollausgabe nach dem Fix:

[OpenClaw] OpenRouter Request Complete
  Model: anthropic/claude-3.5-sonnet
  Cost: $0.0045   ← KORREKT
  Tokens: 2453 input, 892 output

⚠️ Häufige Fehler

  • Stilles Fehlschlag-Muster: Dieser Bug erzeugt keine ausgelösten Fehler. Instrumentieren Sie die Kostenextraktion immer mit Logging, um NaN oder $0 Anomalien zu erkennen.
  • Andere Flachzahl-Provider: Jeder zukünftige OpenAI-kompatible Provider, der einen skalaren usage.cost zurückgibt, löst identischen Fehler aus. Dokumentieren Sie diese Annahme im Code.
  • Build-Artefakt-Mismatch: Das Beheben von Quelldateien erfordert den Neubau von dist/pi-embedded-*.js. Verifizieren Sie, dass das Verteilungs-Bundle die Quelländerungen widerspiegelt.
    # Common mistake: editing dist file without rebuilding
    # Always rebuild after source changes
    npm run build
    npm run dist
    
  • toFiniteNumber Randfälle: Die Hilfsfunktion toFiniteNumber() kann 0 für undefined oder null zurückgeben, was das zugrundeliegende Problem verdeckt.
    // Verify toFiniteNumber behavior
    toFiniteNumber(undefined);  // Returns NaN or 0 depending on implementation
    toFiniteNumber(null);       // Returns 0
    toFiniteNumber(NaN);        // Returns NaN
    
  • Token-Kosten vs. Gesamtkosten: Wenn Kosten eine flache Zahl sind, geht die input- und output-Aufschlüsselung verloren. Überlegen Sie, ob eine granulare Kostenberichterstattung erforderlich ist.
  • OpenRouter /auto-Routing: Bei Verwendung der /auto-Modellauswahl erfolgt die Kostenberechnung nach der Antwort. Stellen Sie sicher, dass der Fix sowohl für explizite als auch für automatisch geroutete Anfragen gilt.

🔗 Zugehörige Fehler

  • TypeError: Cannot read property 'total' of undefined
    Tritt auf, wenn usage.cost selbst undefined ist, anstatt eine Zahl zu sein. Andere Fehlerart, aber gleiche Funktionsposition.
  • NaN erscheint in Kostenaggregationsprotokollen
    Symptom des aktuellen Bugs, wenn das Kostenextraktionsergebnis sich durch arithmetische Operationen ausbreitet.
  • GitHub Issue #142: "Kostenverfolgung defekt für Provider X gibt $0 zurück"
    Historisches Muster, bei dem andere Provider mit nicht-standardmäßigen Kostenformaten identische Symptome verursachten.
  • TypeError: cost.total is not a function (irreführende Meldung)
    Ausgelöst, wenn cost eine Stringdarstellung einer Zahl ist (z.B. "0.0045").
  • Fehlende Kostendaten in Webhook/Nutzlast
    Zugehöriges nachgelagertes Problem, bei dem Kostenverfolgungsfehler leere cost_breakdown-Felder in exportierten Berichten verursachen.
  • OpenRouter API Kostenabweichung
    OpenRouter kann Kosten als null für bestimmte kostenlose oder nicht gecachte Modelle zurückgeben. Stellen Sie sicher, dass auch die Behandlung von Nullwerten berücksichtigt wird.

Belege & Quellen

Diese Troubleshooting-Anleitung wurde automatisch von der FixClaw Intelligence Pipeline aus Community-Diskussionen synthetisiert.