|
1 | 1 | import type { Sandbox as SandboxType } from "@vercel/sandbox"; |
2 | | -import { getWritable } from "workflow"; |
| 2 | +import { getWritable, FatalError } from "workflow"; |
3 | 3 | import { buildAgentCommand, parseAgentOutput, formatStreamEvent } from "./agent-runner.js"; |
4 | 4 | import type { AgentOutput } from "./agent-runner.js"; |
5 | 5 | import { SandboxManager } from "./manager.js"; |
6 | 6 |
|
| 7 | +const NON_RETRYABLE_PATTERNS = [ |
| 8 | + /credit balance is too low/i, |
| 9 | + /insufficient.?quota/i, |
| 10 | + /billing/i, |
| 11 | + /invalid.?api.?key/i, |
| 12 | + /authentication/i, |
| 13 | + /unauthorized/i, |
| 14 | + /permission.?denied/i, |
| 15 | +]; |
| 16 | + |
7 | 17 | type SandboxInstance = Awaited<ReturnType<typeof SandboxType.create>>; |
8 | 18 |
|
9 | 19 | interface RunAgentOptions { |
@@ -69,10 +79,22 @@ export async function runAgent( |
69 | 79 |
|
70 | 80 | const raw = stdout.trim() || stderr.trim(); |
71 | 81 | const output = parseAgentOutput(raw); |
| 82 | + |
| 83 | + const combinedText = `${output.error ?? ""} ${stderr}`; |
| 84 | + if (NON_RETRYABLE_PATTERNS.some((p) => p.test(combinedText))) { |
| 85 | + throw new FatalError(`Non-retryable agent error: ${output.error ?? stderr.slice(0, 500)}`); |
| 86 | + } |
| 87 | + |
72 | 88 | return { output, files }; |
73 | 89 | } catch (err) { |
74 | 90 | await manager.runEndHook(sandbox).catch(() => {}); |
75 | 91 | const files = await manager.extractChanges(sandbox).catch(() => []); |
| 92 | + |
| 93 | + const msg = (err as Error).message ?? ""; |
| 94 | + if (NON_RETRYABLE_PATTERNS.some((p) => p.test(msg))) { |
| 95 | + throw new FatalError(`Non-retryable agent error: ${msg}`); |
| 96 | + } |
| 97 | + |
76 | 98 | throw Object.assign(err as Error, { files }); |
77 | 99 | } finally { |
78 | 100 | await manager.teardown(sandbox); |
|
0 commit comments