Skip to content

Commit a5a30cf

Browse files
committed
fix(config): add per-provider api_base storage to survive provider switches
Problem: Custom api_base URLs were stored as a single global value. When switching providers, set_provider() deleted api_base from user_set (correct for the new provider's defaults), which meant the previous provider's custom base URL was lost on save(). Switching from llama.cpp back to github_copilot and then back would lose the llama.cpp custom endpoint entirely. Solution: Added per-provider api_bases storage, mirroring the existing api_keys pattern. Config now saves {"api_bases":{"llama.cpp":"http://..."}} alongside {"api_keys":{"llama.cpp":"1234"}}. set_provider() saves the outgoing provider's custom base before switching and loads the incoming provider's stored base. All cross-provider routing paths in APIManager, Models, and Config now check per-provider stored bases before falling back to static provider defaults. Changes: - Config.pm: api_bases in DEFAULT_CONFIG, get/set_provider_base() methods, set_provider() preserves outgoing base and loads incoming, load() checks stored bases before provider defaults - API/Config.pm: _set_base() stores per-provider, _set_provider() uses stored bases for session-only and global paths, display shows source - APIManager.pm: both cross-provider routing paths check stored bases - API/Models.pm: _fetch_provider_models() uses stored bases Testing: All 4 files pass perl -c syntax checks"
1 parent 8eba2c3 commit a5a30cf

4 files changed

Lines changed: 120 additions & 16 deletions

File tree

lib/CLIO/Core/APIManager.pm

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -906,10 +906,15 @@ sub get_model_capabilities {
906906
# Same provider as currently configured - use user's api_base (may be overridden)
907907
$api_base = $self->{api_base};
908908
} else {
909-
# Different provider - look up its default api_base
910-
require CLIO::Providers;
911-
my $provider_def = CLIO::Providers::get_provider($target_provider);
912-
$api_base = $provider_def ? $provider_def->{api_base} : $self->{api_base};
909+
# Different provider - check per-provider stored base, then provider default
910+
my $stored_base = $self->{config} ? $self->{config}->get_provider_base($target_provider) : undef;
911+
if ($stored_base) {
912+
$api_base = $stored_base;
913+
} else {
914+
require CLIO::Providers;
915+
my $provider_def = CLIO::Providers::get_provider($target_provider);
916+
$api_base = $provider_def ? $provider_def->{api_base} : $self->{api_base};
917+
}
913918
}
914919
} else {
915920
$api_base = $self->{api_base};
@@ -1513,9 +1518,15 @@ sub _prepare_endpoint_config {
15131518
# Model specifies a different provider - resolve its config
15141519
$endpoint_config = $self->_get_endpoint_config_for_provider($target_provider);
15151520

1516-
require CLIO::Providers;
1517-
my $provider_def = CLIO::Providers::get_provider($target_provider);
1518-
$endpoint = $provider_def ? $provider_def->{api_base} : $self->{api_base};
1521+
# Check per-provider stored base, then provider default
1522+
my $stored_base = $self->{config} ? $self->{config}->get_provider_base($target_provider) : undef;
1523+
if ($stored_base) {
1524+
$endpoint = $stored_base;
1525+
} else {
1526+
require CLIO::Providers;
1527+
my $provider_def = CLIO::Providers::get_provider($target_provider);
1528+
$endpoint = $provider_def ? $provider_def->{api_base} : $self->{api_base};
1529+
}
15191530
} else {
15201531
# Use current provider config
15211532
$endpoint_config = $self->get_endpoint_config();

lib/CLIO/Core/Config.pm

Lines changed: 85 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ use constant LOG_LEVEL => {
4444
use constant DEFAULT_CONFIG => {
4545
api_key => '',
4646
api_keys => {}, # Per-provider API keys: { google => 'AIza...', minimax => '...' }
47+
api_bases => {}, # Per-provider API base URLs: { 'llama.cpp' => 'http://localhost:9090/...' }
4748
provider => 'github_copilot', # Default provider
4849
editor => $ENV{EDITOR} || $ENV{VISUAL} || 'vim', # Default editor
4950
log_level => 'WARNING', # Default log level: ERROR, WARNING, INFO, DEBUG
@@ -170,8 +171,14 @@ sub load {
170171
if ($provider_config) {
171172
# Apply provider's api_base unless user explicitly set it
172173
unless ($self->{user_set}->{api_base}) {
173-
# For GitHub Copilot, try to get user-specific API endpoint
174-
if ($config{provider} eq 'github_copilot') {
174+
# Check per-provider stored base URL first
175+
my $api_bases = $config{api_bases} || {};
176+
my $stored_base = $api_bases->{$config{provider}};
177+
if ($stored_base) {
178+
$config{api_base} = $stored_base;
179+
log_debug('Config', "Using stored api_base for provider '$config{provider}': $config{api_base}");
180+
} elsif ($config{provider} eq 'github_copilot') {
181+
# For GitHub Copilot, try to get user-specific API endpoint
175182
my $user_api_base = $self->_get_copilot_user_api_endpoint();
176183
if ($user_api_base) {
177184
$config{api_base} = $user_api_base;
@@ -320,13 +327,31 @@ sub set_provider {
320327
return 0;
321328
}
322329

330+
# Save outgoing provider's custom api_base before switching
331+
# If the user had set a custom api_base for the current provider,
332+
# preserve it in per-provider storage so it survives the switch
333+
my $old_provider = $self->get('provider');
334+
if ($old_provider && $self->{user_set}->{api_base}) {
335+
my $current_base = $self->{config}->{api_base};
336+
if ($current_base) {
337+
$self->set_provider_base($old_provider, $current_base);
338+
log_debug('Config', "Saved custom api_base for outgoing provider '$old_provider'");
339+
}
340+
}
341+
323342
my $provider_config = get_provider($provider);
324343

325344
# Set provider name (this IS user-set - they chose the provider)
326345
$self->set('provider', $provider, 1); # Mark as user-set
327346

328-
# Apply provider defaults (these are NOT user-set - they come from provider definition)
329-
$self->{config}->{api_base} = $provider_config->{api_base};
347+
# Load the incoming provider's stored api_base, or fall back to provider default
348+
my $provider_base = $self->get_provider_base($provider);
349+
if ($provider_base) {
350+
$self->{config}->{api_base} = $provider_base;
351+
log_debug('Config', "Loaded custom api_base for provider '$provider' from api_bases");
352+
} else {
353+
$self->{config}->{api_base} = $provider_config->{api_base};
354+
}
330355

331356
# Store default model with provider prefix (e.g., "github_copilot/claude-haiku-4.5")
332357
my $default_model = $provider_config->{model};
@@ -353,7 +378,7 @@ sub set_provider {
353378
delete $self->{user_set}->{model};
354379

355380
log_debug('Config', "Switched to provider: $provider");
356-
log_debug('Config', "api_base: $provider_config->{api_base} (from provider)");
381+
log_debug('Config', "api_base: " . $self->{config}->{api_base} . " (from " . ($provider_base ? "stored" : "provider") . ")");
357382
log_debug('Config', "model: $provider_config->{model} (from provider)");
358383

359384
return 1;
@@ -433,6 +458,61 @@ sub list_provider_keys {
433458
return sort keys %$api_keys;
434459
}
435460

461+
=head2 get_provider_base($provider)
462+
463+
Get the API base URL for a specific provider from per-provider storage.
464+
465+
Arguments:
466+
- $provider: Provider name (e.g., 'llama.cpp', 'sam')
467+
468+
Returns: API base URL string or undef if not set
469+
470+
=cut
471+
472+
sub get_provider_base {
473+
my ($self, $provider) = @_;
474+
475+
return unless $provider;
476+
477+
my $api_bases = $self->{config}->{api_bases} || {};
478+
return $api_bases->{$provider};
479+
}
480+
481+
=head2 set_provider_base($provider, $url)
482+
483+
Set the API base URL for a specific provider.
484+
This stores the URL in per-provider storage and also sets it as current
485+
if the provider matches the current provider.
486+
487+
Arguments:
488+
- $provider: Provider name (e.g., 'llama.cpp', 'sam')
489+
- $url: API base URL value
490+
491+
Returns: 1 on success
492+
493+
=cut
494+
495+
sub set_provider_base {
496+
my ($self, $provider, $url) = @_;
497+
498+
# Initialize api_bases hash if needed
499+
$self->{config}->{api_bases} //= {};
500+
501+
# Store the base URL
502+
$self->{config}->{api_bases}{$provider} = $url;
503+
$self->{user_set}->{api_bases} = 1;
504+
505+
# If this is the current provider, also set api_base
506+
my $current_provider = $self->get('provider');
507+
if ($current_provider && $current_provider eq $provider) {
508+
$self->{config}->{api_base} = $url;
509+
}
510+
511+
log_debug('Config', "Stored API base for provider '$provider': $url");
512+
513+
return 1;
514+
}
515+
436516
=head2 get_model_alias($name)
437517
438518
Get the model value for a given alias name. Returns undef if not found.

lib/CLIO/UI/Commands/API/Config.pm

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,14 @@ sub _set_base {
249249
return;
250250
}
251251

252+
# Store per-provider when setting globally
253+
unless ($session_only) {
254+
my $current_provider = $self->{config}->get('provider');
255+
if ($current_provider) {
256+
$self->{config}->set_provider_base($current_provider, $value);
257+
}
258+
}
259+
252260
$self->_set_api_setting('api_base', $value, $session_only);
253261
$self->display_system_message("API base set to: $value" . ($session_only ? " (session only)" : " (saved)"));
254262
$self->_get_auth_helper()->reinit_api_manager();
@@ -360,11 +368,12 @@ sub _set_provider {
360368
$state->{api_config} ||= {};
361369
$state->{api_config}{provider} = $value;
362370

363-
# Also load the provider's api_base and api_key into config
371+
# Load the provider's api_base (per-provider stored or default) and api_key
364372
# so the session can actually use this provider
373+
my $stored_base = $self->{config}->get_provider_base($value);
365374
my $provider_config = CLIO::Providers::get_provider($value);
366375
if ($provider_config) {
367-
$state->{api_config}{api_base} = $provider_config->{api_base};
376+
$state->{api_config}{api_base} = $stored_base || $provider_config->{api_base};
368377
# Load per-provider API key
369378
my $provider_key = $self->{config}->get_provider_key($value);
370379
if ($provider_key) {
@@ -378,10 +387,12 @@ sub _set_provider {
378387
} else {
379388
if ($self->{config}->set_provider($value)) {
380389
my $config = $self->{config}->get_all();
390+
my $has_stored_base = $self->{config}->get_provider_base($value);
391+
my $base_source = $has_stored_base ? "stored" : "provider default";
381392

382393
if ($self->{config}->save()) {
383394
$self->display_system_message("Switched to provider: $value (saved)");
384-
$self->display_system_message(" API Base: " . $config->{api_base} . " (from provider)");
395+
$self->display_system_message(" API Base: " . $config->{api_base} . " (from $base_source)");
385396
$self->display_system_message(" Model: " . $config->{model} . " (from provider)");
386397
} else {
387398
$self->display_system_message("Switched to provider: $value (warning: failed to save)");

lib/CLIO/UI/Commands/API/Models.pm

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,9 @@ sub _fetch_provider_models {
210210
{ id => 'MiniMax-M2', name => 'MiniMax M2', description => 'Function calling, advanced reasoning (204.8k ctx, 131k out)' },
211211
];
212212
} else {
213-
my $api_base = $provider_def->{api_base} || '';
213+
# Use per-provider stored base URL if available, otherwise provider default
214+
my $stored_base = $self->{config}->get_provider_base($provider_name);
215+
my $api_base = $stored_base || $provider_def->{api_base} || '';
214216

215217
my $models_url;
216218
if ($api_base =~ m{openrouter\.ai}i) {

0 commit comments

Comments
 (0)