Files
ha-openai-compatible/custom_components/openai_compatible/strings.json
T
Adam SmithandClaude Opus 5 6a599e092a feat: ask for the model on the first screen of the endpoint flow
The model field was one screen too late. Reported directly: "this is where
I would have expected to see the model dropdown", on the AI Task init step
that showed only Name and the recommended checkbox.

- CONF_CHAT_MODEL moves from async_step_additional to async_step_init for
  conversation and ai_task_data, so all four endpoint types now ask on the
  first screen. stt and tts already did.
- UNSUPPORTED_MODELS is validated there too, since the recommended path
  creates the subentry from that step and would otherwise skip the check.
- model_list.default_model() picks what to preselect: RECOMMENDED_CHAT_MODEL
  only when the provider actually serves it (or discovery returned nothing),
  otherwise the first advertised model. Offering a stranger's gpt-4o-mini to
  a LiteLLM user preselects a model that fails on first use.

This is the trade I got wrong last commit. I kept the field on the second
step to avoid touching 71 ported tests, having judged them unfixable
mechanically. They were not: the 71 were ~13 functions under parametrize,
and test_subentry_switching's data is already a list of per-step dicts, so
moving a model between steps is moving a key between two dicts. Two scripted
passes moved 14 of them; 5 needed reasoning about (an error that now surfaces
on init rather than the next step, and four cases whose expected data gains
the now-always-recorded model).

async_fetch_model_ids now also catches non-OpenAIError exceptions. Found via
a ported test whose client is a MagicMock: the TypeError from awaiting it
escaped and broke the whole form. A real proxy returning a payload the SDK
cannot parse would do the same, and this runs inside a dialog with a working
free-text fallback, so nothing it raises justifies taking the form down.

Also fixes a string that was simply false on a third-party provider, again
reported from the UI: "Store requests and responses in OpenAI" / "stored by
OpenAI and visible in your OpenAI dashboard logs". It now describes the
Responses API store flag and says support varies. Swept the other 43
OpenAI-mentioning strings while there -- the rest are [%key:...%]
self-references or this fork's own action names, and no other option text
makes a claim about who stores the data.

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:18:02 -04:00

334 lines
16 KiB
JSON

{
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"credentials_in_url": "The base URL must not contain a username or password. Put authentication in the API key field instead.",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"invalid_base_url": "That does not look like a valid http(s) URL.",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"step": {
"reauth_confirm": {
"data": {
"api_key": "[%key:common::config_flow::data::api_key%]",
"base_url": "[%key:component::openai_compatible::config::step::user::data::base_url%]"
},
"data_description": {
"api_key": "[%key:component::openai_compatible::config::step::user::data_description::api_key%]",
"base_url": "[%key:component::openai_compatible::config::step::user::data_description::base_url%]"
},
"description": "Reauthentication required. Please enter your updated API key and confirm the API base URL."
},
"user": {
"data": {
"api_key": "[%key:common::config_flow::data::api_key%]",
"base_url": "API base URL",
"name": "[%key:common::config_flow::data::name%]"
},
"data_description": {
"api_key": "The API key for this endpoint. Leave blank if it does not require authentication.",
"base_url": "The base URL of the OpenAI-compatible API, including any version path such as /v1. The endpoint must implement the Responses API.",
"name": "A label for this provider, for example LiteLLM. Defaults to the endpoint's host."
},
"description": "Connect to an OpenAI-compatible API. One entry per server or provider; you add endpoints for individual models afterwards. See the [setup notes]({instructions_url}) for what the endpoint has to support."
}
}
},
"config_subentries": {
"ai_task_data": {
"abort": {
"entry_not_loaded": "[%key:component::openai_compatible::config_subentries::conversation::abort::entry_not_loaded%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
},
"entry_type": "AI task",
"error": {
"code_interpreter_minimal_reasoning": "[%key:component::openai_compatible::config_subentries::conversation::error::code_interpreter_minimal_reasoning%]",
"model_not_supported": "[%key:component::openai_compatible::config_subentries::conversation::error::model_not_supported%]",
"web_search_minimal_reasoning": "[%key:component::openai_compatible::config_subentries::conversation::error::web_search_minimal_reasoning%]"
},
"initiate_flow": {
"reconfigure": "Reconfigure AI task",
"user": "Add AI task"
},
"step": {
"additional": {
"data": {
"chat_model": "[%key:common::generic::model%]",
"max_tokens": "[%key:component::openai_compatible::config_subentries::conversation::step::additional::data::max_tokens%]",
"store_responses": "[%key:component::openai_compatible::config_subentries::conversation::step::additional::data::store_responses%]",
"temperature": "[%key:component::openai_compatible::config_subentries::conversation::step::additional::data::temperature%]",
"top_p": "[%key:component::openai_compatible::config_subentries::conversation::step::additional::data::top_p%]"
},
"data_description": {
"store_responses": "[%key:component::openai_compatible::config_subentries::conversation::step::additional::data_description::store_responses%]"
},
"title": "[%key:component::openai_compatible::config_subentries::conversation::step::additional::title%]"
},
"init": {
"data": {
"name": "[%key:common::config_flow::data::name%]",
"recommended": "[%key:component::openai_compatible::config_subentries::conversation::step::init::data::recommended%]"
}
},
"model": {
"data": {
"code_interpreter": "[%key:component::openai_compatible::config_subentries::conversation::step::model::data::code_interpreter%]",
"image_model": "[%key:component::openai_compatible::config_subentries::conversation::step::model::data::image_model%]",
"inline_citations": "[%key:component::openai_compatible::config_subentries::conversation::step::model::data::inline_citations%]",
"pro_mode": "[%key:component::openai_compatible::config_subentries::conversation::step::model::data::pro_mode%]",
"reasoning_effort": "[%key:component::openai_compatible::config_subentries::conversation::step::model::data::reasoning_effort%]",
"reasoning_summary": "[%key:component::openai_compatible::config_subentries::conversation::step::model::data::reasoning_summary%]",
"search_context_size": "[%key:component::openai_compatible::config_subentries::conversation::step::model::data::search_context_size%]",
"service_tier": "[%key:component::openai_compatible::config_subentries::conversation::step::model::data::service_tier%]",
"user_location": "[%key:component::openai_compatible::config_subentries::conversation::step::model::data::user_location%]",
"web_search": "[%key:component::openai_compatible::config_subentries::conversation::step::model::data::web_search%]"
},
"data_description": {
"code_interpreter": "[%key:component::openai_compatible::config_subentries::conversation::step::model::data_description::code_interpreter%]",
"image_model": "[%key:component::openai_compatible::config_subentries::conversation::step::model::data_description::image_model%]",
"inline_citations": "[%key:component::openai_compatible::config_subentries::conversation::step::model::data_description::inline_citations%]",
"pro_mode": "[%key:component::openai_compatible::config_subentries::conversation::step::model::data_description::pro_mode%]",
"reasoning_effort": "[%key:component::openai_compatible::config_subentries::conversation::step::model::data_description::reasoning_effort%]",
"reasoning_summary": "[%key:component::openai_compatible::config_subentries::conversation::step::model::data_description::reasoning_summary%]",
"search_context_size": "[%key:component::openai_compatible::config_subentries::conversation::step::model::data_description::search_context_size%]",
"service_tier": "[%key:component::openai_compatible::config_subentries::conversation::step::model::data_description::service_tier%]",
"user_location": "[%key:component::openai_compatible::config_subentries::conversation::step::model::data_description::user_location%]",
"web_search": "[%key:component::openai_compatible::config_subentries::conversation::step::model::data_description::web_search%]"
},
"title": "[%key:component::openai_compatible::config_subentries::conversation::step::model::title%]"
}
}
},
"conversation": {
"abort": {
"entry_not_loaded": "Cannot add things while the configuration is disabled.",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
},
"entry_type": "Conversation agent",
"error": {
"code_interpreter_minimal_reasoning": "Code interpreter is not supported with minimal reasoning effort",
"model_not_supported": "This model is not supported, please select a different model",
"web_search_minimal_reasoning": "Web search is currently not supported with minimal reasoning effort"
},
"initiate_flow": {
"reconfigure": "Reconfigure conversation agent",
"user": "Add conversation agent"
},
"step": {
"additional": {
"data": {
"chat_model": "[%key:common::generic::model%]",
"max_tokens": "Maximum tokens to return in response",
"store_responses": "Store requests and responses on the provider",
"temperature": "Temperature",
"top_p": "Top P"
},
"data_description": {
"store_responses": "Sets the Responses API store flag, asking the provider to keep requests and responses in its own logs. Support varies: OpenAI shows them in its dashboard, while many compatible servers ignore the flag entirely."
},
"title": "Additional settings"
},
"init": {
"data": {
"llm_hass_api": "[%key:common::config_flow::data::llm_hass_api%]",
"name": "[%key:common::config_flow::data::name%]",
"prompt": "[%key:common::config_flow::data::prompt%]",
"recommended": "Recommended model settings"
},
"data_description": {
"prompt": "Instruct how the LLM should respond. This can be a template."
}
},
"model": {
"data": {
"code_interpreter": "Enable code interpreter tool",
"image_model": "Image generation model",
"inline_citations": "Include links in web search results",
"pro_mode": "Pro mode",
"reasoning_effort": "Reasoning effort",
"reasoning_summary": "Reasoning summary",
"search_context_size": "Search context size",
"service_tier": "Service tier",
"user_location": "Include home location",
"web_search": "Enable web search"
},
"data_description": {
"code_interpreter": "This tool, also known as the python tool to the model, allows it to run code to answer questions",
"image_model": "The model to use when generating images",
"inline_citations": "If disabled, additional prompt is added to ask the model to not include source citations",
"pro_mode": "Perform more model work to improve reliability on difficult tasks and return a single final answer",
"reasoning_effort": "How many reasoning tokens the model should generate before creating a response to the prompt",
"reasoning_summary": "Controls the length and detail of reasoning summaries provided by the model",
"search_context_size": "High level guidance for the amount of context window space to use for the search",
"service_tier": "Controls the cost and response time",
"user_location": "Refine search results based on geography",
"web_search": "Allow the model to search the web for the latest information before generating a response"
},
"title": "Model-specific options"
}
}
},
"stt": {
"abort": {
"entry_not_loaded": "[%key:component::openai_compatible::config_subentries::conversation::abort::entry_not_loaded%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
},
"entry_type": "Speech-to-text",
"initiate_flow": {
"reconfigure": "Reconfigure speech-to-text service",
"user": "Add speech-to-text service"
},
"step": {
"init": {
"data": {
"chat_model": "Model",
"name": "[%key:common::config_flow::data::name%]",
"prompt": "[%key:common::config_flow::data::prompt%]"
},
"data_description": {
"chat_model": "The model to use to transcribe speech.",
"prompt": "Use this prompt to improve the quality of the transcripts. Translate to the pipeline language for best results. See the documentation for more details."
}
}
}
},
"tts": {
"abort": {
"entry_not_loaded": "[%key:component::openai_compatible::config_subentries::conversation::abort::entry_not_loaded%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
},
"entry_type": "Text-to-speech",
"initiate_flow": {
"reconfigure": "Reconfigure text-to-speech service",
"user": "Add text-to-speech service"
},
"step": {
"init": {
"data": {
"name": "[%key:common::config_flow::data::name%]",
"prompt": "[%key:common::config_flow::data::prompt%]",
"tts_speed": "Speed"
},
"data_description": {
"prompt": "Control aspects of speech, including accent, emotional range, intonation, impressions, speed of speech, tone, whispering, and more",
"tts_speed": "Speed of the generated speech"
}
}
}
}
},
"exceptions": {
"invalid_config_entry": {
"message": "Invalid config entry provided. Got {config_entry}"
}
},
"issues": {
"deprecated_generate_content": {
"description": "Action 'openai_compatible.generate_content' is deprecated and will be removed in the 2026.9.0 release. Please use the 'ai_task.generate_data' action instead",
"title": "Deprecated 'generate_content' action"
},
"deprecated_generate_image": {
"description": "Action 'openai_compatible.generate_image' is deprecated and will be removed in the 2026.9.0 release. Please use the 'ai_task.generate_image' action instead",
"title": "Deprecated 'generate_image' action"
},
"organization_verification_required": {
"description": "Your organization must be verified to use this model. Please go to {platform_settings} and select Verify Organization. If you just verified, it can take up to 15 minutes for access to propagate.",
"title": "Organization verification required"
}
},
"selector": {
"reasoning_effort": {
"options": {
"high": "[%key:common::state::high%]",
"low": "[%key:common::state::low%]",
"max": "Max",
"medium": "[%key:common::state::medium%]",
"minimal": "Minimal",
"none": "None",
"xhigh": "X-High"
}
},
"reasoning_summary": {
"options": {
"auto": "[%key:common::state::auto%]",
"concise": "Concise",
"detailed": "Detailed",
"off": "[%key:common::state::off%]"
}
},
"search_context_size": {
"options": {
"high": "[%key:common::state::high%]",
"low": "[%key:common::state::low%]",
"medium": "[%key:common::state::medium%]"
}
},
"service_tier": {
"options": {
"auto": "[%key:common::state::auto%]",
"default": "Standard",
"flex": "Flex",
"priority": "Priority"
}
},
"verbosity": {
"options": {
"high": "[%key:common::state::high%]",
"low": "[%key:common::state::low%]",
"medium": "[%key:common::state::medium%]"
}
}
},
"services": {
"generate_content": {
"description": "Sends a conversational query to ChatGPT including any attached image or PDF files (deprecated)",
"fields": {
"config_entry": {
"description": "The config entry to use for this action",
"name": "Config entry"
},
"filenames": {
"description": "List of files to upload",
"name": "Files"
},
"prompt": {
"description": "The prompt to send",
"name": "Prompt"
}
},
"name": "Generate content (deprecated)"
},
"generate_image": {
"description": "Turns a prompt into an image (deprecated)",
"fields": {
"config_entry": {
"description": "The config entry to use for this action",
"name": "Config entry"
},
"prompt": {
"description": "The text to turn into an image",
"example": "A photo of a dog",
"name": "Prompt"
},
"quality": {
"description": "The quality of the image that will be generated",
"name": "Quality"
},
"size": {
"description": "The size of the image to generate",
"name": "Size"
},
"style": {
"description": "The style of the generated image",
"name": "Style"
}
},
"name": "Generate image (deprecated)"
}
}
}