Skip to content

Commit 6ff21b4

Browse files
committed
Enforce limited Freebuff models before session gate
1 parent 9bbeaed commit 6ff21b4

2 files changed

Lines changed: 48 additions & 21 deletions

File tree

web/src/app/api/v1/chat/completions/__tests__/completions.test.ts

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -592,10 +592,10 @@ describe('/api/v1/chat/completions POST endpoint', () => {
592592
'x-forwarded-for': '8.8.8.8',
593593
},
594594
body: JSON.stringify({
595-
model: 'minimax/minimax-m2.7',
595+
model: FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID,
596596
stream: false,
597597
codebuff_metadata: {
598-
run_id: 'run-free',
598+
run_id: 'run-free-deepseek-flash',
599599
client_id: 'test-client-id-123',
600600
cost_mode: 'free',
601601
freebuff_instance_id: 'active-instance-123',
@@ -705,6 +705,11 @@ describe('/api/v1/chat/completions POST endpoint', () => {
705705
)
706706

707707
it('limits unknown-location free-mode requests to DeepSeek Flash', async () => {
708+
const checkSessionAdmissible = mock(async () => {
709+
throw new Error(
710+
'limited model enforcement should run before session gate',
711+
)
712+
})
708713
// Use a TEST-NET-1 IP (RFC 5737) that geoip-lite cannot resolve, with
709714
// no cf-ipcountry header. This avoids the dev-only localhost bypass
710715
// (which kicks in when there is no cf-ipcountry AND no/loopback IP).
@@ -738,24 +743,21 @@ describe('/api/v1/chat/completions POST endpoint', () => {
738743
fetch: mockFetch,
739744
insertMessageBigquery: mockInsertMessageBigquery,
740745
loggerWithContext: mockLoggerWithContext,
741-
checkSessionAdmissible: async (params) => {
742-
expect(params.accessTier).toBe('limited')
743-
expect(params.requestedModel).toBe('minimax/minimax-m2.7')
744-
return {
745-
ok: false,
746-
code: 'session_model_mismatch',
747-
message:
748-
'Limited free access is only available with DeepSeek V4 Flash.',
749-
}
750-
},
746+
checkSessionAdmissible,
751747
})
752748

753749
expect(response.status).toBe(409)
754750
const body = await response.json()
755751
expect(body.error).toBe('session_model_mismatch')
752+
expect(checkSessionAdmissible).toHaveBeenCalledTimes(0)
756753
})
757754

758755
it('classifies anonymized Cloudflare country codes as limited access', async () => {
756+
const checkSessionAdmissible = mock(async () => {
757+
throw new Error(
758+
'limited model enforcement should run before session gate',
759+
)
760+
})
759761
const req = new NextRequest(
760762
'http://localhost:3000/api/v1/chat/completions',
761763
{
@@ -787,20 +789,13 @@ describe('/api/v1/chat/completions POST endpoint', () => {
787789
fetch: mockFetch,
788790
insertMessageBigquery: mockInsertMessageBigquery,
789791
loggerWithContext: mockLoggerWithContext,
790-
checkSessionAdmissible: async (params) => {
791-
expect(params.accessTier).toBe('limited')
792-
return {
793-
ok: false,
794-
code: 'session_model_mismatch',
795-
message:
796-
'Limited free access is only available with DeepSeek V4 Flash.',
797-
}
798-
},
792+
checkSessionAdmissible,
799793
})
800794

801795
expect(response.status).toBe(409)
802796
const body = await response.json()
803797
expect(body.error).toBe('session_model_mismatch')
798+
expect(checkSessionAdmissible).toHaveBeenCalledTimes(0)
804799
})
805800

806801
it(

web/src/app/api/v1/chat/completions/_post.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { AnalyticsEvent } from '@codebuff/common/constants/analytics-events'
22
import { BYOK_OPENROUTER_HEADER } from '@codebuff/common/constants/byok'
3+
import {
4+
FREEBUFF_GEMINI_PRO_MODEL_ID,
5+
isFreebuffModelAllowedForAccessTier,
6+
isSupportedFreebuffModelId,
7+
} from '@codebuff/common/constants/freebuff-models'
38
import {
49
isFreebuffGeminiThinkerAgent,
510
isFreebuffRootAgent,
@@ -490,6 +495,33 @@ export async function postChatCompletions(params: {
490495
}
491496
}
492497

498+
if (
499+
isFreeModeRequest &&
500+
freebuffAccessTier === 'limited' &&
501+
(isSupportedFreebuffModelId(typedBody.model) ||
502+
typedBody.model === FREEBUFF_GEMINI_PRO_MODEL_ID) &&
503+
!isFreebuffModelAllowedForAccessTier(typedBody.model, freebuffAccessTier)
504+
) {
505+
trackEvent({
506+
event: AnalyticsEvent.CHAT_COMPLETIONS_VALIDATION_ERROR,
507+
userId,
508+
properties: {
509+
error: 'session_model_mismatch',
510+
model: typedBody.model,
511+
accessTier: freebuffAccessTier,
512+
},
513+
logger,
514+
})
515+
return NextResponse.json(
516+
{
517+
error: 'session_model_mismatch',
518+
message:
519+
'Limited free access is only available with DeepSeek V4 Flash.',
520+
},
521+
{ status: STATUS_BY_GATE_CODE.session_model_mismatch },
522+
)
523+
}
524+
493525
let freeModeSessionGate: SessionGateResult | null = null
494526

495527
// Freebuff waiting-room gate. Usually enforced only when

0 commit comments

Comments
 (0)