@@ -193,38 +193,32 @@ fn server_type_to_dynamic(st: &ServerTypeSummary) -> DynamicMachineType {
193193 }
194194}
195195
196- /// Fetch Hetzner regions dynamically with real-time availability
196+ /// Fetch Hetzner regions dynamically with REAL-TIME availability
197197///
198- /// Uses the /api/v1/cloud-runner/hetzner/options endpoint (same as frontend).
199- /// Returns regions with availability info directly from Hetzner API .
200- /// The agent uses this to make smart deployment decisions based on actual capacity .
198+ /// Uses the /api/deployments/availability/locations endpoint which checks
199+ /// Hetzner's datacenter API for actual capacity - not just what exists .
200+ /// Returns only regions where server types are CURRENTLY available .
201201///
202202/// # Errors
203203/// Returns error if credentials are missing or API call fails.
204204pub async fn get_hetzner_regions_dynamic (
205205 client : & PlatformApiClient ,
206206 project_id : & str ,
207207) -> HetznerFetchResult < Vec < DynamicCloudRegion > > {
208- match client. get_hetzner_options ( project_id) . await {
209- Ok ( options) => {
210- let regions: Vec < DynamicCloudRegion > = options. locations . iter ( ) . map ( |loc| {
211- DynamicCloudRegion {
212- id : loc. name . clone ( ) ,
213- name : loc. city . clone ( ) ,
214- location : loc. country . clone ( ) ,
215- network_zone : loc. network_zone . clone ( ) ,
216- // Find server types available at this location
217- available_server_types : options. server_types . iter ( )
218- . filter ( |st| st. available_locations . contains ( & loc. name ) )
219- . map ( |st| st. name . clone ( ) )
220- . collect ( ) ,
221- }
222- } ) . collect ( ) ;
223- HetznerFetchResult :: Success ( regions)
208+ match client. get_hetzner_locations ( project_id) . await {
209+ Ok ( locations) => {
210+ HetznerFetchResult :: Success ( locations. iter ( ) . map ( location_to_dynamic_region) . collect ( ) )
224211 }
225212 Err ( e) => {
226213 let error_msg = e. to_string ( ) ;
227- if error_msg. contains ( "credentials" ) || error_msg. contains ( "Unauthorized" ) || error_msg. contains ( "token" ) {
214+ // Check for various credential-related error patterns
215+ if error_msg. contains ( "credentials" )
216+ || error_msg. contains ( "Unauthorized" )
217+ || error_msg. contains ( "token" )
218+ || error_msg. contains ( "API token" )
219+ || error_msg. contains ( "401" )
220+ || error_msg. contains ( "412" ) // failedPrecondition
221+ {
228222 HetznerFetchResult :: NoCredentials
229223 } else {
230224 HetznerFetchResult :: ApiError ( error_msg)
@@ -233,42 +227,33 @@ pub async fn get_hetzner_regions_dynamic(
233227 }
234228}
235229
236- /// Fetch Hetzner server types dynamically with pricing and availability
230+ /// Fetch Hetzner server types dynamically with REAL-TIME availability and pricing
237231///
238- /// Uses the /api/v1/cloud-runner/hetzner/options endpoint (same as frontend).
239- /// Returns server types sorted by monthly price (cheapest first) with
240- /// real-time availability per region. The agent uses this for cost-optimized
241- /// resource selection.
232+ /// Uses the /api/deployments/availability/server-types endpoint which returns
233+ /// server types sorted by price with ACTUAL availability per datacenter.
234+ /// Only returns server types that are currently in stock.
242235///
243236/// # Errors
244237/// Returns error if credentials are missing or API call fails.
245238pub async fn get_hetzner_server_types_dynamic (
246239 client : & PlatformApiClient ,
247240 project_id : & str ,
248- _preferred_location : Option < & str > ,
241+ preferred_location : Option < & str > ,
249242) -> HetznerFetchResult < Vec < DynamicMachineType > > {
250- match client. get_hetzner_options ( project_id) . await {
251- Ok ( options) => {
252- let mut server_types: Vec < DynamicMachineType > = options. server_types . iter ( )
253- . filter ( |st| !st. deprecated )
254- . map ( |st| DynamicMachineType {
255- id : st. name . clone ( ) ,
256- name : st. name . clone ( ) ,
257- cores : st. cores ,
258- memory_gb : st. memory ,
259- disk_gb : st. disk ,
260- price_monthly : st. price_monthly ,
261- price_hourly : st. price_monthly / 730.0 , // Approximate hourly from monthly
262- available_in : st. available_locations . clone ( ) ,
263- } )
264- . collect ( ) ;
265- // Sort by price (cheapest first)
266- server_types. sort_by ( |a, b| a. price_monthly . partial_cmp ( & b. price_monthly ) . unwrap ( ) ) ;
267- HetznerFetchResult :: Success ( server_types)
243+ match client. get_hetzner_server_types ( project_id, preferred_location) . await {
244+ Ok ( server_types) => {
245+ HetznerFetchResult :: Success ( server_types. iter ( ) . map ( server_type_to_dynamic) . collect ( ) )
268246 }
269247 Err ( e) => {
270248 let error_msg = e. to_string ( ) ;
271- if error_msg. contains ( "credentials" ) || error_msg. contains ( "Unauthorized" ) || error_msg. contains ( "token" ) {
249+ // Check for various credential-related error patterns
250+ if error_msg. contains ( "credentials" )
251+ || error_msg. contains ( "Unauthorized" )
252+ || error_msg. contains ( "token" )
253+ || error_msg. contains ( "API token" )
254+ || error_msg. contains ( "401" )
255+ || error_msg. contains ( "412" ) // failedPrecondition
256+ {
272257 HetznerFetchResult :: NoCredentials
273258 } else {
274259 HetznerFetchResult :: ApiError ( error_msg)
0 commit comments