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", () => {