# v0.5.20 (2026-07-07)
## Features - **Thinking**: per-model thinking level picker on provider page — appends `(level)` suffix to copied model names for forced reasoning effort across all formats (openai, claude, gemini, deepseek, kimi, qwen, zai, minimax, hunyuan, step) - **RTK**: add JS-native git-log filter (#2423) - **Caveman**: add targeted upstream-aligned style rules (#2424) - **i18n**: add Farsi (fa) language support (#2385) ## Fixes - **Thinking**: strip `(level)` suffix from upstream `body.model` so providers no longer reject requests - **Translator**: preserve developer instructions in openai-responses conversion (#2434) - **count_tokens**: count structured Anthropic blocks (#2419) - **Volcengine-ark**: clamp GLM-5 max_tokens to model output ceiling (#2428) - **Kimi**: normalize reasoning_effort to backend enum (#2427) - **Claude**: reconcile max_tokens vs thinking budget and lift per-model ceiling (#2381) - **Kiro**: deliver system prompt natively, add Opus 4.5/4.7/4.8, tolerate dash version ids (#2366) - **Headroom**: proxy dashboard through app (#2372) - **MITM**: recover from stale lock file on server start
This commit is contained in:
19
CHANGELOG.md
19
CHANGELOG.md
@@ -1,3 +1,22 @@
|
|||||||
|
# v0.5.20 (2026-07-07)
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- **Thinking**: per-model thinking level picker on provider page — appends `(level)` suffix to copied model names for forced reasoning effort across all formats (openai, claude, gemini, deepseek, kimi, qwen, zai, minimax, hunyuan, step)
|
||||||
|
- **RTK**: add JS-native git-log filter (#2423)
|
||||||
|
- **Caveman**: add targeted upstream-aligned style rules (#2424)
|
||||||
|
- **i18n**: add Farsi (fa) language support (#2385)
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
- **Thinking**: strip `(level)` suffix from upstream `body.model` so providers no longer reject requests
|
||||||
|
- **Translator**: preserve developer instructions in openai-responses conversion (#2434)
|
||||||
|
- **count_tokens**: count structured Anthropic blocks (#2419)
|
||||||
|
- **Volcengine-ark**: clamp GLM-5 max_tokens to model output ceiling (#2428)
|
||||||
|
- **Kimi**: normalize reasoning_effort to backend enum (#2427)
|
||||||
|
- **Claude**: reconcile max_tokens vs thinking budget and lift per-model ceiling (#2381)
|
||||||
|
- **Kiro**: deliver system prompt natively, add Opus 4.5/4.7/4.8, tolerate dash version ids (#2366)
|
||||||
|
- **Headroom**: proxy dashboard through app (#2372)
|
||||||
|
- **MITM**: recover from stale lock file on server start
|
||||||
|
|
||||||
# v0.5.18 (2026-07-03)
|
# v0.5.18 (2026-07-03)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "9router",
|
"name": "9router",
|
||||||
"version": "0.5.18",
|
"version": "0.5.20",
|
||||||
"description": "9Router CLI - Start and manage 9Router server",
|
"description": "9Router CLI - Start and manage 9Router server",
|
||||||
"bin": {
|
"bin": {
|
||||||
"9router": "./cli.js"
|
"9router": "./cli.js"
|
||||||
|
|||||||
@@ -63,14 +63,19 @@ export function getModelType(aliasOrId, modelId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getModelUpstreamId(aliasOrId, modelId) {
|
export function getModelUpstreamId(aliasOrId, modelId) {
|
||||||
|
// Split off thinking suffix "(level)" so lookup hits the base id; re-append it to
|
||||||
|
// the result so downstream applyThinking still sees the suffix (body.model is stripped separately).
|
||||||
|
const sufMatch = typeof modelId === "string" ? modelId.match(/\([^()]+\)\s*$/) : null;
|
||||||
|
const suffix = sufMatch ? sufMatch[0] : "";
|
||||||
|
const baseId = suffix ? modelId.slice(0, sufMatch.index).trim() : modelId;
|
||||||
const models = PROVIDER_MODELS[aliasOrId];
|
const models = PROVIDER_MODELS[aliasOrId];
|
||||||
const found = findModel(models, modelId, aliasOrId);
|
const found = findModel(models, baseId, aliasOrId);
|
||||||
if (found?.upstreamModelId) return found.upstreamModelId;
|
if (found?.upstreamModelId) return found.upstreamModelId + suffix;
|
||||||
if (found?.id) return found.id;
|
if (found?.id) return found.id + suffix;
|
||||||
if (aliasOrId === "cx" && typeof modelId === "string" && modelId.endsWith(CODEX_REVIEW_SUFFIX)) {
|
if (aliasOrId === "cx" && typeof baseId === "string" && baseId.endsWith(CODEX_REVIEW_SUFFIX)) {
|
||||||
return modelId.slice(0, -CODEX_REVIEW_SUFFIX.length);
|
return baseId.slice(0, -CODEX_REVIEW_SUFFIX.length) + suffix;
|
||||||
}
|
}
|
||||||
return modelId;
|
return baseId + suffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getModelQuotaFamily(aliasOrId, modelId) {
|
export function getModelQuotaFamily(aliasOrId, modelId) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { detectFormat, getTargetFormat, resolveTransport } from "../services/provider.js";
|
import { detectFormat, getTargetFormat, resolveTransport } from "../services/provider.js";
|
||||||
import { translateRequest } from "../translator/index.js";
|
import { translateRequest } from "../translator/index.js";
|
||||||
|
import { stripThinkingSuffix } from "../translator/concerns/thinkingUnified.js";
|
||||||
import { FORMATS } from "../translator/formats.js";
|
import { FORMATS } from "../translator/formats.js";
|
||||||
import { normalizeClaudePassthrough } from "../translator/formats/claude.js";
|
import { normalizeClaudePassthrough } from "../translator/formats/claude.js";
|
||||||
import { COLORS } from "../utils/stream.js";
|
import { COLORS } from "../utils/stream.js";
|
||||||
@@ -123,9 +124,9 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred
|
|||||||
let toolNameMap;
|
let toolNameMap;
|
||||||
if (passthrough) {
|
if (passthrough) {
|
||||||
log?.debug?.("PASSTHROUGH", `${clientTool} → ${provider} | native lossless`);
|
log?.debug?.("PASSTHROUGH", `${clientTool} → ${provider} | native lossless`);
|
||||||
translatedBody = { ...body, model: upstreamModel };
|
translatedBody = { ...body, model: stripThinkingSuffix(upstreamModel) };
|
||||||
// Normalize newer Cowork/CC beta shapes (adaptive thinking, mid-conversation system) the API rejects
|
// Normalize newer Cowork/CC beta shapes (adaptive thinking, mid-conversation system) the API rejects
|
||||||
if (clientTool === "claude") normalizeClaudePassthrough(translatedBody, upstreamModel);
|
if (clientTool === "claude") normalizeClaudePassthrough(translatedBody, translatedBody.model);
|
||||||
} else {
|
} else {
|
||||||
translatedBody = translateRequest(sourceFormat, targetFormat, upstreamModel, body, stream, credentials, provider, reqLogger, stripList, connectionId, clientTool);
|
translatedBody = translateRequest(sourceFormat, targetFormat, upstreamModel, body, stream, credentials, provider, reqLogger, stripList, connectionId, clientTool);
|
||||||
if (!translatedBody) {
|
if (!translatedBody) {
|
||||||
@@ -134,7 +135,7 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred
|
|||||||
}
|
}
|
||||||
toolNameMap = translatedBody._toolNameMap;
|
toolNameMap = translatedBody._toolNameMap;
|
||||||
delete translatedBody._toolNameMap;
|
delete translatedBody._toolNameMap;
|
||||||
translatedBody.model = upstreamModel;
|
translatedBody.model = stripThinkingSuffix(upstreamModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dedupe duplicate built-in tools when equivalent MCP tools are present (Claude clients only).
|
// Dedupe duplicate built-in tools when equivalent MCP tools are present (Claude clients only).
|
||||||
|
|||||||
@@ -60,12 +60,10 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
models: [
|
models: [
|
||||||
|
{ id: "claude-fable-5", name: "Claude Fable 5" },
|
||||||
|
{ id: "claude-sonnet-5", name: "Claude Sonnet 5" },
|
||||||
{ id: "claude-opus-4-8", name: "Claude Opus 4.8" },
|
{ id: "claude-opus-4-8", name: "Claude Opus 4.8" },
|
||||||
{ id: "claude-opus-4-7", name: "Claude Opus 4.7" },
|
{ id: "claude-opus-4-7", name: "Claude Opus 4.7" },
|
||||||
{ id: "claude-opus-4-6", name: "Claude Opus 4.6" },
|
|
||||||
{ id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" },
|
|
||||||
{ id: "claude-opus-4-5-20251101", name: "Claude 4.5 Opus" },
|
|
||||||
{ id: "claude-sonnet-4-5-20250929", name: "Claude 4.5 Sonnet" },
|
|
||||||
{ id: "claude-haiku-4-5-20251001", name: "Claude 4.5 Haiku" },
|
{ id: "claude-haiku-4-5-20251001", name: "Claude 4.5 Haiku" },
|
||||||
],
|
],
|
||||||
oauth: {
|
oauth: {
|
||||||
|
|||||||
46
open-sse/providers/thinkingLevels.js
Normal file
46
open-sse/providers/thinkingLevels.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
// Resolve valid thinking levels per model — drives UI level picker (suffix "model(level)").
|
||||||
|
// Reuses capabilities.js (thinkingFormat/canDisable) so this file only maps format→levels (DRY).
|
||||||
|
import { getCapabilitiesForModel } from "./capabilities.js";
|
||||||
|
import { matchPattern } from "./pricing.js";
|
||||||
|
|
||||||
|
// Shared level sets (deduped) — verified against provider docs + wire in thinkingUnified.applyFormat.
|
||||||
|
const L = {
|
||||||
|
base: ["none", "low", "medium", "high"], // qwen, step, hunyuan, gemini-budget
|
||||||
|
onOff: ["none", "thinking"], // zai (binary), minimax (adaptive)
|
||||||
|
openai: ["none", "minimal", "low", "medium", "high", "xhigh"], // GPT-5.x / o-series (no "max")
|
||||||
|
levelMax: ["none", "low", "medium", "high", "max"], // claude-adaptive, kimi
|
||||||
|
budgetX: ["none", "low", "medium", "high", "xhigh", "max"], // claude-budget
|
||||||
|
gemini: ["minimal", "low", "medium", "high"], // gemini-3 thinkingLevel (no disable)
|
||||||
|
hiMax: ["none", "high", "max"], // deepseek (low/med→high, xhigh→max)
|
||||||
|
};
|
||||||
|
|
||||||
|
// thinkingFormat → valid selectable levels (source of truth for UI options).
|
||||||
|
const FORMAT_LEVELS = {
|
||||||
|
openai: L.openai,
|
||||||
|
"claude-adaptive": L.levelMax,
|
||||||
|
"claude-budget": L.budgetX,
|
||||||
|
"gemini-level": L.gemini,
|
||||||
|
"gemini-budget": L.base,
|
||||||
|
zai: L.onOff,
|
||||||
|
qwen: L.base,
|
||||||
|
kimi: L.levelMax,
|
||||||
|
deepseek: L.hiMax,
|
||||||
|
minimax: L.onOff,
|
||||||
|
hunyuan: L.base,
|
||||||
|
step: L.base,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Model-name pattern overrides (glob, first match wins) — more precise than format default.
|
||||||
|
const PATTERN_THINKING = [
|
||||||
|
{ pattern: "*codex*", levels: ["low", "medium", "high", "xhigh"] }, // codex cannot disable thinking
|
||||||
|
];
|
||||||
|
|
||||||
|
// Returns valid thinking levels for a model, or null when the model has no reasoning.
|
||||||
|
export function getThinkingLevels(provider, model) {
|
||||||
|
const caps = getCapabilitiesForModel(provider, model);
|
||||||
|
if (!caps.reasoning) return null;
|
||||||
|
const hit = PATTERN_THINKING.find((p) => matchPattern(p.pattern, model));
|
||||||
|
let levels = hit?.levels || FORMAT_LEVELS[caps.thinkingFormat] || L.base;
|
||||||
|
if (caps.thinkingCanDisable === false) levels = levels.filter((l) => l !== "none");
|
||||||
|
return levels;
|
||||||
|
}
|
||||||
@@ -20,6 +20,13 @@ const FORMAT_TO_NATIVE = {
|
|||||||
kiro: "kiro",
|
kiro: "kiro",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Strip a trailing thinking suffix "model(value)" → "model" (no-op when absent).
|
||||||
|
export function stripThinkingSuffix(model) {
|
||||||
|
if (typeof model !== "string") return model;
|
||||||
|
const m = model.match(/^(.*)\([^()]+\)\s*$/);
|
||||||
|
return m ? m[1].trim() : model;
|
||||||
|
}
|
||||||
|
|
||||||
// Parse model-name suffix "model(value)" → { cleanModel, override }.
|
// Parse model-name suffix "model(value)" → { cleanModel, override }.
|
||||||
// value: level name (high) | number (8192) | auto | none. null override when absent.
|
// value: level name (high) | number (8192) | auto | none. null override when absent.
|
||||||
export function parseSuffix(model) {
|
export function parseSuffix(model) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "9router-app",
|
"name": "9router-app",
|
||||||
"version": "0.5.18",
|
"version": "0.5.20",
|
||||||
"description": "9Router web dashboard",
|
"description": "9Router web dashboard",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { CapacityBadges } from "@/shared/components";
|
import { CapacityBadges } from "@/shared/components";
|
||||||
|
|
||||||
export default function ModelRow({ model, fullModel, alias, copied, onCopy, testStatus, isCustom, isFree, onDeleteAlias, onTest, isTesting, onDisable, caps }) {
|
export default function ModelRow({ model, fullModel, alias, copied, onCopy, testStatus, isCustom, isFree, onDeleteAlias, onTest, isTesting, onDisable, caps, thinkingSuffix }) {
|
||||||
|
const displayModel = thinkingSuffix ? `${fullModel}(${thinkingSuffix})` : fullModel;
|
||||||
const borderColor = testStatus === "ok"
|
const borderColor = testStatus === "ok"
|
||||||
? "border-green-500/40"
|
? "border-green-500/40"
|
||||||
: testStatus === "error"
|
: testStatus === "error"
|
||||||
@@ -24,7 +25,7 @@ export default function ModelRow({ model, fullModel, alias, copied, onCopy, test
|
|||||||
{testStatus === "ok" ? "check_circle" : testStatus === "error" ? "cancel" : "smart_toy"}
|
{testStatus === "ok" ? "check_circle" : testStatus === "error" ? "cancel" : "smart_toy"}
|
||||||
</span>
|
</span>
|
||||||
<div className="flex min-w-0 flex-1 flex-col gap-1">
|
<div className="flex min-w-0 flex-1 flex-col gap-1">
|
||||||
<code className="max-w-[72vw] truncate rounded bg-sidebar px-1.5 py-0.5 font-mono text-xs text-text-muted sm:max-w-[360px]">{fullModel}</code>
|
<code className="max-w-[72vw] truncate rounded bg-sidebar px-1.5 py-0.5 font-mono text-xs text-text-muted sm:max-w-[360px]">{displayModel}</code>
|
||||||
<span className="flex min-w-0 items-center text-[9px] gap-1 pl-1">
|
<span className="flex min-w-0 items-center text-[9px] gap-1 pl-1">
|
||||||
{model.name && <span className="truncate text-[9px] italic text-text-muted/70">{model.name}</span>}
|
{model.name && <span className="truncate text-[9px] italic text-text-muted/70">{model.name}</span>}
|
||||||
<CapacityBadges caps={caps} colorOverride="text-text-muted/70" size={12} />
|
<CapacityBadges caps={caps} colorOverride="text-text-muted/70" size={12} />
|
||||||
@@ -48,7 +49,7 @@ export default function ModelRow({ model, fullModel, alias, copied, onCopy, test
|
|||||||
)}
|
)}
|
||||||
<div className="relative shrink-0 group/btn">
|
<div className="relative shrink-0 group/btn">
|
||||||
<button
|
<button
|
||||||
onClick={() => onCopy(fullModel, `model-${model.id}`)}
|
onClick={() => onCopy(displayModel, `model-${model.id}`)}
|
||||||
className="rounded p-0.5 text-text-muted hover:bg-sidebar hover:text-primary"
|
className="rounded p-0.5 text-text-muted hover:bg-sidebar hover:text-primary"
|
||||||
>
|
>
|
||||||
<span className="material-symbols-outlined text-sm">
|
<span className="material-symbols-outlined text-sm">
|
||||||
@@ -97,4 +98,5 @@ ModelRow.propTypes = {
|
|||||||
isTesting: PropTypes.bool,
|
isTesting: PropTypes.bool,
|
||||||
onDisable: PropTypes.func,
|
onDisable: PropTypes.func,
|
||||||
caps: PropTypes.object,
|
caps: PropTypes.object,
|
||||||
|
thinkingSuffix: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ import { useParams, useRouter } from "next/navigation";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { Card, Button, Badge, Input, Modal, CardSkeleton, OAuthModal, KiroOAuthWrapper, CursorAuthModal, IFlowCookieModal, GitLabAuthModal, Toggle, Select, EditConnectionModal, NoAuthProxyCard, ConfirmModal } from "@/shared/components";
|
import { Card, Button, Badge, Input, Modal, CardSkeleton, OAuthModal, KiroOAuthWrapper, CursorAuthModal, IFlowCookieModal, GitLabAuthModal, Toggle, Select, EditConnectionModal, NoAuthProxyCard, ConfirmModal } from "@/shared/components";
|
||||||
import { OAUTH_PROVIDERS, APIKEY_PROVIDERS, FREE_PROVIDERS, FREE_TIER_PROVIDERS, WEB_COOKIE_PROVIDERS, getProviderAlias, isOpenAICompatibleProvider, isAnthropicCompatibleProvider, AI_PROVIDERS, THINKING_CONFIG } from "@/shared/constants/providers";
|
import { OAUTH_PROVIDERS, APIKEY_PROVIDERS, FREE_PROVIDERS, FREE_TIER_PROVIDERS, WEB_COOKIE_PROVIDERS, getProviderAlias, isOpenAICompatibleProvider, isAnthropicCompatibleProvider, AI_PROVIDERS } from "@/shared/constants/providers";
|
||||||
import { getModelsByProviderId, getModelKind } from "@/shared/constants/models";
|
import { getModelsByProviderId, getModelKind } from "@/shared/constants/models";
|
||||||
|
import { getThinkingLevels } from "open-sse/providers/thinkingLevels.js";
|
||||||
import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard";
|
import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard";
|
||||||
import { useModelCaps } from "@/shared/hooks/useModelCaps";
|
import { useModelCaps } from "@/shared/hooks/useModelCaps";
|
||||||
import { translate } from "@/i18n/runtime";
|
import { translate } from "@/i18n/runtime";
|
||||||
@@ -149,7 +150,21 @@ export default function ProviderDetailPage() {
|
|||||||
const hasDualAuthModes = !isCompatible && isOAuth && supportsApiKeyAuth;
|
const hasDualAuthModes = !isCompatible && isOAuth && supportsApiKeyAuth;
|
||||||
const oauthConnectionLabel = providerId === "xai" ? "Grok Build OAuth" : "OAuth";
|
const oauthConnectionLabel = providerId === "xai" ? "Grok Build OAuth" : "OAuth";
|
||||||
const apiKeyConnectionLabel = providerId === "xai" ? "xAI API Key" : "API Key";
|
const apiKeyConnectionLabel = providerId === "xai" ? "xAI API Key" : "API Key";
|
||||||
const thinkingConfig = AI_PROVIDERS[providerId]?.thinkingConfig || THINKING_CONFIG.extended;
|
// Resolve suffix "(level)" for a model when a thinking level is picked and the model supports it.
|
||||||
|
const resolveThinkingSuffix = (modelId) => {
|
||||||
|
if (!thinkingMode || thinkingMode === "auto") return null;
|
||||||
|
const levels = getThinkingLevels(providerId, modelId);
|
||||||
|
return levels && levels.includes(thinkingMode) ? thinkingMode : null;
|
||||||
|
};
|
||||||
|
// Union of levels across this provider's reasoning models — drives the level picker options.
|
||||||
|
const providerThinkingLevels = (() => {
|
||||||
|
const set = new Set();
|
||||||
|
for (const m of models) {
|
||||||
|
const lv = getThinkingLevels(providerId, m.id);
|
||||||
|
if (lv) lv.forEach((l) => { if (l !== "none") set.add(l); });
|
||||||
|
}
|
||||||
|
return set.size ? ["auto", ...[...set]] : null;
|
||||||
|
})();
|
||||||
|
|
||||||
const providerStorageAlias = isCompatible ? providerId : providerAlias;
|
const providerStorageAlias = isCompatible ? providerId : providerAlias;
|
||||||
const providerDisplayAlias = isCompatible
|
const providerDisplayAlias = isCompatible
|
||||||
@@ -1065,6 +1080,7 @@ export default function ProviderDetailPage() {
|
|||||||
isCustom
|
isCustom
|
||||||
isFree={false}
|
isFree={false}
|
||||||
caps={getCaps(`${providerId}/${model.id}`)}
|
caps={getCaps(`${providerId}/${model.id}`)}
|
||||||
|
thinkingSuffix={resolveThinkingSuffix(model.id)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
@@ -1090,6 +1106,7 @@ export default function ProviderDetailPage() {
|
|||||||
isFree={model.isFree}
|
isFree={model.isFree}
|
||||||
onDisable={() => handleDisableModel(model.id)}
|
onDisable={() => handleDisableModel(model.id)}
|
||||||
caps={getCaps(`${providerId}/${model.id}`)}
|
caps={getCaps(`${providerId}/${model.id}`)}
|
||||||
|
thinkingSuffix={resolveThinkingSuffix(model.id)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -1395,21 +1412,6 @@ export default function ProviderDetailPage() {
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{/* Thinking config */}
|
|
||||||
{/* {thinkingConfig && (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span className="text-xs text-text-muted font-medium">Thinking</span>
|
|
||||||
<select
|
|
||||||
value={thinkingMode}
|
|
||||||
onChange={(e) => handleThinkingModeChange(e.target.value)}
|
|
||||||
className="text-xs px-2 py-1 border border-border rounded-md bg-background focus:outline-none focus:border-primary"
|
|
||||||
>
|
|
||||||
{thinkingConfig.options.map((opt) => (
|
|
||||||
<option key={opt} value={opt}>{opt.charAt(0).toUpperCase() + opt.slice(1)}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
)} */}
|
|
||||||
{/* Round Robin toggle */}
|
{/* Round Robin toggle */}
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<span className="text-xs text-text-muted font-medium">Round Robin</span>
|
<span className="text-xs text-text-muted font-medium">Round Robin</span>
|
||||||
@@ -1580,9 +1582,23 @@ export default function ProviderDetailPage() {
|
|||||||
{/* Models */}
|
{/* Models */}
|
||||||
<Card>
|
<Card>
|
||||||
<div className="mb-4 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
<div className="mb-4 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
||||||
<h2 className="text-lg font-semibold">
|
<div className="flex items-center gap-3">
|
||||||
{"Available Models"}
|
<h2 className="text-lg font-semibold">
|
||||||
</h2>
|
{"Available Models"}
|
||||||
|
</h2>
|
||||||
|
{providerThinkingLevels && (
|
||||||
|
<select
|
||||||
|
value={thinkingMode}
|
||||||
|
onChange={(e) => handleThinkingModeChange(e.target.value)}
|
||||||
|
title="Appends (level) suffix to copied model names"
|
||||||
|
className="rounded-md border border-border bg-background px-2 py-1 text-xs focus:border-primary focus:outline-none"
|
||||||
|
>
|
||||||
|
{providerThinkingLevels.map((opt) => (
|
||||||
|
<option key={opt} value={opt}>{`Thinking: ${opt.charAt(0).toUpperCase() + opt.slice(1)}`}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
{!isCompatible && (() => {
|
{!isCompatible && (() => {
|
||||||
const allIds = [
|
const allIds = [
|
||||||
...models,
|
...models,
|
||||||
|
|||||||
@@ -95,13 +95,15 @@ export const CLI_TOOLS = {
|
|||||||
model: "ANTHROPIC_MODEL",
|
model: "ANTHROPIC_MODEL",
|
||||||
opusModel: "ANTHROPIC_DEFAULT_OPUS_MODEL",
|
opusModel: "ANTHROPIC_DEFAULT_OPUS_MODEL",
|
||||||
sonnetModel: "ANTHROPIC_DEFAULT_SONNET_MODEL",
|
sonnetModel: "ANTHROPIC_DEFAULT_SONNET_MODEL",
|
||||||
|
fableModel: "ANTHROPIC_DEFAULT_FABLE_MODEL",
|
||||||
haikuModel: "ANTHROPIC_DEFAULT_HAIKU_MODEL",
|
haikuModel: "ANTHROPIC_DEFAULT_HAIKU_MODEL",
|
||||||
},
|
},
|
||||||
modelAliases: ["default", "sonnet", "opus", "haiku", "opusplan"],
|
modelAliases: ["default", "sonnet", "opus", "fable", "haiku", "opusplan"],
|
||||||
settingsFile: "~/.claude/settings.json",
|
settingsFile: "~/.claude/settings.json",
|
||||||
defaultModels: [
|
defaultModels: [
|
||||||
{ id: "opus", name: "Claude Opus", alias: "opus", envKey: "ANTHROPIC_DEFAULT_OPUS_MODEL", defaultValue: "cc/claude-opus-4-6" },
|
{ id: "opus", name: "Claude Opus", alias: "opus", envKey: "ANTHROPIC_DEFAULT_OPUS_MODEL", defaultValue: "cc/claude-opus-4-8" },
|
||||||
{ id: "sonnet", name: "Claude Sonnet", alias: "sonnet", envKey: "ANTHROPIC_DEFAULT_SONNET_MODEL", defaultValue: "cc/claude-sonnet-4-6" },
|
{ id: "sonnet", name: "Claude Sonnet", alias: "sonnet", envKey: "ANTHROPIC_DEFAULT_SONNET_MODEL", defaultValue: "cc/claude-sonnet-5" },
|
||||||
|
{ id: "fable", name: "Claude Fable", alias: "fable", envKey: "ANTHROPIC_DEFAULT_FABLE_MODEL", defaultValue: "cc/claude-fable-5" },
|
||||||
{ id: "haiku", name: "Claude Haiku", alias: "haiku", envKey: "ANTHROPIC_DEFAULT_HAIKU_MODEL", defaultValue: "cc/claude-haiku-4-5-20251001" },
|
{ id: "haiku", name: "Claude Haiku", alias: "haiku", envKey: "ANTHROPIC_DEFAULT_HAIKU_MODEL", defaultValue: "cc/claude-haiku-4-5-20251001" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user