Skip to content

Commit 888e121

Browse files
committed
Make Lite into Free mode!
1 parent 669b925 commit 888e121

File tree

29 files changed

+188
-60
lines changed

29 files changed

+188
-60
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/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-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: 4 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

@@ -148,6 +149,7 @@ function parseArgs(): ParsedArgs {
148149
}
149150

150151
async function main(): Promise<void> {
152+
console.log()
151153
// Run OSC theme detection BEFORE anything else.
152154
// This MUST happen before OpenTUI starts because OSC responses come through stdin,
153155
// and OpenTUI also listens to stdin. Running detection here ensures stdin is clean.

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 all agents 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'>

cli/src/utils/create-run-config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export type CreateRunConfigParams = {
2323
agentDefinitions: AgentDefinition[]
2424
eventHandlerState: EventHandlerState
2525
signal: AbortSignal
26+
costMode?: 'free' | 'normal' | 'max' | 'experimental' | 'ask'
2627
}
2728

2829
const SENSITIVE_EXTENSIONS = new Set([
@@ -98,6 +99,7 @@ export const createRunConfig = (params: CreateRunConfigParams) => {
9899
previousRunState,
99100
agentDefinitions,
100101
eventHandlerState,
102+
costMode,
101103
} = params
102104

103105
return {
@@ -111,6 +113,7 @@ export const createRunConfig = (params: CreateRunConfigParams) => {
111113
handleStreamChunk: createStreamChunkHandler(eventHandlerState),
112114
handleEvent: createEventHandler(eventHandlerState),
113115
signal: params.signal,
116+
costMode,
114117
fileFilter: ((filePath: string) => {
115118
if (isSensitiveFile(filePath)) return { status: 'blocked' }
116119
if (isEnvTemplateFile(filePath)) return { status: 'allow-example' }

cli/src/utils/message-updater.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ChatMessage, ContentBlock } from '../types/chat'
1+
import type { ChatMessage, ContentBlock, TextContentBlock } from '../types/chat'
22

33
// Small wrapper to avoid repeating the ai-message map/update pattern.
44
export type SetMessagesFn = (
@@ -57,9 +57,25 @@ export const createMessageUpdater = (
5757
const markComplete = (metadata?: Partial<ChatMessage>) => {
5858
updateAiMessage((msg) => {
5959
const { metadata: messageMetadata, ...rest } = metadata ?? {}
60+
61+
// Mark native reasoning blocks as complete by setting thinkingOpen = false
62+
// This ensures thinking blocks auto-collapse when the message finishes
63+
// Check for thinkingOpen !== false to handle both true (native) and undefined (legacy)
64+
const updatedBlocks = msg.blocks?.map((block) => {
65+
if (
66+
block.type === 'text' &&
67+
(block as TextContentBlock).textType === 'reasoning' &&
68+
(block as TextContentBlock).thinkingOpen !== false
69+
) {
70+
return { ...block, thinkingOpen: false } as ContentBlock
71+
}
72+
return block
73+
})
74+
6075
const nextMessage: ChatMessage = {
6176
...msg,
6277
isComplete: true,
78+
...(updatedBlocks && { blocks: updatedBlocks }),
6379
...rest,
6480
}
6581

@@ -184,9 +200,25 @@ export const createBatchedMessageUpdater = (
184200
prev.map((msg) => {
185201
if (msg.id !== aiMessageId) return msg
186202
const { metadata: messageMetadata, ...rest } = metadata ?? {}
203+
204+
// Mark native reasoning blocks as complete by setting thinkingOpen = false
205+
// This ensures thinking blocks auto-collapse when the message finishes
206+
// Check for thinkingOpen !== false to handle both true (native) and undefined (legacy)
207+
const updatedBlocks = msg.blocks?.map((block) => {
208+
if (
209+
block.type === 'text' &&
210+
(block as TextContentBlock).textType === 'reasoning' &&
211+
(block as TextContentBlock).thinkingOpen !== false
212+
) {
213+
return { ...block, thinkingOpen: false } as ContentBlock
214+
}
215+
return block
216+
})
217+
187218
const nextMessage: ChatMessage = {
188219
...msg,
189220
isComplete: true,
221+
...(updatedBlocks && { blocks: updatedBlocks }),
190222
...rest,
191223
}
192224
if (messageMetadata) {

0 commit comments

Comments
 (0)