Skip to content
Closed
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,8 @@ gitbook/README.md
open-sse.old/
.graphifyignore
graphify-out/*


dist/*
.omc
.claude
30 changes: 29 additions & 1 deletion open-sse/executors/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { getCachedClaudeHeaders } from "../utils/claudeHeaderCache.js";
import { proxyAwareFetch } from "../utils/proxyFetch.js";
import { injectReasoningContent } from "../utils/reasoningContentInjector.js";
import { stripUnsupportedParams } from "../translator/concerns/paramSupport.js";
import { getCapabilitiesForModel } from "../providers/capabilities.js";

// Auth header descriptors — derived from registry transport.auth, fallback to hardcoded defaults.
const BEARER = { combined: true, header: "Authorization", scheme: "bearer" };
Expand Down Expand Up @@ -92,7 +93,34 @@ export class DefaultExecutor extends BaseExecutor {
stripUnsupportedParams(this.provider, model, transformed);
}

return injectReasoningContent({ provider: this.provider, model, body: transformed });
const withReasoning = injectReasoningContent({ provider: this.provider, model, body: transformed });
return this.ensureThinkingBudget(withReasoning, model);
}

// ClinePass / OpenRouter-style thinking models burn all of max_tokens on reasoning
// when the budget is too small, leaving content empty (finish_reason: "length").
// Bump max_tokens to a safe minimum only when reasoning is enabled and budget undersized.
ensureThinkingBudget(body, model) {
if (!body || this.provider !== "clinepass") return body;
const caps = getCapabilitiesForModel(this.provider, model);
if (!caps?.reasoning) return body;

const reasoningEnabled = body.extra_body?.thinking?.type === "enabled"
|| (typeof body.reasoning_effort === "string" && body.reasoning_effort !== "none" && body.reasoning_effort !== "off")
|| body.reasoning_effort === true;
if (!reasoningEnabled) return body;

const MIN_TOKENS = 4096;
const cap = typeof caps.maxOutput === "number" && caps.maxOutput > 0 ? caps.maxOutput : MIN_TOKENS;
const target = Math.min(MIN_TOKENS, cap);
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 < cap) {
body.max_tokens = MIN_TOKENS;
}
return body;
}

// Fallback json_schema → json_object for openai-compatible providers without native Structured Output.
Expand Down
18 changes: 17 additions & 1 deletion open-sse/handlers/chatCore.js
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,23 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred

// True non-streaming response
if (!stream) {
const result = await handleNonStreamingResponse({ ...sharedCtx, providerResponse, sourceFormat, targetFormat, reqLogger, toolNameMap, trackDone, appendLog });
let result = await handleNonStreamingResponse({ ...sharedCtx, providerResponse, sourceFormat, targetFormat, reqLogger, toolNameMap, trackDone, appendLog });

// ClinePass sometimes returns {success:false, error:"empty response content"} transiently
if (provider === "clinepass" && !result?.success && /empty/i.test(result?.error || "")) {
log?.warn?.("RETRY", `clinepass returned empty content, retrying once after 2s`);
await new Promise(r => setTimeout(r, 2000));
try {
const retry = await executor.execute({ model, body: translatedBody, stream: false, credentials, signal: streamController.signal, log, proxyOptions });
if (retry?.response?.ok) {
providerResponse = retry.response;
result = await handleNonStreamingResponse({ ...sharedCtx, providerResponse, sourceFormat, targetFormat, reqLogger, toolNameMap, trackDone, appendLog });
}
} catch (retryErr) {
log?.warn?.("RETRY", `clinepass retry failed: ${retryErr.message}`);
}
}

streamController.handleComplete();
return result;
}
Expand Down
11 changes: 11 additions & 0 deletions open-sse/handlers/chatCore/nonStreamingHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ollamaBodyToOpenAI } from "../../translator/response/ollama-to-openai.j
import { addBufferToUsage, filterUsageForFormat } from "../../utils/usageTracking.js";
import { createErrorResult } from "../../utils/error.js";
import { HTTP_STATUS } from "../../config/runtimeConfig.js";
import { unwrapClinepassEnvelope } from "../../utils/clinepassEnvelope.js";
import { parseSSEToOpenAIResponse } from "./sseToJsonHandler.js";
import { buildRequestDetail, extractRequestConfig, extractUsageFromResponse, saveUsageStats } from "./requestDetail.js";
import { appendRequestLog, saveRequestDetail } from "@/lib/usageDb.js";
Expand Down Expand Up @@ -230,6 +231,16 @@ export async function handleNonStreamingResponse({ providerResponse, provider, m
});
}

// Unwrap ClinePass {success, data} envelope before decloak/translation
{
const { body: unwrapped, error: envError } = unwrapClinepassEnvelope(responseBody, provider);
if (envError) {
appendLog({ status: `FAILED ${HTTP_STATUS.BAD_GATEWAY}` });
return createErrorResult(HTTP_STATUS.BAD_GATEWAY, envError.message);
}
responseBody = unwrapped;
}

// Decloak tool_use names once on raw Claude body, before any translation (INPUT side)
responseBody = decloakToolNames(responseBody, toolNameMap);

Expand Down
7 changes: 7 additions & 0 deletions open-sse/providers/capabilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,13 @@ export const PROVIDER_CAPABILITIES = {
"deepseek-v4-flash": { vision: true, reasoning: true, thinkingFormat: "openai", thinkingCanDisable: false, contextWindow: 1000000, maxOutput: 50000 },
"deepseek-v3-2-volc": { reasoning: true, thinkingFormat: "openai", thinkingCanDisable: false, contextWindow: 96000, maxOutput: 32000 },
},
// ClinePass proxies through Vercel's OpenAI Chat Completions API, which only
// accepts reasoning.effort in {none,minimal,low,medium,high,xhigh}. Force
// "openai" so thinkingUnified.js emits valid Vercel enum values.
"clinepass": {
"deepseek-v4-pro": { reasoning: true, thinkingFormat: "openai", thinkingCanDisable: false, contextWindow: 1000000, maxOutput: 50000 },
"deepseek-v4-flash": { reasoning: true, thinkingFormat: "openai", thinkingCanDisable: false, contextWindow: 1000000, maxOutput: 50000 },
},
};

/**
Expand Down
46 changes: 46 additions & 0 deletions open-sse/providers/registry/clinepass.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
export default {
id: "clinepass",
priority: 85,
alias: "clinepass",
uiAlias: "clinepass",
display: {
name: "ClinePass",
icon: "vpn_key",
color: "#5B9BD5",
textIcon: "CP",
website: "https://cline.bot",
notice: {
apiKeyUrl: "https://app.cline.bot/settings/api-keys",
},
},
category: "apikey",
transport: {
baseUrl: "https://api.cline.bot/api/v1/chat/completions",
thinkingFormat: "openai",
headers: {
"HTTP-Referer": "https://cline.bot",
"X-Title": "Cline",
},
auth: {
combined: true,
header: "Authorization",
scheme: "bearer",
},
},
models: [
{ id: "glm-5.2", name: "GLM-5.2 (ClinePass)", upstreamModelId: "cline-pass/glm-5.2" },
{ id: "kimi-k2.7-code", name: "Kimi K2.7 Code (ClinePass)", upstreamModelId: "cline-pass/kimi-k2.7-code" },
{ id: "kimi-k2.6", name: "Kimi K2.6 (ClinePass)", upstreamModelId: "cline-pass/kimi-k2.6" },
{ id: "deepseek-v4-pro", name: "DeepSeek V4 Pro (ClinePass)", upstreamModelId: "cline-pass/deepseek-v4-pro" },
{ id: "deepseek-v4-flash", name: "DeepSeek V4 Flash (ClinePass)", upstreamModelId: "cline-pass/deepseek-v4-flash" },
{ id: "mimo-v2.5", name: "MiMo-V2.5 (ClinePass)", upstreamModelId: "cline-pass/mimo-v2.5" },
{ id: "mimo-v2.5-pro", name: "MiMo-V2.5-Pro (ClinePass)", upstreamModelId: "cline-pass/mimo-v2.5-pro" },
{ id: "minimax-m3", name: "MiniMax M3 (ClinePass)", upstreamModelId: "cline-pass/minimax-m3" },
{ id: "qwen3.7-max", name: "Qwen3.7 Max (ClinePass)", upstreamModelId: "cline-pass/qwen3.7-max" },
{ id: "qwen3.7-plus", name: "Qwen3.7 Plus (ClinePass)", upstreamModelId: "cline-pass/qwen3.7-plus" },
],
thinkingConfig: {
options: ["auto", "on", "off"],
defaultMode: "auto",
},
};
164 changes: 83 additions & 81 deletions open-sse/providers/registry/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,86 +15,87 @@ import p12 from "./cerebras.js";
import p13 from "./chutes.js";
import p14 from "./claude.js";
import p15 from "./cline.js";
import p16 from "./cloudflare-ai.js";
import p17 from "./codebuddy-cn.js";
import p18 from "./codex.js";
import p19 from "./cohere.js";
import p20 from "./comfyui.js";
import p21 from "./commandcode.js";
import p22 from "./coqui.js";
import p23 from "./cursor.js";
import p24 from "./deepgram.js";
import p25 from "./deepseek.js";
import p26 from "./edge-tts.js";
import p27 from "./elevenlabs.js";
import p28 from "./exa.js";
import p29 from "./fal-ai.js";
import p30 from "./firecrawl.js";
import p31 from "./fireworks.js";
import p32 from "./gemini-cli.js";
import p33 from "./gemini.js";
import p34 from "./github.js";
import p35 from "./gitlab.js";
import p36 from "./glm-cn.js";
import p37 from "./glm.js";
import p38 from "./google-pse.js";
import p39 from "./google-tts.js";
import p40 from "./grok-web.js";
import p41 from "./groq.js";
import p42 from "./huggingface.js";
import p43 from "./hyperbolic.js";
import p44 from "./iflow.js";
import p45 from "./inworld.js";
import p46 from "./jina-ai.js";
import p47 from "./jina-reader.js";
import p48 from "./kilocode.js";
import p49 from "./kimchi.js";
import p50 from "./kimi-coding.js";
import p51 from "./kimi.js";
import p52 from "./kiro.js";
import p53 from "./linkup.js";
import p54 from "./local-device.js";
import p55 from "./mimo-free.js";
import p56 from "./minimax-cn.js";
import p57 from "./minimax.js";
import p58 from "./mistral.js";
import p59 from "./mmf.js";
import p60 from "./nanobanana.js";
import p61 from "./nebius.js";
import p62 from "./nvidia.js";
import p63 from "./ollama-local.js";
import p64 from "./ollama.js";
import p65 from "./openai.js";
import p66 from "./opencode-go.js";
import p67 from "./opencode.js";
import p68 from "./openrouter.js";
import p69 from "./perplexity-web.js";
import p70 from "./perplexity.js";
import p71 from "./playht.js";
import p72 from "./qoder.js";
import p73 from "./qwen.js";
import p74 from "./recraft.js";
import p75 from "./runwayml.js";
import p76 from "./sdwebui.js";
import p77 from "./searchapi.js";
import p78 from "./searxng.js";
import p79 from "./serper.js";
import p80 from "./siliconflow.js";
import p81 from "./stability-ai.js";
import p82 from "./tavily.js";
import p83 from "./together.js";
import p84 from "./topaz.js";
import p85 from "./tortoise.js";
import p86 from "./venice.js";
import p87 from "./vercel-ai-gateway.js";
import p88 from "./vertex-partner.js";
import p89 from "./vertex.js";
import p90 from "./volcengine-ark.js";
import p91 from "./voyage-ai.js";
import p92 from "./xai.js";
import p93 from "./xiaomi-mimo.js";
import p94 from "./xiaomi-tokenplan.js";
import p95 from "./youcom.js";
import p16 from "./clinepass.js";
import p17 from "./cloudflare-ai.js";
import p18 from "./codebuddy-cn.js";
import p19 from "./codex.js";
import p20 from "./cohere.js";
import p21 from "./comfyui.js";
import p22 from "./commandcode.js";
import p23 from "./coqui.js";
import p24 from "./cursor.js";
import p25 from "./deepgram.js";
import p26 from "./deepseek.js";
import p27 from "./edge-tts.js";
import p28 from "./elevenlabs.js";
import p29 from "./exa.js";
import p30 from "./fal-ai.js";
import p31 from "./firecrawl.js";
import p32 from "./fireworks.js";
import p33 from "./gemini-cli.js";
import p34 from "./gemini.js";
import p35 from "./github.js";
import p36 from "./gitlab.js";
import p37 from "./glm-cn.js";
import p38 from "./glm.js";
import p39 from "./google-pse.js";
import p40 from "./google-tts.js";
import p41 from "./grok-web.js";
import p42 from "./groq.js";
import p43 from "./huggingface.js";
import p44 from "./hyperbolic.js";
import p45 from "./iflow.js";
import p46 from "./inworld.js";
import p47 from "./jina-ai.js";
import p48 from "./jina-reader.js";
import p49 from "./kilocode.js";
import p50 from "./kimchi.js";
import p51 from "./kimi-coding.js";
import p52 from "./kimi.js";
import p53 from "./kiro.js";
import p54 from "./linkup.js";
import p55 from "./local-device.js";
import p56 from "./mimo-free.js";
import p57 from "./minimax-cn.js";
import p58 from "./minimax.js";
import p59 from "./mistral.js";
import p60 from "./mmf.js";
import p61 from "./nanobanana.js";
import p62 from "./nebius.js";
import p63 from "./nvidia.js";
import p64 from "./ollama-local.js";
import p65 from "./ollama.js";
import p66 from "./openai.js";
import p67 from "./opencode-go.js";
import p68 from "./opencode.js";
import p69 from "./openrouter.js";
import p70 from "./perplexity-web.js";
import p71 from "./perplexity.js";
import p72 from "./playht.js";
import p73 from "./qoder.js";
import p74 from "./qwen.js";
import p75 from "./recraft.js";
import p76 from "./runwayml.js";
import p77 from "./sdwebui.js";
import p78 from "./searchapi.js";
import p79 from "./searxng.js";
import p80 from "./serper.js";
import p81 from "./siliconflow.js";
import p82 from "./stability-ai.js";
import p83 from "./tavily.js";
import p84 from "./together.js";
import p85 from "./topaz.js";
import p86 from "./tortoise.js";
import p87 from "./venice.js";
import p88 from "./vercel-ai-gateway.js";
import p89 from "./vertex-partner.js";
import p90 from "./vertex.js";
import p91 from "./volcengine-ark.js";
import p92 from "./voyage-ai.js";
import p93 from "./xai.js";
import p94 from "./xiaomi-mimo.js";
import p95 from "./xiaomi-tokenplan.js";
import p96 from "./youcom.js";

export default [
p0,
Expand Down Expand Up @@ -192,5 +193,6 @@ export default [
p92,
p93,
p94,
p95
p95,
p96
];
52 changes: 52 additions & 0 deletions open-sse/services/clinepassModels.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { buildClineHeaders } from "../shared/clineAuth.js";

const CLINEPASS_MODELS_ENDPOINT = "https://api.cline.bot/api/v1/models";
const FETCH_TIMEOUT_MS = 5000;

function buildModelListHeaders(token, isApiKey) {
if (isApiKey) {
return {
Accept: "application/json",
Authorization: `Bearer ${token}`,
};
}
return buildClineHeaders(token, { Accept: "application/json" });
}

export async function resolveClinepassModels(credentials) {
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;
if (!Array.isArray(rawList)) return null;

const models = rawList
.filter((m) => typeof m?.id === "string" && m.id.startsWith("cline-pass/"))
.map((m) => ({
id: m.id.slice("cline-pass/".length),
name: m.name || m.id.slice("cline-pass/".length),
}));

return models.length ? { models } : null;
} catch {
return null;
} finally {
clearTimeout(timer);
}
}
Loading