@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.
@@ -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
- * Mastra Integration Actions for Convex
7
- *
8
- * These actions run in the Node.js runtime and can use Mastra
9
- * to execute agents and manage workflows.
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
- // Action: Execute agent with Mastra
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: "dashboard",
126
+ channel: "api",
53
127
  });
54
128
 
55
129
  try {
56
- // Import Mastra dynamically (Node.js runtime)
57
- // @ts-expect-error - Mastra is installed at runtime in the user's project
58
- const { Agent } = await import("@mastra/core/agent");
59
-
60
- // Format model string for Mastra
61
- const modelString = agent.model.includes("/")
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
- // Build context from message history
81
- const context = messages
82
- .slice(-10) // Last 10 messages for context
83
- .map((m: { role: string; content: string }) => `${m.role}: ${m.content}`)
84
- .join("\n");
85
-
86
- // Execute agent
87
- const result: any = await mastraAgent.generate(args.prompt, {
88
- ...(args.stream && { stream: args.stream }),
89
- context: context || undefined,
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
- // Extract response content
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
- // Record usage (if available in result)
111
- if (result.usage) {
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: result.usage.promptTokens || 0,
118
- completionTokens: result.usage.completionTokens || 0,
119
- totalTokens: result.usage.totalTokens || 0,
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: result.usage,
200
+ usage,
131
201
  };
132
- } catch (error: any) {
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: ${error.message}`,
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
- // Action: Stream agent response
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
- // Similar to executeAgent but with streaming support
161
- // This would require WebSocket or SSE implementation
162
- // For now, return a placeholder
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: true,
165
- message: "Streaming support coming soon",
260
+ success: result.success,
261
+ message: result.response,
166
262
  };
167
263
  },
168
264
  });
169
265
 
170
- // Action: Execute workflow with multiple agents
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 (ctx, args) => {
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",