Skip to content
Draft
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
15 changes: 7 additions & 8 deletions open-sse/config/providerModels.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,13 @@ export const PROVIDER_MODELS = {
{ id: "deepseek/deepseek-reasoner", name: "DeepSeek Reasoner" },
],
cl: [ // Cline
{ id: "anthropic/claude-sonnet-4-20250514", name: "Claude Sonnet 4" },
{ id: "anthropic/claude-opus-4-20250514", name: "Claude Opus 4" },
{ id: "google/gemini-2.5-pro", name: "Gemini 2.5 Pro" },
{ id: "google/gemini-2.5-flash", name: "Gemini 2.5 Flash" },
{ id: "openai/gpt-4.1", name: "GPT-4.1" },
{ id: "openai/o3", name: "o3" },
{ id: "deepseek/deepseek-chat", name: "DeepSeek Chat" },
{ id: "anthropic/claude-sonnet-4.6", name: "Claude Sonnet 4.6" },
{ id: "anthropic/claude-opus-4.6", name: "Claude Opus 4.6" },
{ id: "openai/gpt-5.3-codex", name: "GPT-5.3 Codex" },
{ id: "openai/gpt-5.4", name: "GPT-5.4" },
{ id: "google/gemini-3.1-pro-preview", name: "Gemini 3.1 Pro Preview" },
{ id: "google/gemini-3.1-flash-lite-preview", name: "Gemini 3.1 Flash Lite Preview" },
{ id: "kwaipilot/kat-coder-pro", name: "KAT Coder Pro" },
],

// API Key Providers (alias = id)
Expand Down Expand Up @@ -372,4 +372,3 @@ export function getModelsByProviderId(providerId) {
const alias = PROVIDER_ID_TO_ALIAS[providerId] || providerId;
return PROVIDER_MODELS[alias] || [];
}

3 changes: 3 additions & 0 deletions open-sse/executors/default.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { BaseExecutor } from "./base.js";
import { PROVIDERS, OAUTH_ENDPOINTS } from "../config/constants.js";
import { buildClineHeaders } from "../../src/shared/utils/clineAuth.js";

export class DefaultExecutor extends BaseExecutor {
constructor(provider) {
Expand Down Expand Up @@ -65,6 +66,8 @@ export class DefaultExecutor extends BaseExecutor {
if (credentials.providerSpecificData?.orgId) {
headers["X-Kilocode-OrganizationID"] = credentials.providerSpecificData.orgId;
}
} else if (this.provider === "cline") {
Object.assign(headers, buildClineHeaders(credentials.apiKey || credentials.accessToken));
} else {
headers["Authorization"] = `Bearer ${credentials.apiKey || credentials.accessToken}`;
}
Expand Down
5 changes: 5 additions & 0 deletions open-sse/services/provider.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { PROVIDERS } from "../config/constants.js";
import { buildClineHeaders } from "../../src/shared/utils/clineAuth.js";

const OPENAI_COMPATIBLE_PREFIX = "openai-compatible-";
const OPENAI_COMPATIBLE_DEFAULTS = {
Expand Down Expand Up @@ -285,6 +286,10 @@ export function buildProviderHeaders(provider, credentials, stream = true, body
case "openrouter":
headers["Authorization"] = `Bearer ${credentials.apiKey || credentials.accessToken}`;
break;

case "cline":
Object.assign(headers, buildClineHeaders(credentials.apiKey || credentials.accessToken));
break;

case "glm":
case "kimi":
Expand Down
18 changes: 5 additions & 13 deletions src/app/api/providers/[id]/test/testUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
CLINE_CONFIG,
KILOCODE_CONFIG,
} from "@/lib/oauth/constants/oauth";
import { buildClineHeaders } from "@/shared/utils/clineAuth";

// OAuth provider test endpoints
const OAUTH_TEST_CONFIG = {
Expand Down Expand Up @@ -56,19 +57,10 @@ const OAUTH_TEST_CONFIG = {
};

async function probeClineAccessToken(accessToken) {
const res = await fetch("https://api.cline.bot/api/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
"HTTP-Referer": "https://cline.bot",
"X-Title": "Cline",
},
body: JSON.stringify({
model: "cl/anthropic/claude-sonnet-4-20250514",
messages: [{ role: "user", content: "test" }],
max_tokens: 1,
stream: false,
const res = await fetch("https://api.cline.bot/api/v1/users/me", {
method: "GET",
headers: buildClineHeaders(accessToken, {
Accept: "application/json",
}),
});

Expand Down
20 changes: 18 additions & 2 deletions src/app/api/version/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import https from "https";
import pkg from "../../../../package.json" with { type: "json" };

const NPM_PACKAGE_NAME = "9router";
const ENABLE_NPM_UPDATE_CHECK = process.env.ENABLE_NPM_UPDATE_CHECK === "true";

// Fetch latest version from npm registry
function fetchLatestVersion() {
Expand Down Expand Up @@ -37,9 +38,24 @@ function compareVersions(a, b) {
}

export async function GET() {
const latestVersion = await fetchLatestVersion();
const currentVersion = pkg.version;
if (!ENABLE_NPM_UPDATE_CHECK) {
return Response.json({
currentVersion,
latestVersion: currentVersion,
hasUpdate: false,
source: "app"
});
}

const latestVersion = await fetchLatestVersion();
const hasUpdate = latestVersion ? compareVersions(latestVersion, currentVersion) > 0 : false;

return Response.json({ currentVersion, latestVersion, hasUpdate });
return Response.json({
currentVersion,
latestVersion: latestVersion || currentVersion,
hasUpdate,
source: "npm",
installCommand: hasUpdate ? `npm install -g ${NPM_PACKAGE_NAME}@latest` : null
});
}
10 changes: 6 additions & 4 deletions src/shared/components/Sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export default function Sidebar({ onClose }) {
.catch(() => {});
}, []);

// Lazy check for new npm version on mount
// Check for update metadata on mount.
useEffect(() => {
fetch("/api/version")
.then(res => res.json())
Expand Down Expand Up @@ -97,9 +97,11 @@ export default function Sidebar({ onClose }) {
<span className="text-xs font-semibold text-green-600 dark:text-amber-500">
↑ New version available: v{updateInfo.latestVersion}
</span>
<code className="text-[10px] text-green-600/80 dark:text-amber-400/70 font-mono select-all">
npm install -g 9router@latest
</code>
{updateInfo.installCommand && (
<code className="text-[10px] text-green-600/80 dark:text-amber-400/70 font-mono select-all">
{updateInfo.installCommand}
</code>
)}
</div>
)}
</div>
Expand Down
37 changes: 37 additions & 0 deletions src/shared/utils/clineAuth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import pkg from "../../../package.json" with { type: "json" };

const APP_VERSION = pkg.version || "0.0.0";

export function getClineAccessToken(token) {
if (typeof token !== "string") return "";
const trimmed = token.trim();
if (!trimmed) return "";
return trimmed.startsWith("workos:") ? trimmed : `workos:${trimmed}`;
}

export function getClineAuthorizationHeader(token) {
const accessToken = getClineAccessToken(token);
return accessToken ? `Bearer ${accessToken}` : "";
}

export function buildClineHeaders(token, extraHeaders = {}) {
const authorization = getClineAuthorizationHeader(token);
const headers = {
"HTTP-Referer": "https://cline.bot",
"X-Title": "Cline",
"User-Agent": `9Router/${APP_VERSION}`,
"X-PLATFORM": process.platform || "unknown",
"X-PLATFORM-VERSION": process.version || "unknown",
"X-CLIENT-TYPE": "9router",
"X-CLIENT-VERSION": APP_VERSION,
"X-CORE-VERSION": APP_VERSION,
"X-IS-MULTIROOT": "false",
...extraHeaders,
};

if (authorization) {
headers.Authorization = authorization;
}

return headers;
}