mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-06 06:41:56 +00:00
Add support for claude 1M context. close #291
This commit is contained in:
@@ -8,6 +8,7 @@ import { claudeRemoteLauncher } from "./claudeRemoteLauncher"
|
||||
import { ApiClient } from "@/lib"
|
||||
import type { SessionModelMode } from "@/api/types"
|
||||
import type { ClaudePermissionMode } from "@hapi/protocol/types"
|
||||
import { resolveClaudeSessionModelMode } from "./modelMode"
|
||||
|
||||
export type PermissionMode = ClaudePermissionMode;
|
||||
|
||||
@@ -45,9 +46,7 @@ export async function loop(opts: LoopOptions) {
|
||||
const logPath = logger.logFilePath;
|
||||
const startedBy = opts.startedBy ?? 'terminal';
|
||||
const startingMode = opts.startingMode ?? 'local';
|
||||
const modelMode: SessionModelMode = opts.model === 'sonnet' || opts.model === 'opus'
|
||||
? opts.model
|
||||
: 'default';
|
||||
const modelMode: SessionModelMode = resolveClaudeSessionModelMode(opts.model)
|
||||
const session = new Session({
|
||||
api: opts.api,
|
||||
client: opts.session,
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { resolveClaudeSessionModelMode } from './modelMode'
|
||||
|
||||
describe('resolveClaudeSessionModelMode', () => {
|
||||
it('returns default when model is missing', () => {
|
||||
expect(resolveClaudeSessionModelMode()).toBe('default')
|
||||
})
|
||||
|
||||
it('returns default for auto and unsupported models', () => {
|
||||
expect(resolveClaudeSessionModelMode('auto')).toBe('default')
|
||||
expect(resolveClaudeSessionModelMode('claude-sonnet-4-5')).toBe('default')
|
||||
})
|
||||
|
||||
it('returns standard Claude session model modes', () => {
|
||||
expect(resolveClaudeSessionModelMode('sonnet')).toBe('sonnet')
|
||||
expect(resolveClaudeSessionModelMode('opus')).toBe('opus')
|
||||
})
|
||||
|
||||
it('returns 1m Claude session model modes', () => {
|
||||
expect(resolveClaudeSessionModelMode('sonnet[1m]')).toBe('sonnet[1m]')
|
||||
expect(resolveClaudeSessionModelMode('opus[1m]')).toBe('opus[1m]')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { SessionModelMode } from '@/api/types'
|
||||
|
||||
const CLAUDE_SESSION_MODEL_MODES = new Set<SessionModelMode>([
|
||||
'sonnet',
|
||||
'sonnet[1m]',
|
||||
'opus',
|
||||
'opus[1m]'
|
||||
])
|
||||
|
||||
export function resolveClaudeSessionModelMode(model?: string): SessionModelMode {
|
||||
if (!model) {
|
||||
return 'default'
|
||||
}
|
||||
|
||||
return CLAUDE_SESSION_MODEL_MODES.has(model as SessionModelMode)
|
||||
? model as SessionModelMode
|
||||
: 'default'
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import { createModeChangeHandler, createRunnerLifecycle, setControlledByUser } f
|
||||
import { isModelModeAllowedForFlavor, isPermissionModeAllowedForFlavor } from '@hapi/protocol';
|
||||
import { ModelModeSchema, PermissionModeSchema } from '@hapi/protocol/schemas';
|
||||
import { formatMessageWithAttachments } from '@/utils/attachmentFormatter';
|
||||
import { resolveClaudeSessionModelMode } from './modelMode';
|
||||
|
||||
export interface StartOptions {
|
||||
model?: string
|
||||
@@ -143,7 +144,7 @@ export async function runClaude(options: StartOptions = {}): Promise<void> {
|
||||
|
||||
// Forward messages to the queue
|
||||
let currentPermissionMode: PermissionMode = options.permissionMode ?? 'default';
|
||||
let currentModelMode: SessionModelMode = options.model === 'sonnet' || options.model === 'opus' ? options.model : 'default';
|
||||
let currentModelMode: SessionModelMode = resolveClaudeSessionModelMode(options.model);
|
||||
let currentFallbackModel: string | undefined = undefined; // Track current fallback model
|
||||
let currentCustomSystemPrompt: string | undefined = undefined; // Track current custom system prompt
|
||||
let currentAppendSystemPrompt: string | undefined = undefined; // Track current append system prompt
|
||||
|
||||
+8
-2
@@ -25,7 +25,7 @@ export const PERMISSION_MODES = [
|
||||
] as const
|
||||
export type PermissionMode = typeof PERMISSION_MODES[number]
|
||||
|
||||
export const MODEL_MODES = ['default', 'sonnet', 'opus'] as const
|
||||
export const MODEL_MODES = ['default', 'sonnet', 'sonnet[1m]', 'opus', 'opus[1m]'] as const
|
||||
export type ModelMode = typeof MODEL_MODES[number]
|
||||
|
||||
export type AgentFlavor = 'claude' | 'codex' | 'gemini' | 'opencode' | 'cursor'
|
||||
@@ -63,7 +63,13 @@ export type PermissionModeOption = {
|
||||
export const MODEL_MODE_LABELS: Record<ModelMode, string> = {
|
||||
default: 'Default',
|
||||
sonnet: 'Sonnet',
|
||||
opus: 'Opus'
|
||||
'sonnet[1m]': 'Sonnet 1M',
|
||||
opus: 'Opus',
|
||||
'opus[1m]': 'Opus 1M'
|
||||
}
|
||||
|
||||
export function getModelModeLabel(mode: ModelMode): string {
|
||||
return MODEL_MODE_LABELS[mode]
|
||||
}
|
||||
|
||||
export function getPermissionModeLabel(mode: PermissionMode): string {
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ See `src/router.tsx` for route definitions.
|
||||
- Message thread with infinite scroll.
|
||||
- Composer for sending messages.
|
||||
- Permission mode toggle (default/acceptEdits/bypassPermissions/plan).
|
||||
- Model selection (default/sonnet/opus).
|
||||
- Model selection (default/sonnet/sonnet[1m]/opus/opus[1m]).
|
||||
- Session abort and mode switch controls.
|
||||
- Context size display.
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { getContextBudgetTokens } from './modelConfig'
|
||||
|
||||
describe('getContextBudgetTokens', () => {
|
||||
it('uses the existing 200k budget for default Claude modes', () => {
|
||||
expect(getContextBudgetTokens(undefined)).toBe(190_000)
|
||||
expect(getContextBudgetTokens('default')).toBe(190_000)
|
||||
expect(getContextBudgetTokens('sonnet')).toBe(190_000)
|
||||
expect(getContextBudgetTokens('opus')).toBe(190_000)
|
||||
})
|
||||
|
||||
it('uses the 1m budget for Claude 1m modes', () => {
|
||||
expect(getContextBudgetTokens('sonnet[1m]')).toBe(990_000)
|
||||
expect(getContextBudgetTokens('opus[1m]')).toBe(990_000)
|
||||
})
|
||||
})
|
||||
@@ -13,10 +13,12 @@ import type { ModelMode } from '@/types/api'
|
||||
const CONTEXT_HEADROOM_TOKENS = 10_000
|
||||
|
||||
const MODEL_CONTEXT_WINDOWS: Record<ModelMode, number> = {
|
||||
// Claude Code modes used in this app; currently treated as ~200k context.
|
||||
// Claude Code modes used in this app. 1M variants get the larger budget.
|
||||
default: 200_000,
|
||||
sonnet: 200_000,
|
||||
opus: 200_000
|
||||
'sonnet[1m]': 1_000_000,
|
||||
opus: 200_000,
|
||||
'opus[1m]': 1_000_000
|
||||
}
|
||||
|
||||
export function getContextBudgetTokens(modelMode: ModelMode | undefined): number | null {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { getModelModeLabel, MODEL_MODES } from '@hapi/protocol'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { MODEL_OPTIONS } from './types'
|
||||
|
||||
describe('Claude model options', () => {
|
||||
it('includes 1m model options in the expected order', () => {
|
||||
expect(MODEL_OPTIONS.claude).toEqual([
|
||||
{ value: 'auto', label: 'Auto' },
|
||||
{ value: 'opus', label: 'Opus' },
|
||||
{ value: 'opus[1m]', label: 'Opus 1M' },
|
||||
{ value: 'sonnet', label: 'Sonnet' },
|
||||
{ value: 'sonnet[1m]', label: 'Sonnet 1M' },
|
||||
])
|
||||
})
|
||||
|
||||
it('exposes friendly labels for session model modes', () => {
|
||||
expect(MODEL_MODES).toEqual(['default', 'sonnet', 'sonnet[1m]', 'opus', 'opus[1m]'])
|
||||
expect(getModelModeLabel('sonnet[1m]')).toBe('Sonnet 1M')
|
||||
expect(getModelModeLabel('opus[1m]')).toBe('Opus 1M')
|
||||
})
|
||||
})
|
||||
@@ -5,7 +5,9 @@ export const MODEL_OPTIONS: Record<AgentType, { value: string; label: string }[]
|
||||
claude: [
|
||||
{ value: 'auto', label: 'Auto' },
|
||||
{ value: 'opus', label: 'Opus' },
|
||||
{ value: 'opus[1m]', label: 'Opus 1M' },
|
||||
{ value: 'sonnet', label: 'Sonnet' },
|
||||
{ value: 'sonnet[1m]', label: 'Sonnet 1M' },
|
||||
],
|
||||
codex: [
|
||||
{ value: 'auto', label: 'Auto' },
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getModelModeLabel } from '@hapi/protocol'
|
||||
import { useId, useMemo, useRef, useState } from 'react'
|
||||
import type { Session } from '@/types/api'
|
||||
import type { ApiClient } from '@/api/client'
|
||||
@@ -70,6 +71,7 @@ export function SessionHeader(props: {
|
||||
const { session, api, onSessionDeleted } = props
|
||||
const title = useMemo(() => getSessionTitle(session), [session])
|
||||
const worktreeBranch = session.metadata?.worktree?.branch
|
||||
const modelModeLabel = getModelModeLabel(session.modelMode ?? 'default')
|
||||
|
||||
const [menuOpen, setMenuOpen] = useState(false)
|
||||
const [menuAnchorPoint, setMenuAnchorPoint] = useState<{ x: number; y: number }>({ x: 0, y: 0 })
|
||||
@@ -139,7 +141,7 @@ export function SessionHeader(props: {
|
||||
{session.metadata?.flavor?.trim() || 'unknown'}
|
||||
</span>
|
||||
<span>
|
||||
{t('session.item.modelMode')}: {session.modelMode || 'default'}
|
||||
{t('session.item.modelMode')}: {modelModeLabel}
|
||||
</span>
|
||||
{worktreeBranch ? (
|
||||
<span>{t('session.item.worktree')}: {worktreeBranch}</span>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getModelModeLabel } from '@hapi/protocol'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import type { SessionSummary } from '@/types/api'
|
||||
import type { ApiClient } from '@/api/client'
|
||||
@@ -198,6 +199,7 @@ function SessionItem(props: {
|
||||
})
|
||||
|
||||
const sessionName = getSessionTitle(s)
|
||||
const modelModeLabel = getModelModeLabel(s.modelMode ?? 'default')
|
||||
const statusDotClass = s.active
|
||||
? (s.thinking ? 'bg-[#007AFF]' : 'bg-[var(--app-badge-success-text)]')
|
||||
: 'bg-[var(--app-hint)]'
|
||||
@@ -259,7 +261,7 @@ function SessionItem(props: {
|
||||
</span>
|
||||
{getAgentLabel(s)}
|
||||
</span>
|
||||
<span>{t('session.item.modelMode')}: {s.modelMode || 'default'}</span>
|
||||
<span>{t('session.item.modelMode')}: {modelModeLabel}</span>
|
||||
{s.metadata?.worktree?.branch ? (
|
||||
<span>{t('session.item.worktree')}: {s.metadata.worktree.branch}</span>
|
||||
) : null}
|
||||
|
||||
Reference in New Issue
Block a user