@0xmaxma/claude-gateway 1.8.0 → 1.8.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.
- package/dist/agent/model-catalog.d.ts +2 -2
- package/dist/agent/model-catalog.d.ts.map +1 -1
- package/dist/agent/model-catalog.js +74 -34
- package/dist/agent/model-catalog.js.map +1 -1
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +26 -8
- package/dist/agent/runner.js.map +1 -1
- package/dist/session/process.d.ts +14 -5
- package/dist/session/process.d.ts.map +1 -1
- package/dist/session/process.js +155 -26
- package/dist/session/process.js.map +1 -1
- package/mcp/tools/telegram/receiver-server.ts +146 -25
- package/package.json +1 -1
|
@@ -1198,6 +1198,62 @@ bot.command('model', async ctx => {
|
|
|
1198
1198
|
}
|
|
1199
1199
|
})
|
|
1200
1200
|
|
|
1201
|
+
/** Fetches current model + configured/live model lists. Shared by /models, models:back and models:more so all three agree on fallback behavior. */
|
|
1202
|
+
async function fetchModelMenuData(chatId: string): Promise<{
|
|
1203
|
+
currentModel: string
|
|
1204
|
+
configuredModels: Array<{ id: string; label: string }>
|
|
1205
|
+
liveModels: Array<{ id: string; label: string }>
|
|
1206
|
+
} | null> {
|
|
1207
|
+
if (!CALLBACK_URL_BASE) return null
|
|
1208
|
+
const [modelRes, modelsRes] = await Promise.all([
|
|
1209
|
+
fetch(CALLBACK_URL_BASE + '/command', {
|
|
1210
|
+
method: 'POST',
|
|
1211
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1212
|
+
body: JSON.stringify({ command: 'get_model', chat_id: chatId }),
|
|
1213
|
+
}),
|
|
1214
|
+
fetch(CALLBACK_URL_BASE + '/command', {
|
|
1215
|
+
method: 'POST',
|
|
1216
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1217
|
+
body: JSON.stringify({ command: 'get_models' }),
|
|
1218
|
+
}),
|
|
1219
|
+
])
|
|
1220
|
+
if (!modelRes.ok || !modelsRes.ok) throw new Error('model request failed')
|
|
1221
|
+
const modelData = (await modelRes.json()) as { model?: string }
|
|
1222
|
+
const modelsData = (await modelsRes.json()) as {
|
|
1223
|
+
models?: unknown
|
|
1224
|
+
configuredModels?: unknown
|
|
1225
|
+
liveModels?: unknown
|
|
1226
|
+
}
|
|
1227
|
+
const currentModel = typeof modelData.model === 'string' ? modelData.model : ''
|
|
1228
|
+
const availableRaw = validModelRows(modelsData.models)
|
|
1229
|
+
const availableModels = availableRaw.length ? availableRaw : validModelRows(AVAILABLE_MODELS)
|
|
1230
|
+
const configuredModels = Array.isArray(modelsData.configuredModels)
|
|
1231
|
+
? validModelRows(modelsData.configuredModels)
|
|
1232
|
+
: availableModels
|
|
1233
|
+
const liveModels = Array.isArray(modelsData.liveModels)
|
|
1234
|
+
? validModelRows(modelsData.liveModels)
|
|
1235
|
+
: availableModels.filter(m => !configuredModels.some(c => c.id === m.id))
|
|
1236
|
+
return { currentModel, configuredModels, liveModels }
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
/** Builds the root /models keyboard: one row per configured model, plus "More models..." when live-only models exist. */
|
|
1240
|
+
function buildModelsRootKeyboard(
|
|
1241
|
+
currentModel: string,
|
|
1242
|
+
configuredModels: Array<{ id: string; label: string }>,
|
|
1243
|
+
liveModels: Array<{ id: string; label: string }>,
|
|
1244
|
+
): InlineKeyboard {
|
|
1245
|
+
const keyboard = new InlineKeyboard()
|
|
1246
|
+
for (const m of configuredModels) {
|
|
1247
|
+
const id = safeModelId(m.id)
|
|
1248
|
+
if (!id) continue
|
|
1249
|
+
const prefix = m.id === currentModel ? '\u2705 ' : ''
|
|
1250
|
+
keyboard.text(`${prefix}${m.label}`, `model:${id}`).row()
|
|
1251
|
+
}
|
|
1252
|
+
if (liveModels.length) keyboard.text('More models...', 'models:more:0').row()
|
|
1253
|
+
keyboard.text('Dismiss', 'models:dismiss')
|
|
1254
|
+
return keyboard
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1201
1257
|
// /models — show model selection keyboard (receiver mode only)
|
|
1202
1258
|
bot.command('models', async ctx => {
|
|
1203
1259
|
if (ctx.chat?.type !== 'private') return
|
|
@@ -1206,31 +1262,10 @@ bot.command('models', async ctx => {
|
|
|
1206
1262
|
if (!CALLBACK_URL_BASE) return
|
|
1207
1263
|
|
|
1208
1264
|
try {
|
|
1209
|
-
const
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
body: JSON.stringify({ command: 'get_model', chat_id: String(ctx.chat.id) }),
|
|
1214
|
-
}),
|
|
1215
|
-
fetch(CALLBACK_URL_BASE + '/command', {
|
|
1216
|
-
method: 'POST',
|
|
1217
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1218
|
-
body: JSON.stringify({ command: 'get_models' }),
|
|
1219
|
-
}),
|
|
1220
|
-
])
|
|
1221
|
-
const modelData = (await modelRes.json()) as { model?: string }
|
|
1222
|
-
const modelsData = (await modelsRes.json()) as { models?: { id: string; label: string }[] }
|
|
1223
|
-
const currentModel = modelData.model ?? ''
|
|
1224
|
-
const availableModels = modelsData.models ?? AVAILABLE_MODELS
|
|
1225
|
-
|
|
1226
|
-
const keyboard = new InlineKeyboard()
|
|
1227
|
-
for (const m of availableModels) {
|
|
1228
|
-
const prefix = m.id === currentModel ? '\u2705 ' : ''
|
|
1229
|
-
keyboard.text(`${prefix}${m.label}`, `model:${m.id}`).row()
|
|
1230
|
-
}
|
|
1231
|
-
keyboard.text('Dismiss', 'models:dismiss')
|
|
1232
|
-
|
|
1233
|
-
await ctx.reply(`Current model: ${currentModel}\nSelect a model:`, {
|
|
1265
|
+
const menu = await fetchModelMenuData(String(ctx.chat.id))
|
|
1266
|
+
if (!menu) return
|
|
1267
|
+
const keyboard = buildModelsRootKeyboard(menu.currentModel, menu.configuredModels, menu.liveModels)
|
|
1268
|
+
await ctx.reply(`Current model: ${menu.currentModel}\nSelect a model:`, {
|
|
1234
1269
|
reply_markup: keyboard,
|
|
1235
1270
|
})
|
|
1236
1271
|
} catch (err) {
|
|
@@ -1403,6 +1438,26 @@ function isCallbackAuthorized(ctx: Context): boolean {
|
|
|
1403
1438
|
return access.allowFrom.includes(String(ctx.from?.id ?? ''))
|
|
1404
1439
|
}
|
|
1405
1440
|
|
|
1441
|
+
/** Telegram callback_data is capped at 64 UTF-8 bytes; reject untrusted ids safely. */
|
|
1442
|
+
function safeModelId(value: unknown): string | null {
|
|
1443
|
+
if (typeof value !== 'string') return null
|
|
1444
|
+
const id = value.trim()
|
|
1445
|
+
return id && Buffer.byteLength(`model:${id}`, 'utf8') <= 64 ? id : null
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
function validModelRows(value: unknown): Array<{ id: string; label: string }> {
|
|
1449
|
+
if (!Array.isArray(value)) return []
|
|
1450
|
+
const rows: Array<{ id: string; label: string }> = []
|
|
1451
|
+
for (const item of value) {
|
|
1452
|
+
if (typeof item !== 'object' || item === null) continue
|
|
1453
|
+
const row = item as { id?: unknown; model_id?: unknown; label?: unknown; display_name?: unknown; name?: unknown }
|
|
1454
|
+
const id = safeModelId(row.id) ?? safeModelId(row.model_id)
|
|
1455
|
+
const labelValue = [row.label, row.display_name, row.name].find(v => typeof v === 'string' && v.trim())
|
|
1456
|
+
if (id && typeof labelValue === 'string') rows.push({ id, label: labelValue.trim() })
|
|
1457
|
+
}
|
|
1458
|
+
return rows
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1406
1461
|
// Inline-button handler for permission requests. Callback data is
|
|
1407
1462
|
// `perm:allow:<id>`, `perm:deny:<id>`, or `perm:more:<id>`.
|
|
1408
1463
|
// Security mirrors the text-reply path: allowFrom must contain the sender.
|
|
@@ -1489,6 +1544,72 @@ bot.on('callback_query:data', async ctx => {
|
|
|
1489
1544
|
return
|
|
1490
1545
|
}
|
|
1491
1546
|
|
|
1547
|
+
const moreMatch = /^models:more:(\d+)$/.exec(data)
|
|
1548
|
+
if (moreMatch) {
|
|
1549
|
+
if (!isCallbackAuthorized(ctx)) {
|
|
1550
|
+
await ctx.answerCallbackQuery({ text: 'Not authorized.' }).catch(() => {})
|
|
1551
|
+
return
|
|
1552
|
+
}
|
|
1553
|
+
if (!CALLBACK_URL_BASE) {
|
|
1554
|
+
await ctx.answerCallbackQuery({ text: 'Not available.' }).catch(() => {})
|
|
1555
|
+
return
|
|
1556
|
+
}
|
|
1557
|
+
try {
|
|
1558
|
+
const chatId = String(ctx.callbackQuery.message?.chat.id ?? ctx.from.id)
|
|
1559
|
+
const menu = await fetchModelMenuData(chatId)
|
|
1560
|
+
if (!menu) {
|
|
1561
|
+
await ctx.answerCallbackQuery({ text: 'Not available.' }).catch(() => {})
|
|
1562
|
+
return
|
|
1563
|
+
}
|
|
1564
|
+
const { currentModel, liveModels } = menu
|
|
1565
|
+
const page = Number(moreMatch[1])
|
|
1566
|
+
const pageSize = 16
|
|
1567
|
+
const totalPages = Math.max(1, Math.ceil(liveModels.length / pageSize))
|
|
1568
|
+
const safePage = Math.min(page, totalPages - 1)
|
|
1569
|
+
const keyboard = new InlineKeyboard()
|
|
1570
|
+
for (const [index, m] of liveModels.slice(safePage * pageSize, (safePage + 1) * pageSize).entries()) {
|
|
1571
|
+
const modelCallback = safeModelId(m.id)
|
|
1572
|
+
if (!modelCallback) continue
|
|
1573
|
+
keyboard.text(`${m.id === currentModel ? '\u2705 ' : ''}${m.id}`, `model:${modelCallback}`)
|
|
1574
|
+
if (index % 2 === 1) keyboard.row()
|
|
1575
|
+
}
|
|
1576
|
+
if (liveModels.slice(safePage * pageSize, (safePage + 1) * pageSize).length % 2 === 1) keyboard.row()
|
|
1577
|
+
keyboard.text('«', safePage === 0 ? 'models:back' : `models:more:${safePage - 1}`)
|
|
1578
|
+
if (safePage < totalPages - 1) keyboard.text('»', `models:more:${safePage + 1}`)
|
|
1579
|
+
keyboard.row().text('Dismiss', 'models:dismiss')
|
|
1580
|
+
await ctx.answerCallbackQuery().catch(() => {})
|
|
1581
|
+
await ctx.editMessageText(`More models (live) — page ${safePage + 1}/${totalPages}:`, { reply_markup: keyboard })
|
|
1582
|
+
} catch {
|
|
1583
|
+
await ctx.answerCallbackQuery({ text: 'Request failed' }).catch(() => {})
|
|
1584
|
+
}
|
|
1585
|
+
return
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
if (data === 'models:back') {
|
|
1589
|
+
if (!isCallbackAuthorized(ctx)) {
|
|
1590
|
+
await ctx.answerCallbackQuery({ text: 'Not authorized.' }).catch(() => {})
|
|
1591
|
+
return
|
|
1592
|
+
}
|
|
1593
|
+
if (!CALLBACK_URL_BASE) {
|
|
1594
|
+
await ctx.answerCallbackQuery({ text: 'Not available.' }).catch(() => {})
|
|
1595
|
+
return
|
|
1596
|
+
}
|
|
1597
|
+
try {
|
|
1598
|
+
const chatId = String(ctx.callbackQuery.message?.chat.id ?? ctx.from.id)
|
|
1599
|
+
const menu = await fetchModelMenuData(chatId)
|
|
1600
|
+
if (!menu) {
|
|
1601
|
+
await ctx.answerCallbackQuery({ text: 'Not available.' }).catch(() => {})
|
|
1602
|
+
return
|
|
1603
|
+
}
|
|
1604
|
+
const keyboard = buildModelsRootKeyboard(menu.currentModel, menu.configuredModels, menu.liveModels)
|
|
1605
|
+
await ctx.answerCallbackQuery().catch(() => {})
|
|
1606
|
+
await ctx.editMessageText(`Current model: ${menu.currentModel}\nSelect a model:`, { reply_markup: keyboard })
|
|
1607
|
+
} catch {
|
|
1608
|
+
await ctx.answerCallbackQuery({ text: 'Request failed' }).catch(() => {})
|
|
1609
|
+
}
|
|
1610
|
+
return
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1492
1613
|
// Handle model selection callback: model:<model_id>
|
|
1493
1614
|
const modelMatch = /^model:(.+)$/.exec(data)
|
|
1494
1615
|
if (modelMatch) {
|