feat(providers): add ClinePass API-key provider (#5942)

Integrated into release/v3.8.44 — ClinePass API-key (BYOK) provider (port upstream 9router#2304, co-authored @adentdk). Validated locally: 16 clinepass tests green; fixed the APIKEY count 158→159 + translate-path golden snapshot (clinepass is a genuine new provider). Remaining UNSTABLE red is the pre-existing environmental setup-claude base-red (opencode-plugin dist not built in fast-path). Supersedes stub #5541.
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-02 17:36:16 -03:00
committed by GitHub
parent 2b0da37c19
commit 26fd7b6a8a
14 changed files with 548 additions and 9 deletions

View File

@@ -8,11 +8,11 @@
### ✨ New Features
_TBD_
- **feat(providers):** add ClinePass as a first-class API-key provider (Cline's BYOK gateway). (thanks @adentdk)
### 🔧 Bug Fixes
- **fix(translator):** antigravity→openai request now emits Anthropic-compliant content blocks — drops empty text blocks and preserves tool calls/text co-located with tool results. (thanks @SahrulRamadhanHardiansyah)
_TBD_
### 📝 Maintenance

View File

@@ -49,6 +49,7 @@ import { groqProvider } from "./registry/groq/index.ts";
import { inference_netProvider } from "./registry/inference-net/index.ts";
import { llm7Provider } from "./registry/llm7/index.ts";
import { cerebrasProvider } from "./registry/cerebras/index.ts";
import { clinepassProvider } from "./registry/clinepass/index.ts";
import { sparkdeskProvider } from "./registry/sparkdesk/index.ts";
import { nlpcloudProvider } from "./registry/nlpcloud/index.ts";
import { nvidiaProvider } from "./registry/nvidia/index.ts";
@@ -220,6 +221,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
"inference-net": inference_netProvider,
llm7: llm7Provider,
cerebras: cerebrasProvider,
clinepass: clinepassProvider,
sparkdesk: sparkdeskProvider,
nlpcloud: nlpcloudProvider,
nvidia: nvidiaProvider,

View File

@@ -0,0 +1,42 @@
import type { RegistryEntry } from "../../shared.ts";
// ClinePass — Cline's $9.99/mo BYOK API-key gateway (https://cline.bot). Distinct
// from the OAuth `cline` provider: same host (api.cline.bot) but a plain Bearer
// API key and the `cline-pass/*` model namespace. Responses are wrapped in a
// {success, data} envelope — unwrapped by open-sse/utils/clinepassEnvelope.ts.
export const clinepassProvider: RegistryEntry = {
id: "clinepass",
alias: "clinepass",
format: "openai",
executor: "default",
baseUrl: "https://api.cline.bot/api/v1/chat/completions",
authType: "apikey",
authHeader: "bearer",
extraHeaders: {
"HTTP-Referer": "https://cline.bot",
"X-Title": "Cline",
},
models: [
{ id: "cline-pass/glm-5.2", name: "GLM-5.2 (ClinePass)" },
{ id: "cline-pass/kimi-k2.7-code", name: "Kimi K2.7 Code (ClinePass)" },
{ id: "cline-pass/kimi-k2.6", name: "Kimi K2.6 (ClinePass)" },
{
id: "cline-pass/deepseek-v4-pro",
name: "DeepSeek V4 Pro (ClinePass)",
supportsReasoning: true,
maxOutputTokens: 50000,
},
{
id: "cline-pass/deepseek-v4-flash",
name: "DeepSeek V4 Flash (ClinePass)",
supportsReasoning: true,
maxOutputTokens: 50000,
},
{ id: "cline-pass/mimo-v2.5", name: "MiMo-V2.5 (ClinePass)" },
{ id: "cline-pass/mimo-v2.5-pro", name: "MiMo-V2.5-Pro (ClinePass)" },
{ id: "cline-pass/minimax-m3", name: "MiniMax M3 (ClinePass)" },
{ id: "cline-pass/qwen3.7-max", name: "Qwen3.7 Max (ClinePass)" },
{ id: "cline-pass/qwen3.7-plus", name: "Qwen3.7 Plus (ClinePass)" },
],
passthroughModels: true,
};

View File

@@ -737,9 +737,54 @@ export class DefaultExecutor extends BaseExecutor {
}
}
// ClinePass reasoning models burn all of max_tokens on the thinking phase
// when the budget is too small, leaving content empty (finish_reason:
// "length"). Bump max_tokens to a safe floor when reasoning is enabled and
// the budget is undersized. CLINEPASS-GATED — no-op for every other provider.
if (typeof withDefaults === "object" && withDefaults !== null) {
this.ensureThinkingBudget(withDefaults as Record<string, unknown>, model);
}
return withDefaults;
}
// ClinePass / OpenRouter-style thinking models leave content empty when the
// reasoning budget consumes all of max_tokens. Bump max_tokens to a safe
// minimum only when reasoning is enabled and the budget is undersized.
// CLINEPASS-GATED: returns early for every other provider.
ensureThinkingBudget(body: Record<string, unknown>, model: string): Record<string, unknown> {
if (!body || this.provider !== "clinepass") return body;
const outboundModel = typeof body.model === "string" ? body.model : model;
const entry = getRegistryEntry(this.provider);
const modelEntry = entry?.models?.find((m) => m.id === outboundModel);
if (!modelEntry?.supportsReasoning) return body;
const extraBody = body.extra_body as Record<string, unknown> | undefined;
const thinking = extraBody?.thinking as Record<string, unknown> | undefined;
const effort = body.reasoning_effort;
const reasoningEnabled =
thinking?.type === "enabled" ||
(typeof effort === "string" && effort !== "none" && effort !== "off") ||
effort === true;
if (!reasoningEnabled) return body;
const MIN_TOKENS = 4096;
const maxOutput =
typeof modelEntry.maxOutputTokens === "number" && modelEntry.maxOutputTokens > 0
? modelEntry.maxOutputTokens
: MIN_TOKENS;
const target = Math.min(MIN_TOKENS, maxOutput);
const current = body.max_tokens ?? body.max_completion_tokens;
if (typeof current !== "number" || current <= 0) {
body.max_tokens = target;
} else if (current < MIN_TOKENS && current < maxOutput) {
body.max_tokens = MIN_TOKENS;
}
return body;
}
/**
* Refresh credentials via the centralized tokenRefresh service.
* Delegates to getAccessToken() which handles all providers with

View File

@@ -212,6 +212,7 @@ import {
type NonStreamingSseTerminalState,
} from "./chatCore/nonStreamingSse.ts";
import { parseNonStreamingResponseBody } from "./chatCore/nonStreamingResponseParse.ts";
import { unwrapClinepassEnvelope } from "../utils/clinepassEnvelope.ts";
import { recordNonStreamingUsageStats } from "./chatCore/nonStreamingUsageStats.ts";
import {
createBodyTimeoutError,
@@ -3517,6 +3518,63 @@ export async function handleChatCore({
let responseBody = parsed.responseBody;
let responsePayloadFormat = parsed.responsePayloadFormat;
// ── ClinePass {success,data} envelope unwrap (before translation) ──────────
// ClinePass wraps non-streaming JSON in a {success, data} envelope; errors
// use {success:false, error}. Transient {success:false, error:"empty..."}
// responses get one 2s retry before surfacing. CLINEPASS-GATED — untouched
// for every other provider. Envelope errors route through createErrorResult
// (→ buildErrorBody/sanitizeErrorMessage, Rule #12).
if (provider === "clinepass") {
let { body: unwrapped, error: envError } = unwrapClinepassEnvelope(responseBody, provider);
if (envError && /empty/i.test(envError.message || "")) {
log?.warn?.("RETRY", "clinepass returned empty content, retrying once after 2s");
await new Promise((r) => setTimeout(r, 2000));
try {
const retryResult = await executeProviderRequest(effectiveModel, false);
if (retryResult?.response?.ok) {
const retryParsed = await parseNonStreamingResponseBody({
providerResponse: retryResult.response,
upstreamStream: undefined,
providerHeaders: retryResult.headers,
finalBody: retryResult.transformedBody,
targetFormat,
model,
log,
});
if (retryParsed.kind !== "invalid_sse" && retryParsed.kind !== "invalid_json") {
providerResponse = retryResult.response;
providerUrl = retryResult.url;
providerHeaders = retryResult.headers;
finalBody = providerRequestCapture.body(retryResult.transformedBody);
({ body: unwrapped, error: envError } = unwrapClinepassEnvelope(
retryParsed.responseBody,
provider
));
}
}
} catch (retryErr) {
log?.warn?.(
"RETRY",
`clinepass retry failed: ${
retryErr instanceof Error ? retryErr.message : String(retryErr)
}`
);
}
}
if (envError) {
appendRequestLog({
model,
provider,
connectionId,
status: `FAILED ${HTTP_STATUS.BAD_GATEWAY}`,
}).catch(() => {});
persistFailureUsage(HTTP_STATUS.BAD_GATEWAY, "clinepass_envelope_error");
trackPendingRequest(model, provider, connectionId, false);
return createErrorResult(HTTP_STATUS.BAD_GATEWAY, envError.message);
}
responseBody = unwrapped;
}
// Check for empty content response (fake success) - trigger fallback
if (isEmptyContentResponse(responseBody)) {
appendRequestLog({

View File

@@ -0,0 +1,76 @@
import { buildClineHeaders } from "@/shared/utils/clineAuth";
// ClinePass live-models resolver. ClinePass is API-key-only (BYOK), but the
// underlying api.cline.bot host also accepts the OAuth `cline` credential shape,
// so the resolver reuses buildClineHeaders() (the shared workos:-prefixed Cline
// header set) for the non-apikey path. Only `cline-pass/*` model ids are kept.
const CLINEPASS_MODELS_ENDPOINT = "https://api.cline.bot/api/v1/models";
const FETCH_TIMEOUT_MS = 5000;
export interface ClinepassModel {
id: string;
name: string;
}
/**
* Filter a raw models list down to the ClinePass namespace (`cline-pass/*`).
* Pure — shared by the live resolver and the discovery-config parseResponse.
*/
export function filterClinepassModels(rawList: unknown): ClinepassModel[] {
if (!Array.isArray(rawList)) return [];
return rawList
.filter(
(m): m is { id: string; name?: string } =>
!!m &&
typeof (m as { id?: unknown }).id === "string" &&
(m as { id: string }).id.startsWith("cline-pass/")
)
.map((m) => ({ id: m.id, name: m.name || m.id }));
}
function buildModelListHeaders(token: string, isApiKey: boolean): Record<string, string> {
if (isApiKey) {
return {
Accept: "application/json",
Authorization: `Bearer ${token}`,
};
}
return buildClineHeaders(token, { Accept: "application/json" });
}
/**
* Resolve the live ClinePass model catalogue for a connection. Returns
* `{ models }` on success or `null` on any failure (missing token, non-2xx,
* bad shape, timeout) so callers fall back to the static registry catalogue.
*/
export async function resolveClinepassModels(credentials: {
apiKey?: string | null;
accessToken?: string | null;
}): Promise<{ models: ClinepassModel[] } | null> {
const isApiKey = Boolean(credentials?.apiKey);
const token = isApiKey ? credentials.apiKey : credentials?.accessToken;
if (!token) return null;
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
try {
const headers = buildModelListHeaders(token, isApiKey);
const response = await fetch(CLINEPASS_MODELS_ENDPOINT, {
method: "GET",
headers,
signal: controller.signal,
});
if (!response.ok) return null;
const json = await response.json();
const rawList = Array.isArray(json) ? json : json?.data;
const models = filterClinepassModels(rawList);
return models.length ? { models } : null;
} catch {
return null;
} finally {
clearTimeout(timer);
}
}

View File

@@ -0,0 +1,59 @@
// ClinePass upstream wraps non-streaming JSON responses in a {success, data}
// envelope (errors use {success: false, error}). Detect and unwrap; pass the
// payload through untouched for every other provider / shape.
export interface ClinepassEnvelopeError {
message: string;
status: number | null;
}
export interface ClinepassEnvelopeResult {
body: unknown;
error: ClinepassEnvelopeError | null;
}
/**
* Unwrap a ClinePass {success, data} envelope.
*
* - Non-clinepass provider, non-object, array, or object without a `success`
* key → pass through untouched ({ body, error: null }).
* - { success: false, ... } → { body: null, error: { message, status } } with
* the upstream error string extracted (never a local stack — the caller must
* still route it through sanitizeErrorMessage before emitting a response).
* - { success: true, data: {...} } → unwrap to `data`.
*/
export function unwrapClinepassEnvelope(
body: unknown,
provider: string | null | undefined
): ClinepassEnvelopeResult {
if (provider !== "clinepass") return { body, error: null };
if (!body || typeof body !== "object" || Array.isArray(body)) return { body, error: null };
const record = body as Record<string, unknown>;
if (!("success" in record)) return { body, error: null };
if (record.success === false) {
const rawError = record.error;
const message =
typeof rawError === "string"
? rawError
: (rawError && typeof rawError === "object"
? ((rawError as Record<string, unknown>).message as string | undefined)
: undefined) ||
(typeof record.message === "string" ? record.message : undefined) ||
"Upstream error";
const statusCode = typeof record.statusCode === "number" ? record.statusCode : null;
return { body: null, error: { message, status: statusCode } };
}
if (
record.success === true &&
"data" in record &&
record.data !== null &&
typeof record.data === "object"
) {
return { body: record.data, error: null };
}
return { body, error: null };
}

View File

@@ -1,4 +1,5 @@
import { CORS_HEADERS } from "./cors.ts";
import { unwrapClinepassEnvelope } from "./clinepassEnvelope.ts";
import { getDefaultErrorMessage, getErrorInfo } from "../config/errorConfig.ts";
import { normalizePayloadForLog } from "@/lib/logPayloads";
import type { ModelCooldownErrorPayload } from "@/types";
@@ -230,7 +231,14 @@ export async function parseUpstreamError(response: Response, provider: string |
const parsed = JSON.parse(text);
// Handle array responses (e.g., from some Gemini APIs)
const json = (Array.isArray(parsed) && parsed.length > 0 ? parsed[0] : parsed) || {};
message = json.error?.message || json.message || json.error || text;
// ClinePass wraps upstream errors in a {success:false, error} envelope.
// Extract the upstream error string (an upstream JSON field, not a local
// stack) — still routed through sanitizeErrorMessage/buildErrorBody by
// every consumer below (Rule #12).
const { error: clinepassEnvError } = unwrapClinepassEnvelope(json, provider);
message = clinepassEnvError
? clinepassEnvError.message
: json.error?.message || json.message || json.error || text;
errorCode = json.error?.code || json.code;
errorType = json.error?.type || json.type;
} catch {
@@ -497,7 +505,8 @@ export function formatProviderError(
const message = error.message || "Unknown error";
// Expose low-level cause (e.g. UND_ERR_SOCKET, ECONNRESET, ETIMEDOUT) for diagnosing fetch failures
const cause = (error as { cause?: unknown }).cause;
const causeObj = cause && typeof cause === "object" ? (cause as Record<string, unknown>) : undefined;
const causeObj =
cause && typeof cause === "object" ? (cause as Record<string, unknown>) : undefined;
const causeCode = typeof causeObj?.code === "string" ? causeObj.code : undefined;
const causeMsg = typeof causeObj?.message === "string" ? causeObj.message : undefined;
const causeStr =

View File

@@ -1,6 +1,7 @@
import { getAntigravityModelsDiscoveryUrls } from "@omniroute/open-sse/config/antigravityUpstream.ts";
import { getAntigravityHeaders } from "@omniroute/open-sse/services/antigravityHeaders.ts";
import { parseGeminiModelsList } from "@/lib/providerModels/geminiModelsParser";
import { filterClinepassModels } from "@omniroute/open-sse/services/clinepassModels.ts";
import { normalizeOpenAiLikeModelsResponse } from "./normalizers";
export type ProviderModelsConfigEntry = {
@@ -246,6 +247,16 @@ export const PROVIDER_MODELS_CONFIG: Record<string, ProviderModelsConfigEntry> =
authPrefix: "Bearer ",
parseResponse: (data) => data.data || data.models || [],
},
// ClinePass (BYOK apikey gateway) — same host as OAuth `cline`, but only the
// `cline-pass/*` namespace is surfaced (filterClinepassModels).
clinepass: {
url: "https://api.cline.bot/api/v1/models",
method: "GET",
headers: { "Content-Type": "application/json" },
authHeader: "Authorization",
authPrefix: "Bearer ",
parseResponse: (data) => filterClinepassModels(Array.isArray(data) ? data : data?.data),
},
cohere: {
url: "https://api.cohere.com/v2/models",
method: "GET",

View File

@@ -28,6 +28,20 @@ export const APIKEY_PROVIDERS_GATEWAYS = {
"Use a Command Code API key. Requests are sent to Command Code's /alpha/generate endpoint.",
apiHint: "Create or copy an API key from Command Code, then paste it here as a Bearer token.",
},
clinepass: {
id: "clinepass",
alias: "clinepass",
name: "ClinePass",
icon: "vpn_key",
color: "#5B9BD5",
textIcon: "CP",
passthroughModels: true,
website: "https://cline.bot",
notice: {
text: "ClinePass is Cline's paid BYOK gateway ($9.99/mo). Bring your own Cline API key; requests hit api.cline.bot with the cline-pass/* model namespace.",
apiKeyUrl: "https://app.cline.bot/settings/api-keys",
},
},
openrouter: {
id: "openrouter",
alias: "openrouter",

View File

@@ -772,6 +772,35 @@
"stream": "https://api.cline.bot/api/v1/chat/completions"
}
},
"clinepass": {
"format": "openai",
"headers": {
"apiKey": {
"Accept": "text/event-stream",
"Authorization": "Bearer <TOK>",
"Content-Type": "application/json",
"HTTP-Referer": "https://cline.bot",
"X-Title": "Cline"
},
"nonStream": {
"Authorization": "Bearer <TOK>",
"Content-Type": "application/json",
"HTTP-Referer": "https://cline.bot",
"X-Title": "Cline"
},
"oauth": {
"Accept": "text/event-stream",
"Authorization": "Bearer <TOK>",
"Content-Type": "application/json",
"HTTP-Referer": "https://cline.bot",
"X-Title": "Cline"
}
},
"url": {
"nonStream": "https://api.cline.bot/api/v1/chat/completions",
"stream": "https://api.cline.bot/api/v1/chat/completions"
}
},
"cloudflare-ai": {
"format": "openai",
"headers": {

View File

@@ -0,0 +1,117 @@
import test from "node:test";
import assert from "node:assert/strict";
const { APIKEY_PROVIDERS } = await import("../../src/shared/constants/providers.ts");
const { REGISTRY: providerRegistry } = await import("../../open-sse/config/providerRegistry.ts");
const { unwrapClinepassEnvelope } = await import("../../open-sse/utils/clinepassEnvelope.ts");
const { filterClinepassModels } = await import("../../open-sse/services/clinepassModels.ts");
const { parseUpstreamError, buildErrorBody } = await import("../../open-sse/utils/error.ts");
// ── Provider metadata (Zod-validated APIKEY catalog) ─────────────────────────
test("ClinePass is registered as an API-key provider with the canonical identity", () => {
const cp = APIKEY_PROVIDERS.clinepass;
assert.ok(cp, "APIKEY_PROVIDERS.clinepass must be defined");
assert.equal(cp.id, "clinepass");
assert.equal(cp.alias, "clinepass");
assert.equal(cp.name, "ClinePass");
assert.equal(cp.website, "https://cline.bot");
assert.equal(
(cp as { notice?: { apiKeyUrl?: string } }).notice?.apiKeyUrl,
"https://app.cline.bot/settings/api-keys"
);
});
test("ClinePass registry entry uses OpenAI format with bearer apikey auth + Cline headers", () => {
const entry = providerRegistry.clinepass;
assert.ok(entry, "providerRegistry.clinepass must be defined");
assert.equal(entry.id, "clinepass");
assert.equal(entry.format, "openai");
assert.equal(entry.executor, "default");
assert.equal(entry.authType, "apikey");
assert.equal(entry.authHeader, "bearer");
assert.equal(entry.baseUrl, "https://api.cline.bot/api/v1/chat/completions");
assert.equal(entry.extraHeaders?.["HTTP-Referer"], "https://cline.bot");
assert.equal(entry.extraHeaders?.["X-Title"], "Cline");
});
test("ClinePass models are cline-pass/* and deepseek entries flag reasoning", () => {
const models = providerRegistry.clinepass.models;
const ids = models.map((m: { id: string }) => m.id);
assert.ok(ids.length >= 8, "expect a non-trivial seed list");
assert.equal(new Set(ids).size, ids.length, "model ids must be unique");
for (const id of ids) {
assert.ok(id.startsWith("cline-pass/"), `${id} must be in the cline-pass/ namespace`);
}
const deepseek = models.filter((m: { id: string }) => m.id.includes("deepseek"));
assert.ok(deepseek.length >= 2, "expect the two DeepSeek V4 entries");
for (const m of deepseek) {
assert.equal((m as { supportsReasoning?: boolean }).supportsReasoning, true);
}
});
// ── Envelope unwrap ──────────────────────────────────────────────────────────
test("unwrapClinepassEnvelope: success unwraps to data", () => {
const inner = { id: "chatcmpl-1", choices: [] };
const { body, error } = unwrapClinepassEnvelope({ success: true, data: inner }, "clinepass");
assert.equal(error, null);
assert.deepEqual(body, inner);
});
test("unwrapClinepassEnvelope: {success:false} yields an error", () => {
const { body, error } = unwrapClinepassEnvelope(
{ success: false, error: "empty response content", statusCode: 502 },
"clinepass"
);
assert.equal(body, null);
assert.ok(error);
assert.equal(error?.message, "empty response content");
assert.equal(error?.status, 502);
});
test("unwrapClinepassEnvelope: nested error.message extracted", () => {
const { error } = unwrapClinepassEnvelope(
{ success: false, error: { message: "quota exceeded" } },
"clinepass"
);
assert.equal(error?.message, "quota exceeded");
});
test("unwrapClinepassEnvelope: non-clinepass provider passes through untouched", () => {
const payload = { success: false, error: "boom" };
const { body, error } = unwrapClinepassEnvelope(payload, "openai");
assert.equal(error, null);
assert.deepEqual(body, payload);
});
test("unwrapClinepassEnvelope: non-object / array / no-success passthrough", () => {
assert.deepEqual(unwrapClinepassEnvelope("plain", "clinepass"), { body: "plain", error: null });
assert.deepEqual(unwrapClinepassEnvelope([1, 2], "clinepass"), { body: [1, 2], error: null });
const bare = { id: "x" };
assert.deepEqual(unwrapClinepassEnvelope(bare, "clinepass"), { body: bare, error: null });
});
// ── Model filter ─────────────────────────────────────────────────────────────
test("filterClinepassModels keeps only cline-pass/* ids", () => {
const out = filterClinepassModels([
{ id: "cline-pass/glm-5.2", name: "GLM" },
{ id: "openai/gpt-5.5" },
{ id: "cline-pass/deepseek-v4-pro" },
{ notId: true },
]);
assert.deepEqual(out, [
{ id: "cline-pass/glm-5.2", name: "GLM" },
{ id: "cline-pass/deepseek-v4-pro", name: "cline-pass/deepseek-v4-pro" },
]);
assert.deepEqual(filterClinepassModels("not-array"), []);
});
// ── Error sanitization (Rule #12 — no stack leak) ────────────────────────────
test("parseUpstreamError unwraps clinepass envelope error without leaking a stack", async () => {
const upstream = new Response(
JSON.stringify({ success: false, error: "upstream at /srv/x.js:1:1 failed" }),
{ status: 502, headers: { "content-type": "application/json" } }
);
const parsed = await parseUpstreamError(upstream, "clinepass");
const body = buildErrorBody(502, parsed.message) as { error: { message: string } };
assert.ok(!body.error.message.includes("at /"), "sanitized error must not include a stack frame");
});

View File

@@ -0,0 +1,77 @@
import test from "node:test";
import assert from "node:assert/strict";
import { DefaultExecutor } from "../../open-sse/executors/default.ts";
// DefaultExecutor.ensureThinkingBudget — clinepass-gated max_tokens floor for
// reasoning models (prevents empty content when the budget is undersized).
test("bumps undersized max_tokens to 4096 for a clinepass reasoning model", () => {
const executor = new DefaultExecutor("clinepass");
const body = {
model: "cline-pass/deepseek-v4-pro",
reasoning_effort: "high",
max_tokens: 512,
} as Record<string, unknown>;
executor.ensureThinkingBudget(body, "cline-pass/deepseek-v4-pro");
assert.equal(body.max_tokens, 4096);
});
test("sets max_tokens floor when absent for a reasoning model", () => {
const executor = new DefaultExecutor("clinepass");
const body = {
model: "cline-pass/deepseek-v4-flash",
reasoning_effort: "medium",
} as Record<string, unknown>;
executor.ensureThinkingBudget(body, "cline-pass/deepseek-v4-flash");
assert.equal(body.max_tokens, 4096);
});
test("leaves an already-sufficient budget untouched", () => {
const executor = new DefaultExecutor("clinepass");
const body = {
model: "cline-pass/deepseek-v4-pro",
reasoning_effort: "high",
max_tokens: 8000,
} as Record<string, unknown>;
executor.ensureThinkingBudget(body, "cline-pass/deepseek-v4-pro");
assert.equal(body.max_tokens, 8000);
});
test("no-op when reasoning is disabled", () => {
const executor = new DefaultExecutor("clinepass");
const body = {
model: "cline-pass/deepseek-v4-pro",
max_tokens: 100,
} as Record<string, unknown>;
executor.ensureThinkingBudget(body, "cline-pass/deepseek-v4-pro");
assert.equal(body.max_tokens, 100);
});
test("no-op for a non-reasoning clinepass model", () => {
const executor = new DefaultExecutor("clinepass");
const body = {
model: "cline-pass/glm-5.2",
reasoning_effort: "high",
max_tokens: 100,
} as Record<string, unknown>;
executor.ensureThinkingBudget(body, "cline-pass/glm-5.2");
assert.equal(body.max_tokens, 100);
});
test("no-op for a non-clinepass provider (gate)", () => {
const executor = new DefaultExecutor("openrouter");
const body = {
model: "cline-pass/deepseek-v4-pro",
reasoning_effort: "high",
max_tokens: 100,
} as Record<string, unknown>;
executor.ensureThinkingBudget(body, "cline-pass/deepseek-v4-pro");
assert.equal(body.max_tokens, 100);
});

View File

@@ -31,12 +31,12 @@ test("barrel still exports every catalog + key helpers", () => {
}
});
test("APIKEY_PROVIDERS merges the 6 family files into 158 entries (no loss / no dup)", async () => {
test("APIKEY_PROVIDERS merges the 6 family files into 159 entries (no loss / no dup)", async () => {
const keys = Object.keys((P as Record<string, object>).APIKEY_PROVIDERS);
assert.equal(keys.length, 158);
assert.equal(new Set(keys).size, 158, "duplicate keys after spread-merge");
assert.equal(keys.length, 159);
assert.equal(new Set(keys).size, 159, "duplicate keys after spread-merge");
// the merged object's entry-count equals the sum of the 6 semantic family files; families are a
// strict partition (every provider in exactly one), so the sum must be exactly 158.
// strict partition (every provider in exactly one), so the sum must be exactly 159.
const families: [string, string][] = [
["gateways", "APIKEY_PROVIDERS_GATEWAYS"],
["frontier-labs", "APIKEY_PROVIDERS_FRONTIER"],
@@ -56,7 +56,7 @@ test("APIKEY_PROVIDERS merges the 6 family files into 158 entries (no loss / no
seen.add(k);
}
}
assert.equal(famTotal, 158, "families must partition all 158 providers");
assert.equal(famTotal, 159, "families must partition all 159 providers");
});
test("AI_PROVIDERS Proxy aggregates all sections; lookups resolve", () => {