{"record":{"id":"dcffbef76685c0df","repo":"vercel/ai","slug":"the-model-provider-model-model-modelid-doe","errorCode":null,"errorMessage":"The ${model.provider} model \"${model.modelId}\" does not support batch processing.","messagePattern":"The (.+?) model \"(.+?)\" does not support batch processing\\.","errorType":"exception","errorClass":"UnsupportedFunctionalityError","httpStatus":null,"severity":"error","filePath":"packages/ai/src/batch/batch.ts","lineNumber":224,"sourceCode":"\n      await stream.pipeTo(transform.writable, {\n        signal: operationAbortSignal,\n      });\n    } catch (error) {\n      await transform.writable.abort(wrapGatewayError(error)).catch(() => {});\n    }\n  })();\n\n  return asAsyncIterableStream(transform.readable);\n}\n\nfunction resolveBatchLanguageModel(\n  modelArg: StartTextBatchOptions['model'],\n): BatchLanguageModelV4 {\n  const model = resolveLanguageModel(modelArg);\n\n  if (!isBatchLanguageModel(model)) {\n    throw new UnsupportedFunctionalityError({\n      functionality: 'batch processing',\n      message: `The ${model.provider} model \"${model.modelId}\" does not support batch processing.`,\n    });\n  }\n\n  return model;\n}\n\nfunction isBatchLanguageModel(\n  model: LanguageModelV4,\n): model is BatchLanguageModelV4 {\n  const candidate = model as Partial<BatchLanguageModelV4>;\n  return (\n    typeof candidate.experimental_doStartBatch === 'function' &&\n    typeof candidate.experimental_doGetBatchStatus === 'function' &&\n    typeof candidate.experimental_doGetBatchResults === 'function'\n  );\n}","sourceCodeStart":206,"sourceCodeEnd":242,"githubUrl":"https://github.com/vercel/ai/blob/69428b1f8b037e4d118fb4853428d5c4e620493c/packages/ai/src/batch/batch.ts#L206-L242","documentation":"The ACP harness fingerprints the active host tool catalog (SHA-256 of the JSON-serialized HarnessV1ToolSpec array) and persists it with the turn. During a lossy rerun (a resumed turn whose full protocol state no longer exists), assertRecoveryToolCatalog compares the persisted fingerprint against the currently configured tools. A mismatch means the rerun would execute against a different tool surface than the original turn, so the harness refuses to proceed rather than replaying with wrong tools.","triggerScenarios":"Resuming/rerunning a previous ACP turn after the `tools` array passed to the harness (or the resolved builtin + MCP tool catalog) changed between the original turn and the rerun; adding, removing, renaming, or reordering host tools; changing tool parameter schemas; switching mcpServers so the effective catalog differs.","commonSituations":"Deploying a new app version that added or removed a tool while a session was in-flight; toggling MCP servers via env-driven config; a tool library upgrade that changed a tool's name or parameters; reordering tools and assuming order is irrelevant.","solutions":["Restore the exact `tools` array (names, descriptions, parameter schemas, order) that was active when the original turn ran.","If the tool change is intentional, start a new session/turn instead of rerunning the old one.","Diff the persisted tool catalog from your lifecycle/turn state against the current HarnessV1ToolSpec[] to identify what changed.","Pin tool definitions (including MCP server versions) so they are stable across deploys for the lifetime of a session."],"exampleFix":"// before\nclassifierAgent({ tools: [readFileTool, writeFileTool, grepTool] }) // original turn\n// rerun after removing grepTool\nagent({ tools: [readFileTool, writeFileTool] }) // throws\n\n// after\nagent({ tools: [readFileTool, writeFileTool, grepTool] }) // identical catalog","handlingStrategy":"validation","validationCode":"import { createHash } from 'node:crypto';\nconst fingerprint = (tools: unknown) =>\n  createHash('sha256').update(JSON.stringify(tools)).digest('hex');\n\n// before rerunning, compare against the fingerprint persisted with the turn\nif (fingerprint(currentTools) !== persistedToolCatalogFingerprint) {\n  throw new Error('Tool catalog changed since the original turn; start a new session.');\n}","typeGuard":null,"tryCatchPattern":"try {\n  await agent.rerunTurn({ turnId });\n} catch (error) {\n  if (error instanceof Error && error.message.includes('same active host tool catalog')) {\n    // tool catalog drifted: recreate the session with the current catalog\n    session = await createSession({ tools: currentTools });\n  } else {\n    throw error;\n  }\n}","preventionTips":["Treat the tools array as immutable for the lifetime of a session; version it alongside session state.","Pin MCP server versions and tool library versions in lockfile/config.","When tools must change, deliberately create a new session instead of rerunning old turns."],"tags":["acp","tools","session-recovery","fingerprint-mismatch"],"backgroundTag":"tool-catalog-mismatch","analyzedSha":"69428b1f8b037e4d118fb4853428d5c4e620493c","analyzedAt":"2026-08-30T12:32:21.016Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}