From 206c9acb4644dd1c40477a1042a1391c8b40fd7e Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 29 Oct 2026 14:47:34 -0700 Subject: [PATCH] feat: add the DeepSeek platform provider and deepseek-flash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refs #83 The first vendor-cloud chat row in agent/models.yml. The provider label is the bare word `deepseek` because pi-catalog/src/hosts.ts keys the direct-DeepSeek behaviour set (max_tokens field, thinking extraBody, thinking-disable mode) off that exact name and off api.deepseek.com; the house ` ()` pattern would not match and the row would lose the quirks the endpoint answers to. The key is command-backed (`!cat ~/.config/secrets/deepseek_api_key`, 600), so no sk-… value enters the tracked tree, and the row declares the model card's own pair: contextWindow 1048576 / maxTokens 262144, input [text, image], with the October 2026 peak rate card in cost. Docs move to the new omp-docs/core/deepseek-provider.md, indexed and logged in the same commit. --- agent/models.yml | 86 ++++++++++++++++++- omp-docs/core/deepseek-provider.md | 127 +++++++++++++++++++++++++++++ omp-docs/core/index.md | 1 + omp-docs/log.md | 17 ++++ 4 files changed, 228 insertions(+), 3 deletions(-) create mode 100644 omp-docs/core/deepseek-provider.md diff --git a/agent/models.yml b/agent/models.yml index 208ed3a..7494cdd 100644 --- a/agent/models.yml +++ b/agent/models.yml @@ -319,10 +319,90 @@ providers: api: openai-speech input: [text] + # Vendor cloud, not a fleet host: DeepSeek's own API. The label is the bare + # `deepseek` — NOT the house ` ()` pattern — because the harness + # matches provider names against a built-in service table + # (`src/config/model-registry.ts` profiles, `dist/cli.js` `VIn` map), and + # `deepseekDirect` lists `providers: ["deepseek"]` with urlMarkers + # `["api.deepseek.com"]`. A name like `deepseek (platform)` matches neither + # form, and the row loses the direct-DeepSeek behaviour set the catalog ships + # for its own built-in provider (see the compat block below). The baseUrl + # carries the host the label can't name. + "deepseek": + baseUrl: https://api.deepseek.com/v1 + api: openai-completions + # Command-backed, like every other credential in this file: the key lives in + # ~/.config/secrets/deepseek_api_key (600) and never enters the tracked + # tree, where the pre-commit shape gate would read it. Quoted because an + # unquoted leading `!` is a YAML tag. The resolved value is cached for the + # process lifetime — rotation is: rewrite the file, then restart omp. + # Written 2026-10-29 from the key Frank issued; every other workstation that + # pulls this repo needs its own copy of the file (nothing here carries it). + apiKey: "!cat ~/.config/secrets/deepseek_api_key" + models: + - id: deepseek-flash + name: "deepseek-flash" + # Official name (api-docs.deepseek.com): `deepseek-flash` is served by + # DeepSeek-V4.1-Flash-1017. The legacy names deepseek-v4-flash and + # deepseek-v4-flash-vision-exp are still accepted but retired — the + # model card says to use `deepseek-flash`; so does Frank. + # The model card states context_window 1M; 262,144 is its `max_tokens ≥ + # 256K` recommendation. The two are the engine's own pair: any prompt + + # budget over 1,048,574 is a refusal, and this row states them together + # so the harness clamps before the API has to. + contextWindow: 1048576 + maxTokens: 262144 + # Natively multimodal (V4.1 = image-text-to-text on the hub), despite + # the retired `-vision-exp` alias suggesting otherwise. + input: [text, image] + # Rate card, per 1M tokens, peak tier ($0.15/$0.60 off-peak is not + # modelled — the harness has one field per direction and a session + # cannot know which window it is billing into). Overriding these here is + # the only way a non-catalog row reports cost at all. + cost: + input: 0.3 + output: 1.2 + cacheRead: 0.006 + cacheWrite: 0 + compat: + # Copied from the harness's own built-in descriptor for this provider + # (`pi-catalog/src/provider-models/openai-compat.ts:7422`), never + # simplified — the same law as the WAN rows: + # V4 thinking mode rejects the `tool_choice` parameter outright; tool + # calls still work and default to auto when tools are present. + supportsToolChoice: false + # V4 has no `developer` role. + supportsDeveloperRole: false + # Reasoning arrives in `reasoning_content`; the harness's default for a + # direct DeepSeek provider is `zai-thinking-disabled` (an explicit + # `thinking: {"type":"disabled"}`), which is what this endpoint + # honours — `reasoning_effort: "none"` is not accepted here. Left unset + # so the built-in default stays in charge rather than a guess. + reasoningContentField: "reasoning_content" + # The documented request enables thinking with the `thinking` toggle + # AND `reasoning_effort`; when thinking is on, the harness strips the + # toggle from extraBody and keeps the effort value, so the request + # carries what the endpoint expects. + # Card-recommended sampler: temperature 1.0, top_p 1.0 (in thinking + # mode the API floors top_p at 0.95 — 1.0 is inside the allowed set). + # top_k is not accepted through the OpenAI surface, so it is not sent. + # extraBody is merged last and wins, so pinning the trio survives a + # server-side sampler edit — same law as the LAN rows. + extraBody: + thinking: + type: enabled + temperature: 1.0 + top_p: 1.0 + # `max_tokens` is the field this API speaks (not + # max_completion_tokens) — set by the built-in default, and this row + # cannot mis-name it because it names no override of its own. + # ─── WAN door ─────────────────────────────────────────────────────────── - # The only block in this file that answers OFF the home network. Every - # provider above is a LAN IP, and `modelRoles` (config.yml) still points at - # those, so away from home you must NAME one of the rows below: + # The fleet's own off-network door. Since 2026-10-29 the `deepseek` block + # above is also reachable from anywhere (a vendor URL, not a LAN IP), but it + # is not this door: this block is the route back to FRANK'S engines. Every + # fleet provider here is a LAN address, and `modelRoles` (config.yml) still + # points at those, so away from home you must NAME one of the rows below: # omp --model 'openwebui (franksdank.cloud)/frank-dragoon:medium' # The door is the OpenWebUI vhost `ai.franksdank.cloud` — Caddy → OWU on # Dankclaw:3005, Anubis-exempt because OWU enforces its own auth, public A diff --git a/omp-docs/core/deepseek-provider.md b/omp-docs/core/deepseek-provider.md new file mode 100644 index 0000000..6731cfe --- /dev/null +++ b/omp-docs/core/deepseek-provider.md @@ -0,0 +1,127 @@ +--- +type: reference +title: DeepSeek Platform Provider (deepseek/deepseek-flash) +description: The vendor-cloud row added to agent/models.yml on 2026-10-29 — why the provider label is the bare word `deepseek`, where its key lives, and the window/maxTokens/cost values it declares. +resource: https://api-docs.deepseek.com/ +tags: + - omp + - models-yml + - deepseek + - providers +verified: 2026-10-29 +timestamp: 2026-10-29 +--- + +# DeepSeek Platform Provider (`deepseek` / `deepseek-flash`) + +`agent/models.yml` gained its first vendor-cloud chat row on 2026-10-29: DeepSeek's +own hosted API, added alongside the four fleet endpoints and the OpenWebUI WAN +door. Use this page when a `deepseek/…` selector fails, when the key needs +rotating, or before adding another upstream-vendor row. + +```bash +omp --model 'deepseek/deepseek-flash:off' +``` + +## Why the label is the bare word `deepseek` + +The house pattern for provider labels is ` ()` — `llamacpp (dankclaw)`, +`openwebui (franksdank.cloud)`. This row breaks it deliberately: the harness +resolves a provider's behaviour set by matching the label (and the `baseUrl`) +against the catalog's host table, `pi-catalog/src/hosts.ts`, whose entry reads + +```ts +/** DeepSeek's first-party API only — gates direct-API quirks (max_tokens field, thinking extraBody). */ +deepseekDirect: { providers: ["deepseek"], urlMarkers: ["api.deepseek.com"] }, +``` + +A label of `deepseek (platform)` matches neither the provider list nor the URL +marker rule for the *name*, and the row loses the direct-DeepSeek quirks the +catalog gates behind that key — the `max_tokens` field name, the thinking +`extraBody`, and the thinking-disable mode, which on this provider must be +`zai-thinking-disabled` (`thinking: {"type":"disabled"}`), not the +`none-effort` the LAN rows need. The `baseUrl` carries the host the label +cannot name. + +The same table is why the provider's roster is not one row but four. A `models.yml` +entry is an **overlay** on the built-in catalog, keyed by `provider/id` +(`#mergeCustomModels` → `mergeByModelKey`), so `omp models deepseek` now prints: + +| Row | Source | +|---|---| +| `deepseek-flash` | the authored row — 1M context, 262K max-out | +| `deepseek-v4-flash` | built-in catalog row, untouched | +| `deepseek-v4-flash-vision-exp` | built-in catalog row, untouched | +| `deepseek-v4-pro` | built-in catalog row, untouched | + +The other three keep catalog windows and pricing; adding a model to the file does +not replace the provider. + +## What the row declares, and from where + +| Field | Value | Authority | +|---|---|---| +| `baseUrl` | `https://api.deepseek.com/v1` | official docs, OpenAI base_url | +| `api` | `openai-completions` | same | +| `id` | `deepseek-flash` | official model name — `deepseek-v4-flash` / `deepseek-v4-flash-vision-exp` are retired aliases that route to `DeepSeek-V4.1-Flash-1017` and bill at the Flash price | +| `contextWindow` | `1048576` | model card (`context_window 1M`) | +| `maxTokens` | `262144` | model card (`max_tokens ≥ 256K`) | +| `input` | `[text, image]` | V4.1 is natively image-text-to-text | +| `cost` | `0.3 / 1.2 / 0.006 / 0` per 1M (in / out / cache-read / cache-write) | October 2026 rate card, peak tier | +| `compat.supportsToolChoice` | `false` | V4 thinking mode rejects the `tool_choice` parameter; tool calls still work and default to `auto` | +| `compat.supportsDeveloperRole` | `false` | V4 has no `developer` role | +| `compat.reasoningContentField` | `reasoning_content` | same field every fleet engine uses | +| `compat.extraBody.thinking` | `{type: enabled}` | the documented request shape | + +Laws worth carrying forward: + +- **`maxTokens` and `contextWindow` are a pair.** The serving contract is + `prompt + max_tokens ≤ contextWindow`; the harness clamps + `max_tokens = min(maxTokens, contextWindow − est − 4096)`, so a declared pair + that exceeds the engine's own contract turns into a 400 rather than a + truncation. `1048576 / 262144` is the pair the card itself states, and it is + the pair the row declares. +- **Cost rates go on the row or nowhere.** `cost` is optional in the schema, and a + custom row that omits it reports `$0` in the status line forever. The schema + models one rate per direction, so the off-peak tier (`$0.15 / $0.60`) cannot be + expressed — peak is the value. +- **Sampling.** The card recommends `temperature 1.0 / top_p 1.0`; thinking mode + floors `top_p` at 0.95 server-side, and `top_k` is not accepted through the + OpenAI surface. The row pins `temperature: 1.0` + `top_p: 1.0` anyway — + `extraBody` is merged last and wins, so pinning it survives a server-side + sampler edit, exactly as the LAN rows do. +- **A row behind a proxy or a vendor façade copies its direct twin's compat + block**; this row has no LAN twin, so it copies the harness's own built-in + descriptor for the same provider (`pi-catalog/src/provider-models/openai-compat.ts`, + `openAiCompletionsDescriptor("deepseek", …)`) instead of inventing one. + +## The key + +`apiKey: "!cat ~/.config/secrets/deepseek_api_key"` — 600-mode file, same +convention as `gitea`, `kagi_api_key`, `hf_token`, and `openwebui_api_key`. The +rules are the ones in [WAN Model Access](/core/wan-model-access.md#the-key): a +literal `sk-…` in the tracked file trips the pre-commit shape gate, and a quoted +`!cmd` value is the only shape that both resolves and stays out of the gate's +assignment scan. Rotation is rewrite-then-restart; the resolved value is cached +for the process lifetime. Every workstation that pulls this repo needs its own +copy — nothing in the repo carries the value. + +## Verification performed 2026-10-29 + +- `omp models --no-extensions` from a scratch `HOME` containing the edited + `models.yml` + `config.yml`: zero `validation failed` lines, all four fleet + providers still listed, `deepseek (4)` present with `deepseek-flash 1M/262K`. +- Live call through the scratch `HOME` — the only proof that auth came from the + new `apiKey` line and not from `~/.omp/agent/auth.json`: + `omp --model 'deepseek/deepseek-flash:off' … -p 'Reply with exactly the word OK'` + → `OK` in 2.6 s. The same scratch tree with the `baseUrl` port poisoned to `:9` + hangs instead of answering, which is what rules out a cached or built-in answer. + +## See also + +- [WAN Model Access](/core/wan-model-access.md) — the credential law and the + off-network route to Frank's own engines; `deepseek` is reachable from anywhere + but is not that door. +- [Core config](/core/index.md) — the rest of `agent/config.yml` / `models.yml`. +- [OMP Subagent Limits](/core/subagent-limits.md) — `providers.maxInFlightRequests` + is set per provider id; the `deepseek` row currently has no cap. diff --git a/omp-docs/core/index.md b/omp-docs/core/index.md index 46401bc..ac097dd 100644 --- a/omp-docs/core/index.md +++ b/omp-docs/core/index.md @@ -16,6 +16,7 @@ These files define the structural conventions that govern the agent system itsel * [OMP Status Line Customization](statusline.md) — How presets, composer shapes, and segment arrays actually interact; the fleet's tuned bar. (moved from /pi/, 2026-10-19) * [OMP Subagent Limits & Fan-out Policy](subagent-limits.md) — Live caps: concurrency 2, no nested spawns (the `maxRecursionDepth: 0` trap), per-endpoint in-flight ceilings, isolation policy. (moved from /pi/, 2026-10-19) * [WAN Model Access](wan-model-access.md) — Driving the fleet's models from outside the home network through the OpenWebUI door: the command, the key file and its rotation law, what the door publishes, and the off-network failure modes. +* [DeepSeek Platform Provider](deepseek-provider.md) — The first vendor-cloud row in `agent/models.yml`: why its label is the bare word `deepseek`, the key file, and the window/maxTokens/cost values it declares. * [OMP Snapcompact — Bitmap-Frame Compaction](snapcompact.md) — What shape frank-archon actually reads, the measured recall/billing table, the `imageCountBudget` knob and the pricing that isn't, and the 85 % trigger law. **Change log:** the bundle keeps exactly one, one level up — [Bundle Change Log](../log.md). A directory here never has its own. diff --git a/omp-docs/log.md b/omp-docs/log.md index 0262f41..431cf65 100644 --- a/omp-docs/log.md +++ b/omp-docs/log.md @@ -22,6 +22,23 @@ not restated here — entry, then pointer. That is what the 2026-10-21 audit pas newest twelve entries: the facts stayed, the duplicates went, `git log --follow` keeps the long form. +## 2026-10-29 + +### A vendor-cloud provider must be named the way the harness names it + +`deepseek` / `deepseek-flash` joins `agent/models.yml` — Frank's DeepSeek platform +key in `~/.config/secrets/deepseek_api_key` (600, `!cat`-backed), a 1M/262K row, +the documented compat block. The label breaks the house ` ()` pattern on +purpose: the catalog's host table (`pi-catalog/src/hosts.ts`) keys the +direct-DeepSeek behaviour set off the literal name `deepseek` (or an +`api.deepseek.com` URL), so a prettier label silently loses the quirks that make +the endpoint answer — the `max_tokens` field, the thinking `extraBody`, and the +thinking-disable mode this API takes. Law: **a provider label is a lookup key, not +only a display name**; check the table before inventing one, and prove the row with +a scratch-HOME call (a poisoned-port control is what separates a real auth success +from a cached one). Page: [core/deepseek-provider.md](core/deepseek-provider.md). + + ## 2026-10-27 ### Snapcompact audited against frank-archon; the blind frame price is the real limit