Files
Adam SmithandClaude Opus 5 b79164b60c docs: record the provider-genericity audit
Writes up what is still OpenAI-specific in this fork, since the base URL
being configurable is what makes it reach a compatible endpoint, not what
makes it behave correctly against one. Four findings, each with what it
actually costs:

1. service_tier, store and prompt_cache_retention are sent on every
   request whatever the provider is (entity.py:515-563); reasoning,
   include and text.verbosity are gated on OpenAI name prefixes.
2. 23 startswith() model-name gates plus the UNSUPPORTED_* lists infer
   capability from OpenAI naming, which fails in both directions: service
   tier, code interpreter and web search are offered to providers that
   cannot honour them, while reasoning effort/summary and verbosity are
   withheld from any model whose name does not look like OpenAI's.
3. manufacturer="OpenAI" and the "Error talking to OpenAI" error text.
4. Repair issues citing core's 2026.9.0 removal schedule, which has
   passed and was never this fork's commitment, and an OpenAI-only
   organization-verification repair.

Two things worth having written down beyond the list itself:

- A proxy set to drop incompatible parameters hides finding 1 entirely
  and makes finding 2 worse, because a dropped parameter turns a
  misconfigured option into a silent no-op rather than an error. Testing
  only against such a proxy therefore says nothing about portability, and
  a strict server is the honest target. This reorders the priorities:
  finding 2 first, since it is what the user sees and the failure is
  silent; finding 1 matters for setups without a normalising proxy.
- The prerequisite decision is recorded rather than assumed. There is no
  capability discovery in the OpenAI API -- GET /v1/models returns ids,
  not feature support -- so the three candidates (per-entry capability
  toggle, host detection, per-endpoint checkboxes) are laid out with
  their trade-offs and explicitly marked unsettled.

No behaviour change; the README links it from a short "known
OpenAI-specific behaviour" section so it is findable from the front page.

Local: 170 passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NxJYWgGJtCw13qUHZtG8ki
2026-09-17 23:32:21 -04:00

5.9 KiB

Provider genericity: audit and roadmap

This fork currently makes one thing configurable — the API base URL — and inherits everything else from homeassistant/components/openai_conversation. That is enough to reach an OpenAI-compatible endpoint, and not enough to behave correctly against one.

This document records what is still OpenAI-specific, what each item actually costs, and the design decision that has to be made before most of it can be fixed. It is an audit, not a plan of record: nothing here is scheduled.

Line references are against the tree at the time of writing; treat them as starting points, not gospel.

Why a proxy makes this hard to notice

A gateway such as LiteLLM can be configured to drop parameters the target provider does not support (drop_params). With that on, an endpoint absorbs OpenAI-only parameters silently and requests succeed — so the payload problem below never surfaces, while options that do nothing look like options that work.

That cuts both ways, and it is the single most important thing to understand about testing this integration:

  • A dropping proxy hides finding 1. No error is ever raised, so passing tests against it say nothing about portability.
  • A dropping proxy makes finding 2 worse. A parameter that is silently discarded turns a misconfigured option into a no-op rather than an error, with nothing in the UI or the log to indicate it had no effect.

A strict server (vLLM, llama.cpp, a minimal home-grown endpoint) is the honest test target, because it will reject what it does not implement.

1. OpenAI-only parameters on every request

entity.py, in the ResponseCreateParamsStreaming construction at line 515:

Parameter Sent when Line
service_tier always 520
store always 521
prompt_cache_retention: "24h" always, unless the model name matches UNSUPPORTED_EXTENDED_CACHE_RETENTION_MODELS 563
reasoning, include: ["reasoning.encrypted_content"] model name starts with o or gpt-5 525-544
text.verbosity model name starts with gpt-5 555-557

service_tier, store and prompt_cache_retention are therefore sent to every provider, whatever it is. A server that validates its input strictly returns 400; a dropping proxy discards them and the request succeeds.

Cost: breaks strict servers outright. Invisible behind a dropping proxy.

2. Capability inferred from OpenAI model names

There are 23 startswith(...) model-name gates across the component (config_flow.py 11, entity.py 9, __init__.py 2, ai_task.py 1), plus the UNSUPPORTED_* lists in const.py, which enumerate OpenAI model names.

Against a third-party model string, every one of those checks is meaningless — and it fails in both directions:

  • Options offered that the provider cannot honour. The UNSUPPORTED_* lists contain only OpenAI names, so a name like claude-sonnet-4-6 never matches one and the option is shown. Service tier ("Controls the cost and response time") is offered for every such model. Code interpreter and web search are OpenAI hosted tools, offered unconditionally, and almost no compatible server implements either.
  • Options withheld that the provider does support. Reasoning effort, reasoning summary and verbosity are only offered when the name starts with o or gpt-5. Point an endpoint at a reasoning-capable model whose name does not look like OpenAI's and there is no way to set a reasoning effort at all — the field never renders. A dropping proxy cannot compensate for this one, because the parameter is never sent.

Cost: the config UI misrepresents the endpoint in both directions. This is the finding to fix first: it is what a user sees, and a dropping proxy makes it silent rather than loud.

3. OpenAI branding in entities and errors

  • manufacturer="OpenAI" on every device (entity.py:492)
  • DEFAULT_CONVERSATION_NAME, DEFAULT_AI_TASK_NAME, DEFAULT_STT_NAME, DEFAULT_TTS_NAME in const.py — "OpenAI Conversation", "OpenAI AI Task", …
  • user-facing error text: OpenAI response incomplete (459), OpenAI response failed (473), OpenAI response error (475), Error talking to OpenAI (719-720)

Cost: cosmetic, but the errors are the ones a user reads while debugging their own provider, which makes them actively misleading.

4. Stale upstream notices

  • The deprecated_generate_content and deprecated_generate_image repair issues state the actions will be removed "in the 2026.9.0 release". That is Home Assistant core's schedule for its own integration, it has already passed, and this fork has made no such commitment.
  • organization_verification_required is an OpenAI-platform-specific repair that directs the user to OpenAI's settings page — meaningless for any other provider.

Cost: informational, but wrong, and repair issues are prominent in the UI.

The open design decision

The OpenAI API has no capability discovery: GET /v1/models returns ids, not feature support. So "which options should this endpoint offer" cannot be answered by asking the provider, and something has to stand in for it. The candidates:

  1. A per-entry capability toggle — "this endpoint supports OpenAI-specific features", defaulted on when the host is api.openai.com. Explicit, one decision per provider, and wrong only if the user sets it wrong.
  2. Host detection alone — infer from the base URL. No user burden; wrong for Azure or OpenAI reached through a proxy, which is a normal setup here.
  3. Per-endpoint feature checkboxes — let each endpoint declare what to send. Most precise, most tedious, and it asks the user to know their provider's API surface.

The shape that was recommended, and not agreed: a portable payload by default, with OpenAI extras behind option 1. Deciding this is the prerequisite for findings 1 and 2; findings 3 and 4 are independent and can be done at any time.