mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
fix(authz): Restore REQUIRE_API_KEY support in clientApi policy
This commit is contained in:
@@ -995,12 +995,16 @@ export class CodexExecutor extends BaseExecutor {
|
||||
}
|
||||
} else {
|
||||
// Translated: use CODEX_DEFAULT_INSTRUCTIONS as fallback when no system
|
||||
// prompt was provided by the client (safety net for bare requests).
|
||||
// prompt was provided by the client, BUT only if tools are requested.
|
||||
// Injecting tool instructions on bare requests causes Harmony leaks (#1686).
|
||||
const hasTools = Array.isArray(body.tools) && body.tools.length > 0;
|
||||
if (
|
||||
!body.instructions ||
|
||||
(typeof body.instructions === "string" && body.instructions.trim() === "")
|
||||
) {
|
||||
body.instructions = CODEX_DEFAULT_INSTRUCTIONS;
|
||||
body.instructions = hasTools
|
||||
? CODEX_DEFAULT_INSTRUCTIONS
|
||||
: "You are a helpful AI assistant.";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -631,11 +631,21 @@ export function openaiResponsesToOpenAIResponse(chunk, state) {
|
||||
|
||||
state.toolCallIndex++;
|
||||
|
||||
let argsToEmit = item.arguments;
|
||||
if (argsToEmit != null && typeof argsToEmit === "object" && !Array.isArray(argsToEmit)) {
|
||||
// Fix #1674: Strip empty string placeholders emitted by GPT-5.5 for optional fields
|
||||
const cleaned = { ...argsToEmit };
|
||||
for (const [k, v] of Object.entries(cleaned)) {
|
||||
if (v === "") delete cleaned[k];
|
||||
}
|
||||
argsToEmit = cleaned;
|
||||
}
|
||||
|
||||
const argsStr =
|
||||
item.arguments != null
|
||||
? typeof item.arguments === "string"
|
||||
? item.arguments
|
||||
: JSON.stringify(item.arguments)
|
||||
argsToEmit != null
|
||||
? typeof argsToEmit === "string"
|
||||
? argsToEmit
|
||||
: JSON.stringify(argsToEmit)
|
||||
: buffered;
|
||||
|
||||
return {
|
||||
@@ -671,8 +681,16 @@ export function openaiResponsesToOpenAIResponse(chunk, state) {
|
||||
|
||||
// Only emit if arguments exist in the done event AND they weren't already streamed via deltas
|
||||
if (item.arguments != null && !buffered) {
|
||||
const argsStr =
|
||||
typeof item.arguments === "string" ? item.arguments : JSON.stringify(item.arguments);
|
||||
let argsToEmit = item.arguments;
|
||||
if (argsToEmit != null && typeof argsToEmit === "object" && !Array.isArray(argsToEmit)) {
|
||||
const cleaned = { ...argsToEmit };
|
||||
for (const [k, v] of Object.entries(cleaned)) {
|
||||
if (v === "") delete cleaned[k];
|
||||
}
|
||||
argsToEmit = cleaned;
|
||||
}
|
||||
|
||||
const argsStr = typeof argsToEmit === "string" ? argsToEmit : JSON.stringify(argsToEmit);
|
||||
if (argsStr) {
|
||||
return {
|
||||
id: state.chatId,
|
||||
|
||||
@@ -35,6 +35,11 @@ export const clientApiPolicy: RoutePolicy = {
|
||||
) {
|
||||
return allow({ kind: "dashboard_session", id: "dashboard" });
|
||||
}
|
||||
|
||||
if (process.env.REQUIRE_API_KEY !== "true") {
|
||||
return allow({ kind: "anonymous", id: "local" });
|
||||
}
|
||||
|
||||
return reject(401, "AUTH_002", "Authentication required");
|
||||
}
|
||||
|
||||
|
||||
@@ -78,6 +78,7 @@ test.describe("Bailian Coding Plan Provider", () => {
|
||||
.isVisible({ timeout: 15000 })
|
||||
.catch(() => false)
|
||||
) {
|
||||
await expect(addKeyButton.first()).toBeEnabled({ timeout: 5000 });
|
||||
await addKeyButton.first().click();
|
||||
}
|
||||
|
||||
@@ -193,6 +194,7 @@ test.describe("Bailian Coding Plan Provider", () => {
|
||||
.isVisible({ timeout: 15000 })
|
||||
.catch(() => false)
|
||||
) {
|
||||
await expect(addKeyButton.first()).toBeEnabled({ timeout: 5000 });
|
||||
await addKeyButton.first().click();
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ test("contract: /api/v1 OPTIONS exposes CORS and allowed methods", async () => {
|
||||
const response = await OPTIONS();
|
||||
|
||||
assert.equal(response.status, 200);
|
||||
assert.ok(response.headers.has("Access-Control-Allow-Origin"));
|
||||
assert.ok(response.headers.has("Access-Control-Allow-Methods"));
|
||||
});
|
||||
|
||||
test("contract: /api/v1/embeddings OPTIONS exposes POST/GET/OPTIONS", async () => {
|
||||
|
||||
@@ -1448,37 +1448,42 @@ test("chatCore redirects background utility tasks to a cheaper mapped model", as
|
||||
});
|
||||
|
||||
test("chatCore retries Qwen quota 429 responses before succeeding", async () => {
|
||||
globalThis.setTimeout = (callback, _ms, ...args) => {
|
||||
callback(...args);
|
||||
return 0;
|
||||
};
|
||||
const originalSetTimeout = globalThis.setTimeout;
|
||||
try {
|
||||
(globalThis as any).setTimeout = (callback: any, _ms: any, ...args: any[]) => {
|
||||
callback(...args);
|
||||
return 0 as any;
|
||||
};
|
||||
|
||||
const { calls, result } = await invokeChatCore({
|
||||
provider: "qwen",
|
||||
model: "qwen3-coder",
|
||||
body: {
|
||||
const { calls, result } = await invokeChatCore({
|
||||
provider: "qwen",
|
||||
model: "qwen3-coder",
|
||||
stream: false,
|
||||
messages: [{ role: "user", content: "retry the quota hit" }],
|
||||
},
|
||||
responseFactory(_captured, seenCalls) {
|
||||
if (seenCalls.length === 1) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: { message: "You exceeded your current quota for Qwen." } }),
|
||||
{
|
||||
status: 429,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
}
|
||||
return buildOpenAIResponse(false, "qwen recovered");
|
||||
},
|
||||
});
|
||||
body: {
|
||||
model: "qwen3-coder",
|
||||
stream: false,
|
||||
messages: [{ role: "user", content: "retry the quota hit" }],
|
||||
},
|
||||
responseFactory(_captured, seenCalls) {
|
||||
if (seenCalls.length === 1) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: { message: "You exceeded your current quota for Qwen." } }),
|
||||
{
|
||||
status: 429,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
}
|
||||
return buildOpenAIResponse(false, "qwen recovered");
|
||||
},
|
||||
});
|
||||
|
||||
const payload = (await result.response.json()) as any;
|
||||
assert.equal(result.success, true);
|
||||
assert.equal(calls.length, 2);
|
||||
assert.equal(payload.choices[0].message.content, "qwen recovered");
|
||||
const payload = (await result.response.json()) as any;
|
||||
assert.equal(result.success, true);
|
||||
assert.equal(calls.length, 2);
|
||||
assert.equal(payload.choices[0].message.content, "qwen recovered");
|
||||
} finally {
|
||||
globalThis.setTimeout = originalSetTimeout;
|
||||
}
|
||||
});
|
||||
|
||||
test("chatCore injects fallback user for Qwen OAuth requests without user", async () => {
|
||||
|
||||
@@ -22,8 +22,11 @@ async function createAuthCookie() {
|
||||
return `auth_token=${token}`;
|
||||
}
|
||||
|
||||
const originalHomedir = os.homedir;
|
||||
|
||||
test.beforeEach(async () => {
|
||||
process.env.DATA_DIR = DUMMY_HOME;
|
||||
os.homedir = () => DUMMY_HOME;
|
||||
await fs.mkdir(DUMMY_HOME, { recursive: true }).catch(() => {});
|
||||
// Initialize DB
|
||||
getDbInstance();
|
||||
@@ -31,6 +34,7 @@ test.beforeEach(async () => {
|
||||
|
||||
test.afterEach(async () => {
|
||||
await fs.rm(DUMMY_HOME, { recursive: true, force: true }).catch(() => {});
|
||||
os.homedir = originalHomedir;
|
||||
if (originalJwtSecret === undefined) delete process.env.JWT_SECRET;
|
||||
else process.env.JWT_SECRET = originalJwtSecret;
|
||||
if (process.env.DATA_DIR?.includes("omniroute-qwen-key-test")) {
|
||||
|
||||
Reference in New Issue
Block a user