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 {
69 Button ,
@@ -76,8 +79,10 @@ export type DiscordModelPickerModelViewParams = {
7679 page ?: number ;
7780 providerPage ?: number ;
7881 currentModel ?: string ;
82+ currentRuntime ?: string ;
7983 pendingModel ?: string ;
8084 pendingModelIndex ?: number ;
85+ pendingRuntime ?: string ;
8186 quickModels ?: string [ ] ;
8287 layout ?: DiscordModelPickerLayout ;
8388} ;
@@ -164,6 +169,59 @@ function createModelSelect(params: {
164169 return new DiscordModelPickerSelect ( ) ;
165170}
166171
172+ 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 (
168226 params : DiscordModelPickerRenderShellParams ,
169227) : DiscordModelPickerRenderedView {
@@ -241,8 +299,10 @@ function buildModelRows(params: {
241299 providerPage : number ;
242300 modelPage : DiscordModelPickerModelPage ;
243301 currentModel ?: string ;
302+ currentRuntime ?: string ;
244303 pendingModel ?: string ;
245304 pendingModelIndex ?: number ;
305+ pendingRuntime ?: string ;
246306 quickModels ?: string [ ] ;
247307} ) : { rows : DiscordModelPickerRow [ ] ; buttonRow : Row < Button > } {
248308 const parsedCurrentModel = parseCurrentModelRef ( params . currentModel ) ;
@@ -279,6 +339,49 @@ function buildModelRows(params: {
279339 ] ) ,
280340 ) ;
281341
342+ 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+
282385 const selectedModelRef = parsedPendingModel ?? parsedCurrentModel ;
283386 const modelOptions : APISelectMenuOption [ ] = params . modelPage . items . map ( ( model ) => ( {
284387 label : model ,
@@ -296,6 +399,7 @@ function buildModelRows(params: {
296399 action : "model" ,
297400 view : "models" ,
298401 provider : params . modelPage . provider ,
402+ runtime : stateRuntime ,
299403 page : params . modelPage . page ,
300404 providerPage : providerPage . page ,
301405 userId : params . userId ,
@@ -327,6 +431,7 @@ function buildModelRows(params: {
327431 action : "cancel" ,
328432 view : "models" ,
329433 provider : params . modelPage . provider ,
434+ runtime : stateRuntime ,
330435 page : params . modelPage . page ,
331436 providerPage : providerPage . page ,
332437 userId : params . userId ,
@@ -341,6 +446,7 @@ function buildModelRows(params: {
341446 action : "reset" ,
342447 view : "models" ,
343448 provider : params . modelPage . provider ,
449+ runtime : stateRuntime ,
344450 page : params . modelPage . page ,
345451 providerPage : providerPage . page ,
346452 userId : params . userId ,
@@ -358,6 +464,7 @@ function buildModelRows(params: {
358464 action : "recents" ,
359465 view : "recents" ,
360466 provider : params . modelPage . provider ,
467+ runtime : stateRuntime ,
361468 page : params . modelPage . page ,
362469 providerPage : providerPage . page ,
363470 userId : params . userId ,
@@ -376,6 +483,7 @@ function buildModelRows(params: {
376483 action : "submit" ,
377484 view : "models" ,
378485 provider : params . modelPage . provider ,
486+ runtime : stateRuntime ,
379487 page : params . modelPage . page ,
380488 providerPage : providerPage . page ,
381489 modelIndex : params . pendingModelIndex ,
@@ -457,14 +565,21 @@ export function renderDiscordModelPickerModelsView(
457565 providerPage,
458566 modelPage,
459567 currentModel : params . currentModel ,
568+ currentRuntime : params . currentRuntime ,
460569 pendingModel : params . pendingModel ,
461570 pendingModelIndex : params . pendingModelIndex ,
571+ pendingRuntime : params . pendingRuntime ,
462572 quickModels : params . quickModels ,
463573 } ) ;
464574
465575 const defaultModel = `${ params . data . resolvedDefault . provider } /${ params . data . resolvedDefault . model } ` ;
466576 const 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." ;
469584
470585 return buildRenderedShell ( {
@@ -483,6 +598,7 @@ export type DiscordModelPickerRecentsViewParams = {
483598 data : ModelsProviderData ;
484599 quickModels : string [ ] ;
485600 currentModel ?: string ;
601+ runtime ?: string ;
486602 provider ?: string ;
487603 page ?: number ;
488604 providerPage ?: number ;
@@ -522,6 +638,7 @@ export function renderDiscordModelPickerRecentsView(
522638 view : "recents" ,
523639 recentSlot : 1 ,
524640 provider : params . provider ,
641+ runtime : params . runtime ,
525642 page : params . page ,
526643 providerPage : params . providerPage ,
527644 userId : params . userId ,
@@ -544,6 +661,7 @@ export function renderDiscordModelPickerRecentsView(
544661 view : "recents" ,
545662 recentSlot : i + 2 ,
546663 provider : params . provider ,
664+ runtime : params . runtime ,
547665 page : params . page ,
548666 providerPage : params . providerPage ,
549667 userId : params . userId ,
@@ -563,6 +681,7 @@ export function renderDiscordModelPickerRecentsView(
563681 action : "back" ,
564682 view : "models" ,
565683 provider : params . provider ,
684+ runtime : params . runtime ,
566685 page : params . page ,
567686 providerPage : params . providerPage ,
568687 userId : params . userId ,
0 commit comments