JuliusBrussee/caveman · error · Error

cave_vercel_terminal_failure

cave_vercel_terminal_failure

Error message

cave_vercel_terminal_failure

What it means

The Vercel AI SDK adapter throws `cave_vercel_terminal_failure` after `agent.generate` returns when `response.error !== undefined` or `terminalFinishReason(response.finishReason) === false`. `terminalFinishReason` accepts exactly `"stop"`, `"length"`, `"content-filter"`, and `"other"` — anything else (`undefined`, `"tool-calls"`, `"error"`, `"unknown"`, custom strings) is non-terminal. In practice this means the ToolLoopAgent ended without a terminal model stop (e.g. its step budget ran out while tools still wanted to run) or the response carried an explicit error object.

Solutions

  1. Reproduce with a raw `agent.generate` call and inspect `response.error` and `response.finishReason` to classify the failure
  2. If finishReason is `tool-calls`, raise the agent's step/stopWhen budget so the loop reaches a model stop
  3. If `response.error` is set, fix credentials/rate limits before retrying — this code is not transient by itself
  4. Pin `ai` at exactly 7.0.43 (the adapter's `assertSupportedUpstream` enforces this installed version)
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-flight: prove the agent reaches a terminal stop and reports v4 usage
// with one cheap raw call before wiring the adapter
const probe = await agent.generate({ prompt: 'ping' });
if (probe.error !== undefined) throw new Error(`agent misconfigured: ${String(probe.error)}`);
if (!['stop', 'length', 'content-filter', 'other'].includes(String(probe.finishReason))) {
  throw new Error(`agent does not terminate: finishReason=${String(probe.finishReason)}`);
}

Try / catch

try {
  return await adapter.run(req);
} catch (e) {
  if (e instanceof Error && e.message === 'cave_vercel_terminal_failure') {
    // classify with one raw generate: response.error → provider/config issue;
    // finishReason 'tool-calls' → raise the agent's step/stopWhen budget
    const diag = await agent.generate({ prompt: 'ping' });
    log.diag({ error: diag.error, finishReason: diag.finishReason });
  }
  throw e;
}

Prevention

When it happens

Trigger: A ToolLoopAgent whose `stopWhen` budget is exhausted so `finishReason` stays `"tool-calls"`; the SDK setting `response.error` on provider auth/rate-limit failures; a signal aborted mid-loop producing an error or undefined finishReason.

Common situations: Misconfigured agent step budget / stopWhen that never triggers; invalid or expired provider API key; provider outage surfacing as an error field; runaway tool loops burning all steps before a final model answer.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18). Data as JSON: /api/errors/5be1f7fe47be181a. Report an issue: GitHub.

Appendix: source

Thrown at packages/agent/src/adapters.ts:213

export function createVercelAISDKAdapter(
  identity: HarnessAdapterIdentity,
  agent: VercelToolLoopAgentBinding,
): HarnessAdapter {
  assertSupportedUpstream(identity, VERCEL_AI_SDK_VERSION, "vercel", "ai");
  return createHarnessAdapter("vercel-ai-sdk", identity, {
    package: "ai",
    class: "ToolLoopAgent",
    method: "generate",
    usage: "LanguageModelUsage.v4",
  }, async (request) => {
    const startedAt = performance.now();
    const response = await agent.generate({
      prompt: request.prompt,
      ...(request.signal === undefined ? {} : { abortSignal: request.signal }),
    });
    if (response.error !== undefined || terminalFinishReason(response.finishReason) === false) {
      throw new Error("cave_vercel_terminal_failure");
    }
    const expected = expectedProviderModel(request.plan);
    const actualModel = normalizedResponseModel(response.response?.modelId, expected.provider);
    return harnessExecution({
      request,
      text: response.text,
      provider: expected.provider,
      model: actualModel,
      usage: usageFromAISDK(response.usage, request.plan.reasoning !== "none"),
      latencyMs: Math.round(performance.now() - startedAt),
    });
  });
}

export interface EveMessageResponseBinding {
  result(): Promise<{
    message: string | undefined;
    status: "completed" | "failed" | "waiting";

View on GitHub (pinned to 766dce6b13)