{"record":{"id":"5be1f7fe47be181a","repo":"JuliusBrussee/caveman","slug":"cave-vercel-terminal-failure","errorCode":"cave_vercel_terminal_failure","errorMessage":"cave_vercel_terminal_failure","messagePattern":"cave_vercel_terminal_failure","errorType":"error_code","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/adapters.ts","lineNumber":213,"sourceCode":"\nexport function createVercelAISDKAdapter(\n  identity: HarnessAdapterIdentity,\n  agent: VercelToolLoopAgentBinding,\n): HarnessAdapter {\n  assertSupportedUpstream(identity, VERCEL_AI_SDK_VERSION, \"vercel\", \"ai\");\n  return createHarnessAdapter(\"vercel-ai-sdk\", identity, {\n    package: \"ai\",\n    class: \"ToolLoopAgent\",\n    method: \"generate\",\n    usage: \"LanguageModelUsage.v4\",\n  }, async (request) => {\n    const startedAt = performance.now();\n    const response = await agent.generate({\n      prompt: request.prompt,\n      ...(request.signal === undefined ? {} : { abortSignal: request.signal }),\n    });\n    if (response.error !== undefined || terminalFinishReason(response.finishReason) === false) {\n      throw new Error(\"cave_vercel_terminal_failure\");\n    }\n    const expected = expectedProviderModel(request.plan);\n    const actualModel = normalizedResponseModel(response.response?.modelId, expected.provider);\n    return harnessExecution({\n      request,\n      text: response.text,\n      provider: expected.provider,\n      model: actualModel,\n      usage: usageFromAISDK(response.usage, request.plan.reasoning !== \"none\"),\n      latencyMs: Math.round(performance.now() - startedAt),\n    });\n  });\n}\n\nexport interface EveMessageResponseBinding {\n  result(): Promise<{\n    message: string | undefined;\n    status: \"completed\" | \"failed\" | \"waiting\";","sourceCodeStart":195,"sourceCodeEnd":231,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/766dce6b1394ebb56a3090748d5a0240a5aefb36/packages/agent/src/adapters.ts#L195-L231","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Reproduce with a raw `agent.generate` call and inspect `response.error` and `response.finishReason` to classify the failure","If finishReason is `tool-calls`, raise the agent's step/stopWhen budget so the loop reaches a model stop","If `response.error` is set, fix credentials/rate limits before retrying — this code is not transient by itself","Pin `ai` at exactly 7.0.43 (the adapter's `assertSupportedUpstream` enforces this installed version)"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// pre-flight: prove the agent reaches a terminal stop and reports v4 usage\n// with one cheap raw call before wiring the adapter\nconst probe = await agent.generate({ prompt: 'ping' });\nif (probe.error !== undefined) throw new Error(`agent misconfigured: ${String(probe.error)}`);\nif (!['stop', 'length', 'content-filter', 'other'].includes(String(probe.finishReason))) {\n  throw new Error(`agent does not terminate: finishReason=${String(probe.finishReason)}`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  return await adapter.run(req);\n} catch (e) {\n  if (e instanceof Error && e.message === 'cave_vercel_terminal_failure') {\n    // classify with one raw generate: response.error → provider/config issue;\n    // finishReason 'tool-calls' → raise the agent's step/stopWhen budget\n    const diag = await agent.generate({ prompt: 'ping' });\n    log.diag({ error: diag.error, finishReason: diag.finishReason });\n  }\n  throw e;\n}","preventionTips":["Build the ToolLoopAgent with a step/stopWhen budget large enough to always reach a model stop","Validate provider credentials with a cheap call before locked runs","Pin ai@7.0.43 exactly — the adapter enforces that installed version anyway"],"tags":["typescript","vercel-ai-sdk","llm","finish-reason","tool-loop"],"backgroundTag":"llm-finish-reason-error","analyzedSha":"766dce6b1394ebb56a3090748d5a0240a5aefb36","analyzedAt":"2026-08-18T03:14:35.516Z","contentChangedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}