import { useAgent } from "agents/react"; import { useState } from "react"; import { Button, Input, Surface, Text } from "@cloudflare/kumo"; import { DemoWrapper } from "../../layout"; import { LogPanel, ConnectionStatus, CodeExplanation, type CodeSection } from "../../components"; import { useLogs, useUserId, useToast } from "../../hooks"; import type { RetryAgent, RetryAgentState } from "./retry-agent"; const codeSections: CodeSection[] = [ { title: "Retry with this.retry()", description: "Wrap any fallible operation in this.retry(). It uses exponential backoff by default. Set class-level defaults with static options, or pass per-call overrides.", code: `import { Agent, callable } from "agents"; class RetryAgent extends Agent { static options = { retry: { maxAttempts: 4, baseDelayMs: 50, maxDelayMs: 1000 }, }; @callable() async retryFlaky(succeedOnAttempt: number) { return await this.retry(async (attempt) => { if (attempt < succeedOnAttempt) { throw new Error(\`Transient failure on attempt \${attempt}\`); } return \`Success on attempt \${attempt}\`; }); } }` }, { title: "Selective retry with shouldRetry", description: "Pass a shouldRetry callback to decide whether a specific error is worth retrying. Return false to bail immediately on permanent failures.", code: ` @callable() async retryWithFilter(failCount: number, permanent: boolean) { return await this.retry( async (attempt) => { if (attempt <= failCount) { const err = new Error("failure"); err.permanent = permanent; throw err; } return "success"; }, { maxAttempts: 10, shouldRetry: (err) => !err.permanent, } ); }` }, { title: "Queue with retry options", description: "this.queue() also supports retry options. The queued callback will be retried with backoff if it throws.", code: ` @callable() async queueWithRetry(maxAttempts: number) { return await this.queue("onQueuedTask", { maxAttempts }, { retry: { maxAttempts, baseDelayMs: 50, maxDelayMs: 500 }, }); } async onQueuedTask(payload: { maxAttempts: number }) { // Fails until last attempt, then succeeds this._attempts++; if (this._attempts < payload.maxAttempts) { throw new Error("Not yet"); } // Success! }` } ]; export function RetryDemo() { const userId = useUserId(); const { logs, addLog, clearLogs } = useLogs(); const { toast } = useToast(); const [succeedOn, setSucceedOn] = useState("3"); const [failCount, setFailCount] = useState("2"); const [permanent, setPermanent] = useState(false); const [queueAttempts, setQueueAttempts] = useState("3"); const agent = useAgent({ agent: "retry-agent", name: `retry-demo-${userId}`, onOpen: () => addLog("info", "connected"), onClose: () => addLog("info", "disconnected"), onError: () => addLog("error", "error", "Connection error"), onMessage: (message: MessageEvent) => { try { const data = JSON.parse(message.data as string); if (data.type === "log" && data.entry) { const entry = data.entry; const logType = entry.type === "success" ? "in" : entry.type === "failure" ? "error" : entry.type === "attempt" ? "out" : "info"; addLog(logType, entry.type, entry.message); } } catch { // Not JSON } } }); const handleRetryFlaky = async () => { addLog("out", "retryFlaky", { succeedOnAttempt: Number(succeedOn) }); try { const result = await agent.call("retryFlaky", [Number(succeedOn)]); addLog("in", "result", result); toast(String(result), "success"); } catch (e) { addLog("error", "error", e instanceof Error ? e.message : String(e)); toast(e instanceof Error ? e.message : String(e), "error"); } }; const handleRetryWithFilter = async () => { addLog("out", "retryWithFilter", { failCount: Number(failCount), permanent }); try { const result = await agent.call("retryWithFilter", [ Number(failCount), permanent ]); addLog("in", "result", result); toast(String(result), "success"); } catch (e) { addLog("error", "error", e instanceof Error ? e.message : String(e)); toast(e instanceof Error ? e.message : String(e), "error"); } }; const handleQueueRetry = async () => { addLog("out", "queueWithRetry", { maxAttempts: Number(queueAttempts) }); try { const id = await agent.call("queueWithRetry", [Number(queueAttempts)]); addLog("in", "queued", { id }); } catch (e) { addLog("error", "error", e instanceof Error ? e.message : String(e)); } }; const handleClear = async () => { clearLogs(); try { await agent.call("clearLog", []); } catch { // ignore } }; return ( Wrap any fallible operation in{" "} this.retry() {" "} to automatically retry with exponential backoff. Set class-level defaults with{" "} static options , or pass per-call overrides. Use a{" "} shouldRetry {" "} callback to bail early on permanent errors. Queued tasks and scheduled tasks also support retry options. } statusIndicator={ } >
{/* Controls */}
{/* this.retry() — Flaky Operation */}
this.retry() — Flaky Operation

Simulates a flaky operation that succeeds on the Nth attempt. Uses class-level defaults (4 max attempts).

Succeed on attempt ) => setSucceedOn(e.target.value) } className="w-20" min={1} max={10} />
{/* shouldRetry — Selective Retry */}
shouldRetry — Selective Retry

Uses shouldRetry to bail early on permanent errors. Transient errors are retried; permanent errors stop immediately.

Failures before success ) => setFailCount(e.target.value) } className="w-20" min={1} max={9} />
{/* Queue with Retry */}
Queue with Retry

Queues a task that fails until the last retry attempt, then succeeds.

Max attempts ) => setQueueAttempts(e.target.value) } className="w-20" min={1} max={10} />
{/* Clear */}
{/* Logs */}
); }