Skip to content

feat(core,runtime-host,desktop): declare per-model relay profiles for OpenAI-compatible connections - #2463

Merged
jackwener merged 3 commits into
mainfrom
feat/2391-relay-model-profiles
Aug 7, 2026
Merged

feat(core,runtime-host,desktop): declare per-model relay profiles for OpenAI-compatible connections#2463
jackwener merged 3 commits into
mainfrom
feat/2391-relay-model-profiles

Conversation

@jackwener

Copy link
Copy Markdown
Member

Summary

Rebased and fixed landing of #2391 onto current main.

OpenAI-compatible relays can front models Maka's built-in metadata does not know about. This adds first-class, per-model relayModelProfiles for:

  • reasoning-effort levels (thinkingLevels)
  • vision input override
  • context-window override

Profiles live on the connection (not inside fetched models[]), so catalog refresh does not wipe user declarations. Storage enforces: openai-compatible only, and profile keys ⊆ enabledModelIds. The same table is carried through Desktop, embedded storage, Runtime Host, Runtime, CLI, and Headless.

Review changes vs upstream #2391

  1. Rebased onto latest main (resolved conflict in provider-add-form.tsx — kept both createdDefaultModel and opencode-free default enabledModelIds).
  2. Bugfix: FileConnectionStore.create previously pruned profiles against only defaultModel, so import/create payloads that seed multi-model enabledModelIds + profiles dropped every non-default declaration. Now uses the full create-time selection; regression test added.

Original PR

Closes #2391 (supersedes the conflicting fork branch). Also supersedes #2351 (thinking-only design that stored declarations on models[]).

Refs #2219.

Validation (this rebased branch)

  • @maka/storage connection-store: 41/41
  • @maka/core model-thinking / chat-model-choice / model-metadata: 41/41
  • @maka/runtime model-factory-thinking: 39/39 (includes real HTTP body assert for reasoning_effort)
  • @maka/storage runtime-policy-stores: 39/39
  • @maka/runtime-host catalog-reader + session-catalog-coordinator (+ related): 34/34

Credits

Primary design and implementation: @sanshanya in #2391.

sans and others added 3 commits August 8, 2026 01:45
A generic openai-compatible relay fronts models the built-in metadata chain
has never heard of: no thinking-level menu, no vision decision, a guessed
context window. This adds first-class, per-model user declarations for the
three facts only the user can decide.

Data model: `Connection.relayModelProfiles: Record<modelId,
{ thinkingLevels?; vision?; contextWindow? }>` on the single connection type
chain. Two invariants are enforced at every store boundary — profiles exist
only on openai-compatible connections (the metadata chain is the truth
elsewhere), and only for ids in `enabledModelIds` (both stores prune the
table when a model is disabled, so no declaration can outlive its row).
Every read enters through one gated seam, `relayModelProfile(connection,
modelId)`; writes are sanitized by `normalizeRelayModelProfiles`, and the
catalog codec validates strictly (closed level vocabulary, subset of the
selection, positive-integer context window) — a malformed legacy document is
noise, a malformed catalog document is a loud failure.

Updates are whole-table tri-state: an absent key leaves the stored table
untouched (capability-blind writers cannot clobber), `null` clears it, a
table replaces it. A baseUrl change in the same update retires an untouched
table unless the update submits one of its own — declarations belong to the
endpoint they were declared against. The embedded store mirrors the same
merge rules, and config import round-trips the typed field instead of an
extras bag.

Host wire: profiles travel on their own `enabled_model_id` page item
(`relayProfile`), never in one header table — a header item is atomic to the
paginator's 48 KiB page budget, so a long declaration list there would have
made the whole connection unreadable. The client reader reassembles the
per-model map; compatibility epoch bumps to 6. Session-model gating and
execution authority consume the same typed table, so a level that passes the
gate is exactly what the wire can send; the connection slug is the
provider-options namespace, and a fake-fetch test pins the translated
`reasoning_effort` request body.

Settings UI: each enabled model row edits a local draft (precise
multi-select of thinking levels — generic relays never declare the 'off'
wire —, Auto/Enabled/Disabled vision tri-state, positive-integer context
window) committed by one explicit 保存能力声明; there is no per-keystroke
persistence machinery. The add-provider form declares the connection's
first (default) model at create time through the same typed field.
…tion

Create previously passed only defaultModel into relayProfilesForStorage, so
import/create payloads that seed enabledModelIds together with per-model
profiles dropped every non-default declaration. Use the same enabled set the
connection will store, and cover the multi-model create path.
…OAuth

claude-subscription discovery is curated/fallback-only after #2336, so
beginModelFetch returns provider_action_unavailable. Seed the OAuth backend
abort test from the curated inventory create already writes, and target a
model id that inventory contains.
@jackwener
jackwener force-pushed the feat/2391-relay-model-profiles branch from a86b99b to 86cbd27 Compare August 7, 2026 17:48
@jackwener
jackwener merged commit 4b217be into main Aug 7, 2026
12 checks passed
@jackwener
jackwener deleted the feat/2391-relay-model-profiles branch August 7, 2026 17:55
likun666661 pushed a commit that referenced this pull request Aug 20, 2026
…3321)

* fix(runtime): emit camelCase OpenAI-compatible provider option keys

The AI SDK resolves openai-compatible providerOptions under the
camelCase alias of the provider name and flags raw dashed keys as
deprecated on every doGenerate. Derive the key via toCamelCase for all
openai-compatible identities (extends #2463, which only covered custom
relay slugs) and fix the three direct dashed emissions. The
open-responses path is unchanged: that SDK resolves only the raw name.

Closes #1430

Generated-by: Claude Code (Fable 5)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(runtime): pin the metadata namespace and the passthrough lane

Address review: note in the providerOptions-key docstring that the
camelCase alias also selects the SDK's response metadata namespace,
assert the capture test's providerMetadata comes back under the
camelCase key, and add a wire-level capture for volcengine-ark's
passthrough thinking object - the lane the existing reasoningEffort
captures do not exercise.

Generated-by: Claude Code (Fable 5)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
M4n5ter pushed a commit to qian0817/maka that referenced this pull request Aug 26, 2026
…ocol relays

An anthropic-compatible relay fronts models the metadata chain cannot
resolve (no provider alias exists for it), so its models offered no
thinking menu at all — while the declaration mechanism that would fix
it was gated OpenAI-relay-only since apache#2463.

The gate is now per-field rather than per-provider: thinking-level
declarations are legal on all three custom relays, serviceTier stays
OpenAI Responses-only. The declarable vocabulary is per provider:
`off` joins it only for anthropic-compatible, whose protocol has a
true disable wire (`thinking: { type: 'disabled' }`); the OpenAI
relays keep their vocabulary unchanged (`off` there encodes
`reasoning_effort: 'none'`, which no generic relay is presumed to
honor).

- `DECLARABLE_RELAY_THINKING_LEVELS` becomes
  `declarableRelayThinkingLevels(providerType)`; normalize gains an
  optional provider and stays provider-blind without one (the
  host-wire decode edge has no provider context — the canonical store
  codec has already validated fit).
- The catalog codec asserts field-level provider fit, and the update
  path threads providerType through to table decode.
- The runtime anthropic wire maps a declared `off` to
  `thinking.disabled` and other tiers to a passthrough `effort`.
- The settings page's thinking menu, bulk control, and draft seed use
  the per-provider vocabulary (the same controls the OpenAI relays
  already expose).

Older builds reject a document carrying these declarations (same
forward-compat posture as apache#3309's serviceTier); SCHEMA_VERSION stays
1, matching the repo convention that value-range growth is not a
schema change.

Generated-by: Claude Sonnet 4.5 via pi
M4n5ter pushed a commit to qian0817/maka that referenced this pull request Aug 26, 2026
…ocol relays

An anthropic-compatible relay fronts models the metadata chain cannot
resolve (no provider alias exists for it), so its models offered no
thinking menu at all — while the declaration mechanism that would fix
it was gated OpenAI-relay-only since apache#2463.

The gate is now per-field rather than per-provider: thinking-level
declarations are legal on all three custom relays, serviceTier stays
OpenAI Responses-only. The declarable vocabulary is per provider:
`off` joins it only for anthropic-compatible, whose protocol has a
true disable wire (`thinking: { type: 'disabled' }`); the OpenAI
relays keep their vocabulary unchanged (`off` there encodes
`reasoning_effort: 'none'`, which no generic relay is presumed to
honor).

- `DECLARABLE_RELAY_THINKING_LEVELS` becomes
  `declarableRelayThinkingLevels(providerType)`; normalize gains an
  optional provider and stays provider-blind without one (the
  host-wire decode edge has no provider context — the canonical store
  codec has already validated fit).
- The catalog codec asserts field-level provider fit, and the update
  path threads providerType through to table decode.
- The runtime anthropic wire maps a declared `off` to
  `thinking.disabled` and other tiers to a passthrough `effort`.
- The settings page's thinking menu, bulk control, and draft seed use
  the per-provider vocabulary (the same controls the OpenAI relays
  already expose).

Older builds reject a document carrying these declarations (same
forward-compat posture as apache#3309's serviceTier); SCHEMA_VERSION stays
1, matching the repo convention that value-range growth is not a
schema change.

Generated-by: Claude Sonnet 4.5 via pi
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