Version
packages/playwright-cloudflare/examples/stagehand @ 7c31261.
Steps to reproduce
The example typechecks in this repo only because it commits a worker-configuration.d.ts generated back in September:
|
// Runtime types generated with workerd@1.20250902.0 2025-09-01 enable_nodejs_fs_module,nodejs_compat |
Regenerating the types breaks it:
git clone --depth 1 https://github.com/cloudflare/playwright
cd playwright/packages/playwright-cloudflare/examples/stagehand
npm install
npx tsc --noEmit # clean, using the committed worker-configuration.d.ts
npx wrangler types # regenerate against the installed wrangler
npx tsc --noEmit # fails
This matters outside the repo because the Browser Run Stagehand docs tell users to "Copy workersAIClient.ts to your project", where they generate their own types instead of inheriting the committed one.
Expected behavior
The example typechecks against the Workers AI types that its own pinned wrangler generates:
Actual behavior
Two different failures depending on the wrangler version, both originating in the same call:
|
this.options?.logger?.({ category: "workersai", message: "thinking..." }); |
|
const { response } = await this.binding.run(this.modelName as keyof AiModels, { |
|
messages: options.messages, |
|
// @ts-ignore |
|
tools: options.tools, |
|
response_format: schema ? { |
|
type: "json_schema", |
|
json_schema: zodToJsonSchema(schema), |
|
} : undefined, |
|
temperature: 0, |
|
}, this.options) as AiTextGenerationOutput; |
| wrangler |
generated runtime |
error |
4.57.0 (resolved from the ^4.57.0 above) |
workerd@1.20260103.0 |
TS2322 at messages: Type 'ChatMessage[]' is not assignable to type '{ role: string; content: string; }[]' |
4.125.0 (current) |
workerd@1.20260820.x |
TS2769 at the model argument: Argument of type 'keyof AiModels' is not assignable to parameter of type 'never' |
The root cause is this.modelName as keyof AiModels on line 32. Widening the model id from a literal to the full union means no known-model overload can match, because inputs then has to satisfy every model's input type at once. Resolution falls through to the unknown-model overload, whose Model extends keyof AiModelList ? never : Model constraint is built to reject known keys, which is where the never comes from. That constraint is deliberate and documented in the current generated types:
// Unknown model (fallback).
//
// The `Exclude<..., keyof AiModelList>` constraint forces TypeScript to
// route any model name that is a literal key of `AiModelList` to one of
// the known-model overloads above ...
run<Model extends string>(model: Model extends keyof AiModelList ? never : Model, ...)
That overload does not exist in the committed September types, which is why the problem is invisible here.
The existing // @ts-ignore on line 34 does not help either: it applies to tools on line 35, while the overload-resolution error is reported on the model argument.
Additional context
This patch typechecks cleanly in all three combinations I tried: the committed September types, wrangler 4.57.0 regenerated types, and wrangler 4.125.0 regenerated types. Keeping the literal lets the known-model overload resolve, and one explicit cast covers the genuine mismatch (Stagehand's message and tool shapes are structurally looser than the generated input types) rather than a directive pointed at the wrong line.
-const modelId = "@cf/meta/llama-3.3-70b-instruct-fp8-fast";
+const modelId = "@cf/meta/llama-3.3-70b-instruct-fp8-fast" as const;
+
+type ModelInputs = AiModels[typeof modelId]["inputs"];
@@
- const { response } = await this.binding.run(this.modelName as keyof AiModels, {
+ // Stagehand's message and tool shapes are structurally looser than the
+ // generated Workers AI input types, but the model accepts them at runtime.
+ const inputs = {
messages: options.messages,
- // @ts-ignore
tools: options.tools,
response_format: schema ? {
type: "json_schema",
json_schema: zodToJsonSchema(schema),
} : undefined,
temperature: 0,
- }, this.options) as AiTextGenerationOutput;
+ } as unknown as ModelInputs;
+
+ const { response } = await this.binding.run(modelId, inputs, this.options) as AiTextGenerationOutput;
Happy to open a PR with this if it is useful.
Separately: regenerating worker-configuration.d.ts in CI, or typechecking the examples against current types, would catch this class of drift before it reaches anyone copying the file.
Environment
Node.js v24
npm, Linux x64 (WSL2)
wrangler 4.57.0 (from this example's lockfile) and 4.125.0
typescript 5.8.3 (from this example) and 7.0.2
Version
packages/playwright-cloudflare/examples/stagehand@7c31261.Steps to reproduce
The example typechecks in this repo only because it commits a
worker-configuration.d.tsgenerated back in September:playwright/packages/playwright-cloudflare/examples/stagehand/worker-configuration.d.ts
Line 3 in 7c31261
Regenerating the types breaks it:
This matters outside the repo because the Browser Run Stagehand docs tell users to "Copy
workersAIClient.tsto your project", where they generate their own types instead of inheriting the committed one.Expected behavior
The example typechecks against the Workers AI types that its own pinned
wranglergenerates:playwright/packages/playwright-cloudflare/examples/stagehand/package.json
Line 22 in 7c31261
Actual behavior
Two different failures depending on the wrangler version, both originating in the same call:
playwright/packages/playwright-cloudflare/examples/stagehand/src/worker/workersAIClient.ts
Lines 31 to 41 in 7c31261
4.57.0(resolved from the^4.57.0above)workerd@1.20260103.0TS2322atmessages:Type 'ChatMessage[]' is not assignable to type '{ role: string; content: string; }[]'4.125.0(current)workerd@1.20260820.xTS2769at the model argument:Argument of type 'keyof AiModels' is not assignable to parameter of type 'never'The root cause is
this.modelName as keyof AiModelson line 32. Widening the model id from a literal to the full union means no known-model overload can match, becauseinputsthen has to satisfy every model's input type at once. Resolution falls through to the unknown-model overload, whoseModel extends keyof AiModelList ? never : Modelconstraint is built to reject known keys, which is where thenevercomes from. That constraint is deliberate and documented in the current generated types:That overload does not exist in the committed September types, which is why the problem is invisible here.
The existing
// @ts-ignoreon line 34 does not help either: it applies totoolson line 35, while the overload-resolution error is reported on the model argument.Additional context
This patch typechecks cleanly in all three combinations I tried: the committed September types, wrangler 4.57.0 regenerated types, and wrangler 4.125.0 regenerated types. Keeping the literal lets the known-model overload resolve, and one explicit cast covers the genuine mismatch (Stagehand's message and tool shapes are structurally looser than the generated input types) rather than a directive pointed at the wrong line.
Happy to open a PR with this if it is useful.
Separately: regenerating
worker-configuration.d.tsin CI, or typechecking the examples against current types, would catch this class of drift before it reaches anyone copying the file.Environment