Skip to content

feat(core): allow openai-compatible relays to declare per-model thinking options - #2351

Closed
cat0825 wants to merge 1 commit into
apache:mainfrom
cat0825:feat/openai-compatible-thinking-options
Closed

feat(core): allow openai-compatible relays to declare per-model thinking options#2351
cat0825 wants to merge 1 commit into
apache:mainfrom
cat0825:feat/openai-compatible-thinking-options

Conversation

@cat0825

@cat0825 cat0825 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

概述

修复 #2219:openai-compatible(中转站/代理)连接现在可以在模型上手动声明 thinkingOptions,声明后聊天输入框显示思考级别选择器,请求体携带对应的 reasoning_effort

wire 层此前已就绪(buildFamilyWire 的 openai-compatible 分支把 level 映射为 reasoning_effortoffnone),缺的是「声明 → 查询 → 校验 → UI」这条链。

变更

数据模型packages/core/src/llm-connections.ts

  • ModelInfo 新增可选 thinkingOptions?: ThinkingOptions,复用现有类型,未声明时行为与之前完全一致。

查询packages/core/src/model-thinking.ts

  • 新增 thinkingVariantsForConnection(connection, modelId):连接模型列表里的显式声明优先,miss 时回退内置目录。任意 providerType 通用(用户声明的模型覆盖目录),不再是 issue 草案里的 openai-compatible 特判——语义更干净:目录条目对未声明模型仍然生效。

链路替换(所有能拿到 connection 对象的调用点)

  • chat-model-choice.ts(模型选择器的 thinkingLevels)
  • model-factory.ts buildProviderOptions + responses wire 的 forceReasoning 判定
  • sessions-ipc-main.ts normalizeSupportedSessionThinkingLevel(建会话 / 切换思考级别两个入口)
  • subagent-settings-page.tsx
  • runtime-host/session-catalog-coordinator.ts

UI(连接设置页 · 模型区)

  • 新增「思考级别」编辑行:选择已启用模型 → 多选档位(none/low/medium/high/xhigh/max)→ 保存;清空即撤销声明。
  • 档位值原样作为 reasoning_effort 发送,UI 文案提示用户先确认中转站透传(常见取值 low/high/max)。
  • 新增中英文案;use-connection-detail 增加 saveThinkingOptions

存储packages/storage/src/connection-store.ts

  • fetched 模型刷新(/v1/models)会整体替换模型列表;现在按 id 把用户声明 merge 回去,刷新不再丢声明。

测试

  • model-thinking.test.ts:声明优先 / 目录回退 / none 出 off 档
  • model-factory-thinking.test.ts:relay 声明 → wire 输出 { [slug]: { reasoningEffort } },未声明模型保持静默
  • connection-store.test.ts:fetched 刷新保留声明

验证

  • npm run typecheck 全 workspace 通过(含 desktop 4 个 tsconfig)
  • core 804 / storage 728 / runtime 3348 测试通过(storage 2 个失败为本地环境问题:dugite git 二进制缺失、root-authority 运行时依赖,与本 PR 无关)
  • desktop renderer vite build 通过

范围说明

  • CLIpi-tui-runner.ts)未改:它只有 providerType + modelId,没有 connection 对象可读声明,行为不变。
  • off 档:只有当声明包含 none 时才暴露(与目录规则一致,无真实 off wire 的模型不暴露 off);声明 ['low','high','max'] 时选 off 会被正常拒绝。
  • 中转站吞掉未知参数或对 reasoning_effort: none 报错的情况需要用户自行用 curl 验证透传,UI 文案已提示。

Fixes #2219

@sanshanya

Copy link
Copy Markdown

写完才发现这个 pr,撞车了😭。
贴下我的链接#2391
我们实现的功能差不多

…ing options

openai-compatible (relay/proxy) connections have no built-in catalog entry,
so thinkingVariantsForModel always reported no thinking support and the
chat composer hid the thinking-level selector, even when the backing model
speaks reasoning_effort. The wire layer already mapped the level when it
reached buildProviderOptions; the missing piece was a way to declare it.

- ModelInfo.thinkingOptions (optional): user-declared ThinkingOptions for a
  connection model; wins over the catalog when present.
- thinkingVariantsForConnection(connection, modelId): declaration-first
  lookup with catalog fallback, used everywhere a connection object exists
  (chat model choices, runtime wire, session IPC validation, subagent
  settings, runtime-host coordinator).
- Connection settings: new "思考级别" editor per enabled model, choosing
  effort levels (none/low/medium/high/xhigh/max) sent verbatim as
  reasoning_effort (off -> none).
- Storage: a fetched model refresh merges declarations back by id, so
  updating the /v1/models list no longer drops the user's setup.

CLI (pi-tui-runner) keeps the old provider+model lookup: it has no
connection object to read declarations from; behavior unchanged there.

Fixes apache#2219
@cat0825
cat0825 force-pushed the feat/openai-compatible-thinking-options branch from 6480816 to 9e70cff Compare August 7, 2026 06:34
@jackwener

Copy link
Copy Markdown
Member

Review decision

Closing in favor of #2391 (and the rebased follow-up once landed).

Why #2391 supersedes this PR

Both PRs address #2219 (openai-compatible relays declaring thinking capabilities). The approaches differ on a first-principles ownership question:

#2351 #2391
Storage site models[].thinkingOptions on fetched model rows First-class relayModelProfiles on the connection
Lifecycle Must re-merge user declarations after every /models refresh Profiles survive refresh without re-merge
Scope Thinking only Thinking + vision + context window
Provider gate Generic connection helper (any providerType) Explicitly gated to openai-compatible
Runtime Host Partial Full catalog transport + lifecycle parity

Putting user configuration inside models[] couples two authorities with different lifetimes (discovery vs. user config). #2391's separate relayModelProfiles table is the cleaner boundary and already covers the thinking path this PR adds, plus vision/context window.

Conflict state against current main also means this branch would need a substantial rebase for little unique value.

Thank you for the solid exploration — the wire path (reasoning_effort) and the “declare then query” chain here informed the review of #2391.

@jackwener

Copy link
Copy Markdown
Member

Superseded by #2391's relayModelProfiles design (see review comment).

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.

feat: 允许为 openai-compatible(中转站)连接手动声明 thinkingOptions,以启用思考深度调节

3 participants