From 623c63baf6665d84f7595e4e4c0dd4d0e2f6d810 Mon Sep 17 00:00:00 2001 From: DavyMassoneto Date: Tue, 10 Mar 2026 23:57:13 -0300 Subject: [PATCH] feat(claude): support [1m] suffix for 1M context window Parse [1m] suffix from model name (e.g. claude-sonnet-4-6[1m]) and propagate extendedContext flag through the request pipeline to append context-1m-2025-08-07 to the Anthropic-Beta header. --- open-sse/executors/base.ts | 26 ++++++++++++++++++++- open-sse/handlers/chatCore.ts | 4 +++- open-sse/services/model.ts | 44 ++++++++++++++++++++++++++++------- src/sse/handlers/chat.ts | 15 +++++++----- src/sse/services/model.ts | 9 ++++++- 5 files changed, 80 insertions(+), 18 deletions(-) diff --git a/open-sse/executors/base.ts b/open-sse/executors/base.ts index 83e1f95f60..52807fb5f4 100644 --- a/open-sse/executors/base.ts +++ b/open-sse/executors/base.ts @@ -40,6 +40,7 @@ export type ExecuteInput = { credentials: ProviderCredentials; signal?: AbortSignal | null; log?: ExecutorLog | null; + extendedContext?: boolean; }; function mergeAbortSignals(primary: AbortSignal, secondary: AbortSignal): AbortSignal { @@ -174,7 +175,7 @@ export class BaseExecutor { return { status: response.status, message: bodyText || `HTTP ${response.status}` }; } - async execute({ model, body, stream, credentials, signal, log }: ExecuteInput) { + async execute({ model, body, stream, credentials, signal, log, extendedContext }: ExecuteInput) { const fallbackCount = this.getFallbackCount(); let lastError: unknown = null; let lastStatus = 0; @@ -182,6 +183,29 @@ export class BaseExecutor { for (let urlIndex = 0; urlIndex < fallbackCount; urlIndex++) { const url = this.buildUrl(model, stream, urlIndex, credentials); const headers = this.buildHeaders(credentials, stream); + + // 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-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 transformedBody = this.transformRequest(model, body, stream, credentials); try { diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index f0175a0b3d..fc45f81748 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -68,7 +68,7 @@ export async function handleChatCore({ userAgent, comboName, }) { - const { provider, model } = modelInfo; + const { provider, model, extendedContext } = modelInfo; const startTime = Date.now(); // ── Phase 9.2: Idempotency check ── @@ -270,6 +270,7 @@ export async function handleChatCore({ credentials, signal: streamController.signal, log, + extendedContext, }) ); @@ -357,6 +358,7 @@ export async function handleChatCore({ credentials, signal: streamController.signal, log, + extendedContext, }); if (retryResult.response.ok) { diff --git a/open-sse/services/model.ts b/open-sse/services/model.ts index 38703bb624..a78c12aa3b 100644 --- a/open-sse/services/model.ts +++ b/open-sse/services/model.ts @@ -59,29 +59,50 @@ function resolveProviderModelAlias(providerOrAlias, modelId) { /** * Parse model string: "alias/model" or "provider/model" or just alias + * Supports [1m] suffix for extended 1M context window (e.g. "claude-sonnet-4-6[1m]") */ export function parseModel(modelStr) { if (!modelStr) { - return { provider: null, model: null, isAlias: false, providerAlias: null }; + return { + provider: null, + model: null, + isAlias: false, + providerAlias: null, + extendedContext: false, + }; } // Sanitize: reject strings with path traversal or control characters if (/\.\.[\/\\]/.test(modelStr) || /[\x00-\x1f]/.test(modelStr)) { console.log(`[MODEL] Warning: rejected malformed model string: "${modelStr.substring(0, 50)}"`); - return { provider: null, model: null, isAlias: false, providerAlias: null }; + return { + provider: null, + model: null, + isAlias: false, + providerAlias: null, + extendedContext: false, + }; + } + + // Extract [1m] suffix before parsing provider/model + let extendedContext = false; + let cleanStr = modelStr; + if (cleanStr.endsWith("[1m]")) { + extendedContext = true; + cleanStr = cleanStr.slice(0, -4); } // Check if standard format: provider/model or alias/model - if (modelStr.includes("/")) { - const firstSlash = modelStr.indexOf("/"); - const providerOrAlias = modelStr.slice(0, firstSlash); - const model = modelStr.slice(firstSlash + 1); + if (cleanStr.includes("/")) { + const firstSlash = cleanStr.indexOf("/"); + const providerOrAlias = cleanStr.slice(0, firstSlash); + const model = cleanStr.slice(firstSlash + 1); const provider = resolveProviderAlias(providerOrAlias); - return { provider, model, isAlias: false, providerAlias: providerOrAlias }; + return { provider, model, isAlias: false, providerAlias: providerOrAlias, extendedContext }; } // Alias format (model alias, not provider alias) - return { provider: null, model: modelStr, isAlias: true, providerAlias: null }; + return { provider: null, model: cleanStr, isAlias: true, providerAlias: null, extendedContext }; } /** @@ -123,12 +144,14 @@ export function resolveModelAliasFromMap(alias, aliases) { */ export async function getModelInfoCore(modelStr, aliasesOrGetter) { const parsed = parseModel(modelStr); + const { extendedContext } = parsed; if (!parsed.isAlias) { const canonicalModel = resolveProviderModelAlias(parsed.provider, parsed.model); return { provider: parsed.provider, model: canonicalModel, + extendedContext, }; } @@ -142,6 +165,7 @@ export async function getModelInfoCore(modelStr, aliasesOrGetter) { return { provider: resolved.provider, model: canonicalModel, + extendedContext, }; } @@ -153,6 +177,7 @@ export async function getModelInfoCore(modelStr, aliasesOrGetter) { return { provider: "openai", model: modelId, + extendedContext, }; } @@ -160,7 +185,7 @@ export async function getModelInfoCore(modelStr, aliasesOrGetter) { if (nonOpenAIProviders.length === 1) { const provider = nonOpenAIProviders[0]; const canonicalModel = resolveProviderModelAlias(provider, modelId); - return { provider, model: canonicalModel }; + return { provider, model: canonicalModel, extendedContext }; } if (nonOpenAIProviders.length > 1) { @@ -182,5 +207,6 @@ export async function getModelInfoCore(modelStr, aliasesOrGetter) { return { provider: "openai", model: modelId, + extendedContext, }; } diff --git a/src/sse/handlers/chat.ts b/src/sse/handlers/chat.ts index 905e1cb6d9..2c1a28f7f4 100644 --- a/src/sse/handlers/chat.ts +++ b/src/sse/handlers/chat.ts @@ -227,7 +227,7 @@ async function handleSingleModelChat( const resolved = await resolveModelOrError(modelStr, body); if (resolved.error) return resolved.error; - const { provider, model, sourceFormat, targetFormat } = resolved; + const { provider, model, sourceFormat, targetFormat, extendedContext } = resolved; // 2. Pipeline gates (availability + circuit breaker) const gate = checkPipelineGates(provider, model); @@ -290,6 +290,7 @@ async function handleSingleModelChat( apiKeyInfo, userAgent, comboName, + extendedContext, }); if (telemetry) telemetry.endPhase(); @@ -366,7 +367,7 @@ async function resolveModelOrError(modelStr: string, body: any) { return { error: errorResponse(HTTP_STATUS.BAD_REQUEST, "Invalid model format") }; } - const { provider, model } = modelInfo; + const { provider, model, extendedContext } = modelInfo; const sourceFormat = detectFormat(body); const providerAlias = PROVIDER_ID_TO_ALIAS[provider] || provider; @@ -378,13 +379,14 @@ async function resolveModelOrError(modelStr: string, body: any) { log.info("ROUTING", `Custom model apiFormat=responses → targetFormat=openai-responses`); } + const ctxTag = extendedContext && providerAlias === "claude" ? " [1m]" : ""; if (modelStr !== `${provider}/${model}`) { - log.info("ROUTING", `${modelStr} → ${provider}/${model}`); + log.info("ROUTING", `${modelStr} → ${provider}/${model}${ctxTag}`); } else { - log.info("ROUTING", `Provider: ${provider}, Model: ${model}`); + log.info("ROUTING", `Provider: ${provider}, Model: ${model}${ctxTag}`); } - return { provider, model, sourceFormat, targetFormat }; + return { provider, model, sourceFormat, targetFormat, extendedContext }; } /** @@ -437,6 +439,7 @@ async function executeChatWithBreaker({ apiKeyInfo, userAgent, comboName, + extendedContext, }: any): Promise<{ result: any; tlsFingerprintUsed: boolean }> { let tlsFingerprintUsed = false; @@ -445,7 +448,7 @@ async function executeChatWithBreaker({ runWithProxyContext(proxyInfo?.proxy || null, () => (handleChatCore as any)({ body: { ...body, model: `${provider}/${model}` }, - modelInfo: { provider, model }, + modelInfo: { provider, model, extendedContext }, credentials: refreshedCredentials, log: logger, clientRawRequest, diff --git a/src/sse/services/model.ts b/src/sse/services/model.ts index 9b41c6077d..5f540e539d 100644 --- a/src/sse/services/model.ts +++ b/src/sse/services/model.ts @@ -39,6 +39,7 @@ async function lookupCustomModelApiFormat( */ export async function getModelInfo(modelStr) { const parsed = parseModel(modelStr); + const { extendedContext } = parsed; // Check custom provider nodes first (for both alias and non-alias formats) if (parsed.providerAlias || parsed.provider) { @@ -53,7 +54,12 @@ export async function getModelInfo(modelStr) { matchedOpenAI.id as string, parsed.model as string ); - return { provider: matchedOpenAI.id, model: parsed.model, ...(apiFormat && { apiFormat }) }; + return { + provider: matchedOpenAI.id, + model: parsed.model, + extendedContext, + ...(apiFormat && { apiFormat }), + }; } // Check Anthropic Compatible nodes @@ -67,6 +73,7 @@ export async function getModelInfo(modelStr) { return { provider: matchedAnthropic.id, model: parsed.model, + extendedContext, ...(apiFormat && { apiFormat }), }; }