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