mirror of
https://github.com/Nezumi-2711/9router.git
synced 2026-09-22 20:00:47 +00:00
feat(clinepass): add ClinePass provider support
Register clinepass provider (OAuth + API-key) using Cline's OpenAI-compatible API with 10 curated models, live /v1/models resolver, refreshCline-based token refresh with workos: prefix, and dashboard OAuth login handler. Reference: https://github.com/jellydn/pi-clinepass-provider Closes #2261 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
committed by
decolua
co-authored by
Cursor
parent
76752a4396
commit
b08751c4ea
@@ -226,6 +226,7 @@ export class DefaultExecutor extends BaseExecutor {
|
||||
gemini: () => this.refreshFromGrant(credentials, proxyOptions),
|
||||
kiro: () => this.refreshKiro(credentials.refreshToken, proxyOptions),
|
||||
cline: () => this.refreshCline(credentials.refreshToken, proxyOptions),
|
||||
clinepass: () => this.refreshCline(credentials.refreshToken, proxyOptions),
|
||||
"kimi-coding": () => this.refreshKimiCoding(credentials.refreshToken, proxyOptions),
|
||||
kilocode: () => this.refreshKilocode(credentials.refreshToken, proxyOptions)
|
||||
};
|
||||
@@ -299,7 +300,11 @@ export class DefaultExecutor extends BaseExecutor {
|
||||
const data = payload?.data || payload;
|
||||
const expiresAtIso = data?.expiresAt;
|
||||
const expiresIn = expiresAtIso ? Math.max(1, Math.floor((new Date(expiresAtIso).getTime() - Date.now()) / 1000)) : undefined;
|
||||
return { accessToken: data?.accessToken, refreshToken: data?.refreshToken || refreshToken, expiresIn };
|
||||
let accessToken = data?.accessToken;
|
||||
if (accessToken && !accessToken.startsWith("workos:")) {
|
||||
accessToken = `workos:${accessToken}`;
|
||||
}
|
||||
return { accessToken, refreshToken: data?.refreshToken || refreshToken, expiresIn };
|
||||
}
|
||||
|
||||
async refreshKimiCoding(refreshToken, proxyOptions = null) {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
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: {
|
||||
signupUrl: "https://app.cline.bot",
|
||||
},
|
||||
},
|
||||
category: "oauth",
|
||||
authModes: ["oauth", "apikey"],
|
||||
hasOAuth: true,
|
||||
transport: {
|
||||
baseUrl: "https://api.cline.bot/api/v1/chat/completions",
|
||||
headers: {
|
||||
"HTTP-Referer": "https://cline.bot",
|
||||
"X-Title": "Cline",
|
||||
},
|
||||
auth: {
|
||||
combined: true,
|
||||
header: "Authorization",
|
||||
scheme: "bearer",
|
||||
hooks: [
|
||||
"clineHeaders",
|
||||
],
|
||||
},
|
||||
},
|
||||
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)" },
|
||||
{ id: "cline-pass/deepseek-v4-flash", name: "DeepSeek V4 Flash (ClinePass)" },
|
||||
{ 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)" },
|
||||
],
|
||||
oauth: {
|
||||
appBaseUrl: "https://app.cline.bot",
|
||||
apiBaseUrl: "https://api.cline.bot",
|
||||
authorizeUrl: "https://api.cline.bot/api/v1/auth/authorize",
|
||||
tokenUrl: "https://api.cline.bot/api/v1/auth/token",
|
||||
refreshUrl: "https://api.cline.bot/api/v1/auth/refresh",
|
||||
},
|
||||
thinkingConfig: {
|
||||
options: ["auto", "on", "off"],
|
||||
defaultMode: "auto",
|
||||
},
|
||||
};
|
||||
@@ -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,
|
||||
@@ -192,5 +193,6 @@ export default [
|
||||
p92,
|
||||
p93,
|
||||
p94,
|
||||
p95
|
||||
p95,
|
||||
p96
|
||||
];
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { buildClineHeaders } from "../shared/clineAuth.js";
|
||||
|
||||
const CLINEPASS_MODELS_ENDPOINT = "https://api.cline.bot/api/v1/models";
|
||||
const FETCH_TIMEOUT_MS = 5000;
|
||||
|
||||
/**
|
||||
* Build request headers for the ClinePass /models endpoint (Cline's upstream API).
|
||||
* - API keys are sent as plain Bearer tokens.
|
||||
* - OAuth access tokens must carry the WorkOS `workos:` prefix (handled by buildClineHeaders).
|
||||
*/
|
||||
function buildModelListHeaders(token, isApiKey) {
|
||||
if (isApiKey) {
|
||||
return {
|
||||
Accept: "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
};
|
||||
}
|
||||
return buildClineHeaders(token, { Accept: "application/json" });
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch ClinePass live model catalog from Cline's /models endpoint.
|
||||
*
|
||||
* @param {object} credentials - Connection credentials ({ accessToken, apiKey })
|
||||
* @returns {Promise<{ models: { id: string, name: string }[] } | null>}
|
||||
*/
|
||||
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,
|
||||
name: m.name || m.id,
|
||||
}));
|
||||
|
||||
return models.length ? { models } : null;
|
||||
} catch {
|
||||
return null;
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user