diff --git a/docs-site/src/content/docs/ja/reference/adapters.md b/docs-site/src/content/docs/ja/reference/adapters.md index fc05e0a617..e637c4cc26 100644 --- a/docs-site/src/content/docs/ja/reference/adapters.md +++ b/docs-site/src/content/docs/ja/reference/adapters.md @@ -35,11 +35,11 @@ interface ProviderAdapter { プロバイダーが明示的に alias を設定しない限り、`xhigh` と `max` は異なるラベルのまま保ちます。`provider.noReasoningModels` に含まれる id には値を **一切送りません**。 - `delta.content`(テキスト)、`delta.reasoning_content`(thinking)、`delta.tool_calls[]` を ストリーミングし、`usage` を収集します。 -- ClinePass は、ライブ検証済みのゲートウェイ形式 `reasoning: { enabled: true, effort: "low" }` +- ClinePass は、ライブ検証済みのゲートウェイ形式 `reasoning: { enabled: true, effort }` (reasoning を無効にする場合は `{ enabled: false }`)を使用します。公開 API ドキュメントには - 現在このリクエスト形式が明記されていません。アダプターは他の effort リクエストを検証済みの - `low` に調整し、`delta.reasoning_content` または `delta.reasoning` を reasoning delta として扱い、 - `stream_options.include_usage` でストリーム usage を要求し、非ストリームのレスポンス envelope からも usage を読み取ります。 + 現在このリクエスト形式が明記されていません。アダプターは要求された `low`、`medium`、`high`、 + `xhigh`、`max` tier をそのまま保持し、`delta.reasoning_content` または `delta.reasoning` を + reasoning delta として扱い、`stream_options.include_usage` でストリーム usage を要求し、非ストリームのレスポンス envelope からも usage を読み取ります。 ## `openai-responses` diff --git a/docs-site/src/content/docs/ko/reference/adapters.md b/docs-site/src/content/docs/ko/reference/adapters.md index e742eafa72..58c61cd7e4 100644 --- a/docs-site/src/content/docs/ko/reference/adapters.md +++ b/docs-site/src/content/docs/ko/reference/adapters.md @@ -41,10 +41,10 @@ interface ProviderAdapter { 유지합니다. `provider.noReasoningModels`에 든 id에는 값을 **아예 보내지 않습니다**. - `delta.content`(텍스트), `delta.reasoning_content`(thinking), `delta.tool_calls[]`를 스트리밍하고 `usage`를 수집합니다. -- ClinePass는 라이브로 검증된 게이트웨이 형식 `reasoning: { enabled: true, effort: "low" }`을 - 사용하며, reasoning을 끌 때는 `{ enabled: false }`를 사용합니다. 공개 API 문서에는 현재 이 요청 - 형식이 명시되어 있지 않습니다. 어댑터는 다른 effort 요청을 검증된 `low`로 조정하고, - `delta.reasoning_content` 또는 `delta.reasoning`을 reasoning delta로 처리하며, +- ClinePass는 라이브로 검증된 게이트웨이 형식 `reasoning: { enabled: true, effort }`을 사용하며, + reasoning을 끌 때는 `{ enabled: false }`를 사용합니다. 공개 API 문서에는 현재 이 요청 형식이 + 명시되어 있지 않습니다. 어댑터는 요청한 `low`, `medium`, `high`, `xhigh`, `max` 단계를 그대로 + 유지하고, `delta.reasoning_content` 또는 `delta.reasoning`을 reasoning delta로 처리하며, `stream_options.include_usage`로 스트림 usage를 요청하고 비스트림 응답 envelope에서도 usage를 읽습니다. ## `openai-responses` diff --git a/docs-site/src/content/docs/reference/adapters.md b/docs-site/src/content/docs/reference/adapters.md index 279e0d7305..93a1c42f63 100644 --- a/docs-site/src/content/docs/reference/adapters.md +++ b/docs-site/src/content/docs/reference/adapters.md @@ -41,11 +41,11 @@ provider — xAI, Kimi, DeepSeek, GLM, Groq, OpenRouter, Ollama (local & cloud), adapter **omits it entirely** for ids in `provider.noReasoningModels`. - Streams `delta.content` (text), `delta.reasoning_content` (thinking), and `delta.tool_calls[]`; collects `usage`. -- ClinePass uses the live-verified gateway format `reasoning: { enabled: true, effort: "low" }` - (or `{ enabled: false }` when reasoning is disabled); its public API docs do not currently specify - this request shape. The adapter clamps other effort requests to the verified `low` tier, accepts - reasoning deltas from either `delta.reasoning_content` or `delta.reasoning`, requests streamed - usage with `stream_options.include_usage`, and reads usage from non-stream response envelopes. +- ClinePass uses the live-verified gateway format `reasoning: { enabled: true, effort }` (or + `{ enabled: false }` when reasoning is disabled); its public API docs do not currently specify + this request shape. The adapter preserves requested `low`, `medium`, `high`, `xhigh`, and `max` + tiers, accepts reasoning deltas from either `delta.reasoning_content` or `delta.reasoning`, requests + streamed usage with `stream_options.include_usage`, and reads usage from non-stream response envelopes. ## `openai-responses` diff --git a/docs-site/src/content/docs/ru/reference/adapters.md b/docs-site/src/content/docs/ru/reference/adapters.md index 6386236c4c..eca16ca669 100644 --- a/docs-site/src/content/docs/ru/reference/adapters.md +++ b/docs-site/src/content/docs/ru/reference/adapters.md @@ -45,11 +45,11 @@ interface ProviderAdapter { - Стримит `delta.content` (текст), `delta.reasoning_content` (thinking) и `delta.tool_calls[]`; собирает `usage`. - ClinePass использует проверенный на живом API формат шлюза - `reasoning: { enabled: true, effort: "low" }` (или `{ enabled: false }`, когда reasoning отключён); - в публичной документации API этот формат запроса пока не указан. Адаптер прижимает другие - уровни effort к проверенному `low`, принимает reasoning delta из `delta.reasoning_content` или - `delta.reasoning`, запрашивает usage потока через `stream_options.include_usage` и читает usage - из envelope нестримингового ответа. + `reasoning: { enabled: true, effort }` (или `{ enabled: false }`, когда reasoning отключён); + в публичной документации API этот формат запроса пока не указан. Адаптер сохраняет запрошенный + уровень `low`, `medium`, `high`, `xhigh` или `max`, принимает reasoning delta из + `delta.reasoning_content` или `delta.reasoning`, запрашивает usage потока через + `stream_options.include_usage` и читает usage из envelope нестримингового ответа. ## `openai-responses` diff --git a/docs-site/src/content/docs/zh-cn/reference/adapters.md b/docs-site/src/content/docs/zh-cn/reference/adapters.md index e6d1c30635..dc39d307b7 100644 --- a/docs-site/src/content/docs/zh-cn/reference/adapters.md +++ b/docs-site/src/content/docs/zh-cn/reference/adapters.md @@ -38,11 +38,10 @@ interface ProviderAdapter { 省略**该参数。 - 流式输出 `delta.content`(文本)、`delta.reasoning_content`(thinking)和 `delta.tool_calls[]`,并收集 `usage`。 -- ClinePass 使用经实时验证的网关格式 `reasoning: { enabled: true, effort: "low" }`;关闭 - reasoning 时使用 `{ enabled: false }`。其公开 API 文档目前没有说明这一请求格式。adapter 会把 - 其他 effort 请求限制到已验证的 `low`,把 `delta.reasoning_content` 或 `delta.reasoning` - 作为 reasoning delta,通过 `stream_options.include_usage` 请求流式 usage,并从非流式响应 - envelope 中读取 usage。 +- ClinePass 使用经实时验证的网关格式 `reasoning: { enabled: true, effort }`;关闭 reasoning 时使用 + `{ enabled: false }`。其公开 API 文档目前没有说明这一请求格式。adapter 会保留请求的 `low`、 + `medium`、`high`、`xhigh` 或 `max` 档位,把 `delta.reasoning_content` 或 `delta.reasoning` + 作为 reasoning delta,通过 `stream_options.include_usage` 请求流式 usage,并从非流式响应 envelope 中读取 usage。 ## `openai-responses` diff --git a/docs-site/src/content/docs/zh-tw/guides/providers.md b/docs-site/src/content/docs/zh-tw/guides/providers.md index d0eb11fcb3..ee7d709880 100644 --- a/docs-site/src/content/docs/zh-tw/guides/providers.md +++ b/docs-site/src/content/docs/zh-tw/guides/providers.md @@ -226,8 +226,10 @@ preset。儀表板的 **Add provider** picker 會開啟 key provider 的 dashboa 與 [Chat Completions endpoint](https://docs.cline.bot/api/chat-completions),由 Cline Bot Inc. 依 [Cline terms](https://cline.bot/tos) 提供。像 `cline-pass/cline-pass/kimi-k3` 這類 routed id 是刻意設計: 第一段選擇 opencodex provider,後面的 `cline-pass/kimi-k3` 才是送往上游的完整 model slug。ClinePass -quota 由帳號共用,包含 rolling 5-hour、weekly 與 monthly limit。opencodex 目前只宣告 live-verified -`low` reasoning tier;更高 requested tier 會 clamp 到 `low`,直到 gateway 發布或驗證更廣的 ladder。 +quota 由帳號共用,包含 rolling 5-hour、weekly 與 monthly limit。2026-08-13 的 live probe 已確認所有 +靜態 ClinePass model 在 gateway input 都接受 `low`、`medium`、`high`、`xhigh` 與 `max`。符合 registry +transport 的 canonical ClinePass 設定會保留 requested tier;同名 custom provider 則保留明確設定的 +reasoning configuration。backend-specific normalization 由 ClinePass 負責。 **Cline** 使用相同 API key 與 endpoint,但採 pay-as-you-go 用量計費,可使用 100+ 模型,包括 OpenRouter 風格 id,例如 `anthropic/claude-sonnet-4-6`。Cline 的 promotional free model 只提供給 Cline diff --git a/src/providers/derive.ts b/src/providers/derive.ts index 712e2f020d..a61d762675 100644 --- a/src/providers/derive.ts +++ b/src/providers/derive.ts @@ -393,6 +393,14 @@ function enrichReasoningSummariesByDestination(prov: OcxProviderConfig): void { applyReasoningSummaryDefaults(prov, destination?.modelSupportsReasoningSummaries); } +/** Repair the exact low-only ClinePass ladder generated by older key-login presets. */ +export function hasLegacyClinePassReasoningEfforts(name: string, prov: OcxProviderConfig): boolean { + return name === "cline-pass" + && prov.reasoningWireFormat === "gateway-object" + && prov.reasoningEfforts?.length === 1 + && prov.reasoningEfforts[0] === "low"; +} + export function enrichProviderFromRegistry(name: string, prov: OcxProviderConfig): void { const entry = PROVIDER_REGISTRY.find(row => row.id === name); if (!entry || !providerMatchesRegistryTransportWithStaticGuards(name, prov)) { @@ -426,7 +434,9 @@ export function enrichProviderFromRegistry(name: string, prov: OcxProviderConfig if (seed.modelInputModalities) prov.modelInputModalities = fillRecordOfArrays(seed.modelInputModalities, prov.modelInputModalities); if (prov.defaultMaxOutputTokens === undefined && seed.defaultMaxOutputTokens !== undefined) prov.defaultMaxOutputTokens = seed.defaultMaxOutputTokens; if (!prov.modelMaxOutputTokens && seed.modelMaxOutputTokens) prov.modelMaxOutputTokens = { ...seed.modelMaxOutputTokens }; - if (!prov.reasoningEfforts && seed.reasoningEfforts) prov.reasoningEfforts = [...seed.reasoningEfforts]; + if ((!prov.reasoningEfforts || hasLegacyClinePassReasoningEfforts(name, prov)) && seed.reasoningEfforts) { + prov.reasoningEfforts = [...seed.reasoningEfforts]; + } if (!prov.modelReasoningEfforts && seed.modelReasoningEfforts) prov.modelReasoningEfforts = cloneRecordOfArrays(seed.modelReasoningEfforts); if (!prov.modelDefaultReasoningEfforts && seed.modelDefaultReasoningEfforts) prov.modelDefaultReasoningEfforts = { ...seed.modelDefaultReasoningEfforts }; if (!prov.reasoningEffortMap && seed.reasoningEffortMap) prov.reasoningEffortMap = { ...seed.reasoningEffortMap }; diff --git a/src/router.ts b/src/router.ts index 6dcb00fba3..e510a1f06f 100644 --- a/src/router.ts +++ b/src/router.ts @@ -12,7 +12,7 @@ import { hasOwnProvider, resolveEnvValue } from "./config"; import { assertProviderDestinationAllowed } from "./lib/destination-policy"; import { redactSecretString, redactUrlForLog } from "./lib/redact"; import { PROVIDER_REGISTRY, providerCodexAccountMode } from "./providers/registry"; -import { applyDirectReasoningEffortContracts } from "./providers/derive"; +import { applyDirectReasoningEffortContracts, hasLegacyClinePassReasoningEfforts } from "./providers/derive"; import { providerMatchesRegistryTransportWithStaticGuards, providerSupportsLiveModelDiscovery, @@ -286,14 +286,6 @@ export function routedProviderConfig(providerName: string, provider: OcxProvider const modelReasoningEffortMap = mergeNestedRecord(registryEntry.modelReasoningEffortMap, provider.modelReasoningEffortMap); const modelReasoningEfforts = mergeStringArrayRecord(registryEntry.modelReasoningEfforts, provider.modelReasoningEfforts); const modelDefaultReasoningEfforts = mergeRecordFill(registryEntry.modelDefaultReasoningEfforts, provider.modelDefaultReasoningEfforts); - // Key-login used to persist this exact low-only ClinePass capability seed. Once the gateway's - // wider input ladder was live-verified, leaving that generated row untouched would keep old - // installs clamped forever. This branch is reached only after canonical transport matching, so - // same-named custom destinations and every other explicit ladder still retain user precedence. - const repairLegacyClinePassReasoningEfforts = providerName === "cline-pass" - && provider.reasoningWireFormat === "gateway-object" - && provider.reasoningEfforts?.length === 1 - && provider.reasoningEfforts[0] === "low"; const modelContextWindows = providerName === OPENAI_API_PROVIDER_ID ? mergePositiveNumberCaps(registryEntry.modelContextWindows, provider.modelContextWindows) : mergeRecordFill(registryEntry.modelContextWindows, provider.modelContextWindows); @@ -374,7 +366,7 @@ export function routedProviderConfig(providerName: string, provider: OcxProvider ...(provider.project === undefined && registryEntry.project !== undefined ? { project: registryEntry.project } : {}), ...(provider.location === undefined && registryEntry.location !== undefined ? { location: registryEntry.location } : {}), ...(provider.contextWindow === undefined && registryEntry.contextWindow !== undefined ? { contextWindow: registryEntry.contextWindow } : {}), - ...((provider.reasoningEfforts === undefined || repairLegacyClinePassReasoningEfforts) + ...((provider.reasoningEfforts === undefined || hasLegacyClinePassReasoningEfforts(providerName, provider)) && registryEntry.reasoningEfforts !== undefined ? { reasoningEfforts: [...registryEntry.reasoningEfforts] } : {}), diff --git a/tests/cline-pass-provider.test.ts b/tests/cline-pass-provider.test.ts index c5e963731e..3bd7d7b827 100644 --- a/tests/cline-pass-provider.test.ts +++ b/tests/cline-pass-provider.test.ts @@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test"; import { createOpenAIChatAdapter } from "../src/adapters/openai-chat"; import { createTranslatorBudget } from "../src/lib/translator-budget"; import { KEY_LOGIN_PROVIDERS } from "../src/oauth/key-providers"; -import { providerConfigSeed } from "../src/providers/derive"; +import { enrichProviderFromRegistry, providerConfigSeed } from "../src/providers/derive"; import { PROVIDER_REGISTRY } from "../src/providers/registry"; import { routeModel } from "../src/router"; import type { OcxConfig, OcxParsedRequest } from "../src/types"; @@ -88,6 +88,19 @@ describe("ClinePass provider", () => { expect(seed).not.toHaveProperty("preserveCustomDestination"); }); + test("catalog enrichment repairs the low-only ladder persisted by older ClinePass presets", () => { + const stale = providerConfigSeed(registryEntry()); + stale.reasoningEfforts = ["low"]; + + enrichProviderFromRegistry("cline-pass", stale); + + expect(stale.reasoningEfforts).toEqual(["low", "medium", "high", "xhigh", "max"]); + + const custom = { ...stale, baseUrl: "https://custom.example/v1", reasoningEfforts: ["low"] }; + enrichProviderFromRegistry("cline-pass", custom); + expect(custom.reasoningEfforts).toEqual(["low"]); + }); + test("routing keeps the full upstream model slug and emits the Cline gateway reasoning object", () => { const config: OcxConfig = { port: 10100,