























@@ -1,6 +1,9 @@
11import type { APISelectMenuOption } from "discord-api-types/v10";
22import { ButtonStyle } from "discord-api-types/v10";
3-import type { ModelsProviderData } from "openclaw/plugin-sdk/models-provider-runtime";
3+import type {
4+ModelsProviderData,
5+ModelsRuntimeChoice,
6+} from "openclaw/plugin-sdk/models-provider-runtime";
47import { normalizeProviderId } from "openclaw/plugin-sdk/provider-model-shared";
58import {
69Button,
@@ -76,8 +79,10 @@ export type DiscordModelPickerModelViewParams = {
7679page?: number;
7780providerPage?: number;
7881currentModel?: string;
82+currentRuntime?: string;
7983pendingModel?: string;
8084pendingModelIndex?: number;
85+pendingRuntime?: string;
8186quickModels?: string[];
8287layout?: DiscordModelPickerLayout;
8388};
@@ -164,6 +169,59 @@ function createModelSelect(params: {
164169return new DiscordModelPickerSelect();
165170}
166171172+function getRuntimeChoices(params: {
173+data: ModelsProviderData;
174+provider: string;
175+}): ModelsRuntimeChoice[] {
176+const choices = params.data.runtimeChoicesByProvider?.get(normalizeProviderId(params.provider));
177+if (choices?.length) {
178+return choices;
179+}
180+return [
181+{
182+id: "pi",
183+label: "OpenClaw Pi Default",
184+description: "Use the built-in OpenClaw Pi runtime.",
185+},
186+];
187+}
188+189+function resolveSelectedRuntime(params: {
190+data: ModelsProviderData;
191+provider: string;
192+currentRuntime?: string;
193+pendingRuntime?: string;
194+}): string {
195+const choices = getRuntimeChoices({ data: params.data, provider: params.provider });
196+const allowed = new Set(choices.map((choice) => choice.id));
197+const pending = params.pendingRuntime?.trim();
198+if (pending && allowed.has(pending)) {
199+return pending;
200+}
201+const current = params.currentRuntime?.trim();
202+if (current && allowed.has(current)) {
203+return current;
204+}
205+return choices[0]?.id ?? "pi";
206+}
207+208+function resolveExplicitRuntimeState(params: {
209+choices: ModelsRuntimeChoice[];
210+currentRuntime?: string;
211+pendingRuntime?: string;
212+}): string | undefined {
213+const allowed = new Set(params.choices.map((choice) => choice.id));
214+const pending = params.pendingRuntime?.trim();
215+if (pending && allowed.has(pending)) {
216+return pending;
217+}
218+const current = params.currentRuntime?.trim();
219+if (current && current !== "auto" && current !== "default" && allowed.has(current)) {
220+return current;
221+}
222+return undefined;
223+}
224+167225function buildRenderedShell(
168226params: DiscordModelPickerRenderShellParams,
169227): DiscordModelPickerRenderedView {
@@ -241,8 +299,10 @@ function buildModelRows(params: {
241299providerPage: number;
242300modelPage: DiscordModelPickerModelPage;
243301currentModel?: string;
302+currentRuntime?: string;
244303pendingModel?: string;
245304pendingModelIndex?: number;
305+pendingRuntime?: string;
246306quickModels?: string[];
247307}): { rows: DiscordModelPickerRow[]; buttonRow: Row<Button> } {
248308const parsedCurrentModel = parseCurrentModelRef(params.currentModel);
@@ -279,6 +339,49 @@ function buildModelRows(params: {
279339]),
280340);
281341342+const runtimeChoices = getRuntimeChoices({
343+data: params.data,
344+provider: params.modelPage.provider,
345+});
346+const selectedRuntime = resolveSelectedRuntime({
347+data: params.data,
348+provider: params.modelPage.provider,
349+currentRuntime: params.currentRuntime,
350+pendingRuntime: params.pendingRuntime,
351+});
352+const stateRuntime = resolveExplicitRuntimeState({
353+choices: runtimeChoices,
354+currentRuntime: params.currentRuntime,
355+pendingRuntime: params.pendingRuntime,
356+});
357+358+if (runtimeChoices.length > 1) {
359+rows.push(
360+new Row([
361+createModelSelect({
362+customId: buildDiscordModelPickerCustomId({
363+command: params.command,
364+action: "runtime",
365+view: "models",
366+provider: params.modelPage.provider,
367+runtime: selectedRuntime,
368+page: params.modelPage.page,
369+providerPage: providerPage.page,
370+modelIndex: params.pendingModelIndex,
371+userId: params.userId,
372+}),
373+options: runtimeChoices.map((choice) => ({
374+label: choice.label,
375+value: choice.id,
376+default: choice.id === selectedRuntime,
377+ ...(choice.description ? { description: choice.description } : {}),
378+})),
379+placeholder: "Select runtime",
380+}),
381+]),
382+);
383+}
384+282385const selectedModelRef = parsedPendingModel ?? parsedCurrentModel;
283386const modelOptions: APISelectMenuOption[] = params.modelPage.items.map((model) => ({
284387label: model,
@@ -296,6 +399,7 @@ function buildModelRows(params: {
296399action: "model",
297400view: "models",
298401provider: params.modelPage.provider,
402+runtime: stateRuntime,
299403page: params.modelPage.page,
300404providerPage: providerPage.page,
301405userId: params.userId,
@@ -327,6 +431,7 @@ function buildModelRows(params: {
327431action: "cancel",
328432view: "models",
329433provider: params.modelPage.provider,
434+runtime: stateRuntime,
330435page: params.modelPage.page,
331436providerPage: providerPage.page,
332437userId: params.userId,
@@ -341,6 +446,7 @@ function buildModelRows(params: {
341446action: "reset",
342447view: "models",
343448provider: params.modelPage.provider,
449+runtime: stateRuntime,
344450page: params.modelPage.page,
345451providerPage: providerPage.page,
346452userId: params.userId,
@@ -358,6 +464,7 @@ function buildModelRows(params: {
358464action: "recents",
359465view: "recents",
360466provider: params.modelPage.provider,
467+runtime: stateRuntime,
361468page: params.modelPage.page,
362469providerPage: providerPage.page,
363470userId: params.userId,
@@ -376,6 +483,7 @@ function buildModelRows(params: {
376483action: "submit",
377484view: "models",
378485provider: params.modelPage.provider,
486+runtime: stateRuntime,
379487page: params.modelPage.page,
380488providerPage: providerPage.page,
381489modelIndex: params.pendingModelIndex,
@@ -457,14 +565,21 @@ export function renderDiscordModelPickerModelsView(
457565 providerPage,
458566 modelPage,
459567currentModel: params.currentModel,
568+currentRuntime: params.currentRuntime,
460569pendingModel: params.pendingModel,
461570pendingModelIndex: params.pendingModelIndex,
571+pendingRuntime: params.pendingRuntime,
462572quickModels: params.quickModels,
463573});
464574465575const defaultModel = `${params.data.resolvedDefault.provider}/${params.data.resolvedDefault.model}`;
466576const pendingLine = params.pendingModel
467- ? `Selected: ${params.pendingModel} (press Submit)`
577+ ? `Selected: ${params.pendingModel} · runtime ${resolveSelectedRuntime({
578+ data: params.data,
579+ provider: modelPage.provider,
580+ currentRuntime: params.currentRuntime,
581+ pendingRuntime: params.pendingRuntime,
582+ })} (press Submit)`
468583 : "Select a model, then press Submit.";
469584470585return buildRenderedShell({
@@ -483,6 +598,7 @@ export type DiscordModelPickerRecentsViewParams = {
483598data: ModelsProviderData;
484599quickModels: string[];
485600currentModel?: string;
601+runtime?: string;
486602provider?: string;
487603page?: number;
488604providerPage?: number;
@@ -522,6 +638,7 @@ export function renderDiscordModelPickerRecentsView(
522638view: "recents",
523639recentSlot: 1,
524640provider: params.provider,
641+runtime: params.runtime,
525642page: params.page,
526643providerPage: params.providerPage,
527644userId: params.userId,
@@ -544,6 +661,7 @@ export function renderDiscordModelPickerRecentsView(
544661view: "recents",
545662recentSlot: i + 2,
546663provider: params.provider,
664+runtime: params.runtime,
547665page: params.page,
548666providerPage: params.providerPage,
549667userId: params.userId,
@@ -563,6 +681,7 @@ export function renderDiscordModelPickerRecentsView(
563681action: "back",
564682view: "models",
565683provider: params.provider,
684+runtime: params.runtime,
566685page: params.page,
567686providerPage: params.providerPage,
568687userId: params.userId,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。