diff --git a/open-sse/config/providerModels.js b/open-sse/config/providerModels.js
index 85b1099a..32e8131a 100644
--- a/open-sse/config/providerModels.js
+++ b/open-sse/config/providerModels.js
@@ -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)
@@ -372,4 +372,3 @@ export function getModelsByProviderId(providerId) {
const alias = PROVIDER_ID_TO_ALIAS[providerId] || providerId;
return PROVIDER_MODELS[alias] || [];
}
-
diff --git a/open-sse/executors/default.js b/open-sse/executors/default.js
index a47da862..eba3c61f 100644
--- a/open-sse/executors/default.js
+++ b/open-sse/executors/default.js
@@ -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) {
@@ -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}`;
}
diff --git a/open-sse/services/provider.js b/open-sse/services/provider.js
index 42931ec1..93101367 100644
--- a/open-sse/services/provider.js
+++ b/open-sse/services/provider.js
@@ -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 = {
@@ -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":
diff --git a/src/app/api/providers/[id]/test/testUtils.js b/src/app/api/providers/[id]/test/testUtils.js
index 45c673ee..f299ae07 100644
--- a/src/app/api/providers/[id]/test/testUtils.js
+++ b/src/app/api/providers/[id]/test/testUtils.js
@@ -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 = {
@@ -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",
}),
});
diff --git a/src/app/api/version/route.js b/src/app/api/version/route.js
index 7e7f4a80..3376beb4 100644
--- a/src/app/api/version/route.js
+++ b/src/app/api/version/route.js
@@ -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() {
@@ -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
+ });
}
diff --git a/src/shared/components/Sidebar.js b/src/shared/components/Sidebar.js
index 8a35fe61..7c10e57a 100644
--- a/src/shared/components/Sidebar.js
+++ b/src/shared/components/Sidebar.js
@@ -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())
@@ -97,9 +97,11 @@ export default function Sidebar({ onClose }) {
↑ New version available: v{updateInfo.latestVersion}
-
- npm install -g 9router@latest
-
+ {updateInfo.installCommand && (
+
+ {updateInfo.installCommand}
+
+ )}
)}
diff --git a/src/shared/utils/clineAuth.js b/src/shared/utils/clineAuth.js
new file mode 100644
index 00000000..a2da21dd
--- /dev/null
+++ b/src/shared/utils/clineAuth.js
@@ -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;
+}