Skip to content

examples/think: the remote Workers AI binding fails from inside the Assistant Durable Object under wrangler dev, so no chat turn completes #63

Description

@3x3xX3N0N

Describe the bug

examples/think cannot complete a single chat turn under wrangler dev. Every model call fails with an opaque internal error; reference = <id>, surfaced to the terminal client as:

Error: internal error; reference = kl1uqro0lemqfdd6nga1j8t7
    at async Object.fetch (…/miniflare/dist/src/workers/core/entry.worker.js:4723:22)

The cause is the calling context, not the request: the remote Workers AI binding does not work from inside a Durable Object under wrangler dev. The example's model call lives in the Assistant DO (examples/think/src/agent.ts:125-127):

override getModel() {
  return createWorkersAI({ binding: this.env.AI })(MODEL_ID);
}

and examples/think/wrangler.jsonc:13 declares that binding as remote:

"ai": { "binding": "AI", "remote": true },

Measured from inside that same DO, in the same turn:

call result
fetch("https://example.com/") 200
fetch("https://api.cloudflare.com/client/v4") 404 (i.e. reached the server)
env.AI.run(…) internal error; reference = …

So general outbound HTTP from the DO is fine; only the AI binding fails there. The identical env.AI.run call succeeds from a plain Worker fetch handler in the same project, same account, same model.

I bisected the request to rule out payload causes. Every one of these still failed from the DO, while all of them succeeded from a plain Worker:

  • with tools and without tools (tools: undefined)
  • stream: true and stream: false
  • 1, 3, 6, 9 tools, and the example's real tool schemas
  • tool_choice: "auto" present and absent
  • the real 4-message history, and a single {role:"user",content:"Say OK."}
  • a second model (@cf/qwen/qwen2.5-coder-32b-instruct)

The last two are the decisive ones: a no-tools, one-message, different-model request still fails from the DO. Note Assistant is both the agent DO and the container class (examples/think/wrangler.jsonc:17, and the withWorkspaceContainer mixin at examples/think/src/agent.ts:65), so a container-related egress path is a plausible suspect, but I did not confirm that — plain fetch from the same DO is unaffected.

This may well be a wrangler/miniflare limitation rather than a bug in this repo. Filing it here because the effect is that the example is unusable locally as shipped, and the workaround belongs with the example either way.

Expected behavior

npm run dev + npm run chat in examples/think completes a chat turn, as examples/think/README.md describes. Failing that, the example documents the limitation and uses a code path that works locally.

Steps to reproduce

  1. npm install at the repo root; wrangler login.
  2. cd examples/think && npx wrangler dev (Linux or WSL — containers are unsupported on Windows).
  3. npm run chat, then send any message.
  4. Observed: Error: internal error; reference = …. Expected: a reply.

To isolate it from the agent entirely, point a plain Worker at the same binding:

// wrangler.jsonc
{ "name": "probe", "main": "index.js", "compatibility_date": "2026-05-26",
  "ai": { "binding": "AI", "remote": true } }
export default {
  async fetch(request, env) {
    const r = await env.AI.run("@cf/meta/llama-3.3-70b-instruct-fp8-fast", {
      messages: [{ role: "user", content: "Say OK." }], max_tokens: 16,
    });
    return new Response(JSON.stringify(r));
  },
};

npx wrangler dev on that returns a normal completion. The same call from the Assistant DO fails.

Separate blocker worth noting, since it is hit first and masks this one: MODEL_ID at examples/think/src/agent.ts:52 is @cf/zai-org/glm-5.2, which returns AiError 5035: This model requires a Workers Paid plan. On a free account the example fails there before reaching the DO problem. @cf/meta/llama-3.3-70b-instruct-fp8-fast, @cf/qwen/qwen2.5-coder-32b-instruct, and @cf/mistralai/mistral-small-3.1-24b-instruct all work on the free plan and support tool calling; @cf/meta/llama-3.1-8b-instruct is deprecated as of 2026-05-30.

Proposed fix

workers-ai-provider supports a credentials mode that goes over plain fetch instead of the binding, and plain fetch works from the DO. Falling back to it only when credentials are present keeps the binding for deployed workers, where it is fine:

override getModel() {
  const accountId = this.env.CF_ACCOUNT_ID;
  const apiKey = this.env.CF_AI_TOKEN;
  if (accountId && apiKey) {
    return createWorkersAI({ accountId, apiKey })(MODEL_ID);
  }
  return createWorkersAI({ binding: this.env.AI })(MODEL_ID);
}

With CF_ACCOUNT_ID and CF_AI_TOKEN (an API token scoped to Account → Workers AI → Read) in examples/think/.dev.vars, the example completes chat turns locally, including container-backend exec. That is a workaround rather than a fix — if the binding is expected to work from a DO under wrangler dev, this is really a wrangler issue and I am happy to re-file it there.

Choosing a default model that exists on the free plan would also let the example run without a paid account.

Environment

  • cloudflare/computer at 76d9e75 (current main)
  • Ubuntu 24.04 under WSL2, Docker 29.1.3, wrangler 4.115.0, Node 22.22.1
  • Workers free plan; examples/think with both the worker-shell and container backends registered

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions