Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 0 additions & 96 deletions open-sse/executors/claude-web-auto-refresh.ts

This file was deleted.

25 changes: 9 additions & 16 deletions open-sse/executors/claude-web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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;
Expand Down Expand Up @@ -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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Missing Retry and Auto-Refresh Logic for cf_clearance\n\nWith the removal of ClaudeWebAutoRefreshExecutor (the retry wrapper) in this PR, the automatic retry and refresh mechanism for Cloudflare Turnstile (cf_clearance) is completely lost.\n\nCurrently, normalizeClaudeSessionCookieWithAutoRefresh only attempts to solve Turnstile if cf_clearance is missing from the cookie. If the cookie already contains an expired or invalid cf_clearance token, it will skip auto-solving and proceed with the request, which will fail with a 403 or 401 error. Since there is no longer any retry wrapper, the request will fail permanently.\n\nTo restore this critical functionality, we should implement the retry logic directly inside ClaudeWebExecutor.execute in claude-web.ts.\n\nHere is how you can update execute to handle this:\n\ntypescript\n // Change const to let\n let fetchResponse = await tlsFetchClaude(completionUrl, {\n method: \"POST\",\n headers: {\n ...headers,\n Cookie: cookieHeader,\n },\n body: JSON.stringify(claudePayload),\n timeoutMs: FETCH_TIMEOUT_MS,\n stream: true,\n signal: combinedSignal,\n });\n\n // Retry once with a fresh cf_clearance token if we hit a 403 or 401\n if (fetchResponse.status === 403 || fetchResponse.status === 401) {\n log?.warn?.(\"CLAUDE-WEB\", `HTTP ${fetchResponse.status} - attempting to refresh cf_clearance and retry`);\n try {\n const freshCfClearance = await getCfClearanceToken({ force: true });\n const updatedCookieHeader = cookieHeader.includes(\"cf_clearance=\")\n ? cookieHeader.replace(/cf_clearance=[^;]+/, `cf_clearance=${freshCfClearance}`)\n : cookieHeader ? `${cookieHeader}; cf_clearance=${freshCfClearance}` : `cf_clearance=${freshCfClearance}`;\n\n log?.info?.(\"CLAUDE-WEB\", \"cf_clearance refreshed, retrying request\");\n fetchResponse = await tlsFetchClaude(completionUrl, {\n method: \"POST\",\n headers: {\n ...headers,\n Cookie: updatedCookieHeader,\n },\n body: JSON.stringify(claudePayload),\n timeoutMs: FETCH_TIMEOUT_MS,\n stream: true,\n signal: combinedSignal,\n });\n } catch (retryError) {\n const message = retryError instanceof Error ? retryError.message : String(retryError);\n log?.error?.(\"CLAUDE-WEB\", `Failed to auto-refresh cf_clearance on retry: ${message}`);\n }\n }\n

}
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -479,7 +469,10 @@ export class ClaudeWebExecutor extends BaseExecutor {
};
}

const cookieHeader = normalizeClaudeSessionCookie(rawCookie);
const cookieHeader = await normalizeClaudeSessionCookieWithAutoRefresh(rawCookie, {
allowAutoSolve: true,
log,
});
const deviceId = (credentials as any)?.deviceId as string | undefined;

// Transform request to Claude format
Expand Down Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
51 changes: 27 additions & 24 deletions src/shared/constants/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"]);
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -418,18 +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,
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",
Expand Down Expand Up @@ -2878,6 +2880,7 @@ export function providerAllowsOptionalApiKey(providerId: unknown): boolean {
providerId === "petals" ||
providerId === "pollinations" ||
providerId === "copilot-web" ||
providerId === "duckduckgo-web" ||
providerId === "veoaifree-web" ||
providerId === "hackclub" ||
providerId === "huggingchat" ||
Expand Down