mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-12 10:12:11 +03:00
OmniRoute is an intelligent API gateway that unifies 20+ AI providers behind a single OpenAI-compatible endpoint. Features include intelligent routing with 6 strategies, multi-format translation (OpenAI/Claude/Gemini/Responses API), circuit breakers, semantic caching, combo fallback chains, real-time health monitoring, and a full dashboard with provider management, analytics, and CLI tool integration. Key highlights: - 20+ providers (Claude Code, Codex, Gemini CLI, GitHub Copilot, iFlow, Qwen, Kiro, etc.) - 6 routing strategies (Fill First, Round Robin, P2C, Random, Least Used, Cost Optimized) - Export/Import database backup with full archive support - Translator Playground with 4 modes (Playground, Chat Tester, Test Bench, Live Monitor) - 100% TypeScript across src/ and open-sse/ - Docker support with multi-stage builds - Comprehensive documentation and 9 dashboard screenshots
228 lines
6.7 KiB
TypeScript
228 lines
6.7 KiB
TypeScript
import { OAuthService } from "./oauth";
|
|
import { GITHUB_CONFIG } from "../constants/oauth";
|
|
import { spinner as createSpinner } from "../utils/ui";
|
|
|
|
/**
|
|
* GitHub Copilot OAuth Service
|
|
* Uses Device Code Flow for authentication
|
|
*/
|
|
export class GitHubService extends OAuthService {
|
|
constructor() {
|
|
super(GITHUB_CONFIG);
|
|
}
|
|
|
|
/**
|
|
* Get device code for GitHub authentication
|
|
*/
|
|
async getDeviceCode() {
|
|
const response = await fetch(`${GITHUB_CONFIG.deviceCodeUrl}`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
Accept: "application/json",
|
|
},
|
|
body: new URLSearchParams({
|
|
client_id: GITHUB_CONFIG.clientId,
|
|
scope: GITHUB_CONFIG.scopes,
|
|
}),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.text();
|
|
throw new Error(`Failed to get device code: ${error}`);
|
|
}
|
|
|
|
return await response.json();
|
|
}
|
|
|
|
/**
|
|
* Poll for access token using device code
|
|
*/
|
|
async pollAccessToken(deviceCode, verificationUri, userCode, interval = 5000) {
|
|
const spinner = createSpinner("Waiting for GitHub authentication...").start();
|
|
|
|
// Show user code and verification URL
|
|
console.log(`\nPlease visit: ${verificationUri}`);
|
|
console.log(`Enter code: ${userCode}\n`);
|
|
|
|
// Open browser automatically
|
|
try {
|
|
const open = (await import("open")).default;
|
|
await open(verificationUri);
|
|
} catch (error) {
|
|
console.log("Could not open browser automatically. Please visit the URL above manually.");
|
|
}
|
|
|
|
// Poll for access token
|
|
while (true) {
|
|
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
|
|
const response = await fetch(`${GITHUB_CONFIG.tokenUrl}`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
Accept: "application/json",
|
|
},
|
|
body: new URLSearchParams({
|
|
client_id: GITHUB_CONFIG.clientId,
|
|
device_code: deviceCode,
|
|
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
}),
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.access_token) {
|
|
spinner.succeed("GitHub authentication successful!");
|
|
return {
|
|
access_token: data.access_token,
|
|
token_type: data.token_type,
|
|
scope: data.scope,
|
|
};
|
|
} else if (data.error === "authorization_pending") {
|
|
// Continue polling
|
|
continue;
|
|
} else if (data.error === "slow_down") {
|
|
// Increase polling interval
|
|
interval += 5000;
|
|
continue;
|
|
} else if (data.error === "expired_token") {
|
|
spinner.fail("Device code expired. Please try again.");
|
|
throw new Error("Device code expired");
|
|
} else if (data.error === "access_denied") {
|
|
spinner.fail("Access denied by user.");
|
|
throw new Error("Access denied");
|
|
} else {
|
|
spinner.fail("Failed to get access token.");
|
|
throw new Error(data.error_description || data.error);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get Copilot token using GitHub access token
|
|
*/
|
|
async getCopilotToken(accessToken) {
|
|
const response = await fetch(`${GITHUB_CONFIG.copilotTokenUrl}`, {
|
|
headers: {
|
|
Authorization: `Bearer ${accessToken}`, // GitHub API typically uses Bearer
|
|
Accept: "application/json",
|
|
"X-GitHub-Api-Version": GITHUB_CONFIG.apiVersion,
|
|
"User-Agent": GITHUB_CONFIG.userAgent,
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.text();
|
|
throw new Error(`Failed to get Copilot token: ${error}`);
|
|
}
|
|
|
|
return await response.json();
|
|
}
|
|
|
|
/**
|
|
* Get user info using GitHub access token
|
|
*/
|
|
async getUserInfo(accessToken) {
|
|
const response = await fetch(`${GITHUB_CONFIG.userInfoUrl}`, {
|
|
headers: {
|
|
Authorization: `Bearer ${accessToken}`, // GitHub API typically uses Bearer
|
|
Accept: "application/json",
|
|
"X-GitHub-Api-Version": GITHUB_CONFIG.apiVersion,
|
|
"User-Agent": GITHUB_CONFIG.userAgent,
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.text();
|
|
throw new Error(`Failed to get user info: ${error}`);
|
|
}
|
|
|
|
return await response.json();
|
|
}
|
|
|
|
/**
|
|
* Complete GitHub Copilot authentication flow
|
|
*/
|
|
async authenticate(): Promise<any> {
|
|
try {
|
|
// Get device code
|
|
const deviceResponse = await this.getDeviceCode();
|
|
|
|
// Poll for access token
|
|
const tokenResponse = await this.pollAccessToken(
|
|
deviceResponse.device_code,
|
|
deviceResponse.verification_uri,
|
|
deviceResponse.user_code
|
|
);
|
|
|
|
// Get Copilot token
|
|
const copilotToken = await this.getCopilotToken(tokenResponse.access_token);
|
|
|
|
// Get user info
|
|
const userInfo = await this.getUserInfo(tokenResponse.access_token);
|
|
|
|
console.log(`\n✅ Successfully authenticated as ${userInfo.login}`);
|
|
|
|
return {
|
|
accessToken: tokenResponse.access_token,
|
|
copilotToken: copilotToken.token,
|
|
refreshToken: null, // GitHub device flow doesn't return refresh token
|
|
expiresIn: copilotToken.expires_at,
|
|
userInfo: {
|
|
id: userInfo.id,
|
|
login: userInfo.login,
|
|
name: userInfo.name,
|
|
email: userInfo.email,
|
|
},
|
|
copilotTokenInfo: copilotToken,
|
|
};
|
|
} catch (error) {
|
|
throw new Error(`GitHub authentication failed: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Connect to server with GitHub credentials
|
|
*/
|
|
async connect() {
|
|
try {
|
|
// Authenticate with GitHub
|
|
const authResult = await this.authenticate();
|
|
|
|
// Send credentials to server
|
|
const { server, token, userId } = await import("../config/index").then((m) =>
|
|
m.getServerCredentials()
|
|
);
|
|
const spinner = (await import("../utils/ui")).spinner("Connecting to server...").start();
|
|
|
|
const response = await fetch(`${server}/api/cli/providers/github`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${token}`,
|
|
"X-User-Id": userId,
|
|
},
|
|
body: JSON.stringify({
|
|
accessToken: authResult.accessToken,
|
|
copilotToken: authResult.copilotToken,
|
|
userInfo: authResult.userInfo,
|
|
copilotTokenInfo: authResult.copilotTokenInfo,
|
|
}),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
throw new Error(errorData.error || "Failed to connect to server");
|
|
}
|
|
|
|
spinner.succeed("GitHub Copilot connected successfully!");
|
|
console.log(`\nConnected as: ${authResult.userInfo.login}`);
|
|
} catch (error) {
|
|
const { error: showError } = await import("../utils/ui");
|
|
showError(`GitHub connection failed: ${error.message}`);
|
|
throw error;
|
|
}
|
|
}
|
|
}
|