@cloudflare/codemode 0.0.1 → 0.0.4
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/CHANGELOG.md +35 -0
- package/README.md +333 -0
- package/dist/ai.d.ts +30 -0
- package/dist/ai.js +147 -0
- package/dist/ai.js.map +1 -0
- package/package.json +40 -8
- package/scripts/build.ts +26 -0
- package/src/ai.ts +245 -0
- package/tsconfig.json +4 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# @cloudflare/codemode
|
|
2
|
+
|
|
3
|
+
## 0.0.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#771](https://github.com/cloudflare/agents/pull/771) [`87dc96d`](https://github.com/cloudflare/agents/commit/87dc96d19de1d26dbb2badecbb9955a4eb8e9e2e) Thanks [@threepointone](https://github.com/threepointone)! - update dependencies
|
|
8
|
+
|
|
9
|
+
- Updated dependencies [[`cf8a1e7`](https://github.com/cloudflare/agents/commit/cf8a1e7a24ecaac62c2aefca7b0fd5bf1373e8bd), [`87dc96d`](https://github.com/cloudflare/agents/commit/87dc96d19de1d26dbb2badecbb9955a4eb8e9e2e)]:
|
|
10
|
+
- agents@0.3.4
|
|
11
|
+
|
|
12
|
+
## 0.0.3
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- [`a5d0137`](https://github.com/cloudflare/agents/commit/a5d01379b9ad2d88bc028c50f1858b4e69f106c5) Thanks [@threepointone](https://github.com/threepointone)! - trigger a new release
|
|
17
|
+
|
|
18
|
+
- Updated dependencies [[`a5d0137`](https://github.com/cloudflare/agents/commit/a5d01379b9ad2d88bc028c50f1858b4e69f106c5)]:
|
|
19
|
+
- agents@0.3.3
|
|
20
|
+
|
|
21
|
+
## 0.0.2
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- [#756](https://github.com/cloudflare/agents/pull/756) [`0c4275f`](https://github.com/cloudflare/agents/commit/0c4275f8f4b71c264c32c3742d151ef705739c2f) Thanks [@threepointone](https://github.com/threepointone)! - feat: split ai-chat and codemode into separate packages
|
|
26
|
+
|
|
27
|
+
Extract @cloudflare/ai-chat and @cloudflare/codemode into their own packages
|
|
28
|
+
with comprehensive READMEs. Update agents README to remove chat-specific
|
|
29
|
+
content and point to new packages. Fix documentation imports to reflect
|
|
30
|
+
new package structure.
|
|
31
|
+
|
|
32
|
+
Maintains backward compatibility, no breaking changes.
|
|
33
|
+
|
|
34
|
+
- Updated dependencies [[`0c4275f`](https://github.com/cloudflare/agents/commit/0c4275f8f4b71c264c32c3742d151ef705739c2f), [`f12553f`](https://github.com/cloudflare/agents/commit/f12553f2fa65912c68d9a7620b9a11b70b8790a2)]:
|
|
35
|
+
- agents@0.3.2
|
package/README.md
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
### 💻 `@cloudflare/codemode` - Code Mode: The Better Way to Use MCP
|
|
2
|
+
|
|
3
|
+
Instead of asking LLMs to call tools directly, Code Mode lets them write executable code that orchestrates multiple operations. **LLMs are better at writing code than calling tools** - they've seen millions of lines of real-world TypeScript but only contrived tool-calling examples.
|
|
4
|
+
|
|
5
|
+
Code Mode converts your tools (especially MCP servers) into TypeScript APIs, enabling complex workflows, error handling, and multi-step operations that are natural in code but difficult with traditional tool calling.
|
|
6
|
+
|
|
7
|
+
Built on Cloudflare's Worker Loader API, Code Mode executes generated code in secure, isolated sandboxes with millisecond startup times.
|
|
8
|
+
|
|
9
|
+
> **⚠️ Experimental Feature**: Code Mode is currently experimental and may have breaking changes in future releases. Use with caution in production environments.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
### 🌱 Installation
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
npm install @cloudflare/codemode agents ai
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### 📝 Your First Code Mode Agent
|
|
20
|
+
|
|
21
|
+
Transform your tool-calling agent into a code-generating one:
|
|
22
|
+
|
|
23
|
+
#### Before (Traditional Tool Calling)
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { streamText } from "ai";
|
|
27
|
+
import { tool } from "ai";
|
|
28
|
+
import { z } from "zod";
|
|
29
|
+
|
|
30
|
+
const result = streamText({
|
|
31
|
+
model: openai("gpt-4o"),
|
|
32
|
+
messages,
|
|
33
|
+
tools: {
|
|
34
|
+
getWeather: tool({
|
|
35
|
+
description: "Get weather for a location",
|
|
36
|
+
inputSchema: z.object({ location: z.string() }),
|
|
37
|
+
execute: async ({ location }) => {
|
|
38
|
+
return `Weather in ${location}: 72°F, sunny`;
|
|
39
|
+
}
|
|
40
|
+
}),
|
|
41
|
+
sendEmail: tool({
|
|
42
|
+
description: "Send an email",
|
|
43
|
+
inputSchema: z.object({
|
|
44
|
+
to: z.string(),
|
|
45
|
+
subject: z.string(),
|
|
46
|
+
body: z.string()
|
|
47
|
+
}),
|
|
48
|
+
execute: async ({ to, subject, body }) => {
|
|
49
|
+
// Send email logic
|
|
50
|
+
return `Email sent to ${to}`;
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
#### After (With Code Mode)
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { experimental_codemode as codemode } from "@cloudflare/codemode/ai";
|
|
61
|
+
import { streamText } from "ai";
|
|
62
|
+
import { tool } from "ai";
|
|
63
|
+
import { z } from "zod";
|
|
64
|
+
|
|
65
|
+
// Define your tools as usual
|
|
66
|
+
const tools = {
|
|
67
|
+
getWeather: tool({
|
|
68
|
+
description: "Get weather for a location",
|
|
69
|
+
inputSchema: z.object({ location: z.string() }),
|
|
70
|
+
execute: async ({ location }) => {
|
|
71
|
+
return `Weather in ${location}: 72°F, sunny`;
|
|
72
|
+
}
|
|
73
|
+
}),
|
|
74
|
+
sendEmail: tool({
|
|
75
|
+
description: "Send an email",
|
|
76
|
+
inputSchema: z.object({
|
|
77
|
+
to: z.string(),
|
|
78
|
+
subject: z.string(),
|
|
79
|
+
body: z.string()
|
|
80
|
+
}),
|
|
81
|
+
execute: async ({ to, subject, body }) => {
|
|
82
|
+
// Send email logic
|
|
83
|
+
return `Email sent to ${to}`;
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Configure Code Mode
|
|
89
|
+
const { prompt, tools: wrappedTools } = await codemode({
|
|
90
|
+
prompt: "You are a helpful assistant...",
|
|
91
|
+
tools,
|
|
92
|
+
globalOutbound: env.globalOutbound,
|
|
93
|
+
loader: env.LOADER,
|
|
94
|
+
proxy: this.ctx.exports.CodeModeProxy({
|
|
95
|
+
props: {
|
|
96
|
+
binding: "MyAgent",
|
|
97
|
+
name: this.name,
|
|
98
|
+
callback: "callTool"
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Use the wrapped tools - now the LLM will generate code instead!
|
|
104
|
+
const result = streamText({
|
|
105
|
+
model: openai("gpt-4o"),
|
|
106
|
+
system: prompt,
|
|
107
|
+
messages,
|
|
108
|
+
tools: wrappedTools // Single "codemode" tool that generates code
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
That's it! Your agent now generates executable code that orchestrates your tools.
|
|
113
|
+
|
|
114
|
+
### 🏰 Configuration
|
|
115
|
+
|
|
116
|
+
Define the required bindings in your `wrangler.toml`:
|
|
117
|
+
|
|
118
|
+
```jsonc
|
|
119
|
+
{
|
|
120
|
+
"compatibility_flags": ["experimental", "enable_ctx_exports"],
|
|
121
|
+
"worker_loaders": [
|
|
122
|
+
{
|
|
123
|
+
"binding": "LOADER"
|
|
124
|
+
}
|
|
125
|
+
],
|
|
126
|
+
"services": [
|
|
127
|
+
{
|
|
128
|
+
"binding": "globalOutbound",
|
|
129
|
+
"service": "your-service",
|
|
130
|
+
"entrypoint": "globalOutbound"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"binding": "CodeModeProxy",
|
|
134
|
+
"service": "your-service",
|
|
135
|
+
"entrypoint": "CodeModeProxy"
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 🎭 Agent Integration
|
|
142
|
+
|
|
143
|
+
#### With MCP Servers
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
import { Agent } from "agents";
|
|
147
|
+
import { experimental_codemode as codemode } from "@cloudflare/codemode/ai";
|
|
148
|
+
import { streamText, convertToModelMessages } from "ai";
|
|
149
|
+
import { openai } from "@ai-sdk/openai";
|
|
150
|
+
|
|
151
|
+
export class CodeModeAgent extends Agent<Env> {
|
|
152
|
+
async onChatMessage() {
|
|
153
|
+
const allTools = {
|
|
154
|
+
...regularTools,
|
|
155
|
+
...this.mcp.getAITools() // Include MCP tools
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const { prompt, tools: wrappedTools } = await codemode({
|
|
159
|
+
prompt: "You are a helpful assistant...",
|
|
160
|
+
tools: allTools,
|
|
161
|
+
globalOutbound: env.globalOutbound,
|
|
162
|
+
loader: env.LOADER,
|
|
163
|
+
proxy: this.ctx.exports.CodeModeProxy({
|
|
164
|
+
props: {
|
|
165
|
+
binding: "CodeModeAgent",
|
|
166
|
+
name: this.name,
|
|
167
|
+
callback: "callTool"
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const result = streamText({
|
|
173
|
+
model: openai("gpt-4o"),
|
|
174
|
+
system: prompt,
|
|
175
|
+
messages: await convertToModelMessages(this.messages),
|
|
176
|
+
tools: wrappedTools
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
return result.toUIMessageStreamResponse();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
callTool(functionName: string, args: unknown[]) {
|
|
183
|
+
return this.tools[functionName]?.execute?.(args, {
|
|
184
|
+
abortSignal: new AbortController().signal,
|
|
185
|
+
toolCallId: "codemode",
|
|
186
|
+
messages: []
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export { CodeModeProxy } from "@cloudflare/codemode/ai";
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### 🌊 Generated Code Example
|
|
195
|
+
|
|
196
|
+
Code Mode enables complex workflows that chain multiple operations:
|
|
197
|
+
|
|
198
|
+
```javascript
|
|
199
|
+
// Example generated code orchestrating multiple MCP servers:
|
|
200
|
+
async function executeTask() {
|
|
201
|
+
const files = await codemode.listFiles({ path: "/projects" });
|
|
202
|
+
const recentProject = files
|
|
203
|
+
.filter((f) => f.type === "directory")
|
|
204
|
+
.sort((a, b) => new Date(b.modified) - new Date(a.modified))[0];
|
|
205
|
+
|
|
206
|
+
const projectStatus = await codemode.queryDatabase({
|
|
207
|
+
query: "SELECT * FROM projects WHERE name = ?",
|
|
208
|
+
params: [recentProject.name]
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
if (projectStatus.length === 0 || projectStatus[0].status === "incomplete") {
|
|
212
|
+
await codemode.createTask({
|
|
213
|
+
title: `Review project: ${recentProject.name}`,
|
|
214
|
+
priority: "high"
|
|
215
|
+
});
|
|
216
|
+
await codemode.sendEmail({
|
|
217
|
+
to: "team@company.com",
|
|
218
|
+
subject: "Project Review Needed"
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return { success: true, project: recentProject };
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### 🔒 Security
|
|
227
|
+
|
|
228
|
+
Code runs in isolated Workers with millisecond startup times. No network access by default - only through explicit bindings. API keys are hidden in bindings, preventing leaks.
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
export const globalOutbound = {
|
|
232
|
+
fetch: async (input: string | URL | RequestInfo, init?: RequestInit) => {
|
|
233
|
+
const url = new URL(typeof input === "string" ? input : input.toString());
|
|
234
|
+
if (url.hostname === "example.com") {
|
|
235
|
+
return new Response("Not allowed", { status: 403 });
|
|
236
|
+
}
|
|
237
|
+
return fetch(input, init);
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### 🔧 Setup
|
|
243
|
+
|
|
244
|
+
**Required bindings:**
|
|
245
|
+
|
|
246
|
+
- `LOADER`: Worker Loader for code execution
|
|
247
|
+
- `globalOutbound`: Service for network access control
|
|
248
|
+
- `CodeModeProxy`: Service for tool execution proxy
|
|
249
|
+
|
|
250
|
+
**Environment:**
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
export const globalOutbound = {
|
|
254
|
+
fetch: async (input: string | URL | RequestInfo, init?: RequestInit) => {
|
|
255
|
+
// Your security policies
|
|
256
|
+
return fetch(input, init);
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
export { CodeModeProxy } from "@cloudflare/codemode/ai";
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
**Proxy configuration:**
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
proxy: this.ctx.exports.CodeModeProxy({
|
|
267
|
+
props: {
|
|
268
|
+
binding: "YourAgentClass",
|
|
269
|
+
name: this.name,
|
|
270
|
+
callback: "callTool"
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### 🎯 Real-World Examples
|
|
276
|
+
|
|
277
|
+
Explore these examples to see Code Mode in action:
|
|
278
|
+
|
|
279
|
+
- **Complete Demo**: [`examples/codemode/`](../../examples/codemode/) - Full working example with MCP integration
|
|
280
|
+
- **Documentation**: [`docs/codemode.md`](../../docs/codemode.md) - Detailed guide and examples
|
|
281
|
+
- **Blog Post**: [Code Mode: the better way to use MCP](https://blog.cloudflare.com/code-mode/) - Deep dive into the philosophy and implementation
|
|
282
|
+
|
|
283
|
+
### 📚 API Reference
|
|
284
|
+
|
|
285
|
+
#### `experimental_codemode(options)`
|
|
286
|
+
|
|
287
|
+
Wraps your tools with Code Mode, converting them into a single code-generating tool.
|
|
288
|
+
|
|
289
|
+
**Options:**
|
|
290
|
+
|
|
291
|
+
- `tools: ToolSet` - Your tool definitions (including MCP tools)
|
|
292
|
+
- `prompt: string` - System prompt for the LLM
|
|
293
|
+
- `globalOutbound: Fetcher` - Service binding for network access control
|
|
294
|
+
- `loader: WorkerLoader` - Worker Loader binding for code execution
|
|
295
|
+
- `proxy: Fetcher<CodeModeProxy>` - Proxy binding for tool execution
|
|
296
|
+
|
|
297
|
+
**Returns:**
|
|
298
|
+
|
|
299
|
+
- `prompt: string` - Enhanced system prompt
|
|
300
|
+
- `tools: ToolSet` - Wrapped tools (single "codemode" tool)
|
|
301
|
+
|
|
302
|
+
#### `CodeModeProxy`
|
|
303
|
+
|
|
304
|
+
Worker entrypoint that routes tool calls back to your agent.
|
|
305
|
+
|
|
306
|
+
**Props:**
|
|
307
|
+
|
|
308
|
+
- `binding: string` - Your agent class name
|
|
309
|
+
- `name: string` - Agent instance name
|
|
310
|
+
- `callback: string` - Method name to call for tool execution
|
|
311
|
+
|
|
312
|
+
### 🔗 Integration
|
|
313
|
+
|
|
314
|
+
`@cloudflare/codemode` integrates with the [`agents`](../agents/) framework and works with any agent that extends `Agent`, including MCP server integration via `Agent.mcp`.
|
|
315
|
+
|
|
316
|
+
### 🚀 Limitations
|
|
317
|
+
|
|
318
|
+
- **Experimental**: Subject to breaking changes
|
|
319
|
+
- **Requires Cloudflare Workers**: Uses Worker Loader API (beta)
|
|
320
|
+
- **JavaScript Only**: Python support planned
|
|
321
|
+
|
|
322
|
+
### Contributing
|
|
323
|
+
|
|
324
|
+
Contributions are welcome! Please:
|
|
325
|
+
|
|
326
|
+
1. Open an issue to discuss your proposal
|
|
327
|
+
2. Ensure your changes align with the package's goals
|
|
328
|
+
3. Include tests for new features
|
|
329
|
+
4. Update documentation as needed
|
|
330
|
+
|
|
331
|
+
### License
|
|
332
|
+
|
|
333
|
+
MIT licensed. See the LICENSE file at the root of this repository for details.
|
package/dist/ai.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ToolSet } from "ai";
|
|
2
|
+
import { WorkerEntrypoint } from "cloudflare:workers";
|
|
3
|
+
|
|
4
|
+
//#region src/ai.d.ts
|
|
5
|
+
declare class CodeModeProxy extends WorkerEntrypoint<
|
|
6
|
+
Cloudflare.Env,
|
|
7
|
+
{
|
|
8
|
+
binding: string;
|
|
9
|
+
name: string;
|
|
10
|
+
callback: string;
|
|
11
|
+
}
|
|
12
|
+
> {
|
|
13
|
+
callFunction(options: {
|
|
14
|
+
functionName: string;
|
|
15
|
+
args: unknown[];
|
|
16
|
+
}): Promise<any>;
|
|
17
|
+
}
|
|
18
|
+
declare function experimental_codemode(options: {
|
|
19
|
+
tools: ToolSet;
|
|
20
|
+
prompt: string;
|
|
21
|
+
globalOutbound: Fetcher;
|
|
22
|
+
loader: WorkerLoader;
|
|
23
|
+
proxy: Fetcher<CodeModeProxy>;
|
|
24
|
+
}): Promise<{
|
|
25
|
+
prompt: string;
|
|
26
|
+
tools: ToolSet;
|
|
27
|
+
}>;
|
|
28
|
+
//#endregion
|
|
29
|
+
export { CodeModeProxy, experimental_codemode };
|
|
30
|
+
//# sourceMappingURL=ai.d.ts.map
|
package/dist/ai.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { generateObject, tool } from "ai";
|
|
2
|
+
import { openai } from "@ai-sdk/openai";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { compile } from "json-schema-to-typescript";
|
|
5
|
+
import { createTypeAlias, printNode, zodToTs } from "zod-to-ts";
|
|
6
|
+
import { getAgentByName } from "agents";
|
|
7
|
+
import { WorkerEntrypoint, env } from "cloudflare:workers";
|
|
8
|
+
|
|
9
|
+
//#region src/ai.ts
|
|
10
|
+
function toCamelCase(str) {
|
|
11
|
+
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()).replace(/^[a-z]/, (letter) => letter.toUpperCase());
|
|
12
|
+
}
|
|
13
|
+
var CodeModeProxy = class extends WorkerEntrypoint {
|
|
14
|
+
async callFunction(options) {
|
|
15
|
+
return (await getAgentByName(env[this.ctx.props.binding], this.ctx.props.name))[this.ctx.props.callback](options.functionName, options.args);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
async function experimental_codemode(options) {
|
|
19
|
+
const generatedTypes = await generateTypes(options.tools);
|
|
20
|
+
return {
|
|
21
|
+
prompt: `You are a helpful assistant. You have access to the "codemode" tool that can do different things:
|
|
22
|
+
|
|
23
|
+
${getToolDescriptions(options.tools)}
|
|
24
|
+
|
|
25
|
+
If the user asks to do anything that be achieveable by the codemode tool, then simply pass over control to it by giving it a simple function description. Don't be too verbose.
|
|
26
|
+
|
|
27
|
+
`,
|
|
28
|
+
tools: { codemode: tool({
|
|
29
|
+
description: "codemode: a tool that can generate code to achieve a goal",
|
|
30
|
+
inputSchema: z.object({ functionDescription: z.string() }),
|
|
31
|
+
outputSchema: z.object({
|
|
32
|
+
code: z.string(),
|
|
33
|
+
result: z.any()
|
|
34
|
+
}),
|
|
35
|
+
execute: async ({ functionDescription }) => {
|
|
36
|
+
try {
|
|
37
|
+
const response = await generateObject({
|
|
38
|
+
model: openai("gpt-4.1"),
|
|
39
|
+
schema: z.object({ code: z.string() }),
|
|
40
|
+
prompt: `You are a code generating machine.
|
|
41
|
+
|
|
42
|
+
In addition to regular javascript, you can also use the following functions:
|
|
43
|
+
|
|
44
|
+
${generatedTypes}
|
|
45
|
+
|
|
46
|
+
Respond only with the code, nothing else. Output javascript code.
|
|
47
|
+
|
|
48
|
+
Generate an async function that achieves the goal. This async function doesn't accept any arguments.
|
|
49
|
+
|
|
50
|
+
Here is user input: ${functionDescription}`
|
|
51
|
+
});
|
|
52
|
+
const result = await createEvaluator(response.object.code, {
|
|
53
|
+
proxy: options.proxy,
|
|
54
|
+
loader: options.loader
|
|
55
|
+
})();
|
|
56
|
+
return {
|
|
57
|
+
code: response.object.code,
|
|
58
|
+
result
|
|
59
|
+
};
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error("error", error);
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}) }
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function createEvaluator(code, options) {
|
|
69
|
+
return async () => {
|
|
70
|
+
return await options.loader.get(`code-${Math.random()}`, () => {
|
|
71
|
+
return {
|
|
72
|
+
compatibilityDate: "2025-06-01",
|
|
73
|
+
compatibilityFlags: ["nodejs_compat"],
|
|
74
|
+
mainModule: "foo.js",
|
|
75
|
+
modules: { "foo.js": `
|
|
76
|
+
import { env, WorkerEntrypoint } from "cloudflare:workers";
|
|
77
|
+
|
|
78
|
+
export default class CodeModeWorker extends WorkerEntrypoint {
|
|
79
|
+
async evaluate() {
|
|
80
|
+
try {
|
|
81
|
+
const { CodeModeProxy } = env;
|
|
82
|
+
const codemode = new Proxy(
|
|
83
|
+
{},
|
|
84
|
+
{
|
|
85
|
+
get: (target, prop) => {
|
|
86
|
+
return (args) => {
|
|
87
|
+
return CodeModeProxy.callFunction({
|
|
88
|
+
functionName: prop,
|
|
89
|
+
args: args,
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
return await ${code}();
|
|
97
|
+
} catch (err) {
|
|
98
|
+
return {
|
|
99
|
+
err: err.message,
|
|
100
|
+
stack: err.stack
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
` },
|
|
107
|
+
env: { CodeModeProxy: options.proxy },
|
|
108
|
+
globalOutbound: null
|
|
109
|
+
};
|
|
110
|
+
}).getEntrypoint().evaluate();
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
async function generateTypes(tools) {
|
|
114
|
+
let availableTools = "";
|
|
115
|
+
let availableTypes = "";
|
|
116
|
+
for (const [toolName, tool$1] of Object.entries(tools)) {
|
|
117
|
+
const inputJsonType = tool$1.inputSchema.jsonSchema ? await compile(tool$1.inputSchema.jsonSchema, `${toCamelCase(toolName)}Input`, {
|
|
118
|
+
format: false,
|
|
119
|
+
bannerComment: " "
|
|
120
|
+
}) : printNode(createTypeAlias(zodToTs(tool$1.inputSchema, `${toCamelCase(toolName)}Input`).node, `${toCamelCase(toolName)}Input`));
|
|
121
|
+
const outputJsonType = tool$1.outputSchema?.jsonSchema ? await compile(tool$1.outputSchema?.jsonSchema, `${toCamelCase(toolName)}Output`, {
|
|
122
|
+
format: false,
|
|
123
|
+
bannerComment: " "
|
|
124
|
+
}) : tool$1.outputSchema ? printNode(createTypeAlias(zodToTs(tool$1.outputSchema, `${toCamelCase(toolName)}Output`).node, `${toCamelCase(toolName)}Output`)) : `interface ${toCamelCase(toolName)}Output { [key: string]: any }`;
|
|
125
|
+
const InputType = inputJsonType.trim().replace("export interface", "interface");
|
|
126
|
+
const OutputType = outputJsonType.trim().replace("export interface", "interface");
|
|
127
|
+
availableTypes += `\n${InputType}`;
|
|
128
|
+
availableTypes += `\n${OutputType}`;
|
|
129
|
+
availableTools += `\n\t/*\n\t${tool$1.description?.trim()}\n\t*/`;
|
|
130
|
+
availableTools += `\n\t${toolName}: (input: ${toCamelCase(toolName)}Input) => Promise<${toCamelCase(toolName)}Output>;`;
|
|
131
|
+
availableTools += "\n";
|
|
132
|
+
}
|
|
133
|
+
availableTools = `\ndeclare const codemode: {${availableTools}}`;
|
|
134
|
+
return `
|
|
135
|
+
${availableTypes}
|
|
136
|
+
${availableTools}
|
|
137
|
+
`;
|
|
138
|
+
}
|
|
139
|
+
function getToolDescriptions(tools) {
|
|
140
|
+
return Object.entries(tools).map(([_toolName, tool$1]) => {
|
|
141
|
+
return `\n- ${tool$1.description?.trim()}`;
|
|
142
|
+
}).join("");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
//#endregion
|
|
146
|
+
export { CodeModeProxy, experimental_codemode };
|
|
147
|
+
//# sourceMappingURL=ai.js.map
|
package/dist/ai.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai.js","names":["tool","compileJsonSchemaToTs","printNodeZodToTs"],"sources":["../src/ai.ts"],"sourcesContent":["import { generateObject, tool, type ToolSet } from \"ai\";\nimport { openai } from \"@ai-sdk/openai\";\nimport { z } from \"zod\";\nimport { compile as compileJsonSchemaToTs } from \"json-schema-to-typescript\";\nimport {\n zodToTs,\n printNode as printNodeZodToTs,\n createTypeAlias\n} from \"zod-to-ts\";\nimport { getAgentByName } from \"agents\";\nimport { env, WorkerEntrypoint } from \"cloudflare:workers\";\n\nfunction toCamelCase(str: string) {\n return str\n .replace(/_([a-z])/g, (_, letter) => letter.toUpperCase())\n .replace(/^[a-z]/, (letter) => letter.toUpperCase());\n}\n\nexport class CodeModeProxy extends WorkerEntrypoint<\n Cloudflare.Env,\n {\n binding: string;\n name: string;\n callback: string;\n }\n> {\n async callFunction(options: { functionName: string; args: unknown[] }) {\n const stub = (await getAgentByName(\n // @ts-expect-error\n env[this.ctx.props.binding] as DurableObjectNamespace<T>,\n this.ctx.props.name\n )) as DurableObjectStub;\n // @ts-expect-error\n return stub[this.ctx.props.callback](options.functionName, options.args);\n }\n}\n\nexport async function experimental_codemode(options: {\n tools: ToolSet;\n prompt: string;\n globalOutbound: Fetcher;\n loader: WorkerLoader;\n proxy: Fetcher<CodeModeProxy>;\n}): Promise<{\n prompt: string;\n tools: ToolSet;\n}> {\n const generatedTypes = await generateTypes(options.tools);\n const prompt = `You are a helpful assistant. You have access to the \"codemode\" tool that can do different things: \n \n ${getToolDescriptions(options.tools)} \n \n If the user asks to do anything that be achieveable by the codemode tool, then simply pass over control to it by giving it a simple function description. Don't be too verbose.\n \n `;\n\n const codemodeTool = tool({\n description: \"codemode: a tool that can generate code to achieve a goal\",\n inputSchema: z.object({\n functionDescription: z.string()\n }),\n outputSchema: z.object({\n code: z.string(),\n result: z.any()\n }),\n execute: async ({ functionDescription }) => {\n try {\n const response = await generateObject({\n model: openai(\"gpt-4.1\"),\n schema: z.object({\n code: z.string()\n }),\n prompt: `You are a code generating machine.\n\n In addition to regular javascript, you can also use the following functions:\n\n ${generatedTypes} \n\n Respond only with the code, nothing else. Output javascript code.\n\n Generate an async function that achieves the goal. This async function doesn't accept any arguments.\n\n Here is user input: ${functionDescription}` // insert ts types for the tools here\n });\n\n // console.log(\"args\", response.object.args);\n const evaluator = createEvaluator(response.object.code, {\n proxy: options.proxy,\n loader: options.loader\n });\n const result = await evaluator();\n return { code: response.object.code, result: result };\n } catch (error) {\n console.error(\"error\", error);\n throw error;\n // return { code: \"\", result: error };\n }\n }\n });\n\n return { prompt, tools: { codemode: codemodeTool } };\n}\n\nfunction createEvaluator(\n code: string,\n options: {\n loader: WorkerLoader;\n proxy: Fetcher<CodeModeProxy>;\n }\n) {\n return async () => {\n const worker = options.loader.get(`code-${Math.random()}`, () => {\n return {\n compatibilityDate: \"2025-06-01\",\n compatibilityFlags: [\"nodejs_compat\"],\n mainModule: \"foo.js\",\n modules: {\n \"foo.js\": `\nimport { env, WorkerEntrypoint } from \"cloudflare:workers\";\n\nexport default class CodeModeWorker extends WorkerEntrypoint {\n async evaluate() {\n try {\n const { CodeModeProxy } = env;\n const codemode = new Proxy(\n {},\n {\n get: (target, prop) => {\n return (args) => {\n return CodeModeProxy.callFunction({\n functionName: prop,\n args: args, \n });\n };\n }\n }\n );\n\n return await ${code}();\n } catch (err) {\n return {\n err: err.message,\n stack: err.stack\n };\n }\n }\n}\n \n `\n },\n env: {\n // insert keys and bindings to tools/ts functions here\n CodeModeProxy: options.proxy\n },\n globalOutbound: null\n };\n });\n\n // @ts-expect-error TODO: fix this\n return await worker.getEntrypoint().evaluate();\n };\n}\n\nasync function generateTypes(tools: ToolSet) {\n let availableTools = \"\";\n let availableTypes = \"\";\n\n for (const [toolName, tool] of Object.entries(tools)) {\n // @ts-expect-error TODO: fix this\n const inputJsonType = tool.inputSchema.jsonSchema\n ? await compileJsonSchemaToTs(\n // @ts-expect-error TODO: fix this\n tool.inputSchema.jsonSchema,\n `${toCamelCase(toolName)}Input`,\n {\n format: false,\n bannerComment: \" \"\n }\n )\n : printNodeZodToTs(\n createTypeAlias(\n zodToTs(\n // @ts-expect-error TODO: fix this\n tool.inputSchema,\n `${toCamelCase(toolName)}Input`\n ).node,\n `${toCamelCase(toolName)}Input`\n )\n );\n\n const outputJsonType =\n // @ts-expect-error TODO: fix this\n tool.outputSchema?.jsonSchema\n ? await compileJsonSchemaToTs(\n // @ts-expect-error TODO: fix this\n tool.outputSchema?.jsonSchema,\n `${toCamelCase(toolName)}Output`,\n {\n format: false,\n bannerComment: \" \"\n }\n )\n : tool.outputSchema\n ? printNodeZodToTs(\n createTypeAlias(\n zodToTs(\n // @ts-expect-error TODO: fix this\n tool.outputSchema,\n `${toCamelCase(toolName)}Output`\n ).node,\n `${toCamelCase(toolName)}Output`\n )\n )\n : `interface ${toCamelCase(toolName)}Output { [key: string]: any }`;\n\n const InputType = inputJsonType\n .trim()\n .replace(\"export interface\", \"interface\");\n\n const OutputType = outputJsonType\n .trim()\n .replace(\"export interface\", \"interface\");\n\n availableTypes += `\\n${InputType}`;\n availableTypes += `\\n${OutputType}`;\n availableTools += `\\n\\t/*\\n\\t${tool.description?.trim()}\\n\\t*/`;\n availableTools += `\\n\\t${toolName}: (input: ${toCamelCase(toolName)}Input) => Promise<${toCamelCase(toolName)}Output>;`;\n availableTools += \"\\n\";\n }\n\n availableTools = `\\ndeclare const codemode: {${availableTools}}`;\n\n return `\n${availableTypes}\n${availableTools}\n `;\n}\n\nfunction getToolDescriptions(tools: ToolSet) {\n return Object.entries(tools)\n .map(([_toolName, tool]) => {\n return `\\n- ${tool.description?.trim()}`;\n })\n .join(\"\");\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,13 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudflare/codemode",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "",
|
|
5
|
-
"
|
|
3
|
+
"version": "0.0.4",
|
|
4
|
+
"description": "Code Mode: use LLMs to generate executable code that performs tool calls",
|
|
5
|
+
"repository": {
|
|
6
|
+
"directory": "packages/codemode",
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/cloudflare/agents.git"
|
|
9
|
+
},
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/cloudflare/agents/issues"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"zod-to-ts": "^2.0.0"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"ai": "^6.0.15",
|
|
18
|
+
"zod": "^4.3.5"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"agents": "^0.3.4",
|
|
22
|
+
"ai": "^6.0.0",
|
|
23
|
+
"zod": "^3.25.0 || ^4.0.0"
|
|
24
|
+
},
|
|
25
|
+
"exports": {
|
|
26
|
+
"./ai": {
|
|
27
|
+
"types": "./dist/ai.d.ts",
|
|
28
|
+
"import": "./dist/ai.js",
|
|
29
|
+
"require": "./dist/ai.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
6
32
|
"scripts": {
|
|
7
|
-
"
|
|
33
|
+
"build": "tsx ./scripts/build.ts"
|
|
8
34
|
},
|
|
9
|
-
"keywords": [
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
35
|
+
"keywords": [
|
|
36
|
+
"cloudflare",
|
|
37
|
+
"agents",
|
|
38
|
+
"ai",
|
|
39
|
+
"llm",
|
|
40
|
+
"codemode"
|
|
41
|
+
],
|
|
42
|
+
"author": "Cloudflare Inc.",
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"type": "module"
|
|
13
45
|
}
|
package/scripts/build.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { build } from "tsdown";
|
|
3
|
+
|
|
4
|
+
async function main() {
|
|
5
|
+
await build({
|
|
6
|
+
clean: true,
|
|
7
|
+
dts: true,
|
|
8
|
+
entry: ["src/ai.ts"],
|
|
9
|
+
skipNodeModulesBundle: true,
|
|
10
|
+
external: ["cloudflare:workers"],
|
|
11
|
+
format: "esm",
|
|
12
|
+
sourcemap: true,
|
|
13
|
+
fixedExtension: false
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// then run prettier on the generated .d.ts files
|
|
17
|
+
execSync("prettier --write ./dist/*.d.ts");
|
|
18
|
+
|
|
19
|
+
process.exit(0);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
main().catch((err) => {
|
|
23
|
+
// Build failures should fail
|
|
24
|
+
console.error(err);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
});
|
package/src/ai.ts
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { generateObject, tool, type ToolSet } from "ai";
|
|
2
|
+
import { openai } from "@ai-sdk/openai";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { compile as compileJsonSchemaToTs } from "json-schema-to-typescript";
|
|
5
|
+
import {
|
|
6
|
+
zodToTs,
|
|
7
|
+
printNode as printNodeZodToTs,
|
|
8
|
+
createTypeAlias
|
|
9
|
+
} from "zod-to-ts";
|
|
10
|
+
import { getAgentByName } from "agents";
|
|
11
|
+
import { env, WorkerEntrypoint } from "cloudflare:workers";
|
|
12
|
+
|
|
13
|
+
function toCamelCase(str: string) {
|
|
14
|
+
return str
|
|
15
|
+
.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase())
|
|
16
|
+
.replace(/^[a-z]/, (letter) => letter.toUpperCase());
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class CodeModeProxy extends WorkerEntrypoint<
|
|
20
|
+
Cloudflare.Env,
|
|
21
|
+
{
|
|
22
|
+
binding: string;
|
|
23
|
+
name: string;
|
|
24
|
+
callback: string;
|
|
25
|
+
}
|
|
26
|
+
> {
|
|
27
|
+
async callFunction(options: { functionName: string; args: unknown[] }) {
|
|
28
|
+
const stub = (await getAgentByName(
|
|
29
|
+
// @ts-expect-error
|
|
30
|
+
env[this.ctx.props.binding] as DurableObjectNamespace<T>,
|
|
31
|
+
this.ctx.props.name
|
|
32
|
+
)) as DurableObjectStub;
|
|
33
|
+
// @ts-expect-error
|
|
34
|
+
return stub[this.ctx.props.callback](options.functionName, options.args);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function experimental_codemode(options: {
|
|
39
|
+
tools: ToolSet;
|
|
40
|
+
prompt: string;
|
|
41
|
+
globalOutbound: Fetcher;
|
|
42
|
+
loader: WorkerLoader;
|
|
43
|
+
proxy: Fetcher<CodeModeProxy>;
|
|
44
|
+
}): Promise<{
|
|
45
|
+
prompt: string;
|
|
46
|
+
tools: ToolSet;
|
|
47
|
+
}> {
|
|
48
|
+
const generatedTypes = await generateTypes(options.tools);
|
|
49
|
+
const prompt = `You are a helpful assistant. You have access to the "codemode" tool that can do different things:
|
|
50
|
+
|
|
51
|
+
${getToolDescriptions(options.tools)}
|
|
52
|
+
|
|
53
|
+
If the user asks to do anything that be achieveable by the codemode tool, then simply pass over control to it by giving it a simple function description. Don't be too verbose.
|
|
54
|
+
|
|
55
|
+
`;
|
|
56
|
+
|
|
57
|
+
const codemodeTool = tool({
|
|
58
|
+
description: "codemode: a tool that can generate code to achieve a goal",
|
|
59
|
+
inputSchema: z.object({
|
|
60
|
+
functionDescription: z.string()
|
|
61
|
+
}),
|
|
62
|
+
outputSchema: z.object({
|
|
63
|
+
code: z.string(),
|
|
64
|
+
result: z.any()
|
|
65
|
+
}),
|
|
66
|
+
execute: async ({ functionDescription }) => {
|
|
67
|
+
try {
|
|
68
|
+
const response = await generateObject({
|
|
69
|
+
model: openai("gpt-4.1"),
|
|
70
|
+
schema: z.object({
|
|
71
|
+
code: z.string()
|
|
72
|
+
}),
|
|
73
|
+
prompt: `You are a code generating machine.
|
|
74
|
+
|
|
75
|
+
In addition to regular javascript, you can also use the following functions:
|
|
76
|
+
|
|
77
|
+
${generatedTypes}
|
|
78
|
+
|
|
79
|
+
Respond only with the code, nothing else. Output javascript code.
|
|
80
|
+
|
|
81
|
+
Generate an async function that achieves the goal. This async function doesn't accept any arguments.
|
|
82
|
+
|
|
83
|
+
Here is user input: ${functionDescription}` // insert ts types for the tools here
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// console.log("args", response.object.args);
|
|
87
|
+
const evaluator = createEvaluator(response.object.code, {
|
|
88
|
+
proxy: options.proxy,
|
|
89
|
+
loader: options.loader
|
|
90
|
+
});
|
|
91
|
+
const result = await evaluator();
|
|
92
|
+
return { code: response.object.code, result: result };
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error("error", error);
|
|
95
|
+
throw error;
|
|
96
|
+
// return { code: "", result: error };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return { prompt, tools: { codemode: codemodeTool } };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function createEvaluator(
|
|
105
|
+
code: string,
|
|
106
|
+
options: {
|
|
107
|
+
loader: WorkerLoader;
|
|
108
|
+
proxy: Fetcher<CodeModeProxy>;
|
|
109
|
+
}
|
|
110
|
+
) {
|
|
111
|
+
return async () => {
|
|
112
|
+
const worker = options.loader.get(`code-${Math.random()}`, () => {
|
|
113
|
+
return {
|
|
114
|
+
compatibilityDate: "2025-06-01",
|
|
115
|
+
compatibilityFlags: ["nodejs_compat"],
|
|
116
|
+
mainModule: "foo.js",
|
|
117
|
+
modules: {
|
|
118
|
+
"foo.js": `
|
|
119
|
+
import { env, WorkerEntrypoint } from "cloudflare:workers";
|
|
120
|
+
|
|
121
|
+
export default class CodeModeWorker extends WorkerEntrypoint {
|
|
122
|
+
async evaluate() {
|
|
123
|
+
try {
|
|
124
|
+
const { CodeModeProxy } = env;
|
|
125
|
+
const codemode = new Proxy(
|
|
126
|
+
{},
|
|
127
|
+
{
|
|
128
|
+
get: (target, prop) => {
|
|
129
|
+
return (args) => {
|
|
130
|
+
return CodeModeProxy.callFunction({
|
|
131
|
+
functionName: prop,
|
|
132
|
+
args: args,
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
return await ${code}();
|
|
140
|
+
} catch (err) {
|
|
141
|
+
return {
|
|
142
|
+
err: err.message,
|
|
143
|
+
stack: err.stack
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
`
|
|
150
|
+
},
|
|
151
|
+
env: {
|
|
152
|
+
// insert keys and bindings to tools/ts functions here
|
|
153
|
+
CodeModeProxy: options.proxy
|
|
154
|
+
},
|
|
155
|
+
globalOutbound: null
|
|
156
|
+
};
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// @ts-expect-error TODO: fix this
|
|
160
|
+
return await worker.getEntrypoint().evaluate();
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function generateTypes(tools: ToolSet) {
|
|
165
|
+
let availableTools = "";
|
|
166
|
+
let availableTypes = "";
|
|
167
|
+
|
|
168
|
+
for (const [toolName, tool] of Object.entries(tools)) {
|
|
169
|
+
// @ts-expect-error TODO: fix this
|
|
170
|
+
const inputJsonType = tool.inputSchema.jsonSchema
|
|
171
|
+
? await compileJsonSchemaToTs(
|
|
172
|
+
// @ts-expect-error TODO: fix this
|
|
173
|
+
tool.inputSchema.jsonSchema,
|
|
174
|
+
`${toCamelCase(toolName)}Input`,
|
|
175
|
+
{
|
|
176
|
+
format: false,
|
|
177
|
+
bannerComment: " "
|
|
178
|
+
}
|
|
179
|
+
)
|
|
180
|
+
: printNodeZodToTs(
|
|
181
|
+
createTypeAlias(
|
|
182
|
+
zodToTs(
|
|
183
|
+
// @ts-expect-error TODO: fix this
|
|
184
|
+
tool.inputSchema,
|
|
185
|
+
`${toCamelCase(toolName)}Input`
|
|
186
|
+
).node,
|
|
187
|
+
`${toCamelCase(toolName)}Input`
|
|
188
|
+
)
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const outputJsonType =
|
|
192
|
+
// @ts-expect-error TODO: fix this
|
|
193
|
+
tool.outputSchema?.jsonSchema
|
|
194
|
+
? await compileJsonSchemaToTs(
|
|
195
|
+
// @ts-expect-error TODO: fix this
|
|
196
|
+
tool.outputSchema?.jsonSchema,
|
|
197
|
+
`${toCamelCase(toolName)}Output`,
|
|
198
|
+
{
|
|
199
|
+
format: false,
|
|
200
|
+
bannerComment: " "
|
|
201
|
+
}
|
|
202
|
+
)
|
|
203
|
+
: tool.outputSchema
|
|
204
|
+
? printNodeZodToTs(
|
|
205
|
+
createTypeAlias(
|
|
206
|
+
zodToTs(
|
|
207
|
+
// @ts-expect-error TODO: fix this
|
|
208
|
+
tool.outputSchema,
|
|
209
|
+
`${toCamelCase(toolName)}Output`
|
|
210
|
+
).node,
|
|
211
|
+
`${toCamelCase(toolName)}Output`
|
|
212
|
+
)
|
|
213
|
+
)
|
|
214
|
+
: `interface ${toCamelCase(toolName)}Output { [key: string]: any }`;
|
|
215
|
+
|
|
216
|
+
const InputType = inputJsonType
|
|
217
|
+
.trim()
|
|
218
|
+
.replace("export interface", "interface");
|
|
219
|
+
|
|
220
|
+
const OutputType = outputJsonType
|
|
221
|
+
.trim()
|
|
222
|
+
.replace("export interface", "interface");
|
|
223
|
+
|
|
224
|
+
availableTypes += `\n${InputType}`;
|
|
225
|
+
availableTypes += `\n${OutputType}`;
|
|
226
|
+
availableTools += `\n\t/*\n\t${tool.description?.trim()}\n\t*/`;
|
|
227
|
+
availableTools += `\n\t${toolName}: (input: ${toCamelCase(toolName)}Input) => Promise<${toCamelCase(toolName)}Output>;`;
|
|
228
|
+
availableTools += "\n";
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
availableTools = `\ndeclare const codemode: {${availableTools}}`;
|
|
232
|
+
|
|
233
|
+
return `
|
|
234
|
+
${availableTypes}
|
|
235
|
+
${availableTools}
|
|
236
|
+
`;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function getToolDescriptions(tools: ToolSet) {
|
|
240
|
+
return Object.entries(tools)
|
|
241
|
+
.map(([_toolName, tool]) => {
|
|
242
|
+
return `\n- ${tool.description?.trim()}`;
|
|
243
|
+
})
|
|
244
|
+
.join("");
|
|
245
|
+
}
|
package/tsconfig.json
ADDED