From 1e64fbb9cdc988c16eafc530a7226be0184b9907 Mon Sep 17 00:00:00 2001 From: Ross <144740362+ross0x01@users.noreply.github.com> Date: Thu, 13 Aug 2026 16:12:16 +0000 Subject: [PATCH] Add DeepSeek V4 Pro 0813 experiment --- lib/ai/__tests__/providers.test.ts | 22 +++ lib/ai/providers.ts | 12 +- .../chat-stream-helpers-fallback.test.ts | 21 +++ lib/api/chat-handler.ts | 39 ++++++ lib/api/chat-stream-helpers.ts | 11 ++ .../__tests__/deepseek-v4-pro-0813.test.ts | 122 ++++++++++++++++ lib/experiments/deepseek-v4-pro-0813.ts | 132 ++++++++++++++++++ lib/rate-limit/__tests__/token-bucket.test.ts | 10 ++ lib/rate-limit/token-bucket.ts | 2 + trigger/agent-long.ts | 42 +++++- 10 files changed, 410 insertions(+), 3 deletions(-) create mode 100644 lib/experiments/__tests__/deepseek-v4-pro-0813.test.ts create mode 100644 lib/experiments/deepseek-v4-pro-0813.ts diff --git a/lib/ai/__tests__/providers.test.ts b/lib/ai/__tests__/providers.test.ts index 3905bd4a..5bdcb00e 100644 --- a/lib/ai/__tests__/providers.test.ts +++ b/lib/ai/__tests__/providers.test.ts @@ -2,6 +2,7 @@ import { getModelCutoffDate, getModelDisplayName, isAnthropicModel, + isDeepSeekModel, isKimiModel, createTrackedProvider, myProvider, @@ -41,6 +42,13 @@ describe("provider registry", () => { (myProvider.languageModel("model-grok-4.6-pro") as { modelId: string }) .modelId, ).toBe("x-ai/grok-4.6"); + expect( + ( + myProvider.languageModel("model-deepseek-v4-pro-0813") as { + modelId: string; + } + ).modelId, + ).toBe("deepseek/deepseek-v4-pro-0813"); expect( (myProvider.languageModel("model-glm-5.2") as { modelId: string }) .modelId, @@ -72,6 +80,12 @@ describe("provider registry", () => { expect(getModelDisplayName("model-grok-4.5-pro")).toBe("xAI Grok 4.6"); expect(getModelDisplayName("model-grok-4.6-pro")).toBe("xAI Grok 4.6"); expect(getModelCutoffDate("model-grok-4.6-pro")).toBe("August 2026"); + expect(getModelDisplayName("model-deepseek-v4-pro-0813")).toBe( + "DeepSeek V4 Pro 0813", + ); + expect(getModelCutoffDate("model-deepseek-v4-pro-0813")).toBe( + "August 2026", + ); expect(getModelDisplayName("model-glm-5.2")).toBe("Z.ai GLM 5.2"); expect(getModelDisplayName("model-kimi-k3")).toBe("Moonshot Kimi K3"); expect(getModelCutoffDate("model-opus-4.6")).toBe("July 2026"); @@ -87,6 +101,11 @@ describe("provider registry", () => { expect(isAnthropicModel("anthropic/claude-opus-4.6")).toBe(true); }); + it("classifies both DeepSeek V4 Pro experiment routes as DeepSeek", () => { + expect(isDeepSeekModel("model-deepseek-v4-pro")).toBe(true); + expect(isDeepSeekModel("model-deepseek-v4-pro-0813")).toBe(true); + }); + it("keeps tracked free routes split by mode", () => { const provider = createTrackedProvider(); expect( @@ -273,6 +292,9 @@ describe("supportsMultimodalToolResults", () => { it("rejects text-only DeepSeek model keys", () => { expect(supportsMultimodalToolResults("agent-model-free")).toBe(false); expect(supportsMultimodalToolResults("model-deepseek-v4-pro")).toBe(false); + expect(supportsMultimodalToolResults("model-deepseek-v4-pro-0813")).toBe( + false, + ); }); it.each([ diff --git a/lib/ai/providers.ts b/lib/ai/providers.ts index c07a067f..177101bb 100644 --- a/lib/ai/providers.ts +++ b/lib/ai/providers.ts @@ -182,6 +182,8 @@ type OpenRouterInstance = typeof openrouter; export const KIMI_K3_SLUG = "moonshotai/kimi-k3"; export const GLM_5_2_SLUG = "z-ai/glm-5.2"; export const GROK_4_6_SLUG = "x-ai/grok-4.6"; +export const DEEPSEEK_V4_PRO_SLUG = "deepseek/deepseek-v4-pro"; +export const DEEPSEEK_V4_PRO_0813_SLUG = "deepseek/deepseek-v4-pro-0813"; export const DEEPSEEK_V4_FLASH_SLUG = "deepseek/deepseek-v4-flash-0731"; export const DEEPSEEK_V4_FLASH_PREVIOUS_SLUG = "deepseek/deepseek-v4-flash"; const TITLE_GENERATOR_DEEPSEEK_SLUG = "deepseek/deepseek-v4-flash"; @@ -209,7 +211,10 @@ const buildProviderMap = ( "model-grok-4.5": or(GROK_4_6_SLUG), "model-grok-4.5-pro": or(GROK_4_6_SLUG), "model-grok-4.6-pro": or(GROK_4_6_SLUG), - "model-deepseek-v4-pro": or("deepseek/deepseek-v4-pro"), + "model-deepseek-v4-pro": or(DEEPSEEK_V4_PRO_SLUG), + // HAC-68 treatment alias. Auto and Standard keep their persisted tier + // while the server-side experiment chooses the provider route per user. + "model-deepseek-v4-pro-0813": or(DEEPSEEK_V4_PRO_0813_SLUG), // Keep the persisted Max compatibility key while routing new requests to // Kimi K3. Renaming the key would invalidate existing stored selections. "model-opus-4.6": or(KIMI_K3_SLUG), @@ -236,6 +241,7 @@ export const modelCutoffDates: Partial> & "model-grok-4.5-pro": "August 2026", "model-grok-4.6-pro": "August 2026", "model-deepseek-v4-pro": "May 2025", + "model-deepseek-v4-pro-0813": "August 2026", "model-opus-4.6": "July 2026", "model-glm-5.2": "June 2026", "fallback-agent-model": "August 2026", @@ -255,6 +261,7 @@ export const modelDisplayNames: Record & "model-grok-4.5-pro": "xAI Grok 4.6", "model-grok-4.6-pro": "xAI Grok 4.6", "model-deepseek-v4-pro": "DeepSeek V4 Pro", + "model-deepseek-v4-pro-0813": "DeepSeek V4 Pro 0813", "model-opus-4.6": "Moonshot Kimi K3", "model-glm-5.2": "Z.ai GLM 5.2", "model-kimi-k3": "Moonshot Kimi K3", @@ -283,7 +290,8 @@ export function isDeepSeekModel(modelName: string): boolean { return ( modelName === "ask-model-free" || modelName === "agent-model-free" || - modelName === "model-deepseek-v4-pro" + modelName === "model-deepseek-v4-pro" || + modelName === "model-deepseek-v4-pro-0813" ); } diff --git a/lib/api/__tests__/chat-stream-helpers-fallback.test.ts b/lib/api/__tests__/chat-stream-helpers-fallback.test.ts index 8b062bf9..a1fea68f 100644 --- a/lib/api/__tests__/chat-stream-helpers-fallback.test.ts +++ b/lib/api/__tests__/chat-stream-helpers-fallback.test.ts @@ -32,6 +32,7 @@ const DEEPSEEK_FLASH_CANONICAL_SLUG = "deepseek/deepseek-v4-flash-20260731"; const DEEPSEEK_FLASH_PREVIOUS_SLUG = "deepseek/deepseek-v4-flash"; const DEEPSEEK_FLASH_PREVIOUS_CANONICAL_SLUG = "deepseek/deepseek-v4-flash-20260423"; +const DEEPSEEK_V4_PRO_SLUG = "deepseek/deepseek-v4-pro"; const MEDIUM_GROK_PRIMARY_MODELS = [ "ask-model", "agent-model", @@ -43,6 +44,7 @@ const HIGH_GROK_PRIMARY_OR_FALLBACK_MODELS = [ "model-grok-4.5-pro", "model-grok-4.6-pro", "model-deepseek-v4-pro", + "model-deepseek-v4-pro-0813", "model-opus-4.6", "model-glm-5.2", "model-kimi-k3", @@ -288,6 +290,19 @@ describe("buildProviderOptions fallback chain", () => { }); }); + it("falls back from DeepSeek V4 Pro 0813 through the control route, Grok, then Kimi K3", () => { + const opts = buildProviderOptions( + false, + "user-1", + "model-deepseek-v4-pro-0813", + "ask", + ); + expect(opts.openrouter).toMatchObject({ + models: [DEEPSEEK_V4_PRO_SLUG, GROK_SLUG, KIMI_K3_SLUG], + user: "user-1", + }); + }); + it("falls back from the Grok-backed paid Ask image route to Kimi K3", () => { const opts = buildProviderOptions(false, "user-1", "ask-model", "ask"); expect(opts.openrouter).toMatchObject({ @@ -632,6 +647,12 @@ describe("getRetryFallbackModel", () => { ); }); + it("retries the DeepSeek 0813 treatment with the current DeepSeek route", () => { + expect(getRetryFallbackModel("model-deepseek-v4-pro-0813", "ask")).toBe( + "model-deepseek-v4-pro", + ); + }); + it("retries Kimi K3 with the active Grok route", () => { expect(getRetryFallbackModel("model-kimi-k3", "agent")).toBe( "model-grok-4.6", diff --git a/lib/api/chat-handler.ts b/lib/api/chat-handler.ts index 788e3f68..f10c8d81 100644 --- a/lib/api/chat-handler.ts +++ b/lib/api/chat-handler.ts @@ -146,6 +146,12 @@ import { phLogger } from "@/lib/posthog/server"; import { PAID_FUNNEL_EVENTS } from "@/lib/analytics/paid-funnel"; import { readAnalyticsRequestContext } from "@/lib/analytics/request-context"; import { buildAgentStepLimitTelemetry } from "@/lib/analytics/agent-step-limit-telemetry"; +import { + captureDeepSeekV4Pro0813ExperimentExposure, + evaluateDeepSeekV4Pro0813Experiment, + getActiveDeepSeekV4Pro0813ExperimentAssignment, + getDeepSeekV4Pro0813ExperimentContext, +} from "@/lib/experiments/deepseek-v4-pro-0813"; import { capturePaidDailyFreeAllowanceServerEvent, createPaidDailyFreeAllowanceBudgetSnapshot, @@ -424,6 +430,17 @@ export const createChatHandler = () => { ); } + const deepSeekV4Pro0813Experiment = + await evaluateDeepSeekV4Pro0813Experiment({ + posthog: (posthog ??= PostHogClient()), + userId, + selectedModel, + requestId: req.headers.get("x-vercel-id") ?? undefined, + }); + if (deepSeekV4Pro0813Experiment) { + selectedModel = deepSeekV4Pro0813Experiment.modelKey; + } + const notesEnabled = (subscription !== "free" || isAgentMode(mode)) && (userCustomization?.include_notes ?? true); @@ -559,6 +576,15 @@ export const createChatHandler = () => { }); } + const activeDeepSeekV4Pro0813Experiment = + getActiveDeepSeekV4Pro0813ExperimentAssignment( + deepSeekV4Pro0813Experiment, + selectedModel, + ); + const routingExperimentContext = getDeepSeekV4Pro0813ExperimentContext( + activeDeepSeekV4Pro0813Experiment, + ); + const freeMonthlyBudgetSnapshot = subscription === "free" ? await checkFreeMonthlyCostLimit(freeUsageSubject) @@ -1110,6 +1136,7 @@ export const createChatHandler = () => { usage: usageCostRecord, responseModel: state.responseModel, analyticsRequestContext, + experiment: routingExperimentContext, fallbackServed: state.responseModel && retryUsedFallbackModel ? true @@ -1315,6 +1342,16 @@ export const createChatHandler = () => { let result; try { + captureDeepSeekV4Pro0813ExperimentExposure({ + posthog, + userId, + subscription, + mode, + selectedModelOverride, + selectedModel, + configuredModel: configuredModelId, + assignment: activeDeepSeekV4Pro0813Experiment, + }); result = await createStream(selectedModel); } catch (error) { // If provider returns an API error before streaming, retry with fallback. @@ -1678,6 +1715,7 @@ export const createChatHandler = () => { finishReason: state.streamFinishReason, budgetAbortDetails: state.budgetAbortDetails, isAutoContinue: !!isAutoContinue, + experiment: routingExperimentContext, stepLimitTelemetry: buildAgentStepLimitTelemetry({ configuredMaxSteps: @@ -1993,6 +2031,7 @@ export const createChatHandler = () => { finishReason: state.streamFinishReason, budgetAbortDetails: state.budgetAbortDetails, isAutoContinue: !!isAutoContinue, + experiment: routingExperimentContext, stepLimitTelemetry: buildAgentStepLimitTelemetry({ configuredMaxSteps: state.configuredMaxSteps, stepCount: state.agentStepCount, diff --git a/lib/api/chat-stream-helpers.ts b/lib/api/chat-stream-helpers.ts index 5fa03435..a8343d55 100644 --- a/lib/api/chat-stream-helpers.ts +++ b/lib/api/chat-stream-helpers.ts @@ -541,6 +541,13 @@ const AGENT_TEXT_FALLBACK_CHAIN = [ "model-kimi-k3", ] as const satisfies readonly ModelName[]; +// HAC-68 treatment falls back through today's DeepSeek route before the +// existing Grok/Kimi recovery chain. +const DEEPSEEK_V4_PRO_0813_FALLBACK_CHAIN = [ + "model-deepseek-v4-pro", + ...AGENT_TEXT_FALLBACK_CHAIN, +] as const satisfies readonly ModelName[]; + // HackerAI Pro uses Grok 4.6 for every request. GLM 5.2 remains its first // fallback, followed by Kimi K3 so media requests still have a multimodal final // recovery path if both primary providers are unavailable. @@ -553,6 +560,7 @@ const MODEL_FALLBACK_CHAIN: Partial> = { "ask-model-free": AGENT_TEXT_FALLBACK_CHAIN, "agent-model-free": AGENT_TEXT_FALLBACK_CHAIN, "model-deepseek-v4-pro": AGENT_TEXT_FALLBACK_CHAIN, + "model-deepseek-v4-pro-0813": DEEPSEEK_V4_PRO_0813_FALLBACK_CHAIN, "ask-model": GROK_4_6_FALLBACK_CHAIN, "agent-model": GROK_4_6_FALLBACK_CHAIN, "model-grok-4.6": GROK_4_6_FALLBACK_CHAIN, @@ -642,6 +650,9 @@ export function getRetryFallbackModel( modelName: ModelName, _mode: ChatMode, ): ModelName { + if (modelName === "model-deepseek-v4-pro-0813") { + return "model-deepseek-v4-pro"; + } if ( modelName === "model-grok-4.6-pro" || modelName === "model-grok-4.5-pro" diff --git a/lib/experiments/__tests__/deepseek-v4-pro-0813.test.ts b/lib/experiments/__tests__/deepseek-v4-pro-0813.test.ts new file mode 100644 index 00000000..7b9e1b2b --- /dev/null +++ b/lib/experiments/__tests__/deepseek-v4-pro-0813.test.ts @@ -0,0 +1,122 @@ +import { + captureDeepSeekV4Pro0813ExperimentExposure, + DEEPSEEK_V4_PRO_0813_EXPERIMENT_KEY, + DEEPSEEK_V4_PRO_0813_EXPOSURE_EVENT, + DEEPSEEK_V4_PRO_0813_FEATURE_PROPERTY, + evaluateDeepSeekV4Pro0813Experiment, + getActiveDeepSeekV4Pro0813ExperimentAssignment, + getDeepSeekV4Pro0813ExperimentContext, + isEligibleForDeepSeekV4Pro0813Experiment, +} from "@/lib/experiments/deepseek-v4-pro-0813"; + +describe("DeepSeek V4 Pro 0813 experiment", () => { + it("only evaluates requests already resolved to the current DeepSeek V4 Pro route", () => { + expect( + isEligibleForDeepSeekV4Pro0813Experiment("model-deepseek-v4-pro"), + ).toBe(true); + expect(isEligibleForDeepSeekV4Pro0813Experiment("model-grok-4.5")).toBe( + false, + ); + expect(isEligibleForDeepSeekV4Pro0813Experiment("agent-model-free")).toBe( + false, + ); + }); + + it.each([ + ["control", "model-deepseek-v4-pro"], + ["test", "model-deepseek-v4-pro-0813"], + ] as const)("maps %s to %s", async (variant, modelKey) => { + const evaluateFlags = jest.fn(async () => ({ getFlag: () => variant })); + + await expect( + evaluateDeepSeekV4Pro0813Experiment({ + posthog: { evaluateFlags } as never, + userId: "user-1", + selectedModel: "model-deepseek-v4-pro", + }), + ).resolves.toEqual({ + key: DEEPSEEK_V4_PRO_0813_EXPERIMENT_KEY, + variant, + modelKey, + }); + }); + + it("does not evaluate ineligible routes", async () => { + const evaluateFlags = jest.fn(); + + await expect( + evaluateDeepSeekV4Pro0813Experiment({ + posthog: { evaluateFlags } as never, + userId: "user-1", + selectedModel: "model-grok-4.5", + }), + ).resolves.toBeUndefined(); + expect(evaluateFlags).not.toHaveBeenCalled(); + }); + + it("fails closed when evaluation returns an unknown value", async () => { + await expect( + evaluateDeepSeekV4Pro0813Experiment({ + posthog: { + evaluateFlags: jest.fn(async () => ({ getFlag: () => true })), + } as never, + userId: "user-1", + selectedModel: "model-deepseek-v4-pro", + }), + ).resolves.toBeUndefined(); + }); + + it("captures a privacy-safe custom exposure from the provider surface", () => { + const capture = jest.fn(); + const assignment = { + key: DEEPSEEK_V4_PRO_0813_EXPERIMENT_KEY, + variant: "test" as const, + modelKey: "model-deepseek-v4-pro-0813" as const, + }; + + captureDeepSeekV4Pro0813ExperimentExposure({ + posthog: { capture } as never, + userId: "user-1", + subscription: "pro", + mode: "agent", + selectedModelOverride: "hackerai-standard", + selectedModel: assignment.modelKey, + configuredModel: "deepseek/deepseek-v4-pro-0813", + assignment, + }); + + expect(capture).toHaveBeenCalledWith({ + distinctId: "user-1", + event: DEEPSEEK_V4_PRO_0813_EXPOSURE_EVENT, + properties: { + experiment_key: DEEPSEEK_V4_PRO_0813_EXPERIMENT_KEY, + experiment_variant: "test", + [DEEPSEEK_V4_PRO_0813_FEATURE_PROPERTY]: "test", + subscription: "pro", + subscription_tier: "pro", + mode: "agent", + selected_model: "model-deepseek-v4-pro-0813", + selected_model_override: "hackerai-standard", + configured_model: "deepseek/deepseek-v4-pro-0813", + exposure_surface: "provider_request", + $process_person_profile: false, + }, + }); + expect(getDeepSeekV4Pro0813ExperimentContext(assignment)).toEqual({ + key: DEEPSEEK_V4_PRO_0813_EXPERIMENT_KEY, + variant: "test", + }); + expect( + getActiveDeepSeekV4Pro0813ExperimentAssignment( + assignment, + "model-deepseek-v4-pro-0813", + ), + ).toBe(assignment); + expect( + getActiveDeepSeekV4Pro0813ExperimentAssignment( + assignment, + "model-grok-4.5", + ), + ).toBeUndefined(); + }); +}); diff --git a/lib/experiments/deepseek-v4-pro-0813.ts b/lib/experiments/deepseek-v4-pro-0813.ts new file mode 100644 index 00000000..424aadfc --- /dev/null +++ b/lib/experiments/deepseek-v4-pro-0813.ts @@ -0,0 +1,132 @@ +import type { PostHog } from "posthog-node"; +import type { ExperimentAnalyticsContext } from "@/lib/analytics/experiment-context"; +import type { ModelName } from "@/lib/ai/providers"; +import type { ChatMode, SelectedModel, SubscriptionTier } from "@/types"; + +export const DEEPSEEK_V4_PRO_0813_EXPERIMENT_KEY = + "deepseek_v4_pro_0813_model_v1"; +export const DEEPSEEK_V4_PRO_0813_EXPOSURE_EVENT = + "hac68_deepseek_v4_pro_0813_experiment_exposed"; +export const DEEPSEEK_V4_PRO_0813_FEATURE_PROPERTY = `$feature/${DEEPSEEK_V4_PRO_0813_EXPERIMENT_KEY}`; + +export type DeepSeekV4Pro0813ExperimentVariant = "control" | "test"; +export type DeepSeekV4Pro0813ModelKey = + "model-deepseek-v4-pro" | "model-deepseek-v4-pro-0813"; + +export type DeepSeekV4Pro0813ExperimentAssignment = { + key: typeof DEEPSEEK_V4_PRO_0813_EXPERIMENT_KEY; + variant: DeepSeekV4Pro0813ExperimentVariant; + modelKey: DeepSeekV4Pro0813ModelKey; +}; + +export function isEligibleForDeepSeekV4Pro0813Experiment( + selectedModel: ModelName, +): selectedModel is "model-deepseek-v4-pro" { + return selectedModel === "model-deepseek-v4-pro"; +} + +export async function evaluateDeepSeekV4Pro0813Experiment({ + posthog, + userId, + selectedModel, + requestId, +}: { + posthog: Pick | null; + userId: string; + selectedModel: ModelName; + requestId?: string; +}): Promise { + if (!posthog || !isEligibleForDeepSeekV4Pro0813Experiment(selectedModel)) { + return undefined; + } + + try { + const flags = await posthog.evaluateFlags(userId, { + flagKeys: [DEEPSEEK_V4_PRO_0813_EXPERIMENT_KEY], + }); + const variant = flags.getFlag(DEEPSEEK_V4_PRO_0813_EXPERIMENT_KEY); + + if (variant !== "control" && variant !== "test") { + return undefined; + } + + return { + key: DEEPSEEK_V4_PRO_0813_EXPERIMENT_KEY, + variant, + modelKey: + variant === "test" + ? "model-deepseek-v4-pro-0813" + : "model-deepseek-v4-pro", + }; + } catch (error) { + console.warn( + JSON.stringify({ + timestamp: new Date().toISOString(), + level: "warn", + event: "deepseek_v4_pro_0813_experiment_evaluation_failed", + service: "model-routing-experiment", + environment: + process.env.VERCEL_ENV ?? process.env.NODE_ENV ?? "unknown", + request_id: requestId ?? "unavailable", + user_id: userId, + flag_key: DEEPSEEK_V4_PRO_0813_EXPERIMENT_KEY, + error_name: error instanceof Error ? error.name : "UnknownError", + }), + ); + return undefined; + } +} + +export function captureDeepSeekV4Pro0813ExperimentExposure({ + posthog, + userId, + subscription, + mode, + selectedModelOverride, + selectedModel, + configuredModel, + assignment, +}: { + posthog: Pick | null; + userId: string; + subscription: SubscriptionTier; + mode: ChatMode; + selectedModelOverride?: SelectedModel; + selectedModel: ModelName; + configuredModel: string; + assignment?: DeepSeekV4Pro0813ExperimentAssignment; +}): void { + if (!posthog || !assignment) return; + + posthog.capture({ + distinctId: userId, + event: DEEPSEEK_V4_PRO_0813_EXPOSURE_EVENT, + properties: { + experiment_key: assignment.key, + experiment_variant: assignment.variant, + [DEEPSEEK_V4_PRO_0813_FEATURE_PROPERTY]: assignment.variant, + subscription, + subscription_tier: subscription, + mode, + selected_model: selectedModel, + selected_model_override: selectedModelOverride ?? "auto", + configured_model: configuredModel, + exposure_surface: "provider_request", + $process_person_profile: false, + }, + }); +} + +export function getDeepSeekV4Pro0813ExperimentContext( + assignment: DeepSeekV4Pro0813ExperimentAssignment | undefined, +): ExperimentAnalyticsContext | undefined { + if (!assignment) return undefined; + return { key: assignment.key, variant: assignment.variant }; +} + +export function getActiveDeepSeekV4Pro0813ExperimentAssignment( + assignment: DeepSeekV4Pro0813ExperimentAssignment | undefined, + selectedModel: ModelName, +): DeepSeekV4Pro0813ExperimentAssignment | undefined { + return assignment?.modelKey === selectedModel ? assignment : undefined; +} diff --git a/lib/rate-limit/__tests__/token-bucket.test.ts b/lib/rate-limit/__tests__/token-bucket.test.ts index 32919272..5ab6dcbe 100644 --- a/lib/rate-limit/__tests__/token-bucket.test.ts +++ b/lib/rate-limit/__tests__/token-bucket.test.ts @@ -482,6 +482,16 @@ describe("token-bucket", () => { expect( calculateTokenCost(1_000_000, "output", "model-deepseek-v4-pro"), ).toBe(13050); + expect( + calculateTokenCost(1_000_000, "input", "model-deepseek-v4-pro-0813"), + ).toBe(6525); + expect( + calculateTokenCost( + 1_000_000, + "output", + "deepseek/deepseek-v4-pro-0813", + ), + ).toBe(13050); }); it("should use GLM 5.2 baseline pricing ($0.76/$2.42)", () => { diff --git a/lib/rate-limit/token-bucket.ts b/lib/rate-limit/token-bucket.ts index 2b0f6e45..dffa8fb7 100644 --- a/lib/rate-limit/token-bucket.ts +++ b/lib/rate-limit/token-bucket.ts @@ -107,6 +107,7 @@ const MODEL_PRICING_MAP: Record = { "ask-model-free": DEEPSEEK_V4_FLASH_0731_PRICING, "agent-model-free": DEEPSEEK_V4_FLASH_0731_PRICING, "model-deepseek-v4-pro": DEEPSEEK_V4_PRO_PRICING, + "model-deepseek-v4-pro-0813": DEEPSEEK_V4_PRO_PRICING, // Persisted Max compatibility key; the active provider route is Kimi K3. "model-opus-4.6": KIMI_K3_PRICING, // Baseline OpenRouter rates: $0.76 in / $2.42 out per 1M tokens. @@ -122,6 +123,7 @@ const MODEL_PRICING_MAP: Record = { "deepseek/deepseek-v4-flash-0731": DEEPSEEK_V4_FLASH_0731_PRICING, "deepseek/deepseek-v4-flash-20260731": DEEPSEEK_V4_FLASH_0731_PRICING, "deepseek/deepseek-v4-pro": DEEPSEEK_V4_PRO_PRICING, + "deepseek/deepseek-v4-pro-0813": DEEPSEEK_V4_PRO_PRICING, "anthropic/claude-opus-4.6": OPUS_4_6_PRICING, "z-ai/glm-5.2": GLM_5_2_PRICING, "z-ai/glm-5.2-20260616": GLM_5_2_PRICING, diff --git a/trigger/agent-long.ts b/trigger/agent-long.ts index 83903f90..07c00cf9 100644 --- a/trigger/agent-long.ts +++ b/trigger/agent-long.ts @@ -129,6 +129,12 @@ import { type AgentApiEndpoint, } from "@/lib/api/agent-endpoints"; import { phLogger } from "@/lib/posthog/server"; +import { + captureDeepSeekV4Pro0813ExperimentExposure, + evaluateDeepSeekV4Pro0813Experiment, + getActiveDeepSeekV4Pro0813ExperimentAssignment, + getDeepSeekV4Pro0813ExperimentContext, +} from "@/lib/experiments/deepseek-v4-pro-0813"; import type { AgentAutoReviewAssignment } from "@/lib/experiments/agent-auto-review"; import { PAID_FUNNEL_EVENTS } from "@/lib/analytics/paid-funnel"; import type { AnalyticsRequestContext } from "@/lib/analytics/request-context"; @@ -2273,6 +2279,18 @@ export const agentLongTask = task({ ); } + const posthog = PostHogClient(); + const deepSeekV4Pro0813Experiment = + await evaluateDeepSeekV4Pro0813Experiment({ + posthog, + userId, + selectedModel, + requestId: ctx.run.id, + }); + if (deepSeekV4Pro0813Experiment) { + selectedModel = deepSeekV4Pro0813Experiment.modelKey; + } + const notesEnabled = userCustomization?.include_notes ?? true; const estimatedInputTokens = await estimatePreflightInputTokens({ @@ -2294,7 +2312,6 @@ export const agentLongTask = task({ }; chatLogger.setChat(chatLogContext, selectedModel); - const posthog = PostHogClient(); chatLogger.getBuilder().setAssistantId(assistantMessageId); // Wire trigger.dev's abort signal into a local controller. @@ -2507,6 +2524,16 @@ export const agentLongTask = task({ }); } + const activeDeepSeekV4Pro0813Experiment = + getActiveDeepSeekV4Pro0813ExperimentAssignment( + deepSeekV4Pro0813Experiment, + selectedModel, + ); + const routingExperimentContext = + getDeepSeekV4Pro0813ExperimentContext( + activeDeepSeekV4Pro0813Experiment, + ); + const freeMonthlyBudgetSnapshot = subscription === "free" ? await checkFreeMonthlyCostLimit(freeUsageSubject) @@ -3256,6 +3283,7 @@ export const agentLongTask = task({ mode, agentPermissionMode, analyticsRequestContext, + experiment: routingExperimentContext, usage: usageCostRecord, responseModel: state.responseModel, ...(usageSettlementState && { @@ -3475,6 +3503,16 @@ export const agentLongTask = task({ let result; try { + captureDeepSeekV4Pro0813ExperimentExposure({ + posthog, + userId, + subscription, + mode, + selectedModelOverride, + selectedModel, + configuredModel: configuredModelId, + assignment: activeDeepSeekV4Pro0813Experiment, + }); result = await createStream(selectedModel); } catch (error) { if ( @@ -3818,6 +3856,7 @@ export const agentLongTask = task({ state.budgetAbortDetails, agentPermissionMode, isAutoContinue: !!isAutoContinue, + experiment: routingExperimentContext, stepLimitTelemetry: buildAgentStepLimitTelemetry({ configuredMaxSteps: @@ -3990,6 +4029,7 @@ export const agentLongTask = task({ budgetAbortDetails: state.budgetAbortDetails, agentPermissionMode, isAutoContinue: !!isAutoContinue, + experiment: routingExperimentContext, stepLimitTelemetry: buildAgentStepLimitTelemetry({ configuredMaxSteps: state.configuredMaxSteps, stepCount: state.agentStepCount,