diff --git a/Sources/CodexBarCore/Resources/Plugins/openrouter.js b/Sources/CodexBarCore/Resources/Plugins/openrouter.js index 5e2a0f83f4..e28b05eed9 100644 --- a/Sources/CodexBarCore/Resources/Plugins/openrouter.js +++ b/Sources/CodexBarCore/Resources/Plugins/openrouter.js @@ -112,26 +112,27 @@ defineProvider({ } else try { const now = ctx.date.now(); - const today = now.toISOString().slice(0, 10); - const cutoffDate = new Date(now.getTime() - 29 * 24 * 60 * 60 * 1000); + const latestCompletedDate = new Date(now.getTime() - 24 * 60 * 60 * 1000); + const latestCompleted = latestCompletedDate.toISOString().slice(0, 10); + const cutoffDate = new Date(latestCompletedDate.getTime() - 29 * 24 * 60 * 60 * 1000); const cutoff = cutoffDate.toISOString().slice(0, 10); // A management credential must never follow the user-configurable API base to a proxy. const activityURL = "https://openrouter.ai/api/v1/activity"; - const [historyResponse, todayResponse] = await Promise.all([ + const [historyResponse, latestCompletedResponse] = await Promise.all([ ctx.http.get(activityURL, { timeoutSeconds: optionalRequestTimeoutSeconds, openRouterManagementAuth: true, }), - ctx.http.get(`${activityURL}?date=${encodeURIComponent(today)}`, { + ctx.http.get(`${activityURL}?date=${encodeURIComponent(latestCompleted)}`, { timeoutSeconds: optionalRequestTimeoutSeconds, openRouterManagementAuth: true, }), ]); - if (historyResponse.status !== 200 || todayResponse.status !== 200) { - const failed = historyResponse.status !== 200 ? historyResponse : todayResponse; + if (historyResponse.status !== 200 || latestCompletedResponse.status !== 200) { + const failed = historyResponse.status !== 200 ? historyResponse : latestCompletedResponse; activityDegradation = activityDegradationReason(failed.status); } else { - const payloads = [historyResponse, todayResponse].map((response) => JSON.parse(response.bodyText)); + const payloads = [historyResponse, latestCompletedResponse].map((response) => JSON.parse(response.bodyText)); const rows = payloads.flatMap((payload) => { if (!payload || !Array.isArray(payload.data)) throw new TypeError("activity.data must be an array"); return payload.data; @@ -157,7 +158,9 @@ defineProvider({ if (!Number.isFinite(parsedDate.getTime()) || parsedDate.toISOString().slice(0, 10) !== date) { throw new TypeError(`activity.data[${index}].date must be a real calendar date`); } - if (date > today) throw new TypeError(`activity.data[${index}].date must not be in the future`); + if (date > latestCompleted) { + throw new TypeError(`activity.data[${index}].date must be a completed UTC day`); + } if (date < cutoff) continue; const rawModel = row.model_permaslug ?? row.model; const model = typeof rawModel === "string" && rawModel.trim() ? rawModel.trim() : null; @@ -246,7 +249,7 @@ defineProvider({ currency: "USD", historyDays: 30, historyLabel: "Last 30 days (UTC)", - windowEnd: today, + windowEnd: latestCompleted, entries, }; } diff --git a/Tests/CodexBarTests/OpenRouterUsageStatsTests.swift b/Tests/CodexBarTests/OpenRouterUsageStatsTests.swift index b3270bc418..0b27166ac8 100644 --- a/Tests/CodexBarTests/OpenRouterUsageStatsTests.swift +++ b/Tests/CodexBarTests/OpenRouterUsageStatsTests.swift @@ -149,6 +149,75 @@ struct OpenRouterPluginGoldenTests { #expect(ordinary.allSatisfy { $0.value(forHTTPHeaderField: "Authorization") == "Bearer standard-key" }) } + @Test + func `activity requests the latest completed UTC day`() async throws { + let requests = OpenRouterRequestRecorder() + let activityBody = #""" + {"data":[ + { + "date":"2026-08-17", + "model":"openai/gpt-5.6", + "prompt_tokens":10, + "completion_tokens":5, + "reasoning_tokens":2, + "requests":1, + "usage":1 + }, + { + "date":"2026-07-19", + "model":"x-ai/grok-4", + "prompt_tokens":4, + "completion_tokens":1, + "reasoning_tokens":0, + "requests":1, + "usage":1 + } + ]} + """# + let runtime = try ProviderPluginRuntime( + bundledPlugin: "openrouter", + transport: ProviderHTTPTransportHandler { request in + await requests.append(request) + let path = request.url?.path ?? "" + if path.hasSuffix("/activity") { + let date = request.url + .flatMap { URLComponents(url: $0, resolvingAgainstBaseURL: false) }? + .queryItems? + .first { $0.name == "date" }? + .value + if date == "2026-08-18" { + return try Self.response( + request, + body: #"{"error":{"message":"Date must be within the last 30 (completed) UTC days"}}"#, + statusCode: 400) + } + return try Self.response(request, body: activityBody) + } + if path.hasSuffix("/key") { + return try Self.response(request, body: #"{"data":{"limit":20,"usage":5}}"#) + } + return try Self.response(request, body: Self.defaultCreditsBody) + }) + let now = Date(timeIntervalSince1970: 1_787_079_600) // 2026-08-18T12:00:00Z; stable injected clock. + + let usage = try await runtime.fetchUsage( + secrets: [ + OpenRouterSettingsReader.envKey: "fixture-key", + OpenRouterSettingsReader.managementAPIKeyEnvironmentKey: "fixture-management-key", + ], + now: now) + let recorded = await requests.requests + let datedRequest = try #require(recorded.first { $0.url?.query != nil }) + let date = datedRequest.url + .flatMap { URLComponents(url: $0, resolvingAgainstBaseURL: false) }? + .queryItems? + .first { $0.name == "date" }? + .value + + #expect(date == "2026-08-17") + #expect(usage.costUsage?.last30DaysCostUSD == 2) + } + @Test func `server remaining drives monthly quota golden`() async throws { let usage = try await Self.fetch(keyBody: #"""