@agentforge-ai/cli 0.5.3 → 0.5.5
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/LICENSE +197 -0
- package/dist/default/agentforge.config.ts +126 -6
- package/dist/default/convex/agents.ts +15 -21
- package/dist/default/convex/chat.ts +331 -0
- package/dist/default/convex/mastraIntegration.ts +163 -67
- package/dist/default/dashboard/app/routes/chat.tsx +462 -167
- package/dist/default/skills/browser-automation/SKILL.md +137 -0
- package/dist/default/skills/browser-automation/config.json +11 -0
- package/dist/default/skills/browser-automation/index.ts +93 -0
- package/dist/default/skills/skill-creator/SKILL.md +69 -230
- package/dist/index.js +2455 -290
- package/dist/index.js.map +1 -1
- package/package.json +13 -12
- package/templates/default/agentforge.config.ts +126 -6
- package/templates/default/convex/agents.ts +15 -21
- package/templates/default/convex/chat.ts +331 -0
- package/templates/default/convex/mastraIntegration.ts +163 -67
- package/templates/default/dashboard/app/routes/chat.tsx +462 -167
- package/templates/default/skills/browser-automation/SKILL.md +137 -0
- package/templates/default/skills/browser-automation/config.json +11 -0
- package/templates/default/skills/browser-automation/index.ts +93 -0
- package/templates/default/skills/skill-creator/SKILL.md +69 -230
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat Actions for AgentForge
|
|
3
|
+
*
|
|
4
|
+
* This module provides the core chat execution pipeline:
|
|
5
|
+
* 1. User sends a message → stored via mutation
|
|
6
|
+
* 2. Convex action triggers LLM generation via OpenRouter (AI SDK)
|
|
7
|
+
* 3. Assistant response stored back in Convex
|
|
8
|
+
* 4. Real-time subscription updates the UI automatically
|
|
9
|
+
*
|
|
10
|
+
* Uses the Vercel AI SDK with OpenRouter as an OpenAI-compatible provider.
|
|
11
|
+
* No dynamic imports of @mastra/core — we use the AI SDK directly in Convex
|
|
12
|
+
* Node.js actions for maximum reliability.
|
|
13
|
+
*/
|
|
14
|
+
import { action, mutation, query } from "./_generated/server";
|
|
15
|
+
import { v } from "convex/values";
|
|
16
|
+
import { api } from "./_generated/api";
|
|
17
|
+
|
|
18
|
+
// ============================================================
|
|
19
|
+
// Queries
|
|
20
|
+
// ============================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get the current chat state for a thread: messages + thread metadata.
|
|
24
|
+
*/
|
|
25
|
+
export const getThreadMessages = query({
|
|
26
|
+
args: { threadId: v.id("threads") },
|
|
27
|
+
handler: async (ctx, args) => {
|
|
28
|
+
const messages = await ctx.db
|
|
29
|
+
.query("messages")
|
|
30
|
+
.withIndex("byThread", (q) => q.eq("threadId", args.threadId))
|
|
31
|
+
.collect();
|
|
32
|
+
return messages;
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* List all threads for a user, ordered by most recent activity.
|
|
38
|
+
*/
|
|
39
|
+
export const listThreads = query({
|
|
40
|
+
args: {
|
|
41
|
+
userId: v.optional(v.string()),
|
|
42
|
+
agentId: v.optional(v.string()),
|
|
43
|
+
},
|
|
44
|
+
handler: async (ctx, args) => {
|
|
45
|
+
let threads;
|
|
46
|
+
if (args.agentId) {
|
|
47
|
+
threads = await ctx.db
|
|
48
|
+
.query("threads")
|
|
49
|
+
.withIndex("byAgentId", (q) => q.eq("agentId", args.agentId!))
|
|
50
|
+
.collect();
|
|
51
|
+
} else if (args.userId) {
|
|
52
|
+
threads = await ctx.db
|
|
53
|
+
.query("threads")
|
|
54
|
+
.withIndex("byUserId", (q) => q.eq("userId", args.userId!))
|
|
55
|
+
.collect();
|
|
56
|
+
} else {
|
|
57
|
+
threads = await ctx.db.query("threads").collect();
|
|
58
|
+
}
|
|
59
|
+
// Sort by most recently updated
|
|
60
|
+
return threads.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// ============================================================
|
|
65
|
+
// Mutations
|
|
66
|
+
// ============================================================
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Create a new chat thread for an agent.
|
|
70
|
+
*/
|
|
71
|
+
export const createThread = mutation({
|
|
72
|
+
args: {
|
|
73
|
+
agentId: v.string(),
|
|
74
|
+
name: v.optional(v.string()),
|
|
75
|
+
userId: v.optional(v.string()),
|
|
76
|
+
},
|
|
77
|
+
handler: async (ctx, args) => {
|
|
78
|
+
const now = Date.now();
|
|
79
|
+
const threadId = await ctx.db.insert("threads", {
|
|
80
|
+
name: args.name || "New Chat",
|
|
81
|
+
agentId: args.agentId,
|
|
82
|
+
userId: args.userId,
|
|
83
|
+
createdAt: now,
|
|
84
|
+
updatedAt: now,
|
|
85
|
+
});
|
|
86
|
+
return threadId;
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Store a user message in a thread (called before triggering LLM).
|
|
92
|
+
*/
|
|
93
|
+
export const addUserMessage = mutation({
|
|
94
|
+
args: {
|
|
95
|
+
threadId: v.id("threads"),
|
|
96
|
+
content: v.string(),
|
|
97
|
+
},
|
|
98
|
+
handler: async (ctx, args) => {
|
|
99
|
+
const messageId = await ctx.db.insert("messages", {
|
|
100
|
+
threadId: args.threadId,
|
|
101
|
+
role: "user",
|
|
102
|
+
content: args.content,
|
|
103
|
+
createdAt: Date.now(),
|
|
104
|
+
});
|
|
105
|
+
await ctx.db.patch(args.threadId, { updatedAt: Date.now() });
|
|
106
|
+
return messageId;
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Store an assistant message in a thread (called after LLM responds).
|
|
112
|
+
*/
|
|
113
|
+
export const addAssistantMessage = mutation({
|
|
114
|
+
args: {
|
|
115
|
+
threadId: v.id("threads"),
|
|
116
|
+
content: v.string(),
|
|
117
|
+
metadata: v.optional(v.any()),
|
|
118
|
+
},
|
|
119
|
+
handler: async (ctx, args) => {
|
|
120
|
+
const messageId = await ctx.db.insert("messages", {
|
|
121
|
+
threadId: args.threadId,
|
|
122
|
+
role: "assistant",
|
|
123
|
+
content: args.content,
|
|
124
|
+
metadata: args.metadata,
|
|
125
|
+
createdAt: Date.now(),
|
|
126
|
+
});
|
|
127
|
+
await ctx.db.patch(args.threadId, { updatedAt: Date.now() });
|
|
128
|
+
return messageId;
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// ============================================================
|
|
133
|
+
// Actions (Node.js runtime — can call external APIs)
|
|
134
|
+
// ============================================================
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Send a message and get an AI response.
|
|
138
|
+
*
|
|
139
|
+
* This is the main chat action. It:
|
|
140
|
+
* 1. Looks up the agent config from the database
|
|
141
|
+
* 2. Stores the user message
|
|
142
|
+
* 3. Builds conversation history from the thread
|
|
143
|
+
* 4. Calls OpenRouter (or other provider) via the AI SDK
|
|
144
|
+
* 5. Stores the assistant response
|
|
145
|
+
* 6. Records usage metrics
|
|
146
|
+
*
|
|
147
|
+
* The UI subscribes to `chat.getThreadMessages` which auto-updates
|
|
148
|
+
* when new messages are inserted.
|
|
149
|
+
*/
|
|
150
|
+
export const sendMessage = action({
|
|
151
|
+
args: {
|
|
152
|
+
agentId: v.string(),
|
|
153
|
+
threadId: v.id("threads"),
|
|
154
|
+
content: v.string(),
|
|
155
|
+
userId: v.optional(v.string()),
|
|
156
|
+
},
|
|
157
|
+
handler: async (ctx, args) => {
|
|
158
|
+
// 1. Get agent configuration
|
|
159
|
+
const agent = await ctx.runQuery(api.agents.get, { id: args.agentId });
|
|
160
|
+
if (!agent) {
|
|
161
|
+
throw new Error(`Agent "${args.agentId}" not found. Please create an agent first.`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// 2. Store the user message
|
|
165
|
+
await ctx.runMutation(api.chat.addUserMessage, {
|
|
166
|
+
threadId: args.threadId,
|
|
167
|
+
content: args.content,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// 3. Get conversation history for context
|
|
171
|
+
const history = await ctx.runQuery(api.chat.getThreadMessages, {
|
|
172
|
+
threadId: args.threadId,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Build messages array for the LLM (last 20 messages for context window)
|
|
176
|
+
const conversationMessages = history
|
|
177
|
+
.slice(-20)
|
|
178
|
+
.map((msg: { role: string; content: string }) => ({
|
|
179
|
+
role: msg.role as "user" | "assistant" | "system",
|
|
180
|
+
content: msg.content,
|
|
181
|
+
}));
|
|
182
|
+
|
|
183
|
+
// 4. Call the LLM via AI SDK
|
|
184
|
+
let responseText: string;
|
|
185
|
+
let usageData: { promptTokens: number; completionTokens: number; totalTokens: number } | null = null;
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
// Dynamically import the AI SDK (available in Convex Node.js actions)
|
|
189
|
+
const { generateText } = await import("ai");
|
|
190
|
+
const { createOpenAI } = await import("@ai-sdk/openai");
|
|
191
|
+
|
|
192
|
+
// Resolve the model provider and ID
|
|
193
|
+
const provider = agent.provider || "openrouter";
|
|
194
|
+
const modelId = agent.model || "openai/gpt-4o-mini";
|
|
195
|
+
|
|
196
|
+
// Create the appropriate provider instance
|
|
197
|
+
let model;
|
|
198
|
+
if (provider === "openrouter") {
|
|
199
|
+
const openrouter = createOpenAI({
|
|
200
|
+
baseURL: "https://openrouter.ai/api/v1",
|
|
201
|
+
apiKey: process.env.OPENROUTER_API_KEY,
|
|
202
|
+
});
|
|
203
|
+
model = openrouter(modelId);
|
|
204
|
+
} else if (provider === "openai") {
|
|
205
|
+
const openai = createOpenAI({
|
|
206
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
207
|
+
});
|
|
208
|
+
model = openai(modelId);
|
|
209
|
+
} else {
|
|
210
|
+
// Default to OpenRouter for any provider — it routes to all models
|
|
211
|
+
const openrouter = createOpenAI({
|
|
212
|
+
baseURL: "https://openrouter.ai/api/v1",
|
|
213
|
+
apiKey: process.env.OPENROUTER_API_KEY,
|
|
214
|
+
});
|
|
215
|
+
// Use provider/model format for OpenRouter routing
|
|
216
|
+
const routerModelId = modelId.includes("/")
|
|
217
|
+
? modelId
|
|
218
|
+
: `${provider}/${modelId}`;
|
|
219
|
+
model = openrouter(routerModelId);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Generate the response
|
|
223
|
+
const result = await generateText({
|
|
224
|
+
model,
|
|
225
|
+
system: agent.instructions || "You are a helpful AI assistant built with AgentForge.",
|
|
226
|
+
messages: conversationMessages,
|
|
227
|
+
...(agent.temperature != null && { temperature: agent.temperature }),
|
|
228
|
+
...(agent.maxTokens != null && { maxTokens: agent.maxTokens }),
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
responseText = result.text;
|
|
232
|
+
|
|
233
|
+
// Extract usage if available
|
|
234
|
+
if (result.usage) {
|
|
235
|
+
usageData = {
|
|
236
|
+
promptTokens: result.usage.promptTokens || 0,
|
|
237
|
+
completionTokens: result.usage.completionTokens || 0,
|
|
238
|
+
totalTokens: (result.usage.promptTokens || 0) + (result.usage.completionTokens || 0),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
} catch (error: unknown) {
|
|
242
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
243
|
+
console.error("[chat.sendMessage] LLM error:", errorMessage);
|
|
244
|
+
|
|
245
|
+
// Store error as assistant message so user sees feedback
|
|
246
|
+
responseText = `I encountered an error while processing your request: ${errorMessage}`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// 5. Store the assistant response
|
|
250
|
+
await ctx.runMutation(api.chat.addAssistantMessage, {
|
|
251
|
+
threadId: args.threadId,
|
|
252
|
+
content: responseText,
|
|
253
|
+
metadata: usageData ? { usage: usageData } : undefined,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// 6. Record usage metrics (non-blocking, best-effort)
|
|
257
|
+
if (usageData) {
|
|
258
|
+
try {
|
|
259
|
+
await ctx.runMutation(api.usage.record, {
|
|
260
|
+
agentId: args.agentId,
|
|
261
|
+
provider: agent.provider || "openrouter",
|
|
262
|
+
model: agent.model || "unknown",
|
|
263
|
+
promptTokens: usageData.promptTokens,
|
|
264
|
+
completionTokens: usageData.completionTokens,
|
|
265
|
+
totalTokens: usageData.totalTokens,
|
|
266
|
+
userId: args.userId,
|
|
267
|
+
});
|
|
268
|
+
} catch (e) {
|
|
269
|
+
console.error("[chat.sendMessage] Usage recording failed:", e);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// 7. Log the interaction
|
|
274
|
+
try {
|
|
275
|
+
await ctx.runMutation(api.logs.add, {
|
|
276
|
+
level: "info",
|
|
277
|
+
source: "chat",
|
|
278
|
+
message: `Agent "${agent.name}" responded to user message`,
|
|
279
|
+
metadata: {
|
|
280
|
+
agentId: args.agentId,
|
|
281
|
+
threadId: args.threadId,
|
|
282
|
+
usage: usageData,
|
|
283
|
+
},
|
|
284
|
+
userId: args.userId,
|
|
285
|
+
});
|
|
286
|
+
} catch (e) {
|
|
287
|
+
console.error("[chat.sendMessage] Logging failed:", e);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return {
|
|
291
|
+
success: true,
|
|
292
|
+
threadId: args.threadId,
|
|
293
|
+
response: responseText,
|
|
294
|
+
usage: usageData,
|
|
295
|
+
};
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Create a new thread and send the first message in one action.
|
|
301
|
+
* Convenience action for starting a new conversation.
|
|
302
|
+
*/
|
|
303
|
+
export const startNewChat = action({
|
|
304
|
+
args: {
|
|
305
|
+
agentId: v.string(),
|
|
306
|
+
content: v.string(),
|
|
307
|
+
threadName: v.optional(v.string()),
|
|
308
|
+
userId: v.optional(v.string()),
|
|
309
|
+
},
|
|
310
|
+
handler: async (ctx, args) => {
|
|
311
|
+
// Create a new thread
|
|
312
|
+
const threadId = await ctx.runMutation(api.chat.createThread, {
|
|
313
|
+
agentId: args.agentId,
|
|
314
|
+
name: args.threadName || "New Chat",
|
|
315
|
+
userId: args.userId,
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// Send the first message
|
|
319
|
+
const result = await ctx.runAction(api.chat.sendMessage, {
|
|
320
|
+
agentId: args.agentId,
|
|
321
|
+
threadId,
|
|
322
|
+
content: args.content,
|
|
323
|
+
userId: args.userId,
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
...result,
|
|
328
|
+
threadId,
|
|
329
|
+
};
|
|
330
|
+
},
|
|
331
|
+
});
|
|
@@ -1,15 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mastra Integration Actions for Convex
|
|
3
|
+
*
|
|
4
|
+
* These actions run in the Convex Node.js runtime and execute LLM calls
|
|
5
|
+
* using the Vercel AI SDK with OpenRouter as the default provider.
|
|
6
|
+
*
|
|
7
|
+
* Architecture:
|
|
8
|
+
* - For chat: use `chat.sendMessage` (preferred entry point)
|
|
9
|
+
* - For programmatic agent execution: use `mastraIntegration.executeAgent`
|
|
10
|
+
* - Model resolution: uses AI SDK providers directly (OpenRouter, OpenAI, etc.)
|
|
11
|
+
*
|
|
12
|
+
* This replaces the previous broken approach of dynamically importing
|
|
13
|
+
* @mastra/core inside Convex actions. The AI SDK is the correct way to
|
|
14
|
+
* call LLMs from Convex Node.js actions.
|
|
15
|
+
*/
|
|
1
16
|
import { action } from "./_generated/server";
|
|
2
17
|
import { v } from "convex/values";
|
|
3
18
|
import { api } from "./_generated/api";
|
|
4
19
|
|
|
20
|
+
// Return type for executeAgent
|
|
21
|
+
type ExecuteAgentResult = {
|
|
22
|
+
success: boolean;
|
|
23
|
+
threadId: string;
|
|
24
|
+
sessionId: string;
|
|
25
|
+
response: string;
|
|
26
|
+
usage?: {
|
|
27
|
+
promptTokens: number;
|
|
28
|
+
completionTokens: number;
|
|
29
|
+
totalTokens: number;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
|
|
5
33
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* to
|
|
34
|
+
* Resolve a model instance from provider + modelId using the AI SDK.
|
|
35
|
+
*
|
|
36
|
+
* Supports: openrouter, openai, anthropic, google, venice, custom.
|
|
37
|
+
* Falls back to OpenRouter for unknown providers (it routes to all models).
|
|
10
38
|
*/
|
|
39
|
+
async function resolveModel(provider: string, modelId: string) {
|
|
40
|
+
const { createOpenAI } = await import("@ai-sdk/openai");
|
|
41
|
+
|
|
42
|
+
switch (provider) {
|
|
43
|
+
case "openai": {
|
|
44
|
+
const openai = createOpenAI({
|
|
45
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
46
|
+
});
|
|
47
|
+
return openai(modelId);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
case "anthropic": {
|
|
51
|
+
const { createAnthropic } = await import("@ai-sdk/anthropic");
|
|
52
|
+
const anthropic = createAnthropic({
|
|
53
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
54
|
+
});
|
|
55
|
+
return anthropic(modelId);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
case "google": {
|
|
59
|
+
const { createGoogleGenerativeAI } = await import("@ai-sdk/google");
|
|
60
|
+
const google = createGoogleGenerativeAI({
|
|
61
|
+
apiKey: process.env.GEMINI_API_KEY,
|
|
62
|
+
});
|
|
63
|
+
return google(modelId);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
case "openrouter":
|
|
67
|
+
default: {
|
|
68
|
+
// OpenRouter is OpenAI-compatible and routes to all providers
|
|
69
|
+
const openrouter = createOpenAI({
|
|
70
|
+
baseURL: "https://openrouter.ai/api/v1",
|
|
71
|
+
apiKey: process.env.OPENROUTER_API_KEY,
|
|
72
|
+
});
|
|
73
|
+
const routerModelId = modelId.includes("/")
|
|
74
|
+
? modelId
|
|
75
|
+
: `${provider}/${modelId}`;
|
|
76
|
+
return openrouter(routerModelId);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
11
80
|
|
|
12
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Execute an agent with a prompt and return the response.
|
|
83
|
+
*
|
|
84
|
+
* This is the programmatic API for agent execution. For chat UI,
|
|
85
|
+
* prefer `chat.sendMessage` which handles thread management automatically.
|
|
86
|
+
*/
|
|
13
87
|
export const executeAgent = action({
|
|
14
88
|
args: {
|
|
15
89
|
agentId: v.string(),
|
|
@@ -18,10 +92,10 @@ export const executeAgent = action({
|
|
|
18
92
|
userId: v.optional(v.string()),
|
|
19
93
|
stream: v.optional(v.boolean()),
|
|
20
94
|
},
|
|
21
|
-
handler: async (ctx, args) => {
|
|
95
|
+
handler: async (ctx, args): Promise<ExecuteAgentResult> => {
|
|
22
96
|
// Get agent configuration from database
|
|
23
97
|
const agent = await ctx.runQuery(api.agents.get, { id: args.agentId });
|
|
24
|
-
|
|
98
|
+
|
|
25
99
|
if (!agent) {
|
|
26
100
|
throw new Error(`Agent ${args.agentId} not found`);
|
|
27
101
|
}
|
|
@@ -49,50 +123,36 @@ export const executeAgent = action({
|
|
|
49
123
|
threadId,
|
|
50
124
|
agentId: args.agentId,
|
|
51
125
|
userId: args.userId,
|
|
52
|
-
channel: "
|
|
126
|
+
channel: "api",
|
|
53
127
|
});
|
|
54
128
|
|
|
55
129
|
try {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
? agent.model
|
|
63
|
-
: `${agent.provider}/${agent.model}`;
|
|
64
|
-
|
|
65
|
-
// Create Mastra agent
|
|
66
|
-
const mastraAgent = new Agent({
|
|
67
|
-
id: agent.id,
|
|
68
|
-
name: agent.name,
|
|
69
|
-
instructions: agent.instructions,
|
|
70
|
-
model: modelString,
|
|
71
|
-
tools: agent.tools || {},
|
|
72
|
-
...(agent.temperature && { temperature: agent.temperature }),
|
|
73
|
-
...(agent.maxTokens && { maxTokens: agent.maxTokens }),
|
|
74
|
-
...(agent.topP && { topP: agent.topP }),
|
|
75
|
-
});
|
|
130
|
+
const { generateText } = await import("ai");
|
|
131
|
+
|
|
132
|
+
// Resolve the model
|
|
133
|
+
const provider = agent.provider || "openrouter";
|
|
134
|
+
const modelId = agent.model || "openai/gpt-4o-mini";
|
|
135
|
+
const model = await resolveModel(provider, modelId);
|
|
76
136
|
|
|
77
137
|
// Get conversation history for context
|
|
78
138
|
const messages = await ctx.runQuery(api.messages.list, { threadId });
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
// Execute
|
|
87
|
-
const result
|
|
88
|
-
|
|
89
|
-
|
|
139
|
+
const conversationMessages = (messages as Array<{ role: string; content: string }>)
|
|
140
|
+
.slice(-20)
|
|
141
|
+
.map((m) => ({
|
|
142
|
+
role: m.role as "user" | "assistant" | "system",
|
|
143
|
+
content: m.content,
|
|
144
|
+
}));
|
|
145
|
+
|
|
146
|
+
// Execute the LLM call
|
|
147
|
+
const result = await generateText({
|
|
148
|
+
model,
|
|
149
|
+
system: agent.instructions || "You are a helpful AI assistant.",
|
|
150
|
+
messages: conversationMessages,
|
|
151
|
+
...(agent.temperature != null && { temperature: agent.temperature }),
|
|
152
|
+
...(agent.maxTokens != null && { maxTokens: agent.maxTokens }),
|
|
90
153
|
});
|
|
91
154
|
|
|
92
|
-
|
|
93
|
-
const responseContent = typeof result === "string"
|
|
94
|
-
? result
|
|
95
|
-
: result.text || result.content || JSON.stringify(result);
|
|
155
|
+
const responseContent = result.text;
|
|
96
156
|
|
|
97
157
|
// Add assistant message to thread
|
|
98
158
|
await ctx.runMutation(api.messages.add, {
|
|
@@ -107,40 +167,66 @@ export const executeAgent = action({
|
|
|
107
167
|
status: "completed",
|
|
108
168
|
});
|
|
109
169
|
|
|
110
|
-
//
|
|
111
|
-
|
|
170
|
+
// Build usage data
|
|
171
|
+
const usage = result.usage
|
|
172
|
+
? {
|
|
173
|
+
promptTokens: result.usage.promptTokens || 0,
|
|
174
|
+
completionTokens: result.usage.completionTokens || 0,
|
|
175
|
+
totalTokens:
|
|
176
|
+
(result.usage.promptTokens || 0) +
|
|
177
|
+
(result.usage.completionTokens || 0),
|
|
178
|
+
}
|
|
179
|
+
: undefined;
|
|
180
|
+
|
|
181
|
+
// Record usage
|
|
182
|
+
if (usage) {
|
|
112
183
|
await ctx.runMutation(api.usage.record, {
|
|
113
184
|
agentId: args.agentId,
|
|
114
185
|
sessionId,
|
|
115
|
-
provider: agent.provider,
|
|
116
|
-
model: agent.model,
|
|
117
|
-
promptTokens:
|
|
118
|
-
completionTokens:
|
|
119
|
-
totalTokens:
|
|
120
|
-
cost: result.usage.cost,
|
|
186
|
+
provider: agent.provider || "openrouter",
|
|
187
|
+
model: agent.model || "unknown",
|
|
188
|
+
promptTokens: usage.promptTokens,
|
|
189
|
+
completionTokens: usage.completionTokens,
|
|
190
|
+
totalTokens: usage.totalTokens,
|
|
121
191
|
userId: args.userId,
|
|
122
192
|
});
|
|
123
193
|
}
|
|
124
194
|
|
|
125
195
|
return {
|
|
126
196
|
success: true,
|
|
127
|
-
threadId,
|
|
197
|
+
threadId: threadId as string,
|
|
128
198
|
sessionId,
|
|
129
199
|
response: responseContent,
|
|
130
|
-
usage
|
|
200
|
+
usage,
|
|
131
201
|
};
|
|
132
|
-
} catch (error:
|
|
202
|
+
} catch (error: unknown) {
|
|
203
|
+
const errorMessage =
|
|
204
|
+
error instanceof Error ? error.message : String(error);
|
|
205
|
+
|
|
133
206
|
// Update session status to error
|
|
134
207
|
await ctx.runMutation(api.sessions.updateStatus, {
|
|
135
208
|
sessionId,
|
|
136
209
|
status: "error",
|
|
137
210
|
});
|
|
138
211
|
|
|
139
|
-
// Add error message
|
|
212
|
+
// Add error message to thread
|
|
140
213
|
await ctx.runMutation(api.messages.add, {
|
|
141
214
|
threadId,
|
|
142
215
|
role: "assistant",
|
|
143
|
-
content: `Error: ${
|
|
216
|
+
content: `Error: ${errorMessage}`,
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Log the error
|
|
220
|
+
await ctx.runMutation(api.logs.add, {
|
|
221
|
+
level: "error",
|
|
222
|
+
source: "mastraIntegration",
|
|
223
|
+
message: `Agent execution failed: ${errorMessage}`,
|
|
224
|
+
metadata: {
|
|
225
|
+
agentId: args.agentId,
|
|
226
|
+
threadId,
|
|
227
|
+
sessionId,
|
|
228
|
+
},
|
|
229
|
+
userId: args.userId,
|
|
144
230
|
});
|
|
145
231
|
|
|
146
232
|
throw error;
|
|
@@ -148,7 +234,12 @@ export const executeAgent = action({
|
|
|
148
234
|
},
|
|
149
235
|
});
|
|
150
236
|
|
|
151
|
-
|
|
237
|
+
/**
|
|
238
|
+
* Stream agent response (placeholder — streaming requires SSE/WebSocket).
|
|
239
|
+
*
|
|
240
|
+
* For now, this falls back to non-streaming execution.
|
|
241
|
+
* Full streaming support will be added via Convex HTTP actions + SSE.
|
|
242
|
+
*/
|
|
152
243
|
export const streamAgent = action({
|
|
153
244
|
args: {
|
|
154
245
|
agentId: v.string(),
|
|
@@ -156,27 +247,32 @@ export const streamAgent = action({
|
|
|
156
247
|
threadId: v.id("threads"),
|
|
157
248
|
userId: v.optional(v.string()),
|
|
158
249
|
},
|
|
159
|
-
handler: async (ctx, args) => {
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
|
|
250
|
+
handler: async (ctx, args): Promise<{ success: boolean; message: string }> => {
|
|
251
|
+
// Fall back to non-streaming execution
|
|
252
|
+
const result = await ctx.runAction(api.mastraIntegration.executeAgent, {
|
|
253
|
+
agentId: args.agentId,
|
|
254
|
+
prompt: args.prompt,
|
|
255
|
+
threadId: args.threadId,
|
|
256
|
+
userId: args.userId,
|
|
257
|
+
});
|
|
258
|
+
|
|
163
259
|
return {
|
|
164
|
-
success:
|
|
165
|
-
message:
|
|
260
|
+
success: result.success,
|
|
261
|
+
message: result.response,
|
|
166
262
|
};
|
|
167
263
|
},
|
|
168
264
|
});
|
|
169
265
|
|
|
170
|
-
|
|
266
|
+
/**
|
|
267
|
+
* Execute workflow with multiple agents (placeholder).
|
|
268
|
+
*/
|
|
171
269
|
export const executeWorkflow = action({
|
|
172
270
|
args: {
|
|
173
271
|
workflowId: v.string(),
|
|
174
272
|
input: v.any(),
|
|
175
273
|
userId: v.optional(v.string()),
|
|
176
274
|
},
|
|
177
|
-
handler: async (
|
|
178
|
-
// Placeholder for workflow execution
|
|
179
|
-
// This would orchestrate multiple agents in sequence or parallel
|
|
275
|
+
handler: async (_ctx, _args): Promise<{ success: boolean; message: string }> => {
|
|
180
276
|
return {
|
|
181
277
|
success: true,
|
|
182
278
|
message: "Workflow execution coming soon",
|