@goodandready/dsh-messenger-gateway 0.4.4 → 0.4.5
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/lib/adapters/telegram.js +597 -596
- package/lib/client.js +11 -6
- package/lib/forum-mirror.js +105 -105
- package/lib/gateway-callbacks.js +188 -187
- package/lib/gateway-commands.js +587 -584
- package/lib/gateway.js +7 -1
- package/lib/http.js +16 -3
- package/lib/index.js +544 -540
- package/lib/models.js +29 -2
- package/package.json +1 -1
package/lib/gateway-callbacks.js
CHANGED
|
@@ -1,187 +1,188 @@
|
|
|
1
|
-
import { t } from './locales/index.js'
|
|
2
|
-
import {
|
|
3
|
-
parseCallbackData, parseAskCallback, targetMatchesAsk,
|
|
4
|
-
releaseCallbacks, buildMultiSelectKeyboard, indexCallbacks, REMOVE_KEYBOARD
|
|
5
|
-
} from './ask.js'
|
|
6
|
-
import {
|
|
7
|
-
listModelCatalog, buildProvidersKeyboard, buildModelsKeyboard, storeModelSelection
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
pending.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
pending.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
const
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
1
|
+
import { t } from './locales/index.js'
|
|
2
|
+
import {
|
|
3
|
+
parseCallbackData, parseAskCallback, targetMatchesAsk,
|
|
4
|
+
releaseCallbacks, buildMultiSelectKeyboard, indexCallbacks, REMOVE_KEYBOARD
|
|
5
|
+
} from './ask.js'
|
|
6
|
+
import {
|
|
7
|
+
listModelCatalog, buildProvidersKeyboard, buildModelsKeyboard, storeModelSelection,
|
|
8
|
+
getStoredModelSelection
|
|
9
|
+
} from './models.js'
|
|
10
|
+
|
|
11
|
+
export async function handleGatewayCallback(gw, cb) {
|
|
12
|
+
if (cb.userId && !gw.isUserAllowed(cb.userId)) {
|
|
13
|
+
try { await cb.answer(t('msg.not_allowed', {}, 'en')) } catch (err) { gw.recordApiFailure('cb.answer.not_allowed', err) }
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
const indexed = gw.callbackIndex.get(cb.data)
|
|
17
|
+
const { token, buttonId } = parseCallbackData(cb.data)
|
|
18
|
+
const askToken = indexed || token
|
|
19
|
+
if (askToken && gw.pendingAsks.has(askToken)) {
|
|
20
|
+
const pending = gw.pendingAsks.get(askToken)
|
|
21
|
+
if (!targetMatchesAsk(pending, cb)) {
|
|
22
|
+
await cb.answer(t('ask.other_chat', {}, 'en'))
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
const action = parseAskCallback(cb.data)
|
|
26
|
+
if (pending.isMulti) {
|
|
27
|
+
if (action.kind === 'toggle') {
|
|
28
|
+
if (pending.selected.has(action.id)) pending.selected.delete(action.id)
|
|
29
|
+
else pending.selected.add(action.id)
|
|
30
|
+
releaseCallbacks(gw.callbackIndex, pending.callbackKeys)
|
|
31
|
+
const nextKb = buildMultiSelectKeyboard(askToken, pending.options, pending.selected, pending.page, pending.pageSize, pending.payload)
|
|
32
|
+
pending.callbackKeys = nextKb.callbackKeys
|
|
33
|
+
indexCallbacks(gw.callbackIndex, nextKb.callbackKeys, askToken)
|
|
34
|
+
try {
|
|
35
|
+
if (cb.editReplyMarkup) await cb.editReplyMarkup(nextKb.replyMarkup)
|
|
36
|
+
else await cb.editMessage(cb.message?.text || 'Selection', nextKb.replyMarkup)
|
|
37
|
+
} catch (err) {
|
|
38
|
+
gw.recordApiFailure('cb.editMessage.ask_multi_toggle', err)
|
|
39
|
+
}
|
|
40
|
+
await cb.answer(pending.selected.has(action.id) ? 'Selected' : 'Deselected')
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
if (action.kind === 'page') {
|
|
44
|
+
pending.page = action.page
|
|
45
|
+
releaseCallbacks(gw.callbackIndex, pending.callbackKeys)
|
|
46
|
+
const nextKb = buildMultiSelectKeyboard(askToken, pending.options, pending.selected, pending.page, pending.pageSize, pending.payload)
|
|
47
|
+
pending.callbackKeys = nextKb.callbackKeys
|
|
48
|
+
indexCallbacks(gw.callbackIndex, nextKb.callbackKeys, askToken)
|
|
49
|
+
try {
|
|
50
|
+
if (cb.editReplyMarkup) await cb.editReplyMarkup(nextKb.replyMarkup)
|
|
51
|
+
else await cb.editMessage(cb.message?.text || 'Selection', nextKb.replyMarkup)
|
|
52
|
+
} catch (err) {
|
|
53
|
+
gw.recordApiFailure('cb.editMessage.ask_multi_page', err)
|
|
54
|
+
}
|
|
55
|
+
await cb.answer()
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
if (action.kind === 'done') {
|
|
59
|
+
gw.pendingAsks.delete(askToken)
|
|
60
|
+
clearTimeout(pending.timer)
|
|
61
|
+
releaseCallbacks(gw.callbackIndex, pending.callbackKeys)
|
|
62
|
+
await cb.answer('OK')
|
|
63
|
+
try { await cb.editMessage(cb.message?.text || 'Done', REMOVE_KEYBOARD) } catch (err) { gw.recordApiFailure('cb.editMessage.ask_multi_done', err) }
|
|
64
|
+
pending.resolve({ buttonId: 'done', selected: Array.from(pending.selected), data: cb.data })
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
if (action.kind === 'cancel') {
|
|
68
|
+
gw.pendingAsks.delete(askToken)
|
|
69
|
+
clearTimeout(pending.timer)
|
|
70
|
+
releaseCallbacks(gw.callbackIndex, pending.callbackKeys)
|
|
71
|
+
await cb.answer('Cancelled')
|
|
72
|
+
try { await cb.editMessage(cb.message?.text || 'Cancelled', REMOVE_KEYBOARD) } catch (err) { gw.recordApiFailure('cb.editMessage.ask_multi_cancel', err) }
|
|
73
|
+
pending.resolve({ buttonId: 'cancel', selected: [], data: cb.data })
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
gw.pendingAsks.delete(askToken)
|
|
78
|
+
clearTimeout(pending.timer)
|
|
79
|
+
releaseCallbacks(gw.callbackIndex, pending.callbackKeys)
|
|
80
|
+
await cb.answer('OK')
|
|
81
|
+
try { await cb.editMessage(cb.message?.text || 'Done', REMOVE_KEYBOARD) } catch (err) { gw.recordApiFailure('cb.editMessage.ask_single_done', err) }
|
|
82
|
+
pending.resolve({ buttonId: action.id || buttonId, data: cb.data })
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Model picker interactive flow
|
|
87
|
+
if (cb.data?.startsWith('mdl:')) {
|
|
88
|
+
const parts = cb.data.split(':')
|
|
89
|
+
const sub = parts[1]
|
|
90
|
+
// Step 2: Selected provider -> show its models (10 per page)
|
|
91
|
+
if (sub === 'p') {
|
|
92
|
+
const providerId = parts.slice(2).join(':')
|
|
93
|
+
const current = gw.resolveAgentModel()
|
|
94
|
+
const catalog = await listModelCatalog(gw.ctx, current)
|
|
95
|
+
const models = catalog.modelsByProvider.get(providerId) || []
|
|
96
|
+
if (!models.length) {
|
|
97
|
+
await cb.answer(t('model.no_models', {}, 'en'))
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
const kb = buildModelsKeyboard(providerId, models, current.model, 0)
|
|
101
|
+
await cb.answer()
|
|
102
|
+
const text = [
|
|
103
|
+
`🤖 <b>Provider:</b> <code>${providerId}</code>`,
|
|
104
|
+
`Select model (page ${kb.page + 1}/${kb.totalPages}):`,
|
|
105
|
+
].join('\n')
|
|
106
|
+
try {
|
|
107
|
+
if (cb.editMessage) await cb.editMessage(text, kb)
|
|
108
|
+
} catch (err) {
|
|
109
|
+
gw.recordApiFailure('cb.editMessage.model_picker_provider', err)
|
|
110
|
+
}
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
// Pagination for models
|
|
114
|
+
if (sub === 'pg') {
|
|
115
|
+
const page = parseInt(parts[parts.length - 1], 10) || 0
|
|
116
|
+
const providerId = parts.slice(2, -1).join(':')
|
|
117
|
+
const current = gw.resolveAgentModel()
|
|
118
|
+
const catalog = await listModelCatalog(gw.ctx, current)
|
|
119
|
+
const models = catalog.modelsByProvider.get(providerId) || []
|
|
120
|
+
const kb = buildModelsKeyboard(providerId, models, current.model, page)
|
|
121
|
+
await cb.answer()
|
|
122
|
+
const text = [
|
|
123
|
+
`🤖 <b>Provider:</b> <code>${providerId}</code>`,
|
|
124
|
+
`Select model (page ${kb.page + 1}/${kb.totalPages}):`,
|
|
125
|
+
].join('\n')
|
|
126
|
+
try {
|
|
127
|
+
if (cb.editMessage) await cb.editMessage(text, kb)
|
|
128
|
+
} catch (err) {
|
|
129
|
+
gw.recordApiFailure('cb.editMessage.model_picker_page', err)
|
|
130
|
+
}
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
// Back to providers
|
|
134
|
+
if (sub === 'back') {
|
|
135
|
+
const current = gw.resolveAgentModel()
|
|
136
|
+
const catalog = await listModelCatalog(gw.ctx, current)
|
|
137
|
+
const kb = buildProvidersKeyboard(catalog.providers, current)
|
|
138
|
+
await cb.answer()
|
|
139
|
+
const text = [
|
|
140
|
+
'🤖 <b>Choose Provider:</b>',
|
|
141
|
+
`Current: <code>${current.provider}/${current.model}</code>`,
|
|
142
|
+
].join('\n')
|
|
143
|
+
try {
|
|
144
|
+
if (cb.editMessage) await cb.editMessage(text, kb)
|
|
145
|
+
} catch (err) {
|
|
146
|
+
gw.recordApiFailure('cb.editMessage.model_picker_back', err)
|
|
147
|
+
}
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
// Select model
|
|
151
|
+
if (sub === 's') {
|
|
152
|
+
const key = parts[2]
|
|
153
|
+
const stored = getStoredModelSelection(key)
|
|
154
|
+
if (!stored) {
|
|
155
|
+
await cb.answer(t('ask.expired', {}, 'en'))
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
const { provider, model } = stored
|
|
159
|
+
try {
|
|
160
|
+
const adm = gw.ctx.get('agentDefaultModel')
|
|
161
|
+
if (adm?.saveSelection) {
|
|
162
|
+
await adm.saveSelection({ provider, model })
|
|
163
|
+
}
|
|
164
|
+
gw.config.agent = { ...gw.config.agent, provider, model }
|
|
165
|
+
try {
|
|
166
|
+
await gw.hooks?.persistAgentModel?.({ provider, model })
|
|
167
|
+
} catch (e) {
|
|
168
|
+
gw.ctx.logger?.warn?.(`persist agent model: ${e.message}`)
|
|
169
|
+
}
|
|
170
|
+
await cb.answer(t('ask.chose', { choice: model }, 'en'))
|
|
171
|
+
try {
|
|
172
|
+
if (cb.editMessage) await cb.editMessage(t('model.switched', { provider, model }, 'en'), REMOVE_KEYBOARD)
|
|
173
|
+
} catch (err) {
|
|
174
|
+
gw.recordApiFailure('cb.editMessage.model_picker_switched', err)
|
|
175
|
+
}
|
|
176
|
+
} catch (err) {
|
|
177
|
+
await cb.answer(`Error: ${err.message}`)
|
|
178
|
+
}
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
if (sub === 'cur') {
|
|
182
|
+
await cb.answer()
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
await cb.answer()
|
|
188
|
+
}
|