mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-14 00:16:38 +00:00
Merge remote-tracking branch 'origin/dev' into kai/review/pr-1655-retry-followup
This commit is contained in:
commit
2d909a162f
43 files changed
+953
-218
No files matched your search
@@ -122,6 +122,7 @@ export function ProviderEditor({
|
||||
opusModel,
|
||||
sonnetModel,
|
||||
haikuModel,
|
||||
fableModel,
|
||||
extendedContextEnabled,
|
||||
toggleExtendedContext,
|
||||
handleRawJsonChange,
|
||||
@@ -271,6 +272,7 @@ export function ProviderEditor({
|
||||
opusModel={opusModel}
|
||||
sonnetModel={sonnetModel}
|
||||
haikuModel={haikuModel}
|
||||
fableModel={fableModel}
|
||||
providerModels={providerModels}
|
||||
routing={routing}
|
||||
extendedContextEnabled={extendedContextEnabled}
|
||||
|
||||
@@ -43,6 +43,7 @@ export function ModelConfigSection({
|
||||
opusModel,
|
||||
sonnetModel,
|
||||
haikuModel,
|
||||
fableModel,
|
||||
providerModels,
|
||||
routing,
|
||||
provider,
|
||||
@@ -67,7 +68,7 @@ export function ModelConfigSection({
|
||||
const extendedContextModels = useMemo(() => {
|
||||
if (!catalog) return [];
|
||||
|
||||
const selectedModels = [currentModel, opusModel, sonnetModel, haikuModel]
|
||||
const selectedModels = [currentModel, opusModel, sonnetModel, haikuModel, fableModel]
|
||||
.filter((modelId): modelId is string => Boolean(modelId))
|
||||
.map((modelId) => stripExtendedContextSuffix(modelId));
|
||||
|
||||
@@ -75,7 +76,7 @@ export function ModelConfigSection({
|
||||
return uniqueIds
|
||||
.map((modelId) => findCatalogModel(catalog.provider, modelId, catalog))
|
||||
.filter((model): model is NonNullable<typeof model> => Boolean(model?.extendedContext));
|
||||
}, [catalog, currentModel, opusModel, sonnetModel, haikuModel]);
|
||||
}, [catalog, currentModel, opusModel, sonnetModel, haikuModel, fableModel]);
|
||||
|
||||
const resolvedCatalogModels = useMemo(
|
||||
() => getResolvedCatalogModels(catalog, providerModels),
|
||||
@@ -253,6 +254,15 @@ export function ModelConfigSection({
|
||||
onToggle={onExtendedContextToggle}
|
||||
/>
|
||||
)}
|
||||
<FlexibleModelSelector
|
||||
label="Fable (Frontier)"
|
||||
description="Maps the fable subagent tier; leave empty to keep the native Claude Fable model"
|
||||
value={fableModel}
|
||||
onChange={(model) => onUpdateEnvValue('ANTHROPIC_DEFAULT_FABLE_MODEL', model)}
|
||||
catalog={catalog}
|
||||
allModels={providerModels}
|
||||
routing={routing}
|
||||
/>
|
||||
<FlexibleModelSelector
|
||||
label="Opus (Most capable)"
|
||||
description="For complex reasoning tasks"
|
||||
|
||||
@@ -27,6 +27,7 @@ interface ModelConfigTabProps {
|
||||
opusModel?: string;
|
||||
sonnetModel?: string;
|
||||
haikuModel?: string;
|
||||
fableModel?: string;
|
||||
providerModels: Array<{ id: string; owned_by: string }>;
|
||||
routing?: CliproxyProviderRoutingHints;
|
||||
/** Whether extended context (1M tokens) is enabled */
|
||||
@@ -72,6 +73,7 @@ export function ModelConfigTab({
|
||||
opusModel,
|
||||
sonnetModel,
|
||||
haikuModel,
|
||||
fableModel,
|
||||
providerModels,
|
||||
routing,
|
||||
extendedContextEnabled,
|
||||
@@ -163,6 +165,7 @@ export function ModelConfigTab({
|
||||
opusModel={opusModel}
|
||||
sonnetModel={sonnetModel}
|
||||
haikuModel={haikuModel}
|
||||
fableModel={fableModel}
|
||||
providerModels={providerModels}
|
||||
routing={routing}
|
||||
provider={provider}
|
||||
|
||||
@@ -126,6 +126,7 @@ export interface ModelConfigSectionProps {
|
||||
opusModel?: string;
|
||||
sonnetModel?: string;
|
||||
haikuModel?: string;
|
||||
fableModel?: string;
|
||||
providerModels: Array<{ id: string; owned_by: string }>;
|
||||
routing?: CliproxyProviderRoutingHints;
|
||||
/** Provider name for display */
|
||||
@@ -154,6 +155,7 @@ export interface UseProviderEditorReturn {
|
||||
opusModel?: string;
|
||||
sonnetModel?: string;
|
||||
haikuModel?: string;
|
||||
fableModel?: string;
|
||||
/** Whether extended context (1M tokens) is enabled */
|
||||
extendedContextEnabled: boolean;
|
||||
/** Toggle extended context on/off */
|
||||
|
||||
@@ -85,6 +85,7 @@ export function useProviderEditor(
|
||||
const opusModel = currentSettings?.env?.ANTHROPIC_DEFAULT_OPUS_MODEL;
|
||||
const sonnetModel = currentSettings?.env?.ANTHROPIC_DEFAULT_SONNET_MODEL;
|
||||
const haikuModel = currentSettings?.env?.ANTHROPIC_DEFAULT_HAIKU_MODEL;
|
||||
const fableModel = currentSettings?.env?.ANTHROPIC_DEFAULT_FABLE_MODEL;
|
||||
|
||||
// Extended context is enabled if any model has [1m] suffix
|
||||
const extendedContextEnabled = useMemo(() => {
|
||||
@@ -224,6 +225,7 @@ export function useProviderEditor(
|
||||
opusModel,
|
||||
sonnetModel,
|
||||
haikuModel,
|
||||
fableModel,
|
||||
extendedContextEnabled,
|
||||
toggleExtendedContext,
|
||||
handleRawJsonChange,
|
||||
|
||||
@@ -10,16 +10,31 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { SearchableSelect } from '@/components/ui/searchable-select';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import type { CliproxyProviderRoutingHints } from '@/lib/api-client';
|
||||
import type { CodexEffort, CodexServiceTier } from '@/lib/codex-effort';
|
||||
import {
|
||||
applyCodexServiceTierSuffix,
|
||||
getCodexEffortDisplay,
|
||||
getCodexEffortVariants,
|
||||
parseCodexEffort,
|
||||
parseCodexServiceTier,
|
||||
stripCodexEffortSuffix,
|
||||
} from '@/lib/codex-effort';
|
||||
import { getResolvedCatalogModels, getSupplementalCatalogModels } from '@/lib/model-catalogs';
|
||||
import {
|
||||
findCatalogModel,
|
||||
getResolvedCatalogModels,
|
||||
getSupplementalCatalogModels,
|
||||
} from '@/lib/model-catalogs';
|
||||
import { getReasoningAdapter } from '@/lib/reasoning-control';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
/** Model entry from catalog */
|
||||
@@ -47,6 +62,8 @@ export interface ModelEntry {
|
||||
codexEfforts?: CodexEffort[];
|
||||
/** Additional Codex service-tier suffixes supported by this model in the dashboard UI. */
|
||||
codexServiceTiers?: CodexServiceTier[];
|
||||
/** Reasoning-level suffixes this model supports via paren syntax in the dashboard UI. */
|
||||
reasoningLevels?: string[];
|
||||
}
|
||||
|
||||
/** Provider catalog */
|
||||
@@ -561,6 +578,34 @@ export function FlexibleModelSelector({
|
||||
}
|
||||
: null;
|
||||
const hasAvailableModels = recommendedOptions.length + allModelOptions.length > 0;
|
||||
const reasoningAdapter = getReasoningAdapter(catalog?.provider);
|
||||
const currentReasoningLevel = reasoningAdapter?.parse(value);
|
||||
const selectableReasoningLevels =
|
||||
reasoningAdapter && catalog
|
||||
? reasoningAdapter.getOptions(
|
||||
reasoningAdapter.strip(normalizeModelValue(value, routing)),
|
||||
catalog
|
||||
)
|
||||
: null;
|
||||
const reasoningOptions =
|
||||
selectableReasoningLevels &&
|
||||
currentReasoningLevel &&
|
||||
!selectableReasoningLevels.includes(currentReasoningLevel)
|
||||
? [...selectableReasoningLevels, currentReasoningLevel]
|
||||
: selectableReasoningLevels;
|
||||
const currentServiceTier = isCodexProvider ? parseCodexServiceTier(value) : undefined;
|
||||
const selectedCodexBaseModel =
|
||||
isCodexProvider && catalog && value
|
||||
? findCatalogModel(
|
||||
catalog.provider,
|
||||
stripCodexEffortSuffix(normalizeModelValue(value, routing)),
|
||||
catalog
|
||||
)
|
||||
: undefined;
|
||||
const showFastToggle =
|
||||
isCodexProvider &&
|
||||
(selectedCodexBaseModel?.codexServiceTiers?.includes('fast') === true ||
|
||||
currentServiceTier === 'fast');
|
||||
|
||||
return (
|
||||
<div className="space-y-1.5">
|
||||
@@ -568,87 +613,129 @@ export function FlexibleModelSelector({
|
||||
<label className="text-xs font-medium">{label}</label>
|
||||
{description && <p className="text-[10px] text-muted-foreground">{description}</p>}
|
||||
</div>
|
||||
<SearchableSelect
|
||||
value={value || undefined}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
placeholder={t('providerModelSelector.selectModel')}
|
||||
searchPlaceholder={t('searchableSelect.searchModels')}
|
||||
emptyText={
|
||||
hasAvailableModels
|
||||
? t('searchableSelect.noResults')
|
||||
: t('providerModelSelector.noModelsAvailable')
|
||||
}
|
||||
triggerClassName="h-9"
|
||||
createOption={createCustomModelOption}
|
||||
groups={[
|
||||
...(selectedValueMissing && legacySelectedOption
|
||||
? [
|
||||
{
|
||||
key: 'current',
|
||||
label: (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t('providerModelSelector.currentValue')}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: 'recommended',
|
||||
label: (
|
||||
<span className="text-xs text-primary">{t('providerModelSelector.recommended')}</span>
|
||||
),
|
||||
},
|
||||
...(isCodexProvider
|
||||
? [
|
||||
{
|
||||
key: 'codex-reasoning',
|
||||
label: (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t('providerModelSelector.codexReasoningVariants')}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'codex-fast',
|
||||
label: (
|
||||
<span className="text-xs text-amber-600">
|
||||
{t('providerModelSelector.codexFastVariants')}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(allModelOptions.length > 0
|
||||
? [
|
||||
{
|
||||
key: 'all',
|
||||
label: (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t('providerModelSelector.allModelsCount', {
|
||||
count: allModelOptions.length,
|
||||
})}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: 'custom',
|
||||
label: (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t('providerModelSelector.customModel')}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
]}
|
||||
options={[
|
||||
...(selectedValueMissing && legacySelectedOption ? [legacySelectedOption] : []),
|
||||
...recommendedOptions,
|
||||
...allModelOptions,
|
||||
]}
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<SearchableSelect
|
||||
value={value || undefined}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
placeholder={t('providerModelSelector.selectModel')}
|
||||
searchPlaceholder={t('searchableSelect.searchModels')}
|
||||
emptyText={
|
||||
hasAvailableModels
|
||||
? t('searchableSelect.noResults')
|
||||
: t('providerModelSelector.noModelsAvailable')
|
||||
}
|
||||
className="min-w-0 flex-1"
|
||||
triggerClassName="h-9"
|
||||
createOption={createCustomModelOption}
|
||||
groups={[
|
||||
...(selectedValueMissing && legacySelectedOption
|
||||
? [
|
||||
{
|
||||
key: 'current',
|
||||
label: (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t('providerModelSelector.currentValue')}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: 'recommended',
|
||||
label: (
|
||||
<span className="text-xs text-primary">
|
||||
{t('providerModelSelector.recommended')}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
...(isCodexProvider
|
||||
? [
|
||||
{
|
||||
key: 'codex-reasoning',
|
||||
label: (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t('providerModelSelector.codexReasoningVariants')}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'codex-fast',
|
||||
label: (
|
||||
<span className="text-xs text-amber-600">
|
||||
{t('providerModelSelector.codexFastVariants')}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(allModelOptions.length > 0
|
||||
? [
|
||||
{
|
||||
key: 'all',
|
||||
label: (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t('providerModelSelector.allModelsCount', {
|
||||
count: allModelOptions.length,
|
||||
})}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: 'custom',
|
||||
label: (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t('providerModelSelector.customModel')}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
]}
|
||||
options={[
|
||||
...(selectedValueMissing && legacySelectedOption ? [legacySelectedOption] : []),
|
||||
...recommendedOptions,
|
||||
...allModelOptions,
|
||||
]}
|
||||
/>
|
||||
{reasoningAdapter && reasoningOptions && (
|
||||
<Select
|
||||
value={currentReasoningLevel ?? 'auto'}
|
||||
onValueChange={(level) =>
|
||||
onChange(reasoningAdapter.apply(value, level === 'auto' ? undefined : level))
|
||||
}
|
||||
disabled={disabled || !value}
|
||||
>
|
||||
<SelectTrigger
|
||||
className="h-9 w-28 shrink-0"
|
||||
aria-label={t('providerModelSelector.effortLabel')}
|
||||
>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="auto">{t('providerModelSelector.effortAuto')}</SelectItem>
|
||||
{reasoningOptions.map((level) => (
|
||||
<SelectItem key={level} value={level}>
|
||||
{level}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
{showFastToggle && (
|
||||
<label className="flex h-9 shrink-0 items-center gap-1.5">
|
||||
<Switch
|
||||
checked={currentServiceTier === 'fast'}
|
||||
onCheckedChange={(checked) => onChange(applyCodexServiceTierSuffix(value, checked))}
|
||||
disabled={disabled || !value}
|
||||
aria-label={t('providerModelSelector.fastTier')}
|
||||
/>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t('providerModelSelector.fastTier')}
|
||||
</span>
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
{selectedRoutingHint ? (
|
||||
<div
|
||||
className={cn(
|
||||
|
||||
Reference in new issue
Block a user