@@ -50,6 +50,19 @@ public async Task Probe_reports_zero_cache_ratio_when_usage_is_known_and_uncache
5050 Assert . Contains ( "cache_ratio_zero" , result . Diagnostics ) ;
5151 }
5252
53+ [ Fact ]
54+ public async Task Probe_resolves_provider_pricing_when_target_is_known ( )
55+ {
56+ var providers = await PricedProvidersAsync ( ) ;
57+ var router = new RecordingRouter ( Result ( "priced" , new ProviderUsage ( 10 , 2 , 12 , 5 , null , null ) ) ) ;
58+ var request = new CacheProbeRequest ( "coding" , JsonDocument . Parse ( "{}" ) . RootElement . Clone ( ) , 1 ) ;
59+
60+ var result = await CacheProbe . RunAsync ( router , providers , request ) ;
61+
62+ Assert . NotNull ( result . Attempts [ 0 ] . Cost ) ;
63+ Assert . Equal ( "estimated" , result . Attempts [ 0 ] . CostSource ) ;
64+ }
65+
5366 [ Fact ]
5467 public async Task Probe_null_arguments_are_rejected ( )
5568 {
@@ -96,6 +109,34 @@ private static async Task<IProviderManager> EmptyProvidersAsync()
96109 return manager ;
97110 }
98111
112+ private static async Task < IProviderManager > PricedProvidersAsync ( )
113+ {
114+ var factory = new FakeFactory ( ) ;
115+ var manager = new ProviderManager ( new InMemoryProviderStore ( ) , [ factory ] ) ;
116+ await manager . InitializeAsync ( ) ;
117+ await manager . AddAsync ( new ProviderDefinition (
118+ "priced" , "priced" , "fake" , "https://example.test" , "key" ,
119+ Models : [ "model" ] , DefaultModel : "model" ,
120+ InputPricePerMillion : 1m , CachedInputPricePerMillion : 0.1m , OutputPricePerMillion : 2m ) ) ;
121+ return manager ;
122+ }
123+
124+ private sealed class FakeFactory : IAiProviderFactory
125+ {
126+ public bool CanCreate ( ProviderDefinition definition ) => definition . Type == "fake" ;
127+ public IAiProvider Create ( ProviderDefinition definition ) => new FakeProvider ( definition ) ;
128+ }
129+
130+ private sealed class FakeProvider ( ProviderDefinition definition ) : IAiProvider
131+ {
132+ public ProviderDefinition Definition { get ; } = definition ;
133+ public ProviderHealth Health { get ; } = new ( ) ;
134+ public Task < ProviderResponse > SendChatAsync ( string model , JsonElement requestBody , bool stream , CancellationToken ct = default ) => Task . FromResult ( new ProviderResponse { Success = true , StatusCode = 200 } ) ;
135+ public Task < ProviderResponse > SendResponsesAsync ( string model , JsonElement requestBody , bool stream , CancellationToken ct = default ) => SendChatAsync ( model , requestBody , stream , ct ) ;
136+ public Task < IReadOnlyList < string > > ListModelsAsync ( CancellationToken ct = default ) => Task . FromResult < IReadOnlyList < string > > ( Definition . Models ?? [ ] ) ;
137+ public Task < ProviderConnectivityResult > CheckHealthAsync ( CancellationToken ct = default ) => Task . FromResult ( new ProviderConnectivityResult ( true ) ) ;
138+ }
139+
99140 private sealed class RecordingRouter ( RouterResult result ) : IAiRouter
100141 {
101142 public int ChatCalls { get ; private set ; }
@@ -107,16 +148,14 @@ public Task<RouterResult> ChatAsync(string model, JsonElement body, bool stream
107148 return Task . FromResult ( result ) ;
108149 }
109150
110- public Task < RouterResult > ChatAsync ( string model , JsonElement body , RouterRequestContext ? requestContext , bool stream = false , CancellationToken ct = default ) =>
111- ChatAsync ( model , body , stream , ct ) ;
151+ public Task < RouterResult > ChatAsync ( string model , JsonElement body , RouterRequestContext ? requestContext , bool stream = false , CancellationToken ct = default ) => ChatAsync ( model , body , stream , ct ) ;
112152
113153 public Task < RouterResult > ResponsesAsync ( string model , JsonElement body , bool stream = false , CancellationToken ct = default )
114154 {
115155 ResponsesCalls ++ ;
116156 return Task . FromResult ( result ) ;
117157 }
118158
119- public Task < RouterResult > ResponsesAsync ( string model , JsonElement body , RouterRequestContext ? requestContext , bool stream = false , CancellationToken ct = default ) =>
120- ResponsesAsync ( model , body , stream , ct ) ;
159+ public Task < RouterResult > ResponsesAsync ( string model , JsonElement body , RouterRequestContext ? requestContext , bool stream = false , CancellationToken ct = default ) => ResponsesAsync ( model , body , stream , ct ) ;
121160 }
122161}
0 commit comments