@standardagents/cli 0.9.2 → 0.9.3
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/dist/index.js +613 -1170
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,53 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
3
|
+
import fs, { readFileSync } from 'fs';
|
|
4
|
+
import path, { dirname, resolve } from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import readline from 'readline';
|
|
7
|
+
import { spawn } from 'child_process';
|
|
6
8
|
import chalk from 'chalk';
|
|
9
|
+
import { parse, modify, applyEdits } from 'jsonc-parser';
|
|
10
|
+
import { loadFile, writeFile, generateCode } from 'magicast';
|
|
11
|
+
import { addVitePlugin } from 'magicast/helpers';
|
|
7
12
|
|
|
8
|
-
function findWranglerConfig(cwd) {
|
|
9
|
-
const jsonc = path3.join(cwd, "wrangler.jsonc");
|
|
10
|
-
const json = path3.join(cwd, "wrangler.json");
|
|
11
|
-
if (fs.existsSync(jsonc)) {
|
|
12
|
-
return jsonc;
|
|
13
|
-
}
|
|
14
|
-
if (fs.existsSync(json)) {
|
|
15
|
-
return json;
|
|
16
|
-
}
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
function readWranglerConfig(filePath) {
|
|
20
|
-
const text = fs.readFileSync(filePath, "utf-8");
|
|
21
|
-
const config = parse(text);
|
|
22
|
-
return { config, text };
|
|
23
|
-
}
|
|
24
|
-
function updateWranglerConfig(filePath, text, updates) {
|
|
25
|
-
let result = text;
|
|
26
|
-
if (updates.durable_objects) {
|
|
27
|
-
const edits = modify(result, ["durable_objects"], updates.durable_objects, {});
|
|
28
|
-
result = applyEdits(result, edits);
|
|
29
|
-
}
|
|
30
|
-
if (updates.migrations) {
|
|
31
|
-
const edits = modify(result, ["migrations"], updates.migrations, {});
|
|
32
|
-
result = applyEdits(result, edits);
|
|
33
|
-
}
|
|
34
|
-
if (updates.env) {
|
|
35
|
-
for (const [envName, envConfig] of Object.entries(updates.env)) {
|
|
36
|
-
if (envConfig.durable_objects) {
|
|
37
|
-
const edits = modify(result, ["env", envName, "durable_objects"], envConfig.durable_objects, {});
|
|
38
|
-
result = applyEdits(result, edits);
|
|
39
|
-
}
|
|
40
|
-
if (envConfig.migrations) {
|
|
41
|
-
const edits = modify(result, ["env", envName, "migrations"], envConfig.migrations, {});
|
|
42
|
-
result = applyEdits(result, edits);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return result;
|
|
47
|
-
}
|
|
48
|
-
function writeWranglerConfig(filePath, content) {
|
|
49
|
-
fs.writeFileSync(filePath, content, "utf-8");
|
|
50
|
-
}
|
|
51
13
|
var logger = {
|
|
52
14
|
success: (message) => {
|
|
53
15
|
console.log(chalk.green("\u2713"), message);
|
|
@@ -65,24 +27,21 @@ var logger = {
|
|
|
65
27
|
console.log(message);
|
|
66
28
|
}
|
|
67
29
|
};
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
logger.info("Initializing Standard Agents configuration...");
|
|
74
|
-
const configPath = findWranglerConfig(cwd);
|
|
75
|
-
if (!configPath) {
|
|
76
|
-
logger.error("No wrangler.jsonc or wrangler.json found in current directory");
|
|
77
|
-
logger.log("\nTo use Standard Agents, you need a Cloudflare Workers project with wrangler configuration.");
|
|
78
|
-
logger.log("\nExample wrangler.jsonc:");
|
|
79
|
-
logger.log(`
|
|
80
|
-
{
|
|
81
|
-
"name": "my-agent",
|
|
82
|
-
"main": "server/index.ts",
|
|
83
|
-
"compatibility_date": "2025-08-13",
|
|
30
|
+
var WRANGLER_TEMPLATE = (name) => `{
|
|
31
|
+
"$schema": "node_modules/wrangler/config-schema.json",
|
|
32
|
+
"name": "${name}",
|
|
33
|
+
"main": "worker/index.ts",
|
|
34
|
+
"compatibility_date": "2025-01-01",
|
|
84
35
|
"compatibility_flags": ["nodejs_compat"],
|
|
85
|
-
|
|
36
|
+
"observability": {
|
|
37
|
+
"enabled": true
|
|
38
|
+
},
|
|
39
|
+
"assets": {
|
|
40
|
+
"directory": "dist",
|
|
41
|
+
"not_found_handling": "single-page-application",
|
|
42
|
+
"binding": "ASSETS",
|
|
43
|
+
"run_worker_first": ["/**"]
|
|
44
|
+
},
|
|
86
45
|
"durable_objects": {
|
|
87
46
|
"bindings": [
|
|
88
47
|
{
|
|
@@ -95,103 +54,39 @@ async function init(options = {}) {
|
|
|
95
54
|
}
|
|
96
55
|
]
|
|
97
56
|
},
|
|
98
|
-
|
|
99
57
|
"migrations": [
|
|
100
58
|
{
|
|
101
59
|
"tag": "v1",
|
|
102
|
-
"new_sqlite_classes": ["DurableThread"
|
|
60
|
+
"new_sqlite_classes": ["DurableThread"]
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"tag": "v2",
|
|
64
|
+
"new_sqlite_classes": ["DurableAgentBuilder"]
|
|
103
65
|
}
|
|
104
66
|
]
|
|
105
67
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
if (!hasAgentBuilderThread || options.force) {
|
|
130
|
-
updates.durable_objects.bindings.push({
|
|
131
|
-
name: "AGENT_BUILDER_THREAD",
|
|
132
|
-
class_name: "DurableThread"
|
|
133
|
-
});
|
|
134
|
-
logger.info("Added AGENT_BUILDER_THREAD binding");
|
|
135
|
-
}
|
|
136
|
-
if (!hasAgentBuilder || options.force) {
|
|
137
|
-
updates.durable_objects.bindings.push({
|
|
138
|
-
name: "AGENT_BUILDER",
|
|
139
|
-
class_name: "DurableAgentBuilder"
|
|
140
|
-
});
|
|
141
|
-
logger.info("Added AGENT_BUILDER binding");
|
|
142
|
-
}
|
|
143
|
-
if (!config.migrations || config.migrations.length === 0) {
|
|
144
|
-
updates.migrations = [
|
|
145
|
-
{
|
|
146
|
-
tag: "v1",
|
|
147
|
-
new_sqlite_classes: ["DurableThread", "DurableAgentBuilder"]
|
|
148
|
-
}
|
|
149
|
-
];
|
|
150
|
-
logger.info("Added Durable Object migrations");
|
|
151
|
-
}
|
|
152
|
-
if ((_e = config.env) == null ? void 0 : _e.test) {
|
|
153
|
-
updates.env = updates.env || {};
|
|
154
|
-
updates.env.test = config.env.test;
|
|
155
|
-
if (!updates.env.test.durable_objects) {
|
|
156
|
-
updates.env.test.durable_objects = { bindings: [] };
|
|
157
|
-
}
|
|
158
|
-
const hasTestThread = updates.env.test.durable_objects.bindings.some(
|
|
159
|
-
(binding) => binding.name === "AGENT_BUILDER_THREAD"
|
|
160
|
-
);
|
|
161
|
-
const hasTestBuilder = updates.env.test.durable_objects.bindings.some(
|
|
162
|
-
(binding) => binding.name === "AGENT_BUILDER"
|
|
163
|
-
);
|
|
164
|
-
if (!hasTestThread) {
|
|
165
|
-
updates.env.test.durable_objects.bindings.push({
|
|
166
|
-
name: "AGENT_BUILDER_THREAD",
|
|
167
|
-
class_name: "DurableThread"
|
|
168
|
-
});
|
|
169
|
-
logger.info("Added test environment AGENT_BUILDER_THREAD binding");
|
|
170
|
-
}
|
|
171
|
-
if (!hasTestBuilder) {
|
|
172
|
-
updates.env.test.durable_objects.bindings.push({
|
|
173
|
-
name: "AGENT_BUILDER",
|
|
174
|
-
class_name: "DurableAgentBuilder"
|
|
175
|
-
});
|
|
176
|
-
logger.info("Added test environment AGENT_BUILDER binding");
|
|
177
|
-
}
|
|
178
|
-
if (!updates.env.test.migrations) {
|
|
179
|
-
updates.env.test.migrations = [
|
|
180
|
-
{
|
|
181
|
-
tag: "v1",
|
|
182
|
-
new_sqlite_classes: ["DurableThread", "DurableAgentBuilder"]
|
|
183
|
-
}
|
|
184
|
-
];
|
|
185
|
-
logger.info("Added test environment Durable Object migrations");
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
const updatedText = updateWranglerConfig(configPath, text, updates);
|
|
189
|
-
writeWranglerConfig(configPath, updatedText);
|
|
190
|
-
logger.success("Configuration updated successfully!");
|
|
191
|
-
logger.log("\nNext steps:");
|
|
192
|
-
logger.log("1. Create your agent definitions in agents/agents/");
|
|
193
|
-
logger.log("2. Start your development server");
|
|
194
|
-
}
|
|
68
|
+
`;
|
|
69
|
+
var THREAD_TS = `import { DurableThread } from 'virtual:@standardagents/builder'
|
|
70
|
+
|
|
71
|
+
export default class Thread extends DurableThread {}
|
|
72
|
+
`;
|
|
73
|
+
var AGENT_BUILDER_TS = `import { DurableAgentBuilder } from 'virtual:@standardagents/builder'
|
|
74
|
+
|
|
75
|
+
export default class AgentBuilder extends DurableAgentBuilder {}
|
|
76
|
+
`;
|
|
77
|
+
var WORKER_INDEX = `import { router } from "virtual:@standardagents/builder"
|
|
78
|
+
import DurableThread from '../agents/Thread';
|
|
79
|
+
import DurableAgentBuilder from '../agents/AgentBuilder';
|
|
80
|
+
|
|
81
|
+
export default {
|
|
82
|
+
async fetch(request, env) {
|
|
83
|
+
const res = await router(request, env)
|
|
84
|
+
return res ?? new Response(null, { status: 404 })
|
|
85
|
+
},
|
|
86
|
+
} satisfies ExportedHandler<Env>
|
|
87
|
+
|
|
88
|
+
export { DurableThread, DurableAgentBuilder }
|
|
89
|
+
`;
|
|
195
90
|
var TOOLS_CLAUDE_MD = `# Custom Tools
|
|
196
91
|
|
|
197
92
|
This directory contains custom tools that your AI agents can call during execution.
|
|
@@ -206,216 +101,28 @@ Tools are functions that extend your agent's capabilities beyond text generation
|
|
|
206
101
|
|
|
207
102
|
## Creating a Tool
|
|
208
103
|
|
|
209
|
-
Create a new file in this directory
|
|
210
|
-
|
|
211
|
-
\`\`\`
|
|
212
|
-
agents/tools/
|
|
213
|
-
\u251C\u2500\u2500 my_tool.ts # \u2713 snake_case recommended
|
|
214
|
-
\u251C\u2500\u2500 another_tool.ts # \u2713 Valid
|
|
215
|
-
\u2514\u2500\u2500 CamelCaseTool.ts # \u26A0 Works but triggers warning
|
|
216
|
-
\`\`\`
|
|
217
|
-
|
|
218
|
-
### Tool File Structure
|
|
219
|
-
|
|
220
|
-
Each tool file should export a default function created with \`defineTool\`:
|
|
104
|
+
Create a new file in this directory:
|
|
221
105
|
|
|
222
106
|
\`\`\`typescript
|
|
223
107
|
import { defineTool } from '@standardagents/builder';
|
|
224
108
|
import { z } from 'zod';
|
|
225
109
|
|
|
226
|
-
// With arguments
|
|
227
110
|
export default defineTool(
|
|
228
111
|
'Description of what this tool does',
|
|
229
112
|
z.object({
|
|
230
113
|
param1: z.string().describe('Description of param1'),
|
|
231
|
-
param2: z.number().optional().describe('Optional parameter'),
|
|
232
114
|
}),
|
|
233
115
|
async (flow, args) => {
|
|
234
116
|
// Tool implementation
|
|
235
|
-
const result = await doSomething(args.param1, args.param2);
|
|
236
|
-
|
|
237
|
-
return {
|
|
238
|
-
status: 'success',
|
|
239
|
-
result: JSON.stringify(result)
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
);
|
|
243
|
-
|
|
244
|
-
// Without arguments
|
|
245
|
-
export default defineTool(
|
|
246
|
-
'Simple tool with no parameters',
|
|
247
|
-
async (flow) => {
|
|
248
117
|
return {
|
|
249
118
|
status: 'success',
|
|
250
|
-
result: '
|
|
119
|
+
result: JSON.stringify({ data: 'result' })
|
|
251
120
|
};
|
|
252
121
|
}
|
|
253
122
|
);
|
|
254
123
|
\`\`\`
|
|
255
124
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
The \`flow\` parameter provides execution context:
|
|
259
|
-
|
|
260
|
-
\`\`\`typescript
|
|
261
|
-
interface FlowState {
|
|
262
|
-
env: Env; // Cloudflare bindings (KV, R2, etc.)
|
|
263
|
-
storage: DurableObjectStorage; // Thread's SQLite storage
|
|
264
|
-
threadId: string; // Current thread ID
|
|
265
|
-
agentId: string; // Current agent ID
|
|
266
|
-
currentSide: 'a' | 'b'; // Which side is executing
|
|
267
|
-
turnCount: number; // Current turn number
|
|
268
|
-
context: Record<string, any>; // Arbitrary state data
|
|
269
|
-
// ... and more
|
|
270
|
-
}
|
|
271
|
-
\`\`\`
|
|
272
|
-
|
|
273
|
-
### Return Value
|
|
274
|
-
|
|
275
|
-
Tools must return a ToolResult object:
|
|
276
|
-
|
|
277
|
-
\`\`\`typescript
|
|
278
|
-
interface ToolResult {
|
|
279
|
-
status: 'success' | 'error';
|
|
280
|
-
result?: string; // Success data
|
|
281
|
-
error?: string; // Error message
|
|
282
|
-
}
|
|
283
|
-
\`\`\`
|
|
284
|
-
|
|
285
|
-
## Tool Discovery
|
|
286
|
-
|
|
287
|
-
Tools are **auto-discovered** at runtime. No manual registration needed!
|
|
288
|
-
|
|
289
|
-
1. Vite plugin scans this directory on startup
|
|
290
|
-
2. Generates virtual module with dynamic imports
|
|
291
|
-
3. Tools become available to all agents
|
|
292
|
-
4. HMR (Hot Module Replacement) works in development
|
|
293
|
-
|
|
294
|
-
## Examples
|
|
295
|
-
|
|
296
|
-
### API Fetch Tool
|
|
297
|
-
|
|
298
|
-
\`\`\`typescript
|
|
299
|
-
import { defineTool } from '@standardagents/builder';
|
|
300
|
-
import { z } from 'zod';
|
|
301
|
-
|
|
302
|
-
export default defineTool(
|
|
303
|
-
'Fetch weather data for a city',
|
|
304
|
-
z.object({
|
|
305
|
-
city: z.string().describe('City name'),
|
|
306
|
-
units: z.enum(['metric', 'imperial']).optional(),
|
|
307
|
-
}),
|
|
308
|
-
async (flow, args) => {
|
|
309
|
-
try {
|
|
310
|
-
const response = await fetch(
|
|
311
|
-
\`https://api.weather.com/\${args.city}\`
|
|
312
|
-
);
|
|
313
|
-
const data = await response.json();
|
|
314
|
-
|
|
315
|
-
return {
|
|
316
|
-
status: 'success',
|
|
317
|
-
result: JSON.stringify(data),
|
|
318
|
-
};
|
|
319
|
-
} catch (error) {
|
|
320
|
-
return {
|
|
321
|
-
status: 'error',
|
|
322
|
-
error: error.message,
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
);
|
|
327
|
-
\`\`\`
|
|
328
|
-
|
|
329
|
-
### Thread Storage Tool
|
|
330
|
-
|
|
331
|
-
\`\`\`typescript
|
|
332
|
-
import { defineTool } from '@standardagents/builder';
|
|
333
|
-
import { z } from 'zod';
|
|
334
|
-
|
|
335
|
-
export default defineTool(
|
|
336
|
-
'Get custom data from thread storage',
|
|
337
|
-
z.object({
|
|
338
|
-
key: z.string().describe('Storage key to look up'),
|
|
339
|
-
}),
|
|
340
|
-
async (flow, args) => {
|
|
341
|
-
try {
|
|
342
|
-
// Use thread's SQLite storage
|
|
343
|
-
const result = await flow.storage.sql.exec(
|
|
344
|
-
\`SELECT value FROM custom_data WHERE key = ?\`,
|
|
345
|
-
args.key
|
|
346
|
-
).toArray();
|
|
347
|
-
|
|
348
|
-
if (result.length === 0) {
|
|
349
|
-
return {
|
|
350
|
-
status: 'error',
|
|
351
|
-
error: 'Data not found',
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
return {
|
|
356
|
-
status: 'success',
|
|
357
|
-
result: JSON.stringify(result[0]),
|
|
358
|
-
};
|
|
359
|
-
} catch (error) {
|
|
360
|
-
return {
|
|
361
|
-
status: 'error',
|
|
362
|
-
error: error.message,
|
|
363
|
-
};
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
);
|
|
367
|
-
\`\`\`
|
|
368
|
-
|
|
369
|
-
## Best Practices
|
|
370
|
-
|
|
371
|
-
1. **Descriptive Names**: Use clear, action-oriented names (e.g., \`fetch_weather\`, not \`weather\`)
|
|
372
|
-
2. **Detailed Descriptions**: The description helps the LLM understand when to use the tool
|
|
373
|
-
3. **Zod Descriptions**: Add \`.describe()\` to all schema fields for better LLM understanding
|
|
374
|
-
4. **Error Handling**: Always wrap in try/catch and return proper error status
|
|
375
|
-
5. **Type Safety**: Use Zod for runtime validation and TypeScript inference
|
|
376
|
-
6. **Keep It Simple**: Each tool should do one thing well
|
|
377
|
-
7. **Stateless**: Don't rely on global state; use FlowState for context
|
|
378
|
-
|
|
379
|
-
## Debugging
|
|
380
|
-
|
|
381
|
-
- Check console output for tool execution logs
|
|
382
|
-
- Tool errors appear in the logs table
|
|
383
|
-
- Use \`console.log\` within tools (visible in dev mode)
|
|
384
|
-
- Check message history to see tool calls and responses
|
|
385
|
-
|
|
386
|
-
## Testing
|
|
387
|
-
|
|
388
|
-
Tools can be tested independently:
|
|
389
|
-
|
|
390
|
-
\`\`\`typescript
|
|
391
|
-
import myTool from './my_tool';
|
|
392
|
-
|
|
393
|
-
const [description, schema, handler] = myTool;
|
|
394
|
-
|
|
395
|
-
// Mock FlowState
|
|
396
|
-
const mockFlow = {
|
|
397
|
-
env: mockEnv,
|
|
398
|
-
storage: mockStorage,
|
|
399
|
-
threadId: 'test-123',
|
|
400
|
-
// ...
|
|
401
|
-
};
|
|
402
|
-
|
|
403
|
-
const result = await handler(mockFlow, { param1: 'test' });
|
|
404
|
-
console.log(result);
|
|
405
|
-
\`\`\`
|
|
406
|
-
|
|
407
|
-
## Limitations
|
|
408
|
-
|
|
409
|
-
- No nested directories (tools must be directly in this folder)
|
|
410
|
-
- Tool execution is **sequential** (no parallel execution)
|
|
411
|
-
- Tool results must be JSON-serializable strings
|
|
412
|
-
- Maximum execution time limited by Workers CPU time
|
|
413
|
-
|
|
414
|
-
## Related
|
|
415
|
-
|
|
416
|
-
- **Hooks**: \`agents/hooks/CLAUDE.md\` - Lifecycle hooks
|
|
417
|
-
- **APIs**: \`agents/api/CLAUDE.md\` - Thread-specific endpoints
|
|
418
|
-
- **Documentation**: Project root \`CLAUDE.md\` for architecture overview
|
|
125
|
+
Tools are auto-discovered at runtime. No manual registration needed!
|
|
419
126
|
`;
|
|
420
127
|
var HOOKS_CLAUDE_MD = `# Lifecycle Hooks
|
|
421
128
|
|
|
@@ -428,901 +135,637 @@ Hooks are optional functions that run at specific points during agent execution:
|
|
|
428
135
|
- **After** LLM responses (post-process messages)
|
|
429
136
|
- At other lifecycle events (message creation, tool execution, etc.)
|
|
430
137
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
## Using defineHook for Type Safety
|
|
434
|
-
|
|
435
|
-
All hooks should use the \`defineHook\` utility for strict typing:
|
|
436
|
-
|
|
437
|
-
\`\`\`typescript
|
|
438
|
-
import { defineHook } from '@standardagents/builder';
|
|
439
|
-
|
|
440
|
-
export default defineHook('filter_messages', async (state, rows) => {
|
|
441
|
-
// TypeScript knows exactly what state and rows are!
|
|
442
|
-
return rows;
|
|
443
|
-
});
|
|
444
|
-
\`\`\`
|
|
445
|
-
|
|
446
|
-
The \`defineHook\` function provides:
|
|
447
|
-
- **Strict typing** for hook parameters based on the hook name
|
|
448
|
-
- **IntelliSense** support in your editor
|
|
449
|
-
- **Type checking** to catch errors before runtime
|
|
450
|
-
- **Better documentation** through type hints
|
|
451
|
-
|
|
452
|
-
## Available Hooks
|
|
453
|
-
|
|
454
|
-
### \`filter_messages\`
|
|
455
|
-
|
|
456
|
-
**When**: Before message history is transformed to chat completion format
|
|
457
|
-
**Purpose**: Filter or modify SQL row data with access to ALL database columns
|
|
458
|
-
|
|
459
|
-
\`\`\`typescript
|
|
460
|
-
import { defineHook, type MessageRow } from '@standardagents/builder';
|
|
461
|
-
|
|
462
|
-
export default defineHook('filter_messages', async (state, rows) => {
|
|
463
|
-
// rows contains ALL columns from messages table:
|
|
464
|
-
// id, role, content, name, tool_calls, tool_call_id, log_id,
|
|
465
|
-
// created_at, request_sent_at, response_completed_at, status,
|
|
466
|
-
// silent, tool_status
|
|
467
|
-
|
|
468
|
-
// Your filtering logic here
|
|
469
|
-
return rows;
|
|
470
|
-
});
|
|
471
|
-
\`\`\`
|
|
472
|
-
|
|
473
|
-
**Common Use Cases**:
|
|
474
|
-
- Filter out failed tool messages (\`tool_status = 'error'\`)
|
|
475
|
-
- Remove messages older than a certain time
|
|
476
|
-
- Filter by status (pending, completed, failed)
|
|
477
|
-
- Access columns not available in chat format
|
|
478
|
-
- Filter based on database-specific metadata
|
|
479
|
-
|
|
480
|
-
**Example - Filter Out Failed Tools**:
|
|
481
|
-
\`\`\`typescript
|
|
482
|
-
export default defineHook('filter_messages', async (state, rows) => {
|
|
483
|
-
// Remove tool messages that failed execution
|
|
484
|
-
return rows.filter(row => {
|
|
485
|
-
if (row.role === 'tool' && row.tool_status === 'error') {
|
|
486
|
-
return false;
|
|
487
|
-
}
|
|
488
|
-
return true;
|
|
489
|
-
});
|
|
490
|
-
});
|
|
491
|
-
\`\`\`
|
|
492
|
-
|
|
493
|
-
**Important**: This hook runs **before** messages are transformed to Message objects and receives raw SQL data. Use this when you need access to database columns like \`tool_status\`, \`silent\`, or \`status\`.
|
|
494
|
-
|
|
495
|
-
### \`prefilter_llm_history\`
|
|
496
|
-
|
|
497
|
-
**When**: Immediately after message transformation, before sending to LLM
|
|
498
|
-
**Purpose**: Modify, filter, or limit message history in chat completion format
|
|
138
|
+
## Creating a Hook
|
|
499
139
|
|
|
500
140
|
\`\`\`typescript
|
|
501
141
|
import { defineHook } from '@standardagents/builder';
|
|
502
142
|
|
|
503
143
|
export default defineHook('prefilter_llm_history', async (state, messages) => {
|
|
504
|
-
//
|
|
505
|
-
// Available fields: role, content, tool_calls, tool_call_id, name
|
|
506
|
-
|
|
507
|
-
// Your filtering logic here
|
|
144
|
+
// Filter or modify messages before sending to LLM
|
|
508
145
|
return messages;
|
|
509
146
|
});
|
|
510
147
|
\`\`\`
|
|
511
148
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
- Summarize old messages before sending
|
|
516
|
-
- Add dynamic context based on message patterns
|
|
517
|
-
- Filter out sensitive information
|
|
149
|
+
Hook files must be named exactly as the hook type (e.g., \`prefilter_llm_history.ts\`).
|
|
150
|
+
`;
|
|
151
|
+
var API_CLAUDE_MD = `# Thread-Specific API Endpoints
|
|
518
152
|
|
|
519
|
-
|
|
520
|
-
\`\`\`typescript
|
|
521
|
-
import { defineHook } from '@standardagents/builder';
|
|
153
|
+
This directory contains custom API endpoints that operate on specific threads.
|
|
522
154
|
|
|
523
|
-
|
|
524
|
-
// Keep all system messages
|
|
525
|
-
const systemMessages = messages.filter(m => m.role === 'system');
|
|
155
|
+
## What Are Thread Endpoints?
|
|
526
156
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
157
|
+
Thread endpoints are API routes that:
|
|
158
|
+
- Automatically receive a specific thread's Durable Object instance
|
|
159
|
+
- Can access thread-local SQLite storage
|
|
160
|
+
- Perform operations in the context of a conversation
|
|
161
|
+
- Use file-based routing for automatic discovery
|
|
531
162
|
|
|
532
|
-
|
|
533
|
-
});
|
|
534
|
-
\`\`\`
|
|
163
|
+
## Creating an Endpoint
|
|
535
164
|
|
|
536
|
-
**Example - Remove Old Tool Messages**:
|
|
537
165
|
\`\`\`typescript
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
export default defineHook('prefilter_llm_history', async (state, messages) => {
|
|
541
|
-
// Keep messages from last 5 turns, remove old tool messages
|
|
542
|
-
const recentThreshold = messages.length - 10;
|
|
166
|
+
// agents/api/summary.get.ts -> GET /agents/api/threads/:id/summary
|
|
167
|
+
import { defineThreadEndpoint } from '@standardagents/builder';
|
|
543
168
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
}
|
|
548
|
-
return true;
|
|
169
|
+
export default defineThreadEndpoint(async (thread, request, env) => {
|
|
170
|
+
const messages = await thread.getMessages();
|
|
171
|
+
return new Response(JSON.stringify({ count: messages.length }), {
|
|
172
|
+
headers: { 'Content-Type': 'application/json' }
|
|
549
173
|
});
|
|
550
174
|
});
|
|
551
175
|
\`\`\`
|
|
552
176
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
**When**: After receiving a response from the LLM
|
|
558
|
-
**Purpose**: Modify or enhance the assistant's message
|
|
559
|
-
|
|
560
|
-
\`\`\`typescript
|
|
561
|
-
import { defineHook } from '@standardagents/builder';
|
|
177
|
+
Pattern: \`{name}.{method}.ts\` (e.g., \`summary.get.ts\`, \`export.post.ts\`)
|
|
178
|
+
`;
|
|
179
|
+
var AGENTS_CLAUDE_MD = `# Agent Definitions
|
|
562
180
|
|
|
563
|
-
|
|
564
|
-
// Your post-processing logic here
|
|
565
|
-
return message;
|
|
566
|
-
});
|
|
567
|
-
\`\`\`
|
|
181
|
+
This directory contains your AI agent definitions.
|
|
568
182
|
|
|
569
|
-
|
|
570
|
-
- Format or clean up LLM output
|
|
571
|
-
- Add metadata or tracking information
|
|
572
|
-
- Inject additional context into responses
|
|
573
|
-
- Transform tool call formats
|
|
183
|
+
## Creating an Agent
|
|
574
184
|
|
|
575
|
-
**Example - Add Metadata**:
|
|
576
185
|
\`\`\`typescript
|
|
577
|
-
import {
|
|
186
|
+
import { defineAgent } from '@standardagents/builder';
|
|
578
187
|
|
|
579
|
-
export default
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
188
|
+
export default defineAgent({
|
|
189
|
+
name: 'my-agent',
|
|
190
|
+
type: 'ai_human',
|
|
191
|
+
title: 'My Agent',
|
|
192
|
+
defaultPrompt: 'my-prompt',
|
|
193
|
+
defaultModel: 'gpt-4o',
|
|
194
|
+
tools: ['my_tool'],
|
|
584
195
|
});
|
|
585
196
|
\`\`\`
|
|
586
197
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
#### \`before_create_message\`
|
|
594
|
-
|
|
595
|
-
**When**: Immediately before a message is inserted into the database
|
|
596
|
-
**Purpose**: Modify message data before it's stored
|
|
597
|
-
|
|
598
|
-
\`\`\`typescript
|
|
599
|
-
import { defineHook } from '@standardagents/builder';
|
|
600
|
-
|
|
601
|
-
export default defineHook('before_create_message', async (state, message) => {
|
|
602
|
-
// Your modification logic here
|
|
603
|
-
return message;
|
|
604
|
-
});
|
|
605
|
-
\`\`\`
|
|
198
|
+
Agent types:
|
|
199
|
+
- \`ai_human\`: Human interacts with AI agent
|
|
200
|
+
- \`dual_ai\`: Two AI agents interact with each other
|
|
201
|
+
`;
|
|
202
|
+
var PROMPTS_CLAUDE_MD = `# Prompt Definitions
|
|
606
203
|
|
|
607
|
-
|
|
608
|
-
\`\`\`typescript
|
|
609
|
-
{
|
|
610
|
-
id: string;
|
|
611
|
-
role: string;
|
|
612
|
-
content: string | null;
|
|
613
|
-
tool_calls?: string | null;
|
|
614
|
-
tool_call_id?: string | null;
|
|
615
|
-
name?: string | null;
|
|
616
|
-
created_at: number;
|
|
617
|
-
status?: string;
|
|
618
|
-
silent?: boolean;
|
|
619
|
-
}
|
|
620
|
-
\`\`\`
|
|
204
|
+
This directory contains your prompt/system message definitions.
|
|
621
205
|
|
|
622
|
-
|
|
623
|
-
- Add prefixes or metadata to message content
|
|
624
|
-
- Modify message based on agent configuration
|
|
625
|
-
- Add tracking IDs or identifiers
|
|
626
|
-
- Transform message format before storage
|
|
206
|
+
## Creating a Prompt
|
|
627
207
|
|
|
628
|
-
**Example - Add Metadata**:
|
|
629
208
|
\`\`\`typescript
|
|
630
|
-
import {
|
|
209
|
+
import { definePrompt } from '@standardagents/builder';
|
|
631
210
|
|
|
632
|
-
export default
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
return message;
|
|
211
|
+
export default definePrompt({
|
|
212
|
+
name: 'my-prompt',
|
|
213
|
+
model: 'gpt-4o',
|
|
214
|
+
systemPrompt: 'You are a helpful assistant...',
|
|
215
|
+
tools: ['search', 'calculate'],
|
|
638
216
|
});
|
|
639
217
|
\`\`\`
|
|
218
|
+
`;
|
|
219
|
+
var MODELS_CLAUDE_MD = `# Model Definitions
|
|
640
220
|
|
|
641
|
-
|
|
221
|
+
This directory contains your AI model configurations.
|
|
642
222
|
|
|
643
|
-
|
|
644
|
-
**Purpose**: Perform actions based on newly created messages
|
|
223
|
+
## Creating a Model
|
|
645
224
|
|
|
646
225
|
\`\`\`typescript
|
|
647
|
-
import {
|
|
226
|
+
import { defineModel } from '@standardagents/builder';
|
|
648
227
|
|
|
649
|
-
export default
|
|
650
|
-
|
|
651
|
-
|
|
228
|
+
export default defineModel({
|
|
229
|
+
name: 'gpt-4o',
|
|
230
|
+
model: 'gpt-4o',
|
|
231
|
+
provider: 'openai',
|
|
232
|
+
inputPrice: 2.5,
|
|
233
|
+
outputPrice: 10,
|
|
652
234
|
});
|
|
653
235
|
\`\`\`
|
|
654
236
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
**Example - Log to External Service**:
|
|
662
|
-
\`\`\`typescript
|
|
663
|
-
import { defineHook } from '@standardagents/builder';
|
|
664
|
-
|
|
665
|
-
export default defineHook('after_create_message', async (state, message) => {
|
|
666
|
-
// Log all assistant messages to analytics
|
|
667
|
-
if (message.role === 'assistant' && message.content) {
|
|
237
|
+
Supported providers: \`openai\`, \`anthropic\`, \`openrouter\`, \`google\`
|
|
238
|
+
`;
|
|
239
|
+
function getProjectName(cwd) {
|
|
240
|
+
const packageJsonPath = path.join(cwd, "package.json");
|
|
241
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
668
242
|
try {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
contentLength: message.content.length,
|
|
675
|
-
})
|
|
676
|
-
});
|
|
677
|
-
} catch (error) {
|
|
678
|
-
console.error('Analytics logging failed:', error);
|
|
243
|
+
const pkg2 = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
244
|
+
if (pkg2.name) {
|
|
245
|
+
return pkg2.name.replace(/^@[^/]+\//, "");
|
|
246
|
+
}
|
|
247
|
+
} catch {
|
|
679
248
|
}
|
|
680
249
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
import { defineHook } from '@standardagents/builder';
|
|
691
|
-
|
|
692
|
-
export default defineHook('before_update_message', async (state, messageId, updates) => {
|
|
693
|
-
// Your modification logic here
|
|
694
|
-
return updates;
|
|
695
|
-
});
|
|
696
|
-
\`\`\`
|
|
697
|
-
|
|
698
|
-
**Common Use Cases**:
|
|
699
|
-
- Validate or sanitize updated content
|
|
700
|
-
- Add completion timestamps
|
|
701
|
-
- Transform status values
|
|
702
|
-
- Inject metadata into updates
|
|
703
|
-
|
|
704
|
-
**Example - Add Completion Timestamp**:
|
|
705
|
-
\`\`\`typescript
|
|
706
|
-
import { defineHook } from '@standardagents/builder';
|
|
707
|
-
|
|
708
|
-
export default defineHook('before_update_message', async (state, messageId, updates) => {
|
|
709
|
-
// Add custom completion timestamp for completed messages
|
|
710
|
-
if (updates.status === 'completed' && !updates.response_completed_at) {
|
|
711
|
-
updates.response_completed_at = Date.now() * 1000; // microseconds
|
|
250
|
+
return path.basename(cwd);
|
|
251
|
+
}
|
|
252
|
+
function findViteConfig(cwd) {
|
|
253
|
+
const candidates = ["vite.config.ts", "vite.config.js", "vite.config.mts", "vite.config.mjs"];
|
|
254
|
+
for (const candidate of candidates) {
|
|
255
|
+
const configPath = path.join(cwd, candidate);
|
|
256
|
+
if (fs.existsSync(configPath)) {
|
|
257
|
+
return configPath;
|
|
258
|
+
}
|
|
712
259
|
}
|
|
713
|
-
return
|
|
714
|
-
});
|
|
715
|
-
\`\`\`
|
|
716
|
-
|
|
717
|
-
#### \`after_update_message\`
|
|
718
|
-
|
|
719
|
-
**When**: Immediately after a message is updated in the database
|
|
720
|
-
**Purpose**: Perform actions based on message updates
|
|
721
|
-
|
|
722
|
-
\`\`\`typescript
|
|
723
|
-
import { defineHook } from '@standardagents/builder';
|
|
724
|
-
|
|
725
|
-
export default defineHook('after_update_message', async (state, messageId, updates) => {
|
|
726
|
-
// Your post-update logic here
|
|
727
|
-
// No return value needed
|
|
728
|
-
});
|
|
729
|
-
\`\`\`
|
|
730
|
-
|
|
731
|
-
**Common Use Cases**:
|
|
732
|
-
- Track message status changes
|
|
733
|
-
- Trigger notifications on completion
|
|
734
|
-
- Update external systems
|
|
735
|
-
- Log state transitions
|
|
736
|
-
|
|
737
|
-
**Example - Notify on Failure**:
|
|
738
|
-
\`\`\`typescript
|
|
739
|
-
import { defineHook } from '@standardagents/builder';
|
|
740
|
-
|
|
741
|
-
export default defineHook('after_update_message', async (state, messageId, updates) => {
|
|
742
|
-
// Send notification when message is marked as failed
|
|
743
|
-
if (updates.status === 'failed') {
|
|
744
|
-
console.log(\`[Alert] Message \${messageId} failed in thread \${state.threadId}\`);
|
|
745
|
-
}
|
|
746
|
-
});
|
|
747
|
-
\`\`\`
|
|
748
|
-
|
|
749
|
-
**Important**:
|
|
750
|
-
- \`before_*\` hooks must return the modified data object
|
|
751
|
-
- \`after_*\` hooks don't need to return anything
|
|
752
|
-
- All 4 hooks run for ANY message operation (agent, tool, user, system messages)
|
|
753
|
-
- Updates passed to update hooks contain only the fields being updated
|
|
754
|
-
|
|
755
|
-
## Creating a Hook
|
|
756
|
-
|
|
757
|
-
1. Create a file named exactly as the hook (e.g., \`prefilter_llm_history.ts\`)
|
|
758
|
-
2. Use \`defineHook\` to ensure correct typing
|
|
759
|
-
3. Return the modified data (or original if no changes)
|
|
760
|
-
|
|
761
|
-
\`\`\`typescript
|
|
762
|
-
// agents/hooks/prefilter_llm_history.ts
|
|
763
|
-
import { defineHook } from '@standardagents/builder';
|
|
764
|
-
|
|
765
|
-
export default defineHook('prefilter_llm_history', async (state, messages) => {
|
|
766
|
-
console.log(\`Processing \${messages.length} messages\`);
|
|
767
|
-
|
|
768
|
-
// Your logic here
|
|
769
|
-
|
|
770
|
-
return messages; // Return modified or original
|
|
771
|
-
});
|
|
772
|
-
\`\`\`
|
|
773
|
-
|
|
774
|
-
## Hook Lifecycle
|
|
775
|
-
|
|
776
|
-
1. **Discovery**: Vite plugin scans this directory on startup
|
|
777
|
-
2. **Virtual Module**: Generates \`virtual:@standardagents-hooks\` with lazy imports
|
|
778
|
-
3. **Lazy Loading**: Hooks are loaded on first use (not at startup)
|
|
779
|
-
4. **Caching**: Once loaded, hooks are cached for performance
|
|
780
|
-
5. **HMR**: Changes trigger hot reload in development
|
|
781
|
-
|
|
782
|
-
## Error Handling
|
|
783
|
-
|
|
784
|
-
Hooks are designed to be **safe**:
|
|
785
|
-
- If hook file doesn't exist \u2192 Silently skipped (no error)
|
|
786
|
-
- If hook throws error \u2192 Logged to console, original data returned
|
|
787
|
-
- If hook returns invalid data \u2192 Original data used as fallback
|
|
788
|
-
|
|
789
|
-
This ensures hooks never break agent execution.
|
|
790
|
-
|
|
791
|
-
## FlowState Object
|
|
792
|
-
|
|
793
|
-
All hooks receive the FlowState context:
|
|
794
|
-
|
|
795
|
-
\`\`\`typescript
|
|
796
|
-
interface FlowState {
|
|
797
|
-
threadId: string; // Current thread ID
|
|
798
|
-
flowId: string; // Unique execution ID
|
|
799
|
-
agentConfig: Agent; // Full agent configuration
|
|
800
|
-
currentSide: 'a' | 'b'; // Which side is executing (dual_ai)
|
|
801
|
-
turnCount: number; // Current turn number
|
|
802
|
-
stopped: boolean; // Whether execution should stop
|
|
803
|
-
messageHistory: Message[]; // Full conversation history
|
|
804
|
-
env: Env; // Cloudflare bindings
|
|
805
|
-
storage: DurableObjectStorage; // Thread's SQLite storage
|
|
806
|
-
context: Record<string, any>; // Arbitrary state data
|
|
807
|
-
// ... and more
|
|
260
|
+
return null;
|
|
808
261
|
}
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
4. **Handle Errors**: Wrap risky operations in try/catch
|
|
819
|
-
5. **Document Behavior**: Add comments explaining what your hook does
|
|
820
|
-
6. **Test Thoroughly**: Hooks affect ALL agent executions
|
|
821
|
-
|
|
822
|
-
## Debugging
|
|
823
|
-
|
|
824
|
-
- Check console output for hook loading/execution logs
|
|
825
|
-
- Hook errors are logged with \`[Hooks] \u2717\` prefix
|
|
826
|
-
- Inspect logs table to see filtered messages (for prefilter hook)
|
|
827
|
-
- Use \`console.log\` within hooks for debugging
|
|
828
|
-
|
|
829
|
-
## Performance Considerations
|
|
830
|
-
|
|
831
|
-
Hooks run on **every turn**, so:
|
|
832
|
-
- Avoid expensive operations (heavy computation, slow APIs)
|
|
833
|
-
- Cache results when possible
|
|
834
|
-
- Consider using FlowState context to skip unnecessary work
|
|
835
|
-
- Use async operations efficiently
|
|
836
|
-
|
|
837
|
-
## Message Data & Tool Status
|
|
838
|
-
|
|
839
|
-
The \`filter_messages\` hook receives SQL row data with ALL columns from the messages table:
|
|
840
|
-
|
|
841
|
-
\`\`\`typescript
|
|
842
|
-
interface MessageRow {
|
|
843
|
-
id: string;
|
|
844
|
-
role: 'system' | 'user' | 'assistant' | 'tool';
|
|
845
|
-
content: string | null;
|
|
846
|
-
name: string | null;
|
|
847
|
-
tool_calls: string | null; // JSON string of tool calls
|
|
848
|
-
tool_call_id: string | null; // For role='tool' messages
|
|
849
|
-
log_id: string | null; // Reference to logs table
|
|
850
|
-
created_at: number; // Microseconds timestamp
|
|
851
|
-
request_sent_at: number | null; // When request was sent to LLM
|
|
852
|
-
response_completed_at: number | null; // When response completed
|
|
853
|
-
status: 'pending' | 'completed' | 'failed' | null;
|
|
854
|
-
silent: number | null; // 1 if message should be hidden, 0/null otherwise
|
|
855
|
-
tool_status: 'success' | 'error' | null; // Status of tool execution (tool messages only)
|
|
262
|
+
function findWranglerConfig(cwd) {
|
|
263
|
+
const candidates = ["wrangler.jsonc", "wrangler.json"];
|
|
264
|
+
for (const candidate of candidates) {
|
|
265
|
+
const configPath = path.join(cwd, candidate);
|
|
266
|
+
if (fs.existsSync(configPath)) {
|
|
267
|
+
return configPath;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return null;
|
|
856
271
|
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
- \u2713 \`after_update_message.ts\`
|
|
899
|
-
- \u2717 \`filterMessages.ts\` (wrong case)
|
|
900
|
-
- \u2717 \`prefilterLLMHistory.ts\` (wrong case)
|
|
901
|
-
- \u2717 \`before-create-message.ts\` (wrong separator)
|
|
902
|
-
|
|
903
|
-
The file name determines which hook point it intercepts.
|
|
904
|
-
|
|
905
|
-
## Available Hooks
|
|
906
|
-
|
|
907
|
-
Currently implemented hooks:
|
|
908
|
-
- \`filter_messages\` - Filter SQL row data before transformation to chat format (access to all DB columns including tool_status)
|
|
909
|
-
- \`prefilter_llm_history\` - Filter messages before sending to LLM (after transformation to chat format)
|
|
910
|
-
- \`before_create_message\` - Before inserting message into database
|
|
911
|
-
- \`after_create_message\` - After message is created in database
|
|
912
|
-
- \`before_update_message\` - Before updating message in database
|
|
913
|
-
- \`after_update_message\` - After message is updated in database
|
|
914
|
-
- \`after_tool_call_success\` - After successful tool execution (can modify result or return null to remove)
|
|
915
|
-
- \`after_tool_call_failure\` - After failed tool execution (can modify error or return null to remove)
|
|
916
|
-
|
|
917
|
-
## Related
|
|
918
|
-
|
|
919
|
-
- **Tools**: \`agents/tools/CLAUDE.md\` - Custom tools
|
|
920
|
-
- **APIs**: \`agents/api/CLAUDE.md\` - Thread endpoints
|
|
921
|
-
- **Documentation**: Project root \`CLAUDE.md\` for architecture
|
|
922
|
-
`;
|
|
923
|
-
var API_CLAUDE_MD = `# Thread-Specific API Endpoints
|
|
924
|
-
|
|
925
|
-
This directory contains custom API endpoints that operate on specific threads.
|
|
926
|
-
|
|
927
|
-
## What Are Thread Endpoints?
|
|
928
|
-
|
|
929
|
-
Thread endpoints are API routes that:
|
|
930
|
-
- Automatically receive a specific thread's Durable Object instance
|
|
931
|
-
- Can access thread-local SQLite storage
|
|
932
|
-
- Perform operations in the context of a conversation
|
|
933
|
-
- Use file-based routing for automatic discovery
|
|
934
|
-
|
|
935
|
-
## File-Based Routing
|
|
936
|
-
|
|
937
|
-
Create files following this naming convention:
|
|
938
|
-
|
|
939
|
-
\`\`\`
|
|
940
|
-
agents/api/
|
|
941
|
-
\u251C\u2500\u2500 summary.get.ts # GET /agents/api/threads/:id/summary
|
|
942
|
-
\u251C\u2500\u2500 export.post.ts # POST /agents/api/threads/:id/export
|
|
943
|
-
\u251C\u2500\u2500 metadata.put.ts # PUT /agents/api/threads/:id/metadata
|
|
944
|
-
\u2514\u2500\u2500 archive.delete.ts # DELETE /agents/api/threads/:id/archive
|
|
945
|
-
\`\`\`
|
|
946
|
-
|
|
947
|
-
**Pattern**: \`{name}.{method}.ts\`
|
|
948
|
-
|
|
949
|
-
**Methods**: \`get\`, \`post\`, \`put\`, \`delete\`, \`patch\`
|
|
950
|
-
|
|
951
|
-
**URL**: \`/agents/api/threads/:id/{name}\`
|
|
952
|
-
|
|
953
|
-
## Creating a Thread Endpoint
|
|
954
|
-
|
|
955
|
-
Use \`defineThreadEndpoint\` from \`@standardagents/builder\`:
|
|
956
|
-
|
|
957
|
-
\`\`\`typescript
|
|
958
|
-
import { defineThreadEndpoint } from '@standardagents/builder';
|
|
959
|
-
|
|
960
|
-
export default defineThreadEndpoint(async (thread, request, env) => {
|
|
961
|
-
// thread is the DurableObject stub for the requested thread
|
|
962
|
-
// request is the incoming Request object
|
|
963
|
-
// env is the Cloudflare environment with bindings
|
|
964
|
-
|
|
965
|
-
// Access thread's storage directly
|
|
966
|
-
const messages = await thread.getMessages();
|
|
967
|
-
|
|
968
|
-
// Perform operations
|
|
969
|
-
const summary = generateSummary(messages);
|
|
970
|
-
|
|
971
|
-
// Return response
|
|
972
|
-
return new Response(JSON.stringify({ summary }), {
|
|
973
|
-
headers: { 'Content-Type': 'application/json' }
|
|
974
|
-
});
|
|
975
|
-
});
|
|
976
|
-
\`\`\`
|
|
977
|
-
|
|
978
|
-
## Thread Object
|
|
979
|
-
|
|
980
|
-
The \`thread\` parameter is a DurableObject stub with RPC methods:
|
|
981
|
-
|
|
982
|
-
\`\`\`typescript
|
|
983
|
-
interface DurableThread {
|
|
984
|
-
// Get messages from thread's SQLite storage
|
|
985
|
-
getMessages(limit?: number, offset?: number): Promise<Message[]>;
|
|
986
|
-
|
|
987
|
-
// Get execution logs
|
|
988
|
-
getLogs(limit?: number, offset?: number): Promise<Log[]>;
|
|
989
|
-
|
|
990
|
-
// Process a new message (starts agent execution)
|
|
991
|
-
processMessage(content: string, role?: string): Promise<Response>;
|
|
992
|
-
|
|
993
|
-
// Get thread metadata
|
|
994
|
-
getThreadMeta(threadId: string): Promise<ThreadMetadata>;
|
|
995
|
-
|
|
996
|
-
// Direct storage access (advanced)
|
|
997
|
-
storage: DurableObjectStorage;
|
|
272
|
+
async function modifyViteConfig(configPath, force) {
|
|
273
|
+
try {
|
|
274
|
+
const mod = await loadFile(configPath);
|
|
275
|
+
const code = fs.readFileSync(configPath, "utf-8");
|
|
276
|
+
const hasCloudflare = code.includes("@cloudflare/vite-plugin") || code.includes("cloudflare()");
|
|
277
|
+
const hasAgentBuilder = code.includes("@standardagents/builder") || code.includes("agentbuilder()");
|
|
278
|
+
if (hasCloudflare && hasAgentBuilder && !force) {
|
|
279
|
+
logger.info("Vite config already includes Standard Agents plugins");
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
if (!hasCloudflare || force) {
|
|
283
|
+
addVitePlugin(mod, {
|
|
284
|
+
from: "@cloudflare/vite-plugin",
|
|
285
|
+
imported: "cloudflare",
|
|
286
|
+
constructor: "cloudflare"
|
|
287
|
+
});
|
|
288
|
+
logger.success("Added cloudflare plugin to vite.config");
|
|
289
|
+
}
|
|
290
|
+
if (!hasAgentBuilder || force) {
|
|
291
|
+
addVitePlugin(mod, {
|
|
292
|
+
from: "@standardagents/builder",
|
|
293
|
+
imported: "agentbuilder",
|
|
294
|
+
constructor: "agentbuilder",
|
|
295
|
+
options: { mountPoint: "/" }
|
|
296
|
+
});
|
|
297
|
+
logger.success("Added agentbuilder plugin to vite.config");
|
|
298
|
+
}
|
|
299
|
+
await writeFile(mod, configPath);
|
|
300
|
+
return true;
|
|
301
|
+
} catch (error) {
|
|
302
|
+
logger.warning(`Could not automatically modify vite.config: ${error}`);
|
|
303
|
+
logger.log("");
|
|
304
|
+
logger.log("Please manually add the following to your vite.config.ts:");
|
|
305
|
+
logger.log("");
|
|
306
|
+
logger.log(` import { cloudflare } from '@cloudflare/vite-plugin'`);
|
|
307
|
+
logger.log(` import { agentbuilder } from '@standardagents/builder'`);
|
|
308
|
+
logger.log("");
|
|
309
|
+
logger.log(` plugins: [cloudflare(), agentbuilder({ mountPoint: '/' })]`);
|
|
310
|
+
logger.log("");
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
998
313
|
}
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
.
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
314
|
+
function createOrUpdateWranglerConfig(cwd, projectName, force) {
|
|
315
|
+
var _a, _b;
|
|
316
|
+
const existingConfig = findWranglerConfig(cwd);
|
|
317
|
+
if (existingConfig && !force) {
|
|
318
|
+
try {
|
|
319
|
+
const text = fs.readFileSync(existingConfig, "utf-8");
|
|
320
|
+
const config = parse(text);
|
|
321
|
+
const hasBindings = (_b = (_a = config.durable_objects) == null ? void 0 : _a.bindings) == null ? void 0 : _b.some(
|
|
322
|
+
(b) => b.name === "AGENT_BUILDER_THREAD" || b.name === "AGENT_BUILDER"
|
|
323
|
+
);
|
|
324
|
+
if (hasBindings) {
|
|
325
|
+
logger.info("wrangler.jsonc already configured for Standard Agents");
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
let result = text;
|
|
329
|
+
if (!config.durable_objects) {
|
|
330
|
+
const edits = modify(result, ["durable_objects"], {
|
|
331
|
+
bindings: [
|
|
332
|
+
{ name: "AGENT_BUILDER_THREAD", class_name: "DurableThread" },
|
|
333
|
+
{ name: "AGENT_BUILDER", class_name: "DurableAgentBuilder" }
|
|
334
|
+
]
|
|
335
|
+
}, {});
|
|
336
|
+
result = applyEdits(result, edits);
|
|
337
|
+
} else {
|
|
338
|
+
const bindings = config.durable_objects.bindings || [];
|
|
339
|
+
bindings.push(
|
|
340
|
+
{ name: "AGENT_BUILDER_THREAD", class_name: "DurableThread" },
|
|
341
|
+
{ name: "AGENT_BUILDER", class_name: "DurableAgentBuilder" }
|
|
342
|
+
);
|
|
343
|
+
const edits = modify(result, ["durable_objects", "bindings"], bindings, {});
|
|
344
|
+
result = applyEdits(result, edits);
|
|
345
|
+
}
|
|
346
|
+
if (!config.migrations) {
|
|
347
|
+
const edits = modify(result, ["migrations"], [
|
|
348
|
+
{ tag: "v1", new_sqlite_classes: ["DurableThread"] },
|
|
349
|
+
{ tag: "v2", new_sqlite_classes: ["DurableAgentBuilder"] }
|
|
350
|
+
], {});
|
|
351
|
+
result = applyEdits(result, edits);
|
|
352
|
+
}
|
|
353
|
+
if (!config.main || !config.main.includes("worker")) {
|
|
354
|
+
const edits = modify(result, ["main"], "worker/index.ts", {});
|
|
355
|
+
result = applyEdits(result, edits);
|
|
356
|
+
}
|
|
357
|
+
if (!config.assets) {
|
|
358
|
+
const edits = modify(result, ["assets"], {
|
|
359
|
+
directory: "dist",
|
|
360
|
+
not_found_handling: "single-page-application",
|
|
361
|
+
binding: "ASSETS",
|
|
362
|
+
run_worker_first: ["/**"]
|
|
363
|
+
}, {});
|
|
364
|
+
result = applyEdits(result, edits);
|
|
365
|
+
}
|
|
366
|
+
fs.writeFileSync(existingConfig, result, "utf-8");
|
|
367
|
+
logger.success("Updated wrangler.jsonc with Standard Agents configuration");
|
|
368
|
+
return true;
|
|
369
|
+
} catch (error) {
|
|
370
|
+
logger.warning(`Could not update wrangler config: ${error}`);
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
1057
373
|
}
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
374
|
+
const wranglerPath = path.join(cwd, "wrangler.jsonc");
|
|
375
|
+
const sanitizedName = projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
376
|
+
fs.writeFileSync(wranglerPath, WRANGLER_TEMPLATE(sanitizedName), "utf-8");
|
|
377
|
+
logger.success("Created wrangler.jsonc");
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
function getWorkerEntryPoint(cwd) {
|
|
381
|
+
const wranglerConfig = findWranglerConfig(cwd);
|
|
382
|
+
if (wranglerConfig) {
|
|
383
|
+
try {
|
|
384
|
+
const text = fs.readFileSync(wranglerConfig, "utf-8");
|
|
385
|
+
const config = parse(text);
|
|
386
|
+
if (config.main) {
|
|
387
|
+
return path.join(cwd, config.main);
|
|
388
|
+
}
|
|
389
|
+
} catch {
|
|
1063
390
|
}
|
|
1064
|
-
});
|
|
1065
|
-
});
|
|
1066
|
-
\`\`\`
|
|
1067
|
-
|
|
1068
|
-
**Usage**: \`POST /agents/api/threads/{threadId}/export\`
|
|
1069
|
-
|
|
1070
|
-
### Direct Storage Access
|
|
1071
|
-
|
|
1072
|
-
\`\`\`typescript
|
|
1073
|
-
// agents/api/stats.get.ts
|
|
1074
|
-
import { defineThreadEndpoint } from '@standardagents/builder';
|
|
1075
|
-
|
|
1076
|
-
export default defineThreadEndpoint(async (thread, request, env) => {
|
|
1077
|
-
// Access SQLite storage directly
|
|
1078
|
-
const storage = (thread as any).storage;
|
|
1079
|
-
|
|
1080
|
-
const cursor = await storage.sql.exec(\`
|
|
1081
|
-
SELECT
|
|
1082
|
-
COUNT(*) as count,
|
|
1083
|
-
role,
|
|
1084
|
-
AVG(LENGTH(content)) as avg_length
|
|
1085
|
-
FROM messages
|
|
1086
|
-
GROUP BY role
|
|
1087
|
-
\`);
|
|
1088
|
-
|
|
1089
|
-
const stats = cursor.toArray();
|
|
1090
|
-
|
|
1091
|
-
return new Response(JSON.stringify({ stats }), {
|
|
1092
|
-
headers: { 'Content-Type': 'application/json' }
|
|
1093
|
-
});
|
|
1094
|
-
});
|
|
1095
|
-
\`\`\`
|
|
1096
|
-
|
|
1097
|
-
## Request Handling
|
|
1098
|
-
|
|
1099
|
-
### Reading Request Body
|
|
1100
|
-
|
|
1101
|
-
\`\`\`typescript
|
|
1102
|
-
export default defineThreadEndpoint(async (thread, request, env) => {
|
|
1103
|
-
// JSON
|
|
1104
|
-
const body = await request.json();
|
|
1105
|
-
|
|
1106
|
-
// FormData
|
|
1107
|
-
const formData = await request.formData();
|
|
1108
|
-
|
|
1109
|
-
// Text
|
|
1110
|
-
const text = await request.text();
|
|
1111
|
-
|
|
1112
|
-
// ...
|
|
1113
|
-
});
|
|
1114
|
-
\`\`\`
|
|
1115
|
-
|
|
1116
|
-
### Query Parameters
|
|
1117
|
-
|
|
1118
|
-
\`\`\`typescript
|
|
1119
|
-
export default defineThreadEndpoint(async (thread, request, env) => {
|
|
1120
|
-
const url = new URL(request.url);
|
|
1121
|
-
const limit = url.searchParams.get('limit') || '10';
|
|
1122
|
-
|
|
1123
|
-
const messages = await thread.getMessages(parseInt(limit));
|
|
1124
|
-
|
|
1125
|
-
// ...
|
|
1126
|
-
});
|
|
1127
|
-
\`\`\`
|
|
1128
|
-
|
|
1129
|
-
### Headers
|
|
1130
|
-
|
|
1131
|
-
\`\`\`typescript
|
|
1132
|
-
export default defineThreadEndpoint(async (thread, request, env) => {
|
|
1133
|
-
const authHeader = request.headers.get('Authorization');
|
|
1134
|
-
|
|
1135
|
-
if (!authHeader) {
|
|
1136
|
-
return new Response('Unauthorized', { status: 401 });
|
|
1137
391
|
}
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
392
|
+
return path.join(cwd, "worker", "index.ts");
|
|
393
|
+
}
|
|
394
|
+
async function createOrUpdateWorkerEntry(cwd, force) {
|
|
395
|
+
const entryPath = getWorkerEntryPoint(cwd);
|
|
396
|
+
const entryDir = path.dirname(entryPath);
|
|
397
|
+
if (!fs.existsSync(entryDir)) {
|
|
398
|
+
fs.mkdirSync(entryDir, { recursive: true });
|
|
399
|
+
logger.success(`Created ${path.relative(cwd, entryDir)} directory`);
|
|
400
|
+
}
|
|
401
|
+
if (!fs.existsSync(entryPath)) {
|
|
402
|
+
fs.writeFileSync(entryPath, WORKER_INDEX, "utf-8");
|
|
403
|
+
logger.success(`Created ${path.relative(cwd, entryPath)}`);
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
406
|
+
const content = fs.readFileSync(entryPath, "utf-8");
|
|
407
|
+
const hasRouter = content.includes("virtual:@standardagents/builder") && content.includes("router");
|
|
408
|
+
const hasDurableExports = content.includes("DurableThread") && content.includes("DurableAgentBuilder");
|
|
409
|
+
if (hasRouter && hasDurableExports && !force) {
|
|
410
|
+
logger.info(`${path.relative(cwd, entryPath)} already configured`);
|
|
411
|
+
return true;
|
|
412
|
+
}
|
|
1149
413
|
try {
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
414
|
+
const mod = await loadFile(entryPath);
|
|
415
|
+
if (!content.includes("virtual:@standardagents/builder") || force) {
|
|
416
|
+
mod.imports.$add({
|
|
417
|
+
from: "virtual:@standardagents/builder",
|
|
418
|
+
imported: "router",
|
|
419
|
+
local: "router"
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
if (!content.includes("'../agents/Thread'") || force) {
|
|
423
|
+
mod.imports.$add({
|
|
424
|
+
from: "../agents/Thread",
|
|
425
|
+
imported: "default",
|
|
426
|
+
local: "DurableThread"
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
if (!content.includes("'../agents/AgentBuilder'") || force) {
|
|
430
|
+
mod.imports.$add({
|
|
431
|
+
from: "../agents/AgentBuilder",
|
|
432
|
+
imported: "default",
|
|
433
|
+
local: "DurableAgentBuilder"
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
const { code } = generateCode(mod);
|
|
437
|
+
if (force || !hasRouter) {
|
|
438
|
+
fs.writeFileSync(entryPath, WORKER_INDEX, "utf-8");
|
|
439
|
+
logger.success(`Updated ${path.relative(cwd, entryPath)} with Standard Agents router`);
|
|
440
|
+
}
|
|
441
|
+
return true;
|
|
1156
442
|
} catch (error) {
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
);
|
|
443
|
+
if (force) {
|
|
444
|
+
fs.writeFileSync(entryPath, WORKER_INDEX, "utf-8");
|
|
445
|
+
logger.success(`Created ${path.relative(cwd, entryPath)}`);
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
logger.warning(`Could not automatically modify ${path.relative(cwd, entryPath)}`);
|
|
449
|
+
logger.log("");
|
|
450
|
+
logger.log("Please ensure your worker entry point includes:");
|
|
451
|
+
logger.log("");
|
|
452
|
+
logger.log(` import { router } from "virtual:@standardagents/builder"`);
|
|
453
|
+
logger.log(` import DurableThread from '../agents/Thread';`);
|
|
454
|
+
logger.log(` import DurableAgentBuilder from '../agents/AgentBuilder';`);
|
|
455
|
+
logger.log("");
|
|
456
|
+
logger.log(" // In your fetch handler:");
|
|
457
|
+
logger.log(" const res = router(request, env)");
|
|
458
|
+
logger.log(" if (res) return res");
|
|
459
|
+
logger.log("");
|
|
460
|
+
logger.log(" // Export Durable Objects:");
|
|
461
|
+
logger.log(" export { DurableThread, DurableAgentBuilder }");
|
|
462
|
+
logger.log("");
|
|
463
|
+
return false;
|
|
1168
464
|
}
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
## Auto-Discovery
|
|
1193
|
-
|
|
1194
|
-
Thread endpoints are **auto-discovered**:
|
|
1195
|
-
|
|
1196
|
-
1. Vite plugin scans this directory on startup
|
|
1197
|
-
2. Generates virtual module with dynamic imports
|
|
1198
|
-
3. Routes registered in the main router
|
|
1199
|
-
4. HMR works in development
|
|
1200
|
-
|
|
1201
|
-
No manual registration needed!
|
|
1202
|
-
|
|
1203
|
-
## URL Structure
|
|
1204
|
-
|
|
1205
|
-
All thread endpoints follow this pattern:
|
|
1206
|
-
|
|
1207
|
-
\`\`\`
|
|
1208
|
-
/agents/api/threads/:id/{endpoint-name}
|
|
1209
|
-
\`\`\`
|
|
1210
|
-
|
|
1211
|
-
Examples:
|
|
1212
|
-
- \`GET /agents/api/threads/abc-123/summary\`
|
|
1213
|
-
- \`POST /agents/api/threads/abc-123/export\`
|
|
1214
|
-
- \`PUT /agents/api/threads/abc-123/metadata\`
|
|
1215
|
-
|
|
1216
|
-
The \`:id\` is automatically used to fetch the correct Durable Object.
|
|
1217
|
-
|
|
1218
|
-
## Built-In Endpoints
|
|
1219
|
-
|
|
1220
|
-
Standard Agents includes built-in thread endpoints (these are in the framework, not this directory):
|
|
1221
|
-
|
|
1222
|
-
- \`GET /agents/api/threads/:id/messages\` - Get message history
|
|
1223
|
-
- \`POST /agents/api/threads/:id/messages\` - Send a message
|
|
1224
|
-
- \`DELETE /agents/api/threads/:id\` - Delete thread
|
|
1225
|
-
- \`GET /agents/api/threads/:id/logs\` - Get execution logs
|
|
1226
|
-
|
|
1227
|
-
Your custom endpoints extend these built-in routes.
|
|
1228
|
-
|
|
1229
|
-
## Best Practices
|
|
1230
|
-
|
|
1231
|
-
1. **Descriptive Names**: Use clear endpoint names (e.g., \`summary\`, \`export\`)
|
|
1232
|
-
2. **Proper HTTP Methods**: Use GET for reads, POST for creates, etc.
|
|
1233
|
-
3. **Error Handling**: Always wrap in try/catch
|
|
1234
|
-
4. **Type Safety**: Use TypeScript interfaces for request/response
|
|
1235
|
-
5. **Performance**: Be mindful of SQLite query performance
|
|
1236
|
-
6. **Authentication**: Add auth checks if endpoints are sensitive
|
|
1237
|
-
7. **CORS**: Add CORS headers if accessed from browser
|
|
1238
|
-
|
|
1239
|
-
## Testing
|
|
1240
|
-
|
|
1241
|
-
Test endpoints with curl or any HTTP client:
|
|
1242
|
-
|
|
1243
|
-
\`\`\`bash
|
|
1244
|
-
# GET summary
|
|
1245
|
-
curl http://localhost:8787/agents/api/threads/abc-123/summary
|
|
1246
|
-
|
|
1247
|
-
# POST export
|
|
1248
|
-
curl -X POST http://localhost:8787/agents/api/threads/abc-123/export \\
|
|
1249
|
-
-H "Content-Type: application/json" \\
|
|
1250
|
-
-d '{"format": "json"}'
|
|
1251
|
-
\`\`\`
|
|
1252
|
-
|
|
1253
|
-
## Limitations
|
|
1254
|
-
|
|
1255
|
-
- No nested directories (endpoints must be directly in this folder)
|
|
1256
|
-
- Thread ID must be in URL path (handled automatically)
|
|
1257
|
-
- No support for multiple path parameters beyond thread ID
|
|
1258
|
-
- Response must be a Web API \`Response\` object
|
|
1259
|
-
|
|
1260
|
-
## Related
|
|
1261
|
-
|
|
1262
|
-
- **Tools**: \`agents/tools/CLAUDE.md\` - Custom tools
|
|
1263
|
-
- **Hooks**: \`agents/hooks/CLAUDE.md\` - Lifecycle hooks
|
|
1264
|
-
- **Built-in APIs**: \`packages/builder/src/api/\` - Framework endpoints
|
|
1265
|
-
- **Documentation**: Project root \`CLAUDE.md\` for architecture
|
|
1266
|
-
`;
|
|
1267
|
-
async function scaffold() {
|
|
1268
|
-
const cwd = process.cwd();
|
|
1269
|
-
logger.info("Scaffolding Standard Agents directories...");
|
|
465
|
+
}
|
|
466
|
+
function createDurableObjects(cwd) {
|
|
467
|
+
const agentsDir = path.join(cwd, "agents");
|
|
468
|
+
if (!fs.existsSync(agentsDir)) {
|
|
469
|
+
fs.mkdirSync(agentsDir, { recursive: true });
|
|
470
|
+
}
|
|
471
|
+
const threadPath = path.join(agentsDir, "Thread.ts");
|
|
472
|
+
if (!fs.existsSync(threadPath)) {
|
|
473
|
+
fs.writeFileSync(threadPath, THREAD_TS, "utf-8");
|
|
474
|
+
logger.success("Created agents/Thread.ts");
|
|
475
|
+
} else {
|
|
476
|
+
logger.info("agents/Thread.ts already exists");
|
|
477
|
+
}
|
|
478
|
+
const agentBuilderPath = path.join(agentsDir, "AgentBuilder.ts");
|
|
479
|
+
if (!fs.existsSync(agentBuilderPath)) {
|
|
480
|
+
fs.writeFileSync(agentBuilderPath, AGENT_BUILDER_TS, "utf-8");
|
|
481
|
+
logger.success("Created agents/AgentBuilder.ts");
|
|
482
|
+
} else {
|
|
483
|
+
logger.info("agents/AgentBuilder.ts already exists");
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
function createDirectoriesAndDocs(cwd) {
|
|
1270
487
|
const directories = [
|
|
1271
|
-
{
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
},
|
|
1276
|
-
{
|
|
1277
|
-
path: path3.join(cwd, "agents", "hooks"),
|
|
1278
|
-
claudeMd: HOOKS_CLAUDE_MD,
|
|
1279
|
-
name: "hooks"
|
|
1280
|
-
},
|
|
1281
|
-
{
|
|
1282
|
-
path: path3.join(cwd, "agents", "api"),
|
|
1283
|
-
claudeMd: API_CLAUDE_MD,
|
|
1284
|
-
name: "api"
|
|
1285
|
-
}
|
|
488
|
+
{ path: "agents/agents", doc: AGENTS_CLAUDE_MD },
|
|
489
|
+
{ path: "agents/prompts", doc: PROMPTS_CLAUDE_MD },
|
|
490
|
+
{ path: "agents/models", doc: MODELS_CLAUDE_MD },
|
|
491
|
+
{ path: "agents/tools", doc: TOOLS_CLAUDE_MD },
|
|
492
|
+
{ path: "agents/hooks", doc: HOOKS_CLAUDE_MD },
|
|
493
|
+
{ path: "agents/api", doc: API_CLAUDE_MD }
|
|
1286
494
|
];
|
|
1287
|
-
let created = 0;
|
|
1288
|
-
let skipped = 0;
|
|
1289
495
|
for (const dir of directories) {
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
496
|
+
const dirPath = path.join(cwd, dir.path);
|
|
497
|
+
const docPath = path.join(dirPath, "CLAUDE.md");
|
|
498
|
+
if (!fs.existsSync(dirPath)) {
|
|
499
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
500
|
+
logger.success(`Created ${dir.path}`);
|
|
501
|
+
}
|
|
502
|
+
if (!fs.existsSync(docPath)) {
|
|
503
|
+
fs.writeFileSync(docPath, dir.doc, "utf-8");
|
|
504
|
+
logger.success(`Created ${dir.path}/CLAUDE.md`);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
function updateTsConfig(cwd) {
|
|
509
|
+
var _a;
|
|
510
|
+
const tsconfigPath = path.join(cwd, "tsconfig.json");
|
|
511
|
+
if (!fs.existsSync(tsconfigPath)) {
|
|
512
|
+
logger.info("No tsconfig.json found, skipping TypeScript configuration");
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
try {
|
|
516
|
+
const text = fs.readFileSync(tsconfigPath, "utf-8");
|
|
517
|
+
const config = parse(text);
|
|
518
|
+
let result = text;
|
|
519
|
+
const types = ((_a = config.compilerOptions) == null ? void 0 : _a.types) || [];
|
|
520
|
+
const newTypes = [
|
|
521
|
+
"./worker-configuration.d.ts",
|
|
522
|
+
"./.agents/types.d.ts",
|
|
523
|
+
"./.agents/virtual-module.d.ts"
|
|
524
|
+
].filter((t) => !types.includes(t));
|
|
525
|
+
if (newTypes.length > 0) {
|
|
526
|
+
const allTypes = [...types, ...newTypes];
|
|
527
|
+
const edits = modify(result, ["compilerOptions", "types"], allTypes, {});
|
|
528
|
+
result = applyEdits(result, edits);
|
|
1296
529
|
}
|
|
1297
|
-
const
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
530
|
+
const include = config.include || [];
|
|
531
|
+
const newIncludes = ["worker", "agents"].filter((i) => !include.includes(i));
|
|
532
|
+
if (newIncludes.length > 0) {
|
|
533
|
+
const allIncludes = [...include, ...newIncludes];
|
|
534
|
+
const edits = modify(result, ["include"], allIncludes, {});
|
|
535
|
+
result = applyEdits(result, edits);
|
|
536
|
+
}
|
|
537
|
+
if (newTypes.length > 0 || newIncludes.length > 0) {
|
|
538
|
+
fs.writeFileSync(tsconfigPath, result, "utf-8");
|
|
539
|
+
logger.success("Updated tsconfig.json with Standard Agents types");
|
|
1302
540
|
} else {
|
|
1303
|
-
logger.info(
|
|
1304
|
-
|
|
541
|
+
logger.info("tsconfig.json already configured");
|
|
542
|
+
}
|
|
543
|
+
} catch (error) {
|
|
544
|
+
logger.warning("Could not update tsconfig.json, you may need to add types manually");
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
function cleanupViteDefaults(cwd) {
|
|
548
|
+
const filesToRemove = [
|
|
549
|
+
"src/main.ts",
|
|
550
|
+
"src/style.css",
|
|
551
|
+
"src/counter.ts",
|
|
552
|
+
"src/typescript.svg",
|
|
553
|
+
"src/vite-env.d.ts",
|
|
554
|
+
"index.html",
|
|
555
|
+
"public/vite.svg"
|
|
556
|
+
];
|
|
557
|
+
const dirsToRemove = ["src", "public"];
|
|
558
|
+
for (const file of filesToRemove) {
|
|
559
|
+
const filePath = path.join(cwd, file);
|
|
560
|
+
if (fs.existsSync(filePath)) {
|
|
561
|
+
try {
|
|
562
|
+
fs.unlinkSync(filePath);
|
|
563
|
+
} catch {
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
for (const dir of dirsToRemove) {
|
|
568
|
+
const dirPath = path.join(cwd, dir);
|
|
569
|
+
if (fs.existsSync(dirPath)) {
|
|
570
|
+
try {
|
|
571
|
+
const files = fs.readdirSync(dirPath);
|
|
572
|
+
if (files.length === 0) {
|
|
573
|
+
fs.rmdirSync(dirPath);
|
|
574
|
+
}
|
|
575
|
+
} catch {
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
async function scaffold(options = {}) {
|
|
581
|
+
const cwd = process.cwd();
|
|
582
|
+
const projectName = getProjectName(cwd);
|
|
583
|
+
const force = options.force || false;
|
|
584
|
+
logger.info("Scaffolding Standard Agents...");
|
|
585
|
+
logger.log("");
|
|
586
|
+
const viteConfigPath = findViteConfig(cwd);
|
|
587
|
+
if (viteConfigPath) {
|
|
588
|
+
await modifyViteConfig(viteConfigPath, force);
|
|
589
|
+
} else {
|
|
590
|
+
logger.warning("No vite.config found. Please create one with cloudflare and agentbuilder plugins.");
|
|
591
|
+
}
|
|
592
|
+
createOrUpdateWranglerConfig(cwd, projectName, force);
|
|
593
|
+
await createOrUpdateWorkerEntry(cwd, force);
|
|
594
|
+
createDurableObjects(cwd);
|
|
595
|
+
createDirectoriesAndDocs(cwd);
|
|
596
|
+
updateTsConfig(cwd);
|
|
597
|
+
if (force) {
|
|
598
|
+
cleanupViteDefaults(cwd);
|
|
599
|
+
}
|
|
600
|
+
logger.log("");
|
|
601
|
+
logger.success("Standard Agents scaffolding complete!");
|
|
602
|
+
logger.log("");
|
|
603
|
+
logger.log("Your project structure:");
|
|
604
|
+
logger.log("");
|
|
605
|
+
logger.log(" agents/");
|
|
606
|
+
logger.log(" \u251C\u2500\u2500 Thread.ts # Durable Object for threads");
|
|
607
|
+
logger.log(" \u251C\u2500\u2500 AgentBuilder.ts # Durable Object for agent state");
|
|
608
|
+
logger.log(" \u251C\u2500\u2500 agents/ # Agent definitions");
|
|
609
|
+
logger.log(" \u251C\u2500\u2500 prompts/ # Prompt definitions");
|
|
610
|
+
logger.log(" \u251C\u2500\u2500 models/ # Model configurations");
|
|
611
|
+
logger.log(" \u251C\u2500\u2500 tools/ # Custom tools");
|
|
612
|
+
logger.log(" \u251C\u2500\u2500 hooks/ # Lifecycle hooks");
|
|
613
|
+
logger.log(" \u2514\u2500\u2500 api/ # Thread API endpoints");
|
|
614
|
+
logger.log(" worker/");
|
|
615
|
+
logger.log(" \u2514\u2500\u2500 index.ts # Cloudflare Worker entry point");
|
|
616
|
+
logger.log("");
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// src/commands/init.ts
|
|
620
|
+
async function prompt(question) {
|
|
621
|
+
const rl = readline.createInterface({
|
|
622
|
+
input: process.stdin,
|
|
623
|
+
output: process.stdout
|
|
624
|
+
});
|
|
625
|
+
return new Promise((resolve2) => {
|
|
626
|
+
rl.question(question, (answer) => {
|
|
627
|
+
rl.close();
|
|
628
|
+
resolve2(answer.trim());
|
|
629
|
+
});
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
function runCommand(command, args, cwd) {
|
|
633
|
+
return new Promise((resolve2, reject) => {
|
|
634
|
+
const child = spawn(command, args, {
|
|
635
|
+
cwd,
|
|
636
|
+
stdio: "inherit",
|
|
637
|
+
shell: true
|
|
638
|
+
});
|
|
639
|
+
child.on("close", (code) => {
|
|
640
|
+
if (code === 0) {
|
|
641
|
+
resolve2();
|
|
642
|
+
} else {
|
|
643
|
+
reject(new Error(`Command failed with exit code ${code}`));
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
child.on("error", reject);
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
function detectPackageManager() {
|
|
650
|
+
const userAgent = process.env.npm_config_user_agent;
|
|
651
|
+
if (userAgent) {
|
|
652
|
+
if (userAgent.includes("pnpm")) return "pnpm";
|
|
653
|
+
if (userAgent.includes("yarn")) return "yarn";
|
|
654
|
+
if (userAgent.includes("bun")) return "bun";
|
|
655
|
+
}
|
|
656
|
+
if (fs.existsSync("pnpm-lock.yaml")) return "pnpm";
|
|
657
|
+
if (fs.existsSync("yarn.lock")) return "yarn";
|
|
658
|
+
if (fs.existsSync("bun.lockb")) return "bun";
|
|
659
|
+
if (fs.existsSync("package-lock.json")) return "npm";
|
|
660
|
+
return "npm";
|
|
661
|
+
}
|
|
662
|
+
async function init(projectNameArg, options = {}) {
|
|
663
|
+
const cwd = process.cwd();
|
|
664
|
+
const pm = detectPackageManager();
|
|
665
|
+
const template = options.template || "vanilla-ts";
|
|
666
|
+
logger.log("");
|
|
667
|
+
logger.info("Creating a new Standard Agents project...");
|
|
668
|
+
logger.log("");
|
|
669
|
+
let projectName = projectNameArg;
|
|
670
|
+
if (!projectName && !options.yes) {
|
|
671
|
+
projectName = await prompt("Project name: ");
|
|
672
|
+
}
|
|
673
|
+
if (!projectName) {
|
|
674
|
+
logger.error("Project name is required");
|
|
675
|
+
process.exit(1);
|
|
676
|
+
}
|
|
677
|
+
const projectPath = path.join(cwd, projectName);
|
|
678
|
+
if (fs.existsSync(projectPath)) {
|
|
679
|
+
logger.error(`Directory "${projectName}" already exists`);
|
|
680
|
+
process.exit(1);
|
|
681
|
+
}
|
|
682
|
+
logger.log("");
|
|
683
|
+
logger.info(`Step 1: Creating Vite project with ${template} template...`);
|
|
684
|
+
logger.log("");
|
|
685
|
+
try {
|
|
686
|
+
let createCmd;
|
|
687
|
+
let createArgs;
|
|
688
|
+
switch (pm) {
|
|
689
|
+
case "pnpm":
|
|
690
|
+
createCmd = "pnpm";
|
|
691
|
+
createArgs = ["create", "vite@latest", projectName, "--template", template, "--no-interactive"];
|
|
692
|
+
break;
|
|
693
|
+
case "yarn":
|
|
694
|
+
createCmd = "yarn";
|
|
695
|
+
createArgs = ["create", "vite@latest", projectName, "--template", template, "--no-interactive"];
|
|
696
|
+
break;
|
|
697
|
+
case "bun":
|
|
698
|
+
createCmd = "bun";
|
|
699
|
+
createArgs = ["create", "vite@latest", projectName, "--template", template, "--no-interactive"];
|
|
700
|
+
break;
|
|
701
|
+
default:
|
|
702
|
+
createCmd = "npm";
|
|
703
|
+
createArgs = ["create", "vite@latest", projectName, "--", "--template", template, "--no-interactive"];
|
|
1305
704
|
}
|
|
705
|
+
await runCommand(createCmd, createArgs, cwd);
|
|
706
|
+
} catch (error) {
|
|
707
|
+
logger.error("Failed to create Vite project");
|
|
708
|
+
process.exit(1);
|
|
1306
709
|
}
|
|
710
|
+
const viteConfigPath = path.join(projectPath, "vite.config.ts");
|
|
711
|
+
if (!fs.existsSync(viteConfigPath)) {
|
|
712
|
+
const viteConfigContent = `import { defineConfig } from 'vite'
|
|
713
|
+
|
|
714
|
+
export default defineConfig({
|
|
715
|
+
plugins: [],
|
|
716
|
+
})
|
|
717
|
+
`;
|
|
718
|
+
fs.writeFileSync(viteConfigPath, viteConfigContent, "utf-8");
|
|
719
|
+
logger.success("Created vite.config.ts");
|
|
720
|
+
}
|
|
721
|
+
logger.log("");
|
|
722
|
+
logger.info("Step 2: Installing Standard Agents dependencies...");
|
|
1307
723
|
logger.log("");
|
|
1308
|
-
|
|
1309
|
-
|
|
724
|
+
try {
|
|
725
|
+
const installCmd = pm === "npm" ? "install" : "add";
|
|
726
|
+
const devFlag = pm === "npm" ? "--save-dev" : "-D";
|
|
727
|
+
await runCommand(pm, [
|
|
728
|
+
installCmd,
|
|
729
|
+
devFlag,
|
|
730
|
+
"@cloudflare/vite-plugin",
|
|
731
|
+
"@standardagents/builder",
|
|
732
|
+
"wrangler"
|
|
733
|
+
], projectPath);
|
|
734
|
+
} catch (error) {
|
|
735
|
+
logger.error("Failed to install dependencies");
|
|
736
|
+
process.exit(1);
|
|
1310
737
|
}
|
|
1311
|
-
|
|
1312
|
-
|
|
738
|
+
logger.log("");
|
|
739
|
+
logger.info("Step 3: Configuring Standard Agents...");
|
|
740
|
+
logger.log("");
|
|
741
|
+
process.chdir(projectPath);
|
|
742
|
+
await scaffold({ force: true });
|
|
743
|
+
logger.log("");
|
|
744
|
+
logger.info("Step 4: Generating Cloudflare types...");
|
|
745
|
+
logger.log("");
|
|
746
|
+
try {
|
|
747
|
+
await runCommand("npx", ["wrangler", "types"], projectPath);
|
|
748
|
+
} catch (error) {
|
|
749
|
+
logger.warning('Could not generate types automatically. Run "npx wrangler types" manually.');
|
|
1313
750
|
}
|
|
1314
|
-
logger.log("
|
|
1315
|
-
logger.
|
|
1316
|
-
logger.log("
|
|
1317
|
-
logger.log("
|
|
1318
|
-
logger.log("
|
|
751
|
+
logger.log("");
|
|
752
|
+
logger.success("Project created successfully!");
|
|
753
|
+
logger.log("");
|
|
754
|
+
logger.log("Next steps:");
|
|
755
|
+
logger.log("");
|
|
756
|
+
logger.log(` cd ${projectName}`);
|
|
757
|
+
logger.log(` ${pm === "npm" ? "npm run" : pm} dev`);
|
|
758
|
+
logger.log("");
|
|
759
|
+
logger.log("For more information, visit: https://standardagents.ai/docs");
|
|
1319
760
|
}
|
|
1320
761
|
|
|
1321
762
|
// src/index.ts
|
|
763
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
764
|
+
var pkg = JSON.parse(readFileSync(resolve(__dirname, "../package.json"), "utf-8"));
|
|
1322
765
|
var program = new Command();
|
|
1323
|
-
program.name("
|
|
1324
|
-
program.command("init").description("
|
|
1325
|
-
program.command("scaffold").description("
|
|
766
|
+
program.name("agents").description("CLI tool for Standard Agents / AgentBuilder").version(pkg.version);
|
|
767
|
+
program.command("init [project-name]").description("Create a new Standard Agents project (runs create vite, then scaffold)").option("-y, --yes", "Skip prompts and use defaults").option("--template <template>", "Vite template to use", "vanilla-ts").action(init);
|
|
768
|
+
program.command("scaffold").description("Add Standard Agents to an existing Vite project").option("--force", "Overwrite existing configuration").action(scaffold);
|
|
1326
769
|
program.parse();
|
|
1327
770
|
//# sourceMappingURL=index.js.map
|
|
1328
771
|
//# sourceMappingURL=index.js.map
|