botversion-sdk 1.0.4 → 1.0.6
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/bin/init.js +186 -140
- package/cli/detector.js +360 -173
- package/cli/generator.js +21 -479
- package/cli/prompts.js +40 -42
- package/cli/writer.js +119 -43
- package/client.js +33 -69
- package/index.js +54 -515
- package/interceptor.js +57 -132
- package/package.json +3 -1
- package/scanner.js +733 -28
package/interceptor.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
// botversion-sdk/interceptor.js
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
|
+
const reportedEndpoints = new Set();
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
7
|
* Attaches a middleware to the Express app that
|
|
6
8
|
* silently intercepts every request and reports
|
|
7
|
-
* new endpoints to BotVersion platform
|
|
9
|
+
* new endpoints to BotVersion platform.
|
|
10
|
+
* Auth and user context are handled client-side — not here.
|
|
8
11
|
*/
|
|
9
12
|
function attachInterceptor(app, client, options) {
|
|
10
13
|
options = options || {};
|
|
11
|
-
var getUserContext = options.getUserContext || null;
|
|
12
14
|
|
|
13
|
-
// Paths to always ignore
|
|
14
15
|
const ignorePaths = [
|
|
15
16
|
"/health",
|
|
16
17
|
"/favicon.ico",
|
|
@@ -19,12 +20,7 @@ function attachInterceptor(app, client, options) {
|
|
|
19
20
|
"/public",
|
|
20
21
|
].concat(options.exclude || []);
|
|
21
22
|
|
|
22
|
-
// Track which endpoints we have already reported
|
|
23
|
-
// so we don't spam the platform on every request
|
|
24
|
-
const reportedEndpoints = new Set();
|
|
25
|
-
|
|
26
23
|
app.use(function botVersionInterceptor(req, res, next) {
|
|
27
|
-
// Skip ignored paths
|
|
28
24
|
const path = req.path || req.url || "";
|
|
29
25
|
const shouldIgnore = ignorePaths.some(function (p) {
|
|
30
26
|
return path.startsWith(p);
|
|
@@ -34,21 +30,15 @@ function attachInterceptor(app, client, options) {
|
|
|
34
30
|
return next();
|
|
35
31
|
}
|
|
36
32
|
|
|
37
|
-
// Skip non-API paths if configured
|
|
38
33
|
if (options.apiPrefix && !path.startsWith(options.apiPrefix)) {
|
|
39
34
|
return next();
|
|
40
35
|
}
|
|
41
36
|
|
|
42
37
|
const method = req.method.toUpperCase();
|
|
43
|
-
|
|
44
|
-
// Normalize path — replace IDs with :param
|
|
45
38
|
const normalizedPath = normalizePath(path);
|
|
46
39
|
const endpointKey = method + ":" + normalizedPath;
|
|
47
40
|
|
|
48
|
-
// Only report if we haven't seen this endpoint before
|
|
49
41
|
const bodyStructure = buildBodyStructure(req.body);
|
|
50
|
-
console.log("[DEBUG] endpoint:", endpointKey);
|
|
51
|
-
console.log("[DEBUG] bodyStructure:", JSON.stringify(bodyStructure));
|
|
52
42
|
const bodyKey =
|
|
53
43
|
endpointKey +
|
|
54
44
|
":" +
|
|
@@ -59,7 +49,6 @@ function attachInterceptor(app, client, options) {
|
|
|
59
49
|
if (!reportedEndpoints.has(bodyKey)) {
|
|
60
50
|
reportedEndpoints.add(bodyKey);
|
|
61
51
|
|
|
62
|
-
// Convert to JSON Schema format so Gemini can read field names
|
|
63
52
|
const jsonSchema = bodyStructure
|
|
64
53
|
? {
|
|
65
54
|
type: "object",
|
|
@@ -78,9 +67,8 @@ function attachInterceptor(app, client, options) {
|
|
|
78
67
|
),
|
|
79
68
|
}
|
|
80
69
|
: null;
|
|
81
|
-
console.log("[DEBUG] jsonSchema:", JSON.stringify(jsonSchema));
|
|
82
70
|
|
|
83
|
-
// Report async —
|
|
71
|
+
// Report async — never block the request
|
|
84
72
|
setImmediate(function () {
|
|
85
73
|
client
|
|
86
74
|
.updateEndpoint({
|
|
@@ -90,65 +78,13 @@ function attachInterceptor(app, client, options) {
|
|
|
90
78
|
detectedBy: "runtime",
|
|
91
79
|
})
|
|
92
80
|
.catch(function (err) {
|
|
93
|
-
|
|
94
|
-
if (options.debug) {
|
|
95
|
-
console.warn(
|
|
96
|
-
"[BotVersion SDK] Failed to report endpoint:",
|
|
97
|
-
err.message,
|
|
98
|
-
);
|
|
99
|
-
}
|
|
81
|
+
console.error("[botversion] update_endpoint failed:", err.message);
|
|
100
82
|
});
|
|
101
83
|
});
|
|
102
84
|
}
|
|
103
85
|
|
|
104
86
|
next();
|
|
105
87
|
});
|
|
106
|
-
// Attached to app so SaaS owner can call it in their agent route
|
|
107
|
-
app.executeAgentCall = function (req, call) {
|
|
108
|
-
var userContext = getUserContext
|
|
109
|
-
? getUserContext(req)
|
|
110
|
-
: extractDefaultContext(req);
|
|
111
|
-
|
|
112
|
-
return client
|
|
113
|
-
.agentChat({
|
|
114
|
-
message: req.body.message,
|
|
115
|
-
conversationHistory: req.body.conversationHistory || [],
|
|
116
|
-
pageContext: req.body.pageContext || {},
|
|
117
|
-
userContext: userContext,
|
|
118
|
-
})
|
|
119
|
-
.then(function (response) {
|
|
120
|
-
// Handle plain chat/greeting responses from ai-response.js
|
|
121
|
-
if (response.answer) {
|
|
122
|
-
return { action: "RESPOND", message: response.answer };
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Handle agent action
|
|
126
|
-
if (response.action !== "EXECUTE_CALL") return response;
|
|
127
|
-
|
|
128
|
-
return makeLocalCall(req, response.call).then(function (result) {
|
|
129
|
-
return client
|
|
130
|
-
.agentToolResult(
|
|
131
|
-
response.sessionToken,
|
|
132
|
-
result,
|
|
133
|
-
response.sessionData,
|
|
134
|
-
)
|
|
135
|
-
.then(function (toolResponse) {
|
|
136
|
-
if (toolResponse.action === "EXECUTE_CALL") {
|
|
137
|
-
return makeLocalCall(req, toolResponse.call).then(
|
|
138
|
-
function (result2) {
|
|
139
|
-
return client.agentToolResult(
|
|
140
|
-
toolResponse.sessionToken,
|
|
141
|
-
result2,
|
|
142
|
-
toolResponse.sessionData,
|
|
143
|
-
);
|
|
144
|
-
},
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
return toolResponse;
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
};
|
|
152
88
|
}
|
|
153
89
|
|
|
154
90
|
/**
|
|
@@ -172,7 +108,7 @@ function normalizePath(path) {
|
|
|
172
108
|
// Numeric ID
|
|
173
109
|
if (/^\d+$/.test(segment)) return ":id";
|
|
174
110
|
|
|
175
|
-
// cuid pattern
|
|
111
|
+
// cuid pattern
|
|
176
112
|
if (/^c[a-z0-9]{20,}$/i.test(segment)) return ":id";
|
|
177
113
|
|
|
178
114
|
// MongoDB ObjectId
|
|
@@ -201,7 +137,6 @@ function buildBodyStructure(body) {
|
|
|
201
137
|
const structure = {};
|
|
202
138
|
|
|
203
139
|
Object.keys(body).forEach(function (key) {
|
|
204
|
-
// Never store sensitive values
|
|
205
140
|
const sensitiveKeys = [
|
|
206
141
|
"password",
|
|
207
142
|
"token",
|
|
@@ -237,69 +172,59 @@ function buildBodyStructure(body) {
|
|
|
237
172
|
return structure;
|
|
238
173
|
}
|
|
239
174
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
// Default userContext extraction — reads common patterns
|
|
247
|
-
// SaaS owner can override this with getUserContext option
|
|
248
|
-
function extractDefaultContext(req) {
|
|
249
|
-
var user = req.user || (req.session && req.session.user) || {};
|
|
250
|
-
return {
|
|
251
|
-
userId: user.id || user.userId || user._id || null,
|
|
252
|
-
email: user.email || null,
|
|
253
|
-
role: user.role || user.userRole || null,
|
|
254
|
-
name: user.name || user.username || null,
|
|
255
|
-
};
|
|
256
|
-
}
|
|
175
|
+
function attachNextJsInterceptor(client, options) {
|
|
176
|
+
try {
|
|
177
|
+
const http = require("http");
|
|
178
|
+
const originalEmit = http.Server.prototype.emit;
|
|
257
179
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
var http = require("http");
|
|
263
|
-
var https = require("https");
|
|
264
|
-
|
|
265
|
-
var body = call.body ? JSON.stringify(call.body) : null;
|
|
266
|
-
var isHttps = req.protocol === "https" || req.secure;
|
|
267
|
-
var lib = isHttps ? https : http;
|
|
268
|
-
|
|
269
|
-
var options = {
|
|
270
|
-
hostname: "127.0.0.1",
|
|
271
|
-
port: process.env.PORT || 3000,
|
|
272
|
-
path: call.path,
|
|
273
|
-
method: call.method,
|
|
274
|
-
headers: {
|
|
275
|
-
"Content-Type": "application/json",
|
|
276
|
-
// Forward the user's real auth token — this is the key part
|
|
277
|
-
Authorization: req.headers["authorization"] || "",
|
|
278
|
-
Cookie: req.headers["cookie"] || "",
|
|
279
|
-
},
|
|
280
|
-
};
|
|
180
|
+
http.Server.prototype.emit = function (event, req, res) {
|
|
181
|
+
if (event === "request") {
|
|
182
|
+
const path = req.url ? req.url.split("?")[0] : "";
|
|
183
|
+
const method = req.method ? req.method.toUpperCase() : "";
|
|
281
184
|
|
|
282
|
-
|
|
185
|
+
const shouldIgnore = (options.exclude || [])
|
|
186
|
+
.concat(["/health", "/favicon.ico", "/_next", "/static", "/public"])
|
|
187
|
+
.some(function (p) {
|
|
188
|
+
return path.startsWith(p);
|
|
189
|
+
});
|
|
283
190
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
191
|
+
const isApiPath = path.startsWith(options.apiPrefix || "/api");
|
|
192
|
+
|
|
193
|
+
if (!shouldIgnore && isApiPath) {
|
|
194
|
+
const normalizedPath = normalizePath(path);
|
|
195
|
+
const endpointKey = method + ":" + normalizedPath;
|
|
196
|
+
|
|
197
|
+
if (!reportedEndpoints.has(endpointKey)) {
|
|
198
|
+
reportedEndpoints.add(endpointKey);
|
|
199
|
+
|
|
200
|
+
setImmediate(function () {
|
|
201
|
+
client
|
|
202
|
+
.updateEndpoint({
|
|
203
|
+
method: method,
|
|
204
|
+
path: normalizedPath,
|
|
205
|
+
detectedBy: "runtime",
|
|
206
|
+
})
|
|
207
|
+
.catch(function (err) {
|
|
208
|
+
console.error("[botversion] update failed:", err.message);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
}
|
|
294
212
|
}
|
|
295
|
-
}
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
apiReq.on("error", function (err) {
|
|
299
|
-
resolve({ status: 500, error: err.message });
|
|
300
|
-
});
|
|
213
|
+
}
|
|
301
214
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
})
|
|
215
|
+
return originalEmit.apply(this, arguments);
|
|
216
|
+
};
|
|
217
|
+
} catch (err) {
|
|
218
|
+
console.error(
|
|
219
|
+
"[botversion] failed to attach Next.js interceptor:",
|
|
220
|
+
err.message,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
305
223
|
}
|
|
224
|
+
|
|
225
|
+
module.exports = {
|
|
226
|
+
attachInterceptor,
|
|
227
|
+
attachNextJsInterceptor,
|
|
228
|
+
normalizePath,
|
|
229
|
+
buildBodyStructure,
|
|
230
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "botversion-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "BotVersion SDK — auto-detect and register your API endpoints",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
"author": "Saurav Dhakal",
|
|
17
17
|
"license": "MIT",
|
|
18
18
|
"dependencies": {
|
|
19
|
+
"@babel/parser": "^7.29.3",
|
|
20
|
+
"@babel/traverse": "^7.29.0",
|
|
19
21
|
"express": "^4.18.2"
|
|
20
22
|
}
|
|
21
23
|
}
|