mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-02 05:12:11 +03:00
fix: strip proxy_ prefix in non-streaming Claude responses & fix LongCat validation (#605, #592) (#607)
- fix(translator): pass toolNameMap to translateNonStreamingResponse so Claude OAuth proxy_ prefix is correctly stripped from tool_use block names in non-streaming responses (was only stripped in streaming path) - fix(validation): add LongCat specialty validator that probes /chat/completions directly, bypassing the /v1/models endpoint that LongCat does not expose (#592) Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
e9ae50be0c
commit
33fee5dcc5
@@ -223,7 +223,8 @@ export async function handleChatCore({
|
||||
|
||||
const endpointPath = String(clientRawRequest?.endpoint || "");
|
||||
const sourceFormat = detectFormatFromEndpoint(body, endpointPath);
|
||||
const isResponsesEndpoint = /\/responses(?=\/|$)/i.test(endpointPath) || /^responses(?=\/|$)/i.test(endpointPath);
|
||||
const isResponsesEndpoint =
|
||||
/\/responses(?=\/|$)/i.test(endpointPath) || /^responses(?=\/|$)/i.test(endpointPath);
|
||||
const nativeCodexPassthrough = shouldUseNativeCodexPassthrough({
|
||||
provider,
|
||||
sourceFormat,
|
||||
@@ -1067,8 +1068,14 @@ export async function handleChatCore({
|
||||
}
|
||||
|
||||
// Translate response to client's expected format (usually OpenAI)
|
||||
// Pass toolNameMap so Claude OAuth proxy_ prefix is stripped in tool_use blocks (#605)
|
||||
let translatedResponse = needsTranslation(targetFormat, sourceFormat)
|
||||
? translateNonStreamingResponse(responseBody, targetFormat, sourceFormat)
|
||||
? translateNonStreamingResponse(
|
||||
responseBody,
|
||||
targetFormat,
|
||||
sourceFormat,
|
||||
toolNameMap as Map<string, string> | null
|
||||
)
|
||||
: responseBody;
|
||||
|
||||
// T26: Strip markdown code blocks if provider format is Claude
|
||||
|
||||
@@ -68,11 +68,14 @@ function findBestMessageText(output: unknown[]): {
|
||||
/**
|
||||
* Translate non-streaming response to OpenAI format
|
||||
* Handles different provider response formats (Gemini, Claude, etc.)
|
||||
*
|
||||
* @param toolNameMap - Optional Map<prefixedName, originalName> for Claude OAuth tool name stripping
|
||||
*/
|
||||
export function translateNonStreamingResponse(
|
||||
responseBody: unknown,
|
||||
targetFormat: string,
|
||||
sourceFormat: string
|
||||
sourceFormat: string,
|
||||
toolNameMap?: Map<string, string> | null
|
||||
): unknown {
|
||||
// If already in source format (usually OpenAI), return as-is
|
||||
if (targetFormat === sourceFormat || targetFormat === FORMATS.OPENAI) {
|
||||
@@ -334,11 +337,15 @@ export function translateNonStreamingResponse(
|
||||
} else if (blockObj.type === "thinking") {
|
||||
thinkingContent += toString(blockObj.thinking);
|
||||
} else if (blockObj.type === "tool_use") {
|
||||
// Strip Claude OAuth tool name prefix (proxy_) using the map from request translation.
|
||||
// Fallback to raw name if block wasn't prefixed (disableToolPrefix path).
|
||||
const rawName = toString(blockObj.name);
|
||||
const strippedName = toolNameMap?.get(rawName) ?? rawName;
|
||||
toolCalls.push({
|
||||
id: toString(blockObj.id, `call_${Date.now()}_${toolCalls.length}`),
|
||||
type: "function",
|
||||
function: {
|
||||
name: toString(blockObj.name),
|
||||
name: strippedName,
|
||||
arguments: JSON.stringify(blockObj.input || {}),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -259,11 +259,19 @@ export function openaiToClaudeRequest(model, body, stream) {
|
||||
toolNameMap.set(toolName, originalName);
|
||||
}
|
||||
|
||||
// Normalize input_schema: Anthropic requires `properties` when type is "object" (#595).
|
||||
// MCP tools (e.g. pencil, computer_use) may omit properties on object-type schemas.
|
||||
const rawSchema: Record<string, unknown> =
|
||||
toolData.parameters || toolData.input_schema || { type: "object", properties: {}, required: [] };
|
||||
const normalizedSchema =
|
||||
rawSchema.type === "object" && !rawSchema.properties
|
||||
? { ...rawSchema, properties: {} }
|
||||
: rawSchema;
|
||||
|
||||
return {
|
||||
name: toolName,
|
||||
description: toolData.description || "",
|
||||
input_schema: toolData.parameters ||
|
||||
toolData.input_schema || { type: "object", properties: {}, required: [] },
|
||||
input_schema: normalizedSchema,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -652,6 +652,27 @@ export async function validateProviderApiKey({ provider, apiKey, providerSpecifi
|
||||
elevenlabs: validateElevenLabsProvider,
|
||||
inworld: validateInworldProvider,
|
||||
"bailian-coding-plan": validateBailianCodingPlanProvider,
|
||||
// LongCat AI — does not expose /v1/models; validate via chat completions directly (#592)
|
||||
longcat: async ({ apiKey }: any) => {
|
||||
try {
|
||||
const res = await fetch("https://longcat.chat/api/v1/chat/completions", {
|
||||
method: "POST",
|
||||
headers: buildBearerHeaders(apiKey),
|
||||
body: JSON.stringify({
|
||||
model: "longcat",
|
||||
messages: [{ role: "user", content: "test" }],
|
||||
max_tokens: 1,
|
||||
}),
|
||||
});
|
||||
if (res.status === 401 || res.status === 403) {
|
||||
return { valid: false, error: "Invalid API key" };
|
||||
}
|
||||
// Any non-auth response (200, 400, 422) means auth passed
|
||||
return { valid: true, error: null };
|
||||
} catch (error: any) {
|
||||
return { valid: false, error: error.message || "Connection failed" };
|
||||
}
|
||||
},
|
||||
// Search providers — use factored validator
|
||||
...Object.fromEntries(
|
||||
Object.entries(SEARCH_VALIDATOR_CONFIGS).map(([id, configFn]) => [
|
||||
|
||||
Reference in New Issue
Block a user