mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-14 20:17:03 +00:00
fix(cliproxy): denylist deprecated agy 4.5 models
This commit is contained in:
1 parent
5913f730ac
commit
f63e2cd5a3
29 files changed
+850
-77
No files matched your search
@@ -14,10 +14,12 @@ import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
|
||||
import { toast } from 'sonner';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useCreateVariant, useCliproxyAuth } from '@/hooks/use-cliproxy';
|
||||
import { usePrivacy } from '@/contexts/privacy-context';
|
||||
import { CLIPROXY_PROVIDERS, getProviderDisplayName } from '@/lib/provider-config';
|
||||
import { isDeniedAgyModelId } from '@/lib/utils';
|
||||
|
||||
const singleProviderSchema = z.object({
|
||||
name: z
|
||||
@@ -68,6 +70,12 @@ const providerOptions = CLIPROXY_PROVIDERS.map((id) => ({
|
||||
value: id,
|
||||
label: getProviderDisplayName(id),
|
||||
}));
|
||||
const AGY_DENYLIST_MESSAGE =
|
||||
'Antigravity denylist: Claude Opus 4.5 and Claude Sonnet 4.5 are deprecated.';
|
||||
|
||||
function isDeniedAgyModelForProvider(provider: string, modelId: string | undefined): boolean {
|
||||
return provider === 'agy' && typeof modelId === 'string' && isDeniedAgyModelId(modelId);
|
||||
}
|
||||
|
||||
export function CliproxyDialog({ open, onClose }: CliproxyDialogProps) {
|
||||
const { t } = useTranslation();
|
||||
@@ -99,6 +107,12 @@ export function CliproxyDialog({ open, onClose }: CliproxyDialogProps) {
|
||||
const providerAccounts = providerAuth?.accounts || [];
|
||||
|
||||
const onSubmitSingle = async (data: SingleProviderFormData) => {
|
||||
if (isDeniedAgyModelForProvider(data.provider, data.model)) {
|
||||
singleForm.setError('model', { message: AGY_DENYLIST_MESSAGE });
|
||||
toast.error(AGY_DENYLIST_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await createMutation.mutateAsync(data);
|
||||
singleForm.reset();
|
||||
@@ -109,6 +123,14 @@ export function CliproxyDialog({ open, onClose }: CliproxyDialogProps) {
|
||||
};
|
||||
|
||||
const onSubmitComposite = async (data: CompositeFormData) => {
|
||||
for (const tier of ['opus', 'sonnet', 'haiku'] as const) {
|
||||
const tierConfig = data.tiers[tier];
|
||||
if (!isDeniedAgyModelForProvider(tierConfig.provider, tierConfig.model)) continue;
|
||||
compositeForm.setError(`tiers.${tier}.model`, { message: AGY_DENYLIST_MESSAGE });
|
||||
toast.error(AGY_DENYLIST_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await createMutation.mutateAsync({
|
||||
name: data.name,
|
||||
|
||||
@@ -12,10 +12,12 @@ import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
|
||||
import { toast } from 'sonner';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useUpdateVariant } from '@/hooks/use-cliproxy';
|
||||
import { CLIPROXY_PROVIDERS, getProviderDisplayName } from '@/lib/provider-config';
|
||||
import type { UpdateVariant, Variant } from '@/lib/api-client';
|
||||
import { isDeniedAgyModelId } from '@/lib/utils';
|
||||
|
||||
const singleProviderSchema = z.object({
|
||||
provider: z.enum(CLIPROXY_PROVIDERS, { message: 'Provider is required' }),
|
||||
@@ -61,12 +63,18 @@ const providerOptions = CLIPROXY_PROVIDERS.map((id) => ({
|
||||
}));
|
||||
|
||||
const COMPOSITE_TIERS = ['opus', 'sonnet', 'haiku'] as const;
|
||||
const AGY_DENYLIST_MESSAGE =
|
||||
'Antigravity denylist: Claude Opus 4.5 and Claude Sonnet 4.5 are deprecated.';
|
||||
|
||||
function normalizeOptionalValue(value?: string): string | undefined {
|
||||
const trimmed = value?.trim();
|
||||
return trimmed ? trimmed : undefined;
|
||||
}
|
||||
|
||||
function isDeniedAgyModelForProvider(provider: string, modelId: string | undefined): boolean {
|
||||
return provider === 'agy' && typeof modelId === 'string' && isDeniedAgyModelId(modelId);
|
||||
}
|
||||
|
||||
function isSingleVariantOnlyTargetChange(variant: Variant, data: SingleProviderFormData): boolean {
|
||||
const currentTarget = variant.target || 'claude';
|
||||
const currentModel = normalizeOptionalValue(variant.model);
|
||||
@@ -190,6 +198,14 @@ export function CliproxyEditDialog({ variant, open, onOpenChange }: CliproxyEdit
|
||||
}
|
||||
}
|
||||
|
||||
const nextProvider = payload.provider || data.provider || variant.provider;
|
||||
const nextModelForValidation = payload.model ?? normalizeOptionalValue(data.model);
|
||||
if (isDeniedAgyModelForProvider(nextProvider, nextModelForValidation)) {
|
||||
singleForm.setError('model', { message: AGY_DENYLIST_MESSAGE });
|
||||
toast.error(AGY_DENYLIST_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Object.keys(payload).length === 0) {
|
||||
onOpenChange(false);
|
||||
return;
|
||||
@@ -243,6 +259,15 @@ export function CliproxyEditDialog({ variant, open, onOpenChange }: CliproxyEdit
|
||||
}
|
||||
}
|
||||
|
||||
const tiersToValidate = payload.tiers ?? data.tiers;
|
||||
for (const tier of COMPOSITE_TIERS) {
|
||||
const tierConfig = tiersToValidate[tier];
|
||||
if (!isDeniedAgyModelForProvider(tierConfig.provider, tierConfig.model)) continue;
|
||||
compositeForm.setError(`tiers.${tier}.model`, { message: AGY_DENYLIST_MESSAGE });
|
||||
toast.error(AGY_DENYLIST_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Object.keys(payload).length === 0) {
|
||||
onOpenChange(false);
|
||||
return;
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
useDeletePreset,
|
||||
} from '@/hooks/use-cliproxy';
|
||||
import { CLIPROXY_DEFAULT_PORT } from '@/lib/preset-utils';
|
||||
import { isDeniedAgyModelId } from '@/lib/utils';
|
||||
import i18n from '@/lib/i18n';
|
||||
import { usePrivacy } from '@/contexts/privacy-context';
|
||||
import { useProviderEditor } from './use-provider-editor';
|
||||
@@ -57,10 +58,23 @@ export function ProviderEditor({
|
||||
const { data: presetsData } = usePresets(provider);
|
||||
const createPresetMutation = useCreatePreset();
|
||||
const deletePresetMutation = useDeletePreset();
|
||||
const savedPresets = presetsData?.presets || [];
|
||||
|
||||
// Use baseProvider for model filtering (for variants, this is the parent provider)
|
||||
const modelFilterProvider = baseProvider || provider;
|
||||
const isAgyProvider = modelFilterProvider.toLowerCase() === 'agy';
|
||||
|
||||
const savedPresets = useMemo(() => {
|
||||
const presets = presetsData?.presets || [];
|
||||
if (!isAgyProvider) return presets;
|
||||
|
||||
return presets.filter(
|
||||
(preset) =>
|
||||
!isDeniedAgyModelId(preset.default) &&
|
||||
!isDeniedAgyModelId(preset.opus) &&
|
||||
!isDeniedAgyModelId(preset.sonnet) &&
|
||||
!isDeniedAgyModelId(preset.haiku)
|
||||
);
|
||||
}, [isAgyProvider, presetsData?.presets]);
|
||||
|
||||
const providerModels = useMemo(() => {
|
||||
if (!modelsData?.models) return [];
|
||||
@@ -77,10 +91,12 @@ export function ProviderEditor({
|
||||
const owners = ownerMap[modelFilterProvider.toLowerCase()] || [
|
||||
modelFilterProvider.toLowerCase(),
|
||||
];
|
||||
return modelsData.models.filter((m) =>
|
||||
owners.some((o) => m.owned_by.toLowerCase().includes(o))
|
||||
);
|
||||
}, [modelsData, modelFilterProvider]);
|
||||
return modelsData.models.filter((m) => {
|
||||
if (!owners.some((o) => m.owned_by.toLowerCase().includes(o))) return false;
|
||||
if (!isAgyProvider) return true;
|
||||
return !isDeniedAgyModelId(m.id);
|
||||
});
|
||||
}, [isAgyProvider, modelsData, modelFilterProvider]);
|
||||
|
||||
const providerRoute = (baseProvider || provider).toLowerCase();
|
||||
|
||||
@@ -131,6 +147,19 @@ export function ProviderEditor({
|
||||
const effectiveApiKey = authTokens?.apiKey?.value ?? 'ccs-internal-managed';
|
||||
|
||||
const handleApplyPreset = (updates: Record<string, string>) => {
|
||||
if (
|
||||
isAgyProvider &&
|
||||
[
|
||||
updates.ANTHROPIC_MODEL,
|
||||
updates.ANTHROPIC_DEFAULT_OPUS_MODEL,
|
||||
updates.ANTHROPIC_DEFAULT_SONNET_MODEL,
|
||||
updates.ANTHROPIC_DEFAULT_HAIKU_MODEL,
|
||||
].some((modelId) => typeof modelId === 'string' && isDeniedAgyModelId(modelId))
|
||||
) {
|
||||
toast.error('Antigravity denylist: Claude Opus 4.5 and Claude Sonnet 4.5 are deprecated.');
|
||||
return;
|
||||
}
|
||||
|
||||
const effectivePort = port ?? CLIPROXY_DEFAULT_PORT;
|
||||
updateEnvValues({
|
||||
ANTHROPIC_BASE_URL: `http://127.0.0.1:${effectivePort}/api/provider/${providerRoute}`,
|
||||
@@ -141,6 +170,16 @@ export function ProviderEditor({
|
||||
};
|
||||
|
||||
const handleCustomPresetApply = (values: ModelMappingValues, presetName?: string) => {
|
||||
if (
|
||||
isAgyProvider &&
|
||||
[values.default, values.opus, values.sonnet, values.haiku].some((modelId) =>
|
||||
isDeniedAgyModelId(modelId)
|
||||
)
|
||||
) {
|
||||
toast.error('Antigravity denylist: Claude Opus 4.5 and Claude Sonnet 4.5 are deprecated.');
|
||||
return;
|
||||
}
|
||||
|
||||
const effectivePort = port ?? CLIPROXY_DEFAULT_PORT;
|
||||
updateEnvValues({
|
||||
ANTHROPIC_BASE_URL: `http://127.0.0.1:${effectivePort}/api/provider/${providerRoute}`,
|
||||
@@ -159,6 +198,15 @@ export function ProviderEditor({
|
||||
toast.error(i18n.t('commonToast.enterPresetName'));
|
||||
return;
|
||||
}
|
||||
if (
|
||||
isAgyProvider &&
|
||||
[values.default, values.opus, values.sonnet, values.haiku].some((modelId) =>
|
||||
isDeniedAgyModelId(modelId)
|
||||
)
|
||||
) {
|
||||
toast.error('Antigravity denylist: Claude Opus 4.5 and Claude Sonnet 4.5 are deprecated.');
|
||||
return;
|
||||
}
|
||||
createPresetMutation.mutate({ profile: provider, data: { name: presetName, ...values } });
|
||||
setCustomPresetOpen(false);
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { ArrowLeft, User } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { cn, isDeniedAgyModelId } from '@/lib/utils';
|
||||
import { PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context';
|
||||
import { MODEL_CATALOGS } from '@/lib/model-catalogs';
|
||||
import type { VariantStepProps } from '../types';
|
||||
@@ -40,6 +40,10 @@ export function VariantStep({
|
||||
const catalogModels = MODEL_CATALOGS[selectedProvider]?.models || [];
|
||||
const isCustomModel = modelName && !catalogModels.some((m) => m.id === modelName);
|
||||
const [showCustomInput, setShowCustomInput] = useState(isCustomModel);
|
||||
const deniedCustomModel =
|
||||
selectedProvider === 'agy' && modelName.trim().length > 0
|
||||
? isDeniedAgyModelId(modelName)
|
||||
: false;
|
||||
|
||||
const handleModelSelect = (value: string) => {
|
||||
if (value === CUSTOM_MODEL_VALUE) {
|
||||
@@ -87,10 +91,20 @@ export function VariantStep({
|
||||
onChange={(e) => onModelChange(e.target.value)}
|
||||
placeholder={t('setupVariant.modelPlaceholder')}
|
||||
/>
|
||||
{deniedCustomModel && (
|
||||
<p className="text-xs text-destructive">
|
||||
Antigravity denylist: Claude Opus 4.5 and Claude Sonnet 4.5 are deprecated.
|
||||
</p>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="text-xs text-primary hover:underline"
|
||||
onClick={() => setShowCustomInput(false)}
|
||||
onClick={() => {
|
||||
setShowCustomInput(false);
|
||||
if (selectedProvider === 'agy' && isDeniedAgyModelId(modelName)) {
|
||||
onModelChange('');
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('setupVariant.choosePresetInstead')}
|
||||
</button>
|
||||
@@ -140,7 +154,7 @@ export function VariantStep({
|
||||
<Button variant="ghost" onClick={onSkip}>
|
||||
{t('setupVariant.skip')}
|
||||
</Button>
|
||||
<Button onClick={onCreate} disabled={!variantName || isPending}>
|
||||
<Button onClick={onCreate} disabled={!variantName || isPending || deniedCustomModel}>
|
||||
{isPending ? t('setupVariant.creating') : t('setupVariant.createVariant')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
+2
-2
@@ -110,7 +110,7 @@ const resources = {
|
||||
variantNamePlaceholder: 'e.g., my-gemini, g3, flash',
|
||||
invokeHintPrefix: 'Use this name to invoke:',
|
||||
model: 'Model',
|
||||
modelPlaceholder: 'e.g., gemini-2.5-pro, claude-opus-4-5',
|
||||
modelPlaceholder: 'e.g., gemini-2.5-pro, claude-opus-4-6-thinking',
|
||||
choosePresetInstead: 'Choose from presets instead',
|
||||
selectModel: 'Select a model',
|
||||
customModelName: 'Custom model name...',
|
||||
@@ -1241,7 +1241,7 @@ const resources = {
|
||||
variantNamePlaceholder: '例如:my-gemini、g3、flash',
|
||||
invokeHintPrefix: '可通过该名称调用:',
|
||||
model: '模型',
|
||||
modelPlaceholder: '例如:gemini-2.5-pro、claude-opus-4-5',
|
||||
modelPlaceholder: '例如:gemini-2.5-pro、claude-opus-4-6-thinking',
|
||||
choosePresetInstead: '改为从预设中选择',
|
||||
selectModel: '选择模型',
|
||||
customModelName: '自定义模型名称...',
|
||||
|
||||
+8
-3
@@ -217,10 +217,15 @@ export function getClaudeResetTime<
|
||||
}
|
||||
|
||||
// Known primary models to show when exhausted (removed from API response)
|
||||
const AGY_DENYLIST_REGEX = /claude-(?:opus|sonnet)-4(?:[.-])5(?:-thinking)?(?=(?:$|[^a-z0-9]))/i;
|
||||
|
||||
export function isDeniedAgyModelId(modelId: string): boolean {
|
||||
return AGY_DENYLIST_REGEX.test((modelId || '').trim());
|
||||
}
|
||||
|
||||
const KNOWN_PRIMARY_MODELS = [
|
||||
{ name: 'claude-opus-4-5-thinking', displayName: 'Claude Opus 4.5 (Thinking)' },
|
||||
{ name: 'claude-sonnet-4-5', displayName: 'Claude Sonnet 4.5' },
|
||||
{ name: 'claude-sonnet-4-5-thinking', displayName: 'Claude Sonnet 4.5 (Thinking)' },
|
||||
{ name: 'claude-opus-4-6-thinking', displayName: 'Claude Opus 4.6 (Thinking)' },
|
||||
{ name: 'claude-sonnet-4-6', displayName: 'Claude Sonnet 4.6' },
|
||||
{ name: 'gpt-oss-120b', displayName: 'GPT-OSS 120B (Medium)' },
|
||||
];
|
||||
|
||||
|
||||
Reference in new issue
Block a user