Persist the codex endpoint correction instead of losing it

In OAuth mode _get_valid_api_key() notices the configured endpoint is
not the ChatGPT backend, corrects base_url in memory and calls
_update_provider_endpoint() to write the correction back. For a global
provider that write called config.save_providers() -- a method that does
not exist. The AttributeError was swallowed by the surrounding
except and logged as a warning, so the correction only ever lived in
memory: providers.json kept the wrong endpoint and every restart re-ran
the same dance. codex_think sat like that, configured for
api.openai.com/v1 while every request went to chatgpt.com.

Rewrite providers.json in place instead, merging into the on-disk
document rather than serialising the in-memory config, so a concurrent
dashboard edit is not clobbered by stale state. The write goes through a
temp file and os.replace() so a crash mid-write cannot truncate the
config. Missing provider is now logged explicitly rather than silently
creating an entry, and the except logs a traceback so the next failure
of this kind is not invisible.

Bump version to 0.99.92.
Co-Authored-By: 's avatarClaude Opus 4.8 (1M context) <noreply@anthropic.com>
parent f674ba8e
...@@ -55,7 +55,7 @@ from .auth.qwen import QwenOAuth2 ...@@ -55,7 +55,7 @@ from .auth.qwen import QwenOAuth2
from .handlers import RequestHandler, RotationHandler, AutoselectHandler from .handlers import RequestHandler, RotationHandler, AutoselectHandler
from .utils import count_messages_tokens, split_messages_into_chunks, get_max_request_tokens_for_model, get_max_completion_tokens_for_model from .utils import count_messages_tokens, split_messages_into_chunks, get_max_request_tokens_for_model, get_max_completion_tokens_for_model
__version__ = "0.99.91" __version__ = "0.99.92"
__all__ = [ __all__ = [
# Config # Config
"config", "config",
......
...@@ -24,8 +24,10 @@ Why did the programmer quit his job? Because he didn't get arrays! ...@@ -24,8 +24,10 @@ Why did the programmer quit his job? Because he didn't get arrays!
import base64 import base64
import json import json
import logging import logging
import os
import time import time
import uuid import uuid
from pathlib import Path
from typing import Dict, List, Optional, Union, AsyncIterator, Tuple from typing import Dict, List, Optional, Union, AsyncIterator, Tuple
from openai import OpenAI from openai import OpenAI
...@@ -352,11 +354,39 @@ class CodexProviderHandler(BaseProviderHandler): ...@@ -352,11 +354,39 @@ class CodexProviderHandler(BaseProviderHandler):
) )
logger.info(f"CodexProviderHandler: Updated endpoint in database for user {self.user_id}: {new_endpoint}") logger.info(f"CodexProviderHandler: Updated endpoint in database for user {self.user_id}: {new_endpoint}")
else: else:
# Global provider: update in config file # Global provider: rewrite providers.json in place.
config.save_providers() #
logger.info(f"CodexProviderHandler: Updated endpoint in config file: {new_endpoint}") # This used to call config.save_providers(), which does not
# exist — the AttributeError was swallowed by the except
# below and logged as a warning, so the correction only ever
# lived in memory and every restart re-ran it against the
# same stale file. Merge into the on-disk document rather
# than serialising the whole config object, so a concurrent
# dashboard edit is not clobbered by stale in-memory state.
from ..app.startup import _providers_json_path
config_path = _providers_json_path()
with open(config_path) as f:
full_config = json.load(f)
providers = full_config.get('providers')
if not isinstance(providers, dict):
providers = full_config
if self.provider_id not in providers:
logger.warning(
f"CodexProviderHandler: {self.provider_id} not present in {config_path}; "
f"endpoint updated in memory only"
)
return
providers[self.provider_id]['endpoint'] = new_endpoint
save_path = Path.home() / '.aisbf' / 'providers.json'
save_path.parent.mkdir(parents=True, exist_ok=True)
tmp_path = save_path.with_suffix('.json.tmp')
with open(tmp_path, 'w') as f:
json.dump(full_config, f, indent=2)
os.replace(tmp_path, save_path)
logger.info(f"CodexProviderHandler: Updated endpoint in {save_path}: {new_endpoint}")
except Exception as e: except Exception as e:
logger.warning(f"CodexProviderHandler: Failed to update endpoint in configuration: {e}") logger.warning(f"CodexProviderHandler: Failed to update endpoint in configuration: {e}", exc_info=True)
# ========================================================================= # =========================================================================
# API Key Mode Methods (Standard OpenAI API) # API Key Mode Methods (Standard OpenAI API)
......
...@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" ...@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "aisbf" name = "aisbf"
version = "0.99.91" version = "0.99.92"
description = "AISBF - AI Service Broker Framework || AI Should Be Free - A modular proxy server for managing multiple AI provider integrations" description = "AISBF - AI Service Broker Framework || AI Should Be Free - A modular proxy server for managing multiple AI provider integrations"
readme = "README.md" readme = "README.md"
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
......
...@@ -106,7 +106,7 @@ class InstallCommand(_install): ...@@ -106,7 +106,7 @@ class InstallCommand(_install):
setup( setup(
name="aisbf", name="aisbf",
version="0.99.91", version="0.99.92",
author="AISBF Contributors", author="AISBF Contributors",
author_email="stefy@nexlab.net", author_email="stefy@nexlab.net",
description="AISBF - AI Service Broker Framework || AI Should Be Free - A modular proxy server for managing multiple AI provider integrations", description="AISBF - AI Service Broker Framework || AI Should Be Free - A modular proxy server for managing multiple AI provider integrations",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment