--- name: omp-provider-row-sources description: "Add a provider/model row to ~/.omp/agent/models.yml by reading the harness's own catalog tables (provider name, api, window, cost, compat) instead of guessing or web-searching, then prove it with a scratch-HOME call." --- # Scope Adding (or correcting) a provider/model row in `~/.omp/agent/models.yml` for a provider the harness may already know: an upstream vendor API (DeepSeek, OpenAI, Anthropic, Moonshot, …) or a new local endpoint. The rule this skill exists for: **almost every field you are about to research on the web already exists in the installed bundle** — research there first, and the web only confirms it. Canonical install root (resolve the symlink first if the repo lives under a worktree): `/home/blank/.bun/install/global/node_modules/@oh-my-pi/`. # The three tables that own the facts | You want | Read | Why | |---|---|---| | The legal **provider name**, its URL marker, and which quirks the harness gates on it | `pi-catalog/src/hosts.ts` (minified in `dist/cli.js` as the `VIn` map; consumed by `pi-catalog/src/compat/resolve.ts`) | The provider key is a **lookup key, not a display name**. e.g. `deepseekDirect: { providers: ["deepseek"], urlMarkers: ["api.deepseek.com"] }`, `anthropic: { providers: ["anthropic"], urlMarkers: ["api.anthropic.com"] }`. A label matching neither loses the behaviour set silently (the `max_tokens` field name, the thinking `extraBody`, the thinking-disable mode). | | Model rows: ids, `contextWindow`, `maxTokens`, `cost` rates, per-model `compat` | `pi-catalog/src/provider-models/.ts` — `openai-compat.ts`, `anthropic-messages.ts`, …; factory helpers `openAiCompletionsDescriptor(...)` / `anthropicMessagesDescriptor(...)` | Rows are fetched from the provider's models-dev key, so the bundle carries the same numbers the vendor publishes (and often rates the vendor page hides). The descriptor's `compat:` block is the authored truth for that provider's transport shape — copy it, never simplify it. | | The **schema** itself (which keys are legal on a provider vs a model, and which are required) | `pi-coding-agent/src/config/models-config-schema-bundle.ts` (and the identical `dist/` copy) | Provider level and model level have *different* field sets. Cheaper and more correct than reading the minified bundle. | Also in `pi-coding-agent/src/config/`: `model-registry.ts` (how `models.yml` merges onto the built-in catalog — `#mergeCustomModels` → `mergeByModelKey`), `resolve-config-value.ts` (how `apiKey` values resolve). # Two traps this table pays for - **Provider-level `api` only accepts chat/judge transports.** Naming a runner API (`openai-speech`, `openai-images`, `openai-embeddings`) at provider level fails whole-file validation and disables *every* custom provider, not just yours (`omp models` prints "models.yml validation failed", lists only built-ins). Runner api belongs on the **model** entry; see the managed skill `omp-models-yml-runner-api-placement`. - **A `models.yml` entry is an overlay, not a replacement.** Adding one model to a provider the catalog already ships does not remove the provider's other rows — `omp models ` will show yours (yours wins on shared fields, including `cost` and `contextWindow`) plus the catalog rows. Same-id rows patch with the `replace` transport policy; a row that omits `contextWindow` inherits the bundled row's window and its policy tiers. # Secret handling (non-prose tracked file) Never inline the credential. `apiKey` resolution order is: leading `!` → command (`stdout` trimmed, cached for the process lifetime) → exact env-var name → literal. So write `apiKey: "!cat ~/.config/secrets/_api_key"` with mode 600. - A bare `sk-…` trips the pre-commit **shape** gate (every file type). - An unquoted `!` is a YAML tag — quote it. - A quoted command value also dodges the *assignment* gate: the gate's value charset rejects `~` and spaces, so only a literal key matches. # Procedure 1. Read the three tables above for the provider you are adding. Take the exact provider name, base URL, api, and compat from them; take `contextWindow` / `maxTokens` / `cost` from the model row and cite the source in a comment. 2. Write the secret to `~/.config/secrets/_api_key`, `chmod 600`. 3. Edit `models.yml`; for a repo-tracked config, follow that repo's own branch/log/index rules (in `dank/command-center`: branch → issue → PR). 4. Verify with a **scratch HOME**, because `models.yml` loads at process start and a live session keeps the old registry: ```bash mkdir -p /tmp/ompx/.omp/agent /tmp/ompx/.config cp agent/models.yml agent/config.yml /tmp/ompx/.omp/agent/ ln -sfn ~/.config/secrets /tmp/ompx/.config/secrets # ~ in a !cmd value = $HOME HOME=/tmp/ompx omp models --no-extensions | grep -c 'validation failed' # must be 0 HOME=/tmp/ompx omp --model '/:off' --tools bash --no-session \ --no-skills --no-rules --no-extensions --no-pty --no-lsp --no-title \ --cwd /tmp -p 'Reply with exactly the word OK and nothing else.' ``` Then the **negative control**, the step that separates a real auth success from a cached/built-in answer: `sed` the `baseUrl` to a dead port in the scratch copy and repeat — it must hang or error, not answer. Then `rm -rf /tmp/ompx` (it holds endpoint URLs). # Voice notes - Prefer the bundled schema file over grepping `dist/cli.js`; grep the bundle only when the source and the bundle disagree, or for a runtime default the schema does not state (e.g. `reasoningDisableMode` per provider family). - `cost` is optional in the schema. Omitting it makes the status line report `$0` forever; one rate per direction is all the schema models, so a peak/off-peak vendor table can only carry the peak value.