@standardagents/builder 0.8.1
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/client.d.ts +73 -0
- package/dist/built-in-routes.js +2333 -0
- package/dist/built-in-routes.js.map +1 -0
- package/dist/client/agents/monacoeditorwork/json.worker.bundle.js +21320 -0
- package/dist/client/assets/codicon.ttf +0 -0
- package/dist/client/assets/editor.worker-CdtUXE0K.js +12 -0
- package/dist/client/assets/img/meta.svg +19 -0
- package/dist/client/assets/img/moonshotai.svg +4 -0
- package/dist/client/assets/img/zai.svg +219 -0
- package/dist/client/assets/index.css +1 -0
- package/dist/client/assets/json.worker-C21G4-GD.js +49 -0
- package/dist/client/assets/monaco.css +1 -0
- package/dist/client/index.html +38 -0
- package/dist/client/index.js +39 -0
- package/dist/client/monaco.js +1508 -0
- package/dist/client/vendor.js +1 -0
- package/dist/client/vue.js +143 -0
- package/dist/index.d.ts +2962 -0
- package/dist/index.js +11016 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp.js +663 -0
- package/dist/mcp.js.map +1 -0
- package/dist/plugin.d.ts +14 -0
- package/dist/plugin.js +3183 -0
- package/dist/plugin.js.map +1 -0
- package/dist/rou3.js +157 -0
- package/dist/rou3.js.map +1 -0
- package/package.json +97 -0
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,3183 @@
|
|
|
1
|
+
import fs2 from 'fs';
|
|
2
|
+
import path3 from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
// src/plugin.ts
|
|
6
|
+
var TSCONFIG_CONTENT = `{
|
|
7
|
+
"compilerOptions": {
|
|
8
|
+
"composite": true,
|
|
9
|
+
"declaration": true,
|
|
10
|
+
"declarationMap": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"strict": true,
|
|
13
|
+
"moduleResolution": "bundler",
|
|
14
|
+
"module": "ESNext",
|
|
15
|
+
"target": "ESNext"
|
|
16
|
+
},
|
|
17
|
+
"include": ["types.d.ts", "virtual-module.d.ts"]
|
|
18
|
+
}
|
|
19
|
+
`;
|
|
20
|
+
function scanForNames(dir, useFilename = false) {
|
|
21
|
+
const names = [];
|
|
22
|
+
if (!fs2.existsSync(dir)) {
|
|
23
|
+
return names;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
27
|
+
for (const entry of entries) {
|
|
28
|
+
if (entry.isFile() && entry.name.endsWith(".ts")) {
|
|
29
|
+
const filePath = path3.join(dir, entry.name);
|
|
30
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
31
|
+
if (useFilename) {
|
|
32
|
+
const toolName = entry.name.replace(/\.ts$/, "");
|
|
33
|
+
if (content.includes("defineTool")) {
|
|
34
|
+
names.push(toolName);
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
const nameMatch = content.match(/name:\s*['"]([^'"]+)['"]/);
|
|
38
|
+
if (nameMatch) {
|
|
39
|
+
names.push(nameMatch[1]);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error(`Error scanning directory ${dir}:`, error);
|
|
46
|
+
}
|
|
47
|
+
return names;
|
|
48
|
+
}
|
|
49
|
+
function generateRegistryProperties(names) {
|
|
50
|
+
if (names.length === 0) {
|
|
51
|
+
return "";
|
|
52
|
+
}
|
|
53
|
+
return names.map((n) => `'${n}': true;`).join("\n ");
|
|
54
|
+
}
|
|
55
|
+
function generateTypesContent(config) {
|
|
56
|
+
const models = scanForNames(config.modelsDir);
|
|
57
|
+
const prompts = scanForNames(config.promptsDir);
|
|
58
|
+
const agents = scanForNames(config.agentsDir);
|
|
59
|
+
const tools = scanForNames(config.toolsDir, true);
|
|
60
|
+
const callables = [...prompts, ...agents, ...tools];
|
|
61
|
+
return `// Auto-generated by @standardagents/builder - DO NOT EDIT
|
|
62
|
+
// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
63
|
+
//
|
|
64
|
+
// This file augments the AgentBuilder namespace declared in @standardagents/builder
|
|
65
|
+
// to provide type-safe references for your models, prompts, agents, and tools.
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Augment the global AgentBuilder namespace with your project's specific types.
|
|
69
|
+
* This provides autocomplete and type checking for model, prompt, agent, and tool references.
|
|
70
|
+
*
|
|
71
|
+
* Uses interface declaration merging with property keys to create union types.
|
|
72
|
+
* Example: interface ModelRegistry { 'gpt-4o': true; } gives type Models = 'gpt-4o'
|
|
73
|
+
*/
|
|
74
|
+
declare global {
|
|
75
|
+
namespace AgentBuilder {
|
|
76
|
+
/** Model names from agents/models/ */
|
|
77
|
+
interface ModelRegistry {
|
|
78
|
+
${generateRegistryProperties(models)}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Prompt names from agents/prompts/ */
|
|
82
|
+
interface PromptRegistry {
|
|
83
|
+
${generateRegistryProperties(prompts)}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Agent names from agents/agents/ */
|
|
87
|
+
interface AgentRegistry {
|
|
88
|
+
${generateRegistryProperties(agents)}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Tool names from agents/tools/ */
|
|
92
|
+
interface ToolRegistry {
|
|
93
|
+
${generateRegistryProperties(tools)}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** All callable items (prompts, agents, tools) that can be used as tools */
|
|
97
|
+
interface CallableRegistry {
|
|
98
|
+
${generateRegistryProperties(callables)}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// This export is required for TypeScript to treat this as a module
|
|
104
|
+
// and allow the declare global to work properly
|
|
105
|
+
export {};
|
|
106
|
+
`;
|
|
107
|
+
}
|
|
108
|
+
function generateVirtualModuleContent() {
|
|
109
|
+
return `// Auto-generated by @standardagents/builder - DO NOT EDIT
|
|
110
|
+
//
|
|
111
|
+
// Type declarations for the virtual:@standardagents/builder module.
|
|
112
|
+
// This file must NOT have any exports to work as an ambient module declaration.
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Type declarations for the consolidated virtual module.
|
|
116
|
+
* This module provides DurableThread and DurableAgentBuilder classes
|
|
117
|
+
* with all virtual module methods (tools, hooks, models, prompts, agents)
|
|
118
|
+
* already implemented, plus the router function.
|
|
119
|
+
*/
|
|
120
|
+
declare module 'virtual:@standardagents/builder' {
|
|
121
|
+
import type { DurableThread as BaseDurableThread } from '@standardagents/builder/runtime';
|
|
122
|
+
import type { DurableAgentBuilder as BaseDurableAgentBuilder } from '@standardagents/builder/runtime';
|
|
123
|
+
import type { ThreadEnv } from '@standardagents/builder/runtime';
|
|
124
|
+
|
|
125
|
+
// ============================================================
|
|
126
|
+
// Message Types
|
|
127
|
+
// ============================================================
|
|
128
|
+
|
|
129
|
+
export interface ThreadMessage {
|
|
130
|
+
id: string;
|
|
131
|
+
role: string;
|
|
132
|
+
content: string | null;
|
|
133
|
+
name: string | null;
|
|
134
|
+
tool_calls: string | null;
|
|
135
|
+
tool_call_id: string | null;
|
|
136
|
+
tool_status: 'success' | 'error' | null;
|
|
137
|
+
log_id: string | null;
|
|
138
|
+
created_at: number;
|
|
139
|
+
silent: boolean;
|
|
140
|
+
parent_id: string | null;
|
|
141
|
+
depth: number;
|
|
142
|
+
status: 'pending' | 'completed' | 'failed' | null;
|
|
143
|
+
reasoning_content: string | null;
|
|
144
|
+
reasoning_details: string | null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface GetMessagesResult {
|
|
148
|
+
messages: ThreadMessage[];
|
|
149
|
+
total: number;
|
|
150
|
+
hasMore: boolean;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ============================================================
|
|
154
|
+
// Log Types
|
|
155
|
+
// ============================================================
|
|
156
|
+
|
|
157
|
+
export interface ThreadLog {
|
|
158
|
+
id: string;
|
|
159
|
+
message_id: string;
|
|
160
|
+
provider: string;
|
|
161
|
+
model: string;
|
|
162
|
+
model_name: string | null;
|
|
163
|
+
prompt_name: string | null;
|
|
164
|
+
tools_called: string | null;
|
|
165
|
+
parent_log_id: string | null;
|
|
166
|
+
retry_of_log_id: string | null;
|
|
167
|
+
error: string | null;
|
|
168
|
+
cost_total: number | null;
|
|
169
|
+
is_complete: boolean;
|
|
170
|
+
created_at: number;
|
|
171
|
+
request_body: string | null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export interface GetLogsResult {
|
|
175
|
+
logs: ThreadLog[];
|
|
176
|
+
total: number;
|
|
177
|
+
hasMore: boolean;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export interface ThreadLogDetails extends ThreadLog {
|
|
181
|
+
endpoint: string | null;
|
|
182
|
+
request_headers: string | null;
|
|
183
|
+
response_body: string | null;
|
|
184
|
+
response_headers: string | null;
|
|
185
|
+
status_code: number | null;
|
|
186
|
+
reasoning_content: string | null;
|
|
187
|
+
input_tokens: number | null;
|
|
188
|
+
cached_tokens: number | null;
|
|
189
|
+
output_tokens: number | null;
|
|
190
|
+
reasoning_tokens: number | null;
|
|
191
|
+
total_tokens: number | null;
|
|
192
|
+
latency_ms: number | null;
|
|
193
|
+
time_to_first_token_ms: number | null;
|
|
194
|
+
finish_reason: string | null;
|
|
195
|
+
error_type: string | null;
|
|
196
|
+
cost_input: number | null;
|
|
197
|
+
cost_output: number | null;
|
|
198
|
+
message_history_length: number | null;
|
|
199
|
+
tools_available: number | null;
|
|
200
|
+
tools_schema: string | null;
|
|
201
|
+
message_history: string | null;
|
|
202
|
+
system_prompt: string | null;
|
|
203
|
+
errors: string | null;
|
|
204
|
+
tool_results: string | null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ============================================================
|
|
208
|
+
// Thread Metadata Types
|
|
209
|
+
// ============================================================
|
|
210
|
+
|
|
211
|
+
export interface ThreadMetaResult {
|
|
212
|
+
thread: {
|
|
213
|
+
id: string;
|
|
214
|
+
agent_id: string;
|
|
215
|
+
user_id: string | null;
|
|
216
|
+
tags: string[];
|
|
217
|
+
created_at: number;
|
|
218
|
+
};
|
|
219
|
+
agent: {
|
|
220
|
+
id: string;
|
|
221
|
+
title: string;
|
|
222
|
+
type: string;
|
|
223
|
+
side_a_label?: string;
|
|
224
|
+
side_b_label?: string;
|
|
225
|
+
} | null;
|
|
226
|
+
stats: {
|
|
227
|
+
message_count: number;
|
|
228
|
+
log_count: number;
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ============================================================
|
|
233
|
+
// DurableThread Class
|
|
234
|
+
// ============================================================
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* DurableThread with all virtual module methods pre-implemented.
|
|
238
|
+
* Extend this class in your agents/Thread.ts file.
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* \`\`\`typescript
|
|
242
|
+
* import { DurableThread } from 'virtual:@standardagents/builder'
|
|
243
|
+
*
|
|
244
|
+
* export class Thread extends DurableThread {}
|
|
245
|
+
* \`\`\`
|
|
246
|
+
*/
|
|
247
|
+
export class DurableThread extends BaseDurableThread {
|
|
248
|
+
// Virtual module registry methods
|
|
249
|
+
tools(): Record<string, () => Promise<any>>;
|
|
250
|
+
hooks(): Record<string, () => Promise<any>>;
|
|
251
|
+
models(): Record<string, () => Promise<any>>;
|
|
252
|
+
prompts(): Record<string, () => Promise<any>>;
|
|
253
|
+
agents(): Record<string, () => Promise<any>>;
|
|
254
|
+
|
|
255
|
+
// Lookup methods
|
|
256
|
+
loadModel(name: string): Promise<any>;
|
|
257
|
+
getModelNames(): string[];
|
|
258
|
+
loadPrompt(name: string): Promise<any>;
|
|
259
|
+
getPromptNames(): string[];
|
|
260
|
+
loadAgent(name: string): Promise<any>;
|
|
261
|
+
getAgentNames(): string[];
|
|
262
|
+
|
|
263
|
+
// Execution methods
|
|
264
|
+
execute(
|
|
265
|
+
threadId: string,
|
|
266
|
+
agentId: string,
|
|
267
|
+
initial_messages?: any[],
|
|
268
|
+
data?: any
|
|
269
|
+
): Promise<Response>;
|
|
270
|
+
sendMessage(
|
|
271
|
+
threadId: string,
|
|
272
|
+
content: string,
|
|
273
|
+
role?: string
|
|
274
|
+
): Promise<Response>;
|
|
275
|
+
stop(): Promise<Response>;
|
|
276
|
+
shouldStop(): Promise<boolean>;
|
|
277
|
+
|
|
278
|
+
// Message methods
|
|
279
|
+
getMessages(
|
|
280
|
+
limit?: number,
|
|
281
|
+
offset?: number,
|
|
282
|
+
order?: 'ASC' | 'DESC',
|
|
283
|
+
includeSilent?: boolean,
|
|
284
|
+
maxDepth?: number
|
|
285
|
+
): Promise<GetMessagesResult>;
|
|
286
|
+
deleteMessage(messageId: string): Promise<{ success: boolean; error?: string }>;
|
|
287
|
+
seedMessages(args: {
|
|
288
|
+
messages: Array<{
|
|
289
|
+
id: string;
|
|
290
|
+
role: string;
|
|
291
|
+
content: string;
|
|
292
|
+
created_at: number;
|
|
293
|
+
}>;
|
|
294
|
+
}): Promise<{ success: boolean; count?: number; error?: string }>;
|
|
295
|
+
|
|
296
|
+
// Log methods
|
|
297
|
+
getLogs(
|
|
298
|
+
limit?: number,
|
|
299
|
+
offset?: number,
|
|
300
|
+
order?: 'ASC' | 'DESC'
|
|
301
|
+
): Promise<GetLogsResult>;
|
|
302
|
+
getLogDetails(logId: string): Promise<ThreadLogDetails>;
|
|
303
|
+
|
|
304
|
+
// Thread management methods
|
|
305
|
+
getThreadMeta(threadId: string): Promise<ThreadMetaResult>;
|
|
306
|
+
updateThreadMeta(
|
|
307
|
+
threadId: string,
|
|
308
|
+
params: UpdateThreadParams
|
|
309
|
+
): Promise<{
|
|
310
|
+
success: boolean;
|
|
311
|
+
error?: string;
|
|
312
|
+
thread?: {
|
|
313
|
+
id: string;
|
|
314
|
+
agent_id: string;
|
|
315
|
+
user_id: string | null;
|
|
316
|
+
tags: string[];
|
|
317
|
+
created_at: number;
|
|
318
|
+
};
|
|
319
|
+
}>;
|
|
320
|
+
deleteThread(): Promise<{ success: boolean; message: string }>;
|
|
321
|
+
|
|
322
|
+
// WebSocket/fetch handler
|
|
323
|
+
fetch(request: Request): Promise<Response>;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// ============================================================
|
|
327
|
+
// DurableAgentBuilder Types
|
|
328
|
+
// ============================================================
|
|
329
|
+
|
|
330
|
+
export interface ThreadRegistryEntry {
|
|
331
|
+
id: string;
|
|
332
|
+
agent_name: string;
|
|
333
|
+
user_id: string | null;
|
|
334
|
+
tags: string[] | null;
|
|
335
|
+
created_at: number;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export interface UpdateThreadParams {
|
|
339
|
+
agent_name?: string;
|
|
340
|
+
user_id?: string | null;
|
|
341
|
+
tags?: string[] | null;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export interface User {
|
|
345
|
+
id: string;
|
|
346
|
+
username: string;
|
|
347
|
+
password_hash: string;
|
|
348
|
+
role: 'admin';
|
|
349
|
+
created_at: number;
|
|
350
|
+
updated_at: number;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export interface Provider {
|
|
354
|
+
name: string;
|
|
355
|
+
sdk: string;
|
|
356
|
+
api_key: string;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// ============================================================
|
|
360
|
+
// DurableAgentBuilder Class
|
|
361
|
+
// ============================================================
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* DurableAgentBuilder with all virtual module methods pre-implemented.
|
|
365
|
+
* Extend this class in your agents/AgentBuilder.ts file.
|
|
366
|
+
*
|
|
367
|
+
* @example
|
|
368
|
+
* \`\`\`typescript
|
|
369
|
+
* import { DurableAgentBuilder } from 'virtual:@standardagents/builder'
|
|
370
|
+
*
|
|
371
|
+
* export class AgentBuilder extends DurableAgentBuilder {}
|
|
372
|
+
* \`\`\`
|
|
373
|
+
*/
|
|
374
|
+
export class DurableAgentBuilder extends BaseDurableAgentBuilder {
|
|
375
|
+
// Virtual module registry methods
|
|
376
|
+
tools(): Record<string, () => Promise<any>>;
|
|
377
|
+
hooks(): Record<string, () => Promise<any>>;
|
|
378
|
+
models(): Record<string, () => Promise<any>>;
|
|
379
|
+
prompts(): Record<string, () => Promise<any>>;
|
|
380
|
+
agents(): Record<string, () => Promise<any>>;
|
|
381
|
+
|
|
382
|
+
// Lookup methods
|
|
383
|
+
loadModel(name: string): Promise<any>;
|
|
384
|
+
getModelNames(): string[];
|
|
385
|
+
loadPrompt(name: string): Promise<any>;
|
|
386
|
+
getPromptNames(): string[];
|
|
387
|
+
loadAgent(name: string): Promise<any>;
|
|
388
|
+
getAgentNames(): string[];
|
|
389
|
+
|
|
390
|
+
// Thread registry methods
|
|
391
|
+
createThread(params: {
|
|
392
|
+
agent_name: string;
|
|
393
|
+
user_id?: string;
|
|
394
|
+
tags?: string[];
|
|
395
|
+
}): Promise<ThreadRegistryEntry>;
|
|
396
|
+
getThread(id: string): Promise<ThreadRegistryEntry | null>;
|
|
397
|
+
listThreads(params?: {
|
|
398
|
+
agent_name?: string;
|
|
399
|
+
user_id?: string;
|
|
400
|
+
limit?: number;
|
|
401
|
+
offset?: number;
|
|
402
|
+
}): Promise<{ threads: ThreadRegistryEntry[]; total: number }>;
|
|
403
|
+
deleteThread(id: string): Promise<boolean>;
|
|
404
|
+
updateThreadAgent(id: string, agent_name: string): Promise<boolean>;
|
|
405
|
+
updateThread(id: string, params: UpdateThreadParams): Promise<ThreadRegistryEntry | null>;
|
|
406
|
+
getThreadStub(threadId: string): DurableObjectStub;
|
|
407
|
+
|
|
408
|
+
// Provider methods
|
|
409
|
+
getProvider(name: string): Promise<Provider | null>;
|
|
410
|
+
setProvider(provider: Provider): Promise<void>;
|
|
411
|
+
listProviders(): Promise<Provider[]>;
|
|
412
|
+
deleteProvider(name: string): Promise<boolean>;
|
|
413
|
+
|
|
414
|
+
// User methods
|
|
415
|
+
getUserByUsername(username: string): Promise<User | null>;
|
|
416
|
+
getUserById(id: string): Promise<User | null>;
|
|
417
|
+
createUser(params: {
|
|
418
|
+
username: string;
|
|
419
|
+
password_hash: string;
|
|
420
|
+
role?: 'admin';
|
|
421
|
+
}): Promise<User>;
|
|
422
|
+
hasUsers(): Promise<boolean>;
|
|
423
|
+
listUsers(): Promise<Array<{
|
|
424
|
+
id: string;
|
|
425
|
+
username: string;
|
|
426
|
+
role: 'admin';
|
|
427
|
+
created_at: number;
|
|
428
|
+
updated_at: number;
|
|
429
|
+
}>>;
|
|
430
|
+
updateUser(
|
|
431
|
+
id: string,
|
|
432
|
+
params: {
|
|
433
|
+
username?: string;
|
|
434
|
+
password_hash?: string;
|
|
435
|
+
role?: 'admin';
|
|
436
|
+
}
|
|
437
|
+
): Promise<User | null>;
|
|
438
|
+
deleteUser(id: string): Promise<boolean>;
|
|
439
|
+
|
|
440
|
+
// Session methods
|
|
441
|
+
createSession(params: {
|
|
442
|
+
user_id: string;
|
|
443
|
+
token_hash: string;
|
|
444
|
+
expires_at: number;
|
|
445
|
+
}): Promise<string>;
|
|
446
|
+
validateSession(tokenHash: string): Promise<{ user_id: string; expires_at: number } | null>;
|
|
447
|
+
cleanupSessions(): Promise<number>;
|
|
448
|
+
deleteSession(tokenHash: string): Promise<void>;
|
|
449
|
+
|
|
450
|
+
// API key methods
|
|
451
|
+
createApiKey(params: {
|
|
452
|
+
name: string;
|
|
453
|
+
key_hash: string;
|
|
454
|
+
key_prefix: string;
|
|
455
|
+
last_five: string;
|
|
456
|
+
user_id: string;
|
|
457
|
+
}): Promise<string>;
|
|
458
|
+
validateApiKey(keyHash: string): Promise<{ user_id: string; id: string } | null>;
|
|
459
|
+
listApiKeys(userId: string): Promise<Array<{
|
|
460
|
+
id: string;
|
|
461
|
+
name: string;
|
|
462
|
+
key_prefix: string;
|
|
463
|
+
last_five: string;
|
|
464
|
+
created_at: number;
|
|
465
|
+
last_used_at: number | null;
|
|
466
|
+
}>>;
|
|
467
|
+
deleteApiKey(id: string, userId: string): Promise<boolean>;
|
|
468
|
+
|
|
469
|
+
// OAuth methods
|
|
470
|
+
linkOAuthAccount(params: {
|
|
471
|
+
user_id: string;
|
|
472
|
+
provider: 'github' | 'google';
|
|
473
|
+
provider_user_id: string;
|
|
474
|
+
provider_username?: string;
|
|
475
|
+
}): Promise<void>;
|
|
476
|
+
findUserByOAuth(
|
|
477
|
+
provider: 'github' | 'google',
|
|
478
|
+
providerUserId: string
|
|
479
|
+
): Promise<User | null>;
|
|
480
|
+
|
|
481
|
+
// Edit lock methods (for GitHub integration)
|
|
482
|
+
acquireEditLock(params: {
|
|
483
|
+
locked_by: string;
|
|
484
|
+
lock_reason: string;
|
|
485
|
+
}): Promise<boolean>;
|
|
486
|
+
releaseEditLock(lockedBy: string): Promise<boolean>;
|
|
487
|
+
getEditLockState(): Promise<{
|
|
488
|
+
locked: boolean;
|
|
489
|
+
locked_by: string | null;
|
|
490
|
+
locked_at: number | null;
|
|
491
|
+
lock_reason: string | null;
|
|
492
|
+
pending_changes: string | null;
|
|
493
|
+
}>;
|
|
494
|
+
setPendingChanges(changes: string): Promise<void>;
|
|
495
|
+
|
|
496
|
+
// WebSocket/fetch handler
|
|
497
|
+
fetch(request: Request): Promise<Response>;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// ============================================================
|
|
501
|
+
// Router Function
|
|
502
|
+
// ============================================================
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Main router function for handling incoming requests.
|
|
506
|
+
* Routes requests to the appropriate API endpoint or serves the UI.
|
|
507
|
+
*
|
|
508
|
+
* @example
|
|
509
|
+
* \`\`\`typescript
|
|
510
|
+
* import { router } from 'virtual:@standardagents/builder'
|
|
511
|
+
*
|
|
512
|
+
* export default {
|
|
513
|
+
* async fetch(request: Request, env: Env) {
|
|
514
|
+
* const response = await router(request, env);
|
|
515
|
+
* return response ?? new Response('Not Found', { status: 404 });
|
|
516
|
+
* }
|
|
517
|
+
* }
|
|
518
|
+
* \`\`\`
|
|
519
|
+
*/
|
|
520
|
+
export function router<Env extends ThreadEnv = ThreadEnv>(
|
|
521
|
+
request: Request,
|
|
522
|
+
env: Env
|
|
523
|
+
): Promise<Response | null>;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Legacy alias for the router module.
|
|
528
|
+
* @deprecated Use \`import { router } from 'virtual:@standardagents/builder'\` instead.
|
|
529
|
+
*/
|
|
530
|
+
declare module 'virtual:@standardagents/router' {
|
|
531
|
+
import type { ThreadEnv } from '@standardagents/builder/runtime';
|
|
532
|
+
|
|
533
|
+
export function router<Env extends ThreadEnv = ThreadEnv>(
|
|
534
|
+
request: Request,
|
|
535
|
+
env: Env
|
|
536
|
+
): Promise<Response | null>;
|
|
537
|
+
|
|
538
|
+
export { DurableThread } from 'virtual:@standardagents/builder';
|
|
539
|
+
}
|
|
540
|
+
`;
|
|
541
|
+
}
|
|
542
|
+
function ensureDir(dir) {
|
|
543
|
+
if (!fs2.existsSync(dir)) {
|
|
544
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
function generateTypes(config) {
|
|
548
|
+
ensureDir(config.outputDir);
|
|
549
|
+
const typesContent = generateTypesContent(config);
|
|
550
|
+
fs2.writeFileSync(path3.join(config.outputDir, "types.d.ts"), typesContent);
|
|
551
|
+
const virtualModuleContent = generateVirtualModuleContent();
|
|
552
|
+
fs2.writeFileSync(path3.join(config.outputDir, "virtual-module.d.ts"), virtualModuleContent);
|
|
553
|
+
fs2.writeFileSync(path3.join(config.outputDir, "tsconfig.json"), TSCONFIG_CONTENT);
|
|
554
|
+
fs2.writeFileSync(path3.join(config.outputDir, ".gitignore"), "*\n");
|
|
555
|
+
}
|
|
556
|
+
function needsRegeneration(config) {
|
|
557
|
+
const typesPath = path3.join(config.outputDir, "types.d.ts");
|
|
558
|
+
if (!fs2.existsSync(typesPath)) {
|
|
559
|
+
return true;
|
|
560
|
+
}
|
|
561
|
+
const typesMtime = fs2.statSync(typesPath).mtime;
|
|
562
|
+
const dirs = [config.modelsDir, config.promptsDir, config.agentsDir, config.toolsDir];
|
|
563
|
+
for (const dir of dirs) {
|
|
564
|
+
if (!fs2.existsSync(dir)) {
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
const entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
568
|
+
for (const entry of entries) {
|
|
569
|
+
if (entry.isFile() && entry.name.endsWith(".ts")) {
|
|
570
|
+
const filePath = path3.join(dir, entry.name);
|
|
571
|
+
const fileMtime = fs2.statSync(filePath).mtime;
|
|
572
|
+
if (fileMtime > typesMtime) {
|
|
573
|
+
return true;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// src/sdk/generators/generateModelFile.ts
|
|
582
|
+
function generateModelFile(data) {
|
|
583
|
+
const lines = [
|
|
584
|
+
`import { defineModel } from '@standardagents/builder';`,
|
|
585
|
+
"",
|
|
586
|
+
`export default defineModel({`,
|
|
587
|
+
` name: '${escapeString(data.name)}',`,
|
|
588
|
+
` provider: '${escapeString(data.provider)}',`,
|
|
589
|
+
` model: '${escapeString(data.model)}',`
|
|
590
|
+
];
|
|
591
|
+
if (data.includedProviders && data.includedProviders.length > 0) {
|
|
592
|
+
lines.push(` includedProviders: ${JSON.stringify(data.includedProviders)},`);
|
|
593
|
+
}
|
|
594
|
+
if (data.fallbacks && data.fallbacks.length > 0) {
|
|
595
|
+
lines.push(` fallbacks: ${JSON.stringify(data.fallbacks)},`);
|
|
596
|
+
}
|
|
597
|
+
if (data.inputPrice !== void 0) {
|
|
598
|
+
lines.push(` inputPrice: ${data.inputPrice},`);
|
|
599
|
+
}
|
|
600
|
+
if (data.outputPrice !== void 0) {
|
|
601
|
+
lines.push(` outputPrice: ${data.outputPrice},`);
|
|
602
|
+
}
|
|
603
|
+
if (data.cachedPrice !== void 0) {
|
|
604
|
+
lines.push(` cachedPrice: ${data.cachedPrice},`);
|
|
605
|
+
}
|
|
606
|
+
lines.push(`});`);
|
|
607
|
+
lines.push("");
|
|
608
|
+
return lines.join("\n");
|
|
609
|
+
}
|
|
610
|
+
function escapeString(str) {
|
|
611
|
+
return str.replace(/'/g, "\\'").replace(/\\/g, "\\\\");
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// src/sdk/generators/jsonSchemaToZod.ts
|
|
615
|
+
function jsonSchemaToZod(schema, indent = 0) {
|
|
616
|
+
if (!schema || Object.keys(schema).length === 0) {
|
|
617
|
+
return "z.any()";
|
|
618
|
+
}
|
|
619
|
+
if (schema.anyOf && schema.anyOf.length > 0) {
|
|
620
|
+
const types = schema.anyOf.map((s) => jsonSchemaToZod(s, indent));
|
|
621
|
+
if (types.length === 1) {
|
|
622
|
+
return types[0];
|
|
623
|
+
}
|
|
624
|
+
return `z.union([${types.join(", ")}])`;
|
|
625
|
+
}
|
|
626
|
+
if (schema.oneOf && schema.oneOf.length > 0) {
|
|
627
|
+
const types = schema.oneOf.map((s) => jsonSchemaToZod(s, indent));
|
|
628
|
+
if (types.length === 1) {
|
|
629
|
+
return types[0];
|
|
630
|
+
}
|
|
631
|
+
return `z.union([${types.join(", ")}])`;
|
|
632
|
+
}
|
|
633
|
+
if (schema.allOf && schema.allOf.length > 0) {
|
|
634
|
+
const types = schema.allOf.map((s) => jsonSchemaToZod(s, indent));
|
|
635
|
+
if (types.length === 1) {
|
|
636
|
+
return types[0];
|
|
637
|
+
}
|
|
638
|
+
return types.reduce((acc, t) => `${acc}.and(${t})`);
|
|
639
|
+
}
|
|
640
|
+
if (schema.const !== void 0) {
|
|
641
|
+
return `z.literal(${JSON.stringify(schema.const)})`;
|
|
642
|
+
}
|
|
643
|
+
if (schema.enum && schema.enum.length > 0) {
|
|
644
|
+
const values = schema.enum.map((v) => JSON.stringify(v));
|
|
645
|
+
if (values.length === 1) {
|
|
646
|
+
return `z.literal(${values[0]})`;
|
|
647
|
+
}
|
|
648
|
+
return `z.enum([${values.join(", ")}])`;
|
|
649
|
+
}
|
|
650
|
+
switch (schema.type) {
|
|
651
|
+
case "string":
|
|
652
|
+
return buildStringSchema(schema);
|
|
653
|
+
case "number":
|
|
654
|
+
case "integer":
|
|
655
|
+
return buildNumberSchema(schema);
|
|
656
|
+
case "boolean":
|
|
657
|
+
return addDescription("z.boolean()", schema.description);
|
|
658
|
+
case "null":
|
|
659
|
+
return addDescription("z.null()", schema.description);
|
|
660
|
+
case "array":
|
|
661
|
+
return buildArraySchema(schema, indent);
|
|
662
|
+
case "object":
|
|
663
|
+
return buildObjectSchema(schema, indent);
|
|
664
|
+
default:
|
|
665
|
+
return "z.any()";
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
function buildStringSchema(schema) {
|
|
669
|
+
let code = "z.string()";
|
|
670
|
+
if (schema.minLength !== void 0) {
|
|
671
|
+
code += `.min(${schema.minLength})`;
|
|
672
|
+
}
|
|
673
|
+
if (schema.maxLength !== void 0) {
|
|
674
|
+
code += `.max(${schema.maxLength})`;
|
|
675
|
+
}
|
|
676
|
+
if (schema.pattern) {
|
|
677
|
+
code += `.regex(/${escapeRegex(schema.pattern)}/)`;
|
|
678
|
+
}
|
|
679
|
+
if (schema.format) {
|
|
680
|
+
switch (schema.format) {
|
|
681
|
+
case "email":
|
|
682
|
+
code += ".email()";
|
|
683
|
+
break;
|
|
684
|
+
case "uri":
|
|
685
|
+
case "url":
|
|
686
|
+
code += ".url()";
|
|
687
|
+
break;
|
|
688
|
+
case "uuid":
|
|
689
|
+
code += ".uuid()";
|
|
690
|
+
break;
|
|
691
|
+
case "date-time":
|
|
692
|
+
code += ".datetime()";
|
|
693
|
+
break;
|
|
694
|
+
case "date":
|
|
695
|
+
code += ".date()";
|
|
696
|
+
break;
|
|
697
|
+
case "time":
|
|
698
|
+
code += ".time()";
|
|
699
|
+
break;
|
|
700
|
+
case "ipv4":
|
|
701
|
+
code += '.ip({ version: "v4" })';
|
|
702
|
+
break;
|
|
703
|
+
case "ipv6":
|
|
704
|
+
code += '.ip({ version: "v6" })';
|
|
705
|
+
break;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
return addDescription(code, schema.description);
|
|
709
|
+
}
|
|
710
|
+
function buildNumberSchema(schema) {
|
|
711
|
+
let code = schema.type === "integer" ? "z.number().int()" : "z.number()";
|
|
712
|
+
if (schema.minimum !== void 0) {
|
|
713
|
+
code += `.min(${schema.minimum})`;
|
|
714
|
+
}
|
|
715
|
+
if (schema.maximum !== void 0) {
|
|
716
|
+
code += `.max(${schema.maximum})`;
|
|
717
|
+
}
|
|
718
|
+
return addDescription(code, schema.description);
|
|
719
|
+
}
|
|
720
|
+
function buildArraySchema(schema, indent) {
|
|
721
|
+
const itemsSchema = schema.items ? jsonSchemaToZod(schema.items, indent) : "z.any()";
|
|
722
|
+
let code = `z.array(${itemsSchema})`;
|
|
723
|
+
return addDescription(code, schema.description);
|
|
724
|
+
}
|
|
725
|
+
function buildObjectSchema(schema, indent) {
|
|
726
|
+
if (!schema.properties || Object.keys(schema.properties).length === 0) {
|
|
727
|
+
if (schema.additionalProperties === false) {
|
|
728
|
+
return addDescription("z.object({})", schema.description);
|
|
729
|
+
}
|
|
730
|
+
return addDescription("z.record(z.any())", schema.description);
|
|
731
|
+
}
|
|
732
|
+
const required = new Set(schema.required || []);
|
|
733
|
+
const spaces = " ".repeat(indent + 1);
|
|
734
|
+
const closingSpaces = " ".repeat(indent);
|
|
735
|
+
const props = Object.entries(schema.properties).map(([key, propSchema]) => {
|
|
736
|
+
let propCode = jsonSchemaToZod(propSchema, indent + 1);
|
|
737
|
+
if (propSchema.nullable) {
|
|
738
|
+
propCode += ".nullable()";
|
|
739
|
+
}
|
|
740
|
+
if (!required.has(key)) {
|
|
741
|
+
propCode += ".optional()";
|
|
742
|
+
}
|
|
743
|
+
if (propSchema.default !== void 0) {
|
|
744
|
+
propCode += `.default(${JSON.stringify(propSchema.default)})`;
|
|
745
|
+
}
|
|
746
|
+
return `${spaces}${safePropertyKey(key)}: ${propCode}`;
|
|
747
|
+
});
|
|
748
|
+
const propsStr = props.join(",\n");
|
|
749
|
+
let code = `z.object({
|
|
750
|
+
${propsStr}
|
|
751
|
+
${closingSpaces}})`;
|
|
752
|
+
if (schema.additionalProperties === false) {
|
|
753
|
+
code += ".strict()";
|
|
754
|
+
}
|
|
755
|
+
return addDescription(code, schema.description);
|
|
756
|
+
}
|
|
757
|
+
function addDescription(code, description) {
|
|
758
|
+
if (description) {
|
|
759
|
+
return `${code}.describe(${JSON.stringify(description)})`;
|
|
760
|
+
}
|
|
761
|
+
return code;
|
|
762
|
+
}
|
|
763
|
+
function escapeRegex(pattern) {
|
|
764
|
+
return pattern.replace(/\//g, "\\/");
|
|
765
|
+
}
|
|
766
|
+
function safePropertyKey(key) {
|
|
767
|
+
if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key)) {
|
|
768
|
+
return key;
|
|
769
|
+
}
|
|
770
|
+
return JSON.stringify(key);
|
|
771
|
+
}
|
|
772
|
+
function isEmptySchema(schema) {
|
|
773
|
+
if (!schema) return true;
|
|
774
|
+
if (Object.keys(schema).length === 0) return true;
|
|
775
|
+
if (schema.type === "object" && (!schema.properties || Object.keys(schema.properties).length === 0) && (!schema.required || schema.required.length === 0)) {
|
|
776
|
+
return true;
|
|
777
|
+
}
|
|
778
|
+
return false;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// src/sdk/generators/generatePromptFile.ts
|
|
782
|
+
function generatePromptFile(data) {
|
|
783
|
+
const hasSchema = data.requiredSchema && !isEmptySchema(data.requiredSchema);
|
|
784
|
+
const lines = [`import { definePrompt } from '@standardagents/builder';`];
|
|
785
|
+
if (hasSchema) {
|
|
786
|
+
lines.push(`import { z } from 'zod';`);
|
|
787
|
+
}
|
|
788
|
+
lines.push("");
|
|
789
|
+
lines.push(`export default definePrompt({`);
|
|
790
|
+
lines.push(` name: '${escapeString2(data.name)}',`);
|
|
791
|
+
if (data.toolDescription) {
|
|
792
|
+
lines.push(` toolDescription: '${escapeString2(data.toolDescription)}',`);
|
|
793
|
+
}
|
|
794
|
+
if (data.prompt !== void 0) {
|
|
795
|
+
const promptCode = formatPromptContent(data.prompt);
|
|
796
|
+
lines.push(` prompt: ${promptCode},`);
|
|
797
|
+
}
|
|
798
|
+
lines.push(` model: '${escapeString2(data.model)}',`);
|
|
799
|
+
if (data.includeChat !== void 0) {
|
|
800
|
+
lines.push(` includeChat: ${data.includeChat},`);
|
|
801
|
+
}
|
|
802
|
+
if (data.includePastTools !== void 0) {
|
|
803
|
+
lines.push(` includePastTools: ${data.includePastTools},`);
|
|
804
|
+
}
|
|
805
|
+
if (data.parallelToolCalls !== void 0) {
|
|
806
|
+
lines.push(` parallelToolCalls: ${data.parallelToolCalls},`);
|
|
807
|
+
}
|
|
808
|
+
if (data.toolChoice && data.toolChoice !== "auto") {
|
|
809
|
+
lines.push(` toolChoice: '${data.toolChoice}',`);
|
|
810
|
+
}
|
|
811
|
+
if (data.tools && data.tools.length > 0) {
|
|
812
|
+
const toolsCode = formatToolsArray(data.tools);
|
|
813
|
+
lines.push(` tools: ${toolsCode},`);
|
|
814
|
+
}
|
|
815
|
+
if (data.handoffAgents && data.handoffAgents.length > 0) {
|
|
816
|
+
const agentsStr = data.handoffAgents.map((a) => `'${escapeString2(a)}'`).join(", ");
|
|
817
|
+
lines.push(` handoffAgents: [${agentsStr}],`);
|
|
818
|
+
}
|
|
819
|
+
if (data.beforeTool) {
|
|
820
|
+
lines.push(` beforeTool: '${escapeString2(data.beforeTool)}',`);
|
|
821
|
+
}
|
|
822
|
+
if (data.afterTool) {
|
|
823
|
+
lines.push(` afterTool: '${escapeString2(data.afterTool)}',`);
|
|
824
|
+
}
|
|
825
|
+
if (data.reasoning && hasNonNullProperties(data.reasoning)) {
|
|
826
|
+
const reasoningCode = formatReasoningConfig(data.reasoning);
|
|
827
|
+
lines.push(` reasoning: ${reasoningCode},`);
|
|
828
|
+
}
|
|
829
|
+
if (hasSchema) {
|
|
830
|
+
const zodCode = jsonSchemaToZod(data.requiredSchema, 1);
|
|
831
|
+
lines.push(` requiredSchema: ${zodCode},`);
|
|
832
|
+
}
|
|
833
|
+
lines.push(`});`);
|
|
834
|
+
lines.push("");
|
|
835
|
+
return lines.join("\n");
|
|
836
|
+
}
|
|
837
|
+
function hasNonNullProperties(obj) {
|
|
838
|
+
return Object.values(obj).some((v) => v !== null && v !== void 0);
|
|
839
|
+
}
|
|
840
|
+
function formatToolsArray(tools) {
|
|
841
|
+
const formatted = tools.map((tool) => {
|
|
842
|
+
if (typeof tool === "string") {
|
|
843
|
+
return `'${escapeString2(tool)}'`;
|
|
844
|
+
}
|
|
845
|
+
return formatToolConfig(tool);
|
|
846
|
+
});
|
|
847
|
+
if (formatted.length === 1) {
|
|
848
|
+
return `[${formatted[0]}]`;
|
|
849
|
+
}
|
|
850
|
+
const indented = formatted.map((t) => ` ${t}`).join(",\n");
|
|
851
|
+
return `[
|
|
852
|
+
${indented}
|
|
853
|
+
]`;
|
|
854
|
+
}
|
|
855
|
+
function formatToolConfig(config) {
|
|
856
|
+
const parts = [];
|
|
857
|
+
parts.push(`name: '${escapeString2(config.name)}'`);
|
|
858
|
+
if (config.include_text_response !== void 0 && config.include_text_response !== true) {
|
|
859
|
+
parts.push(`includeTextResponse: ${config.include_text_response}`);
|
|
860
|
+
}
|
|
861
|
+
if (config.include_tool_calls !== void 0 && config.include_tool_calls !== true) {
|
|
862
|
+
parts.push(`includeToolCalls: ${config.include_tool_calls}`);
|
|
863
|
+
}
|
|
864
|
+
if (config.include_errors !== void 0 && config.include_errors !== true) {
|
|
865
|
+
parts.push(`includeErrors: ${config.include_errors}`);
|
|
866
|
+
}
|
|
867
|
+
if (config.init_user_message_property !== void 0 && config.init_user_message_property !== null) {
|
|
868
|
+
parts.push(`initUserMessageProperty: '${escapeString2(config.init_user_message_property)}'`);
|
|
869
|
+
}
|
|
870
|
+
return `{ ${parts.join(", ")} }`;
|
|
871
|
+
}
|
|
872
|
+
function formatReasoningConfig(reasoning) {
|
|
873
|
+
if (!reasoning) return "{}";
|
|
874
|
+
const parts = [];
|
|
875
|
+
if (reasoning.effort !== void 0 && reasoning.effort !== null) {
|
|
876
|
+
parts.push(`effort: '${reasoning.effort}'`);
|
|
877
|
+
}
|
|
878
|
+
if (reasoning.maxTokens !== void 0 && reasoning.maxTokens !== null) {
|
|
879
|
+
parts.push(`maxTokens: ${reasoning.maxTokens}`);
|
|
880
|
+
}
|
|
881
|
+
if (reasoning.exclude !== void 0 && reasoning.exclude !== null) {
|
|
882
|
+
parts.push(`exclude: ${reasoning.exclude}`);
|
|
883
|
+
}
|
|
884
|
+
if (reasoning.include !== void 0 && reasoning.include !== null) {
|
|
885
|
+
parts.push(`include: ${reasoning.include}`);
|
|
886
|
+
}
|
|
887
|
+
if (parts.length === 0) {
|
|
888
|
+
return "{}";
|
|
889
|
+
}
|
|
890
|
+
return `{ ${parts.join(", ")} }`;
|
|
891
|
+
}
|
|
892
|
+
function escapeString2(str) {
|
|
893
|
+
return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
894
|
+
}
|
|
895
|
+
function escapeTemplateLiteral(str) {
|
|
896
|
+
return str.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\${/g, "\\${");
|
|
897
|
+
}
|
|
898
|
+
function parsePromptContent(prompt) {
|
|
899
|
+
if (!prompt) return null;
|
|
900
|
+
if (Array.isArray(prompt)) {
|
|
901
|
+
return prompt;
|
|
902
|
+
}
|
|
903
|
+
if (typeof prompt === "string") {
|
|
904
|
+
try {
|
|
905
|
+
const parsed = JSON.parse(prompt);
|
|
906
|
+
if (Array.isArray(parsed)) {
|
|
907
|
+
return parsed;
|
|
908
|
+
}
|
|
909
|
+
} catch {
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
return null;
|
|
913
|
+
}
|
|
914
|
+
function formatPromptContent(prompt) {
|
|
915
|
+
if (!prompt) return "''";
|
|
916
|
+
const parts = parsePromptContent(prompt);
|
|
917
|
+
if (parts !== null) {
|
|
918
|
+
return formatStructuredPrompt(parts);
|
|
919
|
+
}
|
|
920
|
+
if (typeof prompt === "string") {
|
|
921
|
+
if (prompt.includes("\n")) {
|
|
922
|
+
return `\`${escapeTemplateLiteral(prompt)}\``;
|
|
923
|
+
}
|
|
924
|
+
return `'${escapeString2(prompt)}'`;
|
|
925
|
+
}
|
|
926
|
+
return "''";
|
|
927
|
+
}
|
|
928
|
+
function formatStructuredPrompt(parts) {
|
|
929
|
+
if (parts.length === 0) {
|
|
930
|
+
return "[]";
|
|
931
|
+
}
|
|
932
|
+
const normalizedParts = parts.map(normalizePart);
|
|
933
|
+
if (normalizedParts.length === 1 && normalizedParts[0].type === "text") {
|
|
934
|
+
const content = normalizedParts[0].content || "";
|
|
935
|
+
if (!content.includes("\n") && content.length < 50) {
|
|
936
|
+
return `[{ type: 'text', content: '${escapeString2(content)}' }]`;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
const formattedParts = normalizedParts.map((part) => {
|
|
940
|
+
if (part.type === "text") {
|
|
941
|
+
const content = part.content || "";
|
|
942
|
+
if (content.includes("\n")) {
|
|
943
|
+
return ` { type: 'text', content: \`${escapeTemplateLiteral(content)}\` }`;
|
|
944
|
+
}
|
|
945
|
+
return ` { type: 'text', content: '${escapeString2(content)}' }`;
|
|
946
|
+
} else if (part.type === "include") {
|
|
947
|
+
const promptName = part.prompt || "";
|
|
948
|
+
return ` { type: 'include', prompt: '${escapeString2(promptName)}' }`;
|
|
949
|
+
}
|
|
950
|
+
return ` // Unknown part type: ${part.type}`;
|
|
951
|
+
});
|
|
952
|
+
return `[
|
|
953
|
+
${formattedParts.join(",\n")},
|
|
954
|
+
]`;
|
|
955
|
+
}
|
|
956
|
+
function normalizePart(part) {
|
|
957
|
+
if (part.type === "text") {
|
|
958
|
+
return { type: "text", content: part.content || "" };
|
|
959
|
+
}
|
|
960
|
+
if (part.type === "include") {
|
|
961
|
+
return { type: "include", prompt: part.prompt || "" };
|
|
962
|
+
}
|
|
963
|
+
if (part.type === "string") {
|
|
964
|
+
return { type: "text", content: part.value || "" };
|
|
965
|
+
}
|
|
966
|
+
if (part.type === "prompt") {
|
|
967
|
+
return { type: "include", prompt: part.id || "" };
|
|
968
|
+
}
|
|
969
|
+
if (part.type === "variable") {
|
|
970
|
+
return { type: "text", content: `{{${part.value || ""}}}` };
|
|
971
|
+
}
|
|
972
|
+
return { type: "text", content: "" };
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// src/sdk/generators/generateAgentFile.ts
|
|
976
|
+
function generateAgentFile(data) {
|
|
977
|
+
const lines = [
|
|
978
|
+
`import { defineAgent } from '@standardagents/builder';`,
|
|
979
|
+
"",
|
|
980
|
+
`export default defineAgent({`,
|
|
981
|
+
` name: '${escapeString3(data.name)}',`
|
|
982
|
+
];
|
|
983
|
+
if (data.title) {
|
|
984
|
+
lines.push(` title: '${escapeString3(data.title)}',`);
|
|
985
|
+
}
|
|
986
|
+
if (data.type && data.type !== "ai_human") {
|
|
987
|
+
lines.push(` type: '${data.type}',`);
|
|
988
|
+
}
|
|
989
|
+
if (data.maxSessionTurns !== void 0) {
|
|
990
|
+
lines.push(` maxSessionTurns: ${data.maxSessionTurns},`);
|
|
991
|
+
}
|
|
992
|
+
lines.push(` sideA: ${formatSideConfig(data.sideA)},`);
|
|
993
|
+
if (data.sideB) {
|
|
994
|
+
lines.push(` sideB: ${formatSideConfig(data.sideB)},`);
|
|
995
|
+
}
|
|
996
|
+
if (data.exposeAsTool !== void 0) {
|
|
997
|
+
lines.push(` exposeAsTool: ${data.exposeAsTool},`);
|
|
998
|
+
}
|
|
999
|
+
if (data.toolDescription) {
|
|
1000
|
+
lines.push(` toolDescription: '${escapeString3(data.toolDescription)}',`);
|
|
1001
|
+
}
|
|
1002
|
+
if (data.tags && data.tags.length > 0) {
|
|
1003
|
+
lines.push(` tags: ${JSON.stringify(data.tags)},`);
|
|
1004
|
+
}
|
|
1005
|
+
lines.push(`});`);
|
|
1006
|
+
lines.push("");
|
|
1007
|
+
return lines.join("\n");
|
|
1008
|
+
}
|
|
1009
|
+
function formatSideConfig(config) {
|
|
1010
|
+
const parts = ["{"];
|
|
1011
|
+
if (config.label) {
|
|
1012
|
+
parts.push(` label: '${escapeString3(config.label)}',`);
|
|
1013
|
+
}
|
|
1014
|
+
parts.push(` prompt: '${escapeString3(config.prompt)}',`);
|
|
1015
|
+
if (config.stopOnResponse !== void 0) {
|
|
1016
|
+
parts.push(` stopOnResponse: ${config.stopOnResponse},`);
|
|
1017
|
+
}
|
|
1018
|
+
if (config.stopTool) {
|
|
1019
|
+
parts.push(` stopTool: '${escapeString3(config.stopTool)}',`);
|
|
1020
|
+
}
|
|
1021
|
+
if (config.stopToolResponseProperty) {
|
|
1022
|
+
parts.push(` stopToolResponseProperty: '${escapeString3(config.stopToolResponseProperty)}',`);
|
|
1023
|
+
}
|
|
1024
|
+
if (config.maxTurns !== void 0) {
|
|
1025
|
+
parts.push(` maxTurns: ${config.maxTurns},`);
|
|
1026
|
+
}
|
|
1027
|
+
if (config.endConversationTool) {
|
|
1028
|
+
parts.push(` endConversationTool: '${escapeString3(config.endConversationTool)}',`);
|
|
1029
|
+
}
|
|
1030
|
+
if (config.manualStopCondition !== void 0) {
|
|
1031
|
+
parts.push(` manualStopCondition: ${config.manualStopCondition},`);
|
|
1032
|
+
}
|
|
1033
|
+
parts.push(" }");
|
|
1034
|
+
return parts.join("\n");
|
|
1035
|
+
}
|
|
1036
|
+
function escapeString3(str) {
|
|
1037
|
+
return str.replace(/'/g, "\\'").replace(/\\/g, "\\\\");
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// src/sdk/persistence/index.ts
|
|
1041
|
+
function nameToFilename(name) {
|
|
1042
|
+
return name.replace(/[/\\]/g, "__").replace(/[:*?"<>|]/g, "_").replace(/-/g, "_");
|
|
1043
|
+
}
|
|
1044
|
+
function getModelFilePath(modelsDir, name) {
|
|
1045
|
+
const filename = nameToFilename(name);
|
|
1046
|
+
return path3.join(modelsDir, `${filename}.ts`);
|
|
1047
|
+
}
|
|
1048
|
+
function modelExists(modelsDir, name) {
|
|
1049
|
+
const filePath = getModelFilePath(modelsDir, name);
|
|
1050
|
+
return fs2.existsSync(filePath);
|
|
1051
|
+
}
|
|
1052
|
+
async function saveModel(modelsDir, data, overwrite = false) {
|
|
1053
|
+
try {
|
|
1054
|
+
if (!fs2.existsSync(modelsDir)) {
|
|
1055
|
+
fs2.mkdirSync(modelsDir, { recursive: true });
|
|
1056
|
+
}
|
|
1057
|
+
const filePath = getModelFilePath(modelsDir, data.name);
|
|
1058
|
+
if (!overwrite && fs2.existsSync(filePath)) {
|
|
1059
|
+
return {
|
|
1060
|
+
success: false,
|
|
1061
|
+
error: `Model file already exists: ${filePath}. Use update to modify existing models.`
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
const content = generateModelFile(data);
|
|
1065
|
+
await fs2.promises.writeFile(filePath, content, "utf-8");
|
|
1066
|
+
return {
|
|
1067
|
+
success: true,
|
|
1068
|
+
filePath
|
|
1069
|
+
};
|
|
1070
|
+
} catch (error) {
|
|
1071
|
+
return {
|
|
1072
|
+
success: false,
|
|
1073
|
+
error: error.message || "Failed to save model"
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
async function deleteModel(modelsDir, name) {
|
|
1078
|
+
try {
|
|
1079
|
+
const filePath = getModelFilePath(modelsDir, name);
|
|
1080
|
+
if (!fs2.existsSync(filePath)) {
|
|
1081
|
+
return {
|
|
1082
|
+
success: false,
|
|
1083
|
+
error: `Model file not found: ${filePath}`
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
await fs2.promises.unlink(filePath);
|
|
1087
|
+
return {
|
|
1088
|
+
success: true,
|
|
1089
|
+
filePath
|
|
1090
|
+
};
|
|
1091
|
+
} catch (error) {
|
|
1092
|
+
return {
|
|
1093
|
+
success: false,
|
|
1094
|
+
error: error.message || "Failed to delete model"
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
function transformModelData(data) {
|
|
1099
|
+
const transformed = {};
|
|
1100
|
+
const isOpenRouter = data.provider === "openrouter";
|
|
1101
|
+
const fieldMappings = {
|
|
1102
|
+
included_providers: "includedProviders",
|
|
1103
|
+
input_price: "inputPrice",
|
|
1104
|
+
output_price: "outputPrice",
|
|
1105
|
+
cached_price: "cachedPrice"
|
|
1106
|
+
};
|
|
1107
|
+
const openRouterSkipFields = ["inputPrice", "outputPrice", "cachedPrice", "input_price", "output_price", "cached_price"];
|
|
1108
|
+
for (const [key, value] of Object.entries(data)) {
|
|
1109
|
+
if (key === "id") continue;
|
|
1110
|
+
if (isOpenRouter && openRouterSkipFields.includes(key)) continue;
|
|
1111
|
+
if (fieldMappings[key]) {
|
|
1112
|
+
const transformedKey = fieldMappings[key];
|
|
1113
|
+
if (isOpenRouter && openRouterSkipFields.includes(transformedKey)) continue;
|
|
1114
|
+
transformed[transformedKey] = value;
|
|
1115
|
+
} else {
|
|
1116
|
+
transformed[key] = value;
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
return transformed;
|
|
1120
|
+
}
|
|
1121
|
+
function validateModelData(data) {
|
|
1122
|
+
if (!data.name || typeof data.name !== "string") {
|
|
1123
|
+
return "Model name is required and must be a string";
|
|
1124
|
+
}
|
|
1125
|
+
if (!data.provider || typeof data.provider !== "string") {
|
|
1126
|
+
return "Model provider is required and must be a string";
|
|
1127
|
+
}
|
|
1128
|
+
const validProviders = ["openai", "openrouter", "anthropic", "google"];
|
|
1129
|
+
if (!validProviders.includes(data.provider)) {
|
|
1130
|
+
return `Invalid provider '${data.provider}'. Must be one of: ${validProviders.join(", ")}`;
|
|
1131
|
+
}
|
|
1132
|
+
if (!data.model || typeof data.model !== "string") {
|
|
1133
|
+
return "Model ID is required and must be a string";
|
|
1134
|
+
}
|
|
1135
|
+
if (data.inputPrice !== void 0 && typeof data.inputPrice !== "number") {
|
|
1136
|
+
return "inputPrice must be a number";
|
|
1137
|
+
}
|
|
1138
|
+
if (data.outputPrice !== void 0 && typeof data.outputPrice !== "number") {
|
|
1139
|
+
return "outputPrice must be a number";
|
|
1140
|
+
}
|
|
1141
|
+
if (data.cachedPrice !== void 0 && typeof data.cachedPrice !== "number") {
|
|
1142
|
+
return "cachedPrice must be a number";
|
|
1143
|
+
}
|
|
1144
|
+
if (data.fallbacks !== void 0) {
|
|
1145
|
+
if (!Array.isArray(data.fallbacks)) {
|
|
1146
|
+
return "fallbacks must be an array";
|
|
1147
|
+
}
|
|
1148
|
+
for (const fallback of data.fallbacks) {
|
|
1149
|
+
if (typeof fallback !== "string") {
|
|
1150
|
+
return "Each fallback must be a string (model name)";
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
if (data.includedProviders !== void 0) {
|
|
1155
|
+
if (!Array.isArray(data.includedProviders)) {
|
|
1156
|
+
return "includedProviders must be an array";
|
|
1157
|
+
}
|
|
1158
|
+
for (const provider of data.includedProviders) {
|
|
1159
|
+
if (typeof provider !== "string") {
|
|
1160
|
+
return "Each includedProvider must be a string";
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
return null;
|
|
1165
|
+
}
|
|
1166
|
+
function transformPromptData(data) {
|
|
1167
|
+
const transformed = {};
|
|
1168
|
+
const fieldMappings = {
|
|
1169
|
+
model_id: "model",
|
|
1170
|
+
tool_description: "toolDescription",
|
|
1171
|
+
expose_as_tool: "exposeAsTool",
|
|
1172
|
+
required_schema: "requiredSchema",
|
|
1173
|
+
include_chat: "includeChat",
|
|
1174
|
+
include_past_tools: "includePastTools",
|
|
1175
|
+
before_tool: "beforeTool",
|
|
1176
|
+
after_tool: "afterTool",
|
|
1177
|
+
parallel_tool_calls: "parallelToolCalls",
|
|
1178
|
+
tool_choice: "toolChoice",
|
|
1179
|
+
handoff_agents: "handoffAgents",
|
|
1180
|
+
reasoning_effort: "reasoningEffort",
|
|
1181
|
+
reasoning_max_tokens: "reasoningMaxTokens",
|
|
1182
|
+
reasoning_exclude: "reasoningExclude",
|
|
1183
|
+
include_reasoning: "includeReasoning"
|
|
1184
|
+
};
|
|
1185
|
+
for (const [key, value] of Object.entries(data)) {
|
|
1186
|
+
if (key === "id") continue;
|
|
1187
|
+
if (fieldMappings[key]) {
|
|
1188
|
+
transformed[fieldMappings[key]] = value;
|
|
1189
|
+
} else {
|
|
1190
|
+
transformed[key] = value;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
if (transformed.reasoningEffort !== void 0 || transformed.reasoningMaxTokens !== void 0 || transformed.reasoningExclude !== void 0 || transformed.includeReasoning !== void 0) {
|
|
1194
|
+
transformed.reasoning = {};
|
|
1195
|
+
if (transformed.reasoningEffort) {
|
|
1196
|
+
transformed.reasoning.effort = transformed.reasoningEffort;
|
|
1197
|
+
}
|
|
1198
|
+
if (transformed.reasoningMaxTokens) {
|
|
1199
|
+
transformed.reasoning.maxTokens = transformed.reasoningMaxTokens;
|
|
1200
|
+
}
|
|
1201
|
+
if (transformed.reasoningExclude !== void 0) {
|
|
1202
|
+
transformed.reasoning.exclude = transformed.reasoningExclude;
|
|
1203
|
+
}
|
|
1204
|
+
if (transformed.includeReasoning !== void 0) {
|
|
1205
|
+
transformed.reasoning.include = transformed.includeReasoning;
|
|
1206
|
+
}
|
|
1207
|
+
delete transformed.reasoningEffort;
|
|
1208
|
+
delete transformed.reasoningMaxTokens;
|
|
1209
|
+
delete transformed.reasoningExclude;
|
|
1210
|
+
delete transformed.includeReasoning;
|
|
1211
|
+
}
|
|
1212
|
+
return transformed;
|
|
1213
|
+
}
|
|
1214
|
+
function getPromptFilePath(promptsDir, name) {
|
|
1215
|
+
const filename = nameToFilename(name);
|
|
1216
|
+
return path3.join(promptsDir, `${filename}.ts`);
|
|
1217
|
+
}
|
|
1218
|
+
function promptExists(promptsDir, name) {
|
|
1219
|
+
const filePath = getPromptFilePath(promptsDir, name);
|
|
1220
|
+
return fs2.existsSync(filePath);
|
|
1221
|
+
}
|
|
1222
|
+
async function savePrompt(promptsDir, data, overwrite = false) {
|
|
1223
|
+
try {
|
|
1224
|
+
if (!fs2.existsSync(promptsDir)) {
|
|
1225
|
+
fs2.mkdirSync(promptsDir, { recursive: true });
|
|
1226
|
+
}
|
|
1227
|
+
const filePath = getPromptFilePath(promptsDir, data.name);
|
|
1228
|
+
if (!overwrite && fs2.existsSync(filePath)) {
|
|
1229
|
+
return {
|
|
1230
|
+
success: false,
|
|
1231
|
+
error: `Prompt file already exists: ${filePath}. Use update to modify existing prompts.`
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
const content = generatePromptFile(data);
|
|
1235
|
+
await fs2.promises.writeFile(filePath, content, "utf-8");
|
|
1236
|
+
return {
|
|
1237
|
+
success: true,
|
|
1238
|
+
filePath
|
|
1239
|
+
};
|
|
1240
|
+
} catch (error) {
|
|
1241
|
+
return {
|
|
1242
|
+
success: false,
|
|
1243
|
+
error: error.message || "Failed to save prompt"
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
async function deletePrompt(promptsDir, name) {
|
|
1248
|
+
try {
|
|
1249
|
+
const filePath = getPromptFilePath(promptsDir, name);
|
|
1250
|
+
if (!fs2.existsSync(filePath)) {
|
|
1251
|
+
return {
|
|
1252
|
+
success: false,
|
|
1253
|
+
error: `Prompt file not found: ${filePath}`
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
await fs2.promises.unlink(filePath);
|
|
1257
|
+
return {
|
|
1258
|
+
success: true,
|
|
1259
|
+
filePath
|
|
1260
|
+
};
|
|
1261
|
+
} catch (error) {
|
|
1262
|
+
return {
|
|
1263
|
+
success: false,
|
|
1264
|
+
error: error.message || "Failed to delete prompt"
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
async function renamePrompt(promptsDir, oldName, newName) {
|
|
1269
|
+
try {
|
|
1270
|
+
const oldFilePath = getPromptFilePath(promptsDir, oldName);
|
|
1271
|
+
const newFilePath = getPromptFilePath(promptsDir, newName);
|
|
1272
|
+
if (!fs2.existsSync(oldFilePath)) {
|
|
1273
|
+
return {
|
|
1274
|
+
success: false,
|
|
1275
|
+
error: `Prompt file not found: ${oldFilePath}`
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
if (fs2.existsSync(newFilePath)) {
|
|
1279
|
+
return {
|
|
1280
|
+
success: false,
|
|
1281
|
+
error: `Prompt file already exists: ${newFilePath}`
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
const content = await fs2.promises.readFile(oldFilePath, "utf-8");
|
|
1285
|
+
const updatedContent = content.replace(
|
|
1286
|
+
/name:\s*['"]([^'"]+)['"]/,
|
|
1287
|
+
`name: '${newName}'`
|
|
1288
|
+
);
|
|
1289
|
+
await fs2.promises.writeFile(newFilePath, updatedContent, "utf-8");
|
|
1290
|
+
await fs2.promises.unlink(oldFilePath);
|
|
1291
|
+
return {
|
|
1292
|
+
success: true,
|
|
1293
|
+
filePath: newFilePath
|
|
1294
|
+
};
|
|
1295
|
+
} catch (error) {
|
|
1296
|
+
return {
|
|
1297
|
+
success: false,
|
|
1298
|
+
error: error.message || "Failed to rename prompt"
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
function validatePromptData(data) {
|
|
1303
|
+
if (!data.name || typeof data.name !== "string") {
|
|
1304
|
+
return "Prompt name is required and must be a string";
|
|
1305
|
+
}
|
|
1306
|
+
if (!data.model || typeof data.model !== "string") {
|
|
1307
|
+
return "Prompt model is required and must be a string";
|
|
1308
|
+
}
|
|
1309
|
+
if (data.toolDescription !== void 0 && typeof data.toolDescription !== "string") {
|
|
1310
|
+
return "toolDescription must be a string";
|
|
1311
|
+
}
|
|
1312
|
+
if (data.prompt !== void 0 && typeof data.prompt !== "string") {
|
|
1313
|
+
return "prompt must be a string";
|
|
1314
|
+
}
|
|
1315
|
+
const booleanFields = ["includeChat", "includePastTools", "parallelToolCalls"];
|
|
1316
|
+
for (const field of booleanFields) {
|
|
1317
|
+
if (data[field] !== void 0 && typeof data[field] !== "boolean") {
|
|
1318
|
+
return `${field} must be a boolean`;
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
if (data.toolChoice !== void 0) {
|
|
1322
|
+
const validChoices = ["auto", "none", "required"];
|
|
1323
|
+
if (!validChoices.includes(data.toolChoice)) {
|
|
1324
|
+
return `Invalid toolChoice '${data.toolChoice}'. Must be one of: ${validChoices.join(", ")}`;
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
if (data.tools !== void 0 && !Array.isArray(data.tools)) {
|
|
1328
|
+
return "tools must be an array";
|
|
1329
|
+
}
|
|
1330
|
+
if (data.handoffAgents !== void 0 && !Array.isArray(data.handoffAgents)) {
|
|
1331
|
+
return "handoffAgents must be an array";
|
|
1332
|
+
}
|
|
1333
|
+
if (data.reasoning !== void 0) {
|
|
1334
|
+
if (typeof data.reasoning !== "object") {
|
|
1335
|
+
return "reasoning must be an object";
|
|
1336
|
+
}
|
|
1337
|
+
if (data.reasoning.effort !== void 0) {
|
|
1338
|
+
const validEfforts = ["low", "medium", "high"];
|
|
1339
|
+
if (!validEfforts.includes(data.reasoning.effort)) {
|
|
1340
|
+
return `Invalid reasoning.effort '${data.reasoning.effort}'. Must be one of: ${validEfforts.join(", ")}`;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
if (data.reasoning.maxTokens !== void 0 && typeof data.reasoning.maxTokens !== "number") {
|
|
1344
|
+
return "reasoning.maxTokens must be a number";
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
return null;
|
|
1348
|
+
}
|
|
1349
|
+
function transformAgentData(data) {
|
|
1350
|
+
const transformed = {
|
|
1351
|
+
name: data.name
|
|
1352
|
+
};
|
|
1353
|
+
if (data.title) {
|
|
1354
|
+
transformed.title = data.title;
|
|
1355
|
+
}
|
|
1356
|
+
if (data.type) {
|
|
1357
|
+
transformed.type = data.type;
|
|
1358
|
+
}
|
|
1359
|
+
if (data.max_session_turns !== void 0) {
|
|
1360
|
+
transformed.maxSessionTurns = data.max_session_turns;
|
|
1361
|
+
}
|
|
1362
|
+
transformed.sideA = {
|
|
1363
|
+
prompt: data.side_a_agent_prompt
|
|
1364
|
+
};
|
|
1365
|
+
if (data.side_a_label) {
|
|
1366
|
+
transformed.sideA.label = data.side_a_label;
|
|
1367
|
+
}
|
|
1368
|
+
if (data.side_a_stop_on_response !== void 0) {
|
|
1369
|
+
transformed.sideA.stopOnResponse = data.side_a_stop_on_response;
|
|
1370
|
+
}
|
|
1371
|
+
if (data.side_a_stop_tool) {
|
|
1372
|
+
transformed.sideA.stopTool = data.side_a_stop_tool;
|
|
1373
|
+
}
|
|
1374
|
+
if (data.side_a_stop_tool_response_property) {
|
|
1375
|
+
transformed.sideA.stopToolResponseProperty = data.side_a_stop_tool_response_property;
|
|
1376
|
+
}
|
|
1377
|
+
if (data.side_a_max_turns !== void 0) {
|
|
1378
|
+
transformed.sideA.maxTurns = data.side_a_max_turns;
|
|
1379
|
+
}
|
|
1380
|
+
if (data.side_a_end_conversation_tool) {
|
|
1381
|
+
transformed.sideA.endConversationTool = data.side_a_end_conversation_tool;
|
|
1382
|
+
}
|
|
1383
|
+
if (data.side_a_manual_stop_condition !== void 0) {
|
|
1384
|
+
transformed.sideA.manualStopCondition = data.side_a_manual_stop_condition;
|
|
1385
|
+
}
|
|
1386
|
+
if (data.side_b_agent_prompt) {
|
|
1387
|
+
transformed.sideB = {
|
|
1388
|
+
prompt: data.side_b_agent_prompt
|
|
1389
|
+
};
|
|
1390
|
+
if (data.side_b_label) {
|
|
1391
|
+
transformed.sideB.label = data.side_b_label;
|
|
1392
|
+
}
|
|
1393
|
+
if (data.side_b_stop_on_response !== void 0) {
|
|
1394
|
+
transformed.sideB.stopOnResponse = data.side_b_stop_on_response;
|
|
1395
|
+
}
|
|
1396
|
+
if (data.side_b_stop_tool) {
|
|
1397
|
+
transformed.sideB.stopTool = data.side_b_stop_tool;
|
|
1398
|
+
}
|
|
1399
|
+
if (data.side_b_stop_tool_response_property) {
|
|
1400
|
+
transformed.sideB.stopToolResponseProperty = data.side_b_stop_tool_response_property;
|
|
1401
|
+
}
|
|
1402
|
+
if (data.side_b_max_turns !== void 0) {
|
|
1403
|
+
transformed.sideB.maxTurns = data.side_b_max_turns;
|
|
1404
|
+
}
|
|
1405
|
+
if (data.side_b_end_conversation_tool) {
|
|
1406
|
+
transformed.sideB.endConversationTool = data.side_b_end_conversation_tool;
|
|
1407
|
+
}
|
|
1408
|
+
if (data.side_b_manual_stop_condition !== void 0) {
|
|
1409
|
+
transformed.sideB.manualStopCondition = data.side_b_manual_stop_condition;
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
if (data.expose_as_tool !== void 0) {
|
|
1413
|
+
transformed.exposeAsTool = data.expose_as_tool;
|
|
1414
|
+
}
|
|
1415
|
+
if (data.tool_description) {
|
|
1416
|
+
transformed.toolDescription = data.tool_description;
|
|
1417
|
+
}
|
|
1418
|
+
if (data.tags) {
|
|
1419
|
+
transformed.tags = data.tags;
|
|
1420
|
+
}
|
|
1421
|
+
return transformed;
|
|
1422
|
+
}
|
|
1423
|
+
function getAgentFilePath(agentsDir, name) {
|
|
1424
|
+
const filename = nameToFilename(name);
|
|
1425
|
+
return path3.join(agentsDir, `${filename}.ts`);
|
|
1426
|
+
}
|
|
1427
|
+
function agentExists(agentsDir, name) {
|
|
1428
|
+
const filePath = getAgentFilePath(agentsDir, name);
|
|
1429
|
+
return fs2.existsSync(filePath);
|
|
1430
|
+
}
|
|
1431
|
+
async function saveAgent(agentsDir, data, overwrite = false) {
|
|
1432
|
+
try {
|
|
1433
|
+
if (!fs2.existsSync(agentsDir)) {
|
|
1434
|
+
fs2.mkdirSync(agentsDir, { recursive: true });
|
|
1435
|
+
}
|
|
1436
|
+
const filePath = getAgentFilePath(agentsDir, data.name);
|
|
1437
|
+
if (!overwrite && fs2.existsSync(filePath)) {
|
|
1438
|
+
return {
|
|
1439
|
+
success: false,
|
|
1440
|
+
error: `Agent file already exists: ${filePath}. Use update to modify existing agents.`
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
const content = generateAgentFile(data);
|
|
1444
|
+
await fs2.promises.writeFile(filePath, content, "utf-8");
|
|
1445
|
+
return {
|
|
1446
|
+
success: true,
|
|
1447
|
+
filePath
|
|
1448
|
+
};
|
|
1449
|
+
} catch (error) {
|
|
1450
|
+
return {
|
|
1451
|
+
success: false,
|
|
1452
|
+
error: error.message || "Failed to save agent"
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
async function deleteAgent(agentsDir, name) {
|
|
1457
|
+
try {
|
|
1458
|
+
const filePath = getAgentFilePath(agentsDir, name);
|
|
1459
|
+
if (!fs2.existsSync(filePath)) {
|
|
1460
|
+
return {
|
|
1461
|
+
success: false,
|
|
1462
|
+
error: `Agent file not found: ${filePath}`
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1465
|
+
await fs2.promises.unlink(filePath);
|
|
1466
|
+
return {
|
|
1467
|
+
success: true,
|
|
1468
|
+
filePath
|
|
1469
|
+
};
|
|
1470
|
+
} catch (error) {
|
|
1471
|
+
return {
|
|
1472
|
+
success: false,
|
|
1473
|
+
error: error.message || "Failed to delete agent"
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
async function renameModel(modelsDir, oldName, newName) {
|
|
1478
|
+
try {
|
|
1479
|
+
const oldFilePath = getModelFilePath(modelsDir, oldName);
|
|
1480
|
+
const newFilePath = getModelFilePath(modelsDir, newName);
|
|
1481
|
+
if (!fs2.existsSync(oldFilePath)) {
|
|
1482
|
+
return {
|
|
1483
|
+
success: false,
|
|
1484
|
+
error: `Model file not found: ${oldFilePath}`
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
if (fs2.existsSync(newFilePath)) {
|
|
1488
|
+
return {
|
|
1489
|
+
success: false,
|
|
1490
|
+
error: `Model file already exists: ${newFilePath}`
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
const content = await fs2.promises.readFile(oldFilePath, "utf-8");
|
|
1494
|
+
const updatedContent = content.replace(
|
|
1495
|
+
/name:\s*['"]([^'"]+)['"]/,
|
|
1496
|
+
`name: '${newName}'`
|
|
1497
|
+
);
|
|
1498
|
+
await fs2.promises.writeFile(newFilePath, updatedContent, "utf-8");
|
|
1499
|
+
await fs2.promises.unlink(oldFilePath);
|
|
1500
|
+
return {
|
|
1501
|
+
success: true,
|
|
1502
|
+
filePath: newFilePath
|
|
1503
|
+
};
|
|
1504
|
+
} catch (error) {
|
|
1505
|
+
return {
|
|
1506
|
+
success: false,
|
|
1507
|
+
error: error.message || "Failed to rename model"
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
async function updateModelReferencesInPrompts(promptsDir, oldModelName, newModelName) {
|
|
1512
|
+
const updatedFiles = [];
|
|
1513
|
+
if (!fs2.existsSync(promptsDir)) {
|
|
1514
|
+
return updatedFiles;
|
|
1515
|
+
}
|
|
1516
|
+
const files = fs2.readdirSync(promptsDir).filter((f) => f.endsWith(".ts"));
|
|
1517
|
+
for (const file of files) {
|
|
1518
|
+
const filePath = path3.join(promptsDir, file);
|
|
1519
|
+
let content = await fs2.promises.readFile(filePath, "utf-8");
|
|
1520
|
+
const modelRegex = new RegExp(`model:\\s*['"]${escapeRegExp(oldModelName)}['"]`, "g");
|
|
1521
|
+
if (modelRegex.test(content)) {
|
|
1522
|
+
content = content.replace(modelRegex, `model: '${newModelName}'`);
|
|
1523
|
+
await fs2.promises.writeFile(filePath, content, "utf-8");
|
|
1524
|
+
updatedFiles.push(filePath);
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
return updatedFiles;
|
|
1528
|
+
}
|
|
1529
|
+
async function updatePromptReferencesInPrompts(promptsDir, oldPromptName, newPromptName) {
|
|
1530
|
+
const updatedFiles = [];
|
|
1531
|
+
if (!fs2.existsSync(promptsDir)) {
|
|
1532
|
+
return updatedFiles;
|
|
1533
|
+
}
|
|
1534
|
+
const files = fs2.readdirSync(promptsDir).filter((f) => f.endsWith(".ts"));
|
|
1535
|
+
for (const file of files) {
|
|
1536
|
+
const filePath = path3.join(promptsDir, file);
|
|
1537
|
+
let content = await fs2.promises.readFile(filePath, "utf-8");
|
|
1538
|
+
let modified = false;
|
|
1539
|
+
const toolsArrayRegex = /tools:\s*\[([^\]]*)\]/gs;
|
|
1540
|
+
const newContent = content.replace(toolsArrayRegex, (match) => {
|
|
1541
|
+
const oldMatch = match;
|
|
1542
|
+
const promptRefRegex = new RegExp(`['"]${escapeRegExp(oldPromptName)}['"]`, "g");
|
|
1543
|
+
const replaced = match.replace(promptRefRegex, `'${newPromptName}'`);
|
|
1544
|
+
if (replaced !== oldMatch) {
|
|
1545
|
+
modified = true;
|
|
1546
|
+
}
|
|
1547
|
+
return replaced;
|
|
1548
|
+
});
|
|
1549
|
+
if (modified) {
|
|
1550
|
+
await fs2.promises.writeFile(filePath, newContent, "utf-8");
|
|
1551
|
+
updatedFiles.push(filePath);
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
return updatedFiles;
|
|
1555
|
+
}
|
|
1556
|
+
async function updatePromptReferencesInAgents(agentsDir, oldPromptName, newPromptName) {
|
|
1557
|
+
const updatedFiles = [];
|
|
1558
|
+
if (!fs2.existsSync(agentsDir)) {
|
|
1559
|
+
return updatedFiles;
|
|
1560
|
+
}
|
|
1561
|
+
const files = fs2.readdirSync(agentsDir).filter((f) => f.endsWith(".ts"));
|
|
1562
|
+
for (const file of files) {
|
|
1563
|
+
const filePath = path3.join(agentsDir, file);
|
|
1564
|
+
let content = await fs2.promises.readFile(filePath, "utf-8");
|
|
1565
|
+
const promptRegex = new RegExp(`prompt:\\s*['"]${escapeRegExp(oldPromptName)}['"]`, "g");
|
|
1566
|
+
if (promptRegex.test(content)) {
|
|
1567
|
+
content = content.replace(promptRegex, `prompt: '${newPromptName}'`);
|
|
1568
|
+
await fs2.promises.writeFile(filePath, content, "utf-8");
|
|
1569
|
+
updatedFiles.push(filePath);
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
return updatedFiles;
|
|
1573
|
+
}
|
|
1574
|
+
function escapeRegExp(string) {
|
|
1575
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1576
|
+
}
|
|
1577
|
+
function validateAgentData(data) {
|
|
1578
|
+
if (!data.name || typeof data.name !== "string") {
|
|
1579
|
+
return "Agent name is required and must be a string";
|
|
1580
|
+
}
|
|
1581
|
+
if (data.title !== void 0 && typeof data.title !== "string") {
|
|
1582
|
+
return "Agent title must be a string if provided";
|
|
1583
|
+
}
|
|
1584
|
+
if (data.type !== void 0) {
|
|
1585
|
+
const validTypes = ["ai_human", "dual_ai"];
|
|
1586
|
+
if (!validTypes.includes(data.type)) {
|
|
1587
|
+
return `Invalid type '${data.type}'. Must be one of: ${validTypes.join(", ")}`;
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
if (!data.sideA || typeof data.sideA !== "object") {
|
|
1591
|
+
return "sideA configuration is required";
|
|
1592
|
+
}
|
|
1593
|
+
if (!data.sideA.prompt || typeof data.sideA.prompt !== "string") {
|
|
1594
|
+
return "sideA.prompt is required and must be a string";
|
|
1595
|
+
}
|
|
1596
|
+
if (data.sideA.label !== void 0 && typeof data.sideA.label !== "string") {
|
|
1597
|
+
return "sideA.label must be a string";
|
|
1598
|
+
}
|
|
1599
|
+
if (data.sideA.stopOnResponse !== void 0 && typeof data.sideA.stopOnResponse !== "boolean") {
|
|
1600
|
+
return "sideA.stopOnResponse must be a boolean";
|
|
1601
|
+
}
|
|
1602
|
+
if (data.sideA.stopTool !== void 0 && typeof data.sideA.stopTool !== "string") {
|
|
1603
|
+
return "sideA.stopTool must be a string";
|
|
1604
|
+
}
|
|
1605
|
+
if (data.sideA.stopTool && !data.sideA.stopToolResponseProperty) {
|
|
1606
|
+
return "sideA.stopToolResponseProperty is required when sideA.stopTool is set";
|
|
1607
|
+
}
|
|
1608
|
+
if (data.sideA.maxTurns !== void 0) {
|
|
1609
|
+
if (typeof data.sideA.maxTurns !== "number" || data.sideA.maxTurns <= 0) {
|
|
1610
|
+
return "sideA.maxTurns must be a positive number";
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
if (data.type === "dual_ai") {
|
|
1614
|
+
if (!data.sideB || typeof data.sideB !== "object") {
|
|
1615
|
+
return "sideB configuration is required for dual_ai type";
|
|
1616
|
+
}
|
|
1617
|
+
if (!data.sideB.prompt || typeof data.sideB.prompt !== "string") {
|
|
1618
|
+
return "sideB.prompt is required for dual_ai type";
|
|
1619
|
+
}
|
|
1620
|
+
if (data.sideB.stopTool && !data.sideB.stopToolResponseProperty) {
|
|
1621
|
+
return "sideB.stopToolResponseProperty is required when sideB.stopTool is set";
|
|
1622
|
+
}
|
|
1623
|
+
if (data.sideB.maxTurns !== void 0) {
|
|
1624
|
+
if (typeof data.sideB.maxTurns !== "number" || data.sideB.maxTurns <= 0) {
|
|
1625
|
+
return "sideB.maxTurns must be a positive number";
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
if (data.exposeAsTool && !data.toolDescription) {
|
|
1630
|
+
return "toolDescription is required when exposeAsTool is true";
|
|
1631
|
+
}
|
|
1632
|
+
if (data.maxSessionTurns !== void 0) {
|
|
1633
|
+
if (typeof data.maxSessionTurns !== "number" || data.maxSessionTurns <= 0) {
|
|
1634
|
+
return "maxSessionTurns must be a positive number";
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
if (data.tags !== void 0) {
|
|
1638
|
+
if (!Array.isArray(data.tags)) {
|
|
1639
|
+
return "tags must be an array";
|
|
1640
|
+
}
|
|
1641
|
+
for (const tag of data.tags) {
|
|
1642
|
+
if (typeof tag !== "string") {
|
|
1643
|
+
return "Each tag must be a string";
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
return null;
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
// src/plugin.ts
|
|
1651
|
+
var VIRTUAL_TOOLS_ID = "virtual:@standardagents-tools";
|
|
1652
|
+
var RESOLVED_VIRTUAL_TOOLS_ID = "\0" + VIRTUAL_TOOLS_ID;
|
|
1653
|
+
var VIRTUAL_ROUTES_ID = "virtual:@standardagents-routes";
|
|
1654
|
+
var RESOLVED_VIRTUAL_ROUTES_ID = "\0" + VIRTUAL_ROUTES_ID;
|
|
1655
|
+
var VIRTUAL_ROUTER_ID = "virtual:@standardagents/router";
|
|
1656
|
+
var RESOLVED_VIRTUAL_ROUTER_ID = "\0" + VIRTUAL_ROUTER_ID;
|
|
1657
|
+
var VIRTUAL_HOOKS_ID = "virtual:@standardagents-hooks";
|
|
1658
|
+
var RESOLVED_VIRTUAL_HOOKS_ID = "\0" + VIRTUAL_HOOKS_ID;
|
|
1659
|
+
var VIRTUAL_CONFIG_ID = "virtual:@standardagents-config";
|
|
1660
|
+
var RESOLVED_VIRTUAL_CONFIG_ID = "\0" + VIRTUAL_CONFIG_ID;
|
|
1661
|
+
var VIRTUAL_THREAD_API_ID = "virtual:@standardagents-thread-api";
|
|
1662
|
+
var RESOLVED_VIRTUAL_THREAD_API_ID = "\0" + VIRTUAL_THREAD_API_ID;
|
|
1663
|
+
var VIRTUAL_MODELS_ID = "virtual:@standardagents-models";
|
|
1664
|
+
var RESOLVED_VIRTUAL_MODELS_ID = "\0" + VIRTUAL_MODELS_ID;
|
|
1665
|
+
var VIRTUAL_PROMPTS_ID = "virtual:@standardagents-prompts";
|
|
1666
|
+
var RESOLVED_VIRTUAL_PROMPTS_ID = "\0" + VIRTUAL_PROMPTS_ID;
|
|
1667
|
+
var VIRTUAL_AGENTS_ID = "virtual:@standardagents-agents";
|
|
1668
|
+
var RESOLVED_VIRTUAL_AGENTS_ID = "\0" + VIRTUAL_AGENTS_ID;
|
|
1669
|
+
var VIRTUAL_BUILDER_ID = "virtual:@standardagents/builder";
|
|
1670
|
+
var RESOLVED_VIRTUAL_BUILDER_ID = "\0" + VIRTUAL_BUILDER_ID;
|
|
1671
|
+
function scanApiDirectory(dir, baseRoute = "") {
|
|
1672
|
+
const routes = [];
|
|
1673
|
+
if (!fs2.existsSync(dir)) {
|
|
1674
|
+
return routes;
|
|
1675
|
+
}
|
|
1676
|
+
try {
|
|
1677
|
+
const entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
1678
|
+
for (const entry of entries) {
|
|
1679
|
+
const fullPath = path3.join(dir, entry.name);
|
|
1680
|
+
if (entry.isDirectory()) {
|
|
1681
|
+
const convertedDirName = entry.name.replace(/\[([^\]]+)\]/g, ":$1");
|
|
1682
|
+
const subRoute = baseRoute + "/" + convertedDirName;
|
|
1683
|
+
routes.push(...scanApiDirectory(fullPath, subRoute));
|
|
1684
|
+
} else if (entry.isFile() && entry.name.endsWith(".ts")) {
|
|
1685
|
+
const fileName = entry.name.replace(/\.ts$/, "");
|
|
1686
|
+
let method = "GET";
|
|
1687
|
+
let routePath = baseRoute;
|
|
1688
|
+
if (fileName.includes(".")) {
|
|
1689
|
+
const parts = fileName.split(".");
|
|
1690
|
+
const methodPart = parts[parts.length - 1].toUpperCase();
|
|
1691
|
+
if (["GET", "POST", "PUT", "DELETE", "PATCH"].includes(methodPart)) {
|
|
1692
|
+
method = methodPart;
|
|
1693
|
+
const pathPart = parts.slice(0, -1).join(".");
|
|
1694
|
+
if (pathPart !== "index") {
|
|
1695
|
+
const convertedPath = pathPart.replace(/\[([^\]]+)\]/g, ":$1");
|
|
1696
|
+
routePath += "/" + convertedPath;
|
|
1697
|
+
}
|
|
1698
|
+
} else {
|
|
1699
|
+
if (fileName !== "index") {
|
|
1700
|
+
const convertedPath = fileName.replace(/\[([^\]]+)\]/g, ":$1");
|
|
1701
|
+
routePath += "/" + convertedPath;
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
} else {
|
|
1705
|
+
if (fileName !== "index") {
|
|
1706
|
+
const convertedPath = fileName.replace(/\[([^\]]+)\]/g, ":$1");
|
|
1707
|
+
routePath += "/" + convertedPath;
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
const importPath = "./" + path3.relative(process.cwd(), fullPath).replace(/\\/g, "/");
|
|
1711
|
+
routes.push({
|
|
1712
|
+
method,
|
|
1713
|
+
route: routePath || "/",
|
|
1714
|
+
importPath
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
return routes;
|
|
1719
|
+
} catch (error) {
|
|
1720
|
+
console.error(`Error scanning API directory ${dir}:`, error);
|
|
1721
|
+
return [];
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
function isSnakeCase(str) {
|
|
1725
|
+
return /^[a-z][a-z0-9_]*[a-z0-9]$/.test(str) || /^[a-z]$/.test(str);
|
|
1726
|
+
}
|
|
1727
|
+
function validateToolFile(filePath, fileName) {
|
|
1728
|
+
try {
|
|
1729
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
1730
|
+
const hasDefaultExport = /export\s+default\s+defineTool/.test(content);
|
|
1731
|
+
if (!hasDefaultExport) {
|
|
1732
|
+
return `Tool file '${fileName}.ts' must have a default export using defineTool()`;
|
|
1733
|
+
}
|
|
1734
|
+
return null;
|
|
1735
|
+
} catch (error) {
|
|
1736
|
+
return `Failed to read tool file '${fileName}.ts'`;
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
async function scanToolsDirectory(dir) {
|
|
1740
|
+
const tools = [];
|
|
1741
|
+
if (!fs2.existsSync(dir)) {
|
|
1742
|
+
return tools;
|
|
1743
|
+
}
|
|
1744
|
+
const entries = await fs2.promises.readdir(dir, { withFileTypes: true });
|
|
1745
|
+
for (const entry of entries) {
|
|
1746
|
+
if (entry.isFile() && entry.name.endsWith(".ts")) {
|
|
1747
|
+
const fileName = entry.name.replace(".ts", "");
|
|
1748
|
+
const filePath = path3.join(dir, entry.name);
|
|
1749
|
+
const importPath = "./" + path3.relative(process.cwd(), filePath).replace(/\\/g, "/");
|
|
1750
|
+
let toolError;
|
|
1751
|
+
const validationError = validateToolFile(filePath, fileName);
|
|
1752
|
+
if (validationError) {
|
|
1753
|
+
toolError = validationError;
|
|
1754
|
+
console.error(`
|
|
1755
|
+
\u274C Tool validation error: ${validationError}`);
|
|
1756
|
+
}
|
|
1757
|
+
if (!isSnakeCase(fileName)) {
|
|
1758
|
+
const warning = `Tool name should be in snake_case format (e.g., 'log_name', 'send_email')`;
|
|
1759
|
+
if (toolError) {
|
|
1760
|
+
toolError += ` | ${warning}`;
|
|
1761
|
+
} else {
|
|
1762
|
+
toolError = warning;
|
|
1763
|
+
}
|
|
1764
|
+
console.warn(
|
|
1765
|
+
`
|
|
1766
|
+
\u26A0\uFE0F Tool naming warning: '${fileName}' should be in snake_case format (e.g., 'log_name', 'send_email')`
|
|
1767
|
+
);
|
|
1768
|
+
}
|
|
1769
|
+
tools.push({ name: fileName, importPath, error: toolError });
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
return tools;
|
|
1773
|
+
}
|
|
1774
|
+
async function scanHooksDirectory(dir) {
|
|
1775
|
+
const hooks = [];
|
|
1776
|
+
if (!fs2.existsSync(dir)) {
|
|
1777
|
+
return hooks;
|
|
1778
|
+
}
|
|
1779
|
+
const entries = await fs2.promises.readdir(dir, { withFileTypes: true });
|
|
1780
|
+
for (const entry of entries) {
|
|
1781
|
+
if (entry.isFile() && entry.name.endsWith(".ts")) {
|
|
1782
|
+
const fileName = entry.name.replace(".ts", "");
|
|
1783
|
+
const filePath = path3.join(dir, entry.name);
|
|
1784
|
+
const importPath = "./" + path3.relative(process.cwd(), filePath).replace(/\\/g, "/");
|
|
1785
|
+
hooks.push({ name: fileName, importPath });
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
return hooks;
|
|
1789
|
+
}
|
|
1790
|
+
async function scanConfigDirectory(dir, definePattern) {
|
|
1791
|
+
const items = [];
|
|
1792
|
+
if (!fs2.existsSync(dir)) {
|
|
1793
|
+
return items;
|
|
1794
|
+
}
|
|
1795
|
+
const entries = await fs2.promises.readdir(dir, { withFileTypes: true });
|
|
1796
|
+
for (const entry of entries) {
|
|
1797
|
+
if (entry.isFile() && entry.name.endsWith(".ts")) {
|
|
1798
|
+
const filePath = path3.join(dir, entry.name);
|
|
1799
|
+
const importPath = "./" + path3.relative(process.cwd(), filePath).replace(/\\/g, "/");
|
|
1800
|
+
try {
|
|
1801
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
1802
|
+
const hasDefaultExport = definePattern.test(content);
|
|
1803
|
+
if (!hasDefaultExport) {
|
|
1804
|
+
items.push({
|
|
1805
|
+
name: entry.name.replace(".ts", ""),
|
|
1806
|
+
importPath,
|
|
1807
|
+
error: `File must have a default export using the define function`
|
|
1808
|
+
});
|
|
1809
|
+
continue;
|
|
1810
|
+
}
|
|
1811
|
+
const nameMatch = content.match(/name:\s*['"]([^'"]+)['"]/);
|
|
1812
|
+
if (nameMatch) {
|
|
1813
|
+
items.push({ name: nameMatch[1], importPath });
|
|
1814
|
+
} else {
|
|
1815
|
+
items.push({
|
|
1816
|
+
name: entry.name.replace(".ts", ""),
|
|
1817
|
+
importPath,
|
|
1818
|
+
error: `Could not extract name from definition`
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
1821
|
+
} catch (error) {
|
|
1822
|
+
items.push({
|
|
1823
|
+
name: entry.name.replace(".ts", ""),
|
|
1824
|
+
importPath,
|
|
1825
|
+
error: `Failed to read file: ${error}`
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
return items;
|
|
1831
|
+
}
|
|
1832
|
+
async function scanModelsDirectory(dir) {
|
|
1833
|
+
return scanConfigDirectory(dir, /export\s+default\s+defineModel/);
|
|
1834
|
+
}
|
|
1835
|
+
async function scanPromptsDirectory(dir) {
|
|
1836
|
+
return scanConfigDirectory(dir, /export\s+default\s+definePrompt/);
|
|
1837
|
+
}
|
|
1838
|
+
async function scanAgentsDirectory(dir) {
|
|
1839
|
+
return scanConfigDirectory(dir, /export\s+default\s+defineAgent/);
|
|
1840
|
+
}
|
|
1841
|
+
function parseRequestBody(req) {
|
|
1842
|
+
return new Promise((resolve, reject) => {
|
|
1843
|
+
let body = "";
|
|
1844
|
+
req.on("data", (chunk) => {
|
|
1845
|
+
body += chunk.toString();
|
|
1846
|
+
});
|
|
1847
|
+
req.on("end", () => {
|
|
1848
|
+
try {
|
|
1849
|
+
resolve(body ? JSON.parse(body) : {});
|
|
1850
|
+
} catch (e) {
|
|
1851
|
+
reject(new Error("Invalid JSON body"));
|
|
1852
|
+
}
|
|
1853
|
+
});
|
|
1854
|
+
req.on("error", reject);
|
|
1855
|
+
});
|
|
1856
|
+
}
|
|
1857
|
+
function agentbuilder(options = {}) {
|
|
1858
|
+
let mountPoint = options.mountPoint || "/agents";
|
|
1859
|
+
if (!mountPoint.startsWith("/")) {
|
|
1860
|
+
mountPoint = "/" + mountPoint;
|
|
1861
|
+
}
|
|
1862
|
+
if (mountPoint.endsWith("/") && mountPoint.length > 1) {
|
|
1863
|
+
mountPoint = mountPoint.slice(0, -1);
|
|
1864
|
+
}
|
|
1865
|
+
const toolsDir = options.toolsDir ? path3.resolve(process.cwd(), options.toolsDir) : path3.resolve(process.cwd(), "agents/tools");
|
|
1866
|
+
const hooksDir = options.hooksDir ? path3.resolve(process.cwd(), options.hooksDir) : path3.resolve(process.cwd(), "agents/hooks");
|
|
1867
|
+
const threadApiDir = options.apiDir ? path3.resolve(process.cwd(), options.apiDir) : path3.resolve(process.cwd(), "agents/api");
|
|
1868
|
+
const modelsDir = options.modelsDir ? path3.resolve(process.cwd(), options.modelsDir) : path3.resolve(process.cwd(), "agents/models");
|
|
1869
|
+
const promptsDir = options.promptsDir ? path3.resolve(process.cwd(), options.promptsDir) : path3.resolve(process.cwd(), "agents/prompts");
|
|
1870
|
+
const agentsDir = options.agentsDir ? path3.resolve(process.cwd(), options.agentsDir) : path3.resolve(process.cwd(), "agents/agents");
|
|
1871
|
+
const outputDir = path3.resolve(process.cwd(), ".agents");
|
|
1872
|
+
const typeGenConfig = {
|
|
1873
|
+
modelsDir,
|
|
1874
|
+
promptsDir,
|
|
1875
|
+
agentsDir,
|
|
1876
|
+
toolsDir,
|
|
1877
|
+
outputDir
|
|
1878
|
+
};
|
|
1879
|
+
function regenerateTypes() {
|
|
1880
|
+
if (needsRegeneration(typeGenConfig)) {
|
|
1881
|
+
console.log("[vite-plugin-agent] Regenerating types...");
|
|
1882
|
+
generateTypes(typeGenConfig);
|
|
1883
|
+
console.log("[vite-plugin-agent] Types regenerated at .agents/types.d.ts");
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
1887
|
+
const __dirname = path3.dirname(__filename);
|
|
1888
|
+
const rou3Path = path3.join(__dirname, "../dist/rou3.js");
|
|
1889
|
+
let rou3Code = "";
|
|
1890
|
+
try {
|
|
1891
|
+
rou3Code = fs2.readFileSync(rou3Path, "utf-8").replace(/^export \{[^}]+\};?\s*$/gm, "").replace(/\/\/# sourceMappingURL=.+$/gm, "").trim();
|
|
1892
|
+
} catch (err) {
|
|
1893
|
+
console.warn("[vite-plugin-agent] Could not read rou3.js for inlining:", err);
|
|
1894
|
+
}
|
|
1895
|
+
let routesVersion = Math.random().toString(36).substring(7);
|
|
1896
|
+
function getWorkerEnvironment(server) {
|
|
1897
|
+
const envNames = Object.keys(server.environments);
|
|
1898
|
+
const workerEnvName = envNames.find((name) => name !== "client");
|
|
1899
|
+
if (!workerEnvName) {
|
|
1900
|
+
console.warn("[vite-plugin-agent] Could not find worker environment");
|
|
1901
|
+
return null;
|
|
1902
|
+
}
|
|
1903
|
+
return server.environments[workerEnvName];
|
|
1904
|
+
}
|
|
1905
|
+
function reloadToolsModule(server) {
|
|
1906
|
+
const workerEnv = getWorkerEnvironment(server);
|
|
1907
|
+
if (!workerEnv) return false;
|
|
1908
|
+
const toolsModule = workerEnv.moduleGraph.getModuleById(
|
|
1909
|
+
RESOLVED_VIRTUAL_TOOLS_ID
|
|
1910
|
+
);
|
|
1911
|
+
if (toolsModule) {
|
|
1912
|
+
workerEnv.reloadModule(toolsModule);
|
|
1913
|
+
server.ws.send({
|
|
1914
|
+
type: "custom",
|
|
1915
|
+
event: "agent:tools-updated",
|
|
1916
|
+
data: { timestamp: Date.now() }
|
|
1917
|
+
});
|
|
1918
|
+
return true;
|
|
1919
|
+
}
|
|
1920
|
+
return false;
|
|
1921
|
+
}
|
|
1922
|
+
function reloadRoutesModule(server) {
|
|
1923
|
+
const workerEnv = getWorkerEnvironment(server);
|
|
1924
|
+
if (!workerEnv) return false;
|
|
1925
|
+
const routesModule = workerEnv.moduleGraph.getModuleById(
|
|
1926
|
+
RESOLVED_VIRTUAL_ROUTES_ID
|
|
1927
|
+
);
|
|
1928
|
+
if (routesModule) {
|
|
1929
|
+
routesVersion = Math.random().toString(36).substring(7);
|
|
1930
|
+
workerEnv.reloadModule(routesModule);
|
|
1931
|
+
server.ws.send({
|
|
1932
|
+
type: "custom",
|
|
1933
|
+
event: "agent:routes-updated",
|
|
1934
|
+
data: { timestamp: Date.now() }
|
|
1935
|
+
});
|
|
1936
|
+
return true;
|
|
1937
|
+
}
|
|
1938
|
+
return false;
|
|
1939
|
+
}
|
|
1940
|
+
function reloadHooksModule(server) {
|
|
1941
|
+
const workerEnv = getWorkerEnvironment(server);
|
|
1942
|
+
if (!workerEnv) return false;
|
|
1943
|
+
const hooksModule = workerEnv.moduleGraph.getModuleById(
|
|
1944
|
+
RESOLVED_VIRTUAL_HOOKS_ID
|
|
1945
|
+
);
|
|
1946
|
+
if (hooksModule) {
|
|
1947
|
+
workerEnv.reloadModule(hooksModule);
|
|
1948
|
+
server.ws.send({
|
|
1949
|
+
type: "custom",
|
|
1950
|
+
event: "agent:hooks-updated",
|
|
1951
|
+
data: { timestamp: Date.now() }
|
|
1952
|
+
});
|
|
1953
|
+
return true;
|
|
1954
|
+
}
|
|
1955
|
+
return false;
|
|
1956
|
+
}
|
|
1957
|
+
async function reloadModelsModule(server) {
|
|
1958
|
+
const workerEnv = getWorkerEnvironment(server);
|
|
1959
|
+
if (!workerEnv) return false;
|
|
1960
|
+
const modelsModule = workerEnv.moduleGraph.getModuleById(
|
|
1961
|
+
RESOLVED_VIRTUAL_MODELS_ID
|
|
1962
|
+
);
|
|
1963
|
+
if (modelsModule) {
|
|
1964
|
+
await workerEnv.reloadModule(modelsModule);
|
|
1965
|
+
server.ws.send({
|
|
1966
|
+
type: "custom",
|
|
1967
|
+
event: "agent:models-updated",
|
|
1968
|
+
data: { timestamp: Date.now() }
|
|
1969
|
+
});
|
|
1970
|
+
return true;
|
|
1971
|
+
}
|
|
1972
|
+
return false;
|
|
1973
|
+
}
|
|
1974
|
+
async function reloadPromptsModule(server) {
|
|
1975
|
+
const workerEnv = getWorkerEnvironment(server);
|
|
1976
|
+
if (!workerEnv) return false;
|
|
1977
|
+
const promptsModule = workerEnv.moduleGraph.getModuleById(
|
|
1978
|
+
RESOLVED_VIRTUAL_PROMPTS_ID
|
|
1979
|
+
);
|
|
1980
|
+
if (promptsModule) {
|
|
1981
|
+
await workerEnv.reloadModule(promptsModule);
|
|
1982
|
+
server.ws.send({
|
|
1983
|
+
type: "custom",
|
|
1984
|
+
event: "agent:prompts-updated",
|
|
1985
|
+
data: { timestamp: Date.now() }
|
|
1986
|
+
});
|
|
1987
|
+
return true;
|
|
1988
|
+
}
|
|
1989
|
+
return false;
|
|
1990
|
+
}
|
|
1991
|
+
async function reloadAgentsModule(server) {
|
|
1992
|
+
const workerEnv = getWorkerEnvironment(server);
|
|
1993
|
+
if (!workerEnv) return false;
|
|
1994
|
+
const agentsModule = workerEnv.moduleGraph.getModuleById(
|
|
1995
|
+
RESOLVED_VIRTUAL_AGENTS_ID
|
|
1996
|
+
);
|
|
1997
|
+
if (agentsModule) {
|
|
1998
|
+
await workerEnv.reloadModule(agentsModule);
|
|
1999
|
+
server.ws.send({
|
|
2000
|
+
type: "custom",
|
|
2001
|
+
event: "agent:agents-updated",
|
|
2002
|
+
data: { timestamp: Date.now() }
|
|
2003
|
+
});
|
|
2004
|
+
return true;
|
|
2005
|
+
}
|
|
2006
|
+
return false;
|
|
2007
|
+
}
|
|
2008
|
+
async function reloadRouterModule(server) {
|
|
2009
|
+
const workerEnv = getWorkerEnvironment(server);
|
|
2010
|
+
if (!workerEnv) return false;
|
|
2011
|
+
const routerModule = workerEnv.moduleGraph.getModuleById(
|
|
2012
|
+
RESOLVED_VIRTUAL_ROUTER_ID
|
|
2013
|
+
);
|
|
2014
|
+
if (routerModule) {
|
|
2015
|
+
await workerEnv.reloadModule(routerModule);
|
|
2016
|
+
return true;
|
|
2017
|
+
}
|
|
2018
|
+
return false;
|
|
2019
|
+
}
|
|
2020
|
+
function handleFileChange(file, server, action) {
|
|
2021
|
+
const isToolFile = file.startsWith(toolsDir) && file.endsWith(".ts");
|
|
2022
|
+
const isThreadApiFile = file.startsWith(threadApiDir) && file.endsWith(".ts");
|
|
2023
|
+
const isHookFile = file.startsWith(hooksDir) && file.endsWith(".ts");
|
|
2024
|
+
const isModelFile = file.startsWith(modelsDir) && file.endsWith(".ts");
|
|
2025
|
+
const isPromptFile = file.startsWith(promptsDir) && file.endsWith(".ts");
|
|
2026
|
+
const isAgentFile = file.startsWith(agentsDir) && file.endsWith(".ts");
|
|
2027
|
+
const isConfigFile = isModelFile || isPromptFile || isAgentFile;
|
|
2028
|
+
if (!isToolFile && !isThreadApiFile && !isHookFile && !isConfigFile) return;
|
|
2029
|
+
const fileType = isToolFile ? "tool" : isThreadApiFile ? "thread-api" : isHookFile ? "hook" : isModelFile ? "model" : isPromptFile ? "prompt" : "agent";
|
|
2030
|
+
console.log(
|
|
2031
|
+
`[vite-plugin-agent] ${action} ${fileType}: ${path3.relative(
|
|
2032
|
+
process.cwd(),
|
|
2033
|
+
file
|
|
2034
|
+
)}`
|
|
2035
|
+
);
|
|
2036
|
+
if (isConfigFile || isToolFile) {
|
|
2037
|
+
regenerateTypes();
|
|
2038
|
+
}
|
|
2039
|
+
if (isToolFile) {
|
|
2040
|
+
reloadToolsModule(server);
|
|
2041
|
+
}
|
|
2042
|
+
if (isModelFile) {
|
|
2043
|
+
reloadModelsModule(server);
|
|
2044
|
+
}
|
|
2045
|
+
if (isPromptFile) {
|
|
2046
|
+
reloadPromptsModule(server);
|
|
2047
|
+
}
|
|
2048
|
+
if (isAgentFile) {
|
|
2049
|
+
reloadAgentsModule(server);
|
|
2050
|
+
}
|
|
2051
|
+
if (isThreadApiFile) {
|
|
2052
|
+
reloadRoutesModule(server);
|
|
2053
|
+
}
|
|
2054
|
+
if (isHookFile) {
|
|
2055
|
+
reloadHooksModule(server);
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
return {
|
|
2059
|
+
name: "vite-plugin-agent",
|
|
2060
|
+
config() {
|
|
2061
|
+
return {
|
|
2062
|
+
optimizeDeps: {
|
|
2063
|
+
// Exclude @standardagents/builder/mcp from pre-bundling (used for MCP server, not needed in worker)
|
|
2064
|
+
exclude: [
|
|
2065
|
+
"@standardagents/builder/mcp"
|
|
2066
|
+
]
|
|
2067
|
+
},
|
|
2068
|
+
ssr: {
|
|
2069
|
+
// Mark as external for SSR/worker builds to prevent bundling
|
|
2070
|
+
// Note: @standardagents/builder, @standardagents/builder/runtime, built-in-routes, and rou3
|
|
2071
|
+
// are NOT external - they must be bundled into the worker for Cloudflare Workers runtime
|
|
2072
|
+
external: [
|
|
2073
|
+
"@standardagents/builder/mcp"
|
|
2074
|
+
]
|
|
2075
|
+
},
|
|
2076
|
+
build: {
|
|
2077
|
+
rollupOptions: {
|
|
2078
|
+
// Also mark as external for Rollup/build (used by Cloudflare Workers plugin)
|
|
2079
|
+
// Note: @standardagents/builder, @standardagents/builder/runtime, built-in-routes, and rou3
|
|
2080
|
+
// are NOT external - they must be bundled into the worker for Cloudflare Workers runtime
|
|
2081
|
+
external: [
|
|
2082
|
+
"@standardagents/builder/mcp"
|
|
2083
|
+
]
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
};
|
|
2087
|
+
},
|
|
2088
|
+
resolveId(id) {
|
|
2089
|
+
if (id.includes("virtual:") || id.includes("agent")) {
|
|
2090
|
+
console.log("[plugin.resolveId]", id);
|
|
2091
|
+
}
|
|
2092
|
+
if (id === VIRTUAL_TOOLS_ID) {
|
|
2093
|
+
console.log("[plugin.resolveId] \u2713 Resolving tools");
|
|
2094
|
+
return RESOLVED_VIRTUAL_TOOLS_ID;
|
|
2095
|
+
}
|
|
2096
|
+
if (id === VIRTUAL_ROUTES_ID || id === VIRTUAL_ROUTER_ID) {
|
|
2097
|
+
console.log("[plugin.resolveId] \u2713 Resolving routes");
|
|
2098
|
+
return RESOLVED_VIRTUAL_ROUTES_ID;
|
|
2099
|
+
}
|
|
2100
|
+
if (id === VIRTUAL_HOOKS_ID) {
|
|
2101
|
+
console.log("[plugin.resolveId] \u2713 Resolving hooks");
|
|
2102
|
+
return RESOLVED_VIRTUAL_HOOKS_ID;
|
|
2103
|
+
}
|
|
2104
|
+
if (id === VIRTUAL_CONFIG_ID) {
|
|
2105
|
+
console.log("[plugin.resolveId] \u2713 Resolving config");
|
|
2106
|
+
return RESOLVED_VIRTUAL_CONFIG_ID;
|
|
2107
|
+
}
|
|
2108
|
+
if (id === VIRTUAL_THREAD_API_ID) {
|
|
2109
|
+
console.log("[plugin.resolveId] \u2713 Resolving thread API");
|
|
2110
|
+
return RESOLVED_VIRTUAL_THREAD_API_ID;
|
|
2111
|
+
}
|
|
2112
|
+
if (id === VIRTUAL_MODELS_ID) {
|
|
2113
|
+
console.log("[plugin.resolveId] \u2713 Resolving models");
|
|
2114
|
+
return RESOLVED_VIRTUAL_MODELS_ID;
|
|
2115
|
+
}
|
|
2116
|
+
if (id === VIRTUAL_PROMPTS_ID) {
|
|
2117
|
+
console.log("[plugin.resolveId] \u2713 Resolving prompts");
|
|
2118
|
+
return RESOLVED_VIRTUAL_PROMPTS_ID;
|
|
2119
|
+
}
|
|
2120
|
+
if (id === VIRTUAL_AGENTS_ID) {
|
|
2121
|
+
console.log("[plugin.resolveId] \u2713 Resolving agents");
|
|
2122
|
+
return RESOLVED_VIRTUAL_AGENTS_ID;
|
|
2123
|
+
}
|
|
2124
|
+
if (id === VIRTUAL_BUILDER_ID) {
|
|
2125
|
+
console.log("[plugin.resolveId] \u2713 Resolving consolidated builder module");
|
|
2126
|
+
return RESOLVED_VIRTUAL_BUILDER_ID;
|
|
2127
|
+
}
|
|
2128
|
+
},
|
|
2129
|
+
async load(id) {
|
|
2130
|
+
if (id.includes("\0virtual:") || id.includes("agent")) {
|
|
2131
|
+
console.log("[plugin.load]", id);
|
|
2132
|
+
}
|
|
2133
|
+
if (id === RESOLVED_VIRTUAL_TOOLS_ID) {
|
|
2134
|
+
const tools = await scanToolsDirectory(toolsDir);
|
|
2135
|
+
const toolsCode = tools.map(({ name, importPath, error }) => {
|
|
2136
|
+
if (error) {
|
|
2137
|
+
const escapedError = error.replace(/"/g, '\\"').replace(/\n/g, "\\n");
|
|
2138
|
+
return ` "${name}": async () => ["${escapedError}", null, async () => ({ status: "error", error: "Tool validation failed" })],`;
|
|
2139
|
+
} else {
|
|
2140
|
+
return ` "${name}": async () => {
|
|
2141
|
+
try {
|
|
2142
|
+
return (await import("${importPath}")).default;
|
|
2143
|
+
} catch (error) {
|
|
2144
|
+
console.error('Failed to import tool ${name}:', error);
|
|
2145
|
+
return ["Failed to load tool: " + error.message, null, async () => ({ status: "error", error: "Import failed" })];
|
|
2146
|
+
}
|
|
2147
|
+
},`;
|
|
2148
|
+
}
|
|
2149
|
+
}).join("\n");
|
|
2150
|
+
const moduleCode = `// Virtual agent tools module
|
|
2151
|
+
export const tools = {
|
|
2152
|
+
${toolsCode}
|
|
2153
|
+
};`;
|
|
2154
|
+
return moduleCode;
|
|
2155
|
+
}
|
|
2156
|
+
if (id === RESOLVED_VIRTUAL_ROUTES_ID) {
|
|
2157
|
+
const threadRoutes = scanApiDirectory(threadApiDir);
|
|
2158
|
+
const threadRouteCode = threadRoutes.map(({ method, route, importPath }) => {
|
|
2159
|
+
const apiRoute = `/api/threads/:id${route}`;
|
|
2160
|
+
return ` addRoute(
|
|
2161
|
+
router,
|
|
2162
|
+
"${method}",
|
|
2163
|
+
"${apiRoute}",
|
|
2164
|
+
/* v${routesVersion} */ async () => (await import("${importPath}")).default
|
|
2165
|
+
);`;
|
|
2166
|
+
}).join("\n");
|
|
2167
|
+
return `// Inline rou3 router code (no external imports)
|
|
2168
|
+
${rou3Code}
|
|
2169
|
+
|
|
2170
|
+
import { registerBuiltInRoutes } from "@standardagents/builder/built-in-routes";
|
|
2171
|
+
import { config } from "virtual:@standardagents-config";
|
|
2172
|
+
import { tools } from "virtual:@standardagents-tools";
|
|
2173
|
+
import { hooks } from "virtual:@standardagents-hooks";
|
|
2174
|
+
import { models, modelNames } from "virtual:@standardagents-models";
|
|
2175
|
+
import { prompts, promptNames } from "virtual:@standardagents-prompts";
|
|
2176
|
+
import { agents, agentNames } from "virtual:@standardagents-agents";
|
|
2177
|
+
import { DurableThread as _DurableThread } from "@standardagents/builder/runtime";
|
|
2178
|
+
import { requireAuth } from "@standardagents/builder/runtime";
|
|
2179
|
+
|
|
2180
|
+
console.log('[Router] Virtual modules loaded - tools:', Object.keys(tools || {}), 'hooks:', Object.keys(hooks || {}), 'models:', modelNames, 'prompts:', promptNames, 'agents:', agentNames);
|
|
2181
|
+
|
|
2182
|
+
const MOUNT_POINT = "${mountPoint}";
|
|
2183
|
+
|
|
2184
|
+
// Routes that don't require authentication
|
|
2185
|
+
const PUBLIC_ROUTES = [
|
|
2186
|
+
'/api/auth/login',
|
|
2187
|
+
'/api/auth/config',
|
|
2188
|
+
'/api/auth/oauth/github',
|
|
2189
|
+
'/api/auth/oauth/google',
|
|
2190
|
+
'/api/auth/oauth/github/callback',
|
|
2191
|
+
'/api/auth/oauth/google/callback'
|
|
2192
|
+
];
|
|
2193
|
+
|
|
2194
|
+
// Check if a route is public (no auth required)
|
|
2195
|
+
function isPublicRoute(routePath) {
|
|
2196
|
+
// Exact match for auth routes
|
|
2197
|
+
if (PUBLIC_ROUTES.includes(routePath)) {
|
|
2198
|
+
return true;
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
// Thread routes are always public
|
|
2202
|
+
if (routePath.startsWith('/api/threads/') || routePath === '/api/threads') {
|
|
2203
|
+
return true;
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
return false;
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
export async function router(request, env) {
|
|
2210
|
+
const url = new URL(request.url);
|
|
2211
|
+
const pathname = url.pathname;
|
|
2212
|
+
|
|
2213
|
+
// Check if request is under mount point
|
|
2214
|
+
if (!pathname.startsWith(MOUNT_POINT)) {
|
|
2215
|
+
return null;
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
// Strip mount point prefix for route matching, ensuring we keep the leading slash
|
|
2219
|
+
let routePath = pathname.slice(MOUNT_POINT.length) || "/";
|
|
2220
|
+
if (!routePath.startsWith('/')) {
|
|
2221
|
+
routePath = '/' + routePath;
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
// Handle API routes
|
|
2225
|
+
const router = createRouter();
|
|
2226
|
+
|
|
2227
|
+
// Register built-in API routes with runtime data
|
|
2228
|
+
registerBuiltInRoutes(router, { config, tools, models, modelNames, prompts, promptNames, agents, agentNames });
|
|
2229
|
+
|
|
2230
|
+
// Register user thread API routes
|
|
2231
|
+
${threadRouteCode}
|
|
2232
|
+
|
|
2233
|
+
const routeMatch = findRoute(
|
|
2234
|
+
router,
|
|
2235
|
+
request.method.toUpperCase(),
|
|
2236
|
+
routePath
|
|
2237
|
+
);
|
|
2238
|
+
|
|
2239
|
+
if (routeMatch) {
|
|
2240
|
+
// Check if authentication is required for this route
|
|
2241
|
+
const publicRoute = isPublicRoute(routePath);
|
|
2242
|
+
const isApiRoute = routePath.startsWith('/api/');
|
|
2243
|
+
|
|
2244
|
+
let authContext = null;
|
|
2245
|
+
|
|
2246
|
+
// Require authentication for all API routes except public ones
|
|
2247
|
+
if (isApiRoute && !publicRoute) {
|
|
2248
|
+
const authResult = await requireAuth(request, env);
|
|
2249
|
+
|
|
2250
|
+
// If requireAuth returns a Response, it's an error (401)
|
|
2251
|
+
if (authResult instanceof Response) {
|
|
2252
|
+
return authResult;
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
authContext = authResult;
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
const controller = await routeMatch.data();
|
|
2259
|
+
const context = {
|
|
2260
|
+
req: request,
|
|
2261
|
+
params: routeMatch.params || {},
|
|
2262
|
+
env: env,
|
|
2263
|
+
url: url,
|
|
2264
|
+
config,
|
|
2265
|
+
tools,
|
|
2266
|
+
auth: authContext, // Add auth context to controller context
|
|
2267
|
+
};
|
|
2268
|
+
const result = await controller(context);
|
|
2269
|
+
|
|
2270
|
+
if (result instanceof Response) {
|
|
2271
|
+
return result;
|
|
2272
|
+
}
|
|
2273
|
+
if (typeof result === "string") {
|
|
2274
|
+
return new Response(result, {
|
|
2275
|
+
headers: {
|
|
2276
|
+
"Content-Type": "text/plain",
|
|
2277
|
+
},
|
|
2278
|
+
});
|
|
2279
|
+
}
|
|
2280
|
+
return Response.json(result);
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
// Serve UI for all other routes (SPA fallback)
|
|
2284
|
+
return serveUI(routePath, env);
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2287
|
+
async function serveUI(pathname, env) {
|
|
2288
|
+
try {
|
|
2289
|
+
// Use Cloudflare Workers Assets binding (automatically created from wrangler assets config)
|
|
2290
|
+
if (env.ASSETS) {
|
|
2291
|
+
try {
|
|
2292
|
+
// Create a proper request for the asset path
|
|
2293
|
+
// Use a dummy origin since we only care about the path
|
|
2294
|
+
// Re-add mount point since pathname was stripped by router
|
|
2295
|
+
const assetUrl = \`http://localhost\${MOUNT_POINT}\${pathname}\`;
|
|
2296
|
+
let response = await env.ASSETS.fetch(assetUrl);
|
|
2297
|
+
|
|
2298
|
+
// If not found, fall back to index.html for SPA routing
|
|
2299
|
+
const isIndexHtml = response.status === 404 || pathname === "/" || !pathname.includes(".");
|
|
2300
|
+
if (isIndexHtml) {
|
|
2301
|
+
response = await env.ASSETS.fetch(\`http://localhost\${MOUNT_POINT}/index.html\`);
|
|
2302
|
+
|
|
2303
|
+
// Transform HTML to use configured mount point
|
|
2304
|
+
if (response.status === 200) {
|
|
2305
|
+
const html = await response.text();
|
|
2306
|
+
// Replace default /agentbuilder/ paths with configured mount point
|
|
2307
|
+
const modifiedHtml = html.replace(/\\/agentbuilder\\//g, \`\${MOUNT_POINT}/\`);
|
|
2308
|
+
return new Response(modifiedHtml, {
|
|
2309
|
+
headers: {
|
|
2310
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
2311
|
+
},
|
|
2312
|
+
});
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
return response;
|
|
2317
|
+
} catch (assetError) {
|
|
2318
|
+
console.error("Error fetching from ASSETS:", assetError);
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
// Fallback: proxy to UI dev server if explicitly configured
|
|
2323
|
+
if (env.UI_DEV_SERVER) {
|
|
2324
|
+
const assetPath = pathname === "/" ? "/index.html" : pathname;
|
|
2325
|
+
const response = await fetch(\`\${env.UI_DEV_SERVER}\${assetPath}\`);
|
|
2326
|
+
return response;
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
// In production/dev, assets should be served by Cloudflare Workers automatically
|
|
2330
|
+
// This function only handles fallback cases
|
|
2331
|
+
return null;
|
|
2332
|
+
} catch (error) {
|
|
2333
|
+
console.error("Error serving UI:", error);
|
|
2334
|
+
return null;
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
// Export DurableThread so users can re-export it from virtual module
|
|
2339
|
+
export { _DurableThread as DurableThread };
|
|
2340
|
+
`;
|
|
2341
|
+
}
|
|
2342
|
+
if (id === RESOLVED_VIRTUAL_HOOKS_ID) {
|
|
2343
|
+
console.log("[plugin.load] \u2713 Loading HOOKS virtual module");
|
|
2344
|
+
console.log("[plugin.load] hooksDir:", hooksDir);
|
|
2345
|
+
const hooks = await scanHooksDirectory(hooksDir);
|
|
2346
|
+
console.log(`[plugin.load] Scanned hooks directory, found ${hooks.length} hooks:`, hooks.map((h) => h.name));
|
|
2347
|
+
const hooksCode = hooks.map(({ name, importPath }) => {
|
|
2348
|
+
return ` "${name}": async () => {
|
|
2349
|
+
try {
|
|
2350
|
+
return (await import("${importPath}")).default;
|
|
2351
|
+
} catch (error) {
|
|
2352
|
+
console.error('[Hooks] Failed to import hook ${name}:', error);
|
|
2353
|
+
return null;
|
|
2354
|
+
}
|
|
2355
|
+
},`;
|
|
2356
|
+
}).join("\n");
|
|
2357
|
+
return `// Virtual agent hooks module
|
|
2358
|
+
console.log('[virtual:agent-hooks] Module loaded with hooks:', Object.keys({${hooks.map((h) => `'${h.name}': null`).join(", ")}}));
|
|
2359
|
+
export const hooks = {
|
|
2360
|
+
${hooksCode}
|
|
2361
|
+
};`;
|
|
2362
|
+
}
|
|
2363
|
+
if (id === RESOLVED_VIRTUAL_CONFIG_ID) {
|
|
2364
|
+
const relativeToolsDir = path3.relative(process.cwd(), toolsDir).replace(/\\/g, "/");
|
|
2365
|
+
const relativeHooksDir = path3.relative(process.cwd(), hooksDir).replace(/\\/g, "/");
|
|
2366
|
+
const relativeApiDir = path3.relative(process.cwd(), threadApiDir).replace(/\\/g, "/");
|
|
2367
|
+
const relativeModelsDir = path3.relative(process.cwd(), modelsDir).replace(/\\/g, "/");
|
|
2368
|
+
const relativePromptsDir = path3.relative(process.cwd(), promptsDir).replace(/\\/g, "/");
|
|
2369
|
+
const relativeAgentsDir = path3.relative(process.cwd(), agentsDir).replace(/\\/g, "/");
|
|
2370
|
+
return `// Virtual agent config module
|
|
2371
|
+
export const config = {
|
|
2372
|
+
toolsDir: "${relativeToolsDir}",
|
|
2373
|
+
hooksDir: "${relativeHooksDir}",
|
|
2374
|
+
apiDir: "${relativeApiDir}",
|
|
2375
|
+
modelsDir: "${relativeModelsDir}",
|
|
2376
|
+
promptsDir: "${relativePromptsDir}",
|
|
2377
|
+
agentsDir: "${relativeAgentsDir}",
|
|
2378
|
+
mountPoint: "${mountPoint}",
|
|
2379
|
+
};`;
|
|
2380
|
+
}
|
|
2381
|
+
if (id === RESOLVED_VIRTUAL_MODELS_ID) {
|
|
2382
|
+
const models = await scanModelsDirectory(modelsDir);
|
|
2383
|
+
const modelsCode = models.map(({ name, importPath, error }) => {
|
|
2384
|
+
if (error) {
|
|
2385
|
+
const escapedError = error.replace(/"/g, '\\"').replace(/\n/g, "\\n");
|
|
2386
|
+
return ` "${name}": async () => { throw new Error("${escapedError}"); },`;
|
|
2387
|
+
} else {
|
|
2388
|
+
return ` "${name}": async () => {
|
|
2389
|
+
try {
|
|
2390
|
+
return (await import("${importPath}")).default;
|
|
2391
|
+
} catch (error) {
|
|
2392
|
+
console.error('Failed to import model ${name}:', error);
|
|
2393
|
+
throw error;
|
|
2394
|
+
}
|
|
2395
|
+
},`;
|
|
2396
|
+
}
|
|
2397
|
+
}).join("\n");
|
|
2398
|
+
return `// Virtual agent models module
|
|
2399
|
+
export const models = {
|
|
2400
|
+
${modelsCode}
|
|
2401
|
+
};
|
|
2402
|
+
|
|
2403
|
+
export const modelNames = ${JSON.stringify(models.filter((m) => !m.error).map((m) => m.name))};
|
|
2404
|
+
`;
|
|
2405
|
+
}
|
|
2406
|
+
if (id === RESOLVED_VIRTUAL_PROMPTS_ID) {
|
|
2407
|
+
const prompts = await scanPromptsDirectory(promptsDir);
|
|
2408
|
+
const promptsCode = prompts.map(({ name, importPath, error }) => {
|
|
2409
|
+
if (error) {
|
|
2410
|
+
const escapedError = error.replace(/"/g, '\\"').replace(/\n/g, "\\n");
|
|
2411
|
+
return ` "${name}": async () => { throw new Error("${escapedError}"); },`;
|
|
2412
|
+
} else {
|
|
2413
|
+
return ` "${name}": async () => {
|
|
2414
|
+
try {
|
|
2415
|
+
return (await import("${importPath}")).default;
|
|
2416
|
+
} catch (error) {
|
|
2417
|
+
console.error('Failed to import prompt ${name}:', error);
|
|
2418
|
+
throw error;
|
|
2419
|
+
}
|
|
2420
|
+
},`;
|
|
2421
|
+
}
|
|
2422
|
+
}).join("\n");
|
|
2423
|
+
return `// Virtual agent prompts module
|
|
2424
|
+
export const prompts = {
|
|
2425
|
+
${promptsCode}
|
|
2426
|
+
};
|
|
2427
|
+
|
|
2428
|
+
export const promptNames = ${JSON.stringify(prompts.filter((p) => !p.error).map((p) => p.name))};
|
|
2429
|
+
`;
|
|
2430
|
+
}
|
|
2431
|
+
if (id === RESOLVED_VIRTUAL_AGENTS_ID) {
|
|
2432
|
+
const agents = await scanAgentsDirectory(agentsDir);
|
|
2433
|
+
const agentsCode = agents.map(({ name, importPath, error }) => {
|
|
2434
|
+
if (error) {
|
|
2435
|
+
const escapedError = error.replace(/"/g, '\\"').replace(/\n/g, "\\n");
|
|
2436
|
+
return ` "${name}": async () => { throw new Error("${escapedError}"); },`;
|
|
2437
|
+
} else {
|
|
2438
|
+
return ` "${name}": async () => {
|
|
2439
|
+
try {
|
|
2440
|
+
return (await import("${importPath}")).default;
|
|
2441
|
+
} catch (error) {
|
|
2442
|
+
console.error('Failed to import agent ${name}:', error);
|
|
2443
|
+
throw error;
|
|
2444
|
+
}
|
|
2445
|
+
},`;
|
|
2446
|
+
}
|
|
2447
|
+
}).join("\n");
|
|
2448
|
+
return `// Virtual agent agents module
|
|
2449
|
+
export const agents = {
|
|
2450
|
+
${agentsCode}
|
|
2451
|
+
};
|
|
2452
|
+
|
|
2453
|
+
export const agentNames = ${JSON.stringify(agents.filter((a) => !a.error).map((a) => a.name))};
|
|
2454
|
+
`;
|
|
2455
|
+
}
|
|
2456
|
+
if (id === RESOLVED_VIRTUAL_BUILDER_ID) {
|
|
2457
|
+
const tools = await scanToolsDirectory(toolsDir);
|
|
2458
|
+
const hooks = await scanHooksDirectory(hooksDir);
|
|
2459
|
+
const models = await scanModelsDirectory(modelsDir);
|
|
2460
|
+
const prompts = await scanPromptsDirectory(promptsDir);
|
|
2461
|
+
const agents = await scanAgentsDirectory(agentsDir);
|
|
2462
|
+
const toAbsolutePath = (relativePath) => {
|
|
2463
|
+
if (relativePath.startsWith("./")) {
|
|
2464
|
+
return path3.resolve(process.cwd(), relativePath).replace(/\\/g, "/");
|
|
2465
|
+
}
|
|
2466
|
+
return relativePath;
|
|
2467
|
+
};
|
|
2468
|
+
const toolsCode = tools.map(({ name, importPath, error }) => {
|
|
2469
|
+
const absPath = toAbsolutePath(importPath);
|
|
2470
|
+
if (error) {
|
|
2471
|
+
const escapedError = error.replace(/"/g, '\\"').replace(/\n/g, "\\n");
|
|
2472
|
+
return ` "${name}": async () => ["${escapedError}", null, async () => ({ status: "error", error: "Tool validation failed" })],`;
|
|
2473
|
+
} else {
|
|
2474
|
+
return ` "${name}": async () => {
|
|
2475
|
+
try {
|
|
2476
|
+
return (await import("${absPath}")).default;
|
|
2477
|
+
} catch (error) {
|
|
2478
|
+
console.error('Failed to import tool ${name}:', error);
|
|
2479
|
+
return ["Failed to load tool: " + error.message, null, async () => ({ status: "error", error: "Import failed" })];
|
|
2480
|
+
}
|
|
2481
|
+
},`;
|
|
2482
|
+
}
|
|
2483
|
+
}).join("\n");
|
|
2484
|
+
const hooksCode = hooks.map(({ name, importPath }) => {
|
|
2485
|
+
const absPath = toAbsolutePath(importPath);
|
|
2486
|
+
return ` "${name}": async () => {
|
|
2487
|
+
try {
|
|
2488
|
+
return (await import("${absPath}")).default;
|
|
2489
|
+
} catch (error) {
|
|
2490
|
+
console.error('[Hooks] Failed to import hook ${name}:', error);
|
|
2491
|
+
return null;
|
|
2492
|
+
}
|
|
2493
|
+
},`;
|
|
2494
|
+
}).join("\n");
|
|
2495
|
+
const modelsCode = models.map(({ name, importPath, error }) => {
|
|
2496
|
+
const absPath = toAbsolutePath(importPath);
|
|
2497
|
+
if (error) {
|
|
2498
|
+
const escapedError = error.replace(/"/g, '\\"').replace(/\n/g, "\\n");
|
|
2499
|
+
return ` "${name}": async () => { throw new Error("${escapedError}"); },`;
|
|
2500
|
+
} else {
|
|
2501
|
+
return ` "${name}": async () => {
|
|
2502
|
+
try {
|
|
2503
|
+
return (await import("${absPath}")).default;
|
|
2504
|
+
} catch (error) {
|
|
2505
|
+
console.error('Failed to import model ${name}:', error);
|
|
2506
|
+
throw error;
|
|
2507
|
+
}
|
|
2508
|
+
},`;
|
|
2509
|
+
}
|
|
2510
|
+
}).join("\n");
|
|
2511
|
+
const promptsCode = prompts.map(({ name, importPath, error }) => {
|
|
2512
|
+
const absPath = toAbsolutePath(importPath);
|
|
2513
|
+
if (error) {
|
|
2514
|
+
const escapedError = error.replace(/"/g, '\\"').replace(/\n/g, "\\n");
|
|
2515
|
+
return ` "${name}": async () => { throw new Error("${escapedError}"); },`;
|
|
2516
|
+
} else {
|
|
2517
|
+
return ` "${name}": async () => {
|
|
2518
|
+
try {
|
|
2519
|
+
return (await import("${absPath}")).default;
|
|
2520
|
+
} catch (error) {
|
|
2521
|
+
console.error('Failed to import prompt ${name}:', error);
|
|
2522
|
+
throw error;
|
|
2523
|
+
}
|
|
2524
|
+
},`;
|
|
2525
|
+
}
|
|
2526
|
+
}).join("\n");
|
|
2527
|
+
const agentsCode = agents.map(({ name, importPath, error }) => {
|
|
2528
|
+
const absPath = toAbsolutePath(importPath);
|
|
2529
|
+
if (error) {
|
|
2530
|
+
const escapedError = error.replace(/"/g, '\\"').replace(/\n/g, "\\n");
|
|
2531
|
+
return ` "${name}": async () => { throw new Error("${escapedError}"); },`;
|
|
2532
|
+
} else {
|
|
2533
|
+
return ` "${name}": async () => {
|
|
2534
|
+
try {
|
|
2535
|
+
return (await import("${absPath}")).default;
|
|
2536
|
+
} catch (error) {
|
|
2537
|
+
console.error('Failed to import agent ${name}:', error);
|
|
2538
|
+
throw error;
|
|
2539
|
+
}
|
|
2540
|
+
},`;
|
|
2541
|
+
}
|
|
2542
|
+
}).join("\n");
|
|
2543
|
+
return `// Consolidated virtual module: virtual:@standardagents/builder
|
|
2544
|
+
// Provides DurableThread, DurableAgentBuilder, and router
|
|
2545
|
+
import { DurableThread as _BaseDurableThread } from '@standardagents/builder/runtime';
|
|
2546
|
+
import { DurableAgentBuilder as _BaseDurableAgentBuilder } from '@standardagents/builder/runtime';
|
|
2547
|
+
|
|
2548
|
+
// Re-export router from virtual:@standardagents-routes
|
|
2549
|
+
export { router } from 'virtual:@standardagents-routes';
|
|
2550
|
+
|
|
2551
|
+
// Registry objects
|
|
2552
|
+
const _tools = {
|
|
2553
|
+
${toolsCode}
|
|
2554
|
+
};
|
|
2555
|
+
|
|
2556
|
+
const _hooks = {
|
|
2557
|
+
${hooksCode}
|
|
2558
|
+
};
|
|
2559
|
+
|
|
2560
|
+
const _models = {
|
|
2561
|
+
${modelsCode}
|
|
2562
|
+
};
|
|
2563
|
+
|
|
2564
|
+
const _prompts = {
|
|
2565
|
+
${promptsCode}
|
|
2566
|
+
};
|
|
2567
|
+
|
|
2568
|
+
const _agents = {
|
|
2569
|
+
${agentsCode}
|
|
2570
|
+
};
|
|
2571
|
+
|
|
2572
|
+
/**
|
|
2573
|
+
* DurableThread with all virtual module methods already implemented.
|
|
2574
|
+
* Simply extend this class in your agents/Thread.ts file.
|
|
2575
|
+
*/
|
|
2576
|
+
export class DurableThread extends _BaseDurableThread {
|
|
2577
|
+
tools() {
|
|
2578
|
+
return _tools;
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2581
|
+
hooks() {
|
|
2582
|
+
return _hooks;
|
|
2583
|
+
}
|
|
2584
|
+
|
|
2585
|
+
models() {
|
|
2586
|
+
return _models;
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2589
|
+
prompts() {
|
|
2590
|
+
return _prompts;
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
agents() {
|
|
2594
|
+
return _agents;
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
/**
|
|
2599
|
+
* DurableAgentBuilder with all virtual module methods already implemented.
|
|
2600
|
+
* Simply extend this class in your agents/AgentBuilder.ts file.
|
|
2601
|
+
*/
|
|
2602
|
+
export class DurableAgentBuilder extends _BaseDurableAgentBuilder {
|
|
2603
|
+
tools() {
|
|
2604
|
+
return _tools;
|
|
2605
|
+
}
|
|
2606
|
+
|
|
2607
|
+
hooks() {
|
|
2608
|
+
return _hooks;
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
models() {
|
|
2612
|
+
return _models;
|
|
2613
|
+
}
|
|
2614
|
+
|
|
2615
|
+
prompts() {
|
|
2616
|
+
return _prompts;
|
|
2617
|
+
}
|
|
2618
|
+
|
|
2619
|
+
agents() {
|
|
2620
|
+
return _agents;
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
`;
|
|
2624
|
+
}
|
|
2625
|
+
},
|
|
2626
|
+
buildStart() {
|
|
2627
|
+
regenerateTypes();
|
|
2628
|
+
this.addWatchFile(toolsDir);
|
|
2629
|
+
this.addWatchFile(threadApiDir);
|
|
2630
|
+
this.addWatchFile(hooksDir);
|
|
2631
|
+
this.addWatchFile(modelsDir);
|
|
2632
|
+
this.addWatchFile(promptsDir);
|
|
2633
|
+
this.addWatchFile(agentsDir);
|
|
2634
|
+
},
|
|
2635
|
+
configureServer(server) {
|
|
2636
|
+
server.watcher.on("add", async (file) => {
|
|
2637
|
+
handleFileChange(file, server, "New file detected");
|
|
2638
|
+
});
|
|
2639
|
+
server.watcher.on("unlink", (file) => {
|
|
2640
|
+
handleFileChange(file, server, "File removed");
|
|
2641
|
+
});
|
|
2642
|
+
server.middlewares.use(async (req, res, next) => {
|
|
2643
|
+
const url = req.url;
|
|
2644
|
+
if (!url || !url.startsWith(mountPoint)) {
|
|
2645
|
+
next();
|
|
2646
|
+
return;
|
|
2647
|
+
}
|
|
2648
|
+
let pathWithoutMount = url.slice(mountPoint.length) || "/";
|
|
2649
|
+
if (!pathWithoutMount.startsWith("/")) {
|
|
2650
|
+
pathWithoutMount = "/" + pathWithoutMount;
|
|
2651
|
+
}
|
|
2652
|
+
const method = req.method?.toUpperCase();
|
|
2653
|
+
if (pathWithoutMount === "/api/models" && method === "GET") {
|
|
2654
|
+
try {
|
|
2655
|
+
const fs4 = await import('fs');
|
|
2656
|
+
const files = fs4.existsSync(modelsDir) ? fs4.readdirSync(modelsDir).filter((f) => f.endsWith(".ts")) : [];
|
|
2657
|
+
const modelList = files.map((file) => {
|
|
2658
|
+
try {
|
|
2659
|
+
const filePath = path3.join(modelsDir, file);
|
|
2660
|
+
const content = fs4.readFileSync(filePath, "utf-8");
|
|
2661
|
+
const getName = (c) => c.match(/name:\s*['"]([^'"]+)['"]/)?.[1];
|
|
2662
|
+
const getProvider = (c) => c.match(/provider:\s*['"]([^'"]+)['"]/)?.[1];
|
|
2663
|
+
const getModel = (c) => c.match(/model:\s*['"]([^'"]+)['"]/)?.[1];
|
|
2664
|
+
const getInputPrice = (c) => {
|
|
2665
|
+
const match = c.match(/inputPrice:\s*([\d.]+)/);
|
|
2666
|
+
return match ? parseFloat(match[1]) : void 0;
|
|
2667
|
+
};
|
|
2668
|
+
const getOutputPrice = (c) => {
|
|
2669
|
+
const match = c.match(/outputPrice:\s*([\d.]+)/);
|
|
2670
|
+
return match ? parseFloat(match[1]) : void 0;
|
|
2671
|
+
};
|
|
2672
|
+
const getCachedPrice = (c) => {
|
|
2673
|
+
const match = c.match(/cachedPrice:\s*([\d.]+)/);
|
|
2674
|
+
return match ? parseFloat(match[1]) : void 0;
|
|
2675
|
+
};
|
|
2676
|
+
const getIncludedProviders = (c) => {
|
|
2677
|
+
const match = c.match(/includedProviders:\s*\[([^\]]*)\]/);
|
|
2678
|
+
if (!match) return void 0;
|
|
2679
|
+
const items = match[1].match(/['"]([^'"]+)['"]/g);
|
|
2680
|
+
return items ? items.map((s) => s.replace(/['"]/g, "")) : [];
|
|
2681
|
+
};
|
|
2682
|
+
const getFallbacks = (c) => {
|
|
2683
|
+
const match = c.match(/fallbacks:\s*\[([^\]]*)\]/);
|
|
2684
|
+
if (!match) return [];
|
|
2685
|
+
const items = match[1].match(/['"]([^'"]+)['"]/g);
|
|
2686
|
+
return items ? items.map((s) => s.replace(/['"]/g, "")) : [];
|
|
2687
|
+
};
|
|
2688
|
+
const name = getName(content);
|
|
2689
|
+
if (!name) return null;
|
|
2690
|
+
const fallbacks = getFallbacks(content);
|
|
2691
|
+
const fallbackObjects = fallbacks.map((fallbackName, index) => ({
|
|
2692
|
+
id: fallbackName,
|
|
2693
|
+
name: fallbackName,
|
|
2694
|
+
order: index
|
|
2695
|
+
}));
|
|
2696
|
+
return {
|
|
2697
|
+
id: name,
|
|
2698
|
+
name,
|
|
2699
|
+
provider: getProvider(content),
|
|
2700
|
+
model: getModel(content),
|
|
2701
|
+
input_price: getInputPrice(content),
|
|
2702
|
+
output_price: getOutputPrice(content),
|
|
2703
|
+
cached_price: getCachedPrice(content),
|
|
2704
|
+
included_providers: getIncludedProviders(content),
|
|
2705
|
+
fallbacks: fallbackObjects,
|
|
2706
|
+
created_at: Math.floor(Date.now() / 1e3)
|
|
2707
|
+
};
|
|
2708
|
+
} catch (error) {
|
|
2709
|
+
console.error(`Error loading model ${file}:`, error);
|
|
2710
|
+
return null;
|
|
2711
|
+
}
|
|
2712
|
+
});
|
|
2713
|
+
const validModels = modelList.filter(Boolean);
|
|
2714
|
+
res.statusCode = 200;
|
|
2715
|
+
res.setHeader("Content-Type", "application/json");
|
|
2716
|
+
res.end(JSON.stringify({ models: validModels }));
|
|
2717
|
+
return;
|
|
2718
|
+
} catch (error) {
|
|
2719
|
+
res.statusCode = 500;
|
|
2720
|
+
res.setHeader("Content-Type", "application/json");
|
|
2721
|
+
res.end(JSON.stringify({ error: error.message || "Failed to list models" }));
|
|
2722
|
+
return;
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
if (pathWithoutMount === "/api/models" && method === "POST") {
|
|
2726
|
+
try {
|
|
2727
|
+
const rawBody = await parseRequestBody(req);
|
|
2728
|
+
const body = transformModelData(rawBody);
|
|
2729
|
+
const validationError = validateModelData(body);
|
|
2730
|
+
if (validationError) {
|
|
2731
|
+
res.statusCode = 400;
|
|
2732
|
+
res.setHeader("Content-Type", "application/json");
|
|
2733
|
+
res.end(JSON.stringify({ error: validationError }));
|
|
2734
|
+
return;
|
|
2735
|
+
}
|
|
2736
|
+
if (modelExists(modelsDir, body.name)) {
|
|
2737
|
+
res.statusCode = 409;
|
|
2738
|
+
res.setHeader("Content-Type", "application/json");
|
|
2739
|
+
res.end(JSON.stringify({ error: `Model '${body.name}' already exists. Use PUT to update.` }));
|
|
2740
|
+
return;
|
|
2741
|
+
}
|
|
2742
|
+
const result = await saveModel(modelsDir, body, false);
|
|
2743
|
+
if (result.success) {
|
|
2744
|
+
await reloadModelsModule(server);
|
|
2745
|
+
await reloadRouterModule(server);
|
|
2746
|
+
res.statusCode = 201;
|
|
2747
|
+
res.setHeader("Content-Type", "application/json");
|
|
2748
|
+
res.end(JSON.stringify({
|
|
2749
|
+
success: true,
|
|
2750
|
+
model: body,
|
|
2751
|
+
filePath: result.filePath
|
|
2752
|
+
}));
|
|
2753
|
+
} else {
|
|
2754
|
+
res.statusCode = 500;
|
|
2755
|
+
res.setHeader("Content-Type", "application/json");
|
|
2756
|
+
res.end(JSON.stringify({ error: result.error }));
|
|
2757
|
+
}
|
|
2758
|
+
return;
|
|
2759
|
+
} catch (error) {
|
|
2760
|
+
res.statusCode = 500;
|
|
2761
|
+
res.setHeader("Content-Type", "application/json");
|
|
2762
|
+
res.end(JSON.stringify({ error: error.message || "Failed to create model" }));
|
|
2763
|
+
return;
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
const modelPutMatch = pathWithoutMount.match(/^\/api\/models\/(.+)$/);
|
|
2767
|
+
if (modelPutMatch && method === "PUT") {
|
|
2768
|
+
try {
|
|
2769
|
+
const urlModelName = decodeURIComponent(modelPutMatch[1]);
|
|
2770
|
+
const rawBody = await parseRequestBody(req);
|
|
2771
|
+
const body = transformModelData(rawBody);
|
|
2772
|
+
const newName = body.name;
|
|
2773
|
+
const isNameChange = newName && newName !== urlModelName;
|
|
2774
|
+
const validationError = validateModelData(body);
|
|
2775
|
+
if (validationError) {
|
|
2776
|
+
res.statusCode = 400;
|
|
2777
|
+
res.setHeader("Content-Type", "application/json");
|
|
2778
|
+
res.end(JSON.stringify({ error: validationError }));
|
|
2779
|
+
return;
|
|
2780
|
+
}
|
|
2781
|
+
let updatedPrompts = [];
|
|
2782
|
+
if (isNameChange) {
|
|
2783
|
+
const renameResult = await renameModel(modelsDir, urlModelName, newName);
|
|
2784
|
+
if (!renameResult.success) {
|
|
2785
|
+
res.statusCode = 400;
|
|
2786
|
+
res.setHeader("Content-Type", "application/json");
|
|
2787
|
+
res.end(JSON.stringify({ error: renameResult.error }));
|
|
2788
|
+
return;
|
|
2789
|
+
}
|
|
2790
|
+
updatedPrompts = await updateModelReferencesInPrompts(promptsDir, urlModelName, newName);
|
|
2791
|
+
}
|
|
2792
|
+
const result = await saveModel(modelsDir, body, true);
|
|
2793
|
+
if (result.success) {
|
|
2794
|
+
await reloadModelsModule(server);
|
|
2795
|
+
if (updatedPrompts.length > 0) {
|
|
2796
|
+
await reloadPromptsModule(server);
|
|
2797
|
+
}
|
|
2798
|
+
await reloadRouterModule(server);
|
|
2799
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2800
|
+
res.statusCode = 200;
|
|
2801
|
+
res.setHeader("Content-Type", "application/json");
|
|
2802
|
+
res.end(JSON.stringify({
|
|
2803
|
+
success: true,
|
|
2804
|
+
model: body,
|
|
2805
|
+
filePath: result.filePath,
|
|
2806
|
+
...isNameChange && { renamed: { from: urlModelName, to: newName }, updatedPrompts }
|
|
2807
|
+
}));
|
|
2808
|
+
} else {
|
|
2809
|
+
res.statusCode = 500;
|
|
2810
|
+
res.setHeader("Content-Type", "application/json");
|
|
2811
|
+
res.end(JSON.stringify({ error: result.error }));
|
|
2812
|
+
}
|
|
2813
|
+
return;
|
|
2814
|
+
} catch (error) {
|
|
2815
|
+
res.statusCode = 500;
|
|
2816
|
+
res.setHeader("Content-Type", "application/json");
|
|
2817
|
+
res.end(JSON.stringify({ error: error.message || "Failed to update model" }));
|
|
2818
|
+
return;
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
const modelDeleteMatch = pathWithoutMount.match(/^\/api\/models\/(.+)$/);
|
|
2822
|
+
if (modelDeleteMatch && method === "DELETE") {
|
|
2823
|
+
try {
|
|
2824
|
+
const modelName = decodeURIComponent(modelDeleteMatch[1]);
|
|
2825
|
+
const result = await deleteModel(modelsDir, modelName);
|
|
2826
|
+
if (result.success) {
|
|
2827
|
+
await reloadModelsModule(server);
|
|
2828
|
+
await reloadRouterModule(server);
|
|
2829
|
+
res.statusCode = 200;
|
|
2830
|
+
res.setHeader("Content-Type", "application/json");
|
|
2831
|
+
res.end(JSON.stringify({
|
|
2832
|
+
success: true,
|
|
2833
|
+
deleted: modelName,
|
|
2834
|
+
filePath: result.filePath
|
|
2835
|
+
}));
|
|
2836
|
+
} else {
|
|
2837
|
+
res.statusCode = 404;
|
|
2838
|
+
res.setHeader("Content-Type", "application/json");
|
|
2839
|
+
res.end(JSON.stringify({ error: result.error }));
|
|
2840
|
+
}
|
|
2841
|
+
return;
|
|
2842
|
+
} catch (error) {
|
|
2843
|
+
res.statusCode = 500;
|
|
2844
|
+
res.setHeader("Content-Type", "application/json");
|
|
2845
|
+
res.end(JSON.stringify({ error: error.message || "Failed to delete model" }));
|
|
2846
|
+
return;
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
if (pathWithoutMount === "/api/prompts" && method === "POST") {
|
|
2850
|
+
try {
|
|
2851
|
+
const rawBody = await parseRequestBody(req);
|
|
2852
|
+
const body = transformPromptData(rawBody);
|
|
2853
|
+
const validationError = validatePromptData(body);
|
|
2854
|
+
if (validationError) {
|
|
2855
|
+
res.statusCode = 400;
|
|
2856
|
+
res.setHeader("Content-Type", "application/json");
|
|
2857
|
+
res.end(JSON.stringify({ error: validationError }));
|
|
2858
|
+
return;
|
|
2859
|
+
}
|
|
2860
|
+
if (promptExists(promptsDir, body.name)) {
|
|
2861
|
+
res.statusCode = 409;
|
|
2862
|
+
res.setHeader("Content-Type", "application/json");
|
|
2863
|
+
res.end(JSON.stringify({ error: `Prompt '${body.name}' already exists. Use PUT to update.` }));
|
|
2864
|
+
return;
|
|
2865
|
+
}
|
|
2866
|
+
const result = await savePrompt(promptsDir, body, false);
|
|
2867
|
+
if (result.success) {
|
|
2868
|
+
await reloadPromptsModule(server);
|
|
2869
|
+
await reloadRouterModule(server);
|
|
2870
|
+
res.statusCode = 201;
|
|
2871
|
+
res.setHeader("Content-Type", "application/json");
|
|
2872
|
+
res.end(JSON.stringify({
|
|
2873
|
+
success: true,
|
|
2874
|
+
prompt: body,
|
|
2875
|
+
filePath: result.filePath
|
|
2876
|
+
}));
|
|
2877
|
+
} else {
|
|
2878
|
+
res.statusCode = 500;
|
|
2879
|
+
res.setHeader("Content-Type", "application/json");
|
|
2880
|
+
res.end(JSON.stringify({ error: result.error }));
|
|
2881
|
+
}
|
|
2882
|
+
return;
|
|
2883
|
+
} catch (error) {
|
|
2884
|
+
res.statusCode = 500;
|
|
2885
|
+
res.setHeader("Content-Type", "application/json");
|
|
2886
|
+
res.end(JSON.stringify({ error: error.message || "Failed to create prompt" }));
|
|
2887
|
+
return;
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
const promptPutMatch = pathWithoutMount.match(/^\/api\/prompts\/([^/]+)$/);
|
|
2891
|
+
if (promptPutMatch && method === "PUT") {
|
|
2892
|
+
try {
|
|
2893
|
+
const urlPromptName = decodeURIComponent(promptPutMatch[1]);
|
|
2894
|
+
const rawBody = await parseRequestBody(req);
|
|
2895
|
+
const body = transformPromptData(rawBody);
|
|
2896
|
+
const newName = body.name;
|
|
2897
|
+
const isNameChange = newName && newName !== urlPromptName;
|
|
2898
|
+
const validationError = validatePromptData(body);
|
|
2899
|
+
if (validationError) {
|
|
2900
|
+
res.statusCode = 400;
|
|
2901
|
+
res.setHeader("Content-Type", "application/json");
|
|
2902
|
+
res.end(JSON.stringify({ error: validationError }));
|
|
2903
|
+
return;
|
|
2904
|
+
}
|
|
2905
|
+
let updatedPrompts = [];
|
|
2906
|
+
let updatedAgents = [];
|
|
2907
|
+
if (isNameChange) {
|
|
2908
|
+
const renameResult = await renamePrompt(promptsDir, urlPromptName, newName);
|
|
2909
|
+
if (!renameResult.success) {
|
|
2910
|
+
res.statusCode = 400;
|
|
2911
|
+
res.setHeader("Content-Type", "application/json");
|
|
2912
|
+
res.end(JSON.stringify({ error: renameResult.error }));
|
|
2913
|
+
return;
|
|
2914
|
+
}
|
|
2915
|
+
updatedPrompts = await updatePromptReferencesInPrompts(promptsDir, urlPromptName, newName);
|
|
2916
|
+
updatedAgents = await updatePromptReferencesInAgents(agentsDir, urlPromptName, newName);
|
|
2917
|
+
}
|
|
2918
|
+
const result = await savePrompt(promptsDir, body, true);
|
|
2919
|
+
if (result.success) {
|
|
2920
|
+
await reloadPromptsModule(server);
|
|
2921
|
+
if (updatedAgents.length > 0) {
|
|
2922
|
+
await reloadAgentsModule(server);
|
|
2923
|
+
}
|
|
2924
|
+
await reloadRouterModule(server);
|
|
2925
|
+
res.statusCode = 200;
|
|
2926
|
+
res.setHeader("Content-Type", "application/json");
|
|
2927
|
+
res.end(JSON.stringify({
|
|
2928
|
+
success: true,
|
|
2929
|
+
prompt: body,
|
|
2930
|
+
filePath: result.filePath,
|
|
2931
|
+
...isNameChange && { renamed: { from: urlPromptName, to: newName }, updatedPrompts, updatedAgents }
|
|
2932
|
+
}));
|
|
2933
|
+
} else {
|
|
2934
|
+
res.statusCode = 500;
|
|
2935
|
+
res.setHeader("Content-Type", "application/json");
|
|
2936
|
+
res.end(JSON.stringify({ error: result.error }));
|
|
2937
|
+
}
|
|
2938
|
+
return;
|
|
2939
|
+
} catch (error) {
|
|
2940
|
+
res.statusCode = 500;
|
|
2941
|
+
res.setHeader("Content-Type", "application/json");
|
|
2942
|
+
res.end(JSON.stringify({ error: error.message || "Failed to update prompt" }));
|
|
2943
|
+
return;
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2946
|
+
const promptDeleteMatch = pathWithoutMount.match(/^\/api\/prompts\/([^/]+)$/);
|
|
2947
|
+
if (promptDeleteMatch && method === "DELETE") {
|
|
2948
|
+
try {
|
|
2949
|
+
const promptName = decodeURIComponent(promptDeleteMatch[1]);
|
|
2950
|
+
const result = await deletePrompt(promptsDir, promptName);
|
|
2951
|
+
if (result.success) {
|
|
2952
|
+
await reloadPromptsModule(server);
|
|
2953
|
+
await reloadRouterModule(server);
|
|
2954
|
+
res.statusCode = 200;
|
|
2955
|
+
res.setHeader("Content-Type", "application/json");
|
|
2956
|
+
res.end(JSON.stringify({
|
|
2957
|
+
success: true,
|
|
2958
|
+
deleted: promptName,
|
|
2959
|
+
filePath: result.filePath
|
|
2960
|
+
}));
|
|
2961
|
+
} else {
|
|
2962
|
+
res.statusCode = 404;
|
|
2963
|
+
res.setHeader("Content-Type", "application/json");
|
|
2964
|
+
res.end(JSON.stringify({ error: result.error }));
|
|
2965
|
+
}
|
|
2966
|
+
return;
|
|
2967
|
+
} catch (error) {
|
|
2968
|
+
res.statusCode = 500;
|
|
2969
|
+
res.setHeader("Content-Type", "application/json");
|
|
2970
|
+
res.end(JSON.stringify({ error: error.message || "Failed to delete prompt" }));
|
|
2971
|
+
return;
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
if (pathWithoutMount === "/api/agents" && method === "POST") {
|
|
2975
|
+
try {
|
|
2976
|
+
const rawBody = await parseRequestBody(req);
|
|
2977
|
+
const body = transformAgentData(rawBody);
|
|
2978
|
+
const validationError = validateAgentData(body);
|
|
2979
|
+
if (validationError) {
|
|
2980
|
+
res.statusCode = 400;
|
|
2981
|
+
res.setHeader("Content-Type", "application/json");
|
|
2982
|
+
res.end(JSON.stringify({ error: validationError }));
|
|
2983
|
+
return;
|
|
2984
|
+
}
|
|
2985
|
+
if (agentExists(agentsDir, body.name)) {
|
|
2986
|
+
res.statusCode = 409;
|
|
2987
|
+
res.setHeader("Content-Type", "application/json");
|
|
2988
|
+
res.end(JSON.stringify({ error: `Agent '${body.name}' already exists. Use PUT to update.` }));
|
|
2989
|
+
return;
|
|
2990
|
+
}
|
|
2991
|
+
const result = await saveAgent(agentsDir, body, false);
|
|
2992
|
+
if (result.success) {
|
|
2993
|
+
await reloadAgentsModule(server);
|
|
2994
|
+
await reloadRouterModule(server);
|
|
2995
|
+
res.statusCode = 201;
|
|
2996
|
+
res.setHeader("Content-Type", "application/json");
|
|
2997
|
+
res.end(JSON.stringify({
|
|
2998
|
+
success: true,
|
|
2999
|
+
agent: body,
|
|
3000
|
+
filePath: result.filePath
|
|
3001
|
+
}));
|
|
3002
|
+
} else {
|
|
3003
|
+
res.statusCode = 500;
|
|
3004
|
+
res.setHeader("Content-Type", "application/json");
|
|
3005
|
+
res.end(JSON.stringify({ error: result.error }));
|
|
3006
|
+
}
|
|
3007
|
+
return;
|
|
3008
|
+
} catch (error) {
|
|
3009
|
+
res.statusCode = 500;
|
|
3010
|
+
res.setHeader("Content-Type", "application/json");
|
|
3011
|
+
res.end(JSON.stringify({ error: error.message || "Failed to create agent" }));
|
|
3012
|
+
return;
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
const agentPutMatch = pathWithoutMount.match(/^\/api\/agents\/([^/]+)$/);
|
|
3016
|
+
if (agentPutMatch && method === "PUT") {
|
|
3017
|
+
try {
|
|
3018
|
+
const agentName = decodeURIComponent(agentPutMatch[1]);
|
|
3019
|
+
const rawBody = await parseRequestBody(req);
|
|
3020
|
+
const body = transformAgentData(rawBody);
|
|
3021
|
+
body.name = agentName;
|
|
3022
|
+
const validationError = validateAgentData(body);
|
|
3023
|
+
if (validationError) {
|
|
3024
|
+
res.statusCode = 400;
|
|
3025
|
+
res.setHeader("Content-Type", "application/json");
|
|
3026
|
+
res.end(JSON.stringify({ error: validationError }));
|
|
3027
|
+
return;
|
|
3028
|
+
}
|
|
3029
|
+
const result = await saveAgent(agentsDir, body, true);
|
|
3030
|
+
if (result.success) {
|
|
3031
|
+
await reloadAgentsModule(server);
|
|
3032
|
+
await reloadRouterModule(server);
|
|
3033
|
+
res.statusCode = 200;
|
|
3034
|
+
res.setHeader("Content-Type", "application/json");
|
|
3035
|
+
res.end(JSON.stringify({
|
|
3036
|
+
success: true,
|
|
3037
|
+
agent: body,
|
|
3038
|
+
filePath: result.filePath
|
|
3039
|
+
}));
|
|
3040
|
+
} else {
|
|
3041
|
+
res.statusCode = 500;
|
|
3042
|
+
res.setHeader("Content-Type", "application/json");
|
|
3043
|
+
res.end(JSON.stringify({ error: result.error }));
|
|
3044
|
+
}
|
|
3045
|
+
return;
|
|
3046
|
+
} catch (error) {
|
|
3047
|
+
res.statusCode = 500;
|
|
3048
|
+
res.setHeader("Content-Type", "application/json");
|
|
3049
|
+
res.end(JSON.stringify({ error: error.message || "Failed to update agent" }));
|
|
3050
|
+
return;
|
|
3051
|
+
}
|
|
3052
|
+
}
|
|
3053
|
+
const agentDeleteMatch = pathWithoutMount.match(/^\/api\/agents\/([^/]+)$/);
|
|
3054
|
+
if (agentDeleteMatch && method === "DELETE") {
|
|
3055
|
+
try {
|
|
3056
|
+
const agentName = decodeURIComponent(agentDeleteMatch[1]);
|
|
3057
|
+
const result = await deleteAgent(agentsDir, agentName);
|
|
3058
|
+
if (result.success) {
|
|
3059
|
+
await reloadAgentsModule(server);
|
|
3060
|
+
await reloadRouterModule(server);
|
|
3061
|
+
res.statusCode = 200;
|
|
3062
|
+
res.setHeader("Content-Type", "application/json");
|
|
3063
|
+
res.end(JSON.stringify({
|
|
3064
|
+
success: true,
|
|
3065
|
+
deleted: agentName,
|
|
3066
|
+
filePath: result.filePath
|
|
3067
|
+
}));
|
|
3068
|
+
} else {
|
|
3069
|
+
res.statusCode = 404;
|
|
3070
|
+
res.setHeader("Content-Type", "application/json");
|
|
3071
|
+
res.end(JSON.stringify({ error: result.error }));
|
|
3072
|
+
}
|
|
3073
|
+
return;
|
|
3074
|
+
} catch (error) {
|
|
3075
|
+
res.statusCode = 500;
|
|
3076
|
+
res.setHeader("Content-Type", "application/json");
|
|
3077
|
+
res.end(JSON.stringify({ error: error.message || "Failed to delete agent" }));
|
|
3078
|
+
return;
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
if (pathWithoutMount.startsWith("/api/")) {
|
|
3082
|
+
next();
|
|
3083
|
+
return;
|
|
3084
|
+
}
|
|
3085
|
+
const isStaticAsset = pathWithoutMount.startsWith("/assets/") || pathWithoutMount.startsWith("/vendor.js") || pathWithoutMount.startsWith("/vue.js") || pathWithoutMount.startsWith("/monaco.js") || pathWithoutMount.startsWith("/index.js") || pathWithoutMount.startsWith("/index.css") || pathWithoutMount.match(/\.(js|css|png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot|ico)$/);
|
|
3086
|
+
{
|
|
3087
|
+
const currentDir = path3.dirname(fileURLToPath(import.meta.url));
|
|
3088
|
+
const isInDist = currentDir.endsWith("dist");
|
|
3089
|
+
const clientDir = path3.resolve(
|
|
3090
|
+
currentDir,
|
|
3091
|
+
isInDist ? "./client" : "../dist/client"
|
|
3092
|
+
);
|
|
3093
|
+
let filePath;
|
|
3094
|
+
if (isStaticAsset) {
|
|
3095
|
+
const cleanUrl = pathWithoutMount.split("?")[0];
|
|
3096
|
+
filePath = path3.join(clientDir, cleanUrl);
|
|
3097
|
+
} else {
|
|
3098
|
+
filePath = path3.join(clientDir, "index.html");
|
|
3099
|
+
}
|
|
3100
|
+
try {
|
|
3101
|
+
if (fs2.existsSync(filePath)) {
|
|
3102
|
+
let content = fs2.readFileSync(filePath);
|
|
3103
|
+
const ext = path3.extname(filePath).toLowerCase();
|
|
3104
|
+
if (ext === ".html") {
|
|
3105
|
+
const configScript = `<script>window.__AGENTBUILDER_CONFIG__ = { mountPoint: "${mountPoint}" };</script>`;
|
|
3106
|
+
let htmlContent = content.toString();
|
|
3107
|
+
const assetPrefix = mountPoint === "/" ? "/" : `${mountPoint}/`;
|
|
3108
|
+
htmlContent = htmlContent.replace(/\/agentbuilder\//g, assetPrefix);
|
|
3109
|
+
htmlContent = htmlContent.replace("</head>", `${configScript}</head>`);
|
|
3110
|
+
content = Buffer.from(htmlContent);
|
|
3111
|
+
}
|
|
3112
|
+
const mimeTypes = {
|
|
3113
|
+
".html": "text/html",
|
|
3114
|
+
".js": "application/javascript",
|
|
3115
|
+
".css": "text/css",
|
|
3116
|
+
".ttf": "font/ttf",
|
|
3117
|
+
".woff": "font/woff",
|
|
3118
|
+
".woff2": "font/woff2",
|
|
3119
|
+
".svg": "image/svg+xml",
|
|
3120
|
+
".png": "image/png",
|
|
3121
|
+
".jpg": "image/jpeg"
|
|
3122
|
+
};
|
|
3123
|
+
const contentType = mimeTypes[ext] || "application/octet-stream";
|
|
3124
|
+
res.setHeader("Content-Type", contentType);
|
|
3125
|
+
res.end(content);
|
|
3126
|
+
return;
|
|
3127
|
+
}
|
|
3128
|
+
} catch (error) {
|
|
3129
|
+
console.error("Error serving AgentBuilder UI:", error);
|
|
3130
|
+
}
|
|
3131
|
+
}
|
|
3132
|
+
next();
|
|
3133
|
+
});
|
|
3134
|
+
},
|
|
3135
|
+
handleHotUpdate({ file, server }) {
|
|
3136
|
+
handleFileChange(file, server, "File modified");
|
|
3137
|
+
},
|
|
3138
|
+
writeBundle(options2, bundle) {
|
|
3139
|
+
const outDir = options2.dir || "dist";
|
|
3140
|
+
const mountPath = mountPoint.slice(1);
|
|
3141
|
+
const mountDir = mountPath ? path3.join(outDir, "../client", mountPath) : path3.join(outDir, "../client");
|
|
3142
|
+
const currentDir = path3.dirname(fileURLToPath(import.meta.url));
|
|
3143
|
+
const isInDist = currentDir.endsWith("dist");
|
|
3144
|
+
const clientDir = path3.resolve(
|
|
3145
|
+
currentDir,
|
|
3146
|
+
isInDist ? "./client" : "../dist/client"
|
|
3147
|
+
);
|
|
3148
|
+
if (!fs2.existsSync(clientDir)) {
|
|
3149
|
+
console.warn(`[agentbuilder] Client directory not found at ${clientDir}`);
|
|
3150
|
+
return;
|
|
3151
|
+
}
|
|
3152
|
+
fs2.mkdirSync(mountDir, { recursive: true });
|
|
3153
|
+
function copyRecursive(src, dest) {
|
|
3154
|
+
const entries = fs2.readdirSync(src, { withFileTypes: true });
|
|
3155
|
+
for (const entry of entries) {
|
|
3156
|
+
const srcPath = path3.join(src, entry.name);
|
|
3157
|
+
const destPath = path3.join(dest, entry.name);
|
|
3158
|
+
if (entry.isDirectory()) {
|
|
3159
|
+
fs2.mkdirSync(destPath, { recursive: true });
|
|
3160
|
+
copyRecursive(srcPath, destPath);
|
|
3161
|
+
} else {
|
|
3162
|
+
let content = fs2.readFileSync(srcPath);
|
|
3163
|
+
if (entry.name === "index.html") {
|
|
3164
|
+
const configScript = `<script>window.__AGENTBUILDER_CONFIG__ = { mountPoint: "${mountPoint}" };</script>`;
|
|
3165
|
+
let htmlContent = content.toString();
|
|
3166
|
+
const assetPrefix = mountPoint === "/" ? "/" : `${mountPoint}/`;
|
|
3167
|
+
htmlContent = htmlContent.replace(/\/agentbuilder\//g, assetPrefix);
|
|
3168
|
+
htmlContent = htmlContent.replace("</head>", `${configScript}</head>`);
|
|
3169
|
+
content = Buffer.from(htmlContent);
|
|
3170
|
+
}
|
|
3171
|
+
fs2.writeFileSync(destPath, content);
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
copyRecursive(clientDir, mountDir);
|
|
3176
|
+
console.log(`[agentbuilder] Copied client files to ${mountDir}`);
|
|
3177
|
+
}
|
|
3178
|
+
};
|
|
3179
|
+
}
|
|
3180
|
+
|
|
3181
|
+
export { agentbuilder };
|
|
3182
|
+
//# sourceMappingURL=plugin.js.map
|
|
3183
|
+
//# sourceMappingURL=plugin.js.map
|