mirror of
https://github.com/Nezumi-2711/9router.git
synced 2026-09-22 20:00:47 +00:00
Add Providers
This commit is contained in:
@@ -192,6 +192,29 @@ export const CURSOR_CONFIG = {
|
||||
},
|
||||
};
|
||||
|
||||
// Kimi Coding OAuth Configuration (Device Code Flow)
|
||||
export const KIMI_CODING_CONFIG = {
|
||||
clientId: process.env.KIMI_CODING_OAUTH_CLIENT_ID || "17e5f671-d194-4dfb-9706-5516cb48c098",
|
||||
deviceCodeUrl: "https://auth.kimi.com/api/oauth/device_authorization",
|
||||
tokenUrl: "https://auth.kimi.com/api/oauth/token",
|
||||
};
|
||||
|
||||
// KiloCode OAuth Configuration (Custom Device Auth Flow)
|
||||
export const KILOCODE_CONFIG = {
|
||||
apiBaseUrl: "https://api.kilo.ai",
|
||||
initiateUrl: "https://api.kilo.ai/api/device-auth/codes",
|
||||
pollUrlBase: "https://api.kilo.ai/api/device-auth/codes",
|
||||
};
|
||||
|
||||
// Cline OAuth Configuration (Local Callback Flow via app.cline.bot)
|
||||
export const CLINE_CONFIG = {
|
||||
appBaseUrl: "https://app.cline.bot",
|
||||
apiBaseUrl: "https://api.cline.bot",
|
||||
authorizeUrl: "https://api.cline.bot/api/v1/auth/authorize",
|
||||
tokenExchangeUrl: "https://api.cline.bot/api/v1/auth/token",
|
||||
refreshUrl: "https://api.cline.bot/api/v1/auth/refresh",
|
||||
};
|
||||
|
||||
// OAuth timeout (5 minutes)
|
||||
export const OAUTH_TIMEOUT = 300000;
|
||||
|
||||
@@ -207,4 +230,7 @@ export const PROVIDERS = {
|
||||
GITHUB: "github",
|
||||
KIRO: "kiro",
|
||||
CURSOR: "cursor",
|
||||
KIMI_CODING: "kimi-coding",
|
||||
KILOCODE: "kilocode",
|
||||
CLINE: "cline",
|
||||
};
|
||||
|
||||
@@ -14,6 +14,9 @@ import {
|
||||
GITHUB_CONFIG,
|
||||
KIRO_CONFIG,
|
||||
CURSOR_CONFIG,
|
||||
KIMI_CODING_CONFIG,
|
||||
KILOCODE_CONFIG,
|
||||
CLINE_CONFIG,
|
||||
getOAuthClientMetadata,
|
||||
} from "./constants/oauth";
|
||||
|
||||
@@ -675,6 +678,161 @@ const PROVIDERS = {
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
"kimi-coding": {
|
||||
config: KIMI_CODING_CONFIG,
|
||||
flowType: "device_code",
|
||||
requestDeviceCode: async (config) => {
|
||||
const response = await fetch(config.deviceCodeUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
|
||||
body: new URLSearchParams({ client_id: config.clientId }),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const error = await response.text();
|
||||
throw new Error(`Device code request failed: ${error}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
return {
|
||||
device_code: data.device_code,
|
||||
user_code: data.user_code,
|
||||
verification_uri: data.verification_uri || "https://www.kimi.com/code/authorize_device",
|
||||
verification_uri_complete:
|
||||
data.verification_uri_complete ||
|
||||
`https://www.kimi.com/code/authorize_device?user_code=${data.user_code}`,
|
||||
expires_in: data.expires_in,
|
||||
interval: data.interval || 5,
|
||||
};
|
||||
},
|
||||
pollToken: async (config, deviceCode) => {
|
||||
const response = await fetch(config.tokenUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
|
||||
body: new URLSearchParams({
|
||||
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
||||
client_id: config.clientId,
|
||||
device_code: deviceCode,
|
||||
}),
|
||||
});
|
||||
let data;
|
||||
try {
|
||||
data = await response.json();
|
||||
} catch (e) {
|
||||
const text = await response.text();
|
||||
data = { error: "invalid_response", error_description: text };
|
||||
}
|
||||
return { ok: response.ok, data };
|
||||
},
|
||||
mapTokens: (tokens) => ({
|
||||
accessToken: tokens.access_token,
|
||||
refreshToken: tokens.refresh_token,
|
||||
expiresIn: tokens.expires_in,
|
||||
}),
|
||||
},
|
||||
|
||||
kilocode: {
|
||||
config: KILOCODE_CONFIG,
|
||||
flowType: "device_code",
|
||||
requestDeviceCode: async (config) => {
|
||||
const response = await fetch(config.initiateUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
if (!response.ok) {
|
||||
if (response.status === 429) {
|
||||
throw new Error("Too many pending authorization requests. Please try again later.");
|
||||
}
|
||||
const error = await response.text();
|
||||
throw new Error(`Device auth initiation failed: ${error}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
return {
|
||||
device_code: data.code,
|
||||
user_code: data.code,
|
||||
verification_uri: data.verificationUrl,
|
||||
verification_uri_complete: data.verificationUrl,
|
||||
expires_in: data.expiresIn || 300,
|
||||
interval: 3,
|
||||
};
|
||||
},
|
||||
pollToken: async (config, deviceCode) => {
|
||||
const response = await fetch(`${config.pollUrlBase}/${deviceCode}`);
|
||||
if (response.status === 202) return { ok: false, data: { error: "authorization_pending" } };
|
||||
if (response.status === 403) return { ok: false, data: { error: "access_denied", error_description: "Authorization denied by user" } };
|
||||
if (response.status === 410) return { ok: false, data: { error: "expired_token", error_description: "Authorization code expired" } };
|
||||
if (!response.ok) return { ok: false, data: { error: "poll_failed", error_description: `Poll failed: ${response.status}` } };
|
||||
const data = await response.json();
|
||||
if (data.status === "approved" && data.token) {
|
||||
return { ok: true, data: { access_token: data.token, _userEmail: data.userEmail } };
|
||||
}
|
||||
return { ok: false, data: { error: "authorization_pending" } };
|
||||
},
|
||||
mapTokens: (tokens) => ({
|
||||
accessToken: tokens.access_token,
|
||||
refreshToken: null,
|
||||
expiresIn: null,
|
||||
email: tokens._userEmail,
|
||||
}),
|
||||
},
|
||||
|
||||
cline: {
|
||||
config: CLINE_CONFIG,
|
||||
flowType: "authorization_code",
|
||||
buildAuthUrl: (config, redirectUri) => {
|
||||
const params = new URLSearchParams({
|
||||
client_type: "extension",
|
||||
callback_url: redirectUri,
|
||||
redirect_uri: redirectUri,
|
||||
});
|
||||
return `${config.authorizeUrl}?${params.toString()}`;
|
||||
},
|
||||
exchangeToken: async (config, code, redirectUri) => {
|
||||
try {
|
||||
// Cline encodes token data as base64 in the code param
|
||||
let base64 = code;
|
||||
const padding = 4 - (base64.length % 4);
|
||||
if (padding !== 4) base64 += "=".repeat(padding);
|
||||
const decoded = Buffer.from(base64, "base64").toString("utf-8");
|
||||
const lastBrace = decoded.lastIndexOf("}");
|
||||
if (lastBrace === -1) throw new Error("No JSON found in decoded code");
|
||||
const tokenData = JSON.parse(decoded.substring(0, lastBrace + 1));
|
||||
return {
|
||||
access_token: tokenData.accessToken,
|
||||
refresh_token: tokenData.refreshToken,
|
||||
email: tokenData.email,
|
||||
firstName: tokenData.firstName,
|
||||
lastName: tokenData.lastName,
|
||||
expires_at: tokenData.expiresAt,
|
||||
};
|
||||
} catch (e) {
|
||||
const response = await fetch(config.tokenExchangeUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
||||
body: JSON.stringify({ grant_type: "authorization_code", code, client_type: "extension", redirect_uri: redirectUri }),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const error = await response.text();
|
||||
throw new Error(`Cline token exchange failed: ${error}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
return {
|
||||
access_token: data.data?.accessToken || data.accessToken,
|
||||
refresh_token: data.data?.refreshToken || data.refreshToken,
|
||||
email: data.data?.userInfo?.email || "",
|
||||
expires_at: data.data?.expiresAt || data.expiresAt,
|
||||
};
|
||||
}
|
||||
},
|
||||
mapTokens: (tokens) => ({
|
||||
accessToken: tokens.access_token,
|
||||
refreshToken: tokens.refresh_token,
|
||||
expiresIn: tokens.expires_at
|
||||
? Math.floor((new Date(tokens.expires_at).getTime() - Date.now()) / 1000)
|
||||
: 3600,
|
||||
email: tokens.email,
|
||||
providerSpecificData: { firstName: tokens.firstName, lastName: tokens.lastName },
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user