@0xmaxma/claude-gateway 1.3.31 → 1.4.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 +3 -1
- package/dist/agent/runner.d.ts +44 -7
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +136 -28
- package/dist/agent/runner.js.map +1 -1
- package/dist/api/router.d.ts.map +1 -1
- package/dist/api/router.js +140 -7
- package/dist/api/router.js.map +1 -1
- package/dist/history/db.d.ts +13 -1
- package/dist/history/db.d.ts.map +1 -1
- package/dist/history/db.js +67 -9
- package/dist/history/db.js.map +1 -1
- package/dist/history/types.d.ts +10 -0
- package/dist/history/types.d.ts.map +1 -1
- package/dist/session/process.d.ts +8 -0
- package/dist/session/process.d.ts.map +1 -1
- package/dist/session/process.js +46 -0
- package/dist/session/process.js.map +1 -1
- package/dist/session/store.d.ts +3 -2
- package/dist/session/store.d.ts.map +1 -1
- package/dist/session/store.js +1 -1
- package/dist/session/store.js.map +1 -1
- package/dist/types.d.ts +18 -0
- package/dist/types.d.ts.map +1 -1
- package/mcp/instructions.ts +39 -0
- package/mcp/server.ts +6 -11
- package/mcp/tools/discord/module.ts +19 -1
- package/mcp/tools/image/module.ts +592 -0
- package/package.json +1 -1
package/dist/api/router.js
CHANGED
|
@@ -239,7 +239,7 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
|
|
|
239
239
|
return;
|
|
240
240
|
}
|
|
241
241
|
const body = req.body;
|
|
242
|
-
const { message, chat_id, session_id, stream, timeout_ms, media_files, model: requestModel, store_user_message } = body;
|
|
242
|
+
const { message, chat_id, session_id, stream, timeout_ms, media_files, model: requestModel, store_user_message, image_params } = body;
|
|
243
243
|
if (message !== undefined && typeof message !== 'string') {
|
|
244
244
|
res.status(400).json({ error: 'message must be a string if provided' });
|
|
245
245
|
return;
|
|
@@ -284,6 +284,38 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
|
|
|
284
284
|
}
|
|
285
285
|
validatedMediaFiles = media_files;
|
|
286
286
|
}
|
|
287
|
+
// Validate optional image_params (contract E5) — surfaced to the agent so it
|
|
288
|
+
// calls generate_image with the composer-selected options.
|
|
289
|
+
let validatedImageParams;
|
|
290
|
+
if (image_params !== undefined) {
|
|
291
|
+
if (typeof image_params !== 'object' || image_params === null || Array.isArray(image_params)) {
|
|
292
|
+
res.status(400).json({ error: 'image_params must be an object if provided' });
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const ip = image_params;
|
|
296
|
+
const strFields = ['model', 'quality', 'size', 'aspect_ratio', 'image_ref'];
|
|
297
|
+
const out = {};
|
|
298
|
+
for (const f of strFields) {
|
|
299
|
+
const v = ip[f];
|
|
300
|
+
if (v !== undefined) {
|
|
301
|
+
if (typeof v !== 'string') {
|
|
302
|
+
res.status(400).json({ error: `image_params.${f} must be a string` });
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (v.trim())
|
|
306
|
+
out[f] = v.trim();
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (ip.n !== undefined) {
|
|
310
|
+
if (typeof ip.n !== 'number' || !Number.isFinite(ip.n) || ip.n < 1) {
|
|
311
|
+
res.status(400).json({ error: 'image_params.n must be a positive number' });
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
out.n = Math.floor(ip.n);
|
|
315
|
+
}
|
|
316
|
+
if (Object.keys(out).length)
|
|
317
|
+
validatedImageParams = out;
|
|
318
|
+
}
|
|
287
319
|
// Allow message OR media_files. Image-only sends pass an empty text
|
|
288
320
|
// alongside the image_path attribute on channelXml so Claude can Read the file.
|
|
289
321
|
const trimmedMessage = typeof message === 'string' ? message.trim() : '';
|
|
@@ -380,7 +412,7 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
|
|
|
380
412
|
res.socket?.setNoDelay(true);
|
|
381
413
|
const agentCfg = agentConfigs.get(agentId);
|
|
382
414
|
const allowTools = agentCfg.allow_tools ?? !!apiKey.allow_tools;
|
|
383
|
-
onClientDisconnect = await runner.sendApiMessageStream(sessionId, chatIdStr, trimmedMessage, sseCallbacks, { timeoutMs, allowTools, mediaFiles: validatedMediaFiles, model: modelStr, skipUserMessage });
|
|
415
|
+
onClientDisconnect = await runner.sendApiMessageStream(sessionId, chatIdStr, trimmedMessage, sseCallbacks, { timeoutMs, allowTools, mediaFiles: validatedMediaFiles, model: modelStr, skipUserMessage, imageParams: validatedImageParams });
|
|
384
416
|
// Client disconnect — marks SSE writes as no-op; stream continues server-side until result is saved to DB
|
|
385
417
|
res.on('close', onClientDisconnect);
|
|
386
418
|
}
|
|
@@ -421,6 +453,7 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
|
|
|
421
453
|
mediaFiles: validatedMediaFiles,
|
|
422
454
|
model: modelStr,
|
|
423
455
|
skipUserMessage,
|
|
456
|
+
imageParams: validatedImageParams,
|
|
424
457
|
}));
|
|
425
458
|
}
|
|
426
459
|
const syncResult = {
|
|
@@ -533,7 +566,7 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
|
|
|
533
566
|
description: cfg?.description ?? '',
|
|
534
567
|
sessions: sessions.map((s) => {
|
|
535
568
|
const meta = metaMap.get(s.sessionId);
|
|
536
|
-
return { ...s, sessionName: meta?.name ?? null };
|
|
569
|
+
return { ...s, sessionName: meta?.name ?? null, imageConfig: meta?.imageConfig ?? null };
|
|
537
570
|
}),
|
|
538
571
|
};
|
|
539
572
|
}));
|
|
@@ -2201,7 +2234,7 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
|
|
|
2201
2234
|
/**
|
|
2202
2235
|
* GET /api/v1/agents/:agentId/chats/:chatId/messages
|
|
2203
2236
|
* Paginated message history (cursor-based).
|
|
2204
|
-
* Query: limit, before (ts ms), after (ts ms), session_id
|
|
2237
|
+
* Query: limit, before (ts ms), after (ts ms), session_id, order (asc|desc, default desc)
|
|
2205
2238
|
*/
|
|
2206
2239
|
router.get('/v1/agents/:agentId/chats/:chatId/messages', auth, (req, res) => {
|
|
2207
2240
|
const { agentId, chatId } = req.params;
|
|
@@ -2217,10 +2250,54 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
|
|
|
2217
2250
|
}
|
|
2218
2251
|
const query = req.query;
|
|
2219
2252
|
const limit = query['limit'] ? Math.min(parseInt(query['limit'], 10) || 50, 200) : 50;
|
|
2220
|
-
|
|
2221
|
-
|
|
2253
|
+
// Numeric cursor params. before/after are ms timestamps; before_id/after_id are the id
|
|
2254
|
+
// component of the composite (ts, id) cursor, echoed from a prior page's nextCursorId —
|
|
2255
|
+
// paired with before/after they stop paging from skipping messages that share a ts
|
|
2256
|
+
// (ignored unless the matching before/after is present). A present-but-non-numeric value
|
|
2257
|
+
// (?before=abc, or a duplicate/structured param) is a malformed request: reject with 400
|
|
2258
|
+
// so client bugs surface, mirroring the `order` guard below, rather than coercing to NaN
|
|
2259
|
+
// and silently returning an empty page.
|
|
2260
|
+
const cursorInts = {};
|
|
2261
|
+
for (const name of ['before', 'after', 'before_id', 'after_id']) {
|
|
2262
|
+
const raw = query[name];
|
|
2263
|
+
if (raw === undefined)
|
|
2264
|
+
continue;
|
|
2265
|
+
const n = typeof raw === 'string' ? parseInt(raw, 10) : NaN;
|
|
2266
|
+
if (!Number.isFinite(n)) {
|
|
2267
|
+
res.status(400).json({ error: `${name} must be a number` });
|
|
2268
|
+
return;
|
|
2269
|
+
}
|
|
2270
|
+
cursorInts[name] = n;
|
|
2271
|
+
}
|
|
2272
|
+
const before = cursorInts['before'];
|
|
2273
|
+
const after = cursorInts['after'];
|
|
2274
|
+
const beforeId = cursorInts['before_id'];
|
|
2275
|
+
const afterId = cursorInts['after_id'];
|
|
2222
2276
|
const sessionId = query['session_id'] ?? undefined;
|
|
2223
|
-
|
|
2277
|
+
// order: case-insensitive; 'asc' seeks forward, 'desc' (or omitted) is the db default.
|
|
2278
|
+
// Reject any other explicit value with 400 so client typos surface instead of silently defaulting.
|
|
2279
|
+
let order;
|
|
2280
|
+
const rawOrder = query['order'];
|
|
2281
|
+
if (rawOrder !== undefined) {
|
|
2282
|
+
// Express parses a repeated/structured param (?order=asc&order=asc) as an
|
|
2283
|
+
// array/object, not a string — guard so .toLowerCase() can't throw a 500.
|
|
2284
|
+
if (typeof rawOrder !== 'string') {
|
|
2285
|
+
res.status(400).json({ error: "order must be 'asc' or 'desc'" });
|
|
2286
|
+
return;
|
|
2287
|
+
}
|
|
2288
|
+
const normalized = rawOrder.toLowerCase();
|
|
2289
|
+
if (normalized === 'asc') {
|
|
2290
|
+
order = 'asc';
|
|
2291
|
+
}
|
|
2292
|
+
else if (normalized === 'desc') {
|
|
2293
|
+
order = undefined; // explicit desc == db default
|
|
2294
|
+
}
|
|
2295
|
+
else {
|
|
2296
|
+
res.status(400).json({ error: "order must be 'asc' or 'desc'" });
|
|
2297
|
+
return;
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
const page = runner.getHistoryDb().getMessages(chatId, { limit, before, after, beforeId, afterId, sessionId, order });
|
|
2224
2301
|
res.json(page);
|
|
2225
2302
|
});
|
|
2226
2303
|
/**
|
|
@@ -2251,6 +2328,62 @@ function createApiRouter(agentRunners, agentConfigs, apiKeys, configPath, models
|
|
|
2251
2328
|
const page = runner.getHistoryDb().searchMessages(chatId, q, { limit, offset });
|
|
2252
2329
|
res.json(page);
|
|
2253
2330
|
});
|
|
2331
|
+
/**
|
|
2332
|
+
* GET /api/v1/agents/:agentId/chats/:chatId/messages/active-days
|
|
2333
|
+
* Distinct local calendar days (YYYY-MM-DD) with >= 1 message in a [from, to) window.
|
|
2334
|
+
* Powers the jump-to-date calendar's per-day "has history" dot in one bounded index scan.
|
|
2335
|
+
* Query: from (ts ms, inclusive), to (ts ms, exclusive), tz_offset (min east of UTC, Bangkok=+420), session_id
|
|
2336
|
+
*/
|
|
2337
|
+
router.get('/v1/agents/:agentId/chats/:chatId/messages/active-days', auth, (req, res) => {
|
|
2338
|
+
const { agentId, chatId } = req.params;
|
|
2339
|
+
const apiKey = req.apiKey;
|
|
2340
|
+
if (!(0, auth_1.canAccessAgent)(apiKey, agentId)) {
|
|
2341
|
+
res.status(403).json({ error: `API key has no access to agent '${agentId}'` });
|
|
2342
|
+
return;
|
|
2343
|
+
}
|
|
2344
|
+
const runner = agentRunners.get(agentId);
|
|
2345
|
+
if (!runner) {
|
|
2346
|
+
res.status(404).json({ error: `Agent '${agentId}' not found` });
|
|
2347
|
+
return;
|
|
2348
|
+
}
|
|
2349
|
+
const query = req.query;
|
|
2350
|
+
// Number(), not parseInt() — parseInt("100garbage") silently returns 100, masking a malformed
|
|
2351
|
+
// client value the same way a case-sensitive/silently-defaulting enum param would (see order).
|
|
2352
|
+
const from = query['from'] ? Number(query['from']) : NaN;
|
|
2353
|
+
const to = query['to'] ? Number(query['to']) : NaN;
|
|
2354
|
+
if (!Number.isFinite(from) || !Number.isFinite(to)) {
|
|
2355
|
+
res.status(400).json({ error: 'from and to (ts ms) are required' });
|
|
2356
|
+
return;
|
|
2357
|
+
}
|
|
2358
|
+
// Bound the window so a malformed client can't turn this into a near-full-history scan.
|
|
2359
|
+
// 366 days is far wider than the one-month view the calendar sends, so it never bites
|
|
2360
|
+
// legitimate navigation while still capping a pathological range (E5 in the design notes).
|
|
2361
|
+
const MAX_ACTIVE_DAYS_SPAN_MS = 366 * 24 * 60 * 60 * 1000;
|
|
2362
|
+
if (to - from > MAX_ACTIVE_DAYS_SPAN_MS) {
|
|
2363
|
+
res.status(400).json({ error: 'window too large (max 366 days between from and to)' });
|
|
2364
|
+
return;
|
|
2365
|
+
}
|
|
2366
|
+
let tzOffset = 0;
|
|
2367
|
+
if (query['tz_offset'] !== undefined) {
|
|
2368
|
+
const parsed = Number(query['tz_offset']);
|
|
2369
|
+
if (!Number.isFinite(parsed)) {
|
|
2370
|
+
res.status(400).json({ error: 'tz_offset must be a number (minutes east of UTC)' });
|
|
2371
|
+
return;
|
|
2372
|
+
}
|
|
2373
|
+
tzOffset = parsed;
|
|
2374
|
+
}
|
|
2375
|
+
// session_id, like order, can arrive as an array when the param is repeated
|
|
2376
|
+
// (?session_id=a&session_id=b). Guard so it can't reach the sqlite bind as an
|
|
2377
|
+
// array and throw a 500 — surface a 400 instead. (Mirrors the order guard above.)
|
|
2378
|
+
const rawSessionId = query['session_id'];
|
|
2379
|
+
if (rawSessionId !== undefined && typeof rawSessionId !== 'string') {
|
|
2380
|
+
res.status(400).json({ error: 'session_id must be a single value' });
|
|
2381
|
+
return;
|
|
2382
|
+
}
|
|
2383
|
+
const sessionId = rawSessionId ?? undefined;
|
|
2384
|
+
const days = runner.getHistoryDb().getActiveDays(chatId, { from, to, tzOffset, sessionId });
|
|
2385
|
+
res.json({ days });
|
|
2386
|
+
});
|
|
2254
2387
|
/**
|
|
2255
2388
|
* POST /api/v1/agents/:agentId/chats/:chatId/sessions/:sessionId/messages
|
|
2256
2389
|
* Inject a message into an existing channel session (cross-channel continuation).
|