diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js index 40d937c6..36649d85 100644 --- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js +++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js @@ -666,6 +666,48 @@ export function parseQuotaData(provider, data) { } break; + case "xai": + // xAI mixes: + // - weekly: percentage window (used/total 0-100) from GetGrokCreditsConfig + // - api_usage: absolute monthly credits from /v1/billing + // For absolute rows, do not forward remainingCredits as `remaining` + // (QuotaTable treats remaining as a 0-100 percentage; same pitfall as Qoder). + if (data.quotas) { + Object.entries(data.quotas).forEach(([quotaType, quota]) => { + const name = + quotaType === "weekly" + ? "Weekly limit" + : quotaType === "api_usage" + ? "Api usage" + : quotaType === "monthly" + ? "Api usage" + : quotaType === "on_demand" + ? "On-demand" + : quotaType; + + if (quotaType === "weekly") { + normalizedQuotas.push({ + name, + used: quota.used || 0, + total: quota.total || 100, + remaining: quota.remaining, + remainingPercentage: quota.remainingPercentage ?? quota.remaining, + resetAt: quota.resetAt || null, + }); + return; + } + + normalizedQuotas.push({ + name, + used: quota.used || 0, + total: quota.total || 0, + unit: quota.unit, + resetAt: quota.resetAt || null, + }); + }); + } + break; + default: // Generic fallback for unknown providers if (data.quotas) { diff --git a/tests/unit/openai-to-kiro.test.js b/tests/unit/openai-to-kiro.test.js index 17773bdb..5a86b276 100644 --- a/tests/unit/openai-to-kiro.test.js +++ b/tests/unit/openai-to-kiro.test.js @@ -11,7 +11,16 @@ import { openaiToKiroRequest } from "../../open-sse/translator/request/openai-to const contentOf = (result) => result.conversationState.currentMessage.userInputMessage.content; -const systemPromptOf = (result) => result.systemPrompt || ""; +// 1fc2a81d dropped the redundant top-level systemPrompt field (Kiro rejects +// it): thinking/agentic directives ride into the frozen session-start message +// (msg0) via contentPrefix, or into the current message when there is no +// session to replay. Read whichever carries them. +const systemPromptOf = (result) => { + const cs = result.conversationState || {}; + const msg0 = cs.history?.[0]?.userInputMessage?.content; + if (typeof msg0 === "string" && msg0) return msg0; + return cs.currentMessage?.userInputMessage?.content || ""; +}; describe("openaiToKiroRequest", () => { describe("basic message conversion", () => { @@ -568,23 +577,36 @@ describe("openaiToKiroRequest", () => { expect(systemPromptOf(result)).toContain("16000"); }); - it("keeps top-level systemPrompt stable across turns", () => { + it("keeps the frozen session-start directive stable across turns", () => { + // Upstream dropped the redundant top-level systemPrompt; cacheability + // now lives in the replayed msg0. Assert the directive rides msg0 and + // the volatile clock only enters the current turn. + const credentials = { + connectionId: "kiro-account-openai-stable", + rawHeaders: { "x-session-id": "hermes-session-openai-stable" }, + }; const first = openaiToKiroRequest( "claude-sonnet-4.6-thinking", { messages: [{ role: "user", content: "first" }] }, true, - {} + credentials ); const second = openaiToKiroRequest( "claude-sonnet-4.6-thinking", { messages: [{ role: "user", content: "second" }] }, true, - {} + credentials ); - expect(first.systemPrompt).toBe(second.systemPrompt); - expect(first.systemPrompt).not.toContain("Current time"); - expect(first.conversationState.currentMessage.userInputMessage.content).toContain("Current time"); + const msg0 = second.conversationState.history[0].userInputMessage.content; + // msg0 carries the thinking directive and is replayed byte-identical on + // later turns (upstream prefix includes the session-start timestamp; + // the cacheability contract is that it does NOT change per turn). + expect(msg0).toBe(first.conversationState.currentMessage.userInputMessage.content); + expect(msg0).toContain(""); + const cur = second.conversationState.currentMessage.userInputMessage.content; + expect(cur).toContain("second"); + expect(cur).toContain("Current time"); }); it("replays frozen msg0 for explicit Kiro sessions while keeping current time fresh", () => {