feat: configurable base_url threaded to all three client sites

Adds CONF_BASE_URL/DEFAULT_BASE_URL and threads base_url through every
AsyncOpenAI construction site: config_flow.validate_input,
__init__.async_setup_entry, and config_flow's web-search geolocation
helper (_get_location_data) — the easy one to miss, since it would
otherwise silently talk to api.openai.com when the user configured a
different endpoint.

Fixes upstream-ported fixtures (conftest.mock_config_entry,
test_config_flow's inline MockConfigEntry/data-schema assertions) to
supply base_url now that the config-flow schema requires it.

No translations/en.json exists in this repo (only strings.json), so
only strings.json got the new "base_url" UI strings.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01739Seac5eMAfNwFeiwV5r4
This commit is contained in:
Adam SmithandClaude Sonnet 5 committed 2026-09-17 17:50:38 -04:00
1 parent 8d2326d435
commit 564c52159f
7 files changed
+50 -4

No files matched your search

@@ -39,6 +39,7 @@ from homeassistant.helpers.httpx_client import get_async_client
from homeassistant.helpers.typing import UNDEFINED, ConfigType, UndefinedType
from .const import (
CONF_BASE_URL,
CONF_CHAT_MODEL,
CONF_FILENAMES,
CONF_MAX_TOKENS,
@@ -282,6 +283,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: OpenAIConfigEntry) -> bo
"""Set up OpenAI Conversation from a config entry."""
client = openai.AsyncOpenAI(
api_key=entry.data[CONF_API_KEY],
base_url=entry.data[CONF_BASE_URL],
http_client=get_async_client(hass),
)
@@ -44,6 +44,7 @@ from homeassistant.helpers.selector import (
from homeassistant.helpers.typing import VolDictType
from .const import (
CONF_BASE_URL,
CONF_CHAT_MODEL,
CONF_CODE_INTERPRETER,
CONF_IMAGE_MODEL,
@@ -67,6 +68,7 @@ from .const import (
CONF_WEB_SEARCH_TIMEZONE,
CONF_WEB_SEARCH_USER_LOCATION,
DEFAULT_AI_TASK_NAME,
DEFAULT_BASE_URL,
DEFAULT_CONVERSATION_NAME,
DEFAULT_STT_NAME,
DEFAULT_STT_PROMPT,
@@ -106,6 +108,7 @@ _LOGGER = logging.getLogger(__name__)
STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_BASE_URL, default=DEFAULT_BASE_URL): str,
vol.Required(CONF_API_KEY): str,
}
)
@@ -117,7 +120,9 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> None:
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
"""
client = openai.AsyncOpenAI(
api_key=data[CONF_API_KEY], http_client=get_async_client(hass)
api_key=data[CONF_API_KEY],
base_url=data[CONF_BASE_URL],
http_client=get_async_client(hass),
)
await client.models.list(timeout=10.0)
@@ -646,6 +651,7 @@ class OpenAISubentryFlowHandler(ConfigSubentryFlow):
if zone_home is not None:
client = openai.AsyncOpenAI(
api_key=self._get_entry().data[CONF_API_KEY],
base_url=self._get_entry().data[CONF_BASE_URL],
http_client=get_async_client(self.hass),
)
location_schema = vol.Schema(
@@ -9,6 +9,9 @@ from homeassistant.helpers import llm
DOMAIN = "openai_compatible"
LOGGER: logging.Logger = logging.getLogger(__package__)
CONF_BASE_URL = "base_url"
DEFAULT_BASE_URL = "https://api.openai.com/v1"
DEFAULT_CONVERSATION_NAME = "OpenAI Conversation"
DEFAULT_AI_TASK_NAME = "OpenAI AI Task"
DEFAULT_STT_NAME = "OpenAI STT"
@@ -21,10 +21,12 @@
},
"user": {
"data": {
"api_key": "[%key:common::config_flow::data::api_key%]"
"api_key": "[%key:common::config_flow::data::api_key%]",
"base_url": "API base URL"
},
"data_description": {
"api_key": "Your OpenAI API key."
"api_key": "Your OpenAI API key.",
"base_url": "The OpenAI-compatible endpoint, e.g. http://192.0.2.10:4000/v1 for LiteLLM. Must support the Responses API."
},
"description": "Set up OpenAI Conversation integration by providing your OpenAI API key. Instructions to obtain an API key can be found [here]({instructions_url})."
}