Jelajahi Sumber

feat: add special-attributes module for 1M context window support

- Add CONTEXT_1M_SUPPORTED_MODELS whitelist (claude-sonnet-4-5, claude-sonnet-4)
- Add CONTEXT_1M_BETA_HEADER constant for anthropic-beta header
- Add Context1mPreference type (inherit, force_enable, disabled)
- Add shouldApplyContext1m() logic for preference resolution
- Add clientRequestsContext1m() helper for header detection
- Add tiered pricing constants (200k threshold, 2x input, 1.5x output)
ding113 2 bulan lalu
induk
melakukan
97a531c4bc
1 mengubah file dengan 122 tambahan dan 0 penghapusan
  1. 122 0
      src/lib/special-attributes/index.ts

+ 122 - 0
src/lib/special-attributes/index.ts

@@ -0,0 +1,122 @@
+/**
+ * Special Attributes Module
+ *
+ * Centralized module for managing special features like 1M context window,
+ * extended cache TTL, and other provider-specific capabilities.
+ */
+
+// =============================================================================
+// 1M Context Window Support
+// =============================================================================
+
+/**
+ * Models that support 1M context window
+ * Only Sonnet models support this feature as of 2025-08
+ */
+export const CONTEXT_1M_SUPPORTED_MODELS = [
+  "claude-sonnet-4-5-20250929",
+  "claude-sonnet-4-20250514",
+] as const;
+
+/**
+ * Anthropic beta header for 1M context window
+ */
+export const CONTEXT_1M_BETA_HEADER = "context-1m-2025-08-07";
+
+/**
+ * Context 1M preference types for provider configuration
+ * - 'inherit': Follow client request (default)
+ * - 'force_enable': Force enable 1M context for supported models
+ * - 'disabled': Disable 1M context even if client requests it
+ */
+export type Context1mPreference = "inherit" | "force_enable" | "disabled";
+
+/**
+ * Token threshold for tiered pricing (200k tokens)
+ */
+export const CONTEXT_1M_TOKEN_THRESHOLD = 200000;
+
+/**
+ * Pricing multipliers for tokens exceeding the threshold
+ * - Input: 2x ($3/MTok -> $6/MTok for tokens >200k)
+ * - Output: 1.5x ($15/MTok -> $22.50/MTok for tokens >200k)
+ */
+export const CONTEXT_1M_INPUT_PREMIUM_MULTIPLIER = 2.0;
+export const CONTEXT_1M_OUTPUT_PREMIUM_MULTIPLIER = 1.5;
+
+/**
+ * Check if a model supports 1M context window
+ * Uses partial matching to handle model variants
+ */
+export function isContext1mSupportedModel(model: string): boolean {
+  if (!model) return false;
+  return CONTEXT_1M_SUPPORTED_MODELS.some((supported) => model.includes(supported));
+}
+
+/**
+ * Determine whether to apply 1M context window based on provider preference and client request
+ *
+ * @param preference - Provider's context 1M preference setting
+ * @param model - The model being used (after any redirects)
+ * @param clientRequestedContext1m - Whether client sent context-1m header
+ * @returns Whether to apply 1M context window
+ */
+export function shouldApplyContext1m(
+  preference: Context1mPreference | null | undefined,
+  model: string,
+  clientRequestedContext1m: boolean
+): boolean {
+  // If provider explicitly disables, never apply
+  if (preference === "disabled") {
+    return false;
+  }
+
+  // If provider force enables, apply for supported models
+  if (preference === "force_enable") {
+    return isContext1mSupportedModel(model);
+  }
+
+  // Default (inherit): follow client request for supported models
+  return clientRequestedContext1m && isContext1mSupportedModel(model);
+}
+
+/**
+ * Check if client request includes context-1m header
+ * @param headers - Request headers (Headers object or plain object)
+ */
+export function clientRequestsContext1m(
+  headers: Headers | Record<string, string> | null | undefined
+): boolean {
+  if (!headers) return false;
+
+  let betaHeader: string | null = null;
+
+  if (headers instanceof Headers) {
+    betaHeader = headers.get("anthropic-beta");
+  } else {
+    // Handle plain object (case-insensitive lookup)
+    const key = Object.keys(headers).find((k) => k.toLowerCase() === "anthropic-beta");
+    betaHeader = key ? headers[key] : null;
+  }
+
+  if (!betaHeader) return false;
+
+  return betaHeader.split(",").some((flag) => {
+    const trimmed = flag.trim();
+    return trimmed === CONTEXT_1M_BETA_HEADER || trimmed.startsWith("context-1m-");
+  });
+}
+
+// =============================================================================
+// Extended Cache TTL Support (Reference)
+// =============================================================================
+
+/**
+ * Cache TTL beta header for 1-hour extended caching
+ */
+export const CACHE_1H_BETA_HEADER = "extended-cache-ttl-2025-04-11";
+
+/**
+ * Cache TTL preference types
+ */
+export type CacheTtlPreference = "5m" | "1h" | null;