fix: register missing web-cookie validators (claude-web, gemini-web, copilot-web, t3-web) (#2793)

Integrated into release/v3.8.6
This commit is contained in:
Paijo
2026-05-28 03:03:55 +07:00
committed by GitHub
parent 619463c573
commit aac17c01c3
2 changed files with 532 additions and 0 deletions

View File

@@ -3351,6 +3351,231 @@ async function validateAdaptaWebProvider({ apiKey, providerSpecificData = {} }:
}
}
async function validateClaudeWebProvider({ apiKey, providerSpecificData = {} }: any) {
try {
const cookieHeader = normalizeSessionCookieHeader(String(apiKey || ""), "sessionKey");
if (!cookieHeader) {
return { valid: false, error: "Paste your sessionKey cookie from claude.ai" };
}
const { tlsFetchClaude, TlsClientUnavailableError } = await import(
"@omniroute/open-sse/services/claudeTlsClient.ts"
);
let response: { status: number; text: string | null };
try {
response = await tlsFetchClaude("https://claude.ai/api/organizations", {
method: "GET",
headers: applyCustomUserAgent(
{
Accept: "application/json",
"Accept-Language": "en-US,en;q=0.9",
"Cache-Control": "no-cache",
Cookie: cookieHeader,
Origin: "https://claude.ai",
Pragma: "no-cache",
Referer: "https://claude.ai/new",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
"User-Agent":
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"anthropic-client-platform": "web_claude_ai",
},
providerSpecificData
),
timeoutMs: 30_000,
});
} catch (err: any) {
if (err instanceof TlsClientUnavailableError) {
return {
valid: false,
error: `${err.message} (claude-web requires this — without it, Cloudflare blocks every request)`,
};
}
throw err;
}
if (response.status === 200) {
return { valid: true, error: null };
}
if (response.status === 401 || response.status === 403) {
return {
valid: false,
error: "Invalid or expired session cookie — re-paste sessionKey from claude.ai DevTools → Cookies",
};
}
if (response.status === 429) {
return { valid: true, error: null };
}
if (response.status >= 500) {
return { valid: false, error: `Claude.ai unavailable (${response.status})` };
}
return { valid: false, error: `Claude.ai validation failed (${response.status})` };
} catch (error: any) {
return toValidationErrorResult(error);
}
}
// ── Gemini Web cookie validator ──
async function validateGeminiWebProvider({ apiKey, providerSpecificData = {} }: any) {
try {
const raw = String(apiKey || "").trim();
if (!raw) {
return { valid: false, error: "Paste your __Secure-1PSID cookie from gemini.google.com" };
}
// Accept full cookie blob or bare value
let cookieHeader = raw;
if (!raw.includes("=")) {
cookieHeader = `__Secure-1PSID=${raw}`;
}
const response = await validationRead("https://gemini.google.com/app", {
headers: applyCustomUserAgent(
{
Accept: "text/html,application/xhtml+xml",
Cookie: cookieHeader,
Origin: "https://gemini.google.com",
Referer: "https://gemini.google.com/",
},
providerSpecificData
),
});
if (response.status === 401 || response.status === 403) {
return {
valid: false,
error: "Invalid or expired __Secure-1PSID cookie — re-paste from gemini.google.com DevTools → Cookies",
};
}
// 200/302 = valid, anything < 500 that isn't auth failure is acceptable
if (response.status < 500) {
return { valid: true, error: null };
}
return { valid: false, error: `Gemini validation failed (${response.status})` };
} catch (error: any) {
return toValidationErrorResult(error);
}
}
// ── Copilot Web token validator ──
async function validateCopilotWebProvider({ apiKey, providerSpecificData = {} }: any) {
try {
const raw = String(apiKey || "").trim();
if (!raw) {
return {
valid: false,
error: "Paste your access_token from copilot.microsoft.com DevTools → Cookies",
};
}
// Extract token — may be bare JWT, cookie string with access_token=, or Bearer prefix
const { extractAccessToken } = await import("@omniroute/open-sse/executors/copilot-web.ts");
const token = extractAccessToken(raw);
if (!token) {
return { valid: false, error: "Could not extract access_token from input" };
}
// Probe Copilot's conversation API to verify token
const response = await validationWrite(
"https://copilot.microsoft.com/c/api/conversations?language=en",
{
method: "GET",
headers: applyCustomUserAgent(
{
Accept: "application/json",
Authorization: `Bearer ${token}`,
Origin: "https://copilot.microsoft.com",
Referer: "https://copilot.microsoft.com/",
},
providerSpecificData
),
}
);
if (response.status === 401 || response.status === 403) {
return {
valid: false,
error: "Invalid or expired access_token — re-paste from copilot.microsoft.com DevTools → Cookies",
};
}
if (response.status >= 500) {
return { valid: false, error: `Copilot unavailable (${response.status})` };
}
// 200, 400, 404 etc. all indicate the token was accepted
return { valid: true, error: null };
} catch (error: any) {
return toValidationErrorResult(error);
}
}
// ── t3.chat Web cookie validator ──
async function validateT3WebProvider({ apiKey, providerSpecificData = {} }: any) {
try {
const raw = String(apiKey || "").trim();
if (!raw) {
return {
valid: false,
error: "Paste your Cookie header and convex-session-id from t3.chat",
};
}
// The cookie field may contain "cookies=<Cookie header>\nconvexSessionId=<id>"
// or just the Cookie header value. Try to parse.
let cookieHeader = raw;
let convexSessionId = "";
if (raw.includes("convexSessionId") || raw.includes("convex-session-id")) {
// Structured format: "cookies=...; convexSessionId=..."
const parts = raw.split(/[,;\n]/).map((s: string) => s.trim());
const cookieParts: string[] = [];
for (const part of parts) {
if (part.startsWith("convexSessionId=") || part.startsWith("convex-session-id=")) {
convexSessionId = part.split("=").slice(1).join("=");
} else if (part.startsWith("cookies=")) {
cookieParts.push(part.slice("cookies=".length));
} else if (part.includes("=")) {
cookieParts.push(part);
}
}
if (cookieParts.length) cookieHeader = cookieParts.join("; ");
}
// Build final cookie with convex-session-id if found
const finalCookie = convexSessionId
? `${cookieHeader}; convex-session-id=${convexSessionId}`
: cookieHeader;
const response = await validationRead("https://t3.chat", {
headers: applyCustomUserAgent(
{
Accept: "text/html",
Cookie: finalCookie,
},
providerSpecificData
),
});
// t3.chat returns 200/302/404 for valid sessions, 5xx for down
if (response.status >= 500) {
return { valid: false, error: `t3.chat unavailable (${response.status})` };
}
return { valid: true, error: null };
} catch (error: any) {
return toValidationErrorResult(error);
}
}
/** Jules API — GET /v1alpha/sources with X-Goog-Api-Key (see developers.google.com/jules/api). */
async function validateJulesProvider({ apiKey }: { apiKey: string }) {
try {
@@ -3550,6 +3775,10 @@ export async function validateProviderApiKey({ provider, apiKey, providerSpecifi
"muse-spark-web": validateMuseSparkWebProvider,
"inner-ai": validateInnerAiProvider,
"adapta-web": validateAdaptaWebProvider,
"claude-web": validateClaudeWebProvider,
"gemini-web": validateGeminiWebProvider,
"copilot-web": validateCopilotWebProvider,
"t3-web": validateT3WebProvider,
"azure-openai": validateAzureOpenAIProvider,
"azure-ai": validateAzureAiProvider,
"voyage-ai": ({ apiKey, providerSpecificData }: any) => {

View File

@@ -2033,6 +2033,309 @@ test("validateCommandCodeProvider rejects auth failures and provider outages", a
});
});
// ─── claude-web validator ────────────────────────────────────────────────────
const { __setTlsFetchOverrideForTesting: __setClaudeTlsFetchOverride } =
await import("../../open-sse/services/claudeTlsClient.ts");
function makeClaudeTlsResponse(status: number, body: string, headers: Record<string, string> = {}): any {
const h = new Headers();
for (const [k, v] of Object.entries(headers)) h.set(k, v);
return { status, ok: status >= 200 && status < 300, headers: h, text: body, body: null };
}
test("claude-web validator: 200 from /api/organizations → valid", async () => {
let captured: { url: string; opts: any } | null = null;
__setClaudeTlsFetchOverride(async (url, opts) => {
captured = { url, opts };
return makeClaudeTlsResponse(200, JSON.stringify({ orgs: [] }));
});
const result = await validateProviderApiKey({
provider: "claude-web",
apiKey: "sessionKey=sk-ant-sid02-test-session-key",
});
assert.equal(result.valid, true);
assert.equal(result.error, null);
assert.equal(captured?.url, "https://claude.ai/api/organizations");
assert.match((captured?.opts.headers as Record<string, string>).Cookie || "", /sessionKey=sk-ant-sid02-test-session-key/);
__setClaudeTlsFetchOverride(null);
});
test("claude-web validator: full cookie blob passes through verbatim", async () => {
let capturedCookie = "";
__setClaudeTlsFetchOverride(async (_url, opts) => {
capturedCookie = (opts.headers as Record<string, string>).Cookie || "";
return makeClaudeTlsResponse(200, JSON.stringify({ orgs: [] }));
});
const blob =
"__cf_bm=abc123; sessionKey=sk-ant-sid02-test; intercom-device-id-lupk8zyo=xyz; __stripe_mid=stripe123";
await validateProviderApiKey({ provider: "claude-web", apiKey: blob });
assert.equal(capturedCookie, blob);
__setClaudeTlsFetchOverride(null);
});
test("claude-web validator: 401 → invalid session cookie", async () => {
__setClaudeTlsFetchOverride(async () =>
makeClaudeTlsResponse(401, JSON.stringify({ error: "unauthorized" }))
);
const result = await validateProviderApiKey({
provider: "claude-web",
apiKey: "sessionKey=expired-key",
});
assert.equal(result.valid, false);
assert.match(result.error || "", /Invalid or expired session cookie/i);
__setClaudeTlsFetchOverride(null);
});
test("claude-web validator: 429 → valid (rate limited means auth passed)", async () => {
__setClaudeTlsFetchOverride(async () =>
makeClaudeTlsResponse(429, JSON.stringify({ error: "rate limited" }))
);
const result = await validateProviderApiKey({
provider: "claude-web",
apiKey: "sessionKey=sk-ant-sid02-good-key",
});
assert.equal(result.valid, true);
__setClaudeTlsFetchOverride(null);
});
test("claude-web validator: 500 → Claude.ai unavailable", async () => {
__setClaudeTlsFetchOverride(async () =>
makeClaudeTlsResponse(500, "internal server error")
);
const result = await validateProviderApiKey({
provider: "claude-web",
apiKey: "sessionKey=sk-ant-sid02-any-key",
});
assert.equal(result.valid, false);
assert.match(result.error || "", /Claude\.ai unavailable \(500\)/i);
__setClaudeTlsFetchOverride(null);
});
test("claude-web validator: TLS client unavailable → clear error", async () => {
const { TlsClientUnavailableError } = await import("../../open-sse/services/claudeTlsClient.ts");
__setClaudeTlsFetchOverride(async () => {
throw new TlsClientUnavailableError("tls-client-node not installed");
});
const result = await validateProviderApiKey({
provider: "claude-web",
apiKey: "sessionKey=sk-ant-sid02-any-key",
});
assert.equal(result.valid, false);
assert.match(result.error || "", /tls-client-node not installed/i);
__setClaudeTlsFetchOverride(null);
});
test("claude-web validator: bare sessionKey value gets prefixed", async () => {
let capturedCookie = "";
__setClaudeTlsFetchOverride(async (_url, opts) => {
capturedCookie = (opts.headers as Record<string, string>).Cookie || "";
return makeClaudeTlsResponse(200, JSON.stringify({ orgs: [] }));
});
await validateProviderApiKey({
provider: "claude-web",
apiKey: "sk-ant-sid02-bare-value",
});
assert.equal(capturedCookie, "sessionKey=sk-ant-sid02-bare-value");
__setClaudeTlsFetchOverride(null);
});
// ─── gemini-web validator ────────────────────────────────────────────────────
test("gemini-web validator: 200 from gemini.google.com → valid", async () => {
globalThis.fetch = async (url, init = {}) => {
const target = String(url);
const headers = init.headers || {};
if (target.includes("gemini.google.com/app")) {
assert.match((headers as Record<string, string>).Cookie || "", /__Secure-1PSID=eyJPSID/);
return new Response("ok", { status: 200 });
}
throw new Error(`unexpected fetch: ${target}`);
};
const result = await validateProviderApiKey({
provider: "gemini-web",
apiKey: "__Secure-1PSID=eyJPSID",
});
assert.equal(result.valid, true);
assert.equal(result.error, null);
});
test("gemini-web validator: bare value gets __Secure-1PSID prefix", async () => {
let capturedCookie = "";
globalThis.fetch = async (url, init = {}) => {
if (String(url).includes("gemini.google.com")) {
capturedCookie = ((init.headers as Record<string, string>) || {}).Cookie || "";
return new Response("ok", { status: 200 });
}
throw new Error(`unexpected fetch: ${String(url)}`);
};
await validateProviderApiKey({ provider: "gemini-web", apiKey: "eyJbarevalue" });
assert.equal(capturedCookie, "__Secure-1PSID=eyJbarevalue");
});
test("gemini-web validator: 401 → invalid cookie", async () => {
globalThis.fetch = async () => new Response("unauthorized", { status: 401 });
const result = await validateProviderApiKey({
provider: "gemini-web",
apiKey: "__Secure-1PSID=expired",
});
assert.equal(result.valid, false);
assert.match(result.error || "", /Invalid or expired __Secure-1PSID/i);
});
test("gemini-web validator: 500 → unavailable", async () => {
globalThis.fetch = async () => new Response("down", { status: 500 });
const result = await validateProviderApiKey({
provider: "gemini-web",
apiKey: "__Secure-1PSID=eyJany",
});
assert.equal(result.valid, false);
assert.match(result.error || "", /Gemini validation failed \(500\)/i);
});
// ─── copilot-web validator ───────────────────────────────────────────────────
test("copilot-web validator: valid access_token → 200", async () => {
globalThis.fetch = async (url, init = {}) => {
const target = String(url);
if (target.includes("copilot.microsoft.com/c/api/conversations")) {
assert.match(
((init.headers as Record<string, string>) || {}).Authorization || "",
/Bearer eyJhbGci/
);
return new Response(JSON.stringify({ conversations: [] }), { status: 200 });
}
throw new Error(`unexpected fetch: ${target}`);
};
const result = await validateProviderApiKey({
provider: "copilot-web",
apiKey: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.test",
});
assert.equal(result.valid, true);
assert.equal(result.error, null);
});
test("copilot-web validator: cookie with access_token= is extracted", async () => {
let capturedAuth = "";
globalThis.fetch = async (url, init = {}) => {
if (String(url).includes("copilot.microsoft.com")) {
capturedAuth = ((init.headers as Record<string, string>) || {}).Authorization || "";
return new Response(JSON.stringify({}), { status: 200 });
}
throw new Error(`unexpected fetch: ${String(url)}`);
};
await validateProviderApiKey({
provider: "copilot-web",
apiKey: "access_token=eyJhbGciOiJIUzI1NiJ9.payload.sig; other_cookie=foo",
});
assert.equal(capturedAuth, "Bearer eyJhbGciOiJIUzI1NiJ9.payload.sig");
});
test("copilot-web validator: 401 → invalid token", async () => {
globalThis.fetch = async () => new Response("unauthorized", { status: 401 });
const result = await validateProviderApiKey({
provider: "copilot-web",
apiKey: "bad-token",
});
assert.equal(result.valid, false);
assert.match(result.error || "", /Invalid or expired access_token/i);
});
test("copilot-web validator: 500 → unavailable", async () => {
globalThis.fetch = async () => new Response("down", { status: 500 });
const result = await validateProviderApiKey({
provider: "copilot-web",
apiKey: "any-token",
});
assert.equal(result.valid, false);
assert.match(result.error || "", /Copilot unavailable \(500\)/i);
});
test("copilot-web validator: empty input → paste prompt", async () => {
globalThis.fetch = async () => {
throw new Error("should not fetch");
};
const result = await validateProviderApiKey({ provider: "copilot-web", apiKey: "" });
assert.equal(result.valid, false);
assert.match(result.error || "", /Paste your access_token/i);
});
// ─── t3-web validator ────────────────────────────────────────────────────────
test("t3-web validator: valid cookies → valid", async () => {
globalThis.fetch = async (url, init = {}) => {
if (String(url).includes("t3.chat")) {
return new Response("ok", { status: 200 });
}
throw new Error(`unexpected fetch: ${String(url)}`);
};
const result = await validateProviderApiKey({
provider: "t3-web",
apiKey: "cookies=__session=abc123; convexSessionId=def456",
});
assert.equal(result.valid, true);
assert.equal(result.error, null);
});
test("t3-web validator: 500 → unavailable", async () => {
globalThis.fetch = async () => new Response("down", { status: 500 });
const result = await validateProviderApiKey({
provider: "t3-web",
apiKey: "cookies=__session=abc",
});
assert.equal(result.valid, false);
assert.match(result.error || "", /t3\.chat unavailable \(500\)/i);
});
test("t3-web validator: valid cookies → passes through", async () => {
globalThis.fetch = async (url, init = {}) => {
if (String(url).includes("t3.chat")) {
return new Response("ok", { status: 200 });
}
throw new Error(`unexpected fetch: ${String(url)}`);
};
const result = await validateProviderApiKey({
provider: "t3-web",
apiKey: "__session=abc123; convex-session-id=def456",
});
assert.equal(result.valid, true);
assert.equal(result.error, null);
});
test("llama-cpp is classified as a self-hosted chat provider", async () => {
const { isSelfHostedChatProvider, isLocalProvider, providerAllowsOptionalApiKey } =
await import("../../src/shared/constants/providers.ts");