feat: allow an empty API key for unauthenticated endpoints
The OpenAI SDK rejects api_key=None, so all three client sites now
fall back to a PLACEHOLDER_API_KEY ("not-required") when the entry has
no key, letting an unauthenticated LiteLLM/proxy endpoint be configured
with the API key field left blank.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01739Seac5eMAfNwFeiwV5r4
This commit is contained in:
1 parent
914c89dc6b
commit
df44abf158
4 files changed
+26
-4
No files matched your search
@@ -54,6 +54,7 @@ from .const import (
|
||||
DEFAULT_TTS_NAME,
|
||||
DOMAIN,
|
||||
LOGGER,
|
||||
PLACEHOLDER_API_KEY,
|
||||
RECOMMENDED_AI_TASK_OPTIONS,
|
||||
RECOMMENDED_CHAT_MODEL,
|
||||
RECOMMENDED_MAX_TOKENS,
|
||||
@@ -282,7 +283,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: OpenAIConfigEntry) -> bool:
|
||||
"""Set up OpenAI Conversation from a config entry."""
|
||||
client = openai.AsyncOpenAI(
|
||||
api_key=entry.data[CONF_API_KEY],
|
||||
api_key=entry.data.get(CONF_API_KEY) or PLACEHOLDER_API_KEY,
|
||||
base_url=entry.data[CONF_BASE_URL],
|
||||
http_client=get_async_client(hass),
|
||||
)
|
||||
|
||||
@@ -74,6 +74,7 @@ from .const import (
|
||||
DEFAULT_STT_PROMPT,
|
||||
DEFAULT_TTS_NAME,
|
||||
DOMAIN,
|
||||
PLACEHOLDER_API_KEY,
|
||||
RECOMMENDED_AI_TASK_OPTIONS,
|
||||
RECOMMENDED_CHAT_MODEL,
|
||||
RECOMMENDED_CODE_INTERPRETER,
|
||||
@@ -110,7 +111,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,
|
||||
vol.Optional(CONF_API_KEY, default=""): str,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -122,7 +123,7 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> None:
|
||||
"""
|
||||
data[CONF_BASE_URL] = normalize_base_url(data[CONF_BASE_URL])
|
||||
client = openai.AsyncOpenAI(
|
||||
api_key=data[CONF_API_KEY],
|
||||
api_key=data.get(CONF_API_KEY) or PLACEHOLDER_API_KEY,
|
||||
base_url=data[CONF_BASE_URL],
|
||||
http_client=get_async_client(hass),
|
||||
)
|
||||
@@ -654,7 +655,7 @@ class OpenAISubentryFlowHandler(ConfigSubentryFlow):
|
||||
zone_home = self.hass.states.get(ENTITY_ID_HOME)
|
||||
if zone_home is not None:
|
||||
client = openai.AsyncOpenAI(
|
||||
api_key=self._get_entry().data[CONF_API_KEY],
|
||||
api_key=self._get_entry().data.get(CONF_API_KEY) or PLACEHOLDER_API_KEY,
|
||||
base_url=self._get_entry().data[CONF_BASE_URL],
|
||||
http_client=get_async_client(self.hass),
|
||||
)
|
||||
|
||||
@@ -11,6 +11,7 @@ LOGGER: logging.Logger = logging.getLogger(__package__)
|
||||
|
||||
CONF_BASE_URL = "base_url"
|
||||
DEFAULT_BASE_URL = "https://api.openai.com/v1"
|
||||
PLACEHOLDER_API_KEY = "not-required"
|
||||
|
||||
DEFAULT_CONVERSATION_NAME = "OpenAI Conversation"
|
||||
DEFAULT_AI_TASK_NAME = "OpenAI AI Task"
|
||||
|
||||
@@ -25,3 +25,22 @@ async def test_client_built_with_base_url(hass: HomeAssistant) -> None:
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_client.call_args.kwargs["base_url"] == "http://192.0.2.10:4000/v1"
|
||||
|
||||
|
||||
async def test_api_key_optional(hass: HomeAssistant) -> None:
|
||||
"""An endpoint needing no auth should still set up."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={CONF_BASE_URL: "http://192.0.2.10:4000/v1"},
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
with patch(
|
||||
"custom_components.openai_compatible.openai.AsyncOpenAI"
|
||||
) as mock_client:
|
||||
mock_client.return_value.platform_headers = lambda: {}
|
||||
mock_client.return_value.with_options.return_value.models.list = AsyncMock()
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_client.call_args.kwargs["api_key"] == "not-required"
|
||||
Reference in new issue
Block a user