# v0.4.55 (2026-05-18)

## Features
- Xiaomi MiMo Token Plan: region selector (Singapore / China / Europe) — keys are cluster-specific
- Antigravity: risk confirmation dialog before first connection
- Gemini CLI: surface upstream retry delay on 429 errors

## Fixes
- MITM: cannot kill process on macOS under sudo (lsof not found in PATH)
- Stream: false-positive stall timeout on Claude reasoning / Kiro responses
- Tunnel: cannot re-enable after disable (stuck state)
- Tunnel: cloudflared error messages now include log tail for easier debugging
- Language switcher: applies selected locale immediately on close (#1234)
- Antigravity OAuth: metadata now matches the official client

## Improvements
- Gemini CLI: bump engine to 0.34.0
- Re-hide `qwen` (OAuth EOL) and `iflow` (not ready) providers
This commit is contained in:
decolua
2026-05-18 16:26:35 +07:00
parent 90a47c3f29
commit 613a0a819a
12 changed files with 125 additions and 47 deletions

View File

@@ -3,6 +3,7 @@
import { useState } from "react";
import PropTypes from "prop-types";
import { Button, Badge, Input, Modal, Select } from "@/shared/components";
import { AI_PROVIDERS } from "@/shared/constants/providers";
const BULK_PLACEHOLDER = `name1|sk-key1\nname2|sk-key2\nsk-key-only-auto-named`;
@@ -17,6 +18,8 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa
const isAzure = provider === "azure";
const isCloudflareAi = provider === "cloudflare-ai";
const providerRegions = AI_PROVIDERS?.[provider]?.regions || null;
const defaultRegion = AI_PROVIDERS?.[provider]?.defaultRegion || providerRegions?.[0]?.id || "";
const [formData, setFormData] = useState({
name: "",
@@ -33,6 +36,7 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa
organization: "",
});
const [cloudflareData, setCloudflareData] = useState({ accountId: "" });
const [region, setRegion] = useState(defaultRegion);
const [validating, setValidating] = useState(false);
const [validationResult, setValidationResult] = useState(null);
const [saving, setSaving] = useState(false);
@@ -55,6 +59,9 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa
if (isCloudflareAi) {
return { accountId: cloudflareData.accountId };
}
if (providerRegions && region) {
return { region };
}
return undefined;
};
@@ -234,6 +241,14 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa
)}
</p>
)}
{providerRegions && (
<Select
label="Region"
value={region}
onChange={(e) => setRegion(e.target.value)}
options={providerRegions.map((r) => ({ value: r.id, label: r.label }))}
/>
)}
{isCompatible && (
<Input
label="Default Model"

View File

@@ -49,8 +49,40 @@ export default function ProviderDetailPage() {
const [kiloFreeModels, setKiloFreeModels] = useState([]);
const [disabledModelIds, setDisabledModelIds] = useState([]);
const [confirmState, setConfirmState] = useState(null);
const [showAgRiskModal, setShowAgRiskModal] = useState(false);
const { copied, copy } = useCopyToClipboard();
const AG_RISK_STORAGE_KEY = "ag_risk_confirmed";
const triggerAddConnection = () => {
if (providerId === "antigravity" && typeof window !== "undefined") {
const confirmed = window.localStorage.getItem(AG_RISK_STORAGE_KEY) === "true";
if (!confirmed) {
setShowAgRiskModal(true);
return;
}
}
if (isOAuth) {
setShowOAuthModal(true);
return;
}
setAddConnectionError("");
setShowAddApiKeyModal(true);
};
const handleAgRiskConfirm = () => {
if (typeof window !== "undefined") {
window.localStorage.setItem(AG_RISK_STORAGE_KEY, "true");
}
setShowAgRiskModal(false);
if (isOAuth) {
setShowOAuthModal(true);
return;
}
setAddConnectionError("");
setShowAddApiKeyModal(true);
};
const providerInfo = providerNode
? {
id: providerNode.id,
@@ -1066,14 +1098,7 @@ export default function ProviderDetailPage() {
<Button
size="sm"
icon="add"
onClick={() => {
if (isOAuth) {
setShowOAuthModal(true);
return;
}
setAddConnectionError("");
setShowAddApiKeyModal(true);
}}
onClick={triggerAddConnection}
>
{isCompatible ? "Add API Key" : (providerId === "iflow" ? "OAuth" : "Add Connection")}
</Button>
@@ -1099,14 +1124,7 @@ export default function ProviderDetailPage() {
<Button
size="sm"
icon="add"
onClick={() => {
if (isOAuth) {
setShowOAuthModal(true);
return;
}
setAddConnectionError("");
setShowAddApiKeyModal(true);
}}
onClick={triggerAddConnection}
className="w-full sm:w-auto"
>
Add
@@ -1242,6 +1260,18 @@ export default function ProviderDetailPage() {
/>
)}
{/* AG Risk Confirmation Modal */}
<ConfirmModal
isOpen={showAgRiskModal}
onClose={() => setShowAgRiskModal(false)}
onConfirm={handleAgRiskConfirm}
title="Risk Notice"
message={providerInfo?.deprecationNotice}
confirmText="I Understand, Continue"
cancelText="Cancel"
variant="danger"
/>
{/* Confirm Modal */}
<ConfirmModal
isOpen={!!confirmState}

View File

@@ -2,7 +2,7 @@ import { NextResponse } from "next/server";
import { getProviderNodeById } from "@/models";
import { isOpenAICompatibleProvider, isAnthropicCompatibleProvider, isCustomEmbeddingProvider, AI_PROVIDERS } from "@/shared/constants/providers";
import { getDefaultModel } from "open-sse/config/providerModels.js";
import { resolveOllamaLocalHost, PROVIDERS } from "open-sse/config/providers.js";
import { resolveOllamaLocalHost, resolveXiaomiTokenplanBaseUrl, PROVIDERS } from "open-sse/config/providers.js";
import { openaiToCommandCode } from "open-sse/translator/request/openai-to-commandcode.js";
import { PROVIDER_ENDPOINTS } from "@/shared/constants/config";
import { normalizeProviderId } from "@/lib/providerNormalization";
@@ -382,7 +382,7 @@ export async function POST(request) {
chutes: "https://llm.chutes.ai/v1/models",
nvidia: "https://integrate.api.nvidia.com/v1/models",
"xiaomi-mimo": "https://api.xiaomimimo.com/v1/models",
"xiaomi-tokenplan": "https://token-plan-sgp.xiaomimimo.com/v1/models"
"xiaomi-tokenplan": `${resolveXiaomiTokenplanBaseUrl({ providerSpecificData })}/models`
};
const headers = {};
if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;