mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
- Issue #199: proxy_ prefix on tool names is now automatically disabled when routing to non-Claude backends (Gemini, Compatible, etc.). Prevents tool name mismatches in OpenCode, Cursor, and other clients. - Issue #198: Added customRpm/customTpm support per provider connection. Users can configure custom rate limits via connection settings, overriding the default auto-learned limits from response headers.
This commit is contained in:
@@ -151,11 +151,18 @@ export async function handleChatCore({
|
||||
// Translate request (pass reqLogger for intermediate logging)
|
||||
let translatedBody = body;
|
||||
try {
|
||||
// Issue #199: Disable tool name prefix when routing Claude-format requests
|
||||
// to non-Claude backends (prefix causes tool name mismatches)
|
||||
const claudeProviders = ["claude", "anthropic"];
|
||||
if (targetFormat === FORMATS.CLAUDE && !claudeProviders.includes(provider?.toLowerCase?.())) {
|
||||
translatedBody = { ...translatedBody, _disableToolPrefix: true };
|
||||
}
|
||||
|
||||
translatedBody = translateRequest(
|
||||
sourceFormat,
|
||||
targetFormat,
|
||||
model,
|
||||
body,
|
||||
translatedBody,
|
||||
stream,
|
||||
credentials,
|
||||
provider,
|
||||
@@ -202,6 +209,7 @@ export async function handleChatCore({
|
||||
// Extract toolNameMap for response translation (Claude OAuth)
|
||||
const toolNameMap = translatedBody._toolNameMap;
|
||||
delete translatedBody._toolNameMap;
|
||||
delete translatedBody._disableToolPrefix;
|
||||
|
||||
// Update model in body
|
||||
translatedBody.model = model;
|
||||
|
||||
@@ -81,6 +81,7 @@ export async function initializeRateLimits() {
|
||||
const connections = await getProviderConnections();
|
||||
let explicitCount = 0;
|
||||
let autoCount = 0;
|
||||
let customCount = 0;
|
||||
|
||||
for (const connRaw of connections as unknown[]) {
|
||||
const conn = toRecord(connRaw);
|
||||
@@ -88,9 +89,33 @@ export async function initializeRateLimits() {
|
||||
const provider = typeof conn.provider === "string" ? conn.provider : "";
|
||||
const isActive = conn.isActive === true;
|
||||
const rateLimitProtection = conn.rateLimitProtection === true;
|
||||
const customRpm = toNumber(conn.customRpm, 0);
|
||||
const customTpm = toNumber(conn.customTpm, 0);
|
||||
if (!connectionId || !provider) continue;
|
||||
|
||||
if (rateLimitProtection) {
|
||||
// Custom rpm/tpm configured — enable rate limiting with user-defined values (#198)
|
||||
if (customRpm > 0 || customTpm > 0) {
|
||||
enabledConnections.add(connectionId);
|
||||
customCount++;
|
||||
|
||||
const key = `${provider}:${connectionId}`;
|
||||
const rpm = customRpm > 0 ? customRpm : DEFAULT_API_LIMITS.requestsPerMinute;
|
||||
const minTime = Math.max(0, Math.floor(60000 / rpm) - 10);
|
||||
|
||||
if (!limiters.has(key)) {
|
||||
limiters.set(
|
||||
key,
|
||||
new Bottleneck({
|
||||
maxConcurrent: DEFAULT_API_LIMITS.concurrentRequests,
|
||||
minTime,
|
||||
reservoir: rpm,
|
||||
reservoirRefreshAmount: rpm,
|
||||
reservoirRefreshInterval: 60 * 1000,
|
||||
id: key,
|
||||
})
|
||||
);
|
||||
}
|
||||
} else if (rateLimitProtection) {
|
||||
// Explicitly enabled by user
|
||||
enabledConnections.add(connectionId);
|
||||
explicitCount++;
|
||||
@@ -117,9 +142,9 @@ export async function initializeRateLimits() {
|
||||
}
|
||||
}
|
||||
|
||||
if (explicitCount > 0 || autoCount > 0) {
|
||||
if (explicitCount > 0 || autoCount > 0 || customCount > 0) {
|
||||
console.log(
|
||||
`🛡️ [RATE-LIMIT] Loaded ${explicitCount} explicit + ${autoCount} auto-enabled (API key) protection(s)`
|
||||
`🛡️ [RATE-LIMIT] Loaded ${explicitCount} explicit + ${autoCount} auto-enabled + ${customCount} custom rpm/tpm protection(s)`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@ import { adjustMaxTokens } from "../helpers/maxTokensHelper.ts";
|
||||
import { DEFAULT_THINKING_CLAUDE_SIGNATURE } from "../../config/defaultThinkingSignature.ts";
|
||||
|
||||
// Prefix for Claude OAuth tool names to avoid conflicts
|
||||
const CLAUDE_OAUTH_TOOL_PREFIX = "proxy_";
|
||||
// Can be disabled per-request via body._disableToolPrefix = true
|
||||
export const CLAUDE_OAUTH_TOOL_PREFIX = "proxy_";
|
||||
const CLAUDE_TOOL_CHOICE_REQUIRED = "an" + "y";
|
||||
|
||||
type ClaudeContentBlock = Record<string, unknown>;
|
||||
@@ -27,6 +28,9 @@ type ClaudeTool = {
|
||||
|
||||
// Convert OpenAI request to Claude format
|
||||
export function openaiToClaudeRequest(model, body, stream) {
|
||||
// Check if tool prefix should be disabled (configured per-provider or global)
|
||||
const disableToolPrefix = body?._disableToolPrefix === true;
|
||||
|
||||
// Tool name mapping for Claude OAuth (capitalizedName → originalName)
|
||||
const toolNameMap = new Map();
|
||||
const result: {
|
||||
@@ -82,7 +86,7 @@ export function openaiToClaudeRequest(model, body, stream) {
|
||||
|
||||
for (const msg of nonSystemMessages) {
|
||||
const newRole = msg.role === "user" || msg.role === "tool" ? "user" : "assistant";
|
||||
const blocks = getContentBlocksFromMessage(msg, toolNameMap);
|
||||
const blocks = getContentBlocksFromMessage(msg, toolNameMap, disableToolPrefix);
|
||||
const hasToolUse = blocks.some((b) => b.type === "tool_use");
|
||||
const hasToolResult = blocks.some((b) => b.type === "tool_result");
|
||||
|
||||
@@ -155,10 +159,13 @@ export function openaiToClaudeRequest(model, body, stream) {
|
||||
const originalName = toolData.name;
|
||||
|
||||
// Claude OAuth requires prefixed tool names to avoid conflicts
|
||||
const toolName = CLAUDE_OAUTH_TOOL_PREFIX + originalName;
|
||||
// When prefix is disabled (non-Claude backends), use original name
|
||||
const toolName = disableToolPrefix ? originalName : CLAUDE_OAUTH_TOOL_PREFIX + originalName;
|
||||
|
||||
// Store mapping for response translation (prefixed → original)
|
||||
toolNameMap.set(toolName, originalName);
|
||||
if (!disableToolPrefix) {
|
||||
toolNameMap.set(toolName, originalName);
|
||||
}
|
||||
|
||||
return {
|
||||
name: toolName,
|
||||
@@ -196,7 +203,7 @@ export function openaiToClaudeRequest(model, body, stream) {
|
||||
}
|
||||
|
||||
// Get content blocks from single message
|
||||
function getContentBlocksFromMessage(msg, toolNameMap = new Map()) {
|
||||
function getContentBlocksFromMessage(msg, toolNameMap = new Map(), disableToolPrefix = false) {
|
||||
const blocks = [];
|
||||
|
||||
if (msg.role === "tool") {
|
||||
@@ -277,8 +284,8 @@ function getContentBlocksFromMessage(msg, toolNameMap = new Map()) {
|
||||
const fnName = tc.function?.name;
|
||||
if (!fnName || !fnName.trim()) continue;
|
||||
|
||||
// Apply prefix to tool name
|
||||
const toolName = CLAUDE_OAUTH_TOOL_PREFIX + fnName;
|
||||
// Apply prefix to tool name (skip if disabled)
|
||||
const toolName = disableToolPrefix ? fnName : CLAUDE_OAUTH_TOOL_PREFIX + fnName;
|
||||
blocks.push({
|
||||
type: "tool_use",
|
||||
id: tc.id,
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { register } from "../registry.ts";
|
||||
import { FORMATS } from "../formats.ts";
|
||||
|
||||
// Prefix for Claude OAuth tool names (must match request translator)
|
||||
const CLAUDE_OAUTH_TOOL_PREFIX = "proxy_";
|
||||
import { CLAUDE_OAUTH_TOOL_PREFIX } from "../request/openai-to-claude.ts";
|
||||
|
||||
// Helper: stop thinking block if started
|
||||
function stopThinkingBlock(state, results) {
|
||||
|
||||
Reference in New Issue
Block a user