mirror of
https://github.com/decolua/9router.git
synced 2026-05-08 12:01:28 +00:00
Fix : Claude OAuth
This commit is contained in:
@@ -1,5 +1,24 @@
|
||||
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 ===
|
||||
export const GITHUB_COPILOT = {
|
||||
VSCODE_VERSION: "1.110.0",
|
||||
@@ -96,23 +115,23 @@ export const PROVIDERS = {
|
||||
format: "claude",
|
||||
headers: {
|
||||
"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",
|
||||
"User-Agent": "claude-cli/1.0.83 (external, cli)",
|
||||
"User-Agent": "claude-cli/2.1.63 (external, cli)",
|
||||
"X-App": "cli",
|
||||
"X-Stainless-Helper-Method": "stream",
|
||||
"X-Stainless-Retry-Count": "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-Lang": "js",
|
||||
"X-Stainless-Arch": "arm64",
|
||||
"X-Stainless-Os": "MacOS",
|
||||
"X-Stainless-Timeout": "60"
|
||||
"X-Stainless-Arch": mapStainlessArch(),
|
||||
"X-Stainless-Os": mapStainlessOs(),
|
||||
"X-Stainless-Timeout": "600"
|
||||
},
|
||||
// Claude OAuth configuration
|
||||
clientId: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
|
||||
tokenUrl: "https://console.anthropic.com/v1/oauth/token"
|
||||
tokenUrl: "https://api.anthropic.com/v1/oauth/token"
|
||||
},
|
||||
gemini: {
|
||||
baseUrl: "https://generativelanguage.googleapis.com/v1beta/models",
|
||||
@@ -376,7 +395,7 @@ export const PROVIDERS = {
|
||||
};
|
||||
|
||||
// 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)
|
||||
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"
|
||||
},
|
||||
anthropic: {
|
||||
token: "https://console.anthropic.com/v1/oauth/token",
|
||||
auth: "https://console.anthropic.com/v1/oauth/authorize"
|
||||
token: "https://api.anthropic.com/v1/oauth/token",
|
||||
auth: "https://api.anthropic.com/v1/oauth/authorize"
|
||||
},
|
||||
qwen: {
|
||||
token: "https://chat.qwen.ai/api/v1/oauth2/token", // From CLIProxyAPI
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Claude helper functions for translator
|
||||
import { DEFAULT_THINKING_CLAUDE_SIGNATURE } from "../../config/defaultThinkingSignature.js";
|
||||
import { adjustMaxTokens } from "./maxTokensHelper.js";
|
||||
import { applyCloaking } from "../../utils/claudeCloaking.js";
|
||||
|
||||
// Check if message has valid non-empty content
|
||||
export function hasValidContent(msg) {
|
||||
@@ -79,7 +80,8 @@ export function fixToolUseOrdering(messages) {
|
||||
// - Filter empty messages
|
||||
// - Add thinking block for Anthropic endpoint (provider === "claude")
|
||||
// - 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
|
||||
if (body.system && Array.isArray(body.system)) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,8 @@ export function translateRequest(sourceFormat, targetFormat, model, body, stream
|
||||
|
||||
// Final step: prepare request for Claude format endpoints
|
||||
if (targetFormat === FORMATS.CLAUDE) {
|
||||
result = prepareClaudeRequest(result, provider);
|
||||
const apiKey = credentials?.accessToken || credentials?.apiKey || null;
|
||||
result = prepareClaudeRequest(result, provider, apiKey);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -3,8 +3,9 @@ import { FORMATS } from "../formats.js";
|
||||
import { CLAUDE_SYSTEM_PROMPT } from "../../config/constants.js";
|
||||
import { adjustMaxTokens } from "../helpers/maxTokensHelper.js";
|
||||
|
||||
// Prefix for Claude OAuth tool names to avoid conflicts
|
||||
const CLAUDE_OAUTH_TOOL_PREFIX = "proxy_";
|
||||
// Empty prefix matches real Claude Code behavior (no tool name prefix).
|
||||
// Previously "proxy_" was used but this is a detectable fingerprint difference.
|
||||
const CLAUDE_OAUTH_TOOL_PREFIX = "";
|
||||
|
||||
// Convert OpenAI request to Claude format
|
||||
export function openaiToClaudeRequest(model, body, stream) {
|
||||
|
||||
60
open-sse/utils/claudeCloaking.js
Normal file
60
open-sse/utils/claudeCloaking.js
Normal file
@@ -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 = {
|
||||
clientId: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
|
||||
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"],
|
||||
codeChallengeMethod: "S256",
|
||||
};
|
||||
|
||||
@@ -140,6 +140,7 @@ async function passthrough(req, res, bodyBuffer) {
|
||||
async function intercept(req, res, bodyBuffer, mappedModel) {
|
||||
try {
|
||||
const body = JSON.parse(bodyBuffer.toString());
|
||||
console.log("🚀 ~ intercept ~ body:", body)
|
||||
body.model = mappedModel;
|
||||
|
||||
const response = await fetch(ROUTER_URL, {
|
||||
|
||||
Reference in New Issue
Block a user