Skip to content

Commit da99a07

Browse files
Sync public snapshot from freebuff-private
Source: CodebuffAI/freebuff-private@5dca6af706323362c461f66ce19e1d3f05d9781c
1 parent 8d39e30 commit da99a07

11 files changed

Lines changed: 170 additions & 141 deletions

File tree

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,10 @@ Freebuff includes a curated model catalog. The regular picker currently offers:
3636
| --------------------------- | ----------------------- | ----------------------------------------------------------------- |
3737
| **DeepSeek V4 Flash 07/31** | Full access | The default everywhere in full mode; fast coding and tool use |
3838
| **GPT-5.6 Luna** | Full access | Deep reasoning with native images; one session a day |
39-
| **MiniMax M3** | Full access | Fast responses with image support; unlimited |
4039
| **MiMo 2.5** | Full and limited access | The limited-mode default; balanced performance with image support |
4140
| **DeepSeek V4 Pro** | Full access | Deepest reasoning; one session a day, closed during peak hours |
4241

43-
These limits are **temporary**, and they exist because the providers serving DeepSeek now charge more than free mode can carry. V4 Pro is one session a day and closed during peak hours; GPT-5.6 Luna is one a day; V4 Flash uses your remaining premium sessions; models may serve from a quantized (Q8_0) build. MiMo 2.5 and MiniMax M3 stay unlimited. All of it is intended to be reverted.
42+
These limits are **temporary**, and they exist because the providers serving DeepSeek now charge more than free mode can carry. V4 Pro is one session a day and closed during peak hours; GPT-5.6 Luna is one a day; V4 Flash uses your remaining premium sessions; models may serve from a quantized (Q8_0) build. MiMo 2.5 stays unlimited. All of it is intended to be reverted.
4443

4544
Beyond the regular picker:
4645

README.zh-CN.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,10 @@ Freebuff 提供经过筛选的模型目录。常规模型选择器目前包括
3636
| --------------------------- | -------------- | ------------------------------------------------ |
3737
| **DeepSeek V4 Flash 07/31** | 完整访问 | 完整模式下所有平台的默认模型;快速编程和工具调用 |
3838
| **GPT-5.6 Luna** | 完整访问 | 深度推理,原生支持图像;每天一次会话 |
39-
| **MiniMax M3** | 完整访问 | 快速响应并支持图像;无限使用 |
4039
| **MiMo 2.5** | 完整和受限访问 | 受限模式的默认模型;均衡性能并支持图像 |
4140
| **DeepSeek V4 Pro** | 完整访问 | 推理最深入;每天一次会话,高峰时段停用 |
4241

43-
以下限制是**临时**的,原因是为 DeepSeek 提供服务的供应商收费已超出免费模式可承受的范围。V4 Pro 每天一次会话,并在高峰时段停用;GPT-5.6 Luna 每天一次;V4 Flash 占用你剩余的高级会话;模型可能由量化(Q8_0)版本提供服务。MiMo 2.5 与 MiniMax M3 保持无限使用。这些调整都计划在之后恢复。
42+
以下限制是**临时**的,原因是为 DeepSeek 提供服务的供应商收费已超出免费模式可承受的范围。V4 Pro 每天一次会话,并在高峰时段停用;GPT-5.6 Luna 每天一次;V4 Flash 占用你剩余的高级会话;模型可能由量化(Q8_0)版本提供服务。MiMo 2.5 保持无限使用。这些调整都计划在之后恢复。
4443

4544
常规模型选择器之外:
4645

bun.lock

Lines changed: 27 additions & 53 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cli/src/components/__tests__/freebuff-model-selector.test.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ describe('FreebuffModelSelector tier layout', () => {
133133
expect(frame).not.toContain('for small tasks')
134134
})
135135

136-
test('orders Luna above MiniMax while keeping the saved premium model focused', async () => {
136+
test('orders the premium rows above UNLIMITED, saved model focused', async () => {
137137
useFreebuffSessionStore.getState().setSession({
138138
status: 'none',
139139
accessTier: 'full',
@@ -151,17 +151,14 @@ describe('FreebuffModelSelector tier layout', () => {
151151
const premiumHeaderIndex = frame.indexOf('PREMIUM')
152152
const recommendedModelIndex = frame.indexOf('DeepSeek V4 Flash')
153153
const selectedModelIndex = frame.indexOf('GPT-5.6 Luna')
154-
const minimaxModelIndex = frame.indexOf('MiniMax M3')
155154
const unlimitedHeaderIndex = frame.indexOf('UNLIMITED')
156155

157156
expect(premiumHeaderIndex).toBeGreaterThanOrEqual(0)
158157
expect(recommendedModelIndex).toBeGreaterThan(premiumHeaderIndex)
159158
expect(selectedModelIndex).toBeGreaterThan(recommendedModelIndex)
160-
// M3 crossed into UNLIMITED on 2026-08-20, so it now sorts BELOW that
161-
// header rather than above it — the premium pool is spent on the rows that
162-
// actually cost money.
159+
// MiniMax M3 anchored the tail of this list until it was withdrawn on
160+
// 2026-08-20 and left the picker entirely.
163161
expect(unlimitedHeaderIndex).toBeGreaterThan(selectedModelIndex)
164-
expect(minimaxModelIndex).toBeGreaterThan(unlimitedHeaderIndex)
165162
// The cursor sits on the SAVED pick, not on the recommendation.
166163
expect(frame).toContain('› GPT-5.6 Luna')
167164
expect(frame).not.toContain('› MiniMax M3')
@@ -175,16 +172,18 @@ describe('FreebuffModelSelector tier layout', () => {
175172
// Assert against the real copy rather than a hardcoded fragment, so
176173
// rewording the notice doesn't fail this test for the wrong reason. It must
177174
// still render on ONE line — the width math reserves exactly its length.
175+
// V4 Pro carries the only supersedes notice left: MiniMax M3's went with
176+
// the model on 2026-08-20, and MiMo's went when Flash became premium.
178177
const notice = getFreebuffModelSupersededBy(
179-
FREEBUFF_MINIMAX_M3_MODEL_ID,
178+
FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID,
180179
FREEBUFF_MODELS.map((m) => m.id),
181180
)!.notice
182181
const occurrences = (frame: string) => frame.split(notice).length - 1
183182

184183
// On a superseded model: the nudge appears, once, on that model's card.
185184
useFreebuffModelStore
186185
.getState()
187-
.setSelectedModel(FREEBUFF_MINIMAX_M3_MODEL_ID)
186+
.setSelectedModel(FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID)
188187
const onSuperseded = (await renderSelector()).captureCharFrame()
189188
expect(occurrences(onSuperseded)).toBe(1)
190189
// It names the dated build, which is what the row it steers to is labelled.
@@ -366,7 +365,7 @@ describe('FreebuffModelSelector tier layout', () => {
366365
const frame = (await renderSelector()).captureCharFrame()
367366

368367
// Natively multimodal: the badge is a real capability claim.
369-
expect(rowOf(frame, 'MiniMax M3')).toContain('Images')
368+
expect(rowOf(frame, 'MiMo 2.5')).toContain('Images')
370369
expect(rowOf(frame, 'GPT-5.6 Luna')).toContain('Images')
371370
expect(rowOf(frame, 'MiMo 2.5')).toContain('Images')
372371
// Text-only. They still accept a pasted image (read server-side as a

cli/src/utils/__tests__/settings.test.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@ import { afterEach, describe, expect, spyOn, test } from 'bun:test'
66
import {
77
FALLBACK_FREEBUFF_MODEL_ID,
88
FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID,
9-
FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID,
109
FREEBUFF_GLM_V52_MODEL_ID,
1110
FREEBUFF_MIMO_V25_MODEL_ID,
12-
FREEBUFF_MINIMAX_M3_MODEL_ID,
11+
FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID,
1312
} from '@codebuff/common/constants/freebuff-models'
1413

1514
import * as auth from '../auth'
@@ -49,11 +48,13 @@ describe('freebuff model preference', () => {
4948
)
5049
getConfigDirSpy = spyOn(auth, 'getConfigDir').mockReturnValue(testConfigDir)
5150

52-
// A preference saved before Flash overtook MiniMax M3. Written directly so
51+
// A preference saved on a row that is still superseded. This was MiniMax
52+
// M3 until it was withdrawn on 2026-08-20 — a withdrawn model resolves to
53+
// nothing, so it can no longer demonstrate a MIGRATION. Written directly so
5354
// it has no migration marker, exactly like a real pre-upgrade settings file.
5455
fs.writeFileSync(
5556
path.join(testConfigDir, 'settings.json'),
56-
JSON.stringify({ freebuffModel: FREEBUFF_MINIMAX_M3_MODEL_ID }),
57+
JSON.stringify({ freebuffModel: FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID }),
5758
)
5859
expect(loadFreebuffModelPreference()).toBe(
5960
FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID,
@@ -62,7 +63,7 @@ describe('freebuff model preference', () => {
6263
// Re-picking M3 does NOT make it the standing default again: the next
6364
// session steers back to Flash. Selecting it still works for the session
6465
// the user is in — this only governs what a fresh launch opens on.
65-
saveFreebuffModelPreference(FREEBUFF_MINIMAX_M3_MODEL_ID)
66+
saveFreebuffModelPreference(FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID)
6667
expect(loadFreebuffModelPreference()).toBe(
6768
FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID,
6869
)

common/src/__tests__/freebuff-models.test.ts

Lines changed: 52 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { describe, expect, test } from 'bun:test'
22

3+
import { isFreeModeAllowedAgentModel } from '../constants/free-agents'
34
import {
45
canFreebuffModelSpawnGeminiThinker,
56
DEFAULT_FREEBUFF_MODEL_ID,
@@ -49,6 +50,7 @@ import {
4950
isFreebuffGpt56LunaModelId,
5051
isFreebuffLimitedOfferModelId,
5152
getFreebuffPerModelSessionCap,
53+
freebuffWithdrawnModelMessage,
5254
isFreebuffPausedFreeModelId,
5355
isFreebuffSessionModelAllowedForAccessTier,
5456
isFreebuffSessionModelAvailable,
@@ -163,9 +165,13 @@ describe('freebuff model availability', () => {
163165
)
164166
})
165167

166-
test('only the DeepSeek family is trace-stored in free mode; M3 has no warning', () => {
167-
const m3 = FREEBUFF_MODELS.find((m) => m.id === MINIMAX_M3_MODEL_ID)
168-
expect((m3 as { warning?: string } | undefined)?.warning).toBeUndefined()
168+
test('only the DeepSeek family is trace-stored in free mode', () => {
169+
// MiMo is the non-training row still in the picker; M3 was withdrawn on
170+
// 2026-08-20 and is no longer there to check.
171+
const mimo = FREEBUFF_MODELS.find(
172+
(m) => m.id === FREEBUFF_MIMO_V25_MODEL_ID,
173+
)
174+
expect((mimo as { warning?: string } | undefined)?.warning).toBeUndefined()
169175
// The DeepSeek family discloses AI training and IS stored.
170176
expect(isFreebuffTracedModelId(FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID)).toBe(
171177
true,
@@ -683,35 +689,43 @@ describe('freebuff model availability', () => {
683689
)
684690
})
685691

686-
test('MiniMax M3 is selectable and UNLIMITED, on no daily pool at all', () => {
687-
expect(SUPPORTED_FREEBUFF_MODELS.map((model) => model.id)).toContain(
688-
MINIMAX_M3_MODEL_ID,
689-
)
690-
expect(FREEBUFF_MODELS.map((model) => model.id)).toContain(
691-
MINIMAX_M3_MODEL_ID,
692-
)
693-
expect(getFreebuffModelsForAccessTier('full').map((m) => m.id)).toContain(
692+
test('MiniMax M3 is withdrawn: recognised, refused, served to nobody', () => {
693+
// Withdrawn from free mode entirely on 2026-08-20 after reaching $213/hr.
694+
// Out of every picker and pool...
695+
expect(FREEBUFF_MODELS.map((model) => model.id)).not.toContain(
694696
MINIMAX_M3_MODEL_ID,
695697
)
696-
expect(isFreebuffModelId(MINIMAX_M3_MODEL_ID)).toBe(true)
697-
expect(isSupportedFreebuffModelId(MINIMAX_M3_MODEL_ID)).toBe(true)
698-
// Un-premiumed on 2026-08-20, so the premium pool is spent on the DeepSeek
699-
// rows and Luna rather than on the cheapest one. Both must agree — a row
700-
// dropped from the id list while still flagged `premium` is metered by NO
701-
// pool (FREEBUFF_WEB_STANDARD_MODEL_IDS filters on `!premium`).
698+
expect(isFreebuffModelId(MINIMAX_M3_MODEL_ID)).toBe(false)
702699
expect(isFreebuffPremiumModelId(MINIMAX_M3_MODEL_ID)).toBe(false)
703-
expect(isFreebuffWebPremiumModelId(MINIMAX_M3_MODEL_ID)).toBe(false)
704700
expect(
705-
FREEBUFF_MODELS.find((m) => m.id === MINIMAX_M3_MODEL_ID)?.premium,
701+
isFreebuffSessionModelAllowedForAccessTier(MINIMAX_M3_MODEL_ID, 'full'),
706702
).toBe(false)
707-
expect(
708-
isFreebuffModelAllowedForAccessTier(MINIMAX_M3_MODEL_ID, 'full'),
703+
704+
// ...but still RECOGNISED, which is what separates withdrawing a model from
705+
// breaking the clients that still ask for it. Released binaries keep this id
706+
// in their compiled-in catalog; an unrecognised id can only be refused, and
707+
// that refusal is the #1801 retry loop.
708+
expect(isFreebuffSessionModelId(MINIMAX_M3_MODEL_ID)).toBe(true)
709+
expect(isFreebuffPausedFreeModelId(MINIMAX_M3_MODEL_ID)).toBe(true)
710+
// It is REFUSED, not silently substituted — the user asked for a specific
711+
// model and is told it is gone, with what to use instead. The refusal is
712+
// not session-ending, so the client shows it rather than re-admitting.
713+
expect(freebuffWithdrawnModelMessage(MINIMAX_M3_MODEL_ID)).toContain(
714+
'no longer available in Freebuff',
715+
)
716+
expect(freebuffWithdrawnModelMessage(MINIMAX_M3_MODEL_ID)).toContain(
717+
'DeepSeek V4 Flash',
718+
)
719+
720+
// The AGENT door stays open, and that is not an oversight. Withdrawal is
721+
// enforced at admission, so no NEW session can name the model. Sessions
722+
// admitted before the deploy are still live and hit this allowlist on
723+
// every turn; dropping the row would fail them mid-turn with
724+
// free_mode_invalid_agent_model — the same wedge withdrawal exists to
725+
// avoid. They drain against a door that is already shut in front of them.
726+
expect(
727+
isFreeModeAllowedAgentModel('base2-free-minimax-m3', MINIMAX_M3_MODEL_ID),
709728
).toBe(true)
710-
// Flash leads again as of 2026-08-20: the one-a-day ceiling narrowed to V4
711-
// Pro, so the recommended row is no longer the capped one.
712-
expect(FREEBUFF_MODELS[0]!.id).toBe(FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID)
713-
expect(FREEBUFF_MODELS[1]!.id).toBe(FREEBUFF_GPT_5_6_LUNA_MODEL_ID)
714-
expect(FREEBUFF_MODELS[2]!.id).toBe(MINIMAX_M3_MODEL_ID)
715729
})
716730

717731
test('the recommended default leads FREEBUFF_MODELS, and the fallback is in it', () => {
@@ -991,16 +1005,19 @@ describe('freebuff model availability', () => {
9911005
}
9921006
})
9931007

994-
test('points users off MiniMax M3 to V4 Flash', () => {
995-
// Flash-0731 overtook M3 on 2026-07-31, so M3 carries a notice and a switch
996-
// target rather than being removed — it is still selectable.
1008+
test('a withdrawn model is not offered as anyone else’s switch target', () => {
1009+
// M3 carried a "switch to V4 Flash" nudge until it was withdrawn on
1010+
// 2026-08-20. Now the check runs the other way: the picker offers a
1011+
// one-click switch for whatever a notice names, so naming a withdrawn model
1012+
// would hand users a row the server refuses.
9971013
const all = FREEBUFF_MODELS.map((model) => model.id)
998-
const superseded = getFreebuffModelSupersededBy(MINIMAX_M3_MODEL_ID, all)
999-
expect(superseded?.modelId).toBe(FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID)
1000-
expect(superseded!.notice.length).toBeGreaterThan(0)
1001-
expect(superseded!.actionLabel.length).toBeGreaterThan(0)
1002-
// M3 remains a real, selectable model — this is a nudge, not a retirement.
1003-
expect(all).toContain(MINIMAX_M3_MODEL_ID)
1014+
expect(all).not.toContain(MINIMAX_M3_MODEL_ID)
1015+
for (const id of all) {
1016+
const superseded = getFreebuffModelSupersededBy(id, all)
1017+
if (!superseded) continue
1018+
expect(superseded.modelId).not.toBe(MINIMAX_M3_MODEL_ID)
1019+
expect(all).toContain(superseded.modelId)
1020+
}
10041021
// The recommended default is never itself marked superseded.
10051022
expect(
10061023
getFreebuffModelSupersededBy(DEFAULT_FREEBUFF_MODEL_ID, all),

common/src/constants/free-agents.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,13 @@ export const FREE_MODE_AGENT_MODELS: Record<string, Set<string>> = {
493493
'base2-free-deepseek': new Set([FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID]),
494494
'base2-free-deepseek-flash': new Set([FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID]),
495495
'base2-free-mimo': new Set([FREEBUFF_MIMO_V25_MODEL_ID]),
496+
// M3 was WITHDRAWN on 2026-08-20 (see FREEBUFF_PAUSED_FREE_MODEL_IDS), and
497+
// this entry stays on purpose. Withdrawal is enforced at ADMISSION: no new
498+
// session can be opened for the model. Sessions admitted before the deploy
499+
// are still live, and they reach this allowlist on every turn — deleting the
500+
// row would fail them mid-turn with free_mode_invalid_agent_model, which is
501+
// the same client wedge the withdrawal was shaped to avoid (#1801). Let them
502+
// drain; the door is already shut in front of them.
496503
'base2-free-minimax-m3': new Set([FREEBUFF_MINIMAX_M3_MODEL_ID]),
497504
'base2-free-luna': new Set([FREEBUFF_GPT_5_6_LUNA_MODEL_ID]),
498505
'base2-free-glm': new Set([FREEBUFF_GLM_V52_MODEL_ID]),

0 commit comments

Comments
 (0)