11/** Resolves configured model refs and tags for model-list rows. */
2+ import { DEFAULT_CONTEXT_TOKENS } from "../../agents/defaults.js" ;
3+ import { normalizeConfiguredProviderCatalogModelId } from "../../agents/model-ref-shared.js" ;
24import {
35 buildModelAliasIndex ,
46 resolveConfiguredModelRef ,
@@ -10,16 +12,65 @@ import {
1012} from "../../config/model-input.js" ;
1113import type { OpenClawConfig } from "../../config/types.openclaw.js" ;
1214import type { PluginMetadataSnapshot } from "../../plugins/plugin-metadata-snapshot.js" ;
15+ import type { ListRowModel } from "./list.model-row.js" ;
1316import type { ConfiguredEntry } from "./list.types.js" ;
14- import { createModelCatalogProviderAliasCanonicalizer } from "./provider-aliases.js" ;
15- import { DEFAULT_MODEL , DEFAULT_PROVIDER , modelKey } from "./shared.js" ;
17+ import {
18+ createModelCatalogProviderAliasCanonicalizer ,
19+ type ModelCatalogProviderAliasCanonicalizer ,
20+ } from "./provider-aliases.js" ;
21+ import { DEFAULT_MODEL , DEFAULT_PROVIDER } from "./shared.js" ;
1622
1723const DISPLAY_MODEL_PARSE_OPTIONS = { allowPluginNormalization : false } as const ;
1824
25+ export type ConfiguredProviderCandidate = {
26+ model : ListRowModel ;
27+ explicitApi : boolean ;
28+ } ;
29+
30+ /** Builds the canonical configured-provider rows once for every list source. */
31+ export function resolveConfiguredProviderCandidates (
32+ cfg : OpenClawConfig ,
33+ canonicalize : ModelCatalogProviderAliasCanonicalizer ,
34+ metadataSnapshot ?: Pick < PluginMetadataSnapshot , "manifestRegistry" > ,
35+ ) : Map < string , ConfiguredProviderCandidate > {
36+ const candidates = new Map < string , ConfiguredProviderCandidate > ( ) ;
37+ for ( const [ rawProvider , providerConfig ] of Object . entries ( cfg . models ?. providers ?? { } ) ) {
38+ const provider = canonicalize . provider ( rawProvider ) ;
39+ for ( const model of providerConfig . models ?? [ ] ) {
40+ const id = normalizeConfiguredProviderCatalogModelId ( provider , model . id , {
41+ manifestPlugins : metadataSnapshot ?. manifestRegistry . plugins ,
42+ } ) ;
43+ const key = canonicalize . key ( provider , id ) ;
44+ // Config order decides alias/case collisions so every source sees one stable row.
45+ if ( candidates . has ( key ) ) {
46+ continue ;
47+ }
48+ const input = model . input ?. filter ( ( item ) => item === "text" || item === "image" ) ?? [ ] ;
49+ candidates . set ( key , {
50+ model : {
51+ provider,
52+ id,
53+ name : model . name ?? model . id ,
54+ baseUrl : model . baseUrl ?? providerConfig . baseUrl ,
55+ input : input . length > 0 ? input : [ "text" ] ,
56+ contextWindow : model . contextWindow ?? DEFAULT_CONTEXT_TOKENS ,
57+ contextTokens : model . contextTokens ,
58+ } ,
59+ explicitApi : providerConfig . api !== undefined || model . api !== undefined ,
60+ } ) ;
61+ }
62+ }
63+ return candidates ;
64+ }
65+
1966/** Returns canonical configured model entries with default/fallback/image/configured tags. */
2067export function resolveConfiguredEntries (
2168 cfg : OpenClawConfig ,
2269 metadataSnapshot ?: Pick < PluginMetadataSnapshot , "manifestRegistry" > ,
70+ canonicalizeProviderAlias = createModelCatalogProviderAliasCanonicalizer ( {
71+ cfg,
72+ metadataSnapshot,
73+ } ) ,
2374) {
2475 const resolvedDefault = resolveConfiguredModelRef ( {
2576 cfg,
@@ -32,39 +83,29 @@ export function resolveConfiguredEntries(
3283 defaultProvider : DEFAULT_PROVIDER ,
3384 ...DISPLAY_MODEL_PARSE_OPTIONS ,
3485 } ) ;
35- const order : string [ ] = [ ] ;
86+ const order : Array < { key : string ; ref : { provider : string ; model : string } } > = [ ] ;
3687 const tagsByKey = new Map < string , Set < string > > ( ) ;
3788 const aliasesByKey = new Map < string , string [ ] > ( ) ;
38- const canonicalizeProviderAlias = createModelCatalogProviderAliasCanonicalizer ( {
39- cfg,
40- metadataSnapshot,
41- } ) ;
42-
4389 for ( const [ key , aliases ] of aliasIndex . byKey . entries ( ) ) {
44- aliasesByKey . set ( key , aliases ) ;
90+ const canonicalKey = canonicalizeProviderAlias . keyFromString ( key ) ;
91+ aliasesByKey . set ( canonicalKey , [
92+ ...new Set ( [ ...( aliasesByKey . get ( canonicalKey ) ?? [ ] ) , ...aliases ] ) ,
93+ ] ) ;
4594 }
4695
4796 const addEntry = ( ref : { provider : string ; model : string } , tag : string ) => {
4897 const canonicalRef = canonicalizeProviderAlias . ref ( ref ) ;
49- const key = modelKey ( canonicalRef . provider , canonicalRef . model ) ;
50- const originalKey = modelKey ( ref . provider , ref . model ) ;
51- if ( originalKey !== key ) {
52- // Preserve aliases attached to pre-canonical provider keys so display rows
53- // still show user-facing aliases after catalog provider canonicalization.
54- const aliases = aliasesByKey . get ( originalKey ) ;
55- if ( aliases ) {
56- aliasesByKey . set ( key , [ ...new Set ( [ ...( aliasesByKey . get ( key ) ?? [ ] ) , ...aliases ] ) ] ) ;
57- }
58- }
98+ const key = canonicalizeProviderAlias . key ( canonicalRef . provider , canonicalRef . model ) ;
5999 if ( ! tagsByKey . has ( key ) ) {
60100 tagsByKey . set ( key , new Set ( ) ) ;
61- order . push ( key ) ;
101+ order . push ( { key, ref : canonicalRef } ) ;
62102 }
63103 tagsByKey . get ( key ) ?. add ( tag ) ;
64104 } ;
65105
66106 const addResolvedModelRef = ( raw : string , tag : string ) => {
67107 const resolved = resolveModelRefFromString ( {
108+ cfg,
68109 raw,
69110 defaultProvider : DEFAULT_PROVIDER ,
70111 aliasIndex,
@@ -110,13 +151,10 @@ export function resolveConfiguredEntries(
110151 addEntry ( resolved . ref , "configured" ) ;
111152 }
112153
113- const entries : ConfiguredEntry [ ] = order . map ( ( key ) => {
114- const slash = key . indexOf ( "/" ) ;
115- const provider = slash === - 1 ? key : key . slice ( 0 , slash ) ;
116- const model = slash === - 1 ? "" : key . slice ( slash + 1 ) ;
154+ const entries : ConfiguredEntry [ ] = order . map ( ( { key, ref } ) => {
117155 return {
118156 key,
119- ref : { provider , model } ,
157+ ref,
120158 tags : tagsByKey . get ( key ) ?? new Set ( ) ,
121159 aliases : aliasesByKey . get ( key ) ?? [ ] ,
122160 } satisfies ConfiguredEntry ;
0 commit comments