From 8f419fb8cc0b1c18305b6b46cbde71c2333d2872 Mon Sep 17 00:00:00 2001 From: pandorax Date: Sat, 21 Jun 2025 23:04:31 +0800 Subject: [PATCH] Update project files and add new features --- README.md | 44 ++ src/lib/token.ts | 8 +- src/main.ts | 11 +- src/public/usage.html | 633 +++++++++++++++++++++++ src/routes/token/route.ts | 19 + src/routes/usage/route.ts | 17 + src/server.ts | 10 + src/services/github/get-copilot-usage.ts | 37 ++ start.bat | 20 + tsconfig.json | 12 +- 10 files changed, 803 insertions(+), 8 deletions(-) create mode 100644 src/public/usage.html create mode 100644 src/routes/token/route.ts create mode 100644 src/routes/usage/route.ts create mode 100644 src/services/github/get-copilot-usage.ts create mode 100644 start.bat diff --git a/README.md b/README.md index 8571d06f9..49966abd8 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,12 @@ A reverse-engineered proxy for the GitHub Copilot API that exposes it as an OpenAI and Anthropic compatible service. This allows you to use GitHub Copilot with any tool that supports the OpenAI Chat Completions API or the Anthropic Messages API, including to power [Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview). +### New Features + +- **Copilot Usage Viewer**: Integrated web interface to view your GitHub Copilot usage statistics and quota information +- **Token Display**: View the current Copilot token being used by the API +- **Real-time Monitoring**: Track your usage and remaining quotas in real-time + ## Demo https://github.com/user-attachments/assets/7654b383-669d-4eb9-b23c-06d7aefee8c5 @@ -113,6 +119,16 @@ These endpoints are designed to be compatible with the Anthropic Messages API. | `POST /v1/messages` | `POST` | Creates a model response for a given conversation. | | `POST /v1/messages/count_tokens` | `POST` | Calculates the number of tokens for a given set of messages. | +### Usage Monitoring Endpoints + +New endpoints for monitoring your Copilot usage and quotas. + +| Endpoint | Method | Description | +| --------------------------- | ------ | --------------------------------------------------------- | +| `GET /usage` | `GET` | Get detailed Copilot usage statistics and quota information. | +| `GET /token` | `GET` | Get the current Copilot token being used by the API. | +| `GET /public/usage.html` | `GET` | Web interface for viewing usage statistics (accessible via browser). | + ## Example Usage Using with npx: @@ -149,6 +165,34 @@ npx copilot-api@latest auth npx copilot-api@latest auth --verbose ``` +## Using the Usage Viewer + +After starting the server, you can access the Copilot Usage Viewer through your web browser: + +1. Start the server: `npx copilot-api@latest start` +2. Open your browser and navigate to: `http://localhost:4141/public/usage.html` +3. The page will automatically load your usage data when opened +4. Use the controls to: + - **Fetch Usage**: Manually refresh usage data + - **Show Current Token**: View the current Copilot token + - **Enable Auto Refresh**: Automatically refresh data every 30 seconds + +### Auto Refresh Feature + +The usage viewer includes an automatic refresh feature that: +- Updates usage data every 30 seconds when enabled +- Shows a countdown timer to the next refresh +- Displays the last update time +- Can be toggled on/off at any time +- Continues running in the background without interrupting your view + +The usage viewer provides: +- Account information (plan type, access type, assigned date) +- Quota information (remaining usage, total quota, overage count) +- Real-time token display with automatic refresh +- Support for Chinese and English interfaces +- Auto-refresh functionality for continuous monitoring + ## Using with Claude Code This proxy can be used to power [Claude Code](https://docs.anthropic.com/en/claude-code), an experimental conversational AI assistant for developers from Anthropic. diff --git a/src/lib/token.ts b/src/lib/token.ts index f2cec3e0d..bd98d24e5 100644 --- a/src/lib/token.ts +++ b/src/lib/token.ts @@ -19,13 +19,19 @@ export const setupCopilotToken = async () => { const { token, refresh_in } = await getCopilotToken() state.copilotToken = token - const refreshInterval = (refresh_in - 60) * 1000 + // Display the Copilot token to the screen + consola.success("GitHub Copilot Token fetched successfully!") + consola.info(`Token: ${token}`) + consola.info(`Token validity: ${Math.floor(refresh_in / 60)} minutes`) + const refreshInterval = (refresh_in - 60) * 1000 setInterval(async () => { consola.start("Refreshing Copilot token") try { const { token } = await getCopilotToken() state.copilotToken = token + consola.success("Copilot token refreshed") + consola.info(`New Token: ${token}`) } catch (error) { consola.error("Failed to refresh Copilot token:", error) throw error diff --git a/src/main.ts b/src/main.ts index 56e02261d..e804a83e0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -49,10 +49,13 @@ export async function runServer(options: RunServerOptions): Promise { consola.info("Using provided GitHub token") } else { await setupGitHubToken() - } - - await setupCopilotToken() - await cacheModels() + } await setupCopilotToken() + await cacheModels() // Display token information prominently + consola.info("=".repeat(50)) + consola.success("🚀 GitHub Copilot API has been successfully started!") + consola.info(`🔑 Current Copilot Token: ${state.copilotToken}`) + consola.info(`🌐 Usage Viewer: http://localhost:${options.port}/public/usage.html`) + consola.info("=".repeat(50)) consola.info( `Available models: \n${state.models?.data.map((model) => `- ${model.id}`).join("\n")}`, diff --git a/src/public/usage.html b/src/public/usage.html new file mode 100644 index 000000000..a6d5a3045 --- /dev/null +++ b/src/public/usage.html @@ -0,0 +1,633 @@ + + + + + + + GitHub Copilot Usage Viewer + + + +
+
+

GitHub Copilot Usage Viewer

+
+ + + +
+ Auto Refresh: Off + +
+
+ +
+
+ + + diff --git a/src/routes/token/route.ts b/src/routes/token/route.ts new file mode 100644 index 000000000..7916fe712 --- /dev/null +++ b/src/routes/token/route.ts @@ -0,0 +1,19 @@ +import { Hono } from "hono" +import { state } from "~/lib/state" + +export const tokenRoute = new Hono() + +tokenRoute.get("/", async (c) => { + try { + return c.json({ + token: state.copilotToken || "No token available", + hasToken: !!state.copilotToken + }) + } catch (error) { + console.error("Error fetching token:", error) + return c.json( + { error: "Failed to fetch token", token: null }, + 500 + ) + } +}) diff --git a/src/routes/usage/route.ts b/src/routes/usage/route.ts new file mode 100644 index 000000000..b9dd9fab9 --- /dev/null +++ b/src/routes/usage/route.ts @@ -0,0 +1,17 @@ +import { Hono } from "hono" +import { getCopilotUsage } from "~/services/github/get-copilot-usage" + +export const usageRoute = new Hono() + +usageRoute.get("/", async (c) => { + try { + const usage = await getCopilotUsage() + return c.json(usage) + } catch (error) { + console.error("Error fetching Copilot usage:", error) + return c.json( + { error: "Failed to fetch Copilot usage" }, + 500 + ) + } +}) diff --git a/src/server.ts b/src/server.ts index f72d61b96..efd674bfe 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,11 +1,14 @@ import { Hono } from "hono" import { cors } from "hono/cors" import { logger } from "hono/logger" +import { serveStatic } from "hono/bun" import { completionRoutes } from "./routes/chat-completions/route" import { embeddingRoutes } from "./routes/embeddings/route" import { messageRoutes } from "./routes/messages/route" import { modelRoutes } from "./routes/models/route" +import { usageRoute } from "./routes/usage/route" +import { tokenRoute } from "./routes/token/route" export const server = new Hono() @@ -14,14 +17,21 @@ server.use(cors()) server.get("/", (c) => c.text("Server running")) +// Serve static files from public directory +server.use("/public/*", serveStatic({ root: "./src" })) + server.route("/chat/completions", completionRoutes) server.route("/models", modelRoutes) server.route("/embeddings", embeddingRoutes) +server.route("/usage", usageRoute) +server.route("/token", tokenRoute) // Compatibility with tools that expect v1/ prefix server.route("/v1/chat/completions", completionRoutes) server.route("/v1/models", modelRoutes) server.route("/v1/embeddings", embeddingRoutes) +server.route("/v1/usage", usageRoute) +server.route("/v1/token", tokenRoute) // Anthropic compatible endpoints server.route("/v1/messages", messageRoutes) diff --git a/src/services/github/get-copilot-usage.ts b/src/services/github/get-copilot-usage.ts new file mode 100644 index 000000000..f8ab68051 --- /dev/null +++ b/src/services/github/get-copilot-usage.ts @@ -0,0 +1,37 @@ +import { GITHUB_API_BASE_URL, githubHeaders } from "~/lib/api-config" +import { HTTPError } from "~/lib/error" +import { state } from "~/lib/state" + +export interface CopilotUsageResponse { + copilot_plan: string + access_type_sku: string + assigned_date: string + chat_enabled: boolean + quota_reset_date: string + can_signup_for_limited: boolean + organization_list: string[] + quota_snapshots: { + [key: string]: { + unlimited: boolean + remaining: number + entitlement: number + overage_count: number + percent_remaining: number + } + } +} + +export const getCopilotUsage = async (): Promise => { + const response = await fetch( + `${GITHUB_API_BASE_URL}/copilot_internal/user`, + { + headers: githubHeaders(state), + }, + ) + + if (!response.ok) { + throw new HTTPError("Failed to get Copilot usage", response) + } + + return (await response.json()) as CopilotUsageResponse +} diff --git a/start.bat b/start.bat new file mode 100644 index 000000000..2c8dff6fe --- /dev/null +++ b/start.bat @@ -0,0 +1,20 @@ +@echo off +echo ================================================ +echo GitHub Copilot API Server with Usage Viewer +echo ================================================ +echo. + +if not exist node_modules ( + echo Installing dependencies... + npm install + echo. +) + +echo Starting server... +echo The usage viewer page will open automatically after the server starts +echo. + +start "" "http://localhost:4141/public/usage.html" +npm run dev + +pause diff --git a/tsconfig.json b/tsconfig.json index bfff5e6b5..11cd3e11b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,11 +15,17 @@ "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true, - + "noUncheckedSideEffectImports": true, "baseUrl": ".", "paths": { "~/*": ["./src/*"] } - } + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "dist" + ] }