@@ -52,6 +52,7 @@ import { STREAM_ERROR_FALLBACK_TEXT } from "../agents/stream-message-shared.js";
5252import { clearRuntimeConfigSnapshot , getRuntimeConfig } from "../config/io.js" ;
5353import type { ModelsConfig , ModelProviderConfig , OpenClawConfig } from "../config/types.js" ;
5454import { isTruthyEnvValue } from "../infra/env.js" ;
55+ import type { ModelRegistry } from "../llm/model-registry.js" ;
5556import { normalizeGoogleModelId } from "../plugin-sdk/google-model-id.js" ;
5657import { resolveProviderThinkingProfile } from "../plugins/provider-runtime.js" ;
5758import type { ProviderThinkingModelCompat } from "../plugins/provider-thinking.types.js" ;
@@ -197,6 +198,29 @@ function providerScopedModelRegistryProviders(params: {
197198 } ) ;
198199}
199200
201+ function filterGatewayLiveModelRefsByProvider (
202+ refs : readonly { provider : string ; id : string } [ ] ,
203+ providerFilter : ReadonlySet < string > | null ,
204+ ) : Array < { provider : string ; id : string } > {
205+ if ( ! providerFilter ) {
206+ return [ ...refs ] ;
207+ }
208+ const providers = new Set (
209+ [ ...providerFilter ] . map ( ( provider ) => normalizeProviderId ( provider ) ) . filter ( Boolean ) ,
210+ ) ;
211+ return refs . filter ( ( ref ) => providers . has ( normalizeProviderId ( ref . provider ) ) ) ;
212+ }
213+
214+ function isWantedSmallGatewayLiveModel ( params : {
215+ model : Pick < Model , "provider" | "id" > ;
216+ targetMatcher : ReturnType < typeof createLiveTargetMatcher > ;
217+ } ) : boolean {
218+ return (
219+ params . targetMatcher . matchesProvider ( params . model . provider ) &&
220+ isSmallLiveModelRef ( { provider : params . model . provider , id : params . model . id } )
221+ ) ;
222+ }
223+
200224function shouldSuppressGatewayLiveOllamaWarnings ( ) : boolean {
201225 return PROVIDERS !== null && ! PROVIDERS . has ( "ollama" ) ;
202226}
@@ -998,6 +1022,24 @@ function createExplicitLiveFallbackModel(provider: string, id: string): Model {
9981022 } ;
9991023}
10001024
1025+ function createGatewayLiveTestRegistry ( overrides : Partial < ModelRegistry > ) : ModelRegistry {
1026+ return {
1027+ find ( ) {
1028+ return undefined ;
1029+ } ,
1030+ getAll ( ) {
1031+ return [ ] ;
1032+ } ,
1033+ getAvailable ( ) {
1034+ return [ ] ;
1035+ } ,
1036+ hasConfiguredAuth ( ) {
1037+ return true ;
1038+ } ,
1039+ ...overrides ,
1040+ } ;
1041+ }
1042+
10011043describe ( "resolveExplicitLiveModelCandidates" , ( ) => {
10021044 it ( "uses targeted registry lookup for explicit provider/model filters" , ( ) => {
10031045 const model = createGatewayLiveTestModel ( "xai" , "grok-4.3" ) ;
@@ -1007,7 +1049,7 @@ describe("resolveExplicitLiveModelCandidates", () => {
10071049 env : { } ,
10081050 } ) ;
10091051 const candidates = resolveExplicitLiveModelCandidates ( {
1010- modelRegistry : {
1052+ modelRegistry : createGatewayLiveTestRegistry ( {
10111053 find ( provider , modelId ) {
10121054 expect ( provider ) . toBe ( "xai" ) ;
10131055 expect ( modelId ) . toBe ( "grok-4.3" ) ;
@@ -1016,7 +1058,7 @@ describe("resolveExplicitLiveModelCandidates", () => {
10161058 getAll ( ) {
10171059 throw new Error ( "explicit model lookup should not enumerate registry" ) ;
10181060 } ,
1019- } ,
1061+ } ) ,
10201062 modelFilter : new Set ( [ "xai/grok-4.3" ] ) ,
10211063 providerFilter : new Set ( [ "xai" ] ) ,
10221064 targetMatcher : matcher ,
@@ -1033,7 +1075,7 @@ describe("resolveExplicitLiveModelCandidates", () => {
10331075 env : { } ,
10341076 } ) ;
10351077 const candidates = resolveExplicitLiveModelCandidates ( {
1036- modelRegistry : {
1078+ modelRegistry : createGatewayLiveTestRegistry ( {
10371079 find ( provider , modelId ) {
10381080 expect ( provider ) . toBe ( "google" ) ;
10391081 expect ( modelId ) . toBe ( "gemini-3.1-pro-preview" ) ;
@@ -1042,7 +1084,7 @@ describe("resolveExplicitLiveModelCandidates", () => {
10421084 getAll ( ) {
10431085 throw new Error ( "explicit model lookup should not enumerate registry" ) ;
10441086 } ,
1045- } ,
1087+ } ) ,
10461088 modelFilter : new Set ( [ "google/gemini-3-pro-preview" ] ) ,
10471089 providerFilter : new Set ( [ "google" ] ) ,
10481090 targetMatcher : matcher ,
@@ -1058,7 +1100,7 @@ describe("resolveExplicitLiveModelCandidates", () => {
10581100 env : { } ,
10591101 } ) ;
10601102 const candidates = resolveExplicitLiveModelCandidates ( {
1061- modelRegistry : {
1103+ modelRegistry : createGatewayLiveTestRegistry ( {
10621104 find ( provider , modelId ) {
10631105 expect ( provider ) . toBe ( "openai" ) ;
10641106 expect ( modelId ) . toBe ( "gpt-5.5" ) ;
@@ -1067,7 +1109,7 @@ describe("resolveExplicitLiveModelCandidates", () => {
10671109 getAll ( ) {
10681110 throw new Error ( "explicit model lookup should not enumerate registry" ) ;
10691111 } ,
1070- } ,
1112+ } ) ,
10711113 modelFilter : new Set ( [ "openai/gpt-5.5" ] ) ,
10721114 providerFilter : new Set ( [ "openai" ] ) ,
10731115 targetMatcher : matcher ,
@@ -1089,14 +1131,14 @@ describe("resolveExplicitLiveModelCandidates", () => {
10891131
10901132 expect (
10911133 resolveExplicitLiveModelCandidates ( {
1092- modelRegistry : {
1134+ modelRegistry : createGatewayLiveTestRegistry ( {
10931135 find ( ) {
10941136 throw new Error ( "ambiguous model-only lookup should not use direct find" ) ;
10951137 } ,
10961138 getAll ( ) {
10971139 return [ ] ;
10981140 } ,
1099- } ,
1141+ } ) ,
11001142 modelFilter : new Set ( [ "grok-4.3" ] ) ,
11011143 providerFilter : null ,
11021144 targetMatcher : matcher ,
@@ -1154,6 +1196,36 @@ describe("providerScopedModelRegistryProviders", () => {
11541196 ) . toEqual ( [ "ollama" ] ) ;
11551197 } ) ;
11561198
1199+ it ( "filters prioritized small refs before dynamic lookup" , ( ) => {
1200+ expect (
1201+ filterGatewayLiveModelRefsByProvider (
1202+ listPrioritizedSmallLiveModelRefs ( ) ,
1203+ new Set ( [ "ollama" ] ) ,
1204+ ) ,
1205+ ) . toEqual ( [ { provider : "ollama" , id : "gemma3:4b" } ] ) ;
1206+ } ) ;
1207+
1208+ it ( "does not count small models outside a provider-scoped gateway sweep" , ( ) => {
1209+ const matcher = createLiveTargetMatcher ( {
1210+ providerFilter : new Set ( [ "ollama" ] ) ,
1211+ modelFilter : null ,
1212+ env : { } ,
1213+ } ) ;
1214+
1215+ expect (
1216+ isWantedSmallGatewayLiveModel ( {
1217+ model : createGatewayLiveTestModel ( "openrouter" , "qwen/qwen3.5-9b" ) ,
1218+ targetMatcher : matcher ,
1219+ } ) ,
1220+ ) . toBe ( false ) ;
1221+ expect (
1222+ isWantedSmallGatewayLiveModel ( {
1223+ model : createGatewayLiveTestModel ( "ollama" , "gemma3:4b" ) ,
1224+ targetMatcher : matcher ,
1225+ } ) ,
1226+ ) . toBe ( true ) ;
1227+ } ) ;
1228+
11571229 it ( "uses explicit provider-qualified model refs without enumerating the full registry" , ( ) => {
11581230 expect (
11591231 providerScopedModelRegistryProviders ( {
@@ -2157,10 +2229,7 @@ type GatewayModelSuiteParams = {
21572229 providerOverrides ?: Record < string , ModelProviderConfig > ;
21582230} ;
21592231
2160- type LiveModelRegistry = {
2161- find ( provider : string , modelId : string ) : Model | null | undefined ;
2162- getAll ( ) : Array < Model > ;
2163- } ;
2232+ type LiveModelRegistry = ModelRegistry ;
21642233
21652234function toGatewayLiveModel ( params : {
21662235 provider : string ;
@@ -2259,6 +2328,12 @@ function createStaticLiveModelRegistry(models: Array<Model>): LiveModelRegistry
22592328 getAll ( ) {
22602329 return models ;
22612330 } ,
2331+ getAvailable ( ) {
2332+ return models ;
2333+ } ,
2334+ hasConfiguredAuth ( ) {
2335+ return true ;
2336+ } ,
22622337 } ;
22632338}
22642339
@@ -3512,7 +3587,10 @@ describeLive("gateway live (dev agent, profile keys)", () => {
35123587 workspaceDir,
35133588 env : process . env ,
35143589 modelRegistry,
3515- refs : listPrioritizedSmallLiveModelRefs ( ) ,
3590+ refs : filterGatewayLiveModelRefsByProvider (
3591+ listPrioritizedSmallLiveModelRefs ( ) ,
3592+ PROVIDERS ,
3593+ ) ,
35163594 } ) ,
35173595 "[all-models] load dynamic small model refs" ,
35183596 ) ;
@@ -3543,7 +3621,7 @@ describeLive("gateway live (dev agent, profile keys)", () => {
35433621 wanted = filter
35443622 ? all . filter ( ( m ) => targetMatcher . matchesModel ( m . provider , m . id ) )
35453623 : useSmall
3546- ? all . filter ( ( m ) => isSmallLiveModelRef ( { provider : m . provider , id : m . id } ) )
3624+ ? all . filter ( ( m ) => isWantedSmallGatewayLiveModel ( { model : m , targetMatcher } ) )
35473625 : all . filter (
35483626 ( m ) =>
35493627 ! shouldExcludeProviderFromDefaultHighSignalLiveSweep ( {
0 commit comments