Skip to content

Commit 840494d

Browse files
authored
Free mode (#420)
1 parent e858700 commit 840494d

File tree

33 files changed

+299
-98
lines changed

33 files changed

+299
-98
lines changed

agents/base2/base2-free.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { createBase2 } from './base2'
2+
3+
const definition = {
4+
...createBase2('free'),
5+
id: 'base2-free',
6+
displayName: 'Buffy the Free Orchestrator',
7+
}
8+
export default definition

agents/base2/base2-lite.ts

Lines changed: 0 additions & 8 deletions
This file was deleted.

agents/base2/base2.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
} from '../types/secret-agent-definition'
88

99
export function createBase2(
10-
mode: 'default' | 'lite' | 'max' | 'fast',
10+
mode: 'default' | 'free' | 'max' | 'fast',
1111
options?: {
1212
hasNoValidation?: boolean
1313
planOnly?: boolean
@@ -22,15 +22,15 @@ export function createBase2(
2222
const isDefault = mode === 'default'
2323
const isFast = mode === 'fast'
2424
const isMax = mode === 'max'
25-
const isLite = mode === 'lite'
25+
const isFree = mode === 'free'
2626

27-
const isOpus = !isLite
27+
const isOpus = !isFree
2828
const isSonnet = false
2929
const isGemini = false
3030

3131
return {
3232
publisher,
33-
model: isLite ? 'x-ai/grok-4.1-fast' : 'anthropic/claude-opus-4.5',
33+
model: isFree ? 'x-ai/grok-4.1-fast' : 'anthropic/claude-opus-4.5',
3434
displayName: 'Buffy the Orchestrator',
3535
spawnerPrompt:
3636
'Advanced base agent that orchestrates planning, editing, and reviewing for complex coding tasks',
@@ -55,7 +55,7 @@ export function createBase2(
5555
'spawn_agents',
5656
'read_files',
5757
'read_subtree',
58-
!isFast && !isLite && 'write_todos',
58+
!isFast && !isFree && 'write_todos',
5959
!isFast && !noAskUser && 'suggest_followups',
6060
'str_replace',
6161
'write_file',
@@ -72,11 +72,11 @@ export function createBase2(
7272
'glob-matcher',
7373
'researcher-web',
7474
'researcher-docs',
75-
isLite ? 'commander-lite' : 'commander',
75+
isFree ? 'commander-lite' : 'commander',
7676
isDefault && 'thinker',
7777
(isDefault || isMax) && ['opus-agent', 'gpt-5-agent'],
7878
isMax && 'thinker-best-of-n-opus',
79-
isLite && 'editor-glm',
79+
isFree && 'editor-glm',
8080
isDefault && 'editor',
8181
isMax && 'editor-multi-prompt',
8282
isDefault && 'code-reviewer',
@@ -133,7 +133,7 @@ Use the spawn_agents tool to spawn specialized agents to help you complete the u
133133
- **Sequence agents properly:** Keep in mind dependencies when spawning different agents. Don't spawn agents in parallel that depend on each other.
134134
${buildArray(
135135
'- Spawn context-gathering agents (file pickers, code-searcher, directory-lister, glob-matcher, and web/docs researchers) before making edits.',
136-
isLite &&
136+
isFree &&
137137
'- Spawn the editor-glm agent to implement the changes after you have gathered all the context you need.',
138138
isDefault &&
139139
'- Spawn the editor agent to implement the changes after you have gathered all the context you need.',
@@ -198,7 +198,7 @@ ${isDefault
198198
? `[ You implement the changes using the editor agent ]`
199199
: isFast
200200
? '[ You implement the changes using the str_replace or write_file tools ]'
201-
: isLite
201+
: isFree
202202
? '[ You implement the changes using the editor-glm agent ]'
203203
: '[ You implement the changes using the editor-multi-prompt agent ]'
204204
}
@@ -248,7 +248,7 @@ ${PLACEHOLDER.GIT_CHANGES_PROMPT}
248248
isFast,
249249
isDefault,
250250
isMax,
251-
isLite,
251+
isFree,
252252
hasNoValidation,
253253
noAskUser,
254254
}),
@@ -260,7 +260,7 @@ ${PLACEHOLDER.GIT_CHANGES_PROMPT}
260260
isMax,
261261
hasNoValidation,
262262
isSonnet,
263-
isLite,
263+
isFree,
264264
noAskUser,
265265
}),
266266

@@ -292,15 +292,15 @@ function buildImplementationInstructionsPrompt({
292292
isFast,
293293
isDefault,
294294
isMax,
295-
isLite,
295+
isFree,
296296
hasNoValidation,
297297
noAskUser,
298298
}: {
299299
isSonnet: boolean
300300
isFast: boolean
301301
isDefault: boolean
302302
isMax: boolean
303-
isLite: boolean
303+
isFree: boolean
304304
hasNoValidation: boolean
305305
noAskUser: boolean
306306
}) {
@@ -320,7 +320,7 @@ ${buildArray(
320320
`- For any task requiring 3+ steps, use the write_todos tool to write out your step-by-step implementation plan. Include ALL of the applicable tasks in the list.${isFast ? '' : ' You should include a step to review the changes after you have implemented the changes.'}:${hasNoValidation ? '' : ' You should include at least one step to validate/test your changes: be specific about whether to typecheck, run tests, run lints, etc.'} You may be able to do reviewing and validation in parallel in the same step. Skip write_todos for simple tasks like quick edits or answering questions.`,
321321
(isDefault || isMax) &&
322322
`- For quick problems, briefly explain your reasoning to the user. If you need to think longer, write your thoughts within the <think> tags. Finally, for complex problems, spawn the thinker agent to help find the best solution. (gpt-5-agent is a last resort for complex problems)`,
323-
isLite &&
323+
isFree &&
324324
'- IMPORTANT: You must spawn the editor-glm agent to implement the changes after you have gathered all the context you need. This agent will do the best job of implementing the changes so you must spawn it for all changes. Do not pass any prompt or params to the editor agent when spawning it. It will make its own best choices of what to do.',
325325
isDefault &&
326326
'- IMPORTANT: You must spawn the editor agent to implement the changes after you have gathered all the context you need. This agent will do the best job of implementing the changes so you must spawn it for all non-trivial changes. Do not pass any prompt or params to the editor agent when spawning it. It will make its own best choices of what to do.',
@@ -347,15 +347,15 @@ function buildImplementationStepPrompt({
347347
isMax,
348348
hasNoValidation,
349349
isSonnet,
350-
isLite,
350+
isFree,
351351
noAskUser,
352352
}: {
353353
isDefault: boolean
354354
isFast: boolean
355355
isMax: boolean
356356
hasNoValidation: boolean
357357
isSonnet: boolean
358-
isLite: boolean
358+
isFree: boolean
359359
noAskUser: boolean
360360
}) {
361361
return buildArray(

cli/src/__tests__/unit/agent-mode-toggle.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ describe('AgentModeToggle - buildExpandedSegments', () => {
1717
for (const mode of modes) {
1818
test(`returns segments with active indicator for ${mode}`, () => {
1919
const segs = buildExpandedSegments(mode)
20-
// 4 mode options (DEFAULT, LITE, MAX, PLAN) + 1 active indicator
20+
// 4 mode options (DEFAULT, FREE, MAX, PLAN) + 1 active indicator
2121
expect(segs.length).toBe(5)
2222

2323
// Current mode is disabled among the choices

cli/src/commands/ads.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { saveSettings, loadSettings } from '../utils/settings'
22
import { getSystemMessage } from '../utils/message-history'
3+
import { useChatStore } from '../state/chat-store'
34
import { logger } from '../utils/logger'
45

56
import type { ChatMessage } from '../types/chat'
@@ -8,7 +9,7 @@ export const handleAdsEnable = (): {
89
postUserMessage: (messages: ChatMessage[]) => ChatMessage[]
910
} => {
1011
logger.info('[gravity] Enabling ads')
11-
12+
1213
saveSettings({ adsEnabled: true })
1314

1415
return {
@@ -34,6 +35,15 @@ export const handleAdsDisable = (): {
3435
}
3536

3637
export const getAdsEnabled = (): boolean => {
38+
// If no mode provided, get it from the store
39+
const mode = useChatStore.getState().agentMode
40+
41+
// In FREE mode, ads are always enabled regardless of saved setting
42+
if (mode === 'FREE') {
43+
return true
44+
}
45+
46+
// Otherwise, use the saved setting
3747
const settings = loadSettings()
3848
return settings.adsEnabled ?? false
3949
}

cli/src/components/blocks/thinking-block.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,10 @@ export const ThinkingBlock = memo(
4242
}
4343
}, [onToggleCollapsed, thinkingId])
4444

45-
// thinkingOpen === true means still streaming
46-
// thinkingOpen === false means explicitly closed with </think> tag
47-
// thinkingOpen === undefined means native reasoning block - complete when message is complete
45+
// thinkingOpen === false means explicitly closed (with </think> tag or message completion)
46+
// Otherwise (true or undefined), completion is determined by message completion
4847
const isThinkingComplete =
49-
firstBlock?.thinkingOpen === false ||
50-
(firstBlock?.thinkingOpen === undefined && isMessageComplete)
48+
firstBlock?.thinkingOpen === false || isMessageComplete
5149

5250
// Hide if no content or no thinkingId (but NOT when thinking is complete)
5351
if (!combinedContent || !thinkingId) {

cli/src/hooks/use-gravity-ad.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,16 @@ export const useGravityAd = (): GravityAdState => {
9999
return
100100
}
101101

102+
// Include mode in request - FREE mode should not grant credits
103+
const agentMode = useChatStore.getState().agentMode
104+
102105
fetch(`${WEBSITE_URL}/api/v1/ads/impression`, {
103106
method: 'POST',
104107
headers: {
105108
'Content-Type': 'application/json',
106109
Authorization: `Bearer ${authToken}`,
107110
},
108-
body: JSON.stringify({ impUrl }),
111+
body: JSON.stringify({ impUrl, mode: agentMode }),
109112
})
110113
.then((res) => res.json())
111114
.then((data) => {

cli/src/hooks/use-send-message.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { setCurrentChatId } from '../project-files'
44
import { createStreamController } from './stream-state'
55
import { useChatStore } from '../state/chat-store'
66
import { getCodebuffClient } from '../utils/codebuff-client'
7-
import { AGENT_MODE_TO_ID } from '../utils/constants'
7+
import { AGENT_MODE_TO_ID, AGENT_MODE_TO_COST_MODE } from '../utils/constants'
88
import { createEventHandlerState } from '../utils/create-event-handler-state'
99
import { createRunConfig } from '../utils/create-run-config'
1010
import { loadAgentDefinitions } from '../utils/local-agent-registry'
@@ -443,6 +443,7 @@ export const useSendMessage = ({
443443
agentDefinitions,
444444
eventHandlerState,
445445
signal: abortController.signal,
446+
costMode: AGENT_MODE_TO_COST_MODE[agentMode],
446447
})
447448

448449
logger.info({ runConfig }, '[send-message] Sending message with sdk run config')

cli/src/index.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ function parseArgs(): ParsedArgs {
114114
'--cwd <directory>',
115115
'Set the working directory (default: current directory)',
116116
)
117-
.option('--lite', 'Start in LITE mode')
117+
.option('--free', 'Start in FREE mode')
118+
.option('--lite', 'Start in FREE mode (deprecated, use --free)')
118119
.option('--max', 'Start in MAX mode')
119120
.option('--plan', 'Start in PLAN mode')
120121
.helpOption('-h, --help', 'Show this help message')
@@ -129,7 +130,7 @@ function parseArgs(): ParsedArgs {
129130

130131
// Determine initial mode from flags (last flag wins if multiple specified)
131132
let initialMode: AgentMode | undefined
132-
if (options.lite) initialMode = 'LITE'
133+
if (options.free || options.lite) initialMode = 'FREE'
133134
if (options.max) initialMode = 'MAX'
134135
if (options.plan) initialMode = 'PLAN'
135136

cli/src/utils/constants.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,21 @@ export const MAIN_AGENT_ID = 'main-agent'
107107
*/
108108
export const AGENT_MODE_TO_ID = {
109109
DEFAULT: 'base2',
110-
LITE: 'base2-lite',
110+
FREE: 'base2-free',
111111
MAX: 'base2-max',
112112
PLAN: 'base2-plan',
113113
} as const
114114

115115
export type AgentMode = keyof typeof AGENT_MODE_TO_ID
116116
export const AGENT_MODES = Object.keys(AGENT_MODE_TO_ID) as AgentMode[]
117+
118+
/**
119+
* Maps CLI agent mode to cost mode for billing.
120+
* FREE mode maps to 'free' cost mode where allowlisted agent+model combos cost 0 credits.
121+
*/
122+
export const AGENT_MODE_TO_COST_MODE = {
123+
DEFAULT: 'normal',
124+
FREE: 'free',
125+
MAX: 'max',
126+
PLAN: 'normal',
127+
} as const satisfies Record<AgentMode, 'free' | 'normal' | 'max' | 'experimental' | 'ask'>

0 commit comments

Comments
 (0)