import Foundationimport Testing@testable import CodexBarCore struct CostUsageScannerClaudeFableTests { @Test func `claude fable 5 issue row gets priced`() throws { let env = try CostUsageTestEnvironment() defer { env.cleanup() } let day = try env.makeLocalNoon(year: 2026, month: 6, day: 9) let fileURL = try env.writeClaudeProjectFile( relativePath: "project-a/fable-5.jsonl", contents: env.jsonl([ [ "message": [ "model": "claude-fable-5", "id": "msg_fable_5", "type": "message", "role": "assistant", "usage": [ "input_tokens": 100, "cache_creation_input_tokens": 10, "cache_read_input_tokens": 20, "output_tokens": 5, ], ], "requestId": "req_fable_5", "type": "assistant", "timestamp": "2026-06-09T12:00:00.000Z", "sessionId": "session_fable_5", ], ])) let parsed = CostUsageScanner.parseClaudeFile( fileURL: fileURL, range: CostUsageScanner.CostUsageDayRange(since: day, until: day), providerFilter: .all) #expect(parsed.rows.count == 1) #expect(parsed.rows[0].model == "claude-fable-5") let expected = 0.001395 #expect(abs((Double(parsed.rows[0].costNanos) / 1_000_000_000) - expected) < 0.000000001) } @Test func `claude transcript refusal remains priced without billing provenance`() throws { let env = try CostUsageTestEnvironment() defer { env.cleanup() } let day = try env.makeLocalNoon(year: 2026, month: 6, day: 9) let fileURL = try env.writeClaudeProjectFile( relativePath: "project-a/fable-5-refusal.jsonl", contents: env.jsonl([ [ "message": [ "model": "claude-fable-5", "id": "msg_fable_5_refusal", "type": "message", "role": "assistant", "stop_reason": "refusal", "usage": [ "input_tokens": 100, "cache_creation_input_tokens": 10, "cache_read_input_tokens": 20, "output_tokens": 0, ], ], "requestId": "req_fable_5_refusal", "type": "assistant", "timestamp": "2026-06-09T12:00:00.000Z", "sessionId": "session_fable_5_refusal", ], ])) let parsed = CostUsageScanner.parseClaudeFile( fileURL: fileURL, range: CostUsageScanner.CostUsageDayRange(since: day, until: day), providerFilter: .all) #expect(parsed.rows.count == 1) #expect(parsed.rows[0].input == 100) #expect(parsed.rows[0].cacheCreate == 10) #expect(parsed.rows[0].cacheRead == 20) #expect(parsed.rows[0].output == 0) let expected = 0.001145 #expect(abs((Double(parsed.rows[0].costNanos) / 1_000_000_000) - expected) < 0.000000001) #expect(parsed.rows[0].costPriced == true) } @Test func `claude fable 5 prices one hour cache creation tokens`() throws { let env = try CostUsageTestEnvironment() defer { env.cleanup() } let day = try env.makeLocalNoon(year: 2026, month: 6, day: 9) let fileURL = try env.writeClaudeProjectFile( relativePath: "project-a/fable-5-cache-ttl.jsonl", contents: env.jsonl([ [ "message": [ "model": "claude-fable-5", "id": "msg_fable_5_cache_ttl", "type": "message", "role": "assistant", "usage": [ "input_tokens": 100, "cache_creation_input_tokens": 30, "cache_creation": [ "ephemeral_5m_input_tokens": 10, "ephemeral_1h_input_tokens": 20, ], "cache_read_input_tokens": 20, "output_tokens": 5, ], ], "requestId": "req_fable_5_cache_ttl", "type": "assistant", "timestamp": "2026-06-09T12:00:00.000Z", "sessionId": "session_fable_5_cache_ttl", ], ])) let parsed = CostUsageScanner.parseClaudeFile( fileURL: fileURL, range: CostUsageScanner.CostUsageDayRange(since: day, until: day), providerFilter: .all) #expect(parsed.rows.count == 1) #expect(parsed.rows[0].cacheCreate == 30) #expect(parsed.rows[0].cacheCreate1h == 20) let expected = 0.001795 #expect(abs((Double(parsed.rows[0].costNanos) / 1_000_000_000) - expected) < 0.000000001) } @Test func `claude cached rows preserve one hour writes for deferred pricing`() throws { let env = try CostUsageTestEnvironment() defer { env.cleanup() } let day = try env.makeLocalNoon(year: 2026, month: 6, day: 9) _ = try env.writeClaudeProjectFile( relativePath: "project-a/custom-cache-ttl.jsonl", contents: env.jsonl([ [ "message": [ "model": "claude-custom-cache-model", "id": "msg_custom_cache_ttl", "type": "message", "role": "assistant", "usage": [ "input_tokens": 100, "cache_creation_input_tokens": 30, "cache_creation": [ "ephemeral_5m_input_tokens": 10, "ephemeral_1h_input_tokens": 20, ], "cache_read_input_tokens": 20, "output_tokens": 5, ], ], "requestId": "req_custom_cache_ttl", "type": "assistant", "timestamp": "2026-06-09T12:00:00.000Z", "sessionId": "session_custom_cache_ttl", ], ])) var options = CostUsageScanner.Options( claudeProjectsRoots: [env.claudeProjectsRoot], cacheRoot: env.cacheRoot) options.refreshMinIntervalSeconds = 0 let unpriced = CostUsageScanner.loadDailyReport( provider: .claude, since: day, until: day, now: day, options: options) #expect(unpriced.summary?.totalCostUSD == nil) let cached = CostUsageCacheIO.load(provider: .claude, cacheRoot: env.cacheRoot) #expect(cached.days["2026-06-09"]?["claude-custom-cache-model"]?[safe: 7] == 20) try ModelsDevCache.save( catalog: Self.anthropicModelsDevCatalog(model: "claude-custom-cache-model"), fetchedAt: day, cacheRoot: env.cacheRoot) options.refreshMinIntervalSeconds = 3600 let repriced = CostUsageScanner.loadDailyReport( provider: .claude, since: day, until: day, now: day.addingTimeInterval(1), options: options) let expected = 0.001795 #expect(abs((repriced.summary?.totalCostUSD ?? 0) - expected) < 0.000000001) } @Test func `claude deferred pricing preserves request long context boundaries`() throws { let env = try CostUsageTestEnvironment() defer { env.cleanup() } let day = try env.makeLocalNoon(year: 2026, month: 6, day: 9) _ = try env.writeClaudeProjectFile( relativePath: "project-a/custom-threshold.jsonl", contents: env.jsonl([ Self.claudeUsageEvent( model: "claude-custom-threshold-model", messageID: "msg_custom_threshold_1", requestID: "req_custom_threshold_1", inputTokens: 150_000), Self.claudeUsageEvent( model: "claude-custom-threshold-model", messageID: "msg_custom_threshold_2", requestID: "req_custom_threshold_2", inputTokens: 150_000), ])) var options = CostUsageScanner.Options( claudeProjectsRoots: [env.claudeProjectsRoot], cacheRoot: env.cacheRoot) options.refreshMinIntervalSeconds = 0 let unpriced = CostUsageScanner.loadDailyReport( provider: .claude, since: day, until: day, now: day, options: options) #expect(unpriced.summary?.totalCostUSD == nil) try ModelsDevCache.save( catalog: Self.anthropicThresholdModelsDevCatalog(model: "claude-custom-threshold-model"), fetchedAt: day, cacheRoot: env.cacheRoot) options.refreshMinIntervalSeconds = 3600 let repriced = CostUsageScanner.loadDailyReport( provider: .claude, since: day, until: day, now: day.addingTimeInterval(1), options: options) #expect(abs((repriced.summary?.totalCostUSD ?? 0) - 3) < 0.000000001) } @Test func `claude cached rows reprice after models dev catalog changes`() throws { let env = try CostUsageTestEnvironment() defer { env.cleanup() } let day = try env.makeLocalNoon(year: 2026, month: 6, day: 9) let model = "claude-custom-repricing-model" _ = try env.writeClaudeProjectFile( relativePath: "project-a/custom-repricing.jsonl", contents: env.jsonl([ Self.claudeUsageEvent( model: model, messageID: "msg_custom_repricing", requestID: "req_custom_repricing", inputTokens: 240_000), ])) try ModelsDevCache.save( catalog: Self.anthropicThresholdModelsDevCatalog(model: model), fetchedAt: day, cacheRoot: env.cacheRoot) var options = CostUsageScanner.Options( claudeProjectsRoots: [env.claudeProjectsRoot], cacheRoot: env.cacheRoot) options.refreshMinIntervalSeconds = 0 let premium = CostUsageScanner.loadDailyReport( provider: .claude, since: day, until: day, now: day, options: options) #expect(abs((premium.summary?.totalCostUSD ?? 0) - 4.8) < 0.000000001) try ModelsDevCache.save( catalog: Self.anthropicModelsDevCatalog(model: model), fetchedAt: day.addingTimeInterval(1), cacheRoot: env.cacheRoot) options.refreshMinIntervalSeconds = 3600 let repriced = CostUsageScanner.loadDailyReport( provider: .claude, since: day, until: day, now: day.addingTimeInterval(2), options: options) #expect(abs((repriced.summary?.totalCostUSD ?? 0) - 2.4) < 0.000000001) } @Test func `claude cached historical rows keep original tariff`() throws { let env = try CostUsageTestEnvironment() defer { env.cleanup() } let day = try env.makeLocalNoon(year: 2026, month: 3, day: 12) _ = try env.writeClaudeProjectFile( relativePath: "project-a/sonnet-46-historical.jsonl", contents: env.jsonl([ [ "message": [ "model": "claude-sonnet-4-6", "id": "msg_sonnet_46_historical", "type": "message", "role": "assistant", "usage": [ "input_tokens": 240_000, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 0, ], ], "requestId": "req_sonnet_46_historical", "type": "assistant", "timestamp": "2026-03-12T12:00:00.000Z", "sessionId": "session_sonnet_46_historical", ], ])) var options = CostUsageScanner.Options( claudeProjectsRoots: [env.claudeProjectsRoot], cacheRoot: env.cacheRoot) options.refreshMinIntervalSeconds = 0 let initial = CostUsageScanner.loadDailyReport( provider: .claude, since: day, until: day, now: day, options: options) #expect(abs((initial.summary?.totalCostUSD ?? 0) - 1.44) < 0.000000001) try ModelsDevCache.save( catalog: Self.anthropicSonnet46StandardCatalog(), fetchedAt: day, cacheRoot: env.cacheRoot) options.refreshMinIntervalSeconds = 3600 let cached = CostUsageScanner.loadDailyReport( provider: .claude, since: day, until: day, now: day.addingTimeInterval(1), options: options) #expect(abs((cached.summary?.totalCostUSD ?? 0) - 1.44) < 0.000000001) } private static func anthropicModelsDevCatalog(model: String) throws -> ModelsDevCatalog { let json = """ { "anthropic": { "id": "anthropic", "models": { "\(model)": { "id": "\(model)", "cost": { "input": 10, "output": 50, "cache_read": 1, "cache_write": 12.5 } } } } } """ return try JSONDecoder().decode(ModelsDevCatalog.self, from: Data(json.utf8)) } private static func anthropicThresholdModelsDevCatalog(model: String) throws -> ModelsDevCatalog { let json = """ { "anthropic": { "id": "anthropic", "models": { "\(model)": { "id": "\(model)", "cost": { "input": 10, "output": 50, "cache_read": 1, "cache_write": 12.5, "context_over_200k": { "input": 20, "output": 75, "cache_read": 2, "cache_write": 25 } } } } } } """ return try JSONDecoder().decode(ModelsDevCatalog.self, from: Data(json.utf8)) } private static func anthropicSonnet46StandardCatalog() throws -> ModelsDevCatalog { let json = """ { "anthropic": { "id": "anthropic", "models": { "claude-sonnet-4-6": { "id": "claude-sonnet-4-6", "cost": { "input": 3, "output": 15, "cache_read": 0.3, "cache_write": 3.75 } } } } } """ return try JSONDecoder().decode(ModelsDevCatalog.self, from: Data(json.utf8)) } private static func claudeUsageEvent( model: String, messageID: String, requestID: String, inputTokens: Int) -> [String: Any] { [ "message": [ "model": model, "id": messageID, "type": "message", "role": "assistant", "usage": [ "input_tokens": inputTokens, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens": 0, ], ], "requestId": requestID, "type": "assistant", "timestamp": "2026-06-09T12:00:00.000Z", "sessionId": "session_\(requestID)", ] }}
import Foundation
import Testing
@testable import CodexBarCore
struct CostUsageScannerClaudeFableTests {
@Test
func `claude fable 5 issue row gets priced`() throws {
let env = try CostUsageTestEnvironment()
defer { env.cleanup() }
let day = try env.makeLocalNoon(year: 2026, month: 6, day: 9)
let fileURL = try env.writeClaudeProjectFile(
relativePath: "project-a/fable-5.jsonl",
contents: env.jsonl([
[
"message": [
"model": "claude-fable-5",
"id": "msg_fable_5",
"type": "message",
"role": "assistant",
"usage": [
"input_tokens": 100,
"cache_creation_input_tokens": 10,
"cache_read_input_tokens": 20,
"output_tokens": 5,
],
],
"requestId": "req_fable_5",
"type": "assistant",
"timestamp": "2026-06-09T12:00:00.000Z",
"sessionId": "session_fable_5",
],
]))
let parsed = CostUsageScanner.parseClaudeFile(
fileURL: fileURL,
range: CostUsageScanner.CostUsageDayRange(since: day, until: day),
providerFilter: .all)
#expect(parsed.rows.count == 1)
#expect(parsed.rows[0].model == "claude-fable-5")
let expected = 0.001395
#expect(abs((Double(parsed.rows[0].costNanos) / 1_000_000_000) - expected) < 0.000000001)
}
@Test
func `claude transcript refusal remains priced without billing provenance`() throws {
let env = try CostUsageTestEnvironment()
defer { env.cleanup() }
let day = try env.makeLocalNoon(year: 2026, month: 6, day: 9)
let fileURL = try env.writeClaudeProjectFile(
relativePath: "project-a/fable-5-refusal.jsonl",
contents: env.jsonl([
[
"message": [
"model": "claude-fable-5",
"id": "msg_fable_5_refusal",
"type": "message",
"role": "assistant",
"stop_reason": "refusal",
"usage": [
"input_tokens": 100,
"cache_creation_input_tokens": 10,
"cache_read_input_tokens": 20,
"output_tokens": 0,
],
],
"requestId": "req_fable_5_refusal",
"type": "assistant",
"timestamp": "2026-06-09T12:00:00.000Z",
"sessionId": "session_fable_5_refusal",
],
]))
let parsed = CostUsageScanner.parseClaudeFile(
fileURL: fileURL,
range: CostUsageScanner.CostUsageDayRange(since: day, until: day),
providerFilter: .all)
#expect(parsed.rows.count == 1)
#expect(parsed.rows[0].input == 100)
#expect(parsed.rows[0].cacheCreate == 10)
#expect(parsed.rows[0].cacheRead == 20)
#expect(parsed.rows[0].output == 0)
let expected = 0.001145
#expect(abs((Double(parsed.rows[0].costNanos) / 1_000_000_000) - expected) < 0.000000001)
#expect(parsed.rows[0].costPriced == true)
}
@Test
func `claude fable 5 prices one hour cache creation tokens`() throws {
let env = try CostUsageTestEnvironment()
defer { env.cleanup() }
let day = try env.makeLocalNoon(year: 2026, month: 6, day: 9)
let fileURL = try env.writeClaudeProjectFile(
relativePath: "project-a/fable-5-cache-ttl.jsonl",
contents: env.jsonl([
[
"message": [
"model": "claude-fable-5",
"id": "msg_fable_5_cache_ttl",
"type": "message",
"role": "assistant",
"usage": [
"input_tokens": 100,
"cache_creation_input_tokens": 30,
"cache_creation": [
"ephemeral_5m_input_tokens": 10,
"ephemeral_1h_input_tokens": 20,
],
"cache_read_input_tokens": 20,
"output_tokens": 5,
],
],
"requestId": "req_fable_5_cache_ttl",
"type": "assistant",
"timestamp": "2026-06-09T12:00:00.000Z",
"sessionId": "session_fable_5_cache_ttl",
],
]))
let parsed = CostUsageScanner.parseClaudeFile(
fileURL: fileURL,
range: CostUsageScanner.CostUsageDayRange(since: day, until: day),
providerFilter: .all)
#expect(parsed.rows.count == 1)
#expect(parsed.rows[0].cacheCreate == 30)
#expect(parsed.rows[0].cacheCreate1h == 20)
let expected = 0.001795
#expect(abs((Double(parsed.rows[0].costNanos) / 1_000_000_000) - expected) < 0.000000001)
}
@Test
func `claude cached rows preserve one hour writes for deferred pricing`() throws {
let env = try CostUsageTestEnvironment()
defer { env.cleanup() }
let day = try env.makeLocalNoon(year: 2026, month: 6, day: 9)
_ = try env.writeClaudeProjectFile(
relativePath: "project-a/custom-cache-ttl.jsonl",
contents: env.jsonl([
[
"message": [
"model": "claude-custom-cache-model",
"id": "msg_custom_cache_ttl",
"type": "message",
"role": "assistant",
"usage": [
"input_tokens": 100,
"cache_creation_input_tokens": 30,
"cache_creation": [
"ephemeral_5m_input_tokens": 10,
"ephemeral_1h_input_tokens": 20,
],
"cache_read_input_tokens": 20,
"output_tokens": 5,
],
],
"requestId": "req_custom_cache_ttl",
"type": "assistant",
"timestamp": "2026-06-09T12:00:00.000Z",
"sessionId": "session_custom_cache_ttl",
],
]))
var options = CostUsageScanner.Options(
claudeProjectsRoots: [env.claudeProjectsRoot],
cacheRoot: env.cacheRoot)
options.refreshMinIntervalSeconds = 0
let unpriced = CostUsageScanner.loadDailyReport(
provider: .claude,
since: day,
until: day,
now: day,
options: options)
#expect(unpriced.summary?.totalCostUSD == nil)
let cached = CostUsageCacheIO.load(provider: .claude, cacheRoot: env.cacheRoot)
#expect(cached.days["2026-06-09"]?["claude-custom-cache-model"]?[safe: 7] == 20)
try ModelsDevCache.save(
catalog: Self.anthropicModelsDevCatalog(model: "claude-custom-cache-model"),
fetchedAt: day,
cacheRoot: env.cacheRoot)
options.refreshMinIntervalSeconds = 3600
let repriced = CostUsageScanner.loadDailyReport(
provider: .claude,
since: day,
until: day,
now: day.addingTimeInterval(1),
options: options)
let expected = 0.001795
#expect(abs((repriced.summary?.totalCostUSD ?? 0) - expected) < 0.000000001)
}
@Test
func `claude deferred pricing preserves request long context boundaries`() throws {
let env = try CostUsageTestEnvironment()
defer { env.cleanup() }
let day = try env.makeLocalNoon(year: 2026, month: 6, day: 9)
_ = try env.writeClaudeProjectFile(
relativePath: "project-a/custom-threshold.jsonl",
contents: env.jsonl([
Self.claudeUsageEvent(
model: "claude-custom-threshold-model",
messageID: "msg_custom_threshold_1",
requestID: "req_custom_threshold_1",
inputTokens: 150_000),
Self.claudeUsageEvent(
model: "claude-custom-threshold-model",
messageID: "msg_custom_threshold_2",
requestID: "req_custom_threshold_2",
inputTokens: 150_000),
]))
var options = CostUsageScanner.Options(
claudeProjectsRoots: [env.claudeProjectsRoot],
cacheRoot: env.cacheRoot)
options.refreshMinIntervalSeconds = 0
let unpriced = CostUsageScanner.loadDailyReport(
provider: .claude,
since: day,
until: day,
now: day,
options: options)
#expect(unpriced.summary?.totalCostUSD == nil)
try ModelsDevCache.save(
catalog: Self.anthropicThresholdModelsDevCatalog(model: "claude-custom-threshold-model"),
fetchedAt: day,
cacheRoot: env.cacheRoot)
options.refreshMinIntervalSeconds = 3600
let repriced = CostUsageScanner.loadDailyReport(
provider: .claude,
since: day,
until: day,
now: day.addingTimeInterval(1),
options: options)
#expect(abs((repriced.summary?.totalCostUSD ?? 0) - 3) < 0.000000001)
}
@Test
func `claude cached rows reprice after models dev catalog changes`() throws {
let env = try CostUsageTestEnvironment()
defer { env.cleanup() }
let day = try env.makeLocalNoon(year: 2026, month: 6, day: 9)
let model = "claude-custom-repricing-model"
_ = try env.writeClaudeProjectFile(
relativePath: "project-a/custom-repricing.jsonl",
contents: env.jsonl([
Self.claudeUsageEvent(
model: model,
messageID: "msg_custom_repricing",
requestID: "req_custom_repricing",
inputTokens: 240_000),
]))
try ModelsDevCache.save(
catalog: Self.anthropicThresholdModelsDevCatalog(model: model),
fetchedAt: day,
cacheRoot: env.cacheRoot)
var options = CostUsageScanner.Options(
claudeProjectsRoots: [env.claudeProjectsRoot],
cacheRoot: env.cacheRoot)
options.refreshMinIntervalSeconds = 0
let premium = CostUsageScanner.loadDailyReport(
provider: .claude,
since: day,
until: day,
now: day,
options: options)
#expect(abs((premium.summary?.totalCostUSD ?? 0) - 4.8) < 0.000000001)
try ModelsDevCache.save(
catalog: Self.anthropicModelsDevCatalog(model: model),
fetchedAt: day.addingTimeInterval(1),
cacheRoot: env.cacheRoot)
options.refreshMinIntervalSeconds = 3600
let repriced = CostUsageScanner.loadDailyReport(
provider: .claude,
since: day,
until: day,
now: day.addingTimeInterval(2),
options: options)
#expect(abs((repriced.summary?.totalCostUSD ?? 0) - 2.4) < 0.000000001)
}
@Test
func `claude cached historical rows keep original tariff`() throws {
let env = try CostUsageTestEnvironment()
defer { env.cleanup() }
let day = try env.makeLocalNoon(year: 2026, month: 3, day: 12)
_ = try env.writeClaudeProjectFile(
relativePath: "project-a/sonnet-46-historical.jsonl",
contents: env.jsonl([
[
"message": [
"model": "claude-sonnet-4-6",
"id": "msg_sonnet_46_historical",
"type": "message",
"role": "assistant",
"usage": [
"input_tokens": 240_000,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 0,
"output_tokens": 0,
],
],
"requestId": "req_sonnet_46_historical",
"type": "assistant",
"timestamp": "2026-03-12T12:00:00.000Z",
"sessionId": "session_sonnet_46_historical",
],
]))
var options = CostUsageScanner.Options(
claudeProjectsRoots: [env.claudeProjectsRoot],
cacheRoot: env.cacheRoot)
options.refreshMinIntervalSeconds = 0
let initial = CostUsageScanner.loadDailyReport(
provider: .claude,
since: day,
until: day,
now: day,
options: options)
#expect(abs((initial.summary?.totalCostUSD ?? 0) - 1.44) < 0.000000001)
try ModelsDevCache.save(
catalog: Self.anthropicSonnet46StandardCatalog(),
fetchedAt: day,
cacheRoot: env.cacheRoot)
options.refreshMinIntervalSeconds = 3600
let cached = CostUsageScanner.loadDailyReport(
provider: .claude,
since: day,
until: day,
now: day.addingTimeInterval(1),
options: options)
#expect(abs((cached.summary?.totalCostUSD ?? 0) - 1.44) < 0.000000001)
}
private static func anthropicModelsDevCatalog(model: String) throws -> ModelsDevCatalog {
let json = """
{
"anthropic": {
"id": "anthropic",
"models": {
"\(model)": {
"id": "\(model)",
"cost": {
"input": 10,
"output": 50,
"cache_read": 1,
"cache_write": 12.5
}
}
}
}
}
"""
return try JSONDecoder().decode(ModelsDevCatalog.self, from: Data(json.utf8))
}
private static func anthropicThresholdModelsDevCatalog(model: String) throws -> ModelsDevCatalog {
let json = """
{
"anthropic": {
"id": "anthropic",
"models": {
"\(model)": {
"id": "\(model)",
"cost": {
"input": 10,
"output": 50,
"cache_read": 1,
"cache_write": 12.5,
"context_over_200k": {
"input": 20,
"output": 75,
"cache_read": 2,
"cache_write": 25
}
}
}
}
}
}
"""
return try JSONDecoder().decode(ModelsDevCatalog.self, from: Data(json.utf8))
}
private static func anthropicSonnet46StandardCatalog() throws -> ModelsDevCatalog {
let json = """
{
"anthropic": {
"id": "anthropic",
"models": {
"claude-sonnet-4-6": {
"id": "claude-sonnet-4-6",
"cost": {
"input": 3,
"output": 15,
"cache_read": 0.3,
"cache_write": 3.75
}
}
}
}
}
"""
return try JSONDecoder().decode(ModelsDevCatalog.self, from: Data(json.utf8))
}
private static func claudeUsageEvent(
model: String,
messageID: String,
requestID: String,
inputTokens: Int) -> [String: Any]
{
[
"message": [
"model": model,
"id": messageID,
"type": "message",
"role": "assistant",
"usage": [
"input_tokens": inputTokens,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 0,
"output_tokens": 0,
],
],
"requestId": requestID,
"type": "assistant",
"timestamp": "2026-06-09T12:00:00.000Z",
"sessionId": "session_\(requestID)",
]
}
}