Skip to content

Commit 51ec433

Browse files
feat(think): bundle workers-ai-provider and accept model id strings (#1832)
Make the common model-config case zero-boilerplate: `getModel()` (and the `beforeTurn`/`beforeStep` per-turn/per-step overrides) now accept a model id string resolved through a built-in `workers-ai-provider` instance, so users no longer install/import/wire the provider themselves. - getModel() returns `ThinkModel` (new exported alias for `LanguageModel | ThinkModelId`); `ThinkModelId` gives `@cf/...` autocomplete while accepting any gateway slug string. - Bundle `workers-ai-provider` + `@ai-sdk/openai`/`@ai-sdk/anthropic` wire plugins as deps; `@cf/...` ids hit Workers AI (with sessionAffinity), other `<provider>/<model>` slugs route through AI Gateway. - Add `getAIBinding()` (default `env.AI`) and public `resolveModel(model?)` for side inference calls (summarization/compaction). - Widen `TurnConfig.model` and `StepConfig.model` to `ThinkModel`; resolve string overrides before handing the step to the AI SDK. - Scaffolder + all starters + non-starter examples + docs use string models; drop `workers-ai-provider` from their deps and from THIRD_PARTY_DEPENDENCIES. Co-authored-by: Sunil Pai <18808+threepointone@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 4d30a84 commit 51ec433

46 files changed

Lines changed: 318 additions & 274 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
"@cloudflare/think": minor
3+
---
4+
5+
`getModel()` now accepts a model id string, resolved through a built-in `workers-ai-provider` instance — no separate provider package to install, import, or wire up for the common case.
6+
7+
Think now depends on `workers-ai-provider` (plus the `@ai-sdk/openai` and `@ai-sdk/anthropic` wire-format plugins) directly. When `getModel()` returns a string, Think resolves it off your `AI` binding:
8+
9+
- A `@cf/...` id hits Workers AI directly (with `sessionAffinity` wired in automatically for prefix-cache hits).
10+
- Any other `"<provider>/<model>"` slug — e.g. `"openai/gpt-5.5"`, `"anthropic/claude-sonnet-4-5"`, `"google/gemini-2.5-pro"`, `"xai/grok-4"`, `"groq/..."` — is routed through AI Gateway.
11+
12+
```typescript
13+
export class MyAgent extends Think<Env> {
14+
getModel() {
15+
return "@cf/moonshotai/kimi-k2.7-code"; // or "openai/gpt-5.5"
16+
}
17+
}
18+
```
19+
20+
Returning a fully-constructed AI SDK `LanguageModel` from `getModel()` still works unchanged for any other provider or for full control over provider/gateway options. A new `getAIBinding()` override (default `this.env.AI`) controls which binding the string resolver uses.
21+
22+
Because `getModel()` may now return a bare string, a new public `resolveModel(model?)` method (defaults to resolving `getModel()`) returns a concrete `LanguageModel`. Use it for side inference calls — e.g. summarization/compaction `generateText` — instead of passing `getModel()` straight to the AI SDK.
23+
24+
The per-turn override `TurnConfig.model` (returned from `beforeTurn`) also accepts a `ThinkModel` now, so you can switch models for a turn with a plain string (e.g. a cheaper model for continuations). The per-step override `StepConfig.model` (returned from `beforeStep`) accepts a `ThinkModel` too — Think resolves a string back into a `LanguageModel` before handing the step to the AI SDK.
25+
26+
`getModel()` is typed to return `ThinkModel` (a newly exported alias for `LanguageModel | ThinkModelId`). `ThinkModelId` (also exported) gives editor autocomplete for the Workers AI text-generation catalog (`@cf/...`, derived from the installed `@cloudflare/workers-types`) while still accepting any string — gateway catalog slugs like `"openai/gpt-5.5"` are validated at runtime, since the catalog lives server-side and is not knowable from types.

docs/think/getting-started.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@ npm init -y
2525
Install dependencies:
2626

2727
```sh
28-
npm install @cloudflare/think agents ai @cloudflare/shell zod workers-ai-provider react react-dom
28+
npm install @cloudflare/think agents ai @cloudflare/shell zod react react-dom
2929
npm install -D wrangler @cloudflare/vite-plugin @cloudflare/workers-types @vitejs/plugin-react @tailwindcss/vite tailwindcss typescript vite
3030
```
3131

32+
Think bundles [`workers-ai-provider`](https://www.npmjs.com/package/workers-ai-provider), so you do not need to install or import it for the common case — `getModel()` can return a model id string (see below).
33+
3234
## 2. Configure wrangler
3335

3436
Create `wrangler.jsonc`:
@@ -78,14 +80,14 @@ Create `src/server.ts`:
7880

7981
```typescript
8082
import { Think } from "@cloudflare/think";
81-
import { createWorkersAI } from "workers-ai-provider";
8283
import { routeAgentRequest } from "agents";
8384

8485
export class MyAgent extends Think<Env> {
8586
getModel() {
86-
return createWorkersAI({ binding: this.env.AI })(
87-
"@cf/moonshotai/kimi-k2.7-code"
88-
);
87+
// A string is resolved through Think's built-in workers-ai-provider off the
88+
// `AI` binding. Use a "@cf/..." id for Workers AI, or a "provider/model"
89+
// slug like "openai/gpt-5.5" to route through AI Gateway.
90+
return "@cf/moonshotai/kimi-k2.7-code";
8991
}
9092

9193
getSystemPrompt() {
@@ -202,10 +204,8 @@ Override `configureSession` to give the model writable memory that survives rest
202204

203205
```typescript
204206
export class MyAgent extends Think<Env> {
205-
getModel(): LanguageModel {
206-
return createWorkersAI({ binding: this.env.AI })(
207-
"@cf/moonshotai/kimi-k2.7-code"
208-
);
207+
getModel() {
208+
return "@cf/moonshotai/kimi-k2.7-code";
209209
}
210210

211211
configureSession(session: Session) {
@@ -240,7 +240,7 @@ import { tool } from "ai";
240240
import { z } from "zod";
241241

242242
export class MyAgent extends Think<Env> {
243-
getModel(): LanguageModel {
243+
getModel() {
244244
/* ... */
245245
}
246246
configureSession(session: Session) {
@@ -287,7 +287,7 @@ import type {
287287
} from "@cloudflare/think";
288288

289289
export class MyAgent extends Think<Env> {
290-
getModel(): LanguageModel {
290+
getModel() {
291291
/* ... */
292292
}
293293

docs/think/index.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,23 @@ If you only need a chat-protocol adapter where you own the loop and the `Respons
3232
### Install
3333

3434
```sh
35-
npm install @cloudflare/think agents ai @cloudflare/shell zod workers-ai-provider
35+
npm install @cloudflare/think agents ai @cloudflare/shell zod
3636
```
3737

38+
`workers-ai-provider` is bundled with Think, so the common case needs no extra provider package — `getModel()` can return a model id string.
39+
3840
### Server
3941

4042
```typescript
4143
import { Think } from "@cloudflare/think";
42-
import { createWorkersAI } from "workers-ai-provider";
4344
import { routeAgentRequest } from "agents";
4445

4546
export class MyAgent extends Think<Env> {
4647
getModel() {
47-
return createWorkersAI({ binding: this.env.AI })(
48-
"@cf/moonshotai/kimi-k2.7-code"
49-
);
48+
// Resolved via Think's built-in workers-ai-provider off the `AI` binding.
49+
// Use a "@cf/..." id for Workers AI, or a "provider/model" slug like
50+
// "openai/gpt-5.5" to route through AI Gateway.
51+
return "@cf/moonshotai/kimi-k2.7-code";
5052
}
5153
}
5254

@@ -838,7 +840,8 @@ path.
838840

839841
| Method / Property | Default | Description |
840842
| -------------------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
841-
| `getModel()` | throws | Return the `LanguageModel` to use |
843+
| `getModel()` | throws | Return a model id `string` (resolved via the bundled `workers-ai-provider` off `getAIBinding()` — a `@cf/...` id hits Workers AI, a `"provider/model"` slug routes through AI Gateway) or a `LanguageModel` |
844+
| `getAIBinding()` | `this.env.AI` | Workers AI binding used to resolve string models from `getModel()` |
842845
| `getSystemPrompt()` | `"You are a helpful assistant."` | System prompt (fallback when no context blocks) |
843846
| `getTools()` | `{}` | AI SDK `ToolSet` for the agentic loop |
844847
| `getScheduledTasks()` | `{}` | Code-declared recurring prompts or handlers |
@@ -1087,7 +1090,7 @@ export class MyAgent extends Think<Env> {
10871090
fast: "@cf/moonshotai/kimi-k2.7-code",
10881091
capable: "@cf/meta/llama-4-scout-17b-16e-instruct"
10891092
};
1090-
return createWorkersAI({ binding: this.env.AI })(models[tier]);
1093+
return models[tier];
10911094
}
10921095
}
10931096
```

docs/think/lifecycle-hooks.md

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ export class MyAgent extends Think<Env> {
8181
.onCompaction(
8282
createCompactFunction({
8383
summarize: (prompt) =>
84-
generateText({ model: this.getModel(), prompt }).then((r) => r.text)
84+
generateText({ model: this.resolveModel(), prompt }).then(
85+
(r) => r.text
86+
)
8587
})
8688
)
8789
.compactAfter(100_000)
@@ -109,7 +111,7 @@ beforeTurn(ctx: TurnContext): TurnConfig | void | Promise<TurnConfig | void>
109111
| `system` | `string` | Assembled system prompt (from context blocks or `getSystemPrompt()`) |
110112
| `messages` | `ModelMessage[]` | Assembled model messages (truncated) |
111113
| `tools` | `ToolSet` | Merged tool set (workspace + getTools + session + MCP + client + caller) |
112-
| `model` | `LanguageModel` | The model from `getModel()` |
114+
| `model` | `LanguageModel` | The resolved model (a string from `getModel()` is already resolved here) |
113115
| `continuation` | `boolean` | Whether this is a continuation turn (auto-continue after tool result) |
114116
| `body` | `Record<string, unknown>` | Custom body fields from the client request |
115117

@@ -119,7 +121,7 @@ All fields are optional. Return only what you want to change.
119121

120122
| Field | Type | Description |
121123
| -------------------------- | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
122-
| `model` | `LanguageModel` | Override the model for this turn |
124+
| `model` | `ThinkModel` | Override the model for this turn — a model id string or a `LanguageModel` |
123125
| `system` | `string` | Override the system prompt |
124126
| `messages` | `ModelMessage[]` | Override the assembled messages |
125127
| `tools` | `ToolSet` | Extra tools to merge (additive) |
@@ -314,15 +316,15 @@ onStepFinish(step 1)
314316

315317
`StepConfig<TOOLS>` is the AI SDK's `PrepareStepResult<TOOLS>`. Return only the fields to override for the current step.
316318

317-
| Field | Type | Description |
318-
| ---------------------- | ------------------------------------------------------ | ----------------------------------------------------------- |
319-
| `model` | `LanguageModel` | Override the model for this step |
320-
| `toolChoice` | `ToolChoice<TOOLS>` | Force or disable tool calling for this step |
321-
| `activeTools` | `Array<keyof TOOLS>` | Limit which tools are available for this step |
322-
| `system` | `string \| SystemModelMessage \| SystemModelMessage[]` | Override the system message for this step |
323-
| `messages` | `ModelMessage[]` | Override the full message list for this step |
324-
| `experimental_context` | `unknown` | Override context passed to tool execution from this step on |
325-
| `providerOptions` | `ProviderOptions` | Provider-specific options for this step |
319+
| Field | Type | Description |
320+
| ---------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------- |
321+
| `model` | `ThinkModel` | Override the model for this step — a model id string or a `LanguageModel` |
322+
| `toolChoice` | `ToolChoice<TOOLS>` | Force or disable tool calling for this step |
323+
| `activeTools` | `Array<keyof TOOLS>` | Limit which tools are available for this step |
324+
| `system` | `string \| SystemModelMessage \| SystemModelMessage[]` | Override the system message for this step |
325+
| `messages` | `ModelMessage[]` | Override the full message list for this step |
326+
| `experimental_context` | `unknown` | Override context passed to tool execution from this step on |
327+
| `providerOptions` | `ProviderOptions` | Provider-specific options for this step |
326328

327329
### Examples
328330

examples/agent-skills/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@
1616
"agents": "*",
1717
"ai": "^6.0.202",
1818
"react": "^19.2.7",
19-
"react-dom": "^19.2.7",
20-
"workers-ai-provider": "^3.2.1"
19+
"react-dom": "^19.2.7"
2120
},
2221
"devDependencies": {
2322
"@cloudflare/vite-plugin": "^1.42.3",

examples/agent-skills/src/server.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { callable, routeAgentRequest } from "agents";
22
import { Think, skills } from "@cloudflare/think";
3-
import { createWorkersAI } from "workers-ai-provider";
43
import bundledSkills from "agents:skills";
54

65
type Env = {
@@ -11,10 +10,7 @@ type Env = {
1110

1211
export class SkillsAgent extends Think<Env> {
1312
getModel() {
14-
return createWorkersAI({ binding: this.env.AI })(
15-
"@cf/moonshotai/kimi-k2.7-code",
16-
{ sessionAffinity: this.sessionAffinity }
17-
);
13+
return "@cf/moonshotai/kimi-k2.7-code";
1814
}
1915

2016
getSystemPrompt() {

examples/agents-as-tools/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
"react": "^19.2.7",
2323
"react-dom": "^19.2.7",
2424
"streamdown": "^2.5.0",
25-
"workers-ai-provider": "^3.2.1",
2625
"zod": "^4.4.3"
2726
},
2827
"devDependencies": {

examples/agents-as-tools/src/server.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@
1010
import { callable, routeAgentRequest } from "agents";
1111
import { agentTool } from "agents/agent-tools";
1212
import { Think } from "@cloudflare/think";
13-
import type { LanguageModel, ToolSet } from "ai";
13+
import type { ToolSet } from "ai";
1414
import { tool } from "ai";
15-
import { createWorkersAI } from "workers-ai-provider";
1615
import { z } from "zod";
1716

1817
type ResearchInput = { query: string };
@@ -31,11 +30,8 @@ function inputText(input: unknown): string {
3130
class DemoToolAgent extends Think<Env> {
3231
override chatRecovery = true;
3332

34-
override getModel(): LanguageModel {
35-
const workersai = createWorkersAI({ binding: this.env.AI });
36-
return workersai("@cf/moonshotai/kimi-k2.7-code", {
37-
sessionAffinity: this.sessionAffinity
38-
});
33+
override getModel() {
34+
return "@cf/moonshotai/kimi-k2.7-code";
3935
}
4036

4137
formatAgentToolInput(input: unknown) {
@@ -176,11 +172,8 @@ export class Planner extends DemoToolAgent {
176172
export class Assistant extends Think<Env> {
177173
override maxConcurrentAgentTools = 4;
178174

179-
override getModel(): LanguageModel {
180-
const workersai = createWorkersAI({ binding: this.env.AI });
181-
return workersai("@cf/moonshotai/kimi-k2.7-code", {
182-
sessionAffinity: this.sessionAffinity
183-
});
175+
override getModel() {
176+
return "@cf/moonshotai/kimi-k2.7-code";
184177
}
185178

186179
override getSystemPrompt(): string {

examples/assistant/agents/assistant/agent.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import { callable } from "agents";
22
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3-
import { createWorkersAI } from "workers-ai-provider";
43
import { Think, Workspace } from "@cloudflare/think";
54
import type { ThinkScheduledTasks } from "@cloudflare/think";
6-
import type { LanguageModel } from "ai";
75
import type { FileInfo, WorkspaceChangeEvent } from "@cloudflare/shell";
86
import { nanoid } from "nanoid";
97
import { MyAssistant } from "./agents/my-assistant/agent";
@@ -34,11 +32,8 @@ export class AssistantDirectory extends Think<Env, DirectoryState> {
3432
// (see `getScheduledTasks`) and leaves room for top-level agentic work
3533
// later. `getModel()` is a stub for that future use — nothing in the
3634
// accumulator role calls it.
37-
override getModel(): LanguageModel {
38-
return createWorkersAI({ binding: this.env.AI })(
39-
"@cf/moonshotai/kimi-k2.7-code",
40-
{ sessionAffinity: this.sessionAffinity }
41-
);
35+
override getModel() {
36+
return "@cf/moonshotai/kimi-k2.7-code";
4237
}
4338

4439
/**

0 commit comments

Comments
 (0)