mirror of
https://github.com/Nezumi-2711/9router.git
synced 2026-09-22 20:00:47 +00:00
Fix : Claude OAuth
This commit is contained in:
@@ -1,5 +1,24 @@
|
|||||||
import { platform, arch } from "os";
|
import { platform, arch } from "os";
|
||||||
|
|
||||||
|
function mapStainlessOs() {
|
||||||
|
switch (platform()) {
|
||||||
|
case "darwin": return "MacOS";
|
||||||
|
case "win32": return "Windows";
|
||||||
|
case "linux": return "Linux";
|
||||||
|
case "freebsd": return "FreeBSD";
|
||||||
|
default: return `Other::${platform()}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStainlessArch() {
|
||||||
|
switch (arch()) {
|
||||||
|
case "x64": return "x64";
|
||||||
|
case "arm64": return "arm64";
|
||||||
|
case "ia32": return "x86";
|
||||||
|
default: return `other::${arch()}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// === GitHub Copilot Version Constants ===
|
// === GitHub Copilot Version Constants ===
|
||||||
export const GITHUB_COPILOT = {
|
export const GITHUB_COPILOT = {
|
||||||
VSCODE_VERSION: "1.110.0",
|
VSCODE_VERSION: "1.110.0",
|
||||||
@@ -96,23 +115,23 @@ export const PROVIDERS = {
|
|||||||
format: "claude",
|
format: "claude",
|
||||||
headers: {
|
headers: {
|
||||||
"Anthropic-Version": "2023-06-01",
|
"Anthropic-Version": "2023-06-01",
|
||||||
"Anthropic-Beta": "claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14,context-management-2025-06-27",
|
"Anthropic-Beta": "claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14,context-management-2025-06-27,prompt-caching-scope-2026-01-05",
|
||||||
"Anthropic-Dangerous-Direct-Browser-Access": "true",
|
"Anthropic-Dangerous-Direct-Browser-Access": "true",
|
||||||
"User-Agent": "claude-cli/1.0.83 (external, cli)",
|
"User-Agent": "claude-cli/2.1.63 (external, cli)",
|
||||||
"X-App": "cli",
|
"X-App": "cli",
|
||||||
"X-Stainless-Helper-Method": "stream",
|
"X-Stainless-Helper-Method": "stream",
|
||||||
"X-Stainless-Retry-Count": "0",
|
"X-Stainless-Retry-Count": "0",
|
||||||
"X-Stainless-Runtime-Version": "v24.3.0",
|
"X-Stainless-Runtime-Version": "v24.3.0",
|
||||||
"X-Stainless-Package-Version": "0.55.1",
|
"X-Stainless-Package-Version": "0.74.0",
|
||||||
"X-Stainless-Runtime": "node",
|
"X-Stainless-Runtime": "node",
|
||||||
"X-Stainless-Lang": "js",
|
"X-Stainless-Lang": "js",
|
||||||
"X-Stainless-Arch": "arm64",
|
"X-Stainless-Arch": mapStainlessArch(),
|
||||||
"X-Stainless-Os": "MacOS",
|
"X-Stainless-Os": mapStainlessOs(),
|
||||||
"X-Stainless-Timeout": "60"
|
"X-Stainless-Timeout": "600"
|
||||||
},
|
},
|
||||||
// Claude OAuth configuration
|
// Claude OAuth configuration
|
||||||
clientId: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
|
clientId: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
|
||||||
tokenUrl: "https://console.anthropic.com/v1/oauth/token"
|
tokenUrl: "https://api.anthropic.com/v1/oauth/token"
|
||||||
},
|
},
|
||||||
gemini: {
|
gemini: {
|
||||||
baseUrl: "https://generativelanguage.googleapis.com/v1beta/models",
|
baseUrl: "https://generativelanguage.googleapis.com/v1beta/models",
|
||||||
@@ -376,7 +395,7 @@ export const PROVIDERS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Claude system prompt
|
// Claude system prompt
|
||||||
export const CLAUDE_SYSTEM_PROMPT = "You are Claude Code, Anthropic's official CLI for Claude.";
|
export const CLAUDE_SYSTEM_PROMPT = "You are a Claude agent, built on Anthropic's Claude Agent SDK.";
|
||||||
|
|
||||||
// Antigravity default system prompt (required for API to work)
|
// Antigravity default system prompt (required for API to work)
|
||||||
export const ANTIGRAVITY_DEFAULT_SYSTEM = "You are Antigravity, a powerful agentic AI coding assistant designed by the Google Deepmind team working on Advanced Agentic Coding.You are pair programming with a USER to solve their coding task. The task may require creating a new codebase, modifying or debugging an existing codebase, or simply answering a question.**Absolute paths only****Proactiveness**";
|
export const ANTIGRAVITY_DEFAULT_SYSTEM = "You are Antigravity, a powerful agentic AI coding assistant designed by the Google Deepmind team working on Advanced Agentic Coding.You are pair programming with a USER to solve their coding task. The task may require creating a new codebase, modifying or debugging an existing codebase, or simply answering a question.**Absolute paths only****Proactiveness**";
|
||||||
@@ -392,8 +411,8 @@ export const OAUTH_ENDPOINTS = {
|
|||||||
auth: "https://auth.openai.com/oauth/authorize"
|
auth: "https://auth.openai.com/oauth/authorize"
|
||||||
},
|
},
|
||||||
anthropic: {
|
anthropic: {
|
||||||
token: "https://console.anthropic.com/v1/oauth/token",
|
token: "https://api.anthropic.com/v1/oauth/token",
|
||||||
auth: "https://console.anthropic.com/v1/oauth/authorize"
|
auth: "https://api.anthropic.com/v1/oauth/authorize"
|
||||||
},
|
},
|
||||||
qwen: {
|
qwen: {
|
||||||
token: "https://chat.qwen.ai/api/v1/oauth2/token", // From CLIProxyAPI
|
token: "https://chat.qwen.ai/api/v1/oauth2/token", // From CLIProxyAPI
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Claude helper functions for translator
|
// Claude helper functions for translator
|
||||||
import { DEFAULT_THINKING_CLAUDE_SIGNATURE } from "../../config/defaultThinkingSignature.js";
|
import { DEFAULT_THINKING_CLAUDE_SIGNATURE } from "../../config/defaultThinkingSignature.js";
|
||||||
import { adjustMaxTokens } from "./maxTokensHelper.js";
|
import { adjustMaxTokens } from "./maxTokensHelper.js";
|
||||||
|
import { applyCloaking } from "../../utils/claudeCloaking.js";
|
||||||
|
|
||||||
// Check if message has valid non-empty content
|
// Check if message has valid non-empty content
|
||||||
export function hasValidContent(msg) {
|
export function hasValidContent(msg) {
|
||||||
@@ -79,7 +80,8 @@ export function fixToolUseOrdering(messages) {
|
|||||||
// - Filter empty messages
|
// - Filter empty messages
|
||||||
// - Add thinking block for Anthropic endpoint (provider === "claude")
|
// - Add thinking block for Anthropic endpoint (provider === "claude")
|
||||||
// - Fix tool_use/tool_result ordering
|
// - Fix tool_use/tool_result ordering
|
||||||
export function prepareClaudeRequest(body, provider = null) {
|
// - Apply cloaking (billing header + fake user ID) for OAuth tokens
|
||||||
|
export function prepareClaudeRequest(body, provider = null, apiKey = null) {
|
||||||
// 1. System: remove all cache_control, add only to last block with ttl 1h
|
// 1. System: remove all cache_control, add only to last block with ttl 1h
|
||||||
if (body.system && Array.isArray(body.system)) {
|
if (body.system && Array.isArray(body.system)) {
|
||||||
body.system = body.system.map((block, i) => {
|
body.system = body.system.map((block, i) => {
|
||||||
@@ -186,6 +188,11 @@ export function prepareClaudeRequest(body, provider = null) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply cloaking for OAuth tokens (billing header + fake user ID)
|
||||||
|
if (provider === "claude" && apiKey) {
|
||||||
|
body = applyCloaking(body, apiKey);
|
||||||
|
}
|
||||||
|
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,7 +90,8 @@ export function translateRequest(sourceFormat, targetFormat, model, body, stream
|
|||||||
|
|
||||||
// Final step: prepare request for Claude format endpoints
|
// Final step: prepare request for Claude format endpoints
|
||||||
if (targetFormat === FORMATS.CLAUDE) {
|
if (targetFormat === FORMATS.CLAUDE) {
|
||||||
result = prepareClaudeRequest(result, provider);
|
const apiKey = credentials?.accessToken || credentials?.apiKey || null;
|
||||||
|
result = prepareClaudeRequest(result, provider, apiKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ import { FORMATS } from "../formats.js";
|
|||||||
import { CLAUDE_SYSTEM_PROMPT } from "../../config/constants.js";
|
import { CLAUDE_SYSTEM_PROMPT } from "../../config/constants.js";
|
||||||
import { adjustMaxTokens } from "../helpers/maxTokensHelper.js";
|
import { adjustMaxTokens } from "../helpers/maxTokensHelper.js";
|
||||||
|
|
||||||
// Prefix for Claude OAuth tool names to avoid conflicts
|
// Empty prefix matches real Claude Code behavior (no tool name prefix).
|
||||||
const CLAUDE_OAUTH_TOOL_PREFIX = "proxy_";
|
// Previously "proxy_" was used but this is a detectable fingerprint difference.
|
||||||
|
const CLAUDE_OAUTH_TOOL_PREFIX = "";
|
||||||
|
|
||||||
// Convert OpenAI request to Claude format
|
// Convert OpenAI request to Claude format
|
||||||
export function openaiToClaudeRequest(model, body, stream) {
|
export function openaiToClaudeRequest(model, body, stream) {
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { createHash, randomBytes } from "crypto";
|
||||||
|
|
||||||
|
const CLAUDE_VERSION = "2.1.63";
|
||||||
|
|
||||||
|
// Generate billing header matching real Claude Code format:
|
||||||
|
// x-anthropic-billing-header: cc_version=<ver>.<build>; cc_entrypoint=cli; cch=<hash>;
|
||||||
|
function generateBillingHeader(payload) {
|
||||||
|
const content = JSON.stringify(payload);
|
||||||
|
const cch = createHash("sha256").update(content).digest("hex").slice(0, 5);
|
||||||
|
const buildHash = randomBytes(2).toString("hex").slice(0, 3);
|
||||||
|
return `x-anthropic-billing-header: cc_version=${CLAUDE_VERSION}.${buildHash}; cc_entrypoint=cli; cch=${cch};`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a random UUID v4
|
||||||
|
function generateFakeUserID() {
|
||||||
|
const bytes = randomBytes(16);
|
||||||
|
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
||||||
|
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
||||||
|
const hex = bytes.toString("hex");
|
||||||
|
return `${hex.slice(0,8)}-${hex.slice(8,12)}-${hex.slice(12,16)}-${hex.slice(16,20)}-${hex.slice(20)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply Claude cloaking to request body:
|
||||||
|
* 1. Inject billing header as first system block
|
||||||
|
* 2. Inject fake user ID into metadata
|
||||||
|
*
|
||||||
|
* Only applies when using OAuth token (sk-ant-oat).
|
||||||
|
* @param {object} body - Claude API request body
|
||||||
|
* @param {string} apiKey - API key or OAuth token
|
||||||
|
* @returns {object} Modified body
|
||||||
|
*/
|
||||||
|
export function applyCloaking(body, apiKey) {
|
||||||
|
if (!apiKey || !apiKey.includes("sk-ant-oat")) return body;
|
||||||
|
|
||||||
|
const result = { ...body };
|
||||||
|
|
||||||
|
// Inject billing header as system[0], preserve existing system blocks
|
||||||
|
const billingText = generateBillingHeader(body);
|
||||||
|
const billingBlock = { type: "text", text: billingText };
|
||||||
|
|
||||||
|
if (Array.isArray(result.system)) {
|
||||||
|
// Skip if already injected
|
||||||
|
if (!result.system[0]?.text?.startsWith("x-anthropic-billing-header:")) {
|
||||||
|
result.system = [billingBlock, ...result.system];
|
||||||
|
}
|
||||||
|
} else if (typeof result.system === "string") {
|
||||||
|
result.system = [billingBlock, { type: "text", text: result.system }];
|
||||||
|
} else {
|
||||||
|
result.system = [billingBlock];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inject fake user ID into metadata
|
||||||
|
const existingUserId = result.metadata?.user_id;
|
||||||
|
if (!existingUserId) {
|
||||||
|
result.metadata = { ...result.metadata, user_id: generateFakeUserID() };
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@ function getOAuthPlatformEnum() {
|
|||||||
export const CLAUDE_CONFIG = {
|
export const CLAUDE_CONFIG = {
|
||||||
clientId: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
|
clientId: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
|
||||||
authorizeUrl: "https://claude.ai/oauth/authorize",
|
authorizeUrl: "https://claude.ai/oauth/authorize",
|
||||||
tokenUrl: "https://console.anthropic.com/v1/oauth/token",
|
tokenUrl: "https://api.anthropic.com/v1/oauth/token",
|
||||||
scopes: ["org:create_api_key", "user:profile", "user:inference"],
|
scopes: ["org:create_api_key", "user:profile", "user:inference"],
|
||||||
codeChallengeMethod: "S256",
|
codeChallengeMethod: "S256",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ async function passthrough(req, res, bodyBuffer) {
|
|||||||
async function intercept(req, res, bodyBuffer, mappedModel) {
|
async function intercept(req, res, bodyBuffer, mappedModel) {
|
||||||
try {
|
try {
|
||||||
const body = JSON.parse(bodyBuffer.toString());
|
const body = JSON.parse(bodyBuffer.toString());
|
||||||
|
console.log("🚀 ~ intercept ~ body:", body)
|
||||||
body.model = mappedModel;
|
body.model = mappedModel;
|
||||||
|
|
||||||
const response = await fetch(ROUTER_URL, {
|
const response = await fetch(ROUTER_URL, {
|
||||||
|
|||||||
Reference in New Issue
Block a user