diff --git a/packages/types/src/message.ts b/packages/types/src/message.ts index 77c055c6e152..89996c9fce92 100644 --- a/packages/types/src/message.ts +++ b/packages/types/src/message.ts @@ -196,6 +196,21 @@ export const contextCondenseSchema = z.object({ export type ContextCondense = z.infer +/** + * RateLimitRetryMetadata + */ +export const rateLimitRetrySchema = z.object({ + type: z.literal("rate_limit_retry"), + status: z.enum(["waiting", "retrying", "cancelled"]), + remainingSeconds: z.number().optional(), + attempt: z.number().optional(), + maxAttempts: z.number().optional(), + origin: z.enum(["pre_request", "retry_attempt"]).optional(), + detail: z.string().optional(), +}) + +export type RateLimitRetryMetadata = z.infer + /** * ClineMessage */ @@ -225,6 +240,7 @@ export const clineMessageSchema = z.object({ reasoning_summary: z.string().optional(), }) .optional(), + rateLimitRetry: rateLimitRetrySchema.optional(), }) .optional(), }) diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 851df91e6c5e..c2138c7d36c0 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -120,6 +120,16 @@ const DEFAULT_USAGE_COLLECTION_TIMEOUT_MS = 5000 // 5 seconds const FORCED_CONTEXT_REDUCTION_PERCENT = 75 // Keep 75% of context (remove 25%) on context window errors const MAX_CONTEXT_WINDOW_RETRIES = 3 // Maximum retries for context window errors +interface RateLimitRetryPayload { + type: "rate_limit_retry" + status: "waiting" | "retrying" | "cancelled" + remainingSeconds?: number + attempt?: number + maxAttempts?: number + origin: "pre_request" | "retry_attempt" + detail?: string +} + export interface TaskOptions extends CreateTaskOptions { provider: ClineProvider apiConfiguration: ProviderSettings @@ -1073,8 +1083,12 @@ export class Task extends EventEmitter implements TaskLike { if (partial !== undefined) { const lastMessage = this.clineMessages.at(-1) + const isRateLimitUpdate = type === "api_req_retry_delayed" && options.metadata?.rateLimitRetry !== undefined const isUpdatingPreviousPartial = - lastMessage && lastMessage.partial && lastMessage.type === "say" && lastMessage.say === type + lastMessage && + lastMessage.type === "say" && + lastMessage.say === type && + (lastMessage.partial || isRateLimitUpdate) if (partial) { if (isUpdatingPreviousPartial) { @@ -1083,6 +1097,13 @@ export class Task extends EventEmitter implements TaskLike { lastMessage.images = images lastMessage.partial = partial lastMessage.progressStatus = progressStatus + if (options.metadata) { + const messageWithMetadata = lastMessage as ClineMessage & ClineMessageWithMetadata + if (!messageWithMetadata.metadata) { + messageWithMetadata.metadata = {} + } + Object.assign(messageWithMetadata.metadata, options.metadata) + } this.updateClineMessage(lastMessage) } else { // This is a new partial message, so add it with partial state. @@ -1170,6 +1191,7 @@ export class Task extends EventEmitter implements TaskLike { images, checkpoint, contextCondense, + metadata: options.metadata, }) } } @@ -2556,6 +2578,124 @@ export class Task extends EventEmitter implements TaskLike { let rateLimitDelay = 0 + const sendRateLimitUpdate = async (payload: RateLimitRetryPayload, isPartial: boolean): Promise => { + await this.say("api_req_retry_delayed", undefined, undefined, isPartial, undefined, undefined, { + metadata: { rateLimitRetry: payload }, + }) + } + + const runRateLimitCountdown = async ({ + seconds, + origin, + attempt, + maxAttempts, + detail, + }: { + seconds: number + origin: RateLimitRetryPayload["origin"] + attempt?: number + maxAttempts?: number + detail?: string + }): Promise => { + const normalizedSeconds = Math.max(0, Math.ceil(seconds)) + + if (normalizedSeconds <= 0) { + if (this.abort) { + await sendRateLimitUpdate( + { + type: "rate_limit_retry", + status: "cancelled", + remainingSeconds: 0, + attempt, + maxAttempts, + origin, + detail, + }, + false, + ) + return false + } + + await sendRateLimitUpdate( + { + type: "rate_limit_retry", + status: "retrying", + remainingSeconds: 0, + attempt, + maxAttempts, + origin, + detail, + }, + false, + ) + return true + } + + for (let i = normalizedSeconds; i > 0; i--) { + if (this.abort) { + await sendRateLimitUpdate( + { + type: "rate_limit_retry", + status: "cancelled", + remainingSeconds: i, + attempt, + maxAttempts, + origin, + detail, + }, + false, + ) + return false + } + + await sendRateLimitUpdate( + { + type: "rate_limit_retry", + status: "waiting", + remainingSeconds: i, + attempt, + maxAttempts, + origin, + detail, + }, + true, + ) + + await delay(1000) + } + + if (this.abort) { + await sendRateLimitUpdate( + { + type: "rate_limit_retry", + status: "cancelled", + remainingSeconds: 0, + attempt, + maxAttempts, + origin, + detail, + }, + false, + ) + return false + } + + await sendRateLimitUpdate( + { + type: "rate_limit_retry", + status: "retrying", + remainingSeconds: 0, + attempt, + maxAttempts, + origin, + detail, + }, + false, + ) + + return true + } + // Use the shared timestamp so that subtasks respect the same rate-limit // window as their parent tasks. if (Task.lastGlobalApiRequestTime) { @@ -2567,11 +2707,16 @@ export class Task extends EventEmitter implements TaskLike { // Only show rate limiting message if we're not retrying. If retrying, we'll include the delay there. if (rateLimitDelay > 0 && retryAttempt === 0) { - // Show countdown timer - for (let i = rateLimitDelay; i > 0; i--) { - const delayMessage = `Rate limiting for ${i} seconds...` - await this.say("api_req_retry_delayed", delayMessage, undefined, true) - await delay(1000) + const countdownCompleted = await runRateLimitCountdown({ + seconds: rateLimitDelay, + origin: "pre_request", + attempt: 1, + }) + + if (!countdownCompleted) { + throw new Error( + `[RooCode#attemptApiRequest] task ${this.taskId}.${this.instanceId} aborted during pre-request rate limit wait`, + ) } } @@ -2723,7 +2868,7 @@ export class Task extends EventEmitter implements TaskLike { // note that this api_req_failed ask is unique in that we only present this option if the api hasn't streamed any content yet (ie it fails on the first chunk due), as it would allow them to hit a retry button. However if the api failed mid-stream, it could be in any arbitrary state where some tools may have executed, so that error is handled differently and requires cancelling the task entirely. if (autoApprovalEnabled && alwaysApproveResubmit) { - let errorMsg + let errorMsg: string if (error.error?.metadata?.raw) { errorMsg = JSON.stringify(error.error.metadata.raw, null, 2) @@ -2755,24 +2900,33 @@ export class Task extends EventEmitter implements TaskLike { // Wait for the greater of the exponential delay or the rate limit delay const finalDelay = Math.max(exponentialDelay, rateLimitDelay) - // Show countdown timer with exponential backoff - for (let i = finalDelay; i > 0; i--) { - await this.say( - "api_req_retry_delayed", - `${errorMsg}\n\nRetry attempt ${retryAttempt + 1}\nRetrying in ${i} seconds...`, - undefined, - true, + const sanitizedDetail = (() => { + if (!errorMsg) { + return undefined + } + const firstLine = errorMsg + .split("\n") + .map((line) => line.trim()) + .find((line) => line.length > 0) + if (!firstLine) { + return undefined + } + return firstLine.length > 160 ? `${firstLine.slice(0, 157)}…` : firstLine + })() + + const countdownCompleted = await runRateLimitCountdown({ + seconds: finalDelay, + origin: "retry_attempt", + attempt: retryAttempt + 2, + detail: sanitizedDetail, + }) + + if (!countdownCompleted) { + throw new Error( + `[RooCode#attemptApiRequest] task ${this.taskId}.${this.instanceId} aborted during rate limit retry wait`, ) - await delay(1000) } - await this.say( - "api_req_retry_delayed", - `${errorMsg}\n\nRetry attempt ${retryAttempt + 1}\nRetrying now...`, - undefined, - false, - ) - // Delegate generator output from the recursive call with // incremented retry count. yield* this.attemptApiRequest(retryAttempt + 1) diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 26bc71074adb..f294932745c9 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -43,6 +43,8 @@ import CodebaseSearchResultsDisplay from "./CodebaseSearchResultsDisplay" import { appendImages } from "@src/utils/imageUtils" import { McpExecution } from "./McpExecution" import { ChatTextArea } from "./ChatTextArea" +import { RateLimitRetryRow } from "./RateLimitRetryRow" +export { RateLimitRetryRow } from "./RateLimitRetryRow" import { MAX_IMAGES_PER_MESSAGE } from "./ChatView" import { useSelectedModel } from "../ui/hooks/useSelectedModel" import { @@ -264,7 +266,7 @@ export const ChatRowContent = ({ {t("chat:taskCompleted")}, ] case "api_req_retry_delayed": - return [] + return [null, null] case "api_req_started": const getIconSpan = (iconName: string, color: string) => (
) + case "api_req_retry_delayed": + // Prevent multiple blocks returning, we only need a single block + // that's constantly updated + if (!isLast) return null + return case "shell_integration_warning": return case "checkpoint_saved": diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index d358c68f1cff..57d14426eff5 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -425,9 +425,14 @@ const ChatViewComponent: React.ForwardRefRenderFunction { + const { t } = useTranslation() + + const description = useMemo(() => { + if (!metadata) { + return "" + } + + if (metadata.status === "retrying") { + return t("chat:rateLimitRetry.retrying") + } + + if (metadata.status === "cancelled") { + return t("chat:rateLimitRetry.cancelled") + } + + if (typeof metadata.remainingSeconds === "number") { + if (metadata.attempt && metadata.maxAttempts) { + return t("chat:rateLimitRetry.waitingWithAttemptMax", { + seconds: metadata.remainingSeconds, + attempt: metadata.attempt, + maxAttempts: metadata.maxAttempts, + }) + } + + if (metadata.attempt) { + return t("chat:rateLimitRetry.waitingWithAttempt", { + seconds: metadata.remainingSeconds, + attempt: metadata.attempt, + }) + } + + return t("chat:rateLimitRetry.waiting", { seconds: metadata.remainingSeconds }) + } + + return "" + }, [metadata, t]) + + const detail = metadata?.detail + const iconNode = + metadata?.status === "cancelled" ? ( + + ) : ( + + ) + + return ( +
+
+
{iconNode}
+
+ {t("chat:rateLimitRetry.title")} + {(description || detail) && ( + + {description} + {detail ? ( + <> + {" — "} + {detail} + + ) : null} + + )} +
+
+
+ ) +} diff --git a/webview-ui/src/components/chat/__tests__/RateLimitRetryRow.spec.tsx b/webview-ui/src/components/chat/__tests__/RateLimitRetryRow.spec.tsx new file mode 100644 index 000000000000..9fc2477ada87 --- /dev/null +++ b/webview-ui/src/components/chat/__tests__/RateLimitRetryRow.spec.tsx @@ -0,0 +1,132 @@ +// npx vitest run src/components/chat/__tests__/RateLimitRetryRow.spec.tsx + +import { render, screen } from "@/utils/test-utils" + +import { RateLimitRetryRow } from "../ChatRow" +import type { RateLimitRetryMetadata } from "@roo-code/types" + +vi.mock("@/i18n/TranslationContext", () => ({ + useAppTranslation: () => ({ + t: (key: string) => key, + }), +})) + +// Manual trigger instructions (for developer reference): +// 1) In Roo Code settings, set your provider Rate limit seconds to a small value (e.g., 5s). +// 2) Send a message to start an API request. +// 3) Immediately send another message within the configured window. +// The pre-request wait will emit `api_req_retry_delayed` with metadata, +// rendering a single live status row: spinner + per‑second countdown, +// then "Retrying now..." at zero. Input remains disabled during the wait. +describe("RateLimitRetryRow", () => { + it("renders waiting countdown with attempt and max attempts", () => { + const metadata: RateLimitRetryMetadata = { + type: "rate_limit_retry", + status: "waiting", + remainingSeconds: 12, + attempt: 2, + maxAttempts: 5, + origin: "retry_attempt", + } + + render() + + expect(screen.getByText("chat:rateLimitRetry.title")).toBeInTheDocument() + expect(screen.getByText("chat:rateLimitRetry.waitingWithAttemptMax")).toBeInTheDocument() + }) + + it("renders retrying state", () => { + const metadata: RateLimitRetryMetadata = { + type: "rate_limit_retry", + status: "retrying", + origin: "retry_attempt", + } + + render() + + expect(screen.getByText("chat:rateLimitRetry.title")).toBeInTheDocument() + expect(screen.getByText("chat:rateLimitRetry.retrying")).toBeInTheDocument() + }) + + it("renders cancelled state (neutral)", () => { + const metadata: RateLimitRetryMetadata = { + type: "rate_limit_retry", + status: "cancelled", + origin: "retry_attempt", + } + + const { container } = render() + + expect(screen.getByText("chat:rateLimitRetry.title")).toBeInTheDocument() + expect(screen.getByText("chat:rateLimitRetry.cancelled")).toBeInTheDocument() + + // Iconography: ensure neutral cancelled icon is present + const cancelledIcon = container.querySelector(".codicon-circle-slash") + expect(cancelledIcon).not.toBeNull() + }) + + it("renders empty description when metadata is missing", () => { + render() + + expect(screen.getByText("chat:rateLimitRetry.title")).toBeInTheDocument() + // Description should be empty when no metadata is provided + const descriptionElement = screen.queryByText(/./i, { selector: ".text-vscode-descriptionForeground span" }) + expect(descriptionElement).not.toBeInTheDocument() + }) + + it("updates when metadata changes from waiting to retrying", () => { + const initialMetadata: RateLimitRetryMetadata = { + type: "rate_limit_retry", + status: "waiting", + remainingSeconds: 5, + attempt: 1, + maxAttempts: 3, + origin: "retry_attempt", + } + + const { rerender } = render() + + // Initial state: waiting + expect(screen.getByText("chat:rateLimitRetry.waitingWithAttemptMax")).toBeInTheDocument() + + // Update to retrying state + const updatedMetadata: RateLimitRetryMetadata = { + type: "rate_limit_retry", + status: "retrying", + origin: "retry_attempt", + } + + rerender() + + // Should now show retrying + expect(screen.getByText("chat:rateLimitRetry.retrying")).toBeInTheDocument() + expect(screen.queryByText("chat:rateLimitRetry.waitingWithAttemptMax")).not.toBeInTheDocument() + }) + + it("updates countdown when remainingSeconds changes", () => { + const metadata1: RateLimitRetryMetadata = { + type: "rate_limit_retry", + status: "waiting", + remainingSeconds: 10, + attempt: 1, + maxAttempts: 3, + origin: "retry_attempt", + } + + const { rerender } = render() + + // Initial countdown + expect(screen.getByText("chat:rateLimitRetry.waitingWithAttemptMax")).toBeInTheDocument() + + // Update countdown + const metadata2: RateLimitRetryMetadata = { + ...metadata1, + remainingSeconds: 5, + } + + rerender() + + // Should still show the same text key but with updated seconds + expect(screen.getByText("chat:rateLimitRetry.waitingWithAttemptMax")).toBeInTheDocument() + }) +}) diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index 4d392ee3bf68..c6333e7eeb2f 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -143,6 +143,14 @@ "cancelled": "Sol·licitud API cancel·lada", "streamingFailed": "Transmissió API ha fallat" }, + "rateLimitRetry": { + "title": "Límit de peticions assolit — si us plau, espera.", + "waiting": "Reintentant en {{seconds}}s", + "waitingWithAttempt": "Reintentant en {{seconds}}s (intent {{attempt}})", + "waitingWithAttemptMax": "Reintentant en {{seconds}}s (intent {{attempt}}/{{maxAttempts}})", + "retrying": "Reintentant ara…", + "cancelled": "Reintent cancel·lat" + }, "checkpoint": { "regular": "Punt de control", "initializingWarning": "Encara s'està inicialitzant el punt de control... Si això triga massa, pots desactivar els punts de control a la configuració i reiniciar la teva tasca.", diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index 65934ab5a29c..2769d5a2b25c 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -143,6 +143,14 @@ "cancelled": "API-Anfrage abgebrochen", "streamingFailed": "API-Streaming fehlgeschlagen" }, + "rateLimitRetry": { + "title": "Ratenlimit ausgelöst — bitte warten.", + "waiting": "Wiederholung in {{seconds}}s", + "waitingWithAttempt": "Wiederholung in {{seconds}}s (Versuch {{attempt}})", + "waitingWithAttemptMax": "Wiederholung in {{seconds}}s (Versuch {{attempt}}/{{maxAttempts}})", + "retrying": "Wiederholung läuft…", + "cancelled": "Wiederholung abgebrochen" + }, "checkpoint": { "regular": "Checkpoint", "initializingWarning": "Checkpoint wird noch initialisiert... Falls dies zu lange dauert, kannst du Checkpoints in den Einstellungen deaktivieren und deine Aufgabe neu starten.", diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index 9ab37b11497a..3b233ddc31de 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -149,6 +149,14 @@ "cancelled": "API Request Cancelled", "streamingFailed": "API Streaming Failed" }, + "rateLimitRetry": { + "title": "Rate limit triggered — please wait.", + "waiting": "Retrying in {{seconds}}s", + "waitingWithAttempt": "Retrying in {{seconds}}s (attempt {{attempt}})", + "waitingWithAttemptMax": "Retrying in {{seconds}}s (attempt {{attempt}}/{{maxAttempts}})", + "retrying": "Retrying now…", + "cancelled": "Retry cancelled" + }, "checkpoint": { "regular": "Checkpoint", "initializingWarning": "Still initializing checkpoint... If this takes too long, you can disable checkpoints in settings and restart your task.", diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index 30693285db2e..e16af1bc5e26 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -159,6 +159,14 @@ }, "current": "Actual" }, + "rateLimitRetry": { + "title": "Límite de tasa activado — por favor, espera.", + "waiting": "Reintentando en {{seconds}}s", + "waitingWithAttempt": "Reintentando en {{seconds}}s (intento {{attempt}})", + "waitingWithAttemptMax": "Reintentando en {{seconds}}s (intento {{attempt}}/{{maxAttempts}})", + "retrying": "Reintentando ahora…", + "cancelled": "Reintento cancelado" + }, "instructions": { "wantsToFetch": "Roo quiere obtener instrucciones detalladas para ayudar con la tarea actual" }, diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index d5dae24a7913..17eb5dd577af 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -159,6 +159,14 @@ }, "current": "Actuel" }, + "rateLimitRetry": { + "title": "Limite de débit atteinte — veuillez patienter.", + "waiting": "Nouvel essai dans {{seconds}}s", + "waitingWithAttempt": "Nouvel essai dans {{seconds}}s (tentative {{attempt}})", + "waitingWithAttemptMax": "Nouvel essai dans {{seconds}}s (tentative {{attempt}}/{{maxAttempts}})", + "retrying": "Nouvel essai en cours…", + "cancelled": "Nouvel essai annulé" + }, "fileOperations": { "wantsToRead": "Roo veut lire ce fichier", "wantsToReadOutsideWorkspace": "Roo veut lire ce fichier en dehors de l'espace de travail", diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index 0ec9cd7c7e93..f23b744940db 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -143,6 +143,14 @@ "cancelled": "API अनुरोध रद्द किया गया", "streamingFailed": "API स्ट्रीमिंग विफल हुई" }, + "rateLimitRetry": { + "title": "दर सीमा ट्रिगर हुई — कृपया प्रतीक्षा करें।", + "waiting": "{{seconds}}s में पुनः प्रयास कर रहा है", + "waitingWithAttempt": "{{seconds}}s में पुनः प्रयास कर रहा है (प्रयास {{attempt}})", + "waitingWithAttemptMax": "{{seconds}}s में पुनः प्रयास कर रहा है (प्रयास {{attempt}}/{{maxAttempts}})", + "retrying": "अभी पुनः प्रयास कर रहा है…", + "cancelled": "पुनः प्रयास रद्द किया गया" + }, "checkpoint": { "regular": "चेकपॉइंट", "initializingWarning": "चेकपॉइंट अभी भी आरंभ हो रहा है... अगर यह बहुत समय ले रहा है, तो आप सेटिंग्स में चेकपॉइंट को अक्षम कर सकते हैं और अपने कार्य को पुनः आरंभ कर सकते हैं।", diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json index a2c5377691e9..8a01a483c88e 100644 --- a/webview-ui/src/i18n/locales/id/chat.json +++ b/webview-ui/src/i18n/locales/id/chat.json @@ -152,6 +152,14 @@ "cancelled": "Permintaan API Dibatalkan", "streamingFailed": "Streaming API Gagal" }, + "rateLimitRetry": { + "title": "Batas kecepatan tercapai — mohon tunggu.", + "waiting": "Mencoba lagi dalam {{seconds}}dtk", + "waitingWithAttempt": "Mencoba lagi dalam {{seconds}}dtk (percobaan {{attempt}})", + "waitingWithAttemptMax": "Mencoba lagi dalam {{seconds}}dtk (percobaan {{attempt}}/{{maxAttempts}})", + "retrying": "Mencoba lagi sekarang…", + "cancelled": "Percobaan ulang dibatalkan" + }, "checkpoint": { "regular": "Checkpoint", "initializingWarning": "Masih menginisialisasi checkpoint... Jika ini terlalu lama, kamu bisa menonaktifkan checkpoint di pengaturan dan restart tugas.", diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index a176be1f2fb6..6d983fdaee9e 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -146,6 +146,14 @@ "cancelled": "Richiesta API annullata", "streamingFailed": "Streaming API fallito" }, + "rateLimitRetry": { + "title": "Limite di frequenza raggiunto — attendi.", + "waiting": "Riprovo tra {{seconds}}s", + "waitingWithAttempt": "Riprovo tra {{seconds}}s (tentativo {{attempt}})", + "waitingWithAttemptMax": "Riprovo tra {{seconds}}s (tentativo {{attempt}}/{{maxAttempts}})", + "retrying": "Riprovo ora…", + "cancelled": "Riprova annullata" + }, "checkpoint": { "regular": "Checkpoint", "initializingWarning": "Inizializzazione del checkpoint in corso... Se questa operazione richiede troppo tempo, puoi disattivare i checkpoint nelle impostazioni e riavviare l'attività.", diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index 0e940b17ecac..37e190e9d870 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -143,6 +143,14 @@ "cancelled": "APIリクエストキャンセル", "streamingFailed": "APIストリーミング失敗" }, + "rateLimitRetry": { + "title": "レート制限がトリガーされました — しばらくお待ちください。", + "waiting": "{{seconds}}秒後に再試行", + "waitingWithAttempt": "{{seconds}}秒後に再試行({{attempt}}回目)", + "waitingWithAttemptMax": "{{seconds}}秒後に再試行({{attempt}}/{{maxAttempts}}回目)", + "retrying": "再試行中…", + "cancelled": "再試行キャンセル" + }, "checkpoint": { "regular": "チェックポイント", "initializingWarning": "チェックポイントの初期化中... 時間がかかりすぎる場合は、設定でチェックポイントを無効にしてタスクを再開できます。", diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index 6fa7c5692ac6..8369c1df1e26 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -143,6 +143,14 @@ "cancelled": "API 요청 취소됨", "streamingFailed": "API 스트리밍 실패" }, + "rateLimitRetry": { + "title": "속도 제한이 트리거되었습니다 — 잠시 기다려주세요.", + "waiting": "{{seconds}}초 후에 다시 시도", + "waitingWithAttempt": "{{seconds}}초 후에 다시 시도 ({{attempt}}번째 시도)", + "waitingWithAttemptMax": "{{seconds}}초 후에 다시 시도 ({{attempt}}/{{maxAttempts}}번째 시도)", + "retrying": "지금 다시 시도 중…", + "cancelled": "다시 시도 취소됨" + }, "checkpoint": { "regular": "체크포인트", "initializingWarning": "체크포인트 초기화 중... 시간이 너무 오래 걸리면 설정에서 체크포인트를 비활성화하고 작업을 다시 시작할 수 있습니다.", diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index 2be312228d27..876dab045e76 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -138,6 +138,14 @@ "cancelled": "API-verzoek geannuleerd", "streamingFailed": "API-streaming mislukt" }, + "rateLimitRetry": { + "title": "Snelheidslimiet bereikt — even geduld a.u.b.", + "waiting": "Opnieuw proberen over {{seconds}}s", + "waitingWithAttempt": "Opnieuw proberen over {{seconds}}s (poging {{attempt}})", + "waitingWithAttemptMax": "Opnieuw proberen over {{seconds}}s (poging {{attempt}}/{{maxAttempts}})", + "retrying": "Nu opnieuw proberen…", + "cancelled": "Opnieuw proberen geannuleerd" + }, "checkpoint": { "regular": "Checkpoint", "initializingWarning": "Checkpoint wordt nog steeds geïnitialiseerd... Als dit te lang duurt, kun je checkpoints uitschakelen in de instellingen en je taak opnieuw starten.", diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index e60ec330db23..835a987b765c 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -143,6 +143,14 @@ "cancelled": "Zapytanie API anulowane", "streamingFailed": "Strumieniowanie API nie powiodło się" }, + "rateLimitRetry": { + "title": "Osiągnięto limit szybkości — proszę czekać.", + "waiting": "Ponawianie za {{seconds}}s", + "waitingWithAttempt": "Ponawianie za {{seconds}}s (próba {{attempt}})", + "waitingWithAttemptMax": "Ponawianie za {{seconds}}s (próba {{attempt}}/{{maxAttempts}})", + "retrying": "Ponawianie teraz…", + "cancelled": "Ponawianie anulowane" + }, "checkpoint": { "regular": "Punkt kontrolny", "initializingWarning": "Trwa inicjalizacja punktu kontrolnego... Jeśli to trwa zbyt długo, możesz wyłączyć punkty kontrolne w ustawieniach i uruchomić zadanie ponownie.", diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index 3408f3a1aa91..1f5764e71e5a 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -143,6 +143,14 @@ "cancelled": "Requisição API cancelada", "streamingFailed": "Streaming API falhou" }, + "rateLimitRetry": { + "title": "Limite de taxa atingido — por favor, aguarde.", + "waiting": "Tentando novamente em {{seconds}}s", + "waitingWithAttempt": "Tentando novamente em {{seconds}}s (tentativa {{attempt}})", + "waitingWithAttemptMax": "Tentando novamente em {{seconds}}s (tentativa {{attempt}}/{{maxAttempts}})", + "retrying": "Tentando novamente agora…", + "cancelled": "Tentativa cancelada" + }, "checkpoint": { "regular": "Ponto de verificação", "initializingWarning": "Ainda inicializando ponto de verificação... Se isso demorar muito, você pode desativar os pontos de verificação nas configurações e reiniciar sua tarefa.", diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index 5018f817aff5..452847646fa0 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -138,6 +138,14 @@ "cancelled": "API-запрос отменен", "streamingFailed": "Ошибка потокового API-запроса" }, + "rateLimitRetry": { + "title": "Превышен лимит запросов — пожалуйста, подождите.", + "waiting": "Повторная попытка через {{seconds}}с", + "waitingWithAttempt": "Повторная попытка через {{seconds}}с (попытка {{attempt}})", + "waitingWithAttemptMax": "Повторная попытка через {{seconds}}с (попытка {{attempt}}/{{maxAttempts}})", + "retrying": "Повторная попытка сейчас…", + "cancelled": "Повторная попытка отменена" + }, "checkpoint": { "regular": "Точка сохранения", "initializingWarning": "Точка сохранения еще инициализируется... Если это занимает слишком много времени, вы можете отключить точки сохранения в настройках и перезапустить задачу.", diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index 2290a85f6dfe..91e53d447e6f 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -159,6 +159,14 @@ }, "current": "Mevcut" }, + "rateLimitRetry": { + "title": "Hız limiti tetiklendi — lütfen bekleyin.", + "waiting": "{{seconds}}s içinde tekrar deniyor", + "waitingWithAttempt": "{{seconds}}s içinde tekrar deniyor (deneme {{attempt}})", + "waitingWithAttemptMax": "{{seconds}}s içinde tekrar deniyor (deneme {{attempt}}/{{maxAttempts}})", + "retrying": "Şimdi tekrar deniyor…", + "cancelled": "Tekrar deneme iptal edildi" + }, "instructions": { "wantsToFetch": "Roo mevcut göreve yardımcı olmak için ayrıntılı talimatlar almak istiyor" }, diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index d9362ea39dde..733c14a7c673 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -143,6 +143,14 @@ "cancelled": "Yêu cầu API đã hủy", "streamingFailed": "Streaming API thất bại" }, + "rateLimitRetry": { + "title": "Đã đạt giới hạn tốc độ — vui lòng đợi.", + "waiting": "Đang thử lại sau {{seconds}}s", + "waitingWithAttempt": "Đang thử lại sau {{seconds}}s (thử lại lần {{attempt}})", + "waitingWithAttemptMax": "Đang thử lại sau {{seconds}}s (thử lại lần {{attempt}}/{{maxAttempts}})", + "retrying": "Đang thử lại ngay…", + "cancelled": "Đã hủy thử lại" + }, "checkpoint": { "regular": "Điểm kiểm tra", "initializingWarning": "Đang khởi tạo điểm kiểm tra... Nếu quá trình này mất quá nhiều thời gian, bạn có thể vô hiệu hóa điểm kiểm tra trong cài đặt và khởi động lại tác vụ của bạn.", diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index 850f79e63038..13ce038a28fb 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -143,6 +143,14 @@ "cancelled": "API请求已取消", "streamingFailed": "API流式传输失败" }, + "rateLimitRetry": { + "title": "API 请求频率限制已触发 — 请稍候。", + "waiting": "正在 {{seconds}} 秒后重试", + "waitingWithAttempt": "正在 {{seconds}} 秒后重试 (第 {{attempt}} 次尝试)", + "waitingWithAttemptMax": "正在 {{seconds}} 秒后重试 (第 {{attempt}}/{{maxAttempts}} 次尝试)", + "retrying": "正在立即重试…", + "cancelled": "重试已取消" + }, "checkpoint": { "regular": "检查点", "initializingWarning": "正在初始化检查点...如果耗时过长,你可以在设置中禁用检查点并重新启动任务。", diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index 24a67b13a9a5..e63db0a84b76 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -149,6 +149,14 @@ "cancelled": "API 請求已取消", "streamingFailed": "API 串流處理失敗" }, + "rateLimitRetry": { + "title": "已觸發速率限制 — 請稍候。", + "waiting": "正在 {{seconds}} 秒後重試", + "waitingWithAttempt": "正在 {{seconds}} 秒後重試 (第 {{attempt}} 次嘗試)", + "waitingWithAttemptMax": "正在 {{seconds}} 秒後重試 (第 {{attempt}}/{{maxAttempts}} 次嘗試)", + "retrying": "正在立即重試…", + "cancelled": "重試已取消" + }, "checkpoint": { "regular": "檢查點", "initializingWarning": "正在初始化檢查點... 如果耗時過長,您可以在設定中停用檢查點並重新啟動工作。",