fix(providers): fix claude-web 403, move no-auth providers out of web-cookie (#3090)

Integrated into release/v3.8.9 — resolved conflicts with release branch (allowAutoSolve:true preserved, duckduckgo-web correctly kept in NOAUTH_PROVIDERS).
This commit is contained in:
Paijo
2026-06-03 16:35:45 +07:00
committed by GitHub
parent c42591f400
commit 9141e98458
4 changed files with 35 additions and 149 deletions

View File

@@ -1,96 +0,0 @@
/**
* Claude Web Executor with Auto-Refresh cf_clearance
*
* Wraps the existing ClaudeWebExecutor with Turnstile solving capability.
* When cf_clearance is missing or invalid, automatically solves Cloudflare
* Turnstile challenge and injects fresh token.
*/
import type { ExecuteInput } from "./base.ts";
import { ClaudeWebExecutor } from "./claude-web.ts";
import { getCfClearanceToken } from "../services/claudeTurnstileSolver.ts";
/**
* Enhanced executor with auto-refresh
*/
export class ClaudeWebAutoRefreshExecutor extends ClaudeWebExecutor {
/**
* Override execute to add cf_clearance auto-refresh
*/
async execute(input: ExecuteInput) {
const { credentials, log, signal } = input;
// First attempt with provided credentials
let result = await super.execute(input);
// Check if response is a 403 (Cloudflare challenge) or 401 (invalid cf_clearance)
if (result.response.status === 403 || result.response.status === 401) {
log?.warn?.(
"CLAUDE-WEB",
`HTTP ${result.response.status} - attempting to refresh cf_clearance`
);
try {
// Attempt to solve Turnstile and get fresh cf_clearance
const freshCfClearance = await getCfClearanceToken({ force: true });
// Update credentials with fresh cf_clearance
const updatedCreds = {
...credentials,
cookie: credentials?.cookie
? `${credentials.cookie}; cf_clearance=${freshCfClearance}`
: `cf_clearance=${freshCfClearance}`,
};
log?.info?.("CLAUDE-WEB", "cf_clearance refreshed, retrying request");
// Retry with fresh cookie
result = await super.execute({
...input,
credentials: updatedCreds,
});
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
log?.error?.("CLAUDE-WEB", `Failed to auto-refresh cf_clearance: ${message}`);
// Return original error response
}
}
return result;
}
/**
* Override testConnection to include cf_clearance check
*/
async testConnection(
credentials: Record<string, unknown>,
signal?: AbortSignal
): Promise<boolean> {
try {
// Test with provided credentials first
const basicTest = await super.testConnection(credentials, signal);
if (basicTest) {
return true;
}
// If basic test failed, try to refresh cf_clearance
const rawCookie = String((credentials as any)?.cookie || "");
if (!rawCookie.trim()) {
return false;
}
const freshCfClearance = await getCfClearanceToken();
const updatedCreds = {
...credentials,
cookie: `${rawCookie}; cf_clearance=${freshCfClearance}`,
};
return await super.testConnection(updatedCreds, signal);
} catch (error) {
return false;
}
}
}
// Export singleton instance
export const claudeWebAutoRefresh = new ClaudeWebAutoRefreshExecutor();

View File

@@ -22,8 +22,7 @@
import { BaseExecutor, mergeAbortSignals, type ExecuteInput } from "./base.ts";
import { FETCH_TIMEOUT_MS } from "../config/constants.ts";
import { tlsFetchClaude } from "../services/claudeTlsClient.ts";
import { createAutoRefreshMiddleware, refreshCookie } from "../services/claudeWebAutoRefresh.ts";
import { getCfClearanceToken, getCacheStatus } from "../services/claudeTurnstileSolver.ts";
import { getCfClearanceToken } from "../services/claudeTurnstileSolver.ts";
import { normalizeSessionCookieHeader } from "@/lib/providers/webCookieAuth";
import { randomUUID } from "crypto";
import { sanitizeErrorMessage } from "../utils/error.ts";
@@ -45,16 +44,6 @@ const DEFAULT_CLAUDE_MODEL = "claude-sonnet-4-6";
/**
* Extended credentials to include organization and conversation context
*/
interface ClaudeWebCredentials {
cookie: string;
deviceId?: string;
orgId?: string;
conversationId?: string;
}
/**
* Full request payload matching real Claude Web API format
*/
interface ClaudeWebRequestPayload {
prompt: string;
model: string;
@@ -175,7 +164,8 @@ async function normalizeClaudeSessionCookieWithAutoRefresh(
options?.log?.info?.("CLAUDE-WEB", "cf_clearance injected successfully");
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
// Continue anyway - request might fail, but that's OK
options?.log?.warn?.("CLAUDE-WEB", `cf_clearance injection failed: ${message}`);
// Continue anyway - the retry wrapper will handle 403
}
}
@@ -429,7 +419,7 @@ export class ClaudeWebExecutor extends BaseExecutor {
/**
* Get user's organization ID from session
*/
async execute({ model, body, stream, credentials, signal, log }: ExecuteInput) {
async execute({ model, body, stream: _stream, credentials, signal, log }: ExecuteInput) {
const bodyObj = (body || {}) as Record<string, unknown>;
try {
@@ -479,7 +469,10 @@ export class ClaudeWebExecutor extends BaseExecutor {
};
}
const cookieHeader = await normalizeClaudeSessionCookieWithAutoRefresh(rawCookie, { log });
const cookieHeader = await normalizeClaudeSessionCookieWithAutoRefresh(rawCookie, {
allowAutoSolve: true,
log,
});
const deviceId = (credentials as any)?.deviceId as string | undefined;
// Transform request to Claude format
@@ -542,7 +535,7 @@ export class ClaudeWebExecutor extends BaseExecutor {
log?.debug?.("CLAUDE-WEB", `Making request to ${completionUrl}`);
// Inject cf_clearance before calling tlsFetchClaude
// cf_clearance is already injected via normalizeClaudeSessionCookieWithAutoRefresh above
const fetchResponse = await tlsFetchClaude(completionUrl, {
method: "POST",

View File

@@ -69,12 +69,6 @@ export const WEB_SESSION_CREDENTIAL_REQUIREMENTS = {
placeholder: "access_token=... or a DevTools HAR export",
acceptsFullCookieHeader: false,
},
"veoaifree-web": {
kind: "none",
credentialName: "",
placeholder: "",
acceptsFullCookieHeader: false,
},
"t3-web": {
kind: "cookie",
credentialName: "convex-session-id + Cookie header",
@@ -93,12 +87,6 @@ export const WEB_SESSION_CREDENTIAL_REQUIREMENTS = {
placeholder: "token_value user@example.com",
acceptsFullCookieHeader: false,
},
"duckduckgo-web": {
kind: "none",
credentialName: "",
placeholder: "",
acceptsFullCookieHeader: false,
},
huggingchat: {
kind: "cookie",
credentialName: "hf-chat",

View File

@@ -45,6 +45,32 @@ export const NOAUTH_PROVIDERS = {
text: "OpenCode Free uses the public OpenCode endpoint (https://opencode.ai/zen/v1). No signup or API key needed. Rate limits apply.",
},
},
"duckduckgo-web": {
id: "duckduckgo-web",
alias: "ddgw",
name: "DuckDuckGo AI Chat",
icon: "auto_awesome",
color: "#DE5833",
textIcon: "DDG",
website: "https://duckduckgo.com/duckchat",
noAuth: true,
hasFree: true,
freeNote: "Free — anonymous access to multiple AI models via DuckDuckGo.",
authHint: "No credentials required — DuckDuckGo AI Chat is anonymous and free.",
},
"veoaifree-web": {
id: "veoaifree-web",
alias: "veo-free",
name: "Veo AI Free",
icon: "videocam",
color: "#8B5CF6",
textIcon: "VF",
website: "https://veoaifree.com",
noAuth: true,
hasFree: true,
freeNote: "Free video generation — VEO 3.1, Seedance. 6 requests/hour.",
authHint: "No auth required. Rate limited to 6 requests/hour per IP.",
},
};
export const FREE_APIKEY_PROVIDER_IDS = new Set(["qoder"]);
@@ -365,18 +391,6 @@ export const WEB_COOKIE_PROVIDERS = {
subscriptionRisk: true,
riskNoticeVariant: "webCookie",
},
"veoaifree-web": {
id: "veoaifree-web",
alias: "veo-free",
name: "Veo AI Free",
icon: "videocam",
color: "#8B5CF6",
textIcon: "VF",
website: "https://veoaifree.com",
hasFree: true,
freeNote: "Free video generation — VEO 3.1, Seedance. 6 requests/hour.",
authHint: "No auth required. Rate limited to 6 requests/hour per IP.",
},
"t3-web": {
id: "t3-web",
alias: "t3chat",
@@ -418,19 +432,6 @@ export const WEB_COOKIE_PROVIDERS = {
authHint:
"Paste your __client cookie value from .clerk.agent.adapta.one (DevTools → Application → Cookies)",
},
"duckduckgo-web": {
id: "duckduckgo-web",
alias: "ddgw",
name: "DuckDuckGo AI Chat",
icon: "auto_awesome",
color: "#DE5833",
textIcon: "DDG",
website: "https://duckduckgo.com/duckchat",
hasFree: true,
noAuth: true,
freeNote: "Free — anonymous access to multiple AI models via DuckDuckGo.",
authHint: "No credentials required — DuckDuckGo AI Chat is anonymous and free.",
},
huggingchat: {
id: "huggingchat",
alias: "hc",