Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs-site/src/content/docs/guides/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,6 @@ also needs the local CLI binary: opencodex first uses `PATH`, then falls back to

After a successful import, opencodex persists the imported credential to
`~/.opencodex/auth.json`.

Keep these variables and the selected database private. Do not attach database files or raw login
diagnostics to bug reports.

Expand All @@ -243,8 +242,9 @@ and [Chat Completions endpoint](https://docs.cline.bot/api/chat-completions), op
[Cline's terms](https://cline.bot/tos). A routed id such as `cline-pass/cline-pass/kimi-k3` is
intentional: the first segment selects the opencodex provider, while `cline-pass/kimi-k3` is the
full model slug sent upstream. ClinePass quota is shared by the account across rolling 5-hour,
weekly, and monthly limits. opencodex currently advertises the live-verified `low` reasoning tier;
higher requested tiers clamp to `low` until the gateway publishes or verifies a wider ladder.
weekly, and monthly limits. A 2026-08-13 live probe verified that every static ClinePass model
accepts `low`, `medium`, `high`, `xhigh`, and `max` at the gateway input boundary. opencodex
preserves those requested tiers; any backend-specific normalization remains ClinePass's responsibility.
Comment thread
Wibias marked this conversation as resolved.

**Cline** is the same API key and endpoint on pay-as-you-go usage billing across 100+ models
(OpenRouter-style ids like `anthropic/claude-sonnet-4-6`). Cline's promotional free models are only
Expand Down
4 changes: 2 additions & 2 deletions docs-site/src/content/docs/ja/guides/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,8 @@ opencodex には組み込みプリセットが 79 個含まれています。キ
[Cline の利用規約](https://cline.bot/tos)に記載された Cline Bot Inc. です。`cline-pass/cline-pass/kimi-k3` のようなルーティング ID は
意図した形式です。先頭は opencodex のプロバイダー、残りの `cline-pass/kimi-k3` は upstream に送信する
完全なモデル slug です。使用量はアカウントのローリング 5 時間、週次、月次の各上限で共有されます。
現在 opencodex が公開する reasoning tier は実機検証済みの `low` のみで、より高い要求は公式範囲が
公開または検証されるまで `low` にクランプされます
2026-08-13 の実機検証で、すべての静的 ClinePass モデルが gateway input で `low`、`medium`、`high`、`xhigh`、`max` を受け付けることを確認しました。
opencodex は要求された tier をそのまま保持し、バックエンド固有の正規化は ClinePass 側に委ねます

**Cline** は同じ API キー・エンドポイントを従量課金で使い、100 以上のモデルにアクセスできます
(OpenRouter 形式の ID、例: `anthropic/claude-sonnet-4-6`)。Cline の期間限定無料モデルは
Expand Down
4 changes: 2 additions & 2 deletions docs-site/src/content/docs/ko/guides/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,8 @@ opencodex에는 빌트인 프리셋이 79개 들어 있습니다. 키 방식 67
같은 라우팅 ID는 정상입니다.
앞의 `cline-pass`는 opencodex 프로바이더이고, 뒤의 `cline-pass/kimi-k3`는 upstream에 보내는
전체 모델 slug입니다. ClinePass 사용량은 계정의 5시간 롤링·주간·월간 한도를 함께 사용합니다.
현재 opencodex는 실측된 `low` reasoning 단계만 광고하며, 더 높은 요청은 공식 지원 범위가
게시되거나 검증될 때까지 `low`로 제한합니다.
2026-08-13 실측에서 모든 정적 ClinePass 모델이 게이트웨이 입력에서 `low`, `medium`, `high`, `xhigh`, `max`를 수락하는 것을 확인했습니다.
opencodex는 요청한 단계를 그대로 보존하며, 백엔드별 정규화는 ClinePass가 담당합니다.

**Cline**은 동일한 API 키·엔드포인트를 종량제로 사용하며 100개 이상의 모델에 접근합니다
(OpenRouter 형식 ID, 예: `anthropic/claude-sonnet-4-6`). Cline의 프로모션 무료 모델은
Expand Down
5 changes: 3 additions & 2 deletions docs-site/src/content/docs/ru/guides/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,9 @@ opencodex поставляется с 79 встроенными пресетам
[условиях Cline](https://cline.bot/tos). Маршрут вида `cline-pass/cline-pass/kimi-k3`
намеренный: первая часть выбирает провайдера opencodex, а полный slug `cline-pass/kimi-k3`
отправляется upstream. Использование учитывается в общих для аккаунта скользящем 5-часовом,
недельном и месячном лимитах. Сейчас opencodex публикует только проверенный на живом API reasoning tier
`low`; более высокие запросы ограничиваются до `low`, пока шлюз не опубликует или не подтвердит более широкий диапазон.
недельном и месячном лимитах. Проверка живого API 2026-08-13 подтвердила, что все статические модели ClinePass
принимают на входе шлюза `low`, `medium`, `high`, `xhigh` и `max`. opencodex сохраняет запрошенный tier без изменения;
нормализация для конкретного backend остаётся ответственностью ClinePass.

**Cline** использует тот же ключ и эндпоинт с оплатой по мере использования и доступом к 100+ моделям
(ID в формате OpenRouter, например `anthropic/claude-sonnet-4-6`). Промо-бесплатные модели Cline
Expand Down
4 changes: 2 additions & 2 deletions docs-site/src/content/docs/zh-cn/guides/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,8 @@ ChatGPT 转发预设。仪表盘的 **Add provider** 选择器会打开密钥提
[Cline 条款](https://cline.bot/tos)所列的 Cline Bot Inc.。
`cline-pass/cline-pass/kimi-k3` 这样的路由 ID 是预期格式:第一段选择 opencodex 提供商,
其余的 `cline-pass/kimi-k3` 是发送到上游的完整模型 slug。用量由账户的滚动 5 小时、每周和
每月限额共同管理。当前 opencodex 仅公开经过实测的 `low` reasoning 档位;在网关公布或验证更宽
档位之前,更高请求会被限制为 `low`。
每月限额共同管理。2026-08-13 的实测确认,所有静态 ClinePass 模型在网关输入端都接受
`low`、`medium`、`high`、`xhigh` 和 `max`。opencodex 会保留请求的档位;后端特定的规范化由 ClinePass 负责

**Cline** 使用相同的 API 密钥和端点,按用量计费,可访问 100 多个模型
(OpenRouter 风格 ID,如 `anthropic/claude-sonnet-4-6`)。Cline 的促销免费模型仅在
Expand Down
10 changes: 5 additions & 5 deletions src/providers/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1300,10 +1300,10 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
modelContextWindows: CLINE_PASS_MODEL_CONTEXT_WINDOWS,
modelInputModalities: CLINE_PASS_MODEL_INPUT_MODALITIES,
noVisionModels: CLINE_PASS_TEXT_ONLY_MODELS,
// Only low and the `reasoning: { enabled, effort }` request shape have been accepted by a live
// ClinePass request. Neither wire detail is currently documented, so clamp higher Codex
// requests to the verified tier until the gateway documents or is live-probed more broadly.
reasoningEfforts: ["low"],
// Live-probed 2026-08-13 across every static ClinePass model: the gateway accepts and
// validates low/medium/high/xhigh/max, and rejects an invalid sentinel. Preserve the
// caller's requested tier and let ClinePass own any backend-specific normalization.
reasoningEfforts: ["low", "medium", "high", "xhigh", "max"],
reasoningWireFormat: "gateway-object",
preserveCustomDestination: true,
note: "ClinePass subscription API. Uses a Cline API key and the full cline-pass/<model> upstream slug; quota is shared across the account's rolling 5-hour, weekly, and monthly limits.",
Expand Down Expand Up @@ -1789,7 +1789,7 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
liveModels: true,
preserveCustomDestination: true,
// /v1/models is documented as callable authenticated or unauthenticated, so a 2xx catalog
// response cannot prove the supplied Bearer key is valid.
// response cannot prove that the supplied Bearer key is valid.
apiKeyValidation: "unknown",
// Featherless documents tool calling, but not a provider-wide parallel tool-call contract.
parallelToolCalls: false,
Expand Down
13 changes: 12 additions & 1 deletion src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,14 @@ 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);
Expand Down Expand Up @@ -342,7 +350,10 @@ 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 && registryEntry.reasoningEfforts !== undefined ? { reasoningEfforts: registryEntry.reasoningEfforts } : {}),
...((provider.reasoningEfforts === undefined || repairLegacyClinePassReasoningEfforts)
&& registryEntry.reasoningEfforts !== undefined
? { reasoningEfforts: [...registryEntry.reasoningEfforts] }
: {}),
...(provider.escapeBuiltinToolNames === undefined && registryEntry.escapeBuiltinToolNames !== undefined ? { escapeBuiltinToolNames: registryEntry.escapeBuiltinToolNames } : {}),
...(provider.keyOptional === undefined && registryEntry.keyOptional !== undefined ? { keyOptional: registryEntry.keyOptional } : {}),
...(provider.modelSuffixBracketStrip === undefined && registryEntry.modelSuffixBracketStrip !== undefined ? { modelSuffixBracketStrip: registryEntry.modelSuffixBracketStrip } : {}),
Expand Down
9 changes: 5 additions & 4 deletions tests/cline-pass-provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ describe("ClinePass provider", () => {
expect(entry?.models).toEqual(OFFICIAL_CLINE_PASS_MODELS);
expect(entry?.models).toContain(entry?.defaultModel);
expect(entry?.liveModels).toBeUndefined();
expect(entry?.reasoningEfforts).toEqual(["low"]);
expect(entry?.reasoningEfforts).toEqual(["low", "medium", "high", "xhigh", "max"]);
expect(entry?.modelReasoningEfforts).toBeUndefined();
expect(entry?.modelMaxInputTokens).toBeUndefined();
expect(entry?.noVisionModels).toEqual([
"cline-pass/glm-5.2",
Expand Down Expand Up @@ -102,12 +103,12 @@ describe("ClinePass provider", () => {
expect(route.modelId).toBe("cline-pass/kimi-k3");
expect(route.provider).toMatchObject({ reasoningWireFormat: "gateway-object" });
expect(body.model).toBe("cline-pass/kimi-k3");
expect(body.reasoning).toEqual({ enabled: true, effort: "low" });
expect(body.reasoning).toEqual({ enabled: true, effort: "high" });
expect(body).not.toHaveProperty("reasoning_effort");
expect(request.reasoningLog).toEqual({
effectiveEffort: "low",
effectiveEffort: "high",
wireField: "reasoning.effort",
wireValue: "low",
wireValue: "high",
});
});

Expand Down
152 changes: 152 additions & 0 deletions tests/cline-pass-reasoning-efforts.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { describe, expect, test } from "bun:test";
import { createOpenAIChatAdapter } from "../src/adapters/openai-chat";
import { PROVIDER_REGISTRY } from "../src/providers/registry";
import { routeModel } from "../src/router";
import type { OcxConfig, OcxParsedRequest } from "../src/types";

const CLINE_PASS_MODELS = [
"cline-pass/glm-5.2",
"cline-pass/kimi-k3",
"cline-pass/kimi-k2.7-code",
"cline-pass/kimi-k2.6",
"cline-pass/deepseek-v4-pro",
"cline-pass/deepseek-v4-flash",
"cline-pass/mimo-v2.5",
"cline-pass/mimo-v2.5-pro",
"cline-pass/minimax-m3",
"cline-pass/qwen3.7-max",
"cline-pass/qwen3.7-plus",
] as const;

const CLINE_PASS_REASONING_EFFORTS = ["low", "medium", "high", "xhigh", "max"] as const;

// Live probe 2026-08-13 against https://api.cline.bot/api/v1/chat/completions:
// every static ClinePass model accepted every effort above, while an invalid sentinel was
// rejected with the gateway's accepted enum (none|minimal|low|medium|high|xhigh|max).
// This pins ClinePass's INPUT contract only. It deliberately does not claim that every backend
// implements five distinct native compute modes; backend-specific normalization remains ClinePass's job.
function registryEntry() {
const entry = PROVIDER_REGISTRY.find(provider => provider.id === "cline-pass");
if (!entry) throw new Error("missing ClinePass registry entry");
return entry;
}

function parsed(modelId: string, reasoning: string): OcxParsedRequest {
return {
modelId,
context: { messages: [{ role: "user", content: "hello", timestamp: 0 }] },
stream: false,
options: { reasoning },
};
}

const config: OcxConfig = {
port: 10100,
defaultProvider: "cline-pass",
providers: {
"cline-pass": {
adapter: "openai-chat",
baseUrl: "https://api.cline.bot/api/v1",
apiKey: "cline-test-key",
authMode: "key",
},
},
};

describe("ClinePass reasoning effort capabilities", () => {
test("registry exposes the full live-probed gateway input ladder provider-wide", () => {
const entry = registryEntry();

expect(entry.reasoningEfforts).toEqual([...CLINE_PASS_REASONING_EFFORTS]);
expect(entry.modelReasoningEfforts).toBeUndefined();
});

test("preserves every live-probed effort for every static ClinePass model", () => {
for (const model of CLINE_PASS_MODELS) {
for (const effort of CLINE_PASS_REASONING_EFFORTS) {
const route = routeModel(config, `cline-pass/${model}`);
const request = createOpenAIChatAdapter(route.provider).buildRequest(parsed(route.modelId, effort));
const body = JSON.parse(request.body) as Record<string, unknown>;

expect(route.modelId).toBe(model);
expect(body.reasoning).toEqual({ enabled: true, effort });
expect(body).not.toHaveProperty("reasoning_effort");
expect(request.reasoningLog).toEqual({
effectiveEffort: effort,
wireField: "reasoning.effort",
wireValue: effort,
});
}
}
});

test("DeepSeek V4 Flash preserves max instead of clamping it", () => {
const route = routeModel(config, "cline-pass/cline-pass/deepseek-v4-flash");
const request = createOpenAIChatAdapter(route.provider).buildRequest(parsed(route.modelId, "max"));
const body = JSON.parse(request.body) as Record<string, unknown>;

expect(body.reasoning).toEqual({ enabled: true, effort: "max" });
expect(request.reasoningLog).toEqual({
effectiveEffort: "max",
wireField: "reasoning.effort",
wireValue: "max",
});
});

test("Codex ultra still crosses the provider boundary as max", () => {
const route = routeModel(config, "cline-pass/cline-pass/glm-5.2");
const request = createOpenAIChatAdapter(route.provider).buildRequest(parsed(route.modelId, "ultra"));
const body = JSON.parse(request.body) as Record<string, unknown>;

expect(body.reasoning).toEqual({ enabled: true, effort: "max" });
expect(request.reasoningLog).toEqual({
effectiveEffort: "max",
wireField: "reasoning.effort",
wireValue: "max",
});
});

test("canonical ClinePass repairs the historical generated low-only preset", () => {
const staleConfig: OcxConfig = {
...config,
providers: {
"cline-pass": {
...config.providers!["cline-pass"],
reasoningEfforts: ["low"],
reasoningWireFormat: "gateway-object",
},
},
};
const route = routeModel(staleConfig, "cline-pass/cline-pass/deepseek-v4-flash");
const request = createOpenAIChatAdapter(route.provider).buildRequest(parsed(route.modelId, "max"));
const body = JSON.parse(request.body) as Record<string, unknown>;

expect(route.provider.reasoningEfforts).toEqual([...CLINE_PASS_REASONING_EFFORTS]);
expect(body.reasoning).toEqual({ enabled: true, effort: "max" });
expect(request.reasoningLog).toEqual({
effectiveEffort: "max",
wireField: "reasoning.effort",
wireValue: "max",
});
});

test("does not repair a low-only same-name provider on a custom destination", () => {
const customConfig: OcxConfig = {
...config,
providers: {
"cline-pass": {
...config.providers!["cline-pass"],
baseUrl: "https://example.com/v1",
reasoningEfforts: ["low"],
reasoningWireFormat: "gateway-object",
},
},
};
const route = routeModel(customConfig, "cline-pass/cline-pass/deepseek-v4-flash");
const request = createOpenAIChatAdapter(route.provider).buildRequest(parsed(route.modelId, "max"));
const body = JSON.parse(request.body) as Record<string, unknown>;

expect(route.provider.reasoningEfforts).toEqual(["low"]);
expect(body.reasoning).toEqual({ enabled: true, effort: "low" });
});
});
Loading