talon-agent 4.3.2 → 4.3.3
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/package.json +1 -1
- package/src/backend/claude-sdk/doctor.ts +1 -1
- package/src/backend/codex/doctor.ts +1 -1
- package/src/backend/openai-agents/doctor.ts +1 -1
- package/src/bootstrap.ts +1 -1
- package/src/cli/doctor.ts +1 -1
- package/src/core/agent-runtime/backend-registry.ts +1 -1
- package/src/core/auth/expiry-monitor.ts +1 -1
- package/src/core/{doctor.ts → doctor/index.ts} +11 -11
- package/src/core/{notify.ts → frontend-runtime/admin-notify.ts} +1 -1
- package/src/core/plugin/native-runtimes.ts +1 -1
- package/src/frontend/discord/commands/admin.ts +1 -1
- package/src/frontend/discord/render.ts +1 -1
- package/src/frontend/telegram/actions/index.ts +1 -1
- package/src/frontend/telegram/actions/moderation/chat.ts +63 -0
- package/src/frontend/telegram/actions/moderation/index.ts +74 -0
- package/src/frontend/telegram/actions/moderation/members.ts +77 -0
- package/src/frontend/telegram/actions/moderation/permissions.ts +69 -0
- package/src/frontend/telegram/actions/moderation/topics.ts +48 -0
- package/src/frontend/telegram/actions/moderation/types.ts +16 -0
- package/src/frontend/telegram/admin/background.ts +66 -0
- package/src/frontend/telegram/admin/chunked-reply.ts +20 -0
- package/src/frontend/telegram/admin/health.ts +81 -0
- package/src/frontend/telegram/admin/sessions.ts +105 -0
- package/src/frontend/telegram/admin.ts +56 -261
- package/src/frontend/telegram/callbacks/model/backend.ts +156 -0
- package/src/frontend/telegram/callbacks/model/control.ts +29 -0
- package/src/frontend/telegram/callbacks/model/nav.ts +54 -0
- package/src/frontend/telegram/callbacks/model/select.ts +100 -0
- package/src/frontend/telegram/callbacks/model/types.ts +39 -0
- package/src/frontend/telegram/callbacks/model/views.ts +116 -0
- package/src/frontend/telegram/callbacks/model.ts +61 -331
- package/src/frontend/telegram/commands/admin.ts +108 -77
- package/src/frontend/telegram/commands/settings.ts +65 -43
- package/src/frontend/telegram/commands/whatsapp-pairing.ts +1 -1
- package/src/frontend/telegram/helpers/diagnostics.ts +1 -1
- package/src/frontend/telegram/middleware.ts +166 -201
- package/src/frontend/terminal/builtins/context.ts +184 -0
- package/src/frontend/terminal/builtins/help.ts +35 -0
- package/src/frontend/terminal/builtins/index.ts +23 -0
- package/src/frontend/terminal/builtins/model.ts +198 -0
- package/src/frontend/terminal/builtins/session.ts +101 -0
- package/src/frontend/terminal/builtins/status.ts +202 -0
- package/src/frontend/terminal/command-registry.ts +80 -0
- package/src/frontend/terminal/commands.ts +11 -674
- package/src/frontend/terminal/input/history.ts +64 -0
- package/src/frontend/terminal/input/keys.ts +143 -0
- package/src/frontend/terminal/input/parts.ts +33 -0
- package/src/frontend/terminal/input/paste.ts +53 -0
- package/src/frontend/terminal/input/render.ts +42 -0
- package/src/frontend/terminal/input/state.ts +60 -0
- package/src/frontend/terminal/input.ts +27 -342
- package/src/frontend/whatsapp/connection.ts +1 -1
- package/src/frontend/whatsapp/index.ts +1 -1
- package/src/frontend/whatsapp/pairing-service.ts +1 -1
- package/src/plugins/github/provision.ts +1 -1
- package/src/plugins/mempalace/provision.ts +1 -1
- package/src/plugins/playwright/provision.ts +1 -1
- package/src/frontend/telegram/actions/moderation.ts +0 -255
- /package/src/core/{doctor-types.ts → doctor/types.ts} +0 -0
- /package/src/core/{pairing-broker.ts → frontend-runtime/pairing-broker.ts} +0 -0
|
@@ -1,359 +1,89 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `model:*` callbacks — the /model menu controller. Each
|
|
3
|
-
* most one piece of chat-settings (or pool) state
|
|
4
|
-
* three views: main menu, backend submenu, or browse.
|
|
2
|
+
* `model:*` callbacks — the /model menu controller. Each handler mutates at
|
|
3
|
+
* most one piece of chat-settings (or pool) state, then the router
|
|
4
|
+
* re-renders one of three views: main menu, backend submenu, or browse.
|
|
5
5
|
*
|
|
6
6
|
* The controller always resolves the *per-chat* backend so the menu reflects
|
|
7
7
|
* active per-chat overrides instead of the global chat-role default.
|
|
8
|
+
*
|
|
9
|
+
* Callback data is parsed by `parseModelCallback` (model-callbacks.ts); the
|
|
10
|
+
* table below maps each parsed `kind` to its handler in `model/`:
|
|
11
|
+
* - `control` — done / noop / unknown (ack only)
|
|
12
|
+
* - `select` — select / reset / toggle-free
|
|
13
|
+
* - `nav` — menu / browse / nav-* / backends (view changes only)
|
|
14
|
+
* - `backend` — backend-select / backend-default (rebind + handoff)
|
|
15
|
+
* - `views` — the three renderers
|
|
8
16
|
*/
|
|
9
17
|
|
|
10
18
|
import type { Context } from "grammy";
|
|
11
|
-
import {
|
|
12
|
-
setChatModelForBackend,
|
|
13
|
-
setChatBackend,
|
|
14
|
-
} from "../../../storage/chat-settings.js";
|
|
15
|
-
import { resolveModelId as resolveModelName } from "../../../core/models/catalog.js";
|
|
16
|
-
import { resetSession } from "../../../storage/sessions.js";
|
|
17
|
-
import { clearHistory } from "../../../storage/history.js";
|
|
18
|
-
import {
|
|
19
|
-
getBackendIdForChat,
|
|
20
|
-
listAvailableBackends,
|
|
21
|
-
rebindChat,
|
|
22
|
-
releaseChat,
|
|
23
|
-
} from "../../../core/engine/backend-controller/index.js";
|
|
24
|
-
import { resetPulseCheckpoint } from "../../../core/background/pulse.js";
|
|
25
|
-
import { escapeHtml } from "../formatting.js";
|
|
26
|
-
import {
|
|
27
|
-
renderModelMenuText,
|
|
28
|
-
renderModelMenuKeyboard,
|
|
29
|
-
renderModelBrowseKeyboard,
|
|
30
|
-
renderBackendMenuKeyboard,
|
|
31
|
-
renderBackendMenuText,
|
|
32
|
-
} from "../helpers/index.js";
|
|
33
19
|
import { parseModelCallback } from "../model-callbacks.js";
|
|
20
|
+
import { answerCallbackQuerySafe, type CallbackDeps } from "./shared.js";
|
|
21
|
+
import { handleDone, handleNoop } from "./model/control.js";
|
|
22
|
+
import { handleSelect, handleReset, handleToggleFree } from "./model/select.js";
|
|
34
23
|
import {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
24
|
+
showMenu,
|
|
25
|
+
showBrowse,
|
|
26
|
+
navBackToProviders,
|
|
27
|
+
navProvider,
|
|
28
|
+
navPage,
|
|
29
|
+
navFilter,
|
|
30
|
+
showBackends,
|
|
31
|
+
} from "./model/nav.js";
|
|
32
|
+
import { handleBackendSelect, handleBackendDefault } from "./model/backend.js";
|
|
33
|
+
import { renderModelView } from "./model/views.js";
|
|
34
|
+
import type {
|
|
35
|
+
ModelCallbackHandler,
|
|
36
|
+
ModelCallbackHandlers,
|
|
37
|
+
} from "./model/types.js";
|
|
48
38
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
chatId: string,
|
|
72
|
-
previousBackend: ReturnType<typeof resolveBackendForChat>,
|
|
73
|
-
gateway: CallbackDeps["gateway"],
|
|
74
|
-
): void {
|
|
75
|
-
previousBackend?.sessions?.resetChat?.(chatId);
|
|
76
|
-
const nextBackend = resolveBackendForChat(chatId, gateway);
|
|
77
|
-
// Same instance on a no-op switch — warming it again is harmless
|
|
78
|
-
// (every step is idempotent) but pointless, so skip it.
|
|
79
|
-
if (!nextBackend || nextBackend === previousBackend) return;
|
|
80
|
-
void Promise.resolve(nextBackend.sessions?.warmSession?.(chatId)).catch(
|
|
81
|
-
(err) =>
|
|
82
|
-
logWarn(
|
|
83
|
-
"bot",
|
|
84
|
-
`[${chatId}] warm after backend switch failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
85
|
-
),
|
|
86
|
-
);
|
|
87
|
-
}
|
|
39
|
+
// Null-prototype so a parsed kind can never resolve an inherited
|
|
40
|
+
// Object.prototype method via `handlers[kind]`.
|
|
41
|
+
export const MODEL_CALLBACK_HANDLERS: ModelCallbackHandlers = Object.assign(
|
|
42
|
+
Object.create(null),
|
|
43
|
+
{
|
|
44
|
+
done: handleDone,
|
|
45
|
+
noop: handleNoop,
|
|
46
|
+
unknown: handleNoop,
|
|
47
|
+
select: handleSelect,
|
|
48
|
+
reset: handleReset,
|
|
49
|
+
"toggle-free": handleToggleFree,
|
|
50
|
+
menu: showMenu,
|
|
51
|
+
browse: showBrowse,
|
|
52
|
+
"nav-back-to-providers": navBackToProviders,
|
|
53
|
+
"nav-provider": navProvider,
|
|
54
|
+
"nav-page": navPage,
|
|
55
|
+
"nav-filter": navFilter,
|
|
56
|
+
backends: showBackends,
|
|
57
|
+
"backend-select": handleBackendSelect,
|
|
58
|
+
"backend-default": handleBackendDefault,
|
|
59
|
+
} satisfies ModelCallbackHandlers,
|
|
60
|
+
);
|
|
88
61
|
|
|
89
62
|
export async function handleModelCallback(
|
|
90
63
|
ctx: Context,
|
|
91
64
|
data: string,
|
|
92
65
|
cid: string,
|
|
93
|
-
|
|
66
|
+
deps: CallbackDeps,
|
|
94
67
|
): Promise<void> {
|
|
95
68
|
const action = parseModelCallback(data);
|
|
96
|
-
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
try {
|
|
102
|
-
await ctx.deleteMessage();
|
|
103
|
-
} catch {
|
|
104
|
-
/* might lack delete permission */
|
|
105
|
-
}
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
if (action.kind === "noop" || action.kind === "unknown") {
|
|
69
|
+
// Every parsed kind has a handler; the cast only widens the parameter
|
|
70
|
+
// type so the union action can be passed through.
|
|
71
|
+
const handler = MODEL_CALLBACK_HANDLERS[action.kind] as
|
|
72
|
+
ModelCallbackHandler | undefined;
|
|
73
|
+
if (!handler) {
|
|
109
74
|
await answerCallbackQuerySafe(ctx);
|
|
110
75
|
return;
|
|
111
76
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
let toast: string | undefined;
|
|
115
|
-
let viewAfter: "menu" | "browse" | "backends" = "menu";
|
|
116
|
-
let browsePage: number | undefined;
|
|
117
|
-
let browseFilter: "all" | "free" | undefined;
|
|
118
|
-
let browseProvider: string | undefined;
|
|
119
|
-
let browseBackToGroups = false;
|
|
120
|
-
|
|
121
|
-
if (action.kind === "select") {
|
|
122
|
-
// Resolve selection against the *per-chat* backend — if the
|
|
123
|
-
// chat has switched to openai-agents we must validate the id
|
|
124
|
-
// against that catalog, not the global default's.
|
|
125
|
-
const be = resolveBackendForChat(cid, gateway);
|
|
126
|
-
const beId = getBackendIdForChat(cid);
|
|
127
|
-
if (be?.models?.resolveModelInfo) {
|
|
128
|
-
const resolution = await be.models?.resolveModelInfo(action.modelId);
|
|
129
|
-
if (resolution.kind !== "exact" || !resolution.model.selectable) {
|
|
130
|
-
await answerCallbackQuerySafe(ctx, {
|
|
131
|
-
text:
|
|
132
|
-
resolution.kind === "exact"
|
|
133
|
-
? (resolution.model.unavailableReason ?? "Unavailable")
|
|
134
|
-
: "Model is unavailable",
|
|
135
|
-
});
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
// Persist the pick into the chat's *backend-specific* slot.
|
|
139
|
-
// Switching backends later restores each side's prior choice
|
|
140
|
-
// automatically — the slot isn't shared across backends.
|
|
141
|
-
setChatModelForBackend(cid, beId, resolution.storedValue);
|
|
142
|
-
// Also pin the chat to this backend so a restart doesn't
|
|
143
|
-
// unbind to the role-default and orphan the model id.
|
|
144
|
-
setChatBackend(cid, beId);
|
|
145
|
-
toast = `Model: ${resolution.model.displayName}`;
|
|
146
|
-
} else {
|
|
147
|
-
setChatModelForBackend(cid, beId, resolveModelName(action.modelId));
|
|
148
|
-
setChatBackend(cid, beId);
|
|
149
|
-
const { model: resolved } = await resolveActiveModelForChat(
|
|
150
|
-
cid,
|
|
151
|
-
be,
|
|
152
|
-
beId,
|
|
153
|
-
config,
|
|
154
|
-
);
|
|
155
|
-
toast = `Model: ${resolved ?? "(unset)"}`;
|
|
156
|
-
}
|
|
157
|
-
} else if (action.kind === "reset") {
|
|
158
|
-
// Clear this backend's slot only — other backends' picks stay.
|
|
159
|
-
// Compute the toast through the resolver so the label names the
|
|
160
|
-
// backend's actual default (or "No model selected" when there
|
|
161
|
-
// isn't one — catalog-driven backend, no operator config).
|
|
162
|
-
const be = resolveBackendForChat(cid, gateway);
|
|
163
|
-
const beId = getBackendIdForChat(cid);
|
|
164
|
-
setChatModelForBackend(cid, beId, undefined);
|
|
165
|
-
const { model: resolvedDefault } = await resolveActiveModelForChat(
|
|
166
|
-
cid,
|
|
167
|
-
be,
|
|
168
|
-
beId,
|
|
169
|
-
config,
|
|
170
|
-
);
|
|
171
|
-
toast = resolvedDefault
|
|
172
|
-
? `Model reset to default (${resolvedDefault})`
|
|
173
|
-
: `Model reset — no default available, pick one`;
|
|
174
|
-
} else if (action.kind === "toggle-free") {
|
|
175
|
-
const next = toggleChatFreeOnly(cid);
|
|
176
|
-
toast = `Free only: ${next ? "on" : "off"}`;
|
|
177
|
-
} else if (action.kind === "menu") {
|
|
178
|
-
viewAfter = "menu";
|
|
179
|
-
} else if (action.kind === "browse") {
|
|
180
|
-
viewAfter = "browse";
|
|
181
|
-
} else if (action.kind === "nav-back-to-providers") {
|
|
182
|
-
viewAfter = "browse";
|
|
183
|
-
browseBackToGroups = true;
|
|
184
|
-
browsePage = 1;
|
|
185
|
-
} else if (action.kind === "nav-provider") {
|
|
186
|
-
viewAfter = "browse";
|
|
187
|
-
browseProvider = action.provider;
|
|
188
|
-
browsePage = 1;
|
|
189
|
-
} else if (action.kind === "nav-page") {
|
|
190
|
-
viewAfter = "browse";
|
|
191
|
-
browsePage = action.page;
|
|
192
|
-
browseFilter = action.filter;
|
|
193
|
-
browseProvider = action.provider;
|
|
194
|
-
} else if (action.kind === "nav-filter") {
|
|
195
|
-
// Legacy support — current UX promotes free-toggle on the main
|
|
196
|
-
// menu, but a `model:nav:filter:*` payload from an old message
|
|
197
|
-
// still routes to the browse view with that filter applied.
|
|
198
|
-
viewAfter = "browse";
|
|
199
|
-
browseFilter = action.filter;
|
|
200
|
-
browsePage = 1;
|
|
201
|
-
} else if (action.kind === "backends") {
|
|
202
|
-
viewAfter = "backends";
|
|
203
|
-
} else if (action.kind === "backend-select") {
|
|
204
|
-
// Rebind chat to the chosen backend. Verify the requested id
|
|
205
|
-
// is on the enabled list to keep the menu and the operation
|
|
206
|
-
// in sync — `config.enabledBackends` is a UX filter and we
|
|
207
|
-
// honour it here too.
|
|
208
|
-
const available = listAvailableBackends(config);
|
|
209
|
-
if (!available.some((b) => b.id === action.backendId)) {
|
|
210
|
-
await answerCallbackQuerySafe(ctx, {
|
|
211
|
-
text: "Backend not available",
|
|
212
|
-
});
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
// Resolve the outgoing backend BEFORE rebinding — afterwards this
|
|
216
|
-
// chat already points at the new one and the old in-process state
|
|
217
|
-
// would be unreachable.
|
|
218
|
-
const previousBackend = resolveBackendForChat(cid, gateway);
|
|
219
|
-
const result = await rebindChat(cid, action.backendId, config);
|
|
220
|
-
if (!result.ok) {
|
|
221
|
-
await answerCallbackQuerySafe(ctx, {
|
|
222
|
-
text: result.error?.slice(0, 200) ?? "Rebind failed",
|
|
223
|
-
});
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
setChatBackend(cid, action.backendId);
|
|
227
|
-
// Switching backends drops the previous backend's per-chat
|
|
228
|
-
// session state (it's not portable across backends). We DO
|
|
229
|
-
// NOT clear `modelByBackend` — keeping each backend's prior
|
|
230
|
-
// pick means switching back-and-forth restores each side's
|
|
231
|
-
// last choice automatically (Codex chat keeps gpt-5.5,
|
|
232
|
-
// OpenRouter chat keeps owl-alpha, etc).
|
|
233
|
-
resetSession(cid);
|
|
234
|
-
clearHistory(cid);
|
|
235
|
-
resetPulseCheckpoint(cid);
|
|
236
|
-
handOffBackendSession(cid, previousBackend, gateway);
|
|
237
|
-
const label =
|
|
238
|
-
available.find((b) => b.id === action.backendId)?.label ??
|
|
239
|
-
action.backendId;
|
|
240
|
-
// Toast names the model the new backend will actually run
|
|
241
|
-
// (per-chat slot if remembered, else canonical / operator
|
|
242
|
-
// default; "no default" if catalog-driven with no config).
|
|
243
|
-
const newBackend = resolveBackendForChat(cid, gateway);
|
|
244
|
-
const { model: resolvedNewModel } = await resolveActiveModelForChat(
|
|
245
|
-
cid,
|
|
246
|
-
newBackend,
|
|
247
|
-
action.backendId,
|
|
248
|
-
config,
|
|
249
|
-
);
|
|
250
|
-
toast = resolvedNewModel
|
|
251
|
-
? `Backend: ${label} (model: ${resolvedNewModel})`
|
|
252
|
-
: `Backend: ${label} — no default model, /model to pick one`;
|
|
253
|
-
viewAfter = "menu";
|
|
254
|
-
} else if (action.kind === "backend-default") {
|
|
255
|
-
// Drop the per-chat backend override; chat reverts to the
|
|
256
|
-
// global chat-role backend. Per-backend model picks are
|
|
257
|
-
// preserved (modelByBackend stays intact) so reverting and
|
|
258
|
-
// switching back later still restores prior choices.
|
|
259
|
-
const previousBackend = resolveBackendForChat(cid, gateway);
|
|
260
|
-
await releaseChat(cid);
|
|
261
|
-
setChatBackend(cid, undefined);
|
|
262
|
-
resetSession(cid);
|
|
263
|
-
clearHistory(cid);
|
|
264
|
-
resetPulseCheckpoint(cid);
|
|
265
|
-
handOffBackendSession(cid, previousBackend, gateway);
|
|
266
|
-
// Resolve the now-default backend's model for the toast.
|
|
267
|
-
const defaultBackend = resolveBackendForChat(cid, gateway);
|
|
268
|
-
const defaultBackendId = getBackendIdForChat(cid);
|
|
269
|
-
const { model: resolvedRoleModel } = await resolveActiveModelForChat(
|
|
270
|
-
cid,
|
|
271
|
-
defaultBackend,
|
|
272
|
-
defaultBackendId,
|
|
273
|
-
config,
|
|
274
|
-
);
|
|
275
|
-
toast = resolvedRoleModel
|
|
276
|
-
? `Backend reset to default (model: ${resolvedRoleModel})`
|
|
277
|
-
: `Backend reset to default — no model picked, /model to pick one`;
|
|
278
|
-
viewAfter = "menu";
|
|
279
|
-
}
|
|
77
|
+
const outcome = await handler(ctx, action, cid, deps);
|
|
78
|
+
if (!outcome) return;
|
|
280
79
|
|
|
281
80
|
// Selection / reset / toggle confirmations toast briefly.
|
|
282
|
-
if (toast !== undefined) {
|
|
283
|
-
await answerCallbackQuerySafe(ctx, { text: toast });
|
|
81
|
+
if (outcome.toast !== undefined) {
|
|
82
|
+
await answerCallbackQuerySafe(ctx, { text: outcome.toast });
|
|
284
83
|
} else {
|
|
285
84
|
await answerCallbackQuerySafe(ctx);
|
|
286
85
|
}
|
|
287
86
|
|
|
288
87
|
// Re-render the message in the appropriate view.
|
|
289
|
-
|
|
290
|
-
const view = await buildModelMenuViewForChat(cid, config, gateway);
|
|
291
|
-
if (!view) return;
|
|
292
|
-
await editOrIgnoreSame(
|
|
293
|
-
ctx,
|
|
294
|
-
renderModelMenuText(view.state),
|
|
295
|
-
renderModelMenuKeyboard(view.state),
|
|
296
|
-
);
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
if (viewAfter === "backends") {
|
|
301
|
-
const menu = buildBackendMenuViewForChat(cid, config);
|
|
302
|
-
await editOrIgnoreSame(
|
|
303
|
-
ctx,
|
|
304
|
-
renderBackendMenuText({
|
|
305
|
-
activeBackend: menu.activeBackend,
|
|
306
|
-
hasBackendOverride: menu.hasBackendOverride,
|
|
307
|
-
defaultBackendLabel: menu.defaultBackendLabel,
|
|
308
|
-
}),
|
|
309
|
-
renderBackendMenuKeyboard({
|
|
310
|
-
available: menu.available,
|
|
311
|
-
activeBackendId: menu.activeBackend.id,
|
|
312
|
-
hasBackendOverride: menu.hasBackendOverride,
|
|
313
|
-
}),
|
|
314
|
-
);
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// browse view — controller picks the per-chat backend and
|
|
319
|
-
// applies the chat's freeOnly preference when the caller
|
|
320
|
-
// doesn't override the filter explicitly. `browseBackToGroups`
|
|
321
|
-
// means "drop any cached provider drill" so we omit `provider`.
|
|
322
|
-
const browse = await buildModelBrowseViewForChat(
|
|
323
|
-
cid,
|
|
324
|
-
config,
|
|
325
|
-
{
|
|
326
|
-
...(browseFilter !== undefined ? { filter: browseFilter } : {}),
|
|
327
|
-
...(browsePage !== undefined ? { page: browsePage } : {}),
|
|
328
|
-
...(browseProvider !== undefined && !browseBackToGroups
|
|
329
|
-
? { provider: browseProvider }
|
|
330
|
-
: {}),
|
|
331
|
-
},
|
|
332
|
-
gateway,
|
|
333
|
-
);
|
|
334
|
-
if (!browse) return;
|
|
335
|
-
const lines = [
|
|
336
|
-
`<b>Model:</b> <code>${escapeHtml(browse.activeDisplay)}</code>`,
|
|
337
|
-
...browse.modelDetails.map(escapeHtml),
|
|
338
|
-
...(browse.filter === "free" && browse.freeCount > 0
|
|
339
|
-
? ["<i>Filter: free-tier only.</i>"]
|
|
340
|
-
: []),
|
|
341
|
-
];
|
|
342
|
-
await editOrIgnoreSame(
|
|
343
|
-
ctx,
|
|
344
|
-
lines.join("\n"),
|
|
345
|
-
renderModelBrowseKeyboard(
|
|
346
|
-
browse.modelButtons,
|
|
347
|
-
{
|
|
348
|
-
page: browse.page,
|
|
349
|
-
totalPages: browse.totalPages,
|
|
350
|
-
filter: browse.filter,
|
|
351
|
-
freeCount: browse.freeCount,
|
|
352
|
-
totalCount: browse.totalCount,
|
|
353
|
-
},
|
|
354
|
-
browse.view,
|
|
355
|
-
browse.provider,
|
|
356
|
-
"model:menu",
|
|
357
|
-
),
|
|
358
|
-
);
|
|
88
|
+
await renderModelView(ctx, outcome.view, cid, deps);
|
|
359
89
|
}
|