[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:
- OpenRouter API gibt
usage.cost = 0.0045zurück (skalare Zahl) extractCostBreakdown()führt aus:const total = toFiniteNumber(cost.total)- Wenn
costeine Zahl ist, ergibtcost.totalundefined toFiniteNumber(undefined)gibtNaNzurück- Die Kostenaggregationslogik behandelt
NaNfür Anzeigezwecke als$0 - 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:
| Provider | usage.cost Typ | Struktur |
|---|---|---|
| OpenAI | Object | { total, input, output } |
| Azure OpenAI | Object | { total, input, output } |
| Anthropic | Object | { total, input, output } |
| OpenRouter | Number | 0.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
NaNoder$0Anomalien zu erkennen. - Andere Flachzahl-Provider: Jeder zukünftige OpenAI-kompatible Provider, der einen skalaren
usage.costzurü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()kann0fürundefinedodernullzurü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- undoutput-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, wennusage.costselbstundefinedist, anstatt eine Zahl zu sein. Andere Fehlerart, aber gleiche Funktionsposition.NaNerscheint 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, wenncosteine Stringdarstellung einer Zahl ist (z.B."0.0045").- Fehlende Kostendaten in Webhook/Nutzlast
Zugehöriges nachgelagertes Problem, bei dem Kostenverfolgungsfehler leerecost_breakdown-Felder in exportierten Berichten verursachen. - OpenRouter API Kostenabweichung
OpenRouter kann Kosten alsnullfür bestimmte kostenlose oder nicht gecachte Modelle zurückgeben. Stellen Sie sicher, dass auch die Behandlung von Nullwerten berücksichtigt wird.