merge: bug fixes for #355 #350 #346 into main

This commit is contained in:
diegosouzapw
2026-03-14 15:30:36 -03:00
4 changed files with 23 additions and 4 deletions

View File

@@ -4,7 +4,9 @@ import { loadProviderCredentials } from "./credentialLoader.ts";
export const FETCH_TIMEOUT_MS = parseInt(process.env.FETCH_TIMEOUT_MS || "120000", 10);
// Idle timeout for SSE streams (ms). Closes stream if no data for this duration.
export const STREAM_IDLE_TIMEOUT_MS = parseInt(process.env.STREAM_IDLE_TIMEOUT_MS || "60000", 10);
// Default: 300s to support extended-thinking models (claude-opus-4-6, o3, etc.)
// that may pause for >60s during deep reasoning phases. Override with STREAM_IDLE_TIMEOUT_MS env var.
export const STREAM_IDLE_TIMEOUT_MS = parseInt(process.env.STREAM_IDLE_TIMEOUT_MS || "300000", 10);
// Provider configurations
// OAuth credentials read from env vars with hardcoded fallbacks for backward compatibility.

View File

@@ -192,6 +192,16 @@ export async function handleChatCore({
return item;
});
}
// ── #346: Strip tools with empty function.name ──
// Claude Code sometimes forwards tool definitions with empty names, causing
// OpenAI-compatible upstream providers to reject with:
// "Invalid 'input[N].name': empty string. Expected minimum length 1."
if (Array.isArray(body.tools)) {
body.tools = body.tools.filter((tool: Record<string, unknown>) => {
const fn = tool.function as Record<string, unknown> | undefined;
return fn?.name && String(fn.name).trim().length > 0;
});
}
translatedBody = translateRequest(
sourceFormat,

View File

@@ -49,6 +49,7 @@ export async function POST(request) {
const startTime = Date.now();
try {
// Send a minimal chat request to the internal SSE handler
// Use OpenAI-compatible format — universally accepted by all providers via the translator
const testBody = {
model: modelStr,
messages: [{ role: "user", content: "Hi" }],
@@ -58,11 +59,15 @@ export async function POST(request) {
const internalUrl = `${getBaseUrl(request)}/v1/chat/completions`;
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 15000); // 15s timeout
const timeout = setTimeout(() => controller.abort(), 20000); // 20s timeout (was 15s, slow providers need more)
const res = await fetch(internalUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
headers: {
"Content-Type": "application/json",
// Fix #350: bypass REQUIRE_API_KEY for internal admin combo tests
"X-Internal-Test": "combo-health-check",
},
body: JSON.stringify(testBody),
signal: controller.signal,
});

View File

@@ -133,7 +133,9 @@ export async function handleChat(request: any, clientRawRequest: any = null) {
// Optional strict API key mode for /v1 endpoints.
// Keep disabled by default to preserve local-mode compatibility.
if (process.env.REQUIRE_API_KEY === "true") {
// Exception: X-Internal-Test header bypasses auth for admin-side combo health checks (#350)
const isInternalTest = request.headers?.get?.("x-internal-test") === "combo-health-check";
if (process.env.REQUIRE_API_KEY === "true" && !isInternalTest) {
if (!apiKey) {
log.warn("AUTH", "Missing API key while REQUIRE_API_KEY=true");
return errorResponse(HTTP_STATUS.UNAUTHORIZED, "Missing API key");