Compatibility

Source: COMPATIBILITY.md at the repo root.

tapedeck operates at the Vercel AI SDK's wrapLanguageModel middleware layer. The structural ceiling on the project's lifetime is that layer's request/response shape, so this file is the public, dated record of what tapedeck is tested against.

Language-model spec versions

Both spec versions are typed as of 0.4.0, not merely supported at runtime. ai@6 takes a spec v3 middleware and ai@7 takes spec v4; the two concrete types are mutually unassignable, so before 0.4.0 an ai@7 consumer had to write as unknown as LanguageModelMiddleware at the call site. cassetteMiddleware now returns a structurally typed TapedeckMiddleware: it declares only the fields tapedeck reads (prompt, tools, the sampling params, provider, modelId) and stays generic in the result type, so one object is assignable to both. specificationVersion stays 'v3' because v4 hosts accept any string while v3 hosts accept only 'v3'.

Both provider majors are dev dependencies, and test/types.test-d.ts asserts assignability to both spec surfaces plus to wrapLanguageModel from whichever ai major the CI matrix leg installed. A spec v5 would show up as a red type test, not as a silent consumer-side cast.

Tested versions

SDK (ai)Date testedtapedeckStatusNotes
7.0.582026-08-110.4.0✅ passSpec v4 typed via TapedeckMiddleware; the as unknown as cast at wrapLanguageModel is gone.
6.0.2562026-08-110.4.0✅ passSpec v3 typed via TapedeckMiddleware; type tests green.
7.0.582026-08-030.3.0✅ passWeekly cron; peer range widened to <8 in 0.3.1.
7.0.372026-07-270.3.0✅ passWeekly cron.
6.0.02026-06-100.3.0✅ passMulti-interaction named cassettes (v2 format); v1 cassettes replay as-is.
6.0.02026-06-100.2.0✅ passSame spec surface as the launch row. Hash digests and cassette format unchanged.
6.0.02026-06-100.1.0✅ passLaunch row. Model spec v3: doGenerate returns content[]; doStream yields text-delta / tool-call parts.

Edge runtimes (Cloudflare Workers, etc.)

As of 0.2.0 the core import graph is edge-safe: hashing uses WebCrypto, the default filesystem store imports node:fs lazily (pass memoryCassetteStore() or a KV/R2-backed store and it never loads), and the one static Node builtin left is node:async_hooks — provided by Cloudflare Workers under the nodejs_compat flag.

A deployed-Worker smoke test is still TODO — treat Workers support as designed-for, not yet CI-verified. The CLI is Node-only by design.

Pinned peer range

{ "peerDependencies": { "ai": ">=6.0.0 <8" } }

A new SDK major joins the peer range only after the weekly cron proves it green: ai@7 passed at 7.0.37 and 7.0.58, so 0.3.1 widened the pin to <8. What forces a tapedeck major is a language-model spec shape change deep enough to break the fields tapedeck reads, not an SDK major that renumbers the spec. The cassette version field (tapedeck@<pkg>) and the recorded modelProvider / modelId make a format boundary loud at replay time.

What "pass" means

A row is ✅ pass when:

  1. pnpm typecheck succeeds against the SDK version.
  2. pnpm test is green (the suite uses MockLanguageModelV3 — no live API calls), including the *.test-d.ts type assertions.
  3. A round-trip holds: a cassette recorded under record replays byte-identical stream parts under replay, and a changed prompt or tool schema misses.

A row is ⚠️ partial when the suite passes but a known shape change required a documented workaround (linked in Notes).

A row is ❌ fail when the suite breaks against a new SDK version.

The spec shape is the boundary, not the SDK major
An ai major only forces a tapedeck major if it changes the request/response shape cassettes are serialized against, which is why ai@7 joined the peer range without one. The cassette version field (tapedeck@<pkg>) together with the recorded modelProvider / modelId makes that boundary loud at replay time: a cassette written under an incompatible version fails rather than replaying silently.