
























@@ -0,0 +1,180 @@
1+import type { ButtonInteraction, StringSelectMenuInteraction } from "@buape/carbon";
2+import type { ChatCommandDefinition, CommandArgs } from "openclaw/plugin-sdk/command-auth";
3+import {
4+applyModelOverrideToSessionEntry,
5+resolveStorePath,
6+updateSessionStore,
7+type OpenClawConfig,
8+} from "openclaw/plugin-sdk/config-runtime";
9+import type { ResolvedAgentRoute } from "openclaw/plugin-sdk/routing";
10+import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
11+import { withTimeout } from "openclaw/plugin-sdk/text-runtime";
12+import {
13+recordDiscordModelPickerRecentModel,
14+type DiscordModelPickerPreferenceScope,
15+} from "./model-picker-preferences.js";
16+import type { DispatchDiscordCommandInteraction } from "./native-command-dispatch.js";
17+import type { ThreadBindingManager } from "./thread-bindings.js";
18+19+type DiscordConfig = NonNullable<OpenClawConfig["channels"]>["discord"];
20+21+export type DiscordModelPickerSelectionCommand = {
22+prompt: string;
23+command: ChatCommandDefinition;
24+args?: CommandArgs;
25+};
26+27+export type DiscordModelPickerApplyResult =
28+| { status: "success"; effectiveModelRef: string; noticeMessage: string }
29+| { status: "mismatch"; effectiveModelRef: string; noticeMessage: string }
30+| { status: "rejected"; noticeMessage: string }
31+| { status: "timeout"; noticeMessage: string }
32+| { status: "failed"; noticeMessage: string };
33+34+async function persistDiscordModelPickerOverride(params: {
35+cfg: OpenClawConfig;
36+route: ResolvedAgentRoute;
37+provider: string;
38+model: string;
39+isDefault: boolean;
40+}): Promise<boolean> {
41+const storePath = resolveStorePath(params.cfg.session?.store, {
42+agentId: params.route.agentId,
43+});
44+let persisted = false;
45+await updateSessionStore(storePath, (store) => {
46+const entry = store[params.route.sessionKey];
47+if (!entry) {
48+return;
49+}
50+persisted =
51+applyModelOverrideToSessionEntry({
52+ entry,
53+selection: {
54+provider: params.provider,
55+model: params.model,
56+isDefault: params.isDefault,
57+},
58+markLiveSwitchPending: true,
59+}).updated || persisted;
60+});
61+return persisted;
62+}
63+64+export async function applyDiscordModelPickerSelection(params: {
65+interaction: ButtonInteraction | StringSelectMenuInteraction;
66+selectionCommand: DiscordModelPickerSelectionCommand;
67+dispatchCommandInteraction: DispatchDiscordCommandInteraction;
68+cfg: OpenClawConfig;
69+discordConfig: DiscordConfig;
70+accountId: string;
71+sessionPrefix: string;
72+threadBindings: ThreadBindingManager;
73+route: ResolvedAgentRoute;
74+resolvedModelRef: string;
75+selectedProvider: string;
76+selectedModel: string;
77+defaultProvider: string;
78+defaultModel: string;
79+preferenceScope: DiscordModelPickerPreferenceScope;
80+settleMs: number;
81+resolveCurrentModel: (route: ResolvedAgentRoute) => string;
82+}): Promise<DiscordModelPickerApplyResult> {
83+try {
84+const dispatchResult = await withTimeout(
85+params.dispatchCommandInteraction({
86+interaction: params.interaction,
87+prompt: params.selectionCommand.prompt,
88+command: params.selectionCommand.command,
89+commandArgs: params.selectionCommand.args,
90+cfg: params.cfg,
91+discordConfig: params.discordConfig,
92+accountId: params.accountId,
93+sessionPrefix: params.sessionPrefix,
94+preferFollowUp: true,
95+threadBindings: params.threadBindings,
96+suppressReplies: true,
97+}),
98+12000,
99+);
100+if (!dispatchResult.accepted) {
101+return {
102+status: "rejected",
103+noticeMessage: `❌ Failed to apply ${params.resolvedModelRef}. Try /model ${params.resolvedModelRef} directly.`,
104+};
105+}
106+107+const fallbackRoute = dispatchResult.effectiveRoute ?? params.route;
108+if (params.settleMs > 0) {
109+await new Promise((resolve) => setTimeout(resolve, params.settleMs));
110+}
111+112+let effectiveModelRef = params.resolveCurrentModel(fallbackRoute);
113+let persisted = effectiveModelRef === params.resolvedModelRef;
114+115+if (!persisted) {
116+logVerbose(
117+`discord: model picker override mismatch — expected ${params.resolvedModelRef} but read ${effectiveModelRef} from session key ${fallbackRoute.sessionKey}; attempting direct session override persist`,
118+);
119+try {
120+const directlyPersisted = await persistDiscordModelPickerOverride({
121+cfg: params.cfg,
122+route: fallbackRoute,
123+provider: params.selectedProvider,
124+model: params.selectedModel,
125+isDefault:
126+params.selectedProvider === params.defaultProvider &&
127+params.selectedModel === params.defaultModel,
128+});
129+await new Promise((resolve) => setTimeout(resolve, 100));
130+effectiveModelRef = params.resolveCurrentModel(fallbackRoute);
131+persisted = effectiveModelRef === params.resolvedModelRef;
132+if (!persisted) {
133+logVerbose(
134+`discord: direct session override persist failed — expected ${params.resolvedModelRef} but read ${effectiveModelRef} from session key ${fallbackRoute.sessionKey}`,
135+);
136+} else if (!directlyPersisted) {
137+logVerbose(
138+`discord: direct session override persist became a no-op because ${params.resolvedModelRef} was already present on re-read for session key ${fallbackRoute.sessionKey}`,
139+);
140+}
141+} catch (error) {
142+const message = error instanceof Error ? error.message : String(error);
143+logVerbose(
144+`discord: direct session override persist threw for session key ${fallbackRoute.sessionKey}: ${message}`,
145+);
146+}
147+}
148+149+if (persisted) {
150+await recordDiscordModelPickerRecentModel({
151+scope: params.preferenceScope,
152+modelRef: params.resolvedModelRef,
153+limit: 5,
154+}).catch(() => undefined);
155+}
156+157+return persisted
158+ ? {
159+status: "success",
160+ effectiveModelRef,
161+noticeMessage: `✅ Model set to ${params.resolvedModelRef}.`,
162+}
163+ : {
164+status: "mismatch",
165+ effectiveModelRef,
166+noticeMessage: `⚠️ Tried to set ${params.resolvedModelRef}, but current model is ${effectiveModelRef}.`,
167+};
168+} catch (error) {
169+if (error instanceof Error && error.message === "timeout") {
170+return {
171+status: "timeout",
172+noticeMessage: `⏳ Model change to ${params.resolvedModelRef} is still processing. Check /status in a few seconds.`,
173+};
174+}
175+return {
176+status: "failed",
177+noticeMessage: `❌ Failed to apply ${params.resolvedModelRef}. Try /model ${params.resolvedModelRef} directly.`,
178+};
179+}
180+}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。