feat(core,runtime-host,desktop): declare per-model relay profiles for OpenAI-compatible connections 添加了自定义 openai 时对推理和 vision 能力和上下文长度自定义 - #2391
Conversation
44c16e5 to
49f9c8f
Compare
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.
49f9c8f to
dff3627
Compare
Review decisionReviewed against first principles and current Problem is real: openai-compatible relays front unknown models; Maka cannot infer thinking levels / vision / context window from built-in metadata alone. Design is correct: separating user-owned Issues fixed while landing:
Landing as maintainer branch PR (rebased + fix) so it can merge cleanly without write access to the fork head. |
|
Landing via rebased+fixed #2463 (see review comment). Thank you @sanshanya! |
English
Summary
OpenAI-compatible relays can front models that Maka's built-in metadata does not know about. For those models, Maka cannot reliably infer three user-visible capabilities:
This PR adds first-class, per-model relay declarations for those capabilities and carries them through Desktop, embedded storage, Runtime Host, Runtime, CLI, and Headless execution paths.
Refs #2219.
Design
Relay declarations live on the connection as a separate typed field:
They intentionally do not live inside fetched
models[]rows.models[]represents provider/discovery data and may be replaced by/modelsrefreshes. Relay declarations are user-owned configuration with a different lifecycle. Keeping the two authorities separate avoids re-merging user state after every discovery refresh and gives endpoint changes explicit semantics.If
baseUrlchanges without a replacement profile table, declarations associated with the previous endpoint are retired.Storage boundaries enforce two invariants:
openai-compatibleconnectionsenabledModelIds, so disabling or retiring a model also removes its declarationRuntime reads go through the provider-gated
relayModelProfile(connection, modelId)seam. Built-in providers therefore continue to use the existing metadata authority.Runtime Host
Embedded and Runtime Host modes use the same typed profile model and lifecycle semantics.
Runtime Host does not place the entire profile table into a single catalog header item. Instead, an optional
relayProfiletravels with each paginatedenabled_model_iditem, and the client reconstructs the table.This reuses the existing catalog paginator and avoids introducing a separate unsplittable payload limit.
Connection updates use whole-table tri-state semantics:
null: clear the tableConfig import adapts snapshot semantics to this update contract. Model refreshes, enabled-model changes, and endpoint changes preserve the same profile invariants in both storage implementations.
Thinking wire
Generic relay declarations support exact reasoning-effort levels from
minimalthroughmax.They intentionally do not declare
off, becauseoffis a provider-specific disable-wire contract rather than a normal reasoning-intensity tier.For custom relay connections, the raw connection slug remains the provider identity, while provider options use the OpenAI-compatible SDK's canonical camelCase alias.
A fake-fetch regression test executes the actual model path and verifies that a declared level reaches the final HTTP request body as:
{ "reasoning_effort": "high" }The same test verifies that this path does not emit the SDK's deprecated raw provider-options-key warning.
Settings
For every enabled model on an
openai-compatibleconnection, Settings exposes:Edits stay in a connection-scoped local draft and are committed with one explicit save action.
Switching connections always reseeds the draft, preventing unsaved declarations from one connection from being displayed or saved into another.
Validation
Regression coverage added by this PR includes:
reasoning_effortGitHub Actions for this fork PR are currently awaiting maintainer approval before jobs can run.
Review focus
models[]data from user-ownedrelayModelProfilesthe right ownership boundary?relayProfileon paginatedenabled_model_iditems the smallest coherent Runtime Host protocol extension?off?简体中文
概要
OpenAI-compatible 中转站可能代理 Maka 内置模型元数据完全不了解的模型。对于这些模型,Maka 无法可靠推断三个直接影响用户使用的能力:
本 PR 为这三项能力增加一等的、按模型声明的 relay profile,并把同一份声明贯通 Desktop、embedded storage、Runtime Host、Runtime、CLI 和 Headless 执行路径。
Refs #2219。
设计
Relay 声明作为连接上的独立 typed field 存储:
这些声明刻意不放进 fetched
models[]条目。models[]属于 provider / discovery 数据,可能被/models刷新整体替换;relay declaration 则属于用户配置,两者生命周期不同。把两种 authority 分开后,不需要在每次模型刷新时把用户状态重新 merge 回模型条目,同时 endpoint 变化也有明确语义:
如果
baseUrl发生变化,而同一次更新没有提交新的 profile table,则旧 endpoint 对应的声明会被清除。Storage boundary 强制两个不变量:
openai-compatibleconnection 可以持有 relay profileenabledModelIds;禁用或淘汰模型时同步移除对应声明Runtime 中的读取统一经过 provider-gated
relayModelProfile(connection, modelId)seam,因此内置 provider 仍然完全以已有 metadata chain 为准。Runtime Host
Embedded 和 Runtime Host 两种模式使用同一套 typed profile 模型和生命周期语义。
Runtime Host 不会把整张 profile table 放进单个 catalog header item。
可选的
relayProfile会跟随已经分页的enabled_model_iditem 传输,再由 client 重建 profile table。这样可以直接复用已有 catalog paginator,同时避免额外引入一张不可拆分的大 payload 及其独立 size limit。
Connection update 对整张 table 使用三态语义:
null:清空 tableConfig import 会把 snapshot semantics 正确适配到这一更新协议。模型刷新、enabled model 变化和 endpoint 变化,在 embedded 与 Runtime Host 两种存储实现中遵守同样的 profile invariant。
Thinking wire
Generic relay 可以精确声明从
minimal到max的 reasoning-effort levels。不会允许 generic relay 声明
off,因为off不是普通的 reasoning intensity,而是 provider-specific 的“关闭推理”wire contract。对于自定义 relay connection,原始 connection slug 仍然作为 provider identity;provider options 则使用 OpenAI-compatible SDK 的 canonical camelCase alias。
fake-fetch 回归测试会真正执行实际 model path,并验证声明的档位最终进入 HTTP request body:
{ "reasoning_effort": "high" }同一个测试还会确认该路径不会产生 SDK 对 raw provider-options key 的 deprecated warning。
设置界面
每个
openai-compatibleconnection 的 enabled model 都可以声明:所有修改首先保存在 connection-scoped local draft 中,通过一次显式保存提交。
切换 connection 时 draft 会强制重新初始化,避免 A 连接未保存的声明显示在 B 连接中,或被错误保存到 B。
验证
本 PR 新增的回归覆盖包括:
reasoning_effort的真实 HTTP request-body 验证当前这个 fork PR 的 GitHub Actions 仍在等待 maintainer approval,jobs 尚未实际运行。
Review focus
models[]与用户拥有的relayModelProfiles分离,是否是合适的数据 ownership boundary?enabled_model_iditem 上携带relayProfile,是否是最小且合理的 Runtime Host protocol extension?off?Screenshots / 截图