fix(commandcode): preserve images and reasoning_effort on /alpha/generate

Command Code dropped vision and ignored client effort through the router:
image blocks became "[image omitted]", HTTP image URLs were never inlined,
and reasoning_effort landed on the envelope wrapper instead of params (so the
DeepSeek family mapping remapped low -> high). The catalog also treated
deepseek/deepseek-v4.1-flash as text-only, so the vision adapter stole those
requests to another provider.

- Map OpenAI image_url / Claude image blocks (base64 or data-URI) to the
  native {type:"image", image:"data:...;base64,...", mimeType} generate block.
- Add FORMATS.COMMANDCODE to TARGETS_NEED_BASE64 so remote http(s) images are
  inlined by the existing SSRF-safe fetcher before translation.
- Write reasoning_effort inside params for targetFormat commandcode and pass
  low|medium|high|xhigh|max through unmapped; allow it in thinkingLevels.
- Provider-scoped capabilities for commandcode/cmc: vision except the CLI
  text-only denylist, thinkingFormat commandcode, so family patterns
  (deepseek-v4 -> thinkingFormat deepseek, vision false) no longer win.
- Quota Tracker: whoami + billing credits/subscriptions (credits vs plan cap,
  5h and weekly windows), labels from AI_PROVIDERS[].name.
This commit is contained in:
KhuatHieu
2026-09-16 20:16:32 +07:00
committed by decolua
parent 9300121366
commit 13b468b889
16 changed files with 529 additions and 17 deletions

View File

@@ -40,7 +40,7 @@ import {
} from "./utils";
import Card from "@/shared/components/Card";
import { ConfirmModal, EditConnectionModal } from "@/shared/components";
import { USAGE_SUPPORTED_PROVIDERS } from "@/shared/constants/providers";
import { USAGE_SUPPORTED_PROVIDERS, AI_PROVIDERS } from "@/shared/constants/providers";
import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard";
// Maps the stored providerSpecificData.authMethod to a human label for Kiro.
@@ -100,6 +100,10 @@ function getCodexResetCreditCount(quota) {
return Number.isFinite(count) ? Math.max(0, count) : 0;
}
function providerLabel(providerId) {
return AI_PROVIDERS[providerId]?.name || providerId;
}
function formatCreditDate(value) {
if (!value) return "N/A";
const date = new Date(value);
@@ -767,7 +771,7 @@ export default function ProviderLimits() {
};
const selectedProviderLabel =
providerFilter === "all" ? "All providers" : providerFilter;
providerFilter === "all" ? "All providers" : providerLabel(providerFilter);
const hasEligibleConnections = totals.eligibleConnections > 0;
const hasVisibleConnections = sortedConnections.length > 0;
const emptyState = getConnectionsEmptyMessage(
@@ -844,7 +848,7 @@ export default function ProviderLimits() {
fallbackText={providerFilter.slice(0, 2).toUpperCase()}
/>
)}
<span className="truncate capitalize hidden lg:inline">
<span className="truncate hidden lg:inline">
{selectedProviderLabel}
</span>
</span>
@@ -905,8 +909,8 @@ export default function ProviderLimits() {
className="size-6 rounded-md object-contain"
fallbackText={provider.slice(0, 2).toUpperCase()}
/>
<span className="font-medium capitalize">
{provider}
<span className="font-medium">
{providerLabel(provider)}
</span>
{providerFilter === provider && (
<span className="material-symbols-outlined ml-auto text-[20px]">
@@ -1079,8 +1083,8 @@ export default function ProviderLimits() {
/>
</div>
<div className="min-w-0">
<h3 className="text-sm font-semibold text-text-primary capitalize truncate">
{conn.provider}
<h3 className="text-sm font-semibold text-text-primary truncate">
{providerLabel(conn.provider)}
</h3>
{getConnectionLabel(conn) ? (
<p className="text-xs text-text-muted truncate">