--- name: omp-models-yml-runner-api-placement description: Add or edit a non-chat (tts/image/embedding) model row in ~/.omp/agent/models.yml without disabling every custom provider --- # Scope `~/.omp/agent/models.yml` (or `models.yaml`) on a workstation: adding a **runner** model row (TTS / image / embedding / rerank / video) alongside chat providers. # The landmine Provider-level `api` accepts **only chat/judge transports**: `openai-completions`, `openai-responses`, `openai-codex-responses`, `azure-openai-responses`, `anthropic-messages`, `bedrock-converse-stream`, `google-generative-ai`, `google-gemini-cli`, `google-vertex`, `openrouter-decisions`, `typesafe`. Naming a runner API (`openai-speech`, `openai-images`, `openai-embeddings`, …) at **provider** level fails whole-file validation, and the failure mode is not scoped to your provider: ``` Warning: models.yml validation failed — custom providers disabled Failed to load config file models, Schema error: providers..api: must be "openai-completions", … ``` Every custom provider disappears from the registry (fleet chat endpoints stop resolving) and `omp models` lists only built-in locals. Measured 2026-10-18. # Correct shape Runner api goes on the **model entry only**; `auth: none` satisfies the credential requirement; omit `contextWindow`/`maxTokens` (the positivity check fires only when they are provided — `omp models` still prints filled defaults, which the runner transport never reads). ```yaml "frank-tts (dankclaw)": baseUrl: http://192.168.0.143:7772/v1 # no `api:` here auth: none models: - id: frank-tts name: "frank-tts (dankclaw) — cloned voices" api: openai-speech # runner api on the model input: [text] ``` Roles live in `config.yml` (`modelRoles`), not in `models.yml`. Kind roles (`image`, `web`, `speech`, `dictation`, `judge`) take no `:thinking` suffix. # Verify after every edit (never assume the file loaded) ```bash omp models --kind tts --no-extensions # the new row must appear omp models --no-extensions | grep -E '' # and still be listed omp models | grep -c 'validation failed' # must be 0 ``` A run that lists only built-ins means your row broke the file — revert before yielding. # Speech transport contract (`openai-speech`) Body is `{model, input, response_format, voice?, speed?}`; `voice` is forwarded only when explicitly set; `sample_rate`/`bit_rate` **throw** (`openai-speech does not support sampleRate`). Codec comes from the `output_path` suffix; 15 000-character cap. An unknown `voice` name is a server error, not a fallback — and `auth: none` makes a dead endpoint read as *available*, so point `modelRoles.speech` at such a provider only after it has answered a real synthesis.