Skip to content

[codemode] 0.4.x main entry statically imports the optional ai peer, breaking bundlers when ai is absent #1779

@aribn

Description

@aribn

Describe the bug

@cloudflare/codemode@0.4.x's main entry (dist/index.js) has a static, top-level import { tool } from "ai", while ai is declared as an optional peer dependency (peerDependenciesMeta.ai.optional = true). Because DynamicWorkerExecutor and normalizeCode are exported only from that same entry module, importing the core executor pulls in the static ai import. When ai is not installed, bundlers that honor the optional-peer contract (Vite + @cloudflare/vite-plugin, in our case) replace ai with an empty stub and the named import tool fails to resolve — breaking the build of code that never touches the AI-SDK surface. The executor is functionally independent of the Vercel AI SDK, so this looks like a packaging issue (a static import of an optional peer in the shared entry) rather than an intended hard requirement.

To Reproduce

Steps to reproduce the behavior:

  1. In a Cloudflare Workers project built with Vite + @cloudflare/vite-plugin, do not install ai.
  2. Import only the core executor: import { DynamicWorkerExecutor, normalizeCode } from "@cloudflare/codemode";
  3. Run the build (vite build).
  4. See the error:
    [MISSING_EXPORT] "tool" is not exported by "__vite-optional-peer-dep:ai:@cloudflare/codemode". 
    node_modules/@cloudflare/codemode/dist/index.js:6:10
    

Expected behavior

Importing DynamicWorkerExecutor / normalizeCode (the non-AI-SDK core) should not require ai to be installed, consistent with ai being declared an optional peer.

Screenshots

N/A — build-time bundler error, full text included under "To Reproduce".

Version:

@cloudflare/codemode@0.4.1 (also reproduces on 0.4.0). Bundler: vite@8 + @cloudflare/vite-plugin@1.42 (Cloudflare Workers build).

Additional context

Root cause, in dist/index.js:

  • line 6: import { tool } from "ai"; (static, top-level)
  • tool is only used in the connector/runtime helpers (tool(...) at ~L1712 and a tool(options) { ... } method at ~L1845), i.e. createCodemodeRuntime / CodemodeConnector — not in DynamicWorkerExecutor (defined ~L185) or normalizeCode.
  • All of these are exported from the same index.js (~L1927), so the executor exports can't be reached without the ai-importing module being evaluated/linked.

Tree-shaking doesn't help: named-binding resolution against the optional-peer stub happens at link time, before unused-export elimination.

Suggested fix — make the ai dependency genuinely lazy so the optional peer is optional for bundlers:

  • Use a dynamic await import("ai") inside the connector/runtime helpers that call tool(...), or
  • Move those AI-SDK-coupled helpers entirely behind the existing ./ai subpath (which already imports ai) and keep DynamicWorkerExecutor / normalizeCode on a main entry that has no ai import.

Either keeps import { DynamicWorkerExecutor } from "@cloudflare/codemode" working without ai installed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions