fix(ai): probe model CORS with a dummy POST (#411)

This commit is contained in:
tianma-if committed 2026-09-17 16:06:43 +08:00
1 parent dcfd11468d
commit 8528f5a912
4 files changed
+120 -23

No files matched your search

@@ -54,7 +54,11 @@ describe("direct tag suggestions", () => {
modelId: "meta/llama",
}), { headers: { "content-type": "application/json" } });
}
if (String(url).includes("integrate.api.nvidia.com")) throw new TypeError("Failed to fetch");
if (String(url).includes("integrate.api.nvidia.com")) {
expect(init?.method).toBe("POST");
expect(new Headers(init?.headers).get("Authorization")).toBe("Bearer edgeever-cors-probe");
throw new TypeError("Failed to fetch");
}
return new Response(JSON.stringify({ suggestions: [{ name: "proxied", existing: false }] }), {
headers: { "content-type": "application/json" },
});
+104 -21
View File
@@ -1,5 +1,6 @@
import { describe, expect, test } from "bun:test";
import { createEdgeEverClient } from "./index.ts";
import { probeAiProviderCors } from "./ai-direct-stream.ts";
describe("direct AI generation client", () => {
test("prepares on the instance then streams from the model provider", async () => {
@@ -56,7 +57,11 @@ describe("direct AI generation client", () => {
baseUrl: "https://notes.example",
tryDirectAiGeneration: true,
fetch: async (url, init) => {
urls.push({ url: String(url), method: init?.method ?? "GET" });
urls.push({
url: String(url),
method: init?.method ?? "GET",
auth: new Headers(init?.headers).get("Authorization"),
});
if (String(url).endsWith("/direct-target")) {
return new Response(JSON.stringify({
provider: "openai-compatible",
@@ -65,6 +70,8 @@ describe("direct AI generation client", () => {
}), { headers: { "content-type": "application/json" } });
}
if (String(url).includes("integrate.api.nvidia.com")) {
expect(init?.method).toBe("POST");
expect(new Headers(init?.headers).get("Authorization")).toBe("Bearer edgeever-cors-probe");
throw new TypeError("Failed to fetch");
}
return new Response("data: {\"type\":\"text-delta\",\"text\":\"proxied\"}\n\n", {
@@ -78,22 +85,27 @@ describe("direct AI generation client", () => {
{ onEvent: (event) => events.push(event) },
);
expect(urls).toEqual([
{ url: "https://notes.example/api/v1/ai/direct-target", method: "GET" },
{ url: "https://integrate.api.nvidia.com/v1/chat/completions", method: "OPTIONS" },
{ url: "https://notes.example/api/v1/ai/generate", method: "POST" },
{ url: "https://notes.example/api/v1/ai/direct-target", method: "GET", auth: null },
{ url: "https://integrate.api.nvidia.com/v1/chat/completions", method: "POST", auth: "Bearer edgeever-cors-probe" },
{ url: "https://notes.example/api/v1/ai/generate", method: "POST", auth: null },
]);
expect(urls.some((call) => call.url.endsWith("/prepare"))).toBe(false);
expect(events).toEqual([{ type: "text-delta", text: "proxied" }]);
});
test("direct-connects from the browser after a successful CORS probe", async () => {
const urls = [];
const calls = [];
const client = createEdgeEverClient({
baseUrl: "https://notes.example",
token: "user-token",
tryDirectAiGeneration: true,
fetch: async (url, init) => {
urls.push(String(url));
const headers = new Headers(init?.headers);
calls.push({
url: String(url),
method: init?.method ?? "GET",
auth: headers.get("Authorization"),
});
if (String(url).endsWith("/direct-target")) {
return new Response(JSON.stringify({
provider: "openai-compatible",
@@ -101,8 +113,17 @@ describe("direct AI generation client", () => {
modelId: "openai/gpt-4o-mini",
}), { headers: { "content-type": "application/json" } });
}
if (String(url).includes("openrouter.ai") && (init?.method ?? "GET") === "OPTIONS") {
return new Response(null, { status: 204 });
if (String(url).includes("openrouter.ai")) {
if (headers.get("Authorization") === "Bearer edgeever-cors-probe") {
return new Response("{}", { status: 401 });
}
expect(headers.get("Authorization")).toBe("Bearer or-key");
return new Response([
`data: {"choices":[{"delta":{"content":"<edgeever-result-x>\\nHello"}}]}`,
`data: {"choices":[{"delta":{"content":"</edgeever-result-x>"},"finish_reason":"stop"}]}`,
"data: [DONE]",
"",
].join("\n\n"), { headers: { "content-type": "text/event-stream" } });
}
if (String(url).endsWith("/prepare")) {
return new Response(JSON.stringify({
@@ -116,14 +137,6 @@ describe("direct AI generation client", () => {
resultBoundary: { start: "<edgeever-result-x>", end: "</edgeever-result-x>" },
}), { headers: { "content-type": "application/json" } });
}
if (String(url).includes("openrouter.ai")) {
return new Response([
`data: {"choices":[{"delta":{"content":"<edgeever-result-x>\\nHello"}}]}`,
`data: {"choices":[{"delta":{"content":"</edgeever-result-x>"},"finish_reason":"stop"}]}`,
"data: [DONE]",
"",
].join("\n\n"), { headers: { "content-type": "text/event-stream" } });
}
throw new Error(`unexpected ${url}`);
},
});
@@ -132,11 +145,11 @@ describe("direct AI generation client", () => {
{ action: "summarize", title: "Note", contentMarkdown: "Body" },
{ onEvent: (event) => events.push(event) },
);
expect(urls).toEqual([
"https://notes.example/api/v1/ai/direct-target",
"https://openrouter.ai/api/v1/chat/completions",
"https://notes.example/api/v1/ai/generate/prepare",
"https://openrouter.ai/api/v1/chat/completions",
expect(calls).toEqual([
{ url: "https://notes.example/api/v1/ai/direct-target", method: "GET", auth: "Bearer user-token" },
{ url: "https://openrouter.ai/api/v1/chat/completions", method: "POST", auth: "Bearer edgeever-cors-probe" },
{ url: "https://notes.example/api/v1/ai/generate/prepare", method: "POST", auth: "Bearer user-token" },
{ url: "https://openrouter.ai/api/v1/chat/completions", method: "POST", auth: "Bearer or-key" },
]);
expect(events.filter((event) => event.type === "text-delta").map((event) => event.text).join("")).toBe("Hello");
});
@@ -166,3 +179,73 @@ describe("direct AI generation client", () => {
expect(events).toEqual([{ type: "text-delta", text: "proxied" }]);
});
});
describe("AI provider CORS probe", () => {
const openaiTarget = {
provider: "openai-compatible",
baseUrl: "https://api.example/v1",
modelId: "demo",
};
test("POSTs a dummy key and treats a readable 401 as CORS success", async () => {
let cancelled = false;
const body = new ReadableStream({
start() {},
cancel() {
cancelled = true;
},
});
const allowed = await probeAiProviderCors(openaiTarget, async (url, init) => {
expect(String(url)).toBe("https://api.example/v1/chat/completions");
expect(init?.method).toBe("POST");
expect(init?.mode).toBe("cors");
const headers = new Headers(init?.headers);
expect(headers.get("Authorization")).toBe("Bearer edgeever-cors-probe");
expect(headers.get("Content-Type")).toBe("application/json");
expect(headers.get("Accept")).toBe("text/event-stream");
expect(JSON.parse(String(init?.body)).stream).toBe(true);
return new Response(body, { status: 401 });
});
expect(allowed).toBe(true);
expect(cancelled).toBe(true);
});
test("sends Anthropic and Google dummy credentials on the real provider paths", async () => {
const anthropicAllowed = await probeAiProviderCors({
provider: "anthropic",
baseUrl: "https://api.anthropic.com/v1",
modelId: "claude",
}, async (url, init) => {
expect(String(url)).toBe("https://api.anthropic.com/v1/messages");
expect(init?.method).toBe("POST");
expect(new Headers(init?.headers).get("x-api-key")).toBe("edgeever-cors-probe");
return new Response("{}", { status: 401 });
});
const googleAllowed = await probeAiProviderCors({
provider: "google",
baseUrl: "https://generativelanguage.googleapis.com/v1beta",
modelId: "models/gemini-2.5-flash",
}, async (url, init) => {
expect(String(url)).toBe("https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:streamGenerateContent?alt=sse");
expect(init?.method).toBe("POST");
expect(new Headers(init?.headers).get("x-goog-api-key")).toBe("edgeever-cors-probe");
return new Response("{}", { status: 403 });
});
expect(anthropicAllowed).toBe(true);
expect(googleAllowed).toBe(true);
});
test("returns false when the browser blocks the probe", async () => {
const allowed = await probeAiProviderCors(openaiTarget, async () => {
throw new TypeError("Failed to fetch");
});
expect(allowed).toBe(false);
});
test("rethrows when the caller aborts the probe", async () => {
const signal = AbortSignal.abort();
await expect(probeAiProviderCors(openaiTarget, async () => {
throw new DOMException("The operation was aborted.", "AbortError");
}, signal)).rejects.toMatchObject({ name: "AbortError" });
});
});
+9 -1
View File
@@ -126,7 +126,15 @@ export const probeAiProviderCors = async (
resultBoundary: { start: "<edgeever-result-probe>", end: "</edgeever-result-probe>" },
});
try {
await fetchImpl(request.url, { method: "OPTIONS", mode: "cors", signal });
// POST with the dummy key so the browser preflight matches the real direct request.
const response = await fetchImpl(request.url, {
method: "POST",
mode: "cors",
headers: request.headers,
body: request.body,
signal,
});
await response.body?.cancel().catch(() => {});
return true;
} catch (error) {
if (signal?.aborted) throw error;
@@ -95,6 +95,8 @@ describe("direct companion client", () => {
}), { headers: { "content-type": "application/json" } });
}
if (String(url).includes("integrate.api.nvidia.com")) {
expect(init?.method).toBe("POST");
expect(new Headers(init?.headers).get("Authorization")).toBe("Bearer edgeever-cors-probe");
throw new TypeError("Failed to fetch");
}
return new Response("data: {\"type\":\"text-delta\",\"text\":\"proxied\"}\n\n", {