diff --git a/.agents/notes/implemented/architecture/2026-08-03-pi-ai-declared-provider-catalog.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-03-pi-ai-declared-provider-catalog.i18n.yaml index 738dc42275..3415bf34c2 100644 --- a/.agents/notes/implemented/architecture/2026-08-03-pi-ai-declared-provider-catalog.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-03-pi-ai-declared-provider-catalog.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-03-pi-ai-declared-provider-catalog.md -2026-08-03-pi-ai-declared-provider-catalog.md: 343b54c5a09be17c0e4c31c219c01b0f1119847a -2026-08-03-pi-ai-declared-provider-catalog.zh.md: 8bc1fceb165cf6477be973944f0091db9bbe75f3 +2026-08-03-pi-ai-declared-provider-catalog.md: 3e926756dcf7d98eea7eb327b6722ff242fe245a +2026-08-03-pi-ai-declared-provider-catalog.zh.md: 5385f0f309146b75ab17b0926d700b8fe66a11f8 diff --git a/.agents/notes/implemented/architecture/2026-08-03-pi-ai-declared-provider-catalog.md b/.agents/notes/implemented/architecture/2026-08-03-pi-ai-declared-provider-catalog.md index 343b54c5a0..3e926756dc 100644 --- a/.agents/notes/implemented/architecture/2026-08-03-pi-ai-declared-provider-catalog.md +++ b/.agents/notes/implemented/architecture/2026-08-03-pi-ai-declared-provider-catalog.md @@ -31,6 +31,12 @@ Resolution fails loud and names the route and model at fault: a model the catalo The configurable-provider directory is now the installed catalog **joined with** every route the current profiles declare, re-registered when that set changes. Without the join a hand-declared route would have no settings address and no configuration surface could show or edit it. +### A capability whose only level does nothing is reported unavailable + +pi-ai reports a model with no reasoning metadata as supporting the single level `off`, and the adapter used to pass that straight through. It reaches the seam as a one-item effort list, which every surface renders as a picker holding one selectable control — and that control is a lie: `off` becomes an *omitted* reasoning option at dispatch, byte-for-byte the request that naming no effort already produces. A provider whose own default is to think keeps thinking while the surface shows `off` selected. + +`reasoningInfo` therefore omits the seam's `reasoning` field whenever `model.reasoning` is falsy. The condition is the model's own metadata, not where the model came from, so this covers every hand-declared model **and** the 251 installed-catalog models pi-ai marks as non-reasoning. Those previously offered the lone `off`; they now offer nothing, and the surface shows the provider default alone. Models that do carry reasoning metadata are untouched — their level list still crosses the seam unfiltered, `off` included, because there it selects between real alternatives. + ### Credentials stay outside pi-ai pi-ai's `Models` carries its own credential concept — a `CredentialStore` keyed by provider id, with `envApiKeyAuth` resolving `credential.key ?? env(VAR)`. Adopting it would have created a second credential source of truth beside `ctx.credentials` and, worse, reintroduced the ambient fallback the harness deliberately forbids: a named-but-missing `apiKeyEnv` must fail with `MISSING_CREDENTIAL` rather than authenticate with whatever unrelated key the environment holds. diff --git a/.agents/notes/implemented/architecture/2026-08-03-pi-ai-declared-provider-catalog.zh.md b/.agents/notes/implemented/architecture/2026-08-03-pi-ai-declared-provider-catalog.zh.md index 8bc1fceb16..5385f0f309 100644 --- a/.agents/notes/implemented/architecture/2026-08-03-pi-ai-declared-provider-catalog.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-03-pi-ai-declared-provider-catalog.zh.md @@ -31,6 +31,12 @@ Status: implemented 可配置提供方目录现在是已安装 catalog **与**当前 profile 声明的每条路由的并集,并在该集合变化时重新登记。没有这个并集,手工声明的路由就没有 settings 地址,任何配置界面都无法展示或编辑它。 +### 唯一档位什么也做不到的能力,报告为不可用 + +pi-ai 把没有推理元数据的模型报告为只支持 `off` 一档,而适配器此前原样透传。它抵达 seam 时是一个单元素的 effort 列表,任何界面都会把它渲染成一个只有一项可选控件的选择器——而这个控件在撒谎:`off` 在派发时变成被*省略*的 reasoning 选项,与「不点名任何档位」产出的请求逐字节相同。自身默认就在思考的提供方会继续思考,界面却显示 `off` 已选中。 + +因此只要 `model.reasoning` 为假,`reasoningInfo` 就省略 seam 的 `reasoning` 字段。判据是模型自身的元数据,而非模型的来源,所以它覆盖每一个手工声明的模型**以及** pi-ai 标记为不具备推理能力的那 251 个已安装 catalog 模型。它们此前提供那个孤零零的 `off`,现在什么也不提供,界面只剩提供方默认。携带推理元数据的模型不受影响——其档位列表仍不经筛选地穿过 seam、`off` 也在内,因为在那里它是在真实备选之间做选择。 + ### 凭据留在 pi-ai 之外 pi-ai 的 `Models` 自带一套凭据概念——按提供方 id 索引的 `CredentialStore`,配合 `envApiKeyAuth` 解析 `credential.key ?? env(VAR)`。采用它会在 `ctx.credentials` 之外制造第二个凭据事实源,更糟的是会把 harness 明确禁止的环境回落重新引进来:点名了却取不到的 `apiKeyEnv` 必须以 `MISSING_CREDENTIAL` 失败,而不是用环境里恰好持有的某个无关密钥完成认证。 diff --git a/packages/llm/llm-pi-ai/src/adapter.ts b/packages/llm/llm-pi-ai/src/adapter.ts index 2cdb7e6657..9b24c90a71 100644 --- a/packages/llm/llm-pi-ai/src/adapter.ts +++ b/packages/llm/llm-pi-ai/src/adapter.ts @@ -110,14 +110,15 @@ function resolveReasoningLevel( /** * Selectable reasoning efforts for one model, or nothing at all. * - * A model the installed catalog does not describe carries no reasoning - * metadata, and pi-ai reports that as the single level `off`. Passing that - * through would offer a control that cannot do what it says: `off` is - * translated to *omitting* the reasoning option, which for such a model is - * byte-for-byte the same request as naming no effort — so a provider whose own - * default is to think would keep thinking with `off` selected. Omitting - * `reasoning` entirely is the seam's way of saying the capability is - * unavailable, which leaves the surface offering only the provider's default. + * A model that carries no reasoning metadata — every hand-declared one, and + * every catalog model pi-ai marks as non-reasoning — is reported by pi-ai as + * supporting the single level `off`. Passing that through would offer a control + * that cannot do what it says: `off` is translated to *omitting* the reasoning + * option, which for such a model is byte-for-byte the same request as naming no + * effort — so a provider whose own default is to think would keep thinking with + * `off` selected. Omitting `reasoning` entirely is the seam's way of saying the + * capability is unavailable, which leaves the surface offering only the + * provider's default. * @param model - the resolved model descriptor. * @param defaultLevel - the profile's configured effort, already validated. * @returns the `reasoning` field, or an empty object when none can be offered.