mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
fix(codex): show catalog-default Fast tier (#1179)
* fix(codex): show catalog-default Fast tier * fix(web): show inherited Fast tier in header
This commit is contained in:
@@ -74,6 +74,7 @@ function normalizeModel(entry: unknown): CodexModelSummary | null {
|
||||
displayName: asNonEmptyString(record.displayName) ?? id,
|
||||
isDefault: record.isDefault === true,
|
||||
defaultReasoningEffort: asNonEmptyString(record.defaultReasoningEffort),
|
||||
defaultServiceTier: asNonEmptyString(record.defaultServiceTier),
|
||||
supportedReasoningEfforts: normalizeSupportedReasoningEfforts(record.supportedReasoningEfforts),
|
||||
serviceTiers: normalizeServiceTiers(record.serviceTiers)
|
||||
};
|
||||
|
||||
@@ -433,6 +433,7 @@ export type CodexModelSummary = {
|
||||
displayName: string
|
||||
isDefault: boolean
|
||||
defaultReasoningEffort?: string | null
|
||||
defaultServiceTier?: string | null
|
||||
supportedReasoningEfforts?: string[]
|
||||
/** Service tier ids advertised for this model in the current auth/plan context (e.g. 'fast'). */
|
||||
serviceTiers?: string[]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
codexModelAdvertisesFastTier,
|
||||
getEffectiveCodexServiceTier,
|
||||
getDisplayedCodexServiceTier,
|
||||
isFastServiceTier
|
||||
} from './codexFastMode'
|
||||
@@ -45,6 +46,7 @@ describe('isFastServiceTier', () => {
|
||||
it('detects the fast tier regardless of casing/spacing', () => {
|
||||
expect(isFastServiceTier('fast')).toBe(true)
|
||||
expect(isFastServiceTier(' Fast ')).toBe(true)
|
||||
expect(isFastServiceTier('priority')).toBe(true)
|
||||
})
|
||||
|
||||
it('treats null/standard as not fast', () => {
|
||||
@@ -54,6 +56,17 @@ describe('isFastServiceTier', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('getEffectiveCodexServiceTier', () => {
|
||||
it('uses the active model default only when the session has no override', () => {
|
||||
const modelsWithDefault = models.map((model) => (
|
||||
model.id === 'gpt-5.5' ? { ...model, defaultServiceTier: 'priority' } : model
|
||||
))
|
||||
|
||||
expect(getEffectiveCodexServiceTier(null, null, modelsWithDefault)).toBe('priority')
|
||||
expect(getEffectiveCodexServiceTier('standard', null, modelsWithDefault)).toBe('standard')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getDisplayedCodexServiceTier', () => {
|
||||
it('displays untouched and explicit non-fast tiers as standard', () => {
|
||||
expect(getDisplayedCodexServiceTier(null)).toBe('standard')
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
type CodexModelCatalogEntry = {
|
||||
id: string
|
||||
isDefault: boolean
|
||||
defaultServiceTier?: string | null
|
||||
serviceTiers?: string[]
|
||||
}
|
||||
|
||||
@@ -47,7 +48,15 @@ export function codexModelAdvertisesFastTier(
|
||||
}
|
||||
|
||||
export function isFastServiceTier(serviceTier?: string | null): boolean {
|
||||
return serviceTier?.trim().toLowerCase() === 'fast'
|
||||
return /^(fast|priority)$/i.test(serviceTier?.trim() ?? '')
|
||||
}
|
||||
|
||||
export function getEffectiveCodexServiceTier(
|
||||
serviceTier: string | null | undefined,
|
||||
sessionModel: string | null | undefined,
|
||||
models: ReadonlyArray<CodexModelCatalogEntry>
|
||||
): string | null | undefined {
|
||||
return serviceTier ?? findActiveModel(sessionModel, models)?.defaultServiceTier
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
supportsCodexReasoningEffort
|
||||
} from '@/lib/codexModelCapabilities'
|
||||
import { HappyComposer, type ComposerSendError } from '@/components/AssistantChat/HappyComposer'
|
||||
import { codexModelAdvertisesFastTier } from '@/components/AssistantChat/codexFastMode'
|
||||
import { codexModelAdvertisesFastTier, getEffectiveCodexServiceTier } from '@/components/AssistantChat/codexFastMode'
|
||||
import type { PendingSchedule } from '@/components/AssistantChat/ScheduleTimePicker'
|
||||
import { resolvePendingSchedule } from '@/components/AssistantChat/ScheduleTimePicker'
|
||||
import { HappyThread } from '@/components/AssistantChat/HappyThread'
|
||||
@@ -543,6 +543,13 @@ function SessionChatInner(props: SessionChatProps) {
|
||||
sessionId: props.session.id,
|
||||
enabled: agentFlavor === 'codex' && props.session.active && !controlledByUser
|
||||
})
|
||||
const effectiveCodexServiceTier = agentFlavor === 'codex'
|
||||
? getEffectiveCodexServiceTier(
|
||||
props.session.serviceTier,
|
||||
props.session.model,
|
||||
codexModelsState.models
|
||||
)
|
||||
: undefined
|
||||
const codexModelOptions = useMemo(() => {
|
||||
if (agentFlavor !== 'codex') {
|
||||
return undefined
|
||||
@@ -1199,6 +1206,7 @@ function SessionChatInner(props: SessionChatProps) {
|
||||
<div className="flex h-full min-h-0 flex-col">
|
||||
<SessionHeader
|
||||
session={props.session}
|
||||
serviceTier={effectiveCodexServiceTier}
|
||||
onBack={props.onBack}
|
||||
onToggleFiles={props.session.metadata?.path ? handleToggleFiles : undefined}
|
||||
filesActive={false}
|
||||
@@ -1431,7 +1439,7 @@ function SessionChatInner(props: SessionChatProps) {
|
||||
: undefined)
|
||||
: handleEffortChange
|
||||
}
|
||||
serviceTier={agentFlavor === 'codex' ? props.session.serviceTier : undefined}
|
||||
serviceTier={effectiveCodexServiceTier}
|
||||
onServiceTierChange={
|
||||
agentFlavor === 'codex'
|
||||
&& props.session.active
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { cleanup, render, screen } from '@testing-library/react'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { Session } from '@/types/api'
|
||||
import { I18nProvider } from '@/lib/i18n-context'
|
||||
import { ToastProvider } from '@/lib/toast-context'
|
||||
import { SessionHeader } from './SessionHeader'
|
||||
|
||||
afterEach(() => cleanup())
|
||||
|
||||
describe('SessionHeader', () => {
|
||||
it('shows an inherited catalog-default Fast tier', () => {
|
||||
const session: Session = {
|
||||
id: 'session-1',
|
||||
namespace: 'default',
|
||||
seq: 0,
|
||||
createdAt: 0,
|
||||
updatedAt: 0,
|
||||
active: true,
|
||||
activeAt: 0,
|
||||
metadata: { flavor: 'codex', path: '/repo', host: 'machine' },
|
||||
metadataVersion: 0,
|
||||
agentState: null,
|
||||
agentStateVersion: 0,
|
||||
thinking: false,
|
||||
thinkingAt: 0,
|
||||
model: null,
|
||||
modelReasoningEffort: null,
|
||||
effort: null,
|
||||
serviceTier: null
|
||||
}
|
||||
|
||||
render(
|
||||
<QueryClientProvider client={new QueryClient()}>
|
||||
<ToastProvider>
|
||||
<I18nProvider>
|
||||
<SessionHeader
|
||||
session={session}
|
||||
serviceTier="priority"
|
||||
onBack={vi.fn()}
|
||||
api={null}
|
||||
/>
|
||||
</I18nProvider>
|
||||
</ToastProvider>
|
||||
</QueryClientProvider>
|
||||
)
|
||||
|
||||
expect(screen.getByText('fast')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -90,6 +90,7 @@ function MoreVerticalIcon(props: { className?: string }) {
|
||||
|
||||
export function SessionHeader(props: {
|
||||
session: Session
|
||||
serviceTier?: string | null
|
||||
onBack: () => void
|
||||
onToggleFiles?: () => void
|
||||
filesActive?: boolean
|
||||
@@ -113,7 +114,7 @@ export function SessionHeader(props: {
|
||||
? formatCodexReasoningLabel(session.modelReasoningEffort)
|
||||
: null
|
||||
// Match expected Fast badge semantics (#1004): only explicit service tier, no effort/model heuristics.
|
||||
const showFastBadge = agentFlavor === 'codex' && isFastServiceTier(session.serviceTier)
|
||||
const showFastBadge = agentFlavor === 'codex' && isFastServiceTier(props.serviceTier ?? session.serviceTier)
|
||||
const codexSessionId = session.metadata?.flavor === 'codex'
|
||||
? session.metadata.codexSessionId?.trim() || null
|
||||
: null
|
||||
|
||||
Reference in New Issue
Block a user