From 1e3ac5fff71c572fa98c6b9e4ba4e00efdec7773 Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Fri, 17 Apr 2026 16:59:18 -0300 Subject: [PATCH] feat: add CC Compatible connection-level 1M context toggle Closes #1357 --- open-sse/executors/base.ts | 38 ++++---- open-sse/services/claudeCodeCompatible.ts | 53 ++++++++++++ .../dashboard/providers/[id]/page.tsx | 59 ++++++++++++- src/lib/providers/requestDefaults.ts | 33 +++++++ src/shared/validation/schemas.ts | 9 ++ tests/unit/executor-default-base.test.ts | 86 +++++++++++++++++++ .../provider-specific-data-schema.test.ts | 46 ++++++++++ .../request-defaults-store-session.test.ts | 36 +++++++- 8 files changed, 336 insertions(+), 24 deletions(-) diff --git a/open-sse/executors/base.ts b/open-sse/executors/base.ts index c623802d7d..71e12d2f18 100644 --- a/open-sse/executors/base.ts +++ b/open-sse/executors/base.ts @@ -4,6 +4,12 @@ import { getRotatingApiKey } from "../services/apiKeyRotator.ts"; import { getOpenAICompatibleType, isClaudeCodeCompatible } from "../services/provider.ts"; import type { ProviderRequestDefaults } from "../services/providerRequestDefaults.ts"; import { signRequestBody } from "../services/claudeCodeCCH.ts"; +import { + appendAnthropicBetaHeader, + CONTEXT_1M_BETA_HEADER, + modelSupportsContext1mBeta, +} from "../services/claudeCodeCompatible.ts"; +import { getClaudeCodeCompatibleRequestDefaults } from "@/lib/providers/requestDefaults"; /** * Sanitizes a custom API path to prevent path traversal attacks. @@ -386,27 +392,17 @@ export class BaseExecutor { const headers = this.buildHeaders(activeCredentials, stream); applyConfiguredUserAgent(headers, activeCredentials?.providerSpecificData); - // Append 1M context beta header when [1m] suffix was used - // Only supported for specific Claude models per Anthropic docs - if (extendedContext) { - const EXTENDED_CONTEXT_MODELS = [ - "claude-opus-4-7", - "claude-opus-4-6", - "claude-sonnet-4-6", - "claude-sonnet-4-5", - "claude-sonnet-4", - ]; - const baseModel = model.replace(/-\d{8}$/, ""); - if ( - EXTENDED_CONTEXT_MODELS.some((m) => baseModel === m || model === m || model.startsWith(m)) - ) { - const existing = headers["Anthropic-Beta"]; - if (existing) { - headers["Anthropic-Beta"] = existing + ",context-1m-2025-08-07"; - } else { - headers["Anthropic-Beta"] = "context-1m-2025-08-07"; - } - } + const ccRequestDefaults = isClaudeCodeCompatible(this.provider) + ? getClaudeCodeCompatibleRequestDefaults(activeCredentials?.providerSpecificData) + : {}; + const shouldForwardExtendedContext = + extendedContext && + modelSupportsContext1mBeta(model) && + !isClaudeCodeCompatible(this.provider); + const shouldForwardCcCompatibleContext1m = + isClaudeCodeCompatible(this.provider) && ccRequestDefaults.context1m === true; + if (shouldForwardExtendedContext || shouldForwardCcCompatibleContext1m) { + appendAnthropicBetaHeader(headers, CONTEXT_1M_BETA_HEADER); } const transformedBody = await this.transformRequest(model, body, stream, activeCredentials); diff --git a/open-sse/services/claudeCodeCompatible.ts b/open-sse/services/claudeCodeCompatible.ts index b0246cce1e..5f8a5571c6 100644 --- a/open-sse/services/claudeCodeCompatible.ts +++ b/open-sse/services/claudeCodeCompatible.ts @@ -35,10 +35,32 @@ export const CLAUDE_CODE_COMPATIBLE_PREFIX = "anthropic-compatible-cc-"; export const CLAUDE_CODE_COMPATIBLE_DEFAULT_CHAT_PATH = "/v1/messages?beta=true"; export const CLAUDE_CODE_COMPATIBLE_DEFAULT_MODELS_PATH = "/models"; export const CLAUDE_CODE_COMPATIBLE_DEFAULT_MAX_TOKENS = 8092; +<<<<<<< HEAD export const CLAUDE_CODE_COMPATIBLE_ANTHROPIC_VERSION = ANTHROPIC_VERSION_HEADER; export const CLAUDE_CODE_COMPATIBLE_ANTHROPIC_BETA = ANTHROPIC_BETA_FULL; export const CLAUDE_CODE_COMPATIBLE_VERSION = CLAUDE_CLI_VERSION; export const CLAUDE_CODE_COMPATIBLE_USER_AGENT = CLAUDE_CLI_USER_AGENT; +||||||| d868124c +export const CLAUDE_CODE_COMPATIBLE_ANTHROPIC_VERSION = "2023-06-01"; +export const CLAUDE_CODE_COMPATIBLE_ANTHROPIC_BETA = + "claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,context-management-2025-06-27,prompt-caching-scope-2026-01-05,effort-2025-11-24,token-efficient-tools-2025-02-19"; +export const CLAUDE_CODE_COMPATIBLE_VERSION = "2.1.87"; +export const CLAUDE_CODE_COMPATIBLE_USER_AGENT = `claude-cli/${CLAUDE_CODE_COMPATIBLE_VERSION} (external, cli)`; +======= +export const CLAUDE_CODE_COMPATIBLE_ANTHROPIC_VERSION = "2023-06-01"; +export const CLAUDE_CODE_COMPATIBLE_ANTHROPIC_BETA = + "claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,context-management-2025-06-27,prompt-caching-scope-2026-01-05,effort-2025-11-24,token-efficient-tools-2025-02-19"; +export const CONTEXT_1M_BETA_HEADER = "context-1m-2025-08-07"; +export const CLAUDE_CODE_COMPATIBLE_VERSION = "2.1.87"; +export const CLAUDE_CODE_COMPATIBLE_USER_AGENT = `claude-cli/${CLAUDE_CODE_COMPATIBLE_VERSION} (external, cli)`; +const CONTEXT_1M_SUPPORTED_MODELS = [ + "claude-opus-4-7", + "claude-opus-4-6", + "claude-sonnet-4-6", + "claude-sonnet-4-5", + "claude-sonnet-4", +]; +>>>>>>> coder/cc-compatible-1m-toggle /** * Build the billing header dynamically with fingerprint and CCH placeholder. * The cch=00000 placeholder is later replaced by signRequestBody(). @@ -128,6 +150,37 @@ export function joinClaudeCodeCompatibleUrl(baseUrl: string, path: string): stri return joinNormalizedBaseUrlAndPath(stripClaudeCodeCompatibleEndpointSuffix(baseUrl), path); } +export function appendAnthropicBetaHeader( + headers: Record, + betaHeader: string +): void { + const existingKey = Object.keys(headers).find((key) => key.toLowerCase() === "anthropic-beta"); + if (!existingKey) { + headers["anthropic-beta"] = betaHeader; + return; + } + + const existingValues = String(headers[existingKey] || "") + .split(",") + .map((value) => value.trim()) + .filter(Boolean); + + if (!existingValues.includes(betaHeader)) { + headers[existingKey] = [...existingValues, betaHeader].join(","); + } +} + +export function modelSupportsContext1mBeta(model: string | null | undefined): boolean { + const normalizedModel = String(model || "") + .trim() + .toLowerCase() + .replace(/-\d{8}$/, ""); + + return CONTEXT_1M_SUPPORTED_MODELS.some( + (supported) => normalizedModel === supported || normalizedModel.startsWith(`${supported}-`) + ); +} + export function buildClaudeCodeCompatibleHeaders( apiKey: string, stream = false, diff --git a/src/app/(dashboard)/dashboard/providers/[id]/page.tsx b/src/app/(dashboard)/dashboard/providers/[id]/page.tsx index 55056e3fbb..2b9d0abfc4 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/page.tsx +++ b/src/app/(dashboard)/dashboard/providers/[id]/page.tsx @@ -48,7 +48,10 @@ import { resolveManagedModelAlias } from "@/shared/utils/providerModelAliases"; import { maskEmail, pickMaskedDisplayValue, pickDisplayValue } from "@/shared/utils/maskEmail"; import useEmailPrivacyStore from "@/store/emailPrivacyStore"; import EmailPrivacyToggle from "@/shared/components/EmailPrivacyToggle"; -import { getCodexRequestDefaults as _getCodexRequestDefaults } from "@/lib/providers/requestDefaults"; +import { + getClaudeCodeCompatibleRequestDefaults as _getClaudeCodeCompatibleRequestDefaults, + getCodexRequestDefaults as _getCodexRequestDefaults, +} from "@/lib/providers/requestDefaults"; import { resolveDashboardProviderInfo } from "../providerPageUtils"; type CompatByProtocolMap = Partial< @@ -608,6 +611,15 @@ function getCodexRequestDefaults(providerSpecificData: unknown): { }; } +function getClaudeCodeCompatibleRequestDefaults(providerSpecificData: unknown): { + context1m: boolean; +} { + const defaults = _getClaudeCodeCompatibleRequestDefaults(providerSpecificData); + return { + context1m: defaults.context1m === true, + }; +} + function compatProtocolLabelKey(protocol: string): string { if (protocol === "openai") return "compatProtocolOpenAI"; if (protocol === "openai-responses") return "compatProtocolOpenAIResponses"; @@ -5389,6 +5401,7 @@ function AddApiKeyModal({ customUserAgent: "", accountId: "", consoleApiKey: "", + ccCompatibleContext1m: false, }); const [validating, setValidating] = useState(false); const [validationResult, setValidationResult] = useState(null); @@ -5497,6 +5510,9 @@ function AddApiKeyModal({ } else if (isCloudflare && formData.accountId.trim()) { providerSpecificData.accountId = formData.accountId.trim(); } + if (isCcCompatible && formData.ccCompatibleContext1m) { + providerSpecificData.requestDefaults = { context1m: true }; + } const payload = { name: formData.name, @@ -5605,6 +5621,16 @@ function AddApiKeyModal({ {saveError} )} + {isCcCompatible && ( +
+ setFormData({ ...formData, ccCompatibleContext1m: checked })} + label="CC Compatible 1M Context" + description="When enabled, this connection appends `anthropic-beta: context-1m-2025-08-07`." + /> +
+ )} {isCompatible && (

{isCcCompatible @@ -5797,6 +5823,7 @@ function EditConnectionModal({ isOpen, connection, onSave, onClose }: EditConnec codexFastServiceTier: false, codexOpenaiStoreEnabled: false, consoleApiKey: "", + ccCompatibleContext1m: false, }); const [testing, setTesting] = useState(false); const [testResult, setTestResult] = useState(null); @@ -5819,6 +5846,7 @@ function EditConnectionModal({ isOpen, connection, onSave, onClose }: EditConnec const isSearxng = connection?.provider === "searxng-search"; const isGooglePse = connection?.provider === "google-pse-search"; const apiKeyOptional = isSearxng; + const isCcCompatible = isClaudeCodeCompatibleProvider(connection?.provider); const defaultRegion = "us-central1"; useEffect(() => { @@ -5835,6 +5863,9 @@ function EditConnectionModal({ isOpen, connection, onSave, onClose }: EditConnec const rawAccountId = connection.providerSpecificData?.accountId; const existingAccountId = typeof rawAccountId === "string" ? rawAccountId : ""; const codexRequestDefaults = getCodexRequestDefaults(connection.providerSpecificData); + const ccRequestDefaults = getClaudeCodeCompatibleRequestDefaults( + connection.providerSpecificData + ); const rawConsoleApiKey = connection.providerSpecificData?.consoleApiKey; const existingConsoleApiKey = typeof rawConsoleApiKey === "string" ? rawConsoleApiKey : ""; setFormData({ @@ -5859,6 +5890,7 @@ function EditConnectionModal({ isOpen, connection, onSave, onClose }: EditConnec codexFastServiceTier: codexRequestDefaults.serviceTier === "priority", codexOpenaiStoreEnabled: connection.providerSpecificData?.openaiStoreEnabled === true, consoleApiKey: existingConsoleApiKey, + ccCompatibleContext1m: ccRequestDefaults.context1m, }); // Load existing extra keys from providerSpecificData const existing = connection.providerSpecificData?.extraApiKeys; @@ -6022,6 +6054,21 @@ function EditConnectionModal({ isOpen, connection, onSave, onClose }: EditConnec } else if (isCloudflare && formData.accountId.trim()) { updates.providerSpecificData.accountId = formData.accountId.trim(); } + if (isCcCompatible) { + const currentRequestDefaults = + updates.providerSpecificData.requestDefaults && + typeof updates.providerSpecificData.requestDefaults === "object" && + !Array.isArray(updates.providerSpecificData.requestDefaults) + ? { ...(updates.providerSpecificData.requestDefaults as Record) } + : {}; + if (formData.ccCompatibleContext1m) { + currentRequestDefaults.context1m = true; + } else { + delete currentRequestDefaults.context1m; + } + updates.providerSpecificData.requestDefaults = + Object.keys(currentRequestDefaults).length > 0 ? currentRequestDefaults : undefined; + } } else { // Also persist tag for OAuth accounts updates.providerSpecificData = { @@ -6112,6 +6159,16 @@ function EditConnectionModal({ isOpen, connection, onSave, onClose }: EditConnec /> )} + {isCcCompatible && ( +

+ setFormData({ ...formData, ccCompatibleContext1m: checked })} + label="CC Compatible 1M Context" + description="When enabled, this connection appends `anthropic-beta: context-1m-2025-08-07`." + /> +
+ )} {isOAuth && connection.email && (

{t("email")}

diff --git a/src/lib/providers/requestDefaults.ts b/src/lib/providers/requestDefaults.ts index d190bf85d8..6163626ec9 100644 --- a/src/lib/providers/requestDefaults.ts +++ b/src/lib/providers/requestDefaults.ts @@ -1,4 +1,5 @@ type JsonRecord = Record; +const CLAUDE_CODE_COMPATIBLE_PROVIDER_PREFIX = "anthropic-compatible-cc-"; import { normalizeExcludedModelPatterns } from "@/domain/connectionModelRules"; import { normalizeRoutingTags } from "@/domain/tagRouter"; @@ -23,6 +24,12 @@ function hasNonEmptyString(value: unknown): boolean { return typeof value === "string" && value.trim().length > 0; } +function isClaudeCodeCompatibleProvider(provider: string | null | undefined): boolean { + return ( + typeof provider === "string" && provider.startsWith(CLAUDE_CODE_COMPATIBLE_PROVIDER_PREFIX) + ); +} + export function normalizeCodexReasoningEffort(value: unknown): CodexReasoningEffort | undefined { const normalized = normalizeString(value); if (!normalized || !CODEX_REASONING_EFFORT_SET.has(normalized)) { @@ -38,6 +45,10 @@ export function normalizeCodexServiceTier(value: unknown): "priority" | undefine return undefined; } +export function normalizeClaudeCodeCompatibleContext1m(value: unknown): true | undefined { + return value === true ? true : undefined; +} + export function normalizeRequestDefaults( provider: string | null | undefined, value: unknown @@ -63,6 +74,15 @@ export function normalizeRequestDefaults( } } + if (isClaudeCodeCompatibleProvider(provider)) { + const context1m = normalizeClaudeCodeCompatibleContext1m(record.context1m); + if (context1m) { + normalized.context1m = true; + } else { + delete normalized.context1m; + } + } + return Object.keys(normalized).length > 0 ? normalized : undefined; } @@ -188,3 +208,16 @@ export function getCodexRequestDefaults(providerSpecificData: unknown): { ...(serviceTier ? { serviceTier } : {}), }; } + +export function getClaudeCodeCompatibleRequestDefaults(providerSpecificData: unknown): { + context1m?: true; +} { + const defaults = getProviderRequestDefaults( + "anthropic-compatible-cc-default", + providerSpecificData + ); + const context1m = normalizeClaudeCodeCompatibleContext1m(defaults.context1m); + return { + ...(context1m ? { context1m } : {}), + }; +} diff --git a/src/shared/validation/schemas.ts b/src/shared/validation/schemas.ts index 24c3f9e595..9217c35581 100644 --- a/src/shared/validation/schemas.ts +++ b/src/shared/validation/schemas.ts @@ -99,6 +99,15 @@ function validateProviderSpecificData( path: ["requestDefaults", "serviceTier"], }); } + + const context1m = requestDefaultsRecord.context1m; + if (context1m !== undefined && context1m !== null && typeof context1m !== "boolean") { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "providerSpecificData.requestDefaults.context1m must be a boolean", + path: ["requestDefaults", "context1m"], + }); + } } } diff --git a/tests/unit/executor-default-base.test.ts b/tests/unit/executor-default-base.test.ts index a1374474d8..b9eeb9fa2e 100644 --- a/tests/unit/executor-default-base.test.ts +++ b/tests/unit/executor-default-base.test.ts @@ -14,6 +14,7 @@ import { PROVIDERS } from "../../open-sse/config/constants.ts"; import { CLAUDE_CODE_COMPATIBLE_ANTHROPIC_VERSION, CLAUDE_CODE_COMPATIBLE_DEFAULT_CHAT_PATH, + CONTEXT_1M_BETA_HEADER, } from "../../open-sse/services/claudeCodeCompatible.ts"; class TestExecutor extends BaseExecutor { @@ -322,6 +323,91 @@ test("DefaultExecutor.buildHeaders rotates extra API keys and builds Claude Code assert.equal(ccJsonHeaders.Accept, "application/json"); }); +test("DefaultExecutor.execute uses CC-compatible connection defaults to append 1M beta", async () => { + const originalFetch = globalThis.fetch; + const calls = []; + const toPlainHeaders = (headers) => + headers instanceof Headers + ? Object.fromEntries(headers.entries()) + : Object.fromEntries( + Object.entries(headers || {}).map(([key, value]) => [ + key, + value == null ? "" : String(value), + ]) + ); + + globalThis.fetch = async (_url, init = {}) => { + calls.push({ headers: toPlainHeaders(init.headers) }); + return new Response(JSON.stringify({ ok: true }), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + }; + + try { + const cc = new DefaultExecutor("anthropic-compatible-cc-test"); + await cc.execute({ + model: "claude-sonnet-4-6", + body: { + model: "claude-sonnet-4-6", + messages: [{ role: "user", content: "hi" }], + max_tokens: 1, + }, + stream: false, + credentials: { + apiKey: "cc-key", + providerSpecificData: { + baseUrl: "https://cc.example.com/v1/messages?beta=true", + ccSessionId: "session-1", + }, + }, + extendedContext: false, + }); + await cc.execute({ + model: "claude-sonnet-4-6", + body: { + model: "claude-sonnet-4-6", + messages: [{ role: "user", content: "hi" }], + max_tokens: 1, + }, + stream: false, + credentials: { + apiKey: "cc-key", + providerSpecificData: { + baseUrl: "https://cc.example.com/v1/messages?beta=true", + ccSessionId: "session-1", + requestDefaults: { context1m: true }, + }, + }, + extendedContext: false, + }); + + const anthropicCompat = new DefaultExecutor("anthropic-compatible-test"); + await anthropicCompat.execute({ + model: "claude-sonnet-4-6", + body: { + model: "claude-sonnet-4-6", + messages: [{ role: "user", content: "hi" }], + max_tokens: 1, + }, + stream: false, + credentials: { + apiKey: "anth-key", + providerSpecificData: { + baseUrl: "https://anthropic.example.com/v1", + }, + }, + extendedContext: true, + }); + } finally { + globalThis.fetch = originalFetch; + } + + assert.equal(calls[0].headers["anthropic-beta"].includes(CONTEXT_1M_BETA_HEADER), false); + assert.equal(calls[1].headers["anthropic-beta"].includes(CONTEXT_1M_BETA_HEADER), true); + assert.equal(calls[2].headers["anthropic-beta"], CONTEXT_1M_BETA_HEADER); +}); + test("DefaultExecutor.transformRequest is a passthrough and preserves model ids with slashes", () => { const executor = new DefaultExecutor("openai"); const body = { model: "zai-org/GLM-5-FP8", messages: [{ role: "user", content: "hi" }] }; diff --git a/tests/unit/provider-specific-data-schema.test.ts b/tests/unit/provider-specific-data-schema.test.ts index 879238ffdb..da324e441f 100644 --- a/tests/unit/provider-specific-data-schema.test.ts +++ b/tests/unit/provider-specific-data-schema.test.ts @@ -41,3 +41,49 @@ test("provider schemas reject non-boolean openaiStoreEnabled values", () => { assert.equal(created.success, false); assert.equal(updated.success, false); }); + +test("provider schemas accept boolean requestDefaults.context1m for CC-compatible providers", () => { + const created = createProviderSchema.safeParse({ + provider: "anthropic-compatible-cc-demo", + apiKey: "token", + name: "CC Compatible", + providerSpecificData: { + requestDefaults: { + context1m: true, + }, + }, + }); + const updated = updateProviderConnectionSchema.safeParse({ + providerSpecificData: { + requestDefaults: { + context1m: false, + }, + }, + }); + + assert.equal(created.success, true); + assert.equal(updated.success, true); +}); + +test("provider schemas reject non-boolean requestDefaults.context1m values", () => { + const created = createProviderSchema.safeParse({ + provider: "anthropic-compatible-cc-demo", + apiKey: "token", + name: "CC Compatible", + providerSpecificData: { + requestDefaults: { + context1m: "yes", + }, + }, + }); + const updated = updateProviderConnectionSchema.safeParse({ + providerSpecificData: { + requestDefaults: { + context1m: 1, + }, + }, + }); + + assert.equal(created.success, false); + assert.equal(updated.success, false); +}); diff --git a/tests/unit/request-defaults-store-session.test.ts b/tests/unit/request-defaults-store-session.test.ts index 0fdf54a5fd..1aa04506aa 100644 --- a/tests/unit/request-defaults-store-session.test.ts +++ b/tests/unit/request-defaults-store-session.test.ts @@ -1,8 +1,12 @@ import test from "node:test"; import assert from "node:assert/strict"; -const { buildOpenAIStoreSessionId, ensureOpenAIStoreSessionFallback } = - await import("../../src/lib/providers/requestDefaults.ts"); +const { + buildOpenAIStoreSessionId, + ensureOpenAIStoreSessionFallback, + getClaudeCodeCompatibleRequestDefaults, + normalizeProviderSpecificData, +} = await import("../../src/lib/providers/requestDefaults.ts"); test("buildOpenAIStoreSessionId normalizes external and generated session ids", () => { assert.equal( @@ -38,3 +42,31 @@ test("ensureOpenAIStoreSessionFallback injects session_id only when no stable ca ); assert.equal(withExplicitSession.session_id, "existing-session"); }); + +test("normalizeProviderSpecificData keeps only boolean CC-compatible 1M request defaults", () => { + const normalized = normalizeProviderSpecificData("anthropic-compatible-cc-demo", { + baseUrl: "https://proxy.example.com/v1/messages?beta=true", + requestDefaults: { + context1m: true, + customFlag: "keep-me", + }, + }); + + assert.deepEqual(getClaudeCodeCompatibleRequestDefaults(normalized), { + context1m: true, + }); + assert.deepEqual(normalized?.requestDefaults, { + context1m: true, + customFlag: "keep-me", + }); + + const stripped = normalizeProviderSpecificData("anthropic-compatible-cc-demo", { + requestDefaults: { + context1m: "yes", + customFlag: "keep-me", + }, + }); + assert.deepEqual(stripped?.requestDefaults, { + customFlag: "keep-me", + }); +});