diff --git a/.changeset/eleven-bulldogs-live.md b/.changeset/eleven-bulldogs-live.md new file mode 100644 index 00000000000..0b02ae8d369 --- /dev/null +++ b/.changeset/eleven-bulldogs-live.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +OpenAI Codex: Add ChatGPT subscription usage limits dashboard diff --git a/packages/types/src/providers/index.ts b/packages/types/src/providers/index.ts index 2b9722a49dd..39064b6eb12 100644 --- a/packages/types/src/providers/index.ts +++ b/packages/types/src/providers/index.ts @@ -27,6 +27,7 @@ export * from "./nano-gpt.js" // kilocode_change export * from "./ollama.js" export * from "./openai.js" export * from "./openai-codex.js" +export * from "./openai-codex-rate-limits.js" export * from "./openrouter.js" export * from "./qwen-code.js" export * from "./requesty.js" diff --git a/packages/types/src/providers/openai-codex-rate-limits.ts b/packages/types/src/providers/openai-codex-rate-limits.ts new file mode 100644 index 00000000000..98ddae2a17d --- /dev/null +++ b/packages/types/src/providers/openai-codex-rate-limits.ts @@ -0,0 +1,29 @@ +/** + * OpenAI Codex usage/rate limit information (ChatGPT subscription) + */ +export interface OpenAiCodexRateLimitInfo { + primary?: { + /** Used percent in 0–100 */ + usedPercent: number + /** Window length in minutes, when provided */ + windowMinutes?: number + /** Reset time (unix ms since epoch), when provided */ + resetsAt?: number + } + secondary?: { + /** Used percent in 0–100 */ + usedPercent: number + /** Window length in minutes, when provided */ + windowMinutes?: number + /** Reset time (unix ms since epoch), when provided */ + resetsAt?: number + } + credits?: { + hasCredits: boolean + unlimited: boolean + balance?: string + } + planType?: string + /** Timestamp when this was fetched (unix ms since epoch) */ + fetchedAt: number +} diff --git a/packages/types/src/vscode-extension-host.ts b/packages/types/src/vscode-extension-host.ts index 136434f98ad..bc118a4ed1d 100644 --- a/packages/types/src/vscode-extension-host.ts +++ b/packages/types/src/vscode-extension-host.ts @@ -21,6 +21,7 @@ import type { GitCommit } from "./git.js" import type { McpServer } from "./mcp.js" import type { ModelRecord, RouterModels, ModelInfo } from "./model.js" import type { CommitRange } from "./kilocode/kilocode.js" +import type { OpenAiCodexRateLimitInfo } from "./providers/openai-codex-rate-limits.js" // kilocode_change start: Type definitions for Kilo Code-specific features // SAP AI Core deployment types @@ -258,6 +259,7 @@ export interface ExtensionMessage { | "taskWithAggregatedCosts" | "skillsData" | "askReviewScope" // kilocode_change: Review mode scope selection + | "openAiCodexRateLimits" text?: string // kilocode_change start completionRequestId?: string // Correlation ID from request @@ -338,7 +340,9 @@ export interface ExtensionMessage { customMode?: ModeConfig slug?: string success?: boolean - values?: Record // eslint-disable-line @typescript-eslint/no-explicit-any + /** Generic payload for extension messages that use `values` */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + values?: Record requestId?: string promptText?: string results?: @@ -462,6 +466,12 @@ export interface ExtensionMessage { // kilocode_change end: Review mode } +export interface OpenAiCodexRateLimitsMessage { + type: "openAiCodexRateLimits" + values?: OpenAiCodexRateLimitInfo + error?: string +} + export type ExtensionState = Pick< GlobalSettings, | "currentApiConfigName" @@ -943,6 +953,7 @@ export interface WebviewMessage { | "chatCompletionAccepted" // kilocode_change: User accepted a chat completion suggestion | "downloadErrorDiagnostics" | "requestClaudeCodeRateLimits" + | "requestOpenAiCodexRateLimits" | "refreshCustomTools" | "requestModes" | "switchMode" @@ -995,6 +1006,7 @@ export interface WebviewMessage { promptMode?: string | "enhance" customPrompt?: PromptComponent dataUrls?: string[] + /** Generic payload for webview messages that use `values` */ // eslint-disable-next-line @typescript-eslint/no-explicit-any values?: Record query?: string @@ -1142,6 +1154,10 @@ export interface TaskHistoryResponsePayload { } // kilocode_change end +export interface RequestOpenAiCodexRateLimitsMessage { + type: "requestOpenAiCodexRateLimits" +} + export const checkoutDiffPayloadSchema = z.object({ ts: z.number().optional(), previousCommitHash: z.string().optional(), diff --git a/src/core/webview/__tests__/webviewMessageHandler.spec.ts b/src/core/webview/__tests__/webviewMessageHandler.spec.ts index b6fbaff1686..89a779ac4b3 100644 --- a/src/core/webview/__tests__/webviewMessageHandler.spec.ts +++ b/src/core/webview/__tests__/webviewMessageHandler.spec.ts @@ -5,6 +5,17 @@ import type { Mock } from "vitest" // Mock dependencies - must come before imports vi.mock("../../../api/providers/fetchers/modelCache") +vi.mock("../../../integrations/openai-codex/oauth", () => ({ + openAiCodexOAuthManager: { + getAccessToken: vi.fn(), + getAccountId: vi.fn(), + }, +})) + +vi.mock("../../../integrations/openai-codex/rate-limits", () => ({ + fetchOpenAiCodexRateLimitInfo: vi.fn(), +})) + // Mock the diagnosticsHandler module vi.mock("../diagnosticsHandler", () => ({ generateErrorDiagnostics: vi.fn().mockResolvedValue({ success: true, filePath: "/tmp/diagnostics.json" }), @@ -15,8 +26,13 @@ import type { ModelRecord } from "@roo-code/types" import { webviewMessageHandler } from "../webviewMessageHandler" import type { ClineProvider } from "../ClineProvider" import { getModels } from "../../../api/providers/fetchers/modelCache" +const { openAiCodexOAuthManager } = await import("../../../integrations/openai-codex/oauth") +const { fetchOpenAiCodexRateLimitInfo } = await import("../../../integrations/openai-codex/rate-limits") const mockGetModels = getModels as Mock +const mockGetAccessToken = vi.mocked(openAiCodexOAuthManager.getAccessToken) +const mockGetAccountId = vi.mocked(openAiCodexOAuthManager.getAccountId) +const mockFetchOpenAiCodexRateLimitInfo = vi.mocked(fetchOpenAiCodexRateLimitInfo) // Mock ClineProvider const mockClineProvider = { @@ -740,6 +756,43 @@ describe("webviewMessageHandler - requestRouterModels", () => { }) }) +describe("webviewMessageHandler - requestOpenAiCodexRateLimits", () => { + beforeEach(() => { + vi.clearAllMocks() + mockGetAccessToken.mockResolvedValue(null) + mockGetAccountId.mockResolvedValue(null) + }) + + it("posts error when not authenticated", async () => { + await webviewMessageHandler(mockClineProvider, { type: "requestOpenAiCodexRateLimits" } as any) + + expect(mockClineProvider.postMessageToWebview).toHaveBeenCalledWith({ + type: "openAiCodexRateLimits", + error: "Not authenticated with OpenAI Codex", + }) + }) + + it("posts values when authenticated", async () => { + mockGetAccessToken.mockResolvedValue("token") + mockGetAccountId.mockResolvedValue("acct_123") + mockFetchOpenAiCodexRateLimitInfo.mockResolvedValue({ + primary: { usedPercent: 10, resetsAt: 1700000000000 }, + fetchedAt: 1700000000000, + }) + + await webviewMessageHandler(mockClineProvider, { type: "requestOpenAiCodexRateLimits" } as any) + + expect(mockFetchOpenAiCodexRateLimitInfo).toHaveBeenCalledWith("token", { accountId: "acct_123" }) + expect(mockClineProvider.postMessageToWebview).toHaveBeenCalledWith({ + type: "openAiCodexRateLimits", + values: { + primary: { usedPercent: 10, resetsAt: 1700000000000 }, + fetchedAt: 1700000000000, + }, + }) + }) +}) + describe("webviewMessageHandler - deleteCustomMode", () => { beforeEach(() => { vi.clearAllMocks() diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index db9d8247a28..9aa7dfc64ba 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -4514,6 +4514,38 @@ export const webviewMessageHandler = async ( break } + case "requestOpenAiCodexRateLimits": { + try { + const { openAiCodexOAuthManager } = await import("../../integrations/openai-codex/oauth") + const accessToken = await openAiCodexOAuthManager.getAccessToken() + + if (!accessToken) { + provider.postMessageToWebview({ + type: "openAiCodexRateLimits", + error: "Not authenticated with OpenAI Codex", + }) + break + } + + const accountId = await openAiCodexOAuthManager.getAccountId() + const { fetchOpenAiCodexRateLimitInfo } = await import("../../integrations/openai-codex/rate-limits") + const rateLimits = await fetchOpenAiCodexRateLimitInfo(accessToken, { accountId }) + + provider.postMessageToWebview({ + type: "openAiCodexRateLimits", + values: rateLimits, + }) + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error) + provider.log(`Error fetching OpenAI Codex rate limits: ${errorMessage}`) + provider.postMessageToWebview({ + type: "openAiCodexRateLimits", + error: errorMessage, + }) + } + break + } + case "openDebugApiHistory": case "openDebugUiHistory": { const currentTask = provider.getCurrentTask() diff --git a/src/integrations/openai-codex/__tests__/rate-limits.spec.ts b/src/integrations/openai-codex/__tests__/rate-limits.spec.ts new file mode 100644 index 00000000000..2f0363ba688 --- /dev/null +++ b/src/integrations/openai-codex/__tests__/rate-limits.spec.ts @@ -0,0 +1,47 @@ +import { describe, it, expect } from "vitest" + +import { parseOpenAiCodexUsagePayload } from "../rate-limits" + +describe("parseOpenAiCodexUsagePayload()", () => { + it("maps primary/secondary windows", () => { + const fetchedAt = 1234567890000 + const payload = { + rate_limit: { + primary_window: { used_percent: 12.34, limit_window_seconds: 300 * 60, reset_at: 1700000000 }, + secondary_window: { used_percent: 99.9, limit_window_seconds: 10080 * 60, reset_at: 1700000000 }, + }, + plan_type: "plus", + } + + const out = parseOpenAiCodexUsagePayload(payload, fetchedAt) + + expect(out).toEqual({ + primary: { + usedPercent: 12.34, + windowMinutes: 300, + resetsAt: 1700000000 * 1000, + }, + secondary: { + usedPercent: 99.9, + windowMinutes: 10080, + resetsAt: 1700000000 * 1000, + }, + planType: "plus", + fetchedAt, + }) + }) + + it("clamps used_percent to 0–100 and tolerates missing fields", () => { + const fetchedAt = 1 + const payload = { + rate_limit: { + primary_window: { used_percent: 1000 }, + secondary_window: { used_percent: -5 }, + }, + } + const out = parseOpenAiCodexUsagePayload(payload, fetchedAt) + expect(out.primary?.usedPercent).toBe(100) + expect(out.secondary?.usedPercent).toBe(0) + expect(out.fetchedAt).toBe(fetchedAt) + }) +}) diff --git a/src/integrations/openai-codex/rate-limits.ts b/src/integrations/openai-codex/rate-limits.ts new file mode 100644 index 00000000000..f6c2af8781a --- /dev/null +++ b/src/integrations/openai-codex/rate-limits.ts @@ -0,0 +1,96 @@ +import type { OpenAiCodexRateLimitInfo } from "@roo-code/types" + +const WHAM_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage" + +type WhamUsageResponse = { + rate_limit?: { + primary_window?: { + limit_window_seconds?: number + used_percent?: number + reset_at?: number + } + secondary_window?: { + limit_window_seconds?: number + used_percent?: number + reset_at?: number + } + } + plan_type?: string +} + +function clampPercent(value: number): number { + if (!Number.isFinite(value)) return 0 + return Math.max(0, Math.min(100, value)) +} + +function secondsToMs(value: number | undefined): number | undefined { + return typeof value === "number" && Number.isFinite(value) ? Math.round(value * 1000) : undefined +} + +export function parseOpenAiCodexUsagePayload(payload: unknown, fetchedAt: number): OpenAiCodexRateLimitInfo { + const data = (payload && typeof payload === "object" ? payload : {}) as WhamUsageResponse + const primaryRaw = data.rate_limit?.primary_window + const secondaryRaw = data.rate_limit?.secondary_window + + const primary: OpenAiCodexRateLimitInfo["primary"] | undefined = + primaryRaw && typeof primaryRaw.used_percent === "number" + ? { + usedPercent: clampPercent(primaryRaw.used_percent), + ...(typeof primaryRaw.limit_window_seconds === "number" + ? { windowMinutes: Math.round(primaryRaw.limit_window_seconds / 60) } + : {}), + ...(secondsToMs(primaryRaw.reset_at) !== undefined + ? { resetsAt: secondsToMs(primaryRaw.reset_at) } + : {}), + } + : undefined + + const secondary: OpenAiCodexRateLimitInfo["secondary"] | undefined = + secondaryRaw && typeof secondaryRaw.used_percent === "number" + ? { + usedPercent: clampPercent(secondaryRaw.used_percent), + ...(typeof secondaryRaw.limit_window_seconds === "number" + ? { windowMinutes: Math.round(secondaryRaw.limit_window_seconds / 60) } + : {}), + ...(secondsToMs(secondaryRaw.reset_at) !== undefined + ? { resetsAt: secondsToMs(secondaryRaw.reset_at) } + : {}), + } + : undefined + + return { + ...(primary ? { primary } : {}), + ...(secondary ? { secondary } : {}), + ...(typeof data.plan_type === "string" ? { planType: data.plan_type } : {}), + fetchedAt, + } +} + +export async function fetchOpenAiCodexRateLimitInfo( + accessToken: string, + options?: { accountId?: string | null }, +): Promise { + const fetchedAt = Date.now() + const headers: Record = { + Authorization: `Bearer ${accessToken}`, + Accept: "application/json", + } + if (options?.accountId) { + headers["ChatGPT-Account-Id"] = options.accountId + } + + const response = await fetch(WHAM_USAGE_URL, { method: "GET", headers }) + if (!response.ok) { + const text = await response.text().catch(() => "") + throw new Error( + `OpenAI Codex WHAM usage request failed: ${response.status} ${response.statusText}${text ? ` - ${text}` : ""}`, + ) + } + + const json = (await response.json()) as unknown + const parsed = parseOpenAiCodexUsagePayload(json, fetchedAt) + if (!parsed.primary && !parsed.secondary) { + throw new Error("OpenAI Codex WHAM usage response did not include rate_limit windows") + } + return parsed +} diff --git a/webview-ui/src/components/settings/providers/OpenAICodex.tsx b/webview-ui/src/components/settings/providers/OpenAICodex.tsx index adb8c6a25c2..755b272702a 100644 --- a/webview-ui/src/components/settings/providers/OpenAICodex.tsx +++ b/webview-ui/src/components/settings/providers/OpenAICodex.tsx @@ -7,6 +7,7 @@ import { Button } from "@src/components/ui" import { vscode } from "@src/utils/vscode" import { ModelPicker } from "../ModelPicker" +import { OpenAICodexRateLimitDashboard } from "./OpenAICodexRateLimitDashboard" interface OpenAICodexProps { apiConfiguration: ProviderSettings @@ -50,6 +51,9 @@ export const OpenAICodex: React.FC = ({ )} + {/* Rate Limit Dashboard - only shown when authenticated */} + + {/* Model Picker */} ) => string + +function formatDurationSeconds(totalSeconds: number, t: Translate): string { + const days = Math.floor(totalSeconds / 86400) + const hours = Math.floor((totalSeconds % 86400) / 3600) + const minutes = Math.floor((totalSeconds % 3600) / 60) + + if (days > 0) { + return t("settings:providers.openAiCodexRateLimits.duration.daysHours", { days, hours }) + } + if (hours > 0) { + return t("settings:providers.openAiCodexRateLimits.duration.hoursMinutes", { hours, minutes }) + } + return t("settings:providers.openAiCodexRateLimits.duration.minutes", { minutes }) +} + +function formatTimeRemainingMs(ms: number | undefined, t: Translate): string { + if (ms === undefined) return "" + if (ms <= 0) return t("settings:providers.openAiCodexRateLimits.time.now") + const totalSeconds = Math.max(0, Math.floor(ms / 1000)) + return formatDurationSeconds(totalSeconds, t) +} + +function formatResetTimeMs(resetMs: number | undefined, t: Translate): string { + if (!resetMs) return t("settings:providers.openAiCodexRateLimits.time.notAvailable") + const diffMs = resetMs - Date.now() + if (diffMs <= 0) return t("settings:providers.openAiCodexRateLimits.time.now") + + const diffSec = Math.floor(diffMs / 1000) + return formatDurationSeconds(diffSec, t) +} + +function formatWindowLabel(windowMinutes: number | undefined, t: Translate): string | undefined { + if (!windowMinutes) return undefined + if (windowMinutes === 60) return t("settings:providers.openAiCodexRateLimits.window.oneHour") + if (windowMinutes === 24 * 60) return t("settings:providers.openAiCodexRateLimits.window.daily") + if (windowMinutes === 7 * 24 * 60) return t("settings:providers.openAiCodexRateLimits.window.weekly") + if (windowMinutes === 5 * 60) return t("settings:providers.openAiCodexRateLimits.window.fiveHour") + if (windowMinutes % (24 * 60) === 0) { + return t("settings:providers.openAiCodexRateLimits.window.days", { days: windowMinutes / (24 * 60) }) + } + if (windowMinutes % 60 === 0) { + return t("settings:providers.openAiCodexRateLimits.window.hours", { hours: windowMinutes / 60 }) + } + return t("settings:providers.openAiCodexRateLimits.window.minutes", { minutes: windowMinutes }) +} + +function formatPlanLabel(planType: string | undefined, t: Translate): string { + if (!planType) return t("settings:providers.openAiCodexRateLimits.plan.default") + return t("settings:providers.openAiCodexRateLimits.plan.withType", { planType }) +} + +const UsageProgressBar: React.FC<{ usedPercent: number; label?: string }> = ({ usedPercent, label }) => { + const percentage = Math.max(0, Math.min(100, usedPercent)) + const isWarning = percentage >= 70 + const isCritical = percentage >= 90 + + return ( +
+ {label ?
{label}
: null} +
+
+
+
+ ) +} + +export const OpenAICodexRateLimitDashboard: React.FC = ({ isAuthenticated }) => { + const { t } = useAppTranslation() + const [rateLimits, setRateLimits] = useState(null) + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(null) + + const fetchRateLimits = useCallback(() => { + if (!isAuthenticated) { + setRateLimits(null) + setError(null) + return + } + setIsLoading(true) + setError(null) + vscode.postMessage({ type: "requestOpenAiCodexRateLimits" }) + }, [isAuthenticated]) + + useEffect(() => { + const handleMessage = (event: MessageEvent) => { + const message = event.data + if (message.type === "openAiCodexRateLimits") { + setIsLoading(false) + if (message.error) { + setError(message.error) + setRateLimits(null) + } else if (message.values) { + setRateLimits(message.values) + setError(null) + } + } + } + window.addEventListener("message", handleMessage) + return () => window.removeEventListener("message", handleMessage) + }, []) + + useEffect(() => { + if (isAuthenticated) { + fetchRateLimits() + } + }, [isAuthenticated, fetchRateLimits]) + + if (!isAuthenticated) return null + + if (isLoading && !rateLimits) { + return ( +
+
+ {t("settings:providers.openAiCodexRateLimits.loading")} +
+
+ ) + } + + if (error) { + return ( +
+
+
+ {t("settings:providers.openAiCodexRateLimits.loadError")} +
+ +
+
{error}
+
+ ) + } + + if (!rateLimits) return null + + const primary = rateLimits.primary + const secondary = rateLimits.secondary + const planType = rateLimits.planType + + const planLabel = formatPlanLabel(planType, t) + + const primaryWindowLabel = primary ? formatWindowLabel(primary.windowMinutes, t) : undefined + const primaryTimeRemaining = primary?.resetsAt ? formatTimeRemainingMs(primary.resetsAt - Date.now(), t) : "" + const primaryUsed = primary ? Math.round(primary.usedPercent) : undefined + + const secondaryWindowLabel = secondary ? formatWindowLabel(secondary.windowMinutes, t) : undefined + const secondaryTimeRemaining = secondary?.resetsAt ? formatTimeRemainingMs(secondary.resetsAt - Date.now(), t) : "" + const secondaryUsed = secondary ? Math.round(secondary.usedPercent) : undefined + + const getUsageStatusLabel = (used: number | undefined, timeRemaining: string, resetAt?: number) => { + const usedLabel = + used !== undefined ? t("settings:providers.openAiCodexRateLimits.usedPercent", { percent: used }) : "" + const resetLabel = timeRemaining + ? t("settings:providers.openAiCodexRateLimits.resetsIn", { time: timeRemaining }) + : resetAt + ? t("settings:providers.openAiCodexRateLimits.resetsIn", { + time: formatResetTimeMs(resetAt, t), + }) + : "" + + if (usedLabel && resetLabel) return `${usedLabel} • ${resetLabel}` + return usedLabel || resetLabel + } + + return ( +
+
+
+ {t("settings:providers.openAiCodexRateLimits.title", { planLabel })} +
+
+ +
+ {primary ? ( +
+
+ + {primaryWindowLabel ?? t("settings:providers.openAiCodexRateLimits.window.usage")} + + + {getUsageStatusLabel(primaryUsed, primaryTimeRemaining, primary.resetsAt)} + +
+ +
+ ) : null} + + {secondary ? ( +
+
+ + {secondaryWindowLabel ?? t("settings:providers.openAiCodexRateLimits.window.usage")} + + + {getUsageStatusLabel(secondaryUsed, secondaryTimeRemaining, secondary.resetsAt)} + +
+ +
+ ) : null} +
+
+ ) +} diff --git a/webview-ui/src/components/settings/providers/__tests__/OpenAICodexRateLimitDashboard.spec.tsx b/webview-ui/src/components/settings/providers/__tests__/OpenAICodexRateLimitDashboard.spec.tsx new file mode 100644 index 00000000000..68145afb84c --- /dev/null +++ b/webview-ui/src/components/settings/providers/__tests__/OpenAICodexRateLimitDashboard.spec.tsx @@ -0,0 +1,79 @@ +import { render, screen, waitFor } from "@/utils/test-utils" + +import { OpenAICodexRateLimitDashboard } from "../OpenAICodexRateLimitDashboard" + +vi.mock("@src/i18n/TranslationContext", () => ({ + useAppTranslation: () => ({ + t: (key: string, options?: Record) => { + switch (key) { + case "settings:providers.openAiCodexRateLimits.title": + return `Usage Limits for Codex${options?.planLabel ?? ""}` + case "settings:providers.openAiCodexRateLimits.plan.withType": + return ` (${options?.planType ?? ""})` + case "settings:providers.openAiCodexRateLimits.plan.default": + return "" + case "settings:providers.openAiCodexRateLimits.window.fiveHour": + return "5h limit" + case "settings:providers.openAiCodexRateLimits.window.weekly": + return "Weekly limit" + case "settings:providers.openAiCodexRateLimits.usedPercent": + return `${options?.percent ?? ""}% used` + default: + return key + } + }, + }), +})) + +const { postMessageMock } = vi.hoisted(() => ({ + postMessageMock: vi.fn(), +})) + +vi.mock("@src/utils/vscode", () => ({ + vscode: { + postMessage: postMessageMock, + }, +})) + +describe("OpenAICodexRateLimitDashboard", () => { + beforeEach(() => { + postMessageMock.mockClear() + }) + + it("hides when not authenticated", () => { + const { container } = render() + expect(container.firstChild).toBeNull() + }) + + it("sends request message when authenticated", () => { + render() + expect(postMessageMock).toHaveBeenCalledWith({ type: "requestOpenAiCodexRateLimits" }) + }) + + it("renders usage values from payload", () => { + render() + + window.dispatchEvent( + new MessageEvent("message", { + data: { + type: "openAiCodexRateLimits", + values: { + primary: { usedPercent: 12.3, windowMinutes: 300, resetsAt: Date.now() + 60_000 }, + secondary: { usedPercent: 45.6, windowMinutes: 10080, resetsAt: Date.now() + 120_000 }, + credits: { hasCredits: true, unlimited: true }, + fetchedAt: Date.now(), + planType: "pro", + }, + }, + }), + ) + + return waitFor(() => { + expect(screen.getByText(/Usage Limits for Codex \(pro\)/)).toBeInTheDocument() + expect(screen.getByText(/5h limit/)).toBeInTheDocument() + expect(screen.getByText(/Weekly limit/)).toBeInTheDocument() + expect(screen.getByText(/12% used/)).toBeInTheDocument() + expect(screen.getByText(/46% used/)).toBeInTheDocument() + }) + }) +}) diff --git a/webview-ui/src/i18n/locales/ar/settings.json b/webview-ui/src/i18n/locales/ar/settings.json index c55de6aa01b..dbdb66ef28f 100644 --- a/webview-ui/src/i18n/locales/ar/settings.json +++ b/webview-ui/src/i18n/locales/ar/settings.json @@ -349,6 +349,37 @@ "apiKeyStorageNotice": "المفاتيح تُحفظ بأمان في مخزن أسرار VSCode", "glamaApiKey": "مفتاح Glama", "getGlamaApiKey": "احصل على مفتاح Glama", + "openAiCodexRateLimits": { + "title": "حدود الاستخدام لـ Codex{{planLabel}}", + "loading": "جاري تحميل حدود الاستخدام...", + "loadError": "فشل تحميل حدود الاستخدام", + "retry": "إعادة المحاولة", + "usedPercent": "{{percent}}% مستخدم", + "resetsIn": "يُعاد التعيين خلال {{time}}", + "plan": { + "default": "", + "withType": " ({{planType}})" + }, + "time": { + "now": "الآن", + "notAvailable": "غير متوفر" + }, + "duration": { + "daysHours": "{{days}} يوم {{hours}} ساعة", + "hoursMinutes": "{{hours}} ساعة {{minutes}} دقيقة", + "minutes": "{{minutes}} دقيقة" + }, + "window": { + "usage": "الاستخدام", + "fiveHour": "حد 5 ساعات", + "oneHour": "حد ساعة واحدة", + "daily": "الحد اليومي", + "weekly": "الحد الأسبوعي", + "days": "حد {{days}} يوم", + "hours": "حد {{hours}} ساعة", + "minutes": "حد {{minutes}} دقيقة" + } + }, "useCustomBaseUrl": "استخدم رابط أساسي مخصص", "useReasoning": "تفعيل الـ Reasoning", "useHostHeader": "استخدم ترويسة Host مخصصة", diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 89553408c32..176883bc97e 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -313,6 +313,37 @@ "glamaApiKey": "Clau API de Glama", "getGlamaApiKey": "Obtenir clau API de Glama", "apiKeyStorageNotice": "Les claus API s'emmagatzemen de forma segura a l'Emmagatzematge Secret de VSCode", + "openAiCodexRateLimits": { + "title": "Usage Limits for Codex{{planLabel}}", + "loading": "Loading usage limits...", + "loadError": "Failed to load usage limits", + "retry": "Retry", + "usedPercent": "{{percent}}% used", + "resetsIn": "Resets in {{time}}", + "plan": { + "default": "", + "withType": " ({{planType}})" + }, + "time": { + "now": "Now", + "notAvailable": "N/A" + }, + "duration": { + "daysHours": "{{days}}d {{hours}}h", + "hoursMinutes": "{{hours}}h {{minutes}}m", + "minutes": "{{minutes}}m" + }, + "window": { + "usage": "Usage", + "fiveHour": "5h limit", + "oneHour": "1h limit", + "daily": "Daily limit", + "weekly": "Weekly limit", + "days": "{{days}}d limit", + "hours": "{{hours}}h limit", + "minutes": "{{minutes}}m limit" + } + }, "useCustomBaseUrl": "Utilitzar URL base personalitzada", "useReasoning": "Activar raonament", "useHostHeader": "Utilitzar capçalera Host personalitzada", diff --git a/webview-ui/src/i18n/locales/cs/settings.json b/webview-ui/src/i18n/locales/cs/settings.json index e54f2c9c105..1f688738ef4 100644 --- a/webview-ui/src/i18n/locales/cs/settings.json +++ b/webview-ui/src/i18n/locales/cs/settings.json @@ -335,6 +335,37 @@ "apiKeyStorageNotice": "Klíče API jsou bezpečně uloženy v tajném úložišti VSCode", "glamaApiKey": "Klíč API Glama", "getGlamaApiKey": "Získat klíč API Glama", + "openAiCodexRateLimits": { + "title": "Limity využití pro Codex{{planLabel}}", + "loading": "Načítání limitů využití...", + "loadError": "Nepodařilo se načíst limity využití", + "retry": "Zkusit znovu", + "usedPercent": "{{percent}}% použito", + "resetsIn": "Obnoví se za {{time}}", + "plan": { + "default": "", + "withType": " ({{planType}})" + }, + "time": { + "now": "Nyní", + "notAvailable": "Nedostupné" + }, + "duration": { + "daysHours": "{{days}}d {{hours}}h", + "hoursMinutes": "{{hours}}h {{minutes}}m", + "minutes": "{{minutes}}m" + }, + "window": { + "usage": "Využití", + "fiveHour": "5h limit", + "oneHour": "1h limit", + "daily": "Denní limit", + "weekly": "Týdenní limit", + "days": "{{days}}d limit", + "hours": "{{hours}}h limit", + "minutes": "{{minutes}}m limit" + } + }, "useCustomBaseUrl": "Použít vlastní základní URL", "useReasoning": "Povolit uvažování", "useHostHeader": "Použít vlastní hlavičku Host", diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 125d7eff3cf..1307aaacde5 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -324,6 +324,37 @@ "apiKeyStorageNotice": "API-Schlüssel werden sicher im VSCode Secret Storage gespeichert", "glamaApiKey": "Glama API-Schlüssel", "getGlamaApiKey": "Glama API-Schlüssel erhalten", + "openAiCodexRateLimits": { + "title": "Usage Limits for Codex{{planLabel}}", + "loading": "Loading usage limits...", + "loadError": "Failed to load usage limits", + "retry": "Retry", + "usedPercent": "{{percent}}% used", + "resetsIn": "Resets in {{time}}", + "plan": { + "default": "", + "withType": " ({{planType}})" + }, + "time": { + "now": "Now", + "notAvailable": "N/A" + }, + "duration": { + "daysHours": "{{days}}d {{hours}}h", + "hoursMinutes": "{{hours}}h {{minutes}}m", + "minutes": "{{minutes}}m" + }, + "window": { + "usage": "Usage", + "fiveHour": "5h limit", + "oneHour": "1h limit", + "daily": "Daily limit", + "weekly": "Weekly limit", + "days": "{{days}}d limit", + "hours": "{{hours}}h limit", + "minutes": "{{minutes}}m limit" + } + }, "useCustomBaseUrl": "Benutzerdefinierte Basis-URL verwenden", "useReasoning": "Reasoning aktivieren", "useHostHeader": "Benutzerdefinierten Host-Header verwenden", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 12a006d70aa..d8c3816f327 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -331,6 +331,37 @@ "apiKeyStorageNotice": "API keys are stored securely in VSCode's Secret Storage", "glamaApiKey": "Glama API Key", "getGlamaApiKey": "Get Glama API Key", + "openAiCodexRateLimits": { + "title": "Usage Limits for Codex{{planLabel}}", + "loading": "Loading usage limits...", + "loadError": "Failed to load usage limits", + "retry": "Retry", + "usedPercent": "{{percent}}% used", + "resetsIn": "Resets in {{time}}", + "plan": { + "default": "", + "withType": " ({{planType}})" + }, + "time": { + "now": "Now", + "notAvailable": "N/A" + }, + "duration": { + "daysHours": "{{days}}d {{hours}}h", + "hoursMinutes": "{{hours}}h {{minutes}}m", + "minutes": "{{minutes}}m" + }, + "window": { + "usage": "Usage", + "fiveHour": "5h limit", + "oneHour": "1h limit", + "daily": "Daily limit", + "weekly": "Weekly limit", + "days": "{{days}}d limit", + "hours": "{{hours}}h limit", + "minutes": "{{minutes}}m limit" + } + }, "useCustomBaseUrl": "Use custom base URL", "useReasoning": "Enable reasoning", "useHostHeader": "Use custom Host header", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index e1572ba5995..a557e27bd02 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -322,6 +322,37 @@ "apiKeyStorageNotice": "Las claves API se almacenan de forma segura en el Almacenamiento Secreto de VSCode", "glamaApiKey": "Clave API de Glama", "getGlamaApiKey": "Obtener clave API de Glama", + "openAiCodexRateLimits": { + "title": "Usage Limits for Codex{{planLabel}}", + "loading": "Loading usage limits...", + "loadError": "Failed to load usage limits", + "retry": "Retry", + "usedPercent": "{{percent}}% used", + "resetsIn": "Resets in {{time}}", + "plan": { + "default": "", + "withType": " ({{planType}})" + }, + "time": { + "now": "Now", + "notAvailable": "N/A" + }, + "duration": { + "daysHours": "{{days}}d {{hours}}h", + "hoursMinutes": "{{hours}}h {{minutes}}m", + "minutes": "{{minutes}}m" + }, + "window": { + "usage": "Usage", + "fiveHour": "5h limit", + "oneHour": "1h limit", + "daily": "Daily limit", + "weekly": "Weekly limit", + "days": "{{days}}d limit", + "hours": "{{hours}}h limit", + "minutes": "{{minutes}}m limit" + } + }, "useCustomBaseUrl": "Usar URL base personalizada", "useReasoning": "Habilitar razonamiento", "useHostHeader": "Usar encabezado Host personalizado", diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index cc2c021c5db..c881ddc2c6d 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -322,6 +322,37 @@ "apiKeyStorageNotice": "Les clés API sont stockées en toute sécurité dans le stockage sécurisé de VSCode", "glamaApiKey": "Clé API Glama", "getGlamaApiKey": "Obtenir la clé API Glama", + "openAiCodexRateLimits": { + "title": "Usage Limits for Codex{{planLabel}}", + "loading": "Loading usage limits...", + "loadError": "Failed to load usage limits", + "retry": "Retry", + "usedPercent": "{{percent}}% used", + "resetsIn": "Resets in {{time}}", + "plan": { + "default": "", + "withType": " ({{planType}})" + }, + "time": { + "now": "Now", + "notAvailable": "N/A" + }, + "duration": { + "daysHours": "{{days}}d {{hours}}h", + "hoursMinutes": "{{hours}}h {{minutes}}m", + "minutes": "{{minutes}}m" + }, + "window": { + "usage": "Usage", + "fiveHour": "5h limit", + "oneHour": "1h limit", + "daily": "Daily limit", + "weekly": "Weekly limit", + "days": "{{days}}d limit", + "hours": "{{hours}}h limit", + "minutes": "{{minutes}}m limit" + } + }, "useCustomBaseUrl": "Utiliser une URL de base personnalisée", "useReasoning": "Activer le raisonnement", "useHostHeader": "Utiliser un en-tête Host personnalisé", diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 1ca1acf4555..97b09d7135b 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -321,6 +321,37 @@ "apiKeyStorageNotice": "API कुंजियाँ VSCode के सुरक्षित स्टोरेज में सुरक्षित रूप से संग्रहीत हैं", "glamaApiKey": "Glama API कुंजी", "getGlamaApiKey": "Glama API कुंजी प्राप्त करें", + "openAiCodexRateLimits": { + "title": "Usage Limits for Codex{{planLabel}}", + "loading": "Loading usage limits...", + "loadError": "Failed to load usage limits", + "retry": "Retry", + "usedPercent": "{{percent}}% used", + "resetsIn": "Resets in {{time}}", + "plan": { + "default": "", + "withType": " ({{planType}})" + }, + "time": { + "now": "Now", + "notAvailable": "N/A" + }, + "duration": { + "daysHours": "{{days}}d {{hours}}h", + "hoursMinutes": "{{hours}}h {{minutes}}m", + "minutes": "{{minutes}}m" + }, + "window": { + "usage": "Usage", + "fiveHour": "5h limit", + "oneHour": "1h limit", + "daily": "Daily limit", + "weekly": "Weekly limit", + "days": "{{days}}d limit", + "hours": "{{hours}}h limit", + "minutes": "{{minutes}}m limit" + } + }, "useCustomBaseUrl": "कस्टम बेस URL का उपयोग करें", "useReasoning": "तर्क सक्षम करें", "useHostHeader": "कस्टम होस्ट हेडर का उपयोग करें", diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index d3b7b17fd1c..e5508ba101b 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -313,6 +313,37 @@ "glamaApiKey": "Glama API Key", "getGlamaApiKey": "Dapatkan Glama API Key", "apiKeyStorageNotice": "API key disimpan dengan aman di Secret Storage VSCode", + "openAiCodexRateLimits": { + "title": "Usage Limits for Codex{{planLabel}}", + "loading": "Loading usage limits...", + "loadError": "Failed to load usage limits", + "retry": "Retry", + "usedPercent": "{{percent}}% used", + "resetsIn": "Resets in {{time}}", + "plan": { + "default": "", + "withType": " ({{planType}})" + }, + "time": { + "now": "Now", + "notAvailable": "N/A" + }, + "duration": { + "daysHours": "{{days}}d {{hours}}h", + "hoursMinutes": "{{hours}}h {{minutes}}m", + "minutes": "{{minutes}}m" + }, + "window": { + "usage": "Usage", + "fiveHour": "5h limit", + "oneHour": "1h limit", + "daily": "Daily limit", + "weekly": "Weekly limit", + "days": "{{days}}d limit", + "hours": "{{hours}}h limit", + "minutes": "{{minutes}}m limit" + } + }, "useCustomBaseUrl": "Gunakan base URL kustom", "useReasoning": "Aktifkan reasoning", "useHostHeader": "Gunakan Host header kustom", diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 22c32082891..545ed9051fb 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -327,6 +327,37 @@ "glamaApiKey": "Chiave API Glama", "getGlamaApiKey": "Ottieni chiave API Glama", "apiKeyStorageNotice": "Le chiavi API sono memorizzate in modo sicuro nell'Archivio Segreto di VSCode", + "openAiCodexRateLimits": { + "title": "Usage Limits for Codex{{planLabel}}", + "loading": "Loading usage limits...", + "loadError": "Failed to load usage limits", + "retry": "Retry", + "usedPercent": "{{percent}}% used", + "resetsIn": "Resets in {{time}}", + "plan": { + "default": "", + "withType": " ({{planType}})" + }, + "time": { + "now": "Now", + "notAvailable": "N/A" + }, + "duration": { + "daysHours": "{{days}}d {{hours}}h", + "hoursMinutes": "{{hours}}h {{minutes}}m", + "minutes": "{{minutes}}m" + }, + "window": { + "usage": "Usage", + "fiveHour": "5h limit", + "oneHour": "1h limit", + "daily": "Daily limit", + "weekly": "Weekly limit", + "days": "{{days}}d limit", + "hours": "{{hours}}h limit", + "minutes": "{{minutes}}m limit" + } + }, "useCustomBaseUrl": "Usa URL base personalizzato", "useReasoning": "Abilita ragionamento", "useHostHeader": "Usa intestazione Host personalizzata", diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index 6b99045f535..a47630f7572 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -318,6 +318,37 @@ "glamaApiKey": "Glama APIキー", "getGlamaApiKey": "Glama APIキーを取得", "apiKeyStorageNotice": "APIキーはVSCodeのシークレットストレージに安全に保存されます", + "openAiCodexRateLimits": { + "title": "Usage Limits for Codex{{planLabel}}", + "loading": "Loading usage limits...", + "loadError": "Failed to load usage limits", + "retry": "Retry", + "usedPercent": "{{percent}}% used", + "resetsIn": "Resets in {{time}}", + "plan": { + "default": "", + "withType": " ({{planType}})" + }, + "time": { + "now": "Now", + "notAvailable": "N/A" + }, + "duration": { + "daysHours": "{{days}}d {{hours}}h", + "hoursMinutes": "{{hours}}h {{minutes}}m", + "minutes": "{{minutes}}m" + }, + "window": { + "usage": "Usage", + "fiveHour": "5h limit", + "oneHour": "1h limit", + "daily": "Daily limit", + "weekly": "Weekly limit", + "days": "{{days}}d limit", + "hours": "{{hours}}h limit", + "minutes": "{{minutes}}m limit" + } + }, "useCustomBaseUrl": "カスタムベースURLを使用", "useReasoning": "推論を有効化", "useHostHeader": "カスタムHostヘッダーを使用", diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 28ff540964e..d6ce23fb383 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -313,6 +313,37 @@ "glamaApiKey": "Glama API 키", "getGlamaApiKey": "Glama API 키 받기", "apiKeyStorageNotice": "API 키는 VSCode의 보안 저장소에 안전하게 저장됩니다", + "openAiCodexRateLimits": { + "title": "Usage Limits for Codex{{planLabel}}", + "loading": "Loading usage limits...", + "loadError": "Failed to load usage limits", + "retry": "Retry", + "usedPercent": "{{percent}}% used", + "resetsIn": "Resets in {{time}}", + "plan": { + "default": "", + "withType": " ({{planType}})" + }, + "time": { + "now": "Now", + "notAvailable": "N/A" + }, + "duration": { + "daysHours": "{{days}}d {{hours}}h", + "hoursMinutes": "{{hours}}h {{minutes}}m", + "minutes": "{{minutes}}m" + }, + "window": { + "usage": "Usage", + "fiveHour": "5h limit", + "oneHour": "1h limit", + "daily": "Daily limit", + "weekly": "Weekly limit", + "days": "{{days}}d limit", + "hours": "{{hours}}h limit", + "minutes": "{{minutes}}m limit" + } + }, "useCustomBaseUrl": "사용자 정의 기본 URL 사용", "useReasoning": "추론 활성화", "useHostHeader": "사용자 정의 Host 헤더 사용", diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index ed4546484be..949791a6b53 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -313,6 +313,37 @@ "glamaApiKey": "Glama API-sleutel", "getGlamaApiKey": "Glama API-sleutel ophalen", "apiKeyStorageNotice": "API-sleutels worden veilig opgeslagen in de geheime opslag van VSCode", + "openAiCodexRateLimits": { + "title": "Usage Limits for Codex{{planLabel}}", + "loading": "Loading usage limits...", + "loadError": "Failed to load usage limits", + "retry": "Retry", + "usedPercent": "{{percent}}% used", + "resetsIn": "Resets in {{time}}", + "plan": { + "default": "", + "withType": " ({{planType}})" + }, + "time": { + "now": "Now", + "notAvailable": "N/A" + }, + "duration": { + "daysHours": "{{days}}d {{hours}}h", + "hoursMinutes": "{{hours}}h {{minutes}}m", + "minutes": "{{minutes}}m" + }, + "window": { + "usage": "Usage", + "fiveHour": "5h limit", + "oneHour": "1h limit", + "daily": "Daily limit", + "weekly": "Weekly limit", + "days": "{{days}}d limit", + "hours": "{{hours}}h limit", + "minutes": "{{minutes}}m limit" + } + }, "useCustomBaseUrl": "Aangepaste basis-URL gebruiken", "useReasoning": "Redenering inschakelen", "useHostHeader": "Aangepaste Host-header gebruiken", diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index 26a513d1c0b..b7631c2ef21 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -313,6 +313,37 @@ "glamaApiKey": "Klucz API Glama", "getGlamaApiKey": "Uzyskaj klucz API Glama", "apiKeyStorageNotice": "Klucze API są bezpiecznie przechowywane w Tajnym Magazynie VSCode", + "openAiCodexRateLimits": { + "title": "Usage Limits for Codex{{planLabel}}", + "loading": "Loading usage limits...", + "loadError": "Failed to load usage limits", + "retry": "Retry", + "usedPercent": "{{percent}}% used", + "resetsIn": "Resets in {{time}}", + "plan": { + "default": "", + "withType": " ({{planType}})" + }, + "time": { + "now": "Now", + "notAvailable": "N/A" + }, + "duration": { + "daysHours": "{{days}}d {{hours}}h", + "hoursMinutes": "{{hours}}h {{minutes}}m", + "minutes": "{{minutes}}m" + }, + "window": { + "usage": "Usage", + "fiveHour": "5h limit", + "oneHour": "1h limit", + "daily": "Daily limit", + "weekly": "Weekly limit", + "days": "{{days}}d limit", + "hours": "{{hours}}h limit", + "minutes": "{{minutes}}m limit" + } + }, "useCustomBaseUrl": "Użyj niestandardowego URL bazowego", "useReasoning": "Włącz rozumowanie", "useHostHeader": "Użyj niestandardowego nagłówka Host", diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index 0fab2256c2b..10a6e876d9b 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -313,6 +313,37 @@ "glamaApiKey": "Chave API do Glama", "getGlamaApiKey": "Obter chave API do Glama", "apiKeyStorageNotice": "As chaves de API são armazenadas com segurança no Armazenamento Secreto do VSCode", + "openAiCodexRateLimits": { + "title": "Usage Limits for Codex{{planLabel}}", + "loading": "Loading usage limits...", + "loadError": "Failed to load usage limits", + "retry": "Retry", + "usedPercent": "{{percent}}% used", + "resetsIn": "Resets in {{time}}", + "plan": { + "default": "", + "withType": " ({{planType}})" + }, + "time": { + "now": "Now", + "notAvailable": "N/A" + }, + "duration": { + "daysHours": "{{days}}d {{hours}}h", + "hoursMinutes": "{{hours}}h {{minutes}}m", + "minutes": "{{minutes}}m" + }, + "window": { + "usage": "Usage", + "fiveHour": "5h limit", + "oneHour": "1h limit", + "daily": "Daily limit", + "weekly": "Weekly limit", + "days": "{{days}}d limit", + "hours": "{{hours}}h limit", + "minutes": "{{minutes}}m limit" + } + }, "useCustomBaseUrl": "Usar URL base personalizado", "useReasoning": "Habilitar raciocínio", "useHostHeader": "Usar cabeçalho Host personalizado", diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index 54836cc6d05..fdcc49b8a9b 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -313,6 +313,37 @@ "glamaApiKey": "API-ключ Glama", "getGlamaApiKey": "Получить API-ключ Glama", "apiKeyStorageNotice": "API-ключи хранятся безопасно в Secret Storage VSCode", + "openAiCodexRateLimits": { + "title": "Usage Limits for Codex{{planLabel}}", + "loading": "Loading usage limits...", + "loadError": "Failed to load usage limits", + "retry": "Retry", + "usedPercent": "{{percent}}% used", + "resetsIn": "Resets in {{time}}", + "plan": { + "default": "", + "withType": " ({{planType}})" + }, + "time": { + "now": "Now", + "notAvailable": "N/A" + }, + "duration": { + "daysHours": "{{days}}d {{hours}}h", + "hoursMinutes": "{{hours}}h {{minutes}}m", + "minutes": "{{minutes}}m" + }, + "window": { + "usage": "Usage", + "fiveHour": "5h limit", + "oneHour": "1h limit", + "daily": "Daily limit", + "weekly": "Weekly limit", + "days": "{{days}}d limit", + "hours": "{{hours}}h limit", + "minutes": "{{minutes}}m limit" + } + }, "useCustomBaseUrl": "Использовать пользовательский базовый URL", "useReasoning": "Включить рассуждения", "useHostHeader": "Использовать пользовательский Host-заголовок", diff --git a/webview-ui/src/i18n/locales/th/settings.json b/webview-ui/src/i18n/locales/th/settings.json index 29887584496..fd4141e6b41 100644 --- a/webview-ui/src/i18n/locales/th/settings.json +++ b/webview-ui/src/i18n/locales/th/settings.json @@ -345,6 +345,37 @@ "apiKeyStorageNotice": "คีย์ API จะถูกเก็บไว้อย่างปลอดภัยในที่เก็บข้อมูลลับของ VSCode", "glamaApiKey": "คีย์ API ของ Glama", "getGlamaApiKey": "รับคีย์ API ของ Glama", + "openAiCodexRateLimits": { + "title": "ขีดจำกัดการใช้งานสำหรับ Codex{{planLabel}}", + "loading": "กำลังโหลดขีดจำกัดการใช้งาน...", + "loadError": "ไม่สามารถโหลดขีดจำกัดการใช้งาน", + "retry": "ลองอีกครั้ง", + "usedPercent": "ใช้ไป {{percent}}%", + "resetsIn": "รีเซ็ตใน {{time}}", + "plan": { + "default": "", + "withType": " ({{planType}})" + }, + "time": { + "now": "ตอนนี้", + "notAvailable": "ไม่พร้อมใช้งาน" + }, + "duration": { + "daysHours": "{{days}} วัน {{hours}} ชม.", + "hoursMinutes": "{{hours}} ชม. {{minutes}} นาที", + "minutes": "{{minutes}} นาที" + }, + "window": { + "usage": "การใช้งาน", + "fiveHour": "ขีดจำกัด 5 ชม.", + "oneHour": "ขีดจำกัด 1 ชม.", + "daily": "ขีดจำกัดรายวัน", + "weekly": "ขีดจำกัดรายสัปดาห์", + "days": "ขีดจำกัด {{days}} วัน", + "hours": "ขีดจำกัด {{hours}} ชม.", + "minutes": "ขีดจำกัด {{minutes}} นาที" + } + }, "useCustomBaseUrl": "ใช้ URL พื้นฐานที่กำหนดเอง", "useReasoning": "เปิดใช้งานการให้เหตุผล", "useHostHeader": "ใช้ส่วนหัวโฮสต์ที่กำหนดเอง", diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index 19a553eee9f..33849cbb267 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -313,6 +313,37 @@ "glamaApiKey": "Glama API Anahtarı", "getGlamaApiKey": "Glama API Anahtarı Al", "apiKeyStorageNotice": "API anahtarları VSCode'un Gizli Depolamasında güvenli bir şekilde saklanır", + "openAiCodexRateLimits": { + "title": "Usage Limits for Codex{{planLabel}}", + "loading": "Loading usage limits...", + "loadError": "Failed to load usage limits", + "retry": "Retry", + "usedPercent": "{{percent}}% used", + "resetsIn": "Resets in {{time}}", + "plan": { + "default": "", + "withType": " ({{planType}})" + }, + "time": { + "now": "Now", + "notAvailable": "N/A" + }, + "duration": { + "daysHours": "{{days}}d {{hours}}h", + "hoursMinutes": "{{hours}}h {{minutes}}m", + "minutes": "{{minutes}}m" + }, + "window": { + "usage": "Usage", + "fiveHour": "5h limit", + "oneHour": "1h limit", + "daily": "Daily limit", + "weekly": "Weekly limit", + "days": "{{days}}d limit", + "hours": "{{hours}}h limit", + "minutes": "{{minutes}}m limit" + } + }, "useCustomBaseUrl": "Özel temel URL kullan", "useReasoning": "Akıl yürütmeyi etkinleştir", "useHostHeader": "Özel Host başlığı kullan", diff --git a/webview-ui/src/i18n/locales/uk/settings.json b/webview-ui/src/i18n/locales/uk/settings.json index fa64c02518c..37c9af8bf7e 100644 --- a/webview-ui/src/i18n/locales/uk/settings.json +++ b/webview-ui/src/i18n/locales/uk/settings.json @@ -350,6 +350,37 @@ "apiKeyStorageNotice": "Ключі API надійно зберігаються в Secret Storage VSCode", "glamaApiKey": "Ключ API Glama", "getGlamaApiKey": "Отримати ключ API Glama", + "openAiCodexRateLimits": { + "title": "Ліміти використання для Codex{{planLabel}}", + "loading": "Завантаження лімітів використання...", + "loadError": "Не вдалося завантажити ліміти використання", + "retry": "Спробувати знову", + "usedPercent": "{{percent}}% використано", + "resetsIn": "Скинеться через {{time}}", + "plan": { + "default": "", + "withType": " ({{planType}})" + }, + "time": { + "now": "Зараз", + "notAvailable": "Недоступно" + }, + "duration": { + "daysHours": "{{days}}д {{hours}}г", + "hoursMinutes": "{{hours}}г {{minutes}}хв", + "minutes": "{{minutes}}хв" + }, + "window": { + "usage": "Використання", + "fiveHour": "5-годинний ліміт", + "oneHour": "1-годинний ліміт", + "daily": "Денний ліміт", + "weekly": "Тижневий ліміт", + "days": "{{days}}д ліміт", + "hours": "{{hours}}г ліміт", + "minutes": "{{minutes}}хв ліміт" + } + }, "useCustomBaseUrl": "Використовувати власний базовий URL", "useReasoning": "Увімкнути міркування", "useHostHeader": "Використовувати власний заголовок Host", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index f92f9f42d36..edd46c07b31 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -321,6 +321,37 @@ "glamaApiKey": "Khóa API Glama", "getGlamaApiKey": "Lấy khóa API Glama", "apiKeyStorageNotice": "Khóa API được lưu trữ an toàn trong Bộ lưu trữ bí mật của VSCode", + "openAiCodexRateLimits": { + "title": "Usage Limits for Codex{{planLabel}}", + "loading": "Loading usage limits...", + "loadError": "Failed to load usage limits", + "retry": "Retry", + "usedPercent": "{{percent}}% used", + "resetsIn": "Resets in {{time}}", + "plan": { + "default": "", + "withType": " ({{planType}})" + }, + "time": { + "now": "Now", + "notAvailable": "N/A" + }, + "duration": { + "daysHours": "{{days}}d {{hours}}h", + "hoursMinutes": "{{hours}}h {{minutes}}m", + "minutes": "{{minutes}}m" + }, + "window": { + "usage": "Usage", + "fiveHour": "5h limit", + "oneHour": "1h limit", + "daily": "Daily limit", + "weekly": "Weekly limit", + "days": "{{days}}d limit", + "hours": "{{hours}}h limit", + "minutes": "{{minutes}}m limit" + } + }, "useCustomBaseUrl": "Sử dụng URL cơ sở tùy chỉnh", "useReasoning": "Bật lý luận", "useHostHeader": "Sử dụng tiêu đề Host tùy chỉnh", diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index 49e0973f618..269bef289d5 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -313,6 +313,37 @@ "glamaApiKey": "Glama API 密钥", "getGlamaApiKey": "获取 Glama API 密钥", "apiKeyStorageNotice": "API 密钥安全存储在 VSCode 的密钥存储中", + "openAiCodexRateLimits": { + "title": "Usage Limits for Codex{{planLabel}}", + "loading": "Loading usage limits...", + "loadError": "Failed to load usage limits", + "retry": "Retry", + "usedPercent": "{{percent}}% used", + "resetsIn": "Resets in {{time}}", + "plan": { + "default": "", + "withType": " ({{planType}})" + }, + "time": { + "now": "Now", + "notAvailable": "N/A" + }, + "duration": { + "daysHours": "{{days}}d {{hours}}h", + "hoursMinutes": "{{hours}}h {{minutes}}m", + "minutes": "{{minutes}}m" + }, + "window": { + "usage": "Usage", + "fiveHour": "5h limit", + "oneHour": "1h limit", + "daily": "Daily limit", + "weekly": "Weekly limit", + "days": "{{days}}d limit", + "hours": "{{hours}}h limit", + "minutes": "{{minutes}}m limit" + } + }, "useCustomBaseUrl": "使用自定义基础 URL", "useReasoning": "启用推理", "useHostHeader": "使用自定义 Host 标头", diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index ceaedca24ab..8f31646ad25 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -323,6 +323,37 @@ "glamaApiKey": "Glama API 金鑰", "getGlamaApiKey": "取得 Glama API 金鑰", "apiKeyStorageNotice": "API 金鑰會安全地儲存在 VS Code 的 Secret Storage 中", + "openAiCodexRateLimits": { + "title": "Usage Limits for Codex{{planLabel}}", + "loading": "Loading usage limits...", + "loadError": "Failed to load usage limits", + "retry": "Retry", + "usedPercent": "{{percent}}% used", + "resetsIn": "Resets in {{time}}", + "plan": { + "default": "", + "withType": " ({{planType}})" + }, + "time": { + "now": "Now", + "notAvailable": "N/A" + }, + "duration": { + "daysHours": "{{days}}d {{hours}}h", + "hoursMinutes": "{{hours}}h {{minutes}}m", + "minutes": "{{minutes}}m" + }, + "window": { + "usage": "Usage", + "fiveHour": "5h limit", + "oneHour": "1h limit", + "daily": "Daily limit", + "weekly": "Weekly limit", + "days": "{{days}}d limit", + "hours": "{{hours}}h limit", + "minutes": "{{minutes}}m limit" + } + }, "useCustomBaseUrl": "使用自訂基礎 URL", "useReasoning": "啟用推理", "useHostHeader": "使用自訂 Host 標頭",