@muxoai/platform 1.2.3 → 1.3.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.
Files changed (41) hide show
  1. package/.agents/plugins/marketplace.json +1 -1
  2. package/.claude-plugin/marketplace.json +19 -4
  3. package/.codex/config.toml +9 -2
  4. package/README.md +72 -28
  5. package/dist/cli.js +1047 -43
  6. package/dist/engine.d.ts +29 -0
  7. package/dist/engine.js +221 -16
  8. package/dist/mcp.d.ts +6 -1
  9. package/dist/mcp.js +120 -26
  10. package/dist/nl.d.ts +34 -0
  11. package/dist/nl.js +601 -0
  12. package/dist/providers/composio-instant.d.ts +1 -0
  13. package/dist/providers/composio-instant.js +6 -5
  14. package/dist/providers/composio-sdk.d.ts +1 -0
  15. package/dist/providers/composio-sdk.js +6 -2
  16. package/dist/providers/stripe-billing.d.ts +13 -3
  17. package/dist/providers/stripe-billing.js +250 -36
  18. package/dist/providers/stripe-projects.js +61 -4
  19. package/dist/providers/supermemory.d.ts +2 -0
  20. package/dist/providers/supermemory.js +58 -40
  21. package/dist/sdk.d.ts +77 -1
  22. package/dist/sdk.js +46 -0
  23. package/dist/server.js +202 -29
  24. package/dist/store.d.ts +19 -2
  25. package/dist/store.js +216 -11
  26. package/dist/types.d.ts +14 -2
  27. package/dist/ui/console-html.js +329 -119
  28. package/package.json +1 -1
  29. package/plugins/muxo/.app.json +1 -1
  30. package/plugins/muxo/.claude-plugin/plugin.json +2 -2
  31. package/plugins/muxo/.codex-plugin/plugin.json +7 -5
  32. package/plugins/muxo/.mcp.json +1 -33
  33. package/plugins/muxo/assets/logo.png +0 -0
  34. package/plugins/muxo/hooks/session-start.sh +1 -1
  35. package/plugins/muxo/plugin.json +63 -6
  36. package/plugins/muxo/skills/muxo/SKILL.md +92 -14
  37. package/plugins/muxo/skills/muxo/agents/openai.yaml +12 -0
  38. package/plugins/muxo/skills/muxo/references/cli.md +4 -2
  39. package/plugins/muxo/skills/muxo/references/mcp.md +15 -9
  40. package/plugins/muxo/skills/setup/SKILL.md +8 -2
  41. package/plugins/muxo/skills/setup/agents/openai.yaml +12 -0
package/dist/nl.js ADDED
@@ -0,0 +1,601 @@
1
+ function extractUrlFromText(text) {
2
+ const fullMatch = text.match(/https?:\/\/[^\s"'<>]+/i);
3
+ if (fullMatch) {
4
+ return fullMatch[0].replace(/[.,;!?)]+$/, "");
5
+ }
6
+ const domainMatch = text.match(/\b((?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+(?:ai|com|io|dev|org|net|app|co|xyz)(?:\/[^\s"'<>]*)?)\b/i);
7
+ if (domainMatch && !text.includes(`@${domainMatch[1]}`)) {
8
+ return `https://${domainMatch[1].replace(/[.,;!?)]+$/, "")}`;
9
+ }
10
+ return null;
11
+ }
12
+ function extractEmailFromText(text) {
13
+ const m = text.match(/\b([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})\b/);
14
+ return m ? m[1] : null;
15
+ }
16
+ function extractNumberAfterKeyword(text, keywords, fallback) {
17
+ for (const kw of keywords) {
18
+ const reAfter = new RegExp(`\\b${kw}\\s+(?:of\\s+|to\\s+|=\\s*)?(\\d[\\d,]*)`, "i");
19
+ const m1 = text.match(reAfter);
20
+ if (m1) {
21
+ const n = Number(m1[1].replace(/,/g, ""));
22
+ if (!Number.isNaN(n) && n > 0)
23
+ return n;
24
+ }
25
+ const reBefore = new RegExp(`(\\d[\\d,]*)\\s+${kw}\\b`, "i");
26
+ const m2 = text.match(reBefore);
27
+ if (m2) {
28
+ const n = Number(m2[1].replace(/,/g, ""));
29
+ if (!Number.isNaN(n) && n > 0)
30
+ return n;
31
+ }
32
+ }
33
+ return fallback;
34
+ }
35
+ export function extractParametersFromNaturalLanguage(prompt, tool, explicitInput) {
36
+ const input = { ...(explicitInput || {}) };
37
+ const slug = tool.slug;
38
+ const text = prompt.trim();
39
+ if (slug === "instant.seo.audit") {
40
+ if (!input.url) {
41
+ const url = extractUrlFromText(text);
42
+ if (url)
43
+ input.url = url;
44
+ }
45
+ if (input.checkBrokenLinks === undefined) {
46
+ input.checkBrokenLinks = !/\b(skip|no)\s+broken\s+links\b/i.test(text);
47
+ }
48
+ }
49
+ else if (slug === "instant.web.crawl") {
50
+ if (!input.url) {
51
+ const url = extractUrlFromText(text);
52
+ if (url)
53
+ input.url = url;
54
+ }
55
+ if (input.maxPages === undefined) {
56
+ input.maxPages = extractNumberAfterKeyword(text, ["maxPages", "pages", "limit"], 5);
57
+ }
58
+ if (input.maxDepth === undefined) {
59
+ input.maxDepth = extractNumberAfterKeyword(text, ["maxDepth", "depth"], 2);
60
+ }
61
+ }
62
+ else if (slug === "instant.web.scrape") {
63
+ if (!input.url) {
64
+ const url = extractUrlFromText(text);
65
+ if (url)
66
+ input.url = url;
67
+ }
68
+ if (!input.formats) {
69
+ input.formats = ["markdown"];
70
+ }
71
+ }
72
+ else if (slug === "instant.web.search") {
73
+ if (!input.query) {
74
+ const cleaned = text
75
+ .replace(/^(?:please\s+)?(?:can\s+you\s+)?(?:search\s+(?:the\s+)?web\s+(?:for|about)|search\s+(?:for|about)|find\s+(?:info|information|articles)\s+(?:on|about|for)|google|lookup|look\s+up)\s+/i, "")
76
+ .trim();
77
+ input.query = cleaned || text;
78
+ }
79
+ if (input.numResults === undefined) {
80
+ input.numResults = extractNumberAfterKeyword(text, ["results", "limit", "top"], 5);
81
+ }
82
+ }
83
+ else if (slug === "instant.code.execute" || slug === "instant.composio.workbench") {
84
+ if (!input.language && slug === "instant.code.execute") {
85
+ input.language = /\b(javascript|js|node|typescript|ts)\b/i.test(text)
86
+ ? "javascript"
87
+ : "python";
88
+ }
89
+ const codeBlock = text.match(/```(?:\w+)?\n([\s\S]*?)```/);
90
+ const inlineCode = text.match(/`([^`]+)`/);
91
+ const extractedCode = codeBlock
92
+ ? codeBlock[1].trim()
93
+ : inlineCode
94
+ ? inlineCode[1].trim()
95
+ : text;
96
+ if (slug === "instant.composio.workbench") {
97
+ if (!input.code_to_execute) {
98
+ input.code_to_execute = String(input.code || extractedCode);
99
+ }
100
+ if (!input.thought) {
101
+ input.thought = `Natural language workbench execution: ${text.slice(0, 80)}`;
102
+ }
103
+ }
104
+ else if (!input.code) {
105
+ input.code = extractedCode;
106
+ }
107
+ }
108
+ else if (slug === "instant.github.repo_intel") {
109
+ const repoMatch = text.match(/\b([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+)\b/);
110
+ if (repoMatch && !repoMatch[0].includes(".ai") && !repoMatch[0].includes(".com")) {
111
+ if (!input.owner)
112
+ input.owner = repoMatch[1];
113
+ if (!input.repo)
114
+ input.repo = repoMatch[2];
115
+ }
116
+ }
117
+ else if (slug.startsWith("resource.")) {
118
+ if (!input.resource_name && !input.name) {
119
+ const namedMatch = text.match(/\b(?:named|called|name|resource)\s+["']?([a-zA-Z0-9_-]+)["']?/i);
120
+ const defaultPrefix = slug.split(".")[1] || "app";
121
+ const resolvedName = namedMatch ? namedMatch[1] : `${defaultPrefix}-main`;
122
+ input.resource_name = resolvedName;
123
+ }
124
+ else if (input.name && !input.resource_name) {
125
+ input.resource_name = input.name;
126
+ }
127
+ }
128
+ else if (slug === "billing.credits.topup") {
129
+ if (input.credits === undefined) {
130
+ input.credits = extractNumberAfterKeyword(text, ["credits", "credit", "pack", "topup", "top-up", "add"], 1000);
131
+ }
132
+ if (!input.description) {
133
+ input.description = `Natural language top-up: ${text.slice(0, 80)}`;
134
+ }
135
+ }
136
+ else if (slug === "billing.subscription.upgrade") {
137
+ if (!input.tier) {
138
+ input.tier = "starter";
139
+ }
140
+ }
141
+ else if (slug === "billing.checkout.create") {
142
+ if (!input.kind) {
143
+ input.kind = /\b(starter|subscription|upgrade|monthly|plan)\b/i.test(text)
144
+ ? "starter_subscription"
145
+ : "credit_topup";
146
+ }
147
+ }
148
+ else if (slug === "app.gmail.send_email") {
149
+ if (!input.recipient_email) {
150
+ const email = extractEmailFromText(text);
151
+ if (email)
152
+ input.recipient_email = email;
153
+ }
154
+ if (!input.subject) {
155
+ const subjMatch = text.match(/\bsubject\s*[:=]?\s*["']([^"']+)["']|\bsubject\s+([^\n]+?)(?:\s+body\b|\s+saying\b|$)/i);
156
+ input.subject = subjMatch
157
+ ? (subjMatch[1] || subjMatch[2]).trim()
158
+ : "Message from Muxo Agent";
159
+ }
160
+ if (!input.body) {
161
+ const bodyMatch = text.match(/\b(?:body|saying|message)\s*[:=]?\s*["']?([\s\S]+?)["']?$/i);
162
+ input.body = bodyMatch ? bodyMatch[1].trim() : text;
163
+ }
164
+ }
165
+ else if (slug === "app.gmail.fetch_emails" || slug === "app.gmail.list_messages") {
166
+ if (!input.query) {
167
+ const qMatch = text.match(/\b(?:matching|query|from|about)\s+["']?([^"'\n]+)["']?/i);
168
+ if (qMatch)
169
+ input.query = qMatch[1].trim();
170
+ }
171
+ if (input.max_results === undefined) {
172
+ input.max_results = extractNumberAfterKeyword(text, ["emails", "messages", "limit", "last"], 10);
173
+ }
174
+ }
175
+ else if (slug === "app.slack.send_message" || slug === "app.slack.post_message") {
176
+ if (!input.channel) {
177
+ const chMatch = text.match(/#([a-zA-Z0-9_-]+)|\bchannel\s+([a-zA-Z0-9_-]+)/i);
178
+ input.channel = chMatch ? chMatch[1] || chMatch[2] : "general";
179
+ }
180
+ if (!input.text) {
181
+ const msgMatch = text.match(/\b(?:saying|message|text)\s*[:=]?\s*["']?([\s\S]+?)["']?$/i);
182
+ input.text = msgMatch ? msgMatch[1].trim() : text;
183
+ }
184
+ }
185
+ else if (slug === "app.github.create_issue" || slug === "app.github.list_pull_requests" || slug === "app.github.list_issues") {
186
+ const repoMatch = text.match(/\b([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+)\b/);
187
+ if (repoMatch && !repoMatch[0].includes(".ai") && !repoMatch[0].includes(".com")) {
188
+ if (!input.owner)
189
+ input.owner = repoMatch[1];
190
+ if (!input.repo)
191
+ input.repo = repoMatch[2];
192
+ }
193
+ if (slug === "app.github.create_issue" && !input.title) {
194
+ const titleMatch = text.match(/\b(?:titled|title)\s*[:=]?\s*["']?([^"'\n]+)["']?/i);
195
+ input.title = titleMatch ? titleMatch[1].trim() : text.slice(0, 80);
196
+ }
197
+ }
198
+ else if (slug === "app.notion.search_pages") {
199
+ if (!input.query) {
200
+ const cleaned = text
201
+ .replace(/^(?:search\s+notion\s+(?:pages\s+)?(?:for)?|find\s+in\s+notion)\s*/i, "")
202
+ .trim();
203
+ input.query = cleaned || text;
204
+ }
205
+ }
206
+ else if (slug === "instant.dns.lookup") {
207
+ if (!input.domain) {
208
+ const domainMatch = text.match(/\b((?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+(?:ai|com|io|dev|org|net|app|co|xyz))\b/i);
209
+ if (domainMatch) {
210
+ input.domain = domainMatch[1].toLowerCase();
211
+ }
212
+ else if (typeof input.hostname === "string") {
213
+ input.domain = input.hostname;
214
+ }
215
+ }
216
+ if (!input.type) {
217
+ const typeMatch = text.match(/\b(AAAA|CNAME|TXT|MX|NS|A)\b/i);
218
+ input.type = typeMatch ? typeMatch[1].toUpperCase() : "A";
219
+ }
220
+ }
221
+ else if (slug === "instant.hackernews.top_stories") {
222
+ if (input.limit === undefined) {
223
+ input.limit = extractNumberAfterKeyword(text, ["top", "stories", "items", "limit", "posts"], 5);
224
+ }
225
+ }
226
+ const schemaReq = Array.isArray(tool.inputSchema?.required)
227
+ ? tool.inputSchema.required
228
+ : [];
229
+ const missingInputs = schemaReq.filter((k) => input[k] === undefined || input[k] === null || input[k] === "");
230
+ return { input, missingInputs };
231
+ }
232
+ function scoreToolForPrompt(prompt, tool) {
233
+ const q = prompt.trim().toLowerCase();
234
+ if (!q)
235
+ return 0;
236
+ if (q === tool.slug.toLowerCase() || q === tool.upstreamSlug.toLowerCase()) {
237
+ return 1000;
238
+ }
239
+ let score = 0;
240
+ const slug = tool.slug.toLowerCase();
241
+ // High-signal intent rules
242
+ if (/\b(seo|meta\s+tag|open\s*graph|json-ld|jsonld|canonical|heading\s+hierarchy|audit\s+site|audit\s+page|seo\s+audit)\b/i.test(q)) {
243
+ if (slug === "instant.seo.audit")
244
+ score += 120;
245
+ }
246
+ if (/\b(crawl|spider|multi-page|sitemap|discover\s+links|all\s+pages)\b/i.test(q)) {
247
+ if (slug === "instant.web.crawl")
248
+ score += 110;
249
+ }
250
+ if (/\b(scrape|extract\s+markdown|fetch\s+page|read\s+url|read\s+webpage|scrape\s+url)\b/i.test(q)) {
251
+ if (slug === "instant.web.scrape")
252
+ score += 110;
253
+ }
254
+ const isExplicitWebSearch = /\b(search\s+(?:the\s+)?web|web\s+search|search\s+online|search\s+(?:for|about)|look\s+up|find\s+(?:news|info|information|articles)|exa|google)\b/i.test(q);
255
+ const hasProvisionVerb = /\b(provision|create|deploy|spin\s+up|setup|set\s+up|add\s+resource|new\s+db|new\s+database|database|db)\b/i.test(q);
256
+ if (isExplicitWebSearch) {
257
+ if (slug === "instant.web.search")
258
+ score += 155;
259
+ }
260
+ if (/\b(run\s+python|execute\s+code|run\s+script|sandbox|workbench|calculate|e2b)\b/i.test(q)) {
261
+ if (slug === "instant.code.execute" || slug === "instant.composio.workbench")
262
+ score += 125;
263
+ }
264
+ if (/\b(repo\s+intel|github\s+repo|inspect\s+repo|repository\s+intel|stars\s+and\s+forks)\b/i.test(q)) {
265
+ if (slug === "instant.github.repo_intel")
266
+ score += 135;
267
+ }
268
+ if (/\b(top\s*up|add\s+credits|buy\s+credits|refill\s+credits|more\s+credits|\d+\s+credits)\b/i.test(q)) {
269
+ if (slug === "billing.credits.topup")
270
+ score += 130;
271
+ }
272
+ if (/\b(upgrade\s+to\s+starter|upgrade\s+plan|starter\s+plan|starter\s+tier|upgrade\s+subscription|upgrade\s+workspace)\b/i.test(q)) {
273
+ if (slug === "billing.subscription.upgrade")
274
+ score += 130;
275
+ }
276
+ if (/\b(stripe\s+checkout|checkout\s+session|payment\s+link|billing\s+link)\b/i.test(q)) {
277
+ if (slug === "billing.checkout.create")
278
+ score += 125;
279
+ }
280
+ if (!isExplicitWebSearch || hasProvisionVerb) {
281
+ if (/\b(turso|libsql|sqlite\s+database|sqlite\s+db)\b/i.test(q)) {
282
+ if (slug.startsWith("resource.turso."))
283
+ score += 120;
284
+ if (slug.includes("database"))
285
+ score += 15;
286
+ }
287
+ if (/\b(neon|postgres|postgresql|pg\s+database)\b/i.test(q)) {
288
+ if (slug.startsWith("resource.neon."))
289
+ score += 120;
290
+ }
291
+ if (/\b(upstash|redis|cache\s+store|kv\s+store)\b/i.test(q)) {
292
+ if (slug.startsWith("resource.upstash."))
293
+ score += 120;
294
+ }
295
+ if (/\b(supabase)\b/i.test(q)) {
296
+ if (slug.startsWith("resource.supabase."))
297
+ score += 120;
298
+ }
299
+ if (/\b(mongodb|mongo)\b/i.test(q)) {
300
+ if (slug.startsWith("resource.mongodb."))
301
+ score += 120;
302
+ }
303
+ }
304
+ if (/\b(hacker\s*news|hn\s+stories|top\s+stories)\b/i.test(q)) {
305
+ if (slug === "instant.hackernews.top_stories")
306
+ score += 125;
307
+ }
308
+ if (/\b(dns|doh|txt\s+records?|mx\s+records?|a\s+records?|cname|nameserver|lookup\s+dns|check\s+dns)\b/i.test(q)) {
309
+ if (slug === "instant.dns.lookup")
310
+ score += 135;
311
+ }
312
+ if (/\b(send\s+an?\s+email|send\s+email|email\s+to|compose\s+email)\b/i.test(q)) {
313
+ if (slug === "app.gmail.send_email")
314
+ score += 120;
315
+ }
316
+ if (/\b(list\s+emails|read\s+emails|check\s+inbox|recent\s+emails|list\s+messages|fetch\s+emails|gmail\s+inbox)\b/i.test(q)) {
317
+ if (slug === "app.gmail.fetch_emails" || slug === "app.gmail.list_messages")
318
+ score += 115;
319
+ }
320
+ if (/\b(post\s+to\s+slack|send\s+slack|slack\s+message|message\s+#)\b/i.test(q)) {
321
+ if (slug === "app.slack.send_message" || slug === "app.slack.post_message")
322
+ score += 120;
323
+ }
324
+ if (/\b(create\s+(?:a\s+)?github\s+issue|open\s+(?:a\s+)?github\s+issue|file\s+an?\s+issue)\b/i.test(q)) {
325
+ if (slug === "app.github.create_issue")
326
+ score += 120;
327
+ }
328
+ if (/\b(list\s+github\s+(?:issues|prs|pull\s+requests)|check\s+github|pull\s+requests)\b/i.test(q)) {
329
+ if (slug === "app.github.list_pull_requests" || slug === "app.github.list_issues")
330
+ score += 110;
331
+ }
332
+ if (/\b(notion\s+page|search\s+notion)\b/i.test(q)) {
333
+ if (slug === "app.notion.search_pages")
334
+ score += 115;
335
+ }
336
+ if (/\b(linear\s+issue|create\s+linear)\b/i.test(q)) {
337
+ if (slug === "app.linear.create_issue")
338
+ score += 115;
339
+ }
340
+ if (/\b(google\s+calendar|schedule\s+meeting|list\s+events|calendar\s+events)\b/i.test(q)) {
341
+ if (slug.includes("calendar") || slug.includes("gcal"))
342
+ score += 110;
343
+ }
344
+ // Generic token overlap fallback
345
+ const haystack = `${tool.slug} ${tool.name} ${tool.description} ${tool.providerSlug} ${(tool.categories || []).join(" ")}`.toLowerCase();
346
+ const tokens = q.split(/[^a-z0-9_.-]+/).filter((t) => t.length >= 2);
347
+ for (const token of tokens) {
348
+ if (slug.includes(token))
349
+ score += 18;
350
+ else if (tool.providerSlug.toLowerCase().includes(token))
351
+ score += 14;
352
+ else if (haystack.includes(token))
353
+ score += 6;
354
+ }
355
+ // If prompt contains a URL and no specific verb was matched, default toward scrape/audit
356
+ if (extractUrlFromText(prompt) && score < 40) {
357
+ if (slug === "instant.web.scrape")
358
+ score += 45;
359
+ if (slug === "instant.seo.audit")
360
+ score += 40;
361
+ if (slug === "instant.web.crawl")
362
+ score += 38;
363
+ }
364
+ return score;
365
+ }
366
+ async function tryByokLlmClassification(params) {
367
+ const openaiKey = params.byokKeys.OPENAI_API_KEY || "";
368
+ const openrouterKey = params.byokKeys.OPENROUTER_API_KEY || "";
369
+ const anthropicKey = params.byokKeys.ANTHROPIC_API_KEY || "";
370
+ if (!openaiKey && !openrouterKey && !anthropicKey) {
371
+ return null;
372
+ }
373
+ const catalogBrief = params.tools
374
+ .slice(0, 35)
375
+ .map((t) => `- ${t.slug} (${t.riskLevel}, ${t.creditCost}cr): ${t.description} | schema keys: ${Object.keys(t.inputSchema?.properties || {}).join(", ")}`)
376
+ .join("\n");
377
+ const systemPrompt = [
378
+ "You are the Muxo Natural Language Intent Router.",
379
+ "Map the user's natural language request to the single best toolSlug from the catalog below and extract its JSON arguments.",
380
+ "Return ONLY valid JSON with shape: {\"toolSlug\": string, \"input\": object, \"reason\": string}.",
381
+ "Catalog:",
382
+ catalogBrief,
383
+ ].join("\n");
384
+ try {
385
+ if (openaiKey || openrouterKey) {
386
+ const isOpenRouter = !openaiKey && Boolean(openrouterKey);
387
+ const endpoint = isOpenRouter
388
+ ? "https://openrouter.ai/api/v1/chat/completions"
389
+ : "https://api.openai.com/v1/chat/completions";
390
+ const res = await fetch(endpoint, {
391
+ method: "POST",
392
+ headers: {
393
+ Authorization: `Bearer ${openaiKey || openrouterKey}`,
394
+ "Content-Type": "application/json",
395
+ },
396
+ body: JSON.stringify({
397
+ model: isOpenRouter ? "openai/gpt-4o-mini" : "gpt-4o-mini",
398
+ temperature: 0,
399
+ response_format: { type: "json_object" },
400
+ messages: [
401
+ { role: "system", content: systemPrompt },
402
+ { role: "user", content: params.prompt },
403
+ ],
404
+ }),
405
+ signal: AbortSignal.timeout(4500),
406
+ });
407
+ if (res.ok) {
408
+ const data = (await res.json());
409
+ const content = data?.choices?.[0]?.message?.content;
410
+ if (typeof content === "string") {
411
+ const parsed = JSON.parse(content);
412
+ if (parsed && typeof parsed.toolSlug === "string") {
413
+ return {
414
+ toolSlug: parsed.toolSlug,
415
+ input: parsed.input && typeof parsed.input === "object"
416
+ ? parsed.input
417
+ : {},
418
+ reason: String(parsed.reason || params.prompt),
419
+ providerUsed: isOpenRouter ? "openrouter" : "openai",
420
+ };
421
+ }
422
+ }
423
+ }
424
+ }
425
+ else if (anthropicKey) {
426
+ const res = await fetch("https://api.anthropic.com/v1/messages", {
427
+ method: "POST",
428
+ headers: {
429
+ "x-api-key": anthropicKey,
430
+ "anthropic-version": "2023-06-01",
431
+ "Content-Type": "application/json",
432
+ },
433
+ body: JSON.stringify({
434
+ model: "claude-3-5-haiku-latest",
435
+ max_tokens: 400,
436
+ system: systemPrompt,
437
+ messages: [{ role: "user", content: params.prompt }],
438
+ }),
439
+ signal: AbortSignal.timeout(4500),
440
+ });
441
+ if (res.ok) {
442
+ const data = (await res.json());
443
+ const text = data?.content?.[0]?.text;
444
+ if (typeof text === "string") {
445
+ const jsonMatch = text.match(/\{[\s\S]*\}/);
446
+ if (jsonMatch) {
447
+ const parsed = JSON.parse(jsonMatch[0]);
448
+ if (parsed && typeof parsed.toolSlug === "string") {
449
+ return {
450
+ toolSlug: parsed.toolSlug,
451
+ input: parsed.input && typeof parsed.input === "object"
452
+ ? parsed.input
453
+ : {},
454
+ reason: String(parsed.reason || params.prompt),
455
+ providerUsed: "anthropic",
456
+ };
457
+ }
458
+ }
459
+ }
460
+ }
461
+ }
462
+ }
463
+ catch {
464
+ // Fall back to deterministic semantic router
465
+ }
466
+ return null;
467
+ }
468
+ export async function resolveNaturalLanguageIntent(params) {
469
+ const prompt = params.prompt.trim();
470
+ const tools = params.tools;
471
+ // 1. Check exact slug match first
472
+ const exactTool = tools.find((t) => t.slug.toLowerCase() === prompt.toLowerCase() ||
473
+ t.upstreamSlug.toLowerCase() === prompt.toLowerCase());
474
+ if (exactTool) {
475
+ const { input, missingInputs } = extractParametersFromNaturalLanguage(prompt, exactTool, params.explicitInput);
476
+ const needsConnection = exactTool.requiresAuth &&
477
+ !(params.activeConnectionProviders?.has(exactTool.providerSlug) ?? false);
478
+ return {
479
+ resolved: true,
480
+ tool: exactTool,
481
+ toolSlug: exactTool.slug,
482
+ input,
483
+ extractedInput: input,
484
+ reason: `Direct execution of ${exactTool.slug}`,
485
+ confidence: 1.0,
486
+ resolutionMethod: "exact_slug",
487
+ byokUsed: null,
488
+ humanSummary: `${exactTool.name} (${exactTool.slug})`,
489
+ missingInputs,
490
+ needsConnection,
491
+ suggestedByokProvider: suggestByokProviderForTool(exactTool),
492
+ alternatives: [],
493
+ };
494
+ }
495
+ // 2. Try BYOK LLM router if a user LLM key is configured in the workspace vault
496
+ if (params.preferByokLlm !== false && params.byokKeys) {
497
+ const llmRes = await tryByokLlmClassification({
498
+ prompt,
499
+ tools,
500
+ byokKeys: params.byokKeys,
501
+ });
502
+ if (llmRes) {
503
+ const matchedTool = tools.find((t) => t.slug.toLowerCase() === llmRes.toolSlug.toLowerCase());
504
+ if (matchedTool) {
505
+ const mergedExplicit = {
506
+ ...llmRes.input,
507
+ ...(params.explicitInput || {}),
508
+ };
509
+ const { input, missingInputs } = extractParametersFromNaturalLanguage(prompt, matchedTool, mergedExplicit);
510
+ const needsConnection = matchedTool.requiresAuth &&
511
+ !(params.activeConnectionProviders?.has(matchedTool.providerSlug) ?? false);
512
+ return {
513
+ resolved: true,
514
+ tool: matchedTool,
515
+ toolSlug: matchedTool.slug,
516
+ input,
517
+ extractedInput: input,
518
+ reason: llmRes.reason || prompt,
519
+ confidence: 0.96,
520
+ resolutionMethod: "byok_llm",
521
+ byokUsed: llmRes.providerUsed,
522
+ humanSummary: `${matchedTool.name} via ${matchedTool.slug}`,
523
+ missingInputs,
524
+ needsConnection,
525
+ suggestedByokProvider: suggestByokProviderForTool(matchedTool),
526
+ alternatives: [],
527
+ };
528
+ }
529
+ }
530
+ }
531
+ // 3. Deterministic Semantic + Regex Router (0 latency, works without LLM API keys)
532
+ const scored = tools
533
+ .map((tool) => ({ tool, score: scoreToolForPrompt(prompt, tool) }))
534
+ .filter((item) => item.score > 0)
535
+ .sort((a, b) => b.score - a.score);
536
+ const best = scored[0]?.tool || null;
537
+ const bestScore = scored[0]?.score || 0;
538
+ const alternatives = scored.slice(1, 5).map((s) => ({
539
+ slug: s.tool.slug,
540
+ name: s.tool.name,
541
+ riskLevel: s.tool.riskLevel,
542
+ creditCost: s.tool.creditCost,
543
+ }));
544
+ if (!best || bestScore < 12) {
545
+ return {
546
+ resolved: false,
547
+ tool: null,
548
+ toolSlug: null,
549
+ input: params.explicitInput || {},
550
+ extractedInput: params.explicitInput || {},
551
+ reason: prompt,
552
+ confidence: 0,
553
+ resolutionMethod: "semantic_router",
554
+ byokUsed: null,
555
+ humanSummary: `Could not confidently match "${prompt}" to a Muxo tool. Try specifying a tool keyword (e.g. "audit seo https://muxo.ai", "search web for AI agents", "top up 1000 credits", "create turso database").`,
556
+ missingInputs: [],
557
+ needsConnection: false,
558
+ suggestedByokProvider: "openai",
559
+ alternatives: tools.slice(0, 5).map((t) => ({
560
+ slug: t.slug,
561
+ name: t.name,
562
+ riskLevel: t.riskLevel,
563
+ creditCost: t.creditCost,
564
+ })),
565
+ };
566
+ }
567
+ const { input, missingInputs } = extractParametersFromNaturalLanguage(prompt, best, params.explicitInput);
568
+ const needsConnection = best.requiresAuth &&
569
+ !(params.activeConnectionProviders?.has(best.providerSlug) ?? false);
570
+ const confidence = Math.min(0.95, Math.round((bestScore / 130) * 100) / 100);
571
+ return {
572
+ resolved: true,
573
+ tool: best,
574
+ toolSlug: best.slug,
575
+ input,
576
+ extractedInput: input,
577
+ reason: prompt,
578
+ confidence,
579
+ resolutionMethod: "semantic_router",
580
+ byokUsed: null,
581
+ humanSummary: `${best.name} (${best.slug})`,
582
+ missingInputs,
583
+ needsConnection,
584
+ suggestedByokProvider: suggestByokProviderForTool(best),
585
+ alternatives,
586
+ };
587
+ }
588
+ function suggestByokProviderForTool(tool) {
589
+ if (tool.slug.startsWith("billing."))
590
+ return "stripe";
591
+ if (tool.lane === "composio_sdk")
592
+ return "composio";
593
+ if (tool.lane === "stripe_projects")
594
+ return "stripe";
595
+ if (tool.slug.includes("scrape") || tool.slug.includes("crawl") || tool.slug.includes("seo")) {
596
+ return "firecrawl";
597
+ }
598
+ if (tool.slug.includes("search"))
599
+ return "exa";
600
+ return "composio";
601
+ }
@@ -10,5 +10,6 @@ export declare class ComposioInstantAdapter {
10
10
  workspaceId: string;
11
11
  tool: MuxoTool;
12
12
  input: Record<string, unknown>;
13
+ composioApiKey?: string | null;
13
14
  }): Promise<Record<string, unknown>>;
14
15
  }
@@ -163,11 +163,11 @@ const INSTANT_TOOLS = [
163
163
  {
164
164
  slug: "instant.composio.workbench",
165
165
  upstreamSlug: "COMPOSIO_REMOTE_WORKBENCH",
166
- name: "Composio Instant: Remote Sandbox Workbench",
167
- description: "Execute isolated data processing, JSON transformation, or Python/JS analysis in Composio's remote sandbox workbench without OAuth.",
166
+ name: "Instant: Remote Sandbox Workbench",
167
+ description: "Execute isolated data processing, JSON transformation, or Python/JS analysis in Muxo's remote sandbox workbench without OAuth.",
168
168
  lane: "composio_instant",
169
169
  providerSlug: "composio",
170
- providerName: "Composio Workbench",
170
+ providerName: "Muxo Workbench",
171
171
  categories: ["sandbox", "compute", "analysis", "instant"],
172
172
  riskLevel: "write",
173
173
  requiresAuth: false,
@@ -211,13 +211,14 @@ export class ComposioInstantAdapter {
211
211
  }
212
212
  async executeTool(params) {
213
213
  const { tool, input } = params;
214
+ const activeComposioKey = params.composioApiKey || this.config.composioApiKey;
214
215
  // 1. Try Composio Instant / No-Auth tool execution first if applicable
215
- if (this.config.composioApiKey && tool.upstreamSlug.startsWith("COMPOSIO_")) {
216
+ if (activeComposioKey && tool.upstreamSlug.startsWith("COMPOSIO_")) {
216
217
  try {
217
218
  const res = await fetch(`${this.config.composioBaseUrl}/tools/execute/${encodeURIComponent(tool.upstreamSlug)}`, {
218
219
  method: "POST",
219
220
  headers: {
220
- "x-api-key": this.config.composioApiKey,
221
+ "x-api-key": activeComposioKey,
221
222
  "Content-Type": "application/json",
222
223
  },
223
224
  body: JSON.stringify({
@@ -37,5 +37,6 @@ export declare class ComposioSdkAdapter {
37
37
  tool: MuxoTool;
38
38
  input: Record<string, unknown>;
39
39
  connectedAccountId?: string | null;
40
+ composioApiKey?: string | null;
40
41
  }): Promise<Record<string, unknown>>;
41
42
  }
@@ -536,7 +536,8 @@ export class ComposioSdkAdapter {
536
536
  },
537
537
  };
538
538
  };
539
- if (!this.config.composioApiKey) {
539
+ const effectiveApiKey = params.composioApiKey || this.config.composioApiKey;
540
+ if (!effectiveApiKey) {
540
541
  return buildConnectedAccountFallback();
541
542
  }
542
543
  const body = {
@@ -549,7 +550,10 @@ export class ComposioSdkAdapter {
549
550
  try {
550
551
  const res = await fetch(`${this.config.composioBaseUrl}/tools/execute/${encodeURIComponent(params.tool.upstreamSlug)}`, {
551
552
  method: "POST",
552
- headers: this.headers,
553
+ headers: {
554
+ ...this.headers,
555
+ "x-api-key": effectiveApiKey,
556
+ },
553
557
  body: JSON.stringify(body),
554
558
  signal: AbortSignal.timeout(25000),
555
559
  });