Skip to content

feat: DeepSeek as a first-class provider pool - #44

Open
atyrode wants to merge 7 commits into
mainfrom
deepseek-provider
Open

feat: DeepSeek as a first-class provider pool#44
atyrode wants to merge 7 commits into
mainfrom
deepseek-provider

Conversation

@atyrode

@atyrode atyrode commented Aug 14, 2026

Copy link
Copy Markdown
Owner

Restructures the hard two-pool (OpenAI/Anthropic) assumptions into a provider registry so DeepSeek becomes a first-class pool across routing, usage, and account management — and catches up with omp 17.3 (security-reviewer role, task.agentAdvisor).

What changed

  • providers.go (new) — single registry table: pool letters, lane names, product/account labels, brand + paint colors, model prefixes, bucket bases, Metered/Required, skeleton windows, special tiers (spark@O0, fable@A4), service tiers. Ordered fallbackPoolOrder (O→A→D) and advisorPoolOrder (A→O→D) replace every otherPool() binary flip. Provider-id literals now live only here and in tests.
  • Catalog format__models__ grows a trailing provider column; the parser accepts 5/6/7-token rows, so old catalogs keep working. The lane facet is catalog-driven: ds-led/ds-only appear only when the catalog carries a D pool; pre-catalog runs keep the classic five lanes.
  • Generator — N-pool genCombo: pool-set validity (spark/fable gated on the lane hosting their pool), optional-pool ladder fill (one verified DeepSeek rung is enough; missing tiers borrow the nearest), relief tails (D tier-2 appended to heavyweight led/mixed chains), vision purity exception (ds-only crosses for images since DeepSeek is text-only), per-pool advisor tables. generate init scaffolds the D pool when candidates exist and bench-probes deepseek; the "need 3 rungs" error applies to Required pools only.
  • Usage panel — DeepSeek prepaid balance (GET api.deepseek.com/user/balance) fetched concurrently with the broker snapshot; rendered as its own group (balance $X USD · pay-as-you-go, no bar/reset — absent key is the normal state, not an error); stale values retained visibly on refresh failure; cache stores value+timestamp, never the key. api_key credentials stay in memory only — never serialized to usage cache or account state; the account pool remains OAuth-only. Manager shows display-only API key · credential #N rows (cursor-skipped).
  • omp catch-upsecurity-reviewer routed exactly like reviewer (deliberative, cross-led, mirrored into task.agentModelOverrides, meter-weighted). Async omp --version probe gates task.agentAdvisor: {task: "on"} emission (advisor=audit AND omp ≥ 17.3); lagging wrappers never see the key.

Compatibility

  • Two-pool render is byte-identical outside the __models__ provider column and the security-reviewer rows — pinned by TestGoldenCatalogTwoPool against testdata/two-pool-golden.plain.
  • Old 5/6-token catalogs parse; unknown lanes in persisted selection state reset cleanly.
  • CODE_* env contract unchanged.

Verification

  • gofmt / go vet / go test ./... green (30 new tests: three-pool goldens, one-rung optional pool, balance fetch contract + degrade paths, key-secrecy, ds-lane overlay YAML, lane-dial growth, usage-group rendering).
  • Headless tmux TUI smoke over a rendered three-pool catalog: ds lanes paint in DeepSeek blue, spark/fable dials hide on ds-only, reviewers cross to GPT on ds-led, cost meter drops to 1/5 on DeepSeek pricing.
  • Deferred (credential-gated): live end-to-end (omp auth-broker login deepseekgenerate init --refresh → ds-led session) — no DeepSeek credential on the dev machine.

Restructure the two-pool (OpenAI/Anthropic) assumptions into a
provider registry so DeepSeek joins routing, usage, and account
management as data, not code surgery.

- providers.go: single registry table (pools, lanes, labels, colors,
  buckets, metered/required, special tiers); ordered fallback and
  advisor pool lists replace every otherPool() flip.
- Catalog: __models__ grows a provider column (5/6/7-token rows all
  parse); the lane facet is catalog-driven, ds-led/ds-only lanes
  appear only when the catalog carries a D pool.
- Generator: N-pool genCombo with pool-set validity, optional-pool
  ladder fill (one verified D rung is enough), relief tails on
  heavyweight led/mixed chains, vision purity exception, per-pool
  advisor tables; generate init scaffolds and bench-probes DeepSeek.
- Usage: DeepSeek prepaid balance fetched concurrently with the
  broker snapshot, rendered as its own group (no bar/reset), cached
  by value only; api_key never serialized, account pool stays
  OAuth-only; manager shows display-only API-key rows.
- omp catch-up: security-reviewer routed like reviewer everywhere;
  omp --version probe gates task.agentAdvisor emission (>= 17.3).

Two-pool output stays byte-identical outside the __models__ provider
column and the security-reviewer rows; goldens pin both.
The account manager's DeepSeek box showed only the display-only API-key
row; the prepaid balance - its single usage datum - lived in the Usage
panel alone. Mirror deepseekBalanceRow under the credential, including
the unavailable/stale degrade, so v answers 'how much is left' too.
…ricing, thinking gauge

Design round 2 with the operator (2026-08-14):

- relief dial: catalogs with an optional pool double their metered-led
  blends into _rel/_norel variants; off strips exactly the pay-as-you-go
  tail. The segment (and dial) exist only in such catalogs, so two-pool
  output stays byte-identical and old ids keep resolving.
- quota-aware lane suggestion: deriveToggles moves a proposal off a lane
  whose lead pool is maxed/unauthed to the first sibling led lane with
  headroom (fallbackPoolOrder), gated for DeepSeek by the prepaid
  balance. Manual dials are never rewritten.
- balance guard: under $2 the balance row warns 'low', suggestions stop
  proposing ds lanes and turn relief off. Unknown balance counts as dry.
- off-peak pricing: DeepSeek rungs cost 50% in the meter during UTC
  16:30-00:30 and the balance row carries the live discount tag. The
  classifier deliberately does not chase the discount: spending money
  while metered quota is free stays an operator decision.
- thinking gauge: the six-word dial renders as a level meter with the
  selected word beside it; arrow behavior and persistence unchanged.
…l/thinking

Operator feedback on round 2:

- The 7-value lane row overflowed the pane and read as too many choices.
  It now renders as a lead dial (gpt / mixed / claude / ds - one entry
  per pool the catalog carries) with a led|only blend child, hidden for
  mixed. sel["lane"] stays the canonical persisted value: lead/blend
  are derived in visibleFacets, recomposed in cycleFacet, and filtered
  from persistence by the facet list.
- The half-height bar gauge clashed with the pane's text height. Both
  the thinking and model dials now draw a notched meter (one segment
  per step) with the selected word beside it. Arrow behavior, facet
  values, ids, and persistence are all unchanged.
- Both notched dials now share a fixed 6-cell track (model spans two
  cells per step), so the meters and the words after them sit in one
  column; a leading space matches the word dials' cell padding.
- mixed anchors the lead dial's left edge: it is the default and the
  only lead without a blend child.
- The relief row now carries a dim state-specific note ('drained chains
  spill into DeepSeek' / 'drained chains wait for quota reset') - it was
  the one dial whose effect was unreadable from the pane.
- audit is the advisor's heavyweight setting, so it now follows the same
  metered logic as the heavyweight roles: with relief on, a metered-led
  blend's audit chain ends on each optional pool's audit rung (glance and
  review stay short; optional-led and pure lanes are untouched). The tail
  flows into both the preview and the launch overlay via applyAdvisor.
- Every gauge now has exactly as many cells as it has selectable
  options: model ▰▰▰ (3), thinking ▰▰▰▱▱▱ (6), advisor ▰▱▱ (3) - the
  shared 6-cell track width is gone.
- advisor's leading 'off' is the dial's zero: it takes no cell, so off
  renders an empty track and each level lights exactly one.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant