mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
feat(web): align Claude effort options with Claude Code --effort levels (#731)
The Claude effort selector (New Session config + in-session composer) only offered auto/medium/high/max, missing `low` and `xhigh` — yet `claude --effort` actually accepts low/medium/high/xhigh/max. Add the two missing levels in both places so the selector faithfully mirrors the CLI. Extract the level list + labels into one shared constant (@hapi/protocol: shared/src/effort.ts, mirroring CLAUDE_MODEL_PRESETS) so the two UIs derive from a single source and can't drift again. No backend change: the effort string is free-form end-to-end through to the --effort flag. ultracode is intentionally excluded — it is a TUI-only /effort session setting, not an --effort value (the CLI rejects `--effort ultracode`).
This commit is contained in:
@@ -14,8 +14,10 @@ describe('normalizeClaudeSessionEffort', () => {
|
||||
})
|
||||
|
||||
it('normalizes supported effort values', () => {
|
||||
expect(normalizeClaudeSessionEffort('low')).toBe('low')
|
||||
expect(normalizeClaudeSessionEffort('medium')).toBe('medium')
|
||||
expect(normalizeClaudeSessionEffort('high')).toBe('high')
|
||||
expect(normalizeClaudeSessionEffort('xhigh')).toBe('xhigh')
|
||||
expect(normalizeClaudeSessionEffort('max')).toBe('max')
|
||||
expect(normalizeClaudeSessionEffort(' High ')).toBe('high')
|
||||
})
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { describe, expect, test } from 'bun:test'
|
||||
import { CLAUDE_EFFORT_LABELS, CLAUDE_EFFORT_LEVELS } from './effort'
|
||||
|
||||
describe('Claude effort constants', () => {
|
||||
test('exposes the Claude Code --effort levels in ascending order', () => {
|
||||
expect(CLAUDE_EFFORT_LEVELS).toEqual(['low', 'medium', 'high', 'xhigh', 'max'])
|
||||
})
|
||||
|
||||
test('every CLAUDE_EFFORT_LEVEL has a label', () => {
|
||||
for (const level of CLAUDE_EFFORT_LEVELS) {
|
||||
expect(CLAUDE_EFFORT_LABELS[level]).toBeDefined()
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,12 @@
|
||||
// Effort levels Claude Code's `--effort` flag accepts, in ascending order.
|
||||
// "auto"/null is hapi's sentinel for omitting --effort (model default), not a level here.
|
||||
export const CLAUDE_EFFORT_LABELS = {
|
||||
low: 'Low',
|
||||
medium: 'Medium',
|
||||
high: 'High',
|
||||
xhigh: 'XHigh',
|
||||
max: 'Max'
|
||||
} as const
|
||||
|
||||
export type ClaudeEffortLevel = keyof typeof CLAUDE_EFFORT_LABELS
|
||||
export const CLAUDE_EFFORT_LEVELS = Object.keys(CLAUDE_EFFORT_LABELS) as ClaudeEffortLevel[]
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from './apiTypes'
|
||||
export * from './messages'
|
||||
export * from './buildInfo'
|
||||
export * from './effort'
|
||||
export * from './flavors'
|
||||
export * from './models'
|
||||
export * from './modes'
|
||||
|
||||
@@ -6,8 +6,10 @@ describe('getClaudeComposerEffortOptions', () => {
|
||||
expect(getClaudeComposerEffortOptions('ultra')).toEqual([
|
||||
{ value: null, label: 'Auto' },
|
||||
{ value: 'ultra', label: 'Ultra' },
|
||||
{ value: 'low', label: 'Low' },
|
||||
{ value: 'medium', label: 'Medium' },
|
||||
{ value: 'high', label: 'High' },
|
||||
{ value: 'xhigh', label: 'XHigh' },
|
||||
{ value: 'max', label: 'Max' },
|
||||
])
|
||||
})
|
||||
@@ -15,8 +17,10 @@ describe('getClaudeComposerEffortOptions', () => {
|
||||
it('does not duplicate preset Claude effort values', () => {
|
||||
expect(getClaudeComposerEffortOptions('high')).toEqual([
|
||||
{ value: null, label: 'Auto' },
|
||||
{ value: 'low', label: 'Low' },
|
||||
{ value: 'medium', label: 'Medium' },
|
||||
{ value: 'high', label: 'High' },
|
||||
{ value: 'xhigh', label: 'XHigh' },
|
||||
{ value: 'max', label: 'Max' },
|
||||
])
|
||||
})
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import { CLAUDE_EFFORT_LABELS, CLAUDE_EFFORT_LEVELS, type ClaudeEffortLevel } from '@hapi/protocol'
|
||||
|
||||
export type ClaudeComposerEffortOption = {
|
||||
value: string | null
|
||||
label: string
|
||||
}
|
||||
|
||||
const CLAUDE_EFFORT_PRESETS = ['medium', 'high', 'max'] as const
|
||||
const CLAUDE_EFFORT_LABELS: Record<(typeof CLAUDE_EFFORT_PRESETS)[number], string> = {
|
||||
medium: 'Medium',
|
||||
high: 'High',
|
||||
max: 'Max'
|
||||
}
|
||||
|
||||
function normalizeClaudeComposerEffort(effort?: string | null): string | null {
|
||||
const trimmedEffort = effort?.trim().toLowerCase()
|
||||
if (!trimmedEffort || trimmedEffort === 'auto' || trimmedEffort === 'default') {
|
||||
@@ -32,7 +27,7 @@ export function getClaudeComposerEffortOptions(currentEffort?: string | null): C
|
||||
|
||||
if (
|
||||
normalizedCurrentEffort
|
||||
&& !CLAUDE_EFFORT_PRESETS.includes(normalizedCurrentEffort as typeof CLAUDE_EFFORT_PRESETS[number])
|
||||
&& !CLAUDE_EFFORT_LEVELS.includes(normalizedCurrentEffort as ClaudeEffortLevel)
|
||||
) {
|
||||
options.push({
|
||||
value: normalizedCurrentEffort,
|
||||
@@ -40,7 +35,7 @@ export function getClaudeComposerEffortOptions(currentEffort?: string | null): C
|
||||
})
|
||||
}
|
||||
|
||||
options.push(...CLAUDE_EFFORT_PRESETS.map((effort) => ({
|
||||
options.push(...CLAUDE_EFFORT_LEVELS.map((effort) => ({
|
||||
value: effort,
|
||||
label: CLAUDE_EFFORT_LABELS[effort]
|
||||
})))
|
||||
|
||||
@@ -24,8 +24,10 @@ describe('Claude effort options', () => {
|
||||
it('matches supported effort presets in expected order', () => {
|
||||
expect(CLAUDE_EFFORT_OPTIONS).toEqual([
|
||||
{ value: 'auto', label: 'Auto' },
|
||||
{ value: 'low', label: 'Low' },
|
||||
{ value: 'medium', label: 'Medium' },
|
||||
{ value: 'high', label: 'High' },
|
||||
{ value: 'xhigh', label: 'XHigh' },
|
||||
{ value: 'max', label: 'Max' },
|
||||
])
|
||||
})
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import {
|
||||
CLAUDE_EFFORT_LABELS,
|
||||
CLAUDE_EFFORT_LEVELS,
|
||||
CLAUDE_MODEL_LABELS,
|
||||
CLAUDE_MODEL_PRESETS,
|
||||
GEMINI_MODEL_LABELS,
|
||||
GEMINI_MODEL_PRESETS
|
||||
} from '@hapi/protocol'
|
||||
import type { AgentFlavor } from '@hapi/protocol'
|
||||
import type { AgentFlavor, ClaudeEffortLevel } from '@hapi/protocol'
|
||||
|
||||
export type AgentType = AgentFlavor
|
||||
export type SessionType = 'simple' | 'worktree'
|
||||
export type CodexReasoningEffort = 'default' | 'low' | 'medium' | 'high' | 'xhigh' | 'max'
|
||||
export type ClaudeEffort = 'auto' | 'medium' | 'high' | 'max'
|
||||
export type ClaudeEffort = 'auto' | ClaudeEffortLevel
|
||||
|
||||
function modelPresetOptions<TModel extends string>(
|
||||
presets: readonly TModel[],
|
||||
@@ -48,7 +50,5 @@ export const CODEX_REASONING_EFFORT_OPTIONS: { value: CodexReasoningEffort; labe
|
||||
|
||||
export const CLAUDE_EFFORT_OPTIONS: { value: ClaudeEffort; label: string }[] = [
|
||||
{ value: 'auto', label: 'Auto' },
|
||||
{ value: 'medium', label: 'Medium' },
|
||||
{ value: 'high', label: 'High' },
|
||||
{ value: 'max', label: 'Max' },
|
||||
...CLAUDE_EFFORT_LEVELS.map((value) => ({ value, label: CLAUDE_EFFORT_LABELS[value] })),
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user