@cloudflare/think 0.0.0 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +241 -0
- package/dist/classPrivateFieldSet2-COLddhya.js +27 -0
- package/dist/classPrivateMethodInitSpec-CdQXQy1O.js +7 -0
- package/dist/extensions/index.d.ts +20 -0
- package/dist/extensions/index.js +62 -0
- package/dist/extensions/index.js.map +1 -0
- package/dist/index-BlcvIdWK.d.ts +171 -0
- package/dist/index-C4OTSwUW.d.ts +193 -0
- package/dist/manager-DIV0gQf3.js +214 -0
- package/dist/manager-DIV0gQf3.js.map +1 -0
- package/dist/message-builder.d.ts +51 -0
- package/dist/message-builder.js +217 -0
- package/dist/message-builder.js.map +1 -0
- package/dist/session/index.d.ts +22 -0
- package/dist/session/index.js +2 -0
- package/dist/session-C6ZU_1zM.js +507 -0
- package/dist/session-C6ZU_1zM.js.map +1 -0
- package/dist/think.d.ts +315 -0
- package/dist/think.js +701 -0
- package/dist/think.js.map +1 -0
- package/dist/tools/execute.d.ts +105 -0
- package/dist/tools/execute.js +64 -0
- package/dist/tools/execute.js.map +1 -0
- package/dist/tools/extensions.d.ts +67 -0
- package/dist/tools/extensions.js +85 -0
- package/dist/tools/extensions.js.map +1 -0
- package/dist/tools/workspace.d.ts +303 -0
- package/dist/tools/workspace.js +398 -0
- package/dist/tools/workspace.js.map +1 -0
- package/dist/transport.d.ts +69 -0
- package/dist/transport.js +166 -0
- package/dist/transport.js.map +1 -0
- package/package.json +83 -9
package/README.md
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# @cloudflare/think
|
|
2
|
+
|
|
3
|
+
An Agent base class for AI assistants on Cloudflare Workers. Handles the full chat lifecycle — session management, agentic loop, streaming, persistence, workspace tools, and extensions — all backed by Durable Object SQLite.
|
|
4
|
+
|
|
5
|
+
Works as both a **top-level agent** (WebSocket chat protocol for browser clients) and a **sub-agent** (RPC streaming from a parent agent).
|
|
6
|
+
|
|
7
|
+
> **Experimental** — requires the `"experimental"` compatibility flag.
|
|
8
|
+
|
|
9
|
+
## Quick start
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { Think } from "@cloudflare/think";
|
|
13
|
+
import { createWorkersAI } from "workers-ai-provider";
|
|
14
|
+
import { createWorkspaceTools } from "@cloudflare/think/tools/workspace";
|
|
15
|
+
import { Workspace } from "agents/experimental/workspace";
|
|
16
|
+
|
|
17
|
+
export class ChatSession extends Think<Env> {
|
|
18
|
+
workspace = new Workspace(this);
|
|
19
|
+
|
|
20
|
+
getModel() {
|
|
21
|
+
return createWorkersAI({ binding: this.env.AI })(
|
|
22
|
+
"@cf/meta/llama-3.3-70b-instruct-fp8-fast"
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
getSystemPrompt() {
|
|
27
|
+
return "You are a helpful coding assistant.";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
getTools() {
|
|
31
|
+
return createWorkspaceTools(this.workspace);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
That's it. `Think` handles the WebSocket chat protocol, session persistence, the agentic loop, message sanitization, and streaming. Connect from the browser with `useAgentChat` or `useChat` + `AgentChatTransport`.
|
|
37
|
+
|
|
38
|
+
## Exports
|
|
39
|
+
|
|
40
|
+
| Export | Description |
|
|
41
|
+
| ------------------------------------ | --------------------------------------------------------------- |
|
|
42
|
+
| `@cloudflare/think` | `Think` — the main class, plus types |
|
|
43
|
+
| `@cloudflare/think/session` | `SessionManager` — conversation persistence with branching |
|
|
44
|
+
| `@cloudflare/think/tools/workspace` | `createWorkspaceTools()` — file operation tools |
|
|
45
|
+
| `@cloudflare/think/tools/execute` | `createExecuteTool()` — sandboxed code execution via codemode |
|
|
46
|
+
| `@cloudflare/think/tools/extensions` | `createExtensionTools()` — LLM-driven extension loading |
|
|
47
|
+
| `@cloudflare/think/extensions` | `ExtensionManager`, `HostBridgeLoopback` — extension runtime |
|
|
48
|
+
| `@cloudflare/think/transport` | `AgentChatTransport` — bridges `useChat` with Agent WebSocket |
|
|
49
|
+
| `@cloudflare/think/message-builder` | `applyChunkToParts()` — reconstruct UIMessage parts from chunks |
|
|
50
|
+
|
|
51
|
+
## Think
|
|
52
|
+
|
|
53
|
+
### Override points
|
|
54
|
+
|
|
55
|
+
| Method | Default | Description |
|
|
56
|
+
| ------------------------- | -------------------------------- | ------------------------------------- |
|
|
57
|
+
| `getModel()` | throws | Return the `LanguageModel` to use |
|
|
58
|
+
| `getSystemPrompt()` | `"You are a helpful assistant."` | System prompt |
|
|
59
|
+
| `getTools()` | `{}` | AI SDK `ToolSet` for the agentic loop |
|
|
60
|
+
| `getMaxSteps()` | `10` | Max tool-call rounds per turn |
|
|
61
|
+
| `assembleContext()` | prune older tool calls | Customize what's sent to the LLM |
|
|
62
|
+
| `onChatMessage(options?)` | `streamText(...)` | Full control over inference |
|
|
63
|
+
| `onChatError(error)` | passthrough | Customize error handling |
|
|
64
|
+
| `getWorkspace()` | `null` | Workspace for extension host bridge |
|
|
65
|
+
|
|
66
|
+
### Session management
|
|
67
|
+
|
|
68
|
+
Think manages multiple named sessions per agent instance. Sessions are created automatically on the first chat message, or explicitly:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
session.createSession("research");
|
|
72
|
+
session.switchSession(sessionId);
|
|
73
|
+
session.getSessions(); // Session[]
|
|
74
|
+
session.deleteSession(id);
|
|
75
|
+
session.renameSession(id, "new name");
|
|
76
|
+
session.getCurrentSessionId();
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Sub-agent streaming via RPC
|
|
80
|
+
|
|
81
|
+
When used as a sub-agent, the `chat()` method runs a full turn and streams events via a callback:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
// StreamCallback — implement as an RpcTarget in the parent
|
|
85
|
+
interface StreamCallback {
|
|
86
|
+
onEvent(json: string): void | Promise<void>;
|
|
87
|
+
onDone(): void | Promise<void>;
|
|
88
|
+
onError?(error: string): void | Promise<void>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const session = await this.subAgent(ChatSession, "agent-abc");
|
|
92
|
+
await session.chat("Summarize the project", relay, {
|
|
93
|
+
tools: extraTools,
|
|
94
|
+
signal: abortController.signal
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Dynamic configuration
|
|
99
|
+
|
|
100
|
+
Think accepts a `Config` type parameter for per-instance configuration persisted in SQLite:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
type MyConfig = { modelTier: "fast" | "capable"; systemPrompt: string };
|
|
104
|
+
|
|
105
|
+
export class ChatSession extends Think<Env, MyConfig> {
|
|
106
|
+
getModel() {
|
|
107
|
+
const tier = this.getConfig()?.modelTier ?? "fast";
|
|
108
|
+
return createWorkersAI({ binding: this.env.AI })(MODEL_IDS[tier]);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// From the parent:
|
|
113
|
+
const session = await this.subAgent(ChatSession, "agent-abc");
|
|
114
|
+
await session.configure({ modelTier: "capable", systemPrompt: "..." });
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Production features
|
|
118
|
+
|
|
119
|
+
- **WebSocket protocol** — wire-compatible with `useAgentChat` / `useChat`
|
|
120
|
+
- **Multi-session** — create, switch, list, delete, rename conversations
|
|
121
|
+
- **Abort/cancel** — pass an `AbortSignal` or send a cancel message
|
|
122
|
+
- **Partial persistence** — on error, the partial assistant message is saved
|
|
123
|
+
- **Message sanitization** — strips ephemeral provider metadata before storage
|
|
124
|
+
- **Row size enforcement** — compacts tool outputs exceeding 1.8MB
|
|
125
|
+
- **Incremental persistence** — skips SQL writes for unchanged messages
|
|
126
|
+
- **Storage bounds** — set `maxPersistedMessages` to cap stored history
|
|
127
|
+
|
|
128
|
+
## SessionManager
|
|
129
|
+
|
|
130
|
+
Persistent conversation storage with tree-structured messages (branching) and compaction. Used internally by Think, but also usable standalone.
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import { SessionManager } from "@cloudflare/think/session";
|
|
134
|
+
|
|
135
|
+
const sessions = new SessionManager(agent);
|
|
136
|
+
const session = sessions.create("my-chat");
|
|
137
|
+
sessions.append(session.id, userMessage);
|
|
138
|
+
const history = sessions.getHistory(session.id); // UIMessage[]
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Also exports truncation utilities (`truncateHead`, `truncateTail`, `truncateMiddle`, `truncateLines`) for managing large tool outputs.
|
|
142
|
+
|
|
143
|
+
## Workspace tools
|
|
144
|
+
|
|
145
|
+
File operation tools backed by the Agents SDK `Workspace`:
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import { createWorkspaceTools } from "@cloudflare/think/tools/workspace";
|
|
149
|
+
|
|
150
|
+
const tools = createWorkspaceTools(this.workspace);
|
|
151
|
+
// Tools: read, write, edit, list, find, grep, delete
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Each tool is an AI SDK `tool()` with Zod schemas. The underlying operations are abstracted behind interfaces (`ReadOperations`, `WriteOperations`, etc.) so you can create tools backed by custom storage.
|
|
155
|
+
|
|
156
|
+
## Code execution tool
|
|
157
|
+
|
|
158
|
+
Let the LLM write and run JavaScript in a sandboxed Worker:
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
import { createExecuteTool } from "@cloudflare/think/tools/execute";
|
|
162
|
+
|
|
163
|
+
getTools() {
|
|
164
|
+
const wsTools = createWorkspaceTools(this.workspace);
|
|
165
|
+
return {
|
|
166
|
+
...wsTools,
|
|
167
|
+
execute: createExecuteTool({ tools: wsTools, loader: this.env.LOADER })
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Requires `@cloudflare/codemode` and a `worker_loaders` binding in `wrangler.jsonc`.
|
|
173
|
+
|
|
174
|
+
## Extensions
|
|
175
|
+
|
|
176
|
+
Dynamic tool loading at runtime. The LLM can write extension source code, load it as a sandboxed Worker, and use the new tools on the next turn.
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
import { ExtensionManager } from "@cloudflare/think/extensions";
|
|
180
|
+
import { createExtensionTools } from "@cloudflare/think/tools/extensions";
|
|
181
|
+
|
|
182
|
+
const extensions = new ExtensionManager({
|
|
183
|
+
loader: this.env.LOADER,
|
|
184
|
+
workspace: this.workspace
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
getTools() {
|
|
188
|
+
return {
|
|
189
|
+
...createWorkspaceTools(this.workspace),
|
|
190
|
+
...createExtensionTools({ manager: extensions }),
|
|
191
|
+
...extensions.getTools()
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Extensions get permission-gated workspace access via `HostBridgeLoopback`. Re-export it from your worker entry point:
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
export { HostBridgeLoopback } from "@cloudflare/think/extensions";
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Chat transport
|
|
203
|
+
|
|
204
|
+
Client-side transport that bridges `useChat` with Agent WebSocket streaming:
|
|
205
|
+
|
|
206
|
+
```tsx
|
|
207
|
+
import { AgentChatTransport } from "@cloudflare/think/transport";
|
|
208
|
+
import { useAgent } from "agents/react";
|
|
209
|
+
import { useChat } from "@ai-sdk/react";
|
|
210
|
+
|
|
211
|
+
const agent = useAgent({ agent: "ChatSession" });
|
|
212
|
+
const transport = useMemo(() => new AgentChatTransport(agent), [agent]);
|
|
213
|
+
const { messages, sendMessage, status } = useChat({ transport });
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Options: `sendMethod` (RPC method name, default `"sendMessage"`), `resumeTimeout` (ms, default `500`). Call `transport.detach()` before switching agents.
|
|
217
|
+
|
|
218
|
+
## Message builder
|
|
219
|
+
|
|
220
|
+
Reconstruct `UIMessage` parts from stream chunks:
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
import { applyChunkToParts } from "@cloudflare/think/message-builder";
|
|
224
|
+
|
|
225
|
+
const msg = { id: "...", role: "assistant", parts: [] };
|
|
226
|
+
for (const chunk of streamChunks) {
|
|
227
|
+
applyChunkToParts(msg.parts, chunk);
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Handles all AI SDK chunk types: `text-delta`, `reasoning-delta`, `tool-call`, `tool-result`, `source`, `file`, and more.
|
|
232
|
+
|
|
233
|
+
## Peer dependencies
|
|
234
|
+
|
|
235
|
+
| Package | Required | Notes |
|
|
236
|
+
| ---------------------- | -------- | --------------------------------- |
|
|
237
|
+
| `agents` | yes | Cloudflare Agents SDK |
|
|
238
|
+
| `ai` | yes | Vercel AI SDK v6 |
|
|
239
|
+
| `zod` | yes | Schema validation (v3.25+ or v4) |
|
|
240
|
+
| `@cloudflare/codemode` | optional | For `createExecuteTool` |
|
|
241
|
+
| `@cloudflare/shell` | optional | For shell execution in extensions |
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region \0@oxc-project+runtime@0.115.0/helpers/checkPrivateRedeclaration.js
|
|
2
|
+
function _checkPrivateRedeclaration(e, t) {
|
|
3
|
+
if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object");
|
|
4
|
+
}
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region \0@oxc-project+runtime@0.115.0/helpers/classPrivateFieldInitSpec.js
|
|
7
|
+
function _classPrivateFieldInitSpec(e, t, a) {
|
|
8
|
+
_checkPrivateRedeclaration(e, t), t.set(e, a);
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region \0@oxc-project+runtime@0.115.0/helpers/assertClassBrand.js
|
|
12
|
+
function _assertClassBrand(e, t, n) {
|
|
13
|
+
if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n;
|
|
14
|
+
throw new TypeError("Private element is not present on this object");
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region \0@oxc-project+runtime@0.115.0/helpers/classPrivateFieldGet2.js
|
|
18
|
+
function _classPrivateFieldGet2(s, a) {
|
|
19
|
+
return s.get(_assertClassBrand(s, a));
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region \0@oxc-project+runtime@0.115.0/helpers/classPrivateFieldSet2.js
|
|
23
|
+
function _classPrivateFieldSet2(s, a, r) {
|
|
24
|
+
return s.set(_assertClassBrand(s, a), r), r;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { _checkPrivateRedeclaration as a, _classPrivateFieldInitSpec as i, _classPrivateFieldGet2 as n, _assertClassBrand as r, _classPrivateFieldSet2 as t };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { a as _checkPrivateRedeclaration } from "./classPrivateFieldSet2-COLddhya.js";
|
|
2
|
+
//#region \0@oxc-project+runtime@0.115.0/helpers/classPrivateMethodInitSpec.js
|
|
3
|
+
function _classPrivateMethodInitSpec(e, a) {
|
|
4
|
+
_checkPrivateRedeclaration(e, a), a.add(e);
|
|
5
|
+
}
|
|
6
|
+
//#endregion
|
|
7
|
+
export { _classPrivateMethodInitSpec as t };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {
|
|
2
|
+
a as ExtensionInfo,
|
|
3
|
+
c as ExtensionToolDescriptor,
|
|
4
|
+
i as ExtensionManagerOptions,
|
|
5
|
+
n as HostBridgeLoopbackProps,
|
|
6
|
+
o as ExtensionManifest,
|
|
7
|
+
r as ExtensionManager,
|
|
8
|
+
s as ExtensionPermissions,
|
|
9
|
+
t as HostBridgeLoopback
|
|
10
|
+
} from "../index-BlcvIdWK.js";
|
|
11
|
+
export {
|
|
12
|
+
ExtensionInfo,
|
|
13
|
+
ExtensionManager,
|
|
14
|
+
ExtensionManagerOptions,
|
|
15
|
+
ExtensionManifest,
|
|
16
|
+
ExtensionPermissions,
|
|
17
|
+
ExtensionToolDescriptor,
|
|
18
|
+
HostBridgeLoopback,
|
|
19
|
+
HostBridgeLoopbackProps
|
|
20
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { r as _assertClassBrand } from "../classPrivateFieldSet2-COLddhya.js";
|
|
2
|
+
import { t as _classPrivateMethodInitSpec } from "../classPrivateMethodInitSpec-CdQXQy1O.js";
|
|
3
|
+
import { t as ExtensionManager } from "../manager-DIV0gQf3.js";
|
|
4
|
+
import { WorkerEntrypoint } from "cloudflare:workers";
|
|
5
|
+
//#region src/extensions/host-bridge.ts
|
|
6
|
+
/**
|
|
7
|
+
* HostBridgeLoopback — a WorkerEntrypoint that provides controlled workspace
|
|
8
|
+
* access to extension Workers loaded via WorkerLoader.
|
|
9
|
+
*
|
|
10
|
+
* This is a loopback: the extension worker's `env.host` binding points here,
|
|
11
|
+
* and each method call resolves the parent agent via `ctx.exports`, then
|
|
12
|
+
* delegates to the agent's workspace proxy methods (`_hostReadFile`, etc.).
|
|
13
|
+
*
|
|
14
|
+
* Props carry serializable identifiers (agent class name, agent ID, and
|
|
15
|
+
* permissions) so the binding survives across requests and hibernation.
|
|
16
|
+
*
|
|
17
|
+
* Users must re-export this class from their worker entry point:
|
|
18
|
+
*
|
|
19
|
+
* ```typescript
|
|
20
|
+
* export { HostBridgeLoopback } from "@cloudflare/think/extensions";
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @experimental Requires the `"experimental"` compatibility flag.
|
|
24
|
+
*/
|
|
25
|
+
var _HostBridgeLoopback_brand = /* @__PURE__ */ new WeakSet();
|
|
26
|
+
var HostBridgeLoopback = class extends WorkerEntrypoint {
|
|
27
|
+
constructor(..._args) {
|
|
28
|
+
super(..._args);
|
|
29
|
+
_classPrivateMethodInitSpec(this, _HostBridgeLoopback_brand);
|
|
30
|
+
this._permissions = this.ctx.props.permissions;
|
|
31
|
+
}
|
|
32
|
+
_getAgent() {
|
|
33
|
+
const { agentClassName, agentId } = this.ctx.props;
|
|
34
|
+
const ns = this.ctx.exports[agentClassName];
|
|
35
|
+
return ns.get(ns.idFromString(agentId));
|
|
36
|
+
}
|
|
37
|
+
async readFile(path) {
|
|
38
|
+
_assertClassBrand(_HostBridgeLoopback_brand, this, _requirePermission).call(this, "read");
|
|
39
|
+
return this._getAgent()._hostReadFile(path);
|
|
40
|
+
}
|
|
41
|
+
async writeFile(path, content) {
|
|
42
|
+
_assertClassBrand(_HostBridgeLoopback_brand, this, _requirePermission).call(this, "read-write");
|
|
43
|
+
return this._getAgent()._hostWriteFile(path, content);
|
|
44
|
+
}
|
|
45
|
+
async deleteFile(path) {
|
|
46
|
+
_assertClassBrand(_HostBridgeLoopback_brand, this, _requirePermission).call(this, "read-write");
|
|
47
|
+
return this._getAgent()._hostDeleteFile(path);
|
|
48
|
+
}
|
|
49
|
+
async listFiles(dir) {
|
|
50
|
+
_assertClassBrand(_HostBridgeLoopback_brand, this, _requirePermission).call(this, "read");
|
|
51
|
+
return this._getAgent()._hostListFiles(dir);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
function _requirePermission(level) {
|
|
55
|
+
const ws = this._permissions.workspace ?? "none";
|
|
56
|
+
if (ws === "none") throw new Error("Extension error: no workspace permission declared");
|
|
57
|
+
if (level === "read-write" && ws !== "read-write") throw new Error("Extension error: workspace write permission required, but only read granted");
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
export { ExtensionManager, HostBridgeLoopback };
|
|
61
|
+
|
|
62
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/extensions/host-bridge.ts"],"sourcesContent":["/**\n * HostBridgeLoopback — a WorkerEntrypoint that provides controlled workspace\n * access to extension Workers loaded via WorkerLoader.\n *\n * This is a loopback: the extension worker's `env.host` binding points here,\n * and each method call resolves the parent agent via `ctx.exports`, then\n * delegates to the agent's workspace proxy methods (`_hostReadFile`, etc.).\n *\n * Props carry serializable identifiers (agent class name, agent ID, and\n * permissions) so the binding survives across requests and hibernation.\n *\n * Users must re-export this class from their worker entry point:\n *\n * ```typescript\n * export { HostBridgeLoopback } from \"@cloudflare/think/extensions\";\n * ```\n *\n * @experimental Requires the `\"experimental\"` compatibility flag.\n */\n\nimport { WorkerEntrypoint } from \"cloudflare:workers\";\nimport type { ExtensionPermissions } from \"./types\";\n\nexport type HostBridgeLoopbackProps = {\n agentClassName: string;\n agentId: string;\n permissions: ExtensionPermissions;\n};\n\nexport class HostBridgeLoopback extends WorkerEntrypoint<\n Record<string, unknown>,\n HostBridgeLoopbackProps\n> {\n private _permissions = this.ctx.props.permissions;\n\n private _getAgent() {\n const { agentClassName, agentId } = this.ctx.props;\n // @ts-expect-error — experimental: ctx.exports on WorkerEntrypoint\n const ns = this.ctx.exports[agentClassName] as DurableObjectNamespace;\n return ns.get(ns.idFromString(agentId));\n }\n\n #requirePermission(level: \"read\" | \"read-write\"): void {\n const ws = this._permissions.workspace ?? \"none\";\n if (ws === \"none\") {\n throw new Error(\"Extension error: no workspace permission declared\");\n }\n if (level === \"read-write\" && ws !== \"read-write\") {\n throw new Error(\n \"Extension error: workspace write permission required, but only read granted\"\n );\n }\n }\n\n async readFile(path: string): Promise<string | null> {\n this.#requirePermission(\"read\");\n return (\n this._getAgent() as unknown as {\n _hostReadFile(path: string): Promise<string | null>;\n }\n )._hostReadFile(path);\n }\n\n async writeFile(path: string, content: string): Promise<void> {\n this.#requirePermission(\"read-write\");\n return (\n this._getAgent() as unknown as {\n _hostWriteFile(path: string, content: string): Promise<void>;\n }\n )._hostWriteFile(path, content);\n }\n\n async deleteFile(path: string): Promise<boolean> {\n this.#requirePermission(\"read-write\");\n return (\n this._getAgent() as unknown as {\n _hostDeleteFile(path: string): Promise<boolean>;\n }\n )._hostDeleteFile(path);\n }\n\n async listFiles(\n dir: string\n ): Promise<\n Array<{ name: string; type: string; size: number; path: string }>\n > {\n this.#requirePermission(\"read\");\n return (\n this._getAgent() as unknown as {\n _hostListFiles(\n dir: string\n ): Promise<\n Array<{ name: string; type: string; size: number; path: string }>\n >;\n }\n )._hostListFiles(dir);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,IAAa,qBAAb,cAAwC,iBAGtC;;;;AACA,OAAQ,eAAe,KAAK,IAAI,MAAM;;CAEtC,YAAoB;EAClB,MAAM,EAAE,gBAAgB,YAAY,KAAK,IAAI;EAE7C,MAAM,KAAK,KAAK,IAAI,QAAQ;AAC5B,SAAO,GAAG,IAAI,GAAG,aAAa,QAAQ,CAAC;;CAezC,MAAM,SAAS,MAAsC;AACnD,oBAAA,2BAAA,MAAA,mBAAuB,CAAA,KAAA,MAAC,OAAO;AAC/B,SACE,KAAK,WAAW,CAGhB,cAAc,KAAK;;CAGvB,MAAM,UAAU,MAAc,SAAgC;AAC5D,oBAAA,2BAAA,MAAA,mBAAuB,CAAA,KAAA,MAAC,aAAa;AACrC,SACE,KAAK,WAAW,CAGhB,eAAe,MAAM,QAAQ;;CAGjC,MAAM,WAAW,MAAgC;AAC/C,oBAAA,2BAAA,MAAA,mBAAuB,CAAA,KAAA,MAAC,aAAa;AACrC,SACE,KAAK,WAAW,CAGhB,gBAAgB,KAAK;;CAGzB,MAAM,UACJ,KAGA;AACA,oBAAA,2BAAA,MAAA,mBAAuB,CAAA,KAAA,MAAC,OAAO;AAC/B,SACE,KAAK,WAAW,CAOhB,eAAe,IAAI;;;AArDvB,SAAA,mBAAmB,OAAoC;CACrD,MAAM,KAAK,KAAK,aAAa,aAAa;AAC1C,KAAI,OAAO,OACT,OAAM,IAAI,MAAM,oDAAoD;AAEtE,KAAI,UAAU,gBAAgB,OAAO,aACnC,OAAM,IAAI,MACR,8EACD"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { ToolSet } from "ai";
|
|
2
|
+
import { WorkerEntrypoint } from "cloudflare:workers";
|
|
3
|
+
|
|
4
|
+
//#region src/extensions/types.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Extension system types.
|
|
7
|
+
*
|
|
8
|
+
* Extensions are sandboxed Workers loaded on demand via WorkerLoader.
|
|
9
|
+
* Each extension provides tools that the agent can use, with controlled
|
|
10
|
+
* access to the host (workspace, network) via permissions.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Manifest declaring an extension's identity and permissions.
|
|
14
|
+
* Passed to ExtensionManager.load() alongside the extension source.
|
|
15
|
+
*/
|
|
16
|
+
interface ExtensionManifest {
|
|
17
|
+
/** Unique name for this extension (used as namespace prefix for tools). */
|
|
18
|
+
name: string;
|
|
19
|
+
/** Semver version string. */
|
|
20
|
+
version: string;
|
|
21
|
+
/** Human-readable description. */
|
|
22
|
+
description?: string;
|
|
23
|
+
/** Permission declarations — controls what the extension can access. */
|
|
24
|
+
permissions?: ExtensionPermissions;
|
|
25
|
+
}
|
|
26
|
+
interface ExtensionPermissions {
|
|
27
|
+
/**
|
|
28
|
+
* Allowed network hosts. If empty or undefined, the extension has
|
|
29
|
+
* no outbound network access (globalOutbound: null).
|
|
30
|
+
* If set, the extension inherits the parent Worker's network.
|
|
31
|
+
*
|
|
32
|
+
* Note: per-host filtering is not yet enforced at the runtime level.
|
|
33
|
+
* This field serves as a declaration of intent; actual enforcement
|
|
34
|
+
* is all-or-nothing via globalOutbound.
|
|
35
|
+
*/
|
|
36
|
+
network?: string[];
|
|
37
|
+
/**
|
|
38
|
+
* Workspace access level.
|
|
39
|
+
* - "none" (default): no workspace access
|
|
40
|
+
* - "read": can read files and list directories
|
|
41
|
+
* - "read-write": can read, write, and delete files
|
|
42
|
+
*/
|
|
43
|
+
workspace?: "read" | "read-write" | "none";
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Tool descriptor returned by the extension's describe() method.
|
|
47
|
+
* Uses JSON Schema for input validation.
|
|
48
|
+
*/
|
|
49
|
+
interface ExtensionToolDescriptor {
|
|
50
|
+
name: string;
|
|
51
|
+
description: string;
|
|
52
|
+
inputSchema: {
|
|
53
|
+
type: "object";
|
|
54
|
+
properties: Record<string, unknown>;
|
|
55
|
+
required?: string[];
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Summary of a loaded extension, returned by ExtensionManager.list().
|
|
60
|
+
*/
|
|
61
|
+
interface ExtensionInfo {
|
|
62
|
+
name: string;
|
|
63
|
+
version: string;
|
|
64
|
+
description?: string;
|
|
65
|
+
/** Names of tools provided by this extension. */
|
|
66
|
+
tools: string[];
|
|
67
|
+
permissions: ExtensionPermissions;
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/extensions/manager.d.ts
|
|
71
|
+
interface ExtensionManagerOptions {
|
|
72
|
+
/** WorkerLoader binding for creating sandboxed extension Workers. */
|
|
73
|
+
loader: WorkerLoader;
|
|
74
|
+
/**
|
|
75
|
+
* Durable Object storage for persisting extensions across hibernation.
|
|
76
|
+
* If provided, loaded extensions survive DO restarts. Call `restore()`
|
|
77
|
+
* on each turn to rebuild in-memory state from storage.
|
|
78
|
+
*/
|
|
79
|
+
storage?: DurableObjectStorage;
|
|
80
|
+
/**
|
|
81
|
+
* Factory that creates a loopback Fetcher for workspace access, given
|
|
82
|
+
* an extension's declared permissions. The returned binding is injected
|
|
83
|
+
* into the extension worker's `env.host`.
|
|
84
|
+
*
|
|
85
|
+
* If not provided, extensions receive no host binding (workspace tools
|
|
86
|
+
* will get `null` for the host parameter).
|
|
87
|
+
*
|
|
88
|
+
* Typically wired up using HostBridgeLoopback via `ctx.exports`:
|
|
89
|
+
* ```typescript
|
|
90
|
+
* createHostBinding: (permissions) =>
|
|
91
|
+
* ctx.exports.HostBridgeLoopback({
|
|
92
|
+
* props: { agentClassName: "ChatSession", agentId: ctx.id.toString(), permissions }
|
|
93
|
+
* })
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
createHostBinding?: (permissions: ExtensionPermissions) => Fetcher;
|
|
97
|
+
}
|
|
98
|
+
declare class ExtensionManager {
|
|
99
|
+
#private;
|
|
100
|
+
constructor(options: ExtensionManagerOptions);
|
|
101
|
+
/**
|
|
102
|
+
* Load an extension from source code.
|
|
103
|
+
*
|
|
104
|
+
* The source is a JS object expression defining tools. Each tool has
|
|
105
|
+
* `description`, `parameters` (JSON Schema properties), optional
|
|
106
|
+
* `required` array, and an `execute` async function.
|
|
107
|
+
*
|
|
108
|
+
* @returns Summary of the loaded extension including discovered tools.
|
|
109
|
+
*/
|
|
110
|
+
/**
|
|
111
|
+
* Restore extensions from DO storage after hibernation.
|
|
112
|
+
*
|
|
113
|
+
* Idempotent — skips extensions already in memory. Call this at the
|
|
114
|
+
* start of each chat turn (e.g. in onChatMessage before getTools).
|
|
115
|
+
*/
|
|
116
|
+
restore(): Promise<void>;
|
|
117
|
+
load(manifest: ExtensionManifest, source: string): Promise<ExtensionInfo>;
|
|
118
|
+
/**
|
|
119
|
+
* Unload an extension, removing its tools from the agent.
|
|
120
|
+
*/
|
|
121
|
+
unload(name: string): Promise<boolean>;
|
|
122
|
+
/**
|
|
123
|
+
* List all loaded extensions.
|
|
124
|
+
*/
|
|
125
|
+
list(): ExtensionInfo[];
|
|
126
|
+
/**
|
|
127
|
+
* Get AI SDK tools from all loaded extensions.
|
|
128
|
+
*
|
|
129
|
+
* Tool names are prefixed with the sanitized extension name to avoid
|
|
130
|
+
* collisions: e.g. extension "github" with tool "create_pr" → "github_create_pr".
|
|
131
|
+
*/
|
|
132
|
+
getTools(): ToolSet;
|
|
133
|
+
}
|
|
134
|
+
//#endregion
|
|
135
|
+
//#region src/extensions/host-bridge.d.ts
|
|
136
|
+
type HostBridgeLoopbackProps = {
|
|
137
|
+
agentClassName: string;
|
|
138
|
+
agentId: string;
|
|
139
|
+
permissions: ExtensionPermissions;
|
|
140
|
+
};
|
|
141
|
+
declare class HostBridgeLoopback extends WorkerEntrypoint<
|
|
142
|
+
Record<string, unknown>,
|
|
143
|
+
HostBridgeLoopbackProps
|
|
144
|
+
> {
|
|
145
|
+
#private;
|
|
146
|
+
private _permissions;
|
|
147
|
+
private _getAgent;
|
|
148
|
+
readFile(path: string): Promise<string | null>;
|
|
149
|
+
writeFile(path: string, content: string): Promise<void>;
|
|
150
|
+
deleteFile(path: string): Promise<boolean>;
|
|
151
|
+
listFiles(dir: string): Promise<
|
|
152
|
+
Array<{
|
|
153
|
+
name: string;
|
|
154
|
+
type: string;
|
|
155
|
+
size: number;
|
|
156
|
+
path: string;
|
|
157
|
+
}>
|
|
158
|
+
>;
|
|
159
|
+
}
|
|
160
|
+
//#endregion
|
|
161
|
+
export {
|
|
162
|
+
ExtensionInfo as a,
|
|
163
|
+
ExtensionToolDescriptor as c,
|
|
164
|
+
ExtensionManagerOptions as i,
|
|
165
|
+
HostBridgeLoopbackProps as n,
|
|
166
|
+
ExtensionManifest as o,
|
|
167
|
+
ExtensionManager as r,
|
|
168
|
+
ExtensionPermissions as s,
|
|
169
|
+
HostBridgeLoopback as t
|
|
170
|
+
};
|
|
171
|
+
//# sourceMappingURL=index-BlcvIdWK.d.ts.map
|