@bitkyc08/opencodex 2.7.17 → 2.7.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gui/dist/assets/index-BUBsQALh.css +1 -0
- package/gui/dist/assets/index-DEbBFENM.js +40 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic-image-guard.ts +63 -7
- package/src/adapters/anthropic-image-normalize.ts +383 -0
- package/src/adapters/anthropic.ts +7 -2
- package/src/adapters/base.ts +6 -0
- package/src/adapters/cursor/exec-policy.ts +9 -1
- package/src/adapters/cursor/live-transport.ts +19 -11
- package/src/adapters/cursor/protobuf-request.ts +7 -6
- package/src/adapters/kiro-images.ts +94 -0
- package/src/adapters/kiro.ts +6 -2
- package/src/adapters/openai-chat.ts +49 -0
- package/src/adapters/openai-responses.ts +128 -2
- package/src/cli/claude.ts +3 -0
- package/src/codex/catalog.ts +11 -0
- package/src/providers/registry.ts +27 -2
- package/src/server/claude-messages.ts +11 -0
- package/src/server/image-retry.ts +42 -0
- package/src/server/management-api.ts +27 -0
- package/src/server/responses.ts +74 -22
- package/src/server/system-env.ts +7 -3
- package/src/types.ts +22 -4
- package/src/web-search/index.ts +8 -5
- package/gui/dist/assets/index-Cq8maiJf.css +0 -1
- package/gui/dist/assets/index-m4o3xsSn.js +0 -40
package/src/server/responses.ts
CHANGED
|
@@ -41,6 +41,7 @@ import { isUsageDebugEnabled } from "../usage/debug";
|
|
|
41
41
|
import { readJsonRequestBody, DecompressedBodyTooLargeError, UnsupportedContentEncodingError } from "./request-decompress";
|
|
42
42
|
import { resolveAdapter, resolveWireProtocolOverride } from "./adapter-resolve";
|
|
43
43
|
import { hasKeyPoolFailover, rotateProviderTransportOn429 } from "../providers/key-failover";
|
|
44
|
+
import { shouldAttemptImageTierRetry } from "./image-retry";
|
|
44
45
|
import { resolveProviderTransport } from "../providers/xai-transport";
|
|
45
46
|
import type { WsData } from "./ws-bridge";
|
|
46
47
|
import { registerTurn, trackStreamLifetime, unregisterTurn } from "./lifecycle";
|
|
@@ -466,6 +467,22 @@ export async function handleResponses(
|
|
|
466
467
|
logCtx.configuredServiceTier = readConfiguredCodexServiceTier();
|
|
467
468
|
logCtx.configuredSpeedLabel = requestLogSpeedLabel(logCtx.configuredServiceTier);
|
|
468
469
|
|
|
470
|
+
// Shadow call intercept: rewrite Codex Desktop's hard-coded gpt-5.4-mini helper calls
|
|
471
|
+
const _sci = config.shadowCallIntercept;
|
|
472
|
+
if (_sci?.enabled && _sci.model && parsed.modelId.startsWith("gpt-5.4-mini")) {
|
|
473
|
+
const _sciOriginal = parsed.modelId;
|
|
474
|
+
parsed.modelId = _sci.model;
|
|
475
|
+
if (parsed._rawBody && typeof parsed._rawBody === "object") {
|
|
476
|
+
(parsed._rawBody as { model?: string }).model = _sci.model;
|
|
477
|
+
}
|
|
478
|
+
// Force effort to low for shadow/helper calls (matching upstream behavior)
|
|
479
|
+
parsed.options.reasoning = "low";
|
|
480
|
+
if (parsed._rawBody && typeof parsed._rawBody === "object") {
|
|
481
|
+
(parsed._rawBody as Record<string, unknown>).reasoning = { effort: "low" };
|
|
482
|
+
}
|
|
483
|
+
(logCtx as unknown as Record<string, unknown>).shadowCallRewrittenFrom = _sciOriginal;
|
|
484
|
+
}
|
|
485
|
+
|
|
469
486
|
let route;
|
|
470
487
|
try {
|
|
471
488
|
route = routeModel(config, parsed.modelId);
|
|
@@ -941,29 +958,22 @@ export async function handleResponses(
|
|
|
941
958
|
}
|
|
942
959
|
|
|
943
960
|
if (!upstreamResponse.ok) {
|
|
944
|
-
//
|
|
945
|
-
//
|
|
946
|
-
//
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
961
|
+
// Recovery loop: multi-key 429 failover + at most ONE anthropic 413 tightened retry
|
|
962
|
+
// (devlog/260714_image_normalization_pipeline/030). One mutable activeAdapter serves
|
|
963
|
+
// both paths so a 429→413 sequence never rebuilds against a stale pre-rotation
|
|
964
|
+
// adapter, and imageTierBias — once armed — rides EVERY subsequent rebuild so a
|
|
965
|
+
// 413→429 rotation cannot silently undo the tightening.
|
|
966
|
+
let activeAdapter = adapter;
|
|
967
|
+
let imageTierBias = 0;
|
|
968
|
+
let imageRetryAttempted = false;
|
|
969
|
+
const rebuildAndRefetch = async (): Promise<Response | { failed: Response }> => {
|
|
970
|
+
const retryRequest = await activeAdapter.buildRequest(parsed, {
|
|
971
|
+
headers: selectedForwardHeaders,
|
|
972
|
+
...(imageTierBias > 0 ? { imageTierBias } : {}),
|
|
953
973
|
});
|
|
954
|
-
if (!rotated) break;
|
|
955
|
-
// Release the failed response's socket before retrying; unread bodies otherwise linger
|
|
956
|
-
// until runtime cleanup (one per rotated key under a rate-limit storm).
|
|
957
|
-
try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
|
|
958
|
-
route.provider = rotated;
|
|
959
|
-
const retryAdapter = resolveAdapter(
|
|
960
|
-
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider),
|
|
961
|
-
config.cacheRetention,
|
|
962
|
-
);
|
|
963
|
-
const retryRequest = await retryAdapter.buildRequest(parsed, { headers: selectedForwardHeaders });
|
|
964
974
|
try {
|
|
965
|
-
|
|
966
|
-
? await
|
|
975
|
+
return activeAdapter.fetchResponse
|
|
976
|
+
? await activeAdapter.fetchResponse(retryRequest, { abortSignal: upstream.signal, timeoutMs: connectMs, stream: parsed.stream })
|
|
967
977
|
: await fetchWithHeaderTimeout(retryRequest.url, {
|
|
968
978
|
method: retryRequest.method, headers: retryRequest.headers, body: retryRequest.body,
|
|
969
979
|
}, upstream.signal, connectMs, parsed.stream);
|
|
@@ -973,8 +983,50 @@ export async function handleResponses(
|
|
|
973
983
|
const msg = err instanceof Error && err.name === "TimeoutError"
|
|
974
984
|
? `Provider connect timeout after ${connectMs}ms`
|
|
975
985
|
: `Provider unreachable: ${err instanceof Error ? err.message : String(err)}`;
|
|
976
|
-
return formatErrorResponse(502, "upstream_error", msg);
|
|
986
|
+
return { failed: formatErrorResponse(502, "upstream_error", msg) };
|
|
987
|
+
}
|
|
988
|
+
};
|
|
989
|
+
recovery: for (;;) {
|
|
990
|
+
// Multi-key 429 failover: rotate to the next pool key (cooldown-aware) and retry the
|
|
991
|
+
// SAME request once per remaining key. OAuth/forward providers and single-key pools
|
|
992
|
+
// return null immediately, so this stays a no-op for them (src/providers/key-failover.ts).
|
|
993
|
+
while (upstreamResponse.status === 429 && hasKeyPoolFailover(route.provider)) {
|
|
994
|
+
const rotated = rotateProviderTransportOn429(config, route.providerName, {
|
|
995
|
+
retryAfter: upstreamResponse.headers.get("retry-after"),
|
|
996
|
+
now: Date.now(),
|
|
997
|
+
attemptedKey: route.provider.apiKey,
|
|
998
|
+
promptCacheKey: parsed.options.promptCacheKey,
|
|
999
|
+
});
|
|
1000
|
+
if (!rotated) break;
|
|
1001
|
+
// Release the failed response's socket before retrying; unread bodies otherwise linger
|
|
1002
|
+
// until runtime cleanup (one per rotated key under a rate-limit storm).
|
|
1003
|
+
try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
|
|
1004
|
+
route.provider = rotated;
|
|
1005
|
+
activeAdapter = resolveAdapter(
|
|
1006
|
+
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider),
|
|
1007
|
+
config.cacheRetention,
|
|
1008
|
+
);
|
|
1009
|
+
const result = await rebuildAndRefetch();
|
|
1010
|
+
if ("failed" in result) return result.failed;
|
|
1011
|
+
upstreamResponse = result;
|
|
1012
|
+
}
|
|
1013
|
+
// Anthropic 413 request_too_large: rebuild once with every image one tier lower
|
|
1014
|
+
// (spiral guard: single attempt). The biased response re-enters the 429 check above.
|
|
1015
|
+
if (shouldAttemptImageTierRetry({
|
|
1016
|
+
status: upstreamResponse.status,
|
|
1017
|
+
adapterName: activeAdapter.name,
|
|
1018
|
+
parsed,
|
|
1019
|
+
alreadyAttempted: imageRetryAttempted,
|
|
1020
|
+
})) {
|
|
1021
|
+
imageRetryAttempted = true;
|
|
1022
|
+
imageTierBias = 1;
|
|
1023
|
+
try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
|
|
1024
|
+
const result = await rebuildAndRefetch();
|
|
1025
|
+
if ("failed" in result) return result.failed;
|
|
1026
|
+
upstreamResponse = result;
|
|
1027
|
+
continue recovery;
|
|
977
1028
|
}
|
|
1029
|
+
break;
|
|
978
1030
|
}
|
|
979
1031
|
if (!upstreamResponse.ok) {
|
|
980
1032
|
const errorText = await upstreamResponse.text().catch(() => "unknown error");
|
package/src/server/system-env.ts
CHANGED
|
@@ -25,13 +25,15 @@ function writeShellEnvFile(port: number, config: OcxConfig, modelEnv: Record<str
|
|
|
25
25
|
`export ANTHROPIC_BASE_URL=${shellValue(`http://127.0.0.1:${port}`)}`,
|
|
26
26
|
`export CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=${shellValue("1")}`,
|
|
27
27
|
];
|
|
28
|
-
if (config.apiKeys?.length) {
|
|
29
|
-
lines.push(`export ANTHROPIC_AUTH_TOKEN=${shellValue(config.apiKeys[0].key)}`);
|
|
30
|
-
}
|
|
31
28
|
// New lever keys are CONDITIONAL exports (audit 139 R2#1): a value the user already
|
|
32
29
|
// exported in their shell wins even though launchctl knows nothing about it.
|
|
33
30
|
const conditional = (name: string, value: string) =>
|
|
34
31
|
`[ -z "\${${name}+x}" ] && export ${name}=${shellValue(value)}`;
|
|
32
|
+
if (config.apiKeys?.length) {
|
|
33
|
+
lines.push(`export ANTHROPIC_AUTH_TOKEN=${shellValue(config.apiKeys[0].key)}`);
|
|
34
|
+
} else if (config.claudeCode?.authMode === "proxy") {
|
|
35
|
+
lines.push(conditional("ANTHROPIC_AUTH_TOKEN", "opencodex-proxy"));
|
|
36
|
+
}
|
|
35
37
|
// Model slots (default + tiers + legacy small-fast) with [1m] applied (devlog 260712 B2).
|
|
36
38
|
if (modelEnv.ANTHROPIC_MODEL) {
|
|
37
39
|
lines.push(`export ANTHROPIC_MODEL=${shellValue(modelEnv.ANTHROPIC_MODEL)}`);
|
|
@@ -238,6 +240,8 @@ export async function injectSystemEnv(port: number, config: OcxConfig): Promise<
|
|
|
238
240
|
inject("CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY", "1");
|
|
239
241
|
if (config.apiKeys?.length) {
|
|
240
242
|
inject("ANTHROPIC_AUTH_TOKEN", config.apiKeys[0].key);
|
|
243
|
+
} else if (config.claudeCode?.authMode === "proxy" && launchctlGetenv("ANTHROPIC_AUTH_TOKEN") === undefined) {
|
|
244
|
+
inject("ANTHROPIC_AUTH_TOKEN", "opencodex-proxy");
|
|
241
245
|
}
|
|
242
246
|
// Lever keys (devlog 136 B6): user-wins — skip any key the user already set in the
|
|
243
247
|
// launchd domain, and track ONLY the keys we actually injected so revert cannot
|
package/src/types.ts
CHANGED
|
@@ -270,6 +270,12 @@ export interface OcxClaudeCodeConfig {
|
|
|
270
270
|
* on stop/shutdown. Default: false (opt-in). macOS only.
|
|
271
271
|
*/
|
|
272
272
|
systemEnv?: boolean;
|
|
273
|
+
/**
|
|
274
|
+
* Auth mode for Claude Code inbound requests. "proxy" injects a dummy
|
|
275
|
+
* ANTHROPIC_AUTH_TOKEN so Claude Code routes through the proxy without a
|
|
276
|
+
* real Anthropic key. Default: undefined (no token injection).
|
|
277
|
+
*/
|
|
278
|
+
authMode?: "proxy";
|
|
273
279
|
/**
|
|
274
280
|
* Context-window override for Claude Code/Desktop clients (devlog 136 B6):
|
|
275
281
|
* injected as CLAUDE_CODE_MAX_CONTEXT_TOKENS + DISABLE_COMPACT=1 (the official
|
|
@@ -379,6 +385,17 @@ export interface OcxConfig {
|
|
|
379
385
|
* are omitted from the bare /v1/models list.
|
|
380
386
|
*/
|
|
381
387
|
disabledModels?: string[];
|
|
388
|
+
/**
|
|
389
|
+
* Shadow call intercept: redirect Codex Desktop's hard-coded gpt-5.4-mini helper calls
|
|
390
|
+
* (title generation, commit messages, skill orchestration) to a user-chosen model.
|
|
391
|
+
* Opt-in; disabled by default. When enabled, effort is forced to low.
|
|
392
|
+
*/
|
|
393
|
+
shadowCallIntercept?: {
|
|
394
|
+
/** When true, all gpt-5.4-mini* requests are rewritten to the configured model. */
|
|
395
|
+
enabled?: boolean;
|
|
396
|
+
/** Replacement model id (e.g. "gpt-5.5"). */
|
|
397
|
+
model?: string;
|
|
398
|
+
};
|
|
382
399
|
/**
|
|
383
400
|
* 3-state multi-agent surface override:
|
|
384
401
|
* - "v1": force ALL models to v1 surface (override upstream pins)
|
|
@@ -668,10 +685,11 @@ export interface OcxProviderConfig {
|
|
|
668
685
|
unsafeAllowNativeLocalExec?: boolean;
|
|
669
686
|
/**
|
|
670
687
|
* Cursor adapter only: native local exec policy mode (exec-policy.ts).
|
|
671
|
-
* "
|
|
672
|
-
*
|
|
673
|
-
*
|
|
674
|
-
*
|
|
688
|
+
* "codex-sandbox" (default) allows server-driven local exec only when the
|
|
689
|
+
* request's instructions/developer text declares the Codex danger-full-access
|
|
690
|
+
* sandbox (approves the normal full-access flow, denies undeclared requests);
|
|
691
|
+
* "off" rejects all server-driven local exec; "on" always allows (same as legacy
|
|
692
|
+
* unsafeAllowNativeLocalExec:true). NOTE: the declaration is CALLER-CONTROLLED prose —
|
|
675
693
|
* the proxy cannot verify it. Enable "codex-sandbox" only where every client
|
|
676
694
|
* that can reach the data plane is trusted: the default loopback bind admits
|
|
677
695
|
* ANY process on this host without auth (including other local users on
|
package/src/web-search/index.ts
CHANGED
|
@@ -97,13 +97,16 @@ export function findAnthropicSidecarProvider(config: OcxConfig): AnthropicSideca
|
|
|
97
97
|
return undefined;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
/**
|
|
100
|
+
/**
|
|
101
|
+
* Precedence: explicit config wins; unset defaults to "openai" (ChatGPT forward path). The
|
|
102
|
+
* anthropic backend (web_search_20250305) is only used when explicitly configured — auto-selecting
|
|
103
|
+
* it from credential availability caused the sidecar to send incompatible models (e.g. gpt-5.6-luna)
|
|
104
|
+
* to the Anthropic API.
|
|
105
|
+
*/
|
|
101
106
|
export function resolveSidecarBackend(
|
|
102
107
|
explicit: "openai" | "anthropic" | undefined,
|
|
103
|
-
anthropicSidecar: AnthropicSidecarProvider | undefined,
|
|
104
108
|
): "openai" | "anthropic" {
|
|
105
|
-
|
|
106
|
-
return anthropicSidecar ? "anthropic" : "openai";
|
|
109
|
+
return explicit === "anthropic" ? "anthropic" : "openai";
|
|
107
110
|
}
|
|
108
111
|
|
|
109
112
|
export interface SidecarPlan {
|
|
@@ -145,7 +148,7 @@ export function planWebSearch(
|
|
|
145
148
|
// Same `?? 200_000` default the server applies when threading connectTimeoutMs into the loop.
|
|
146
149
|
const connectTimeoutMs = config.connectTimeoutMs ?? 200_000;
|
|
147
150
|
const anthropicSidecar = findAnthropicSidecarProvider(config);
|
|
148
|
-
const backend = resolveSidecarBackend(cfg.backend
|
|
151
|
+
const backend = resolveSidecarBackend(cfg.backend);
|
|
149
152
|
const maxSearches = cfg.maxSearchesPerTurn ?? DEFAULT_MAX_SEARCHES;
|
|
150
153
|
const stallTimeoutSec = webSearchStallTimeoutSec(
|
|
151
154
|
config.stallTimeoutSec,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
:root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark;--bg:var(--lightningcss-light,#fff)var(--lightningcss-dark,#212121);--rail:var(--lightningcss-light,#f9f9f9)var(--lightningcss-dark,#171717);--surface:var(--lightningcss-light,#fff)var(--lightningcss-dark,#262626);--raised:var(--lightningcss-light,#f4f4f4)var(--lightningcss-dark,#303030);--raised-hover:var(--lightningcss-light,#ececec)var(--lightningcss-dark,#3a3a3a);--border:var(--lightningcss-light,#e6e6e6)var(--lightningcss-dark,#3d3d3d);--border-soft:var(--lightningcss-light,#f0f0f0)var(--lightningcss-dark,#333);--hover:var(--lightningcss-light,#0d0d0d08)var(--lightningcss-dark,#ffffff08);--text:var(--lightningcss-light,#0d0d0d)var(--lightningcss-dark,#ececec);--muted:var(--lightningcss-light,#6e6e6e)var(--lightningcss-dark,#a6a6a6);--faint:var(--lightningcss-light,#707070)var(--lightningcss-dark,#9a9a9a);--accent:var(--lightningcss-light,#0d0d0d)var(--lightningcss-dark,#ececec);--accent-hover:var(--lightningcss-light,#3d3d3d)var(--lightningcss-dark,#fff);--accent-ink:var(--lightningcss-light,#fff)var(--lightningcss-dark,#0d0d0d);--accent-soft:var(--lightningcss-light,#0d0d0d0f)var(--lightningcss-dark,#ffffff17);--accent-ring:var(--lightningcss-light,#0d0d0d4d)var(--lightningcss-dark,#ffffff61);--green:var(--lightningcss-light,#0a7d5c)var(--lightningcss-dark,#4ecb9d);--green-soft:var(--lightningcss-light,#10a37f1a)var(--lightningcss-dark,#4ecb9d21);--red:var(--lightningcss-light,#b91c1c)var(--lightningcss-dark,#f87171);--red-soft:var(--lightningcss-light,#b91c1c17)var(--lightningcss-dark,#f8717121);--amber:var(--lightningcss-light,#9a4a08)var(--lightningcss-dark,#fbbf24);--amber-soft:var(--lightningcss-light,#b453091a)var(--lightningcss-dark,#fbbf2421);--radius:12px;--radius-sm:8px;--radius-xs:6px;--radius-pill:999px;--font:"OpenAI Sans", "Söhne", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, system-ui, "Helvetica Neue", sans-serif;--mono:ui-monospace, "SF Mono", "JetBrains Mono", "Cascadia Code", Menlo, Consolas, monospace;--shadow:0 1px 2px var(--lightningcss-light,#1018280f)var(--lightningcss-dark,#00000080), 0 10px 28px var(--lightningcss-light,#10182812)var(--lightningcss-dark,#0000004d);--shadow-sm:0 1px 2px var(--lightningcss-light,#1018280f)var(--lightningcss-dark,#0006);--toggle-w:36px;--toggle-h:20px;--toggle-dot:14px;--toggle-off-bg:var(--lightningcss-light,#d4d4d4)var(--lightningcss-dark,#4a4a4a);--toggle-on-bg:var(--lightningcss-light,#0d0d0d)var(--lightningcss-dark,#ececec);--toggle-dot-color:var(--lightningcss-light,#fff)var(--lightningcss-dark,#0d0d0d)}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial}}:root[data-theme=light]{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}:root[data-theme=dark]{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}:root{--glass-rail:var(--lightningcss-light,#f9f9f9a8)var(--lightningcss-dark,#1717179e);--glass-panel:var(--lightningcss-light,#ffffffc7)var(--lightningcss-dark,#262626d1);--glass-blur:saturate(1.6) blur(22px)}*{box-sizing:border-box}html,body,#root{height:100%}html{background:var(--bg);overflow-x:hidden}body{color:var(--text);font-family:var(--font);-webkit-font-smoothing:antialiased;text-rendering:optimizelegibility;background:0 0;margin:0;font-size:14px;line-height:1.5;overflow-x:hidden}body:before{content:"";z-index:-1;pointer-events:none;filter:blur(70px);background:radial-gradient(42% 38% at 12% 6%,var(--lightningcss-light,#a4c4ff6b)var(--lightningcss-dark,#6082c829),#0000 70%),radial-gradient(46% 42% at 88% 18%,var(--lightningcss-light,#a8e2c561)var(--lightningcss-dark,#58a08221),#0000 70%),radial-gradient(40% 36% at 70% 92%,var(--lightningcss-light,#ffe0c24d)var(--lightningcss-dark,#b48c6414),#0000 72%);position:fixed;inset:-20%}a{color:var(--text);text-decoration:underline;-webkit-text-decoration-color:var(--faint);text-decoration-color:var(--faint);text-underline-offset:2px}a:hover{-webkit-text-decoration-color:var(--text);text-decoration-color:var(--text)}code,.mono{font-family:var(--mono);font-size:.92em}h1,h2,h3,h4{letter-spacing:0;margin:0;font-weight:600}::selection{background:var(--accent-soft)}input[type=checkbox],input[type=radio]{accent-color:var(--accent)}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-thumb{background:var(--border);border:2px solid var(--bg);border-radius:99px}::-webkit-scrollbar-thumb:hover{background:var(--faint)}:focus-visible{outline:2px solid var(--accent-ring);outline-offset:2px;border-radius:4px}.app{grid-template-columns:232px 1fr;min-height:100dvh;display:grid}.sidebar{border-right:1px solid var(--border);background:var(--glass-rail);height:100dvh;-webkit-backdrop-filter:var(--glass-blur);flex-direction:column;align-self:start;gap:4px;padding:18px 14px;display:flex;position:sticky;top:0}.brand{align-items:center;gap:10px;padding:6px 8px 14px;display:flex}.brand-logo{background:var(--text);flex-shrink:0;width:26px;height:26px;-webkit-mask:url(/logo.png) 50%/contain no-repeat;mask:url(/logo.png) 50%/contain no-repeat}.brand .name{letter-spacing:0;font-size:15px;font-weight:600;line-height:26px}.brand .ver{font-family:var(--mono);color:var(--muted);background:var(--raised);border:1px solid var(--border);border-radius:99px;align-self:center;padding:2px 6px;font-size:10px;line-height:1}.nav-item{border-radius:var(--radius-sm);text-align:left;cursor:pointer;width:100%;color:var(--muted);font:inherit;background:0 0;border:none;align-items:center;gap:10px;padding:8px 10px;font-size:13.5px;font-weight:500;transition:background .12s,color .12s;display:flex}.nav-item:hover,.nav-item.active{background:var(--accent-soft);color:var(--text)}.nav-item.active{font-weight:600}.nav-item svg{width:17px;height:17px;color:var(--faint);flex-shrink:0}.nav-item.active svg{color:var(--text)}.sidebar-foot{flex-direction:column;gap:2px;margin-top:auto;padding-top:12px;display:flex}.sidebar-link{color:var(--muted);border-radius:var(--radius-sm);align-items:center;gap:9px;padding:8px 10px;font-size:13px;text-decoration:none;display:flex}.sidebar-link:hover{background:var(--accent-soft);color:var(--text);text-decoration:none}.sidebar-link svg{width:16px;height:16px}.lang-toggle{width:100%;color:var(--muted);border-radius:var(--radius-sm);align-items:center;gap:9px;padding:8px 10px;font-size:13px;transition:background .12s,color .12s;display:flex;position:relative}.lang-toggle:hover{background:var(--accent-soft);color:var(--text)}.lang-toggle svg{flex-shrink:0;width:16px;height:16px}.lang-toggle .custom-select{width:100%;position:static!important}.lang-toggle .select-trigger{width:100%;color:inherit;min-height:auto;box-shadow:none;-webkit-backdrop-filter:none;background:0 0;border:none;justify-content:space-between;padding:0;font-size:13px}.lang-toggle .select-trigger:hover:not(:disabled){color:inherit;box-shadow:none;background:0 0;border:none}.lang-toggle .select-dropdown{background:var(--glass-rail);-webkit-backdrop-filter:var(--glass-blur);border:1px solid var(--border);box-shadow:var(--shadow-sm)}.lang-toggle .select-dropdown-beside{min-width:10rem;top:0;left:calc(100% + 6px);right:auto}.theme-toggle{text-align:left;cursor:pointer;width:100%;color:var(--muted);font:inherit;border-radius:var(--radius-sm);background:0 0;border:none;align-items:center;gap:9px;padding:8px 10px;font-size:13px;transition:background .12s,color .12s;display:flex}.theme-toggle:hover{background:var(--accent-soft);color:var(--text)}.theme-toggle svg{flex-shrink:0;width:16px;height:16px}.theme-toggle .mode{text-transform:capitalize}.stop-toggle{color:var(--red)}.stop-toggle:hover{background:var(--red-soft);color:var(--red)}.stop-toggle:disabled{opacity:.5;cursor:default}.main{min-width:0}.main-inner{max-width:980px;margin:0 auto;padding:32px 36px 64px}.page-head{justify-content:space-between;align-items:center;gap:16px;margin-bottom:6px;display:flex}.page-head h2{font-size:19px}.page-sub{color:var(--muted);max-width:70ch;margin:4px 0 22px;font-size:13.5px}.page-sub b{color:var(--text);font-weight:600}.api-page h2 svg{vertical-align:-.16em;width:1em;height:1em}.api-page .page-sub code{color:var(--text);background:var(--raised);border:1px solid var(--border-soft);border-radius:var(--radius-xs);padding:1px 4px;font-size:.92em}.btn{border-radius:var(--radius-pill);font:inherit;cursor:pointer;white-space:nowrap;border:1px solid #0000;justify-content:center;align-items:center;gap:7px;padding:8px 16px;font-size:13px;font-weight:500;transition:background .12s,border-color .12s,opacity .12s;display:inline-flex}.btn svg{width:15px;height:15px}.btn:disabled{opacity:.55;cursor:default}.btn-primary{background:var(--accent);color:var(--accent-ink)}.btn-primary:hover:not(:disabled){background:var(--accent-hover)}.btn-ghost{background:var(--bg);color:var(--text);border-color:var(--border)}.btn-ghost:hover:not(:disabled){background:var(--raised)}.btn-danger{color:var(--red);background:0 0;border-color:#f871714d}.btn-danger:hover:not(:disabled){background:var(--red-soft)}.btn-sm{border-radius:var(--radius-pill);padding:4px 12px;font-size:12px}.btn-icon{appearance:none;font:inherit;cursor:pointer;width:28px;height:28px;color:var(--muted);border-radius:var(--radius-sm);background:0 0;border:none;justify-content:center;align-items:center;padding:0;transition:background .12s,color .12s;display:inline-flex}.btn-icon:hover{background:var(--raised-hover);color:var(--text)}.btn-icon:focus-visible{outline:2px solid var(--accent-ring);outline-offset:1px}.btn-icon svg{width:16px;height:16px;display:block}.btn.btn-ghost.btn-icon{width:28px;height:28px;padding:0}.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);min-width:0}.panel{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:18px}.panel-accent{border-color:color-mix(in srgb, var(--accent) 28%, var(--border));background:color-mix(in srgb, var(--accent) 5%, var(--surface))}.api-panel{flex-direction:column;gap:10px;display:flex;overflow:hidden}.api-panel .panel-title,.api-panel .muted{margin:0}.api-form-row{align-items:center;gap:8px;min-width:0;display:flex}.api-form-row .input{flex:1;min-width:0}.api-code{min-width:0;color:var(--text);background:var(--raised);border:1px solid var(--border-soft);border-radius:var(--radius-sm);font-family:var(--mono);white-space:pre;margin:0;padding:9px 11px;font-size:12.5px;line-height:1.55;display:block;overflow-x:auto}.api-code-inline{white-space:nowrap}.api-actions{justify-content:flex-end;align-items:center;gap:4px;display:flex}.maintenance-head{flex-wrap:wrap;align-items:flex-start}.maintenance-actions{flex-wrap:wrap;justify-content:flex-end;align-items:center;gap:8px;display:flex}.maintenance-notice{align-items:flex-start;margin:14px 0 0}.update-row{justify-content:space-between;align-items:center;gap:12px;margin-bottom:14px;display:flex}.update-row .field-label{margin:0}.update-empty{margin-bottom:14px;padding:18px}.update-box{flex-direction:column;gap:12px;display:flex}.update-command{flex-wrap:wrap;align-items:flex-start;gap:6px;font-size:12px;display:flex}.update-command .chip{white-space:pre-wrap;word-break:break-all;overflow-wrap:anywhere;flex:auto;min-width:0;max-width:100%;line-height:1.5}.update-recheck{flex-wrap:wrap;align-items:center;gap:10px;display:flex}.update-recheck .btn{flex:none}.update-recheck-reason{min-width:0;color:var(--muted);flex:200px;font-size:12px}.update-restart{border-top:1px solid var(--border-soft);padding-top:12px}.injection-head{flex-wrap:wrap;align-items:center;gap:10px;display:flex}.injection-label{font-weight:650}@media (width<=800px){.injection-head{gap:8px}.injection-label{flex:1 0 calc(100% - 70px);order:1}.injection-head>.badge{order:1;margin-left:auto}.injection-head>.custom-select{order:2}}.stat-row{grid-template-columns:repeat(6,1fr);gap:12px;margin-bottom:28px;display:grid}.stat-row>.stat{flex-direction:column;justify-content:center;min-height:80px;display:flex}@media (width<=1100px){.stat-row{grid-template-columns:repeat(3,1fr)}}@media (width<=640px){.stat-row{grid-template-columns:repeat(2,1fr)}}.stat{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;transition:border-color .12s}.stat:hover{border-color:var(--faint)}.stat .label{color:var(--muted);align-items:center;gap:6px;margin-bottom:6px;font-size:12px;font-weight:500;display:flex}.stat .label svg{width:14px;height:14px}.stat .value{letter-spacing:0;font-size:20px;font-weight:600;line-height:1.15}.stat .value.mono{font-family:var(--mono);font-size:17px}.model-group-head{color:var(--text);align-items:baseline;gap:8px;margin:0 0 8px;font-size:13px;font-weight:600;display:flex}.model-group-head .count{font-family:var(--mono);color:var(--faint);font-size:12px;font-weight:500}.group-head{cursor:pointer;background:var(--surface);padding:10px 12px;transition:background .12s}.group-head:hover{background:var(--hover)}.group-head.open{border-bottom:1px solid var(--border-soft)}.model-grid{grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:8px;display:grid}.model-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);padding:10px 12px;transition:border-color .12s,background .12s}.model-card:hover{border-color:var(--faint);background:var(--hover)}.model-card .id{font-family:var(--mono);letter-spacing:0;color:var(--text);font-size:13px;font-weight:600}.badge{font-size:11px;font-weight:600;font-family:var(--mono);letter-spacing:0;border:1px solid #0000;border-radius:99px;align-items:center;gap:5px;padding:2px 8px;display:inline-flex}.badge-accent{background:var(--accent-soft);color:var(--text)}.badge-green{background:var(--green-soft);color:var(--green)}.badge-amber{background:var(--amber-soft);color:var(--amber)}.badge-muted{background:var(--raised);color:var(--muted);border:1px solid var(--border)}.badge-clickable{cursor:pointer;appearance:none;transition:filter .12s}.badge-clickable:hover{filter:brightness(1.1)}.badge-disabled{opacity:.5;cursor:default}.card-badges{flex-wrap:wrap;align-items:center;gap:8px;min-width:0;display:inline-flex}.card-badges .badge{flex-shrink:0}.confirm-icon{background:var(--amber-soft);width:44px;height:44px;color:var(--amber);border-radius:50%;justify-content:center;align-items:center;margin:0 auto 12px;display:flex}.credit-list{flex-direction:column;gap:6px;display:flex}.credit-item{border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--raised);padding:8px 10px;transition:border-color .12s}.credit-next{border-color:var(--amber);background:var(--amber-soft)}.credit-item-head{color:var(--text);align-items:center;gap:6px;margin-bottom:3px;font-size:12px;font-weight:600;display:flex}.credit-item-head svg{color:var(--amber);flex-shrink:0}.credit-item-dates{font-size:11px;font-family:var(--mono);color:var(--muted);justify-content:space-between;display:flex}.credit-urgent{color:var(--red);font-weight:600}.dot{border-radius:50%;flex-shrink:0;width:7px;height:7px}.dot-green{background:var(--green);box-shadow:0 0 0 3px var(--green-soft)}.dot-red{background:var(--red);box-shadow:0 0 0 3px var(--red-soft)}.tbl{border-collapse:collapse;width:100%;font-size:13px}.tbl thead th{text-align:left;color:var(--muted);border-bottom:1px solid var(--border);padding:9px 12px;font-size:12px;font-weight:500}.tbl tbody td{border-bottom:1px solid var(--border-soft);padding:10px 12px}.tbl tbody tr:last-child td{border-bottom:none}.tbl tbody tr:hover td{background:var(--hover)}.tbl .num{text-align:right;font-family:var(--mono)}.tbl-wrap{border:1px solid var(--border);border-radius:var(--radius);background:var(--surface);overflow-x:auto}.input,textarea.input{border-radius:var(--radius-sm);background:var(--raised);border:1px solid var(--border);width:100%;color:var(--text);font:inherit;padding:8px 11px;font-size:13px;transition:border-color .12s}.input::placeholder{color:var(--faint)}.input:focus{border-color:var(--faint);box-shadow:0 0 0 3px var(--accent-soft);outline:none}textarea.input{resize:vertical;font-family:var(--mono);line-height:1.55}.field-label{color:var(--muted);margin-bottom:5px;font-size:12px;font-weight:500;display:block}select.input{appearance:none}.select-sm{background:color-mix(in oklab, canvas 75%, transparent);-webkit-backdrop-filter:blur(12px)saturate(1.3);border:1px solid var(--border);color:var(--text);font:inherit;cursor:pointer;border-radius:999px;padding:6px 12px;font-size:13px;transition:border-color .12s,box-shadow .12s;box-shadow:0 2px 8px #0000000f}.select-sm:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft);outline:none}.select-sm:hover:not(:disabled){border-color:var(--faint);box-shadow:0 2px 12px #0000001a}.select-sm:disabled{opacity:.5;cursor:default;-webkit-backdrop-filter:none}.select-trigger{background:color-mix(in oklab, canvas 75%, transparent);-webkit-backdrop-filter:blur(12px)saturate(1.3);border:1px solid var(--border);color:var(--text);font:inherit;cursor:pointer;white-space:nowrap;border-radius:999px;align-items:center;gap:6px;padding:6px 12px;font-size:13px;transition:border-color .12s,box-shadow .12s;display:inline-flex;box-shadow:0 2px 8px #0000000f}.select-trigger:hover:not(:disabled){border-color:var(--faint);box-shadow:0 2px 12px #0000001a}.select-trigger:disabled{opacity:.5;cursor:default}.select-dropdown{z-index:40;background:color-mix(in oklab, canvas 84%, transparent);-webkit-backdrop-filter:blur(20px)saturate(1.4);border:1px solid var(--border);border-radius:12px;min-width:100%;max-height:280px;padding:4px;position:absolute;top:calc(100% + 4px);left:0;overflow-y:auto;box-shadow:0 8px 32px #00000029}.select-dropdown-right{right:0;left:auto!important}.select-dropdown-beside{min-width:10rem;top:0;left:calc(100% + 6px);right:auto}.select-option{text-align:left;width:100%;color:var(--text);font:inherit;cursor:pointer;white-space:nowrap;background:0 0;border:none;border-radius:8px;padding:7px 12px;font-size:13px;transition:background .1s;display:block}.select-option:hover{background:var(--hover)}.select-option.active{background:var(--accent-soft);color:var(--accent);font-weight:600}.switch{border:1px solid var(--border);cursor:pointer;background:var(--raised);vertical-align:middle;appearance:none;border-radius:99px;flex-shrink:0;align-items:center;width:34px;height:20px;padding:2px;line-height:0;transition:background .15s,border-color .15s;display:inline-flex}.switch.on{background:var(--accent);border-color:var(--accent)}.switch:disabled{opacity:.6;cursor:default}.switch .knob{background:var(--lightningcss-light,#fff)var(--lightningcss-dark,#ececec);border-radius:50%;width:14px;height:14px;transition:transform .15s,background .15s;transform:translate(0);box-shadow:0 0 0 1px #10182814,0 1px 1px #1018282e}.switch.on .knob{background:var(--accent-ink);transform:translate(14px);box-shadow:0 0 0 1px #0000001f}.muted{color:var(--muted)}.faint{color:var(--faint)}.row{align-items:center;gap:10px;display:flex}.spread{justify-content:space-between;align-items:center;gap:12px;display:flex}.setting-hint{margin-top:3px;font-size:13px}.stack{flex-direction:column;display:flex}.chip{font-family:var(--mono);background:var(--raised);border:1px solid var(--border);border-radius:var(--radius-xs);color:var(--text);padding:1px 7px;font-size:12px}.empty{text-align:center;border:1px dashed var(--border);border-radius:var(--radius);color:var(--muted);padding:56px 20px}.empty svg{width:30px;height:30px;color:var(--faint);margin-bottom:12px}.empty .title{color:var(--text);margin-bottom:6px;font-weight:600}.notice{border-radius:var(--radius-sm);align-items:center;gap:8px;margin-bottom:14px;padding:9px 12px;font-size:13px;display:flex}.notice svg{flex-shrink:0;width:15px;height:15px}.notice-ok{background:var(--green-soft);color:var(--green)}.notice-err{background:var(--red-soft);color:var(--red)}.h-section{color:var(--text);align-items:center;gap:8px;margin:30px 0 12px;font-size:13px;font-weight:600;display:flex}.h-section .count{color:var(--muted);font-weight:500;font-family:var(--mono);font-size:12px}.spin{border:2px solid var(--border);border-top-color:var(--accent);border-radius:50%;width:14px;height:14px;animation:.7s linear infinite spin;display:inline-block}@keyframes spin{to{transform:rotate(360deg)}}@media (prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}@supports not ((-webkit-backdrop-filter:blur(1px)) or (backdrop-filter:blur(1px))){.sidebar,.lang-toggle .select-dropdown{background:var(--rail)}.modal-card{background:var(--surface)}}@media (prefers-reduced-transparency:reduce){body:before{display:none}.sidebar,.lang-toggle .select-dropdown{background:var(--rail);-webkit-backdrop-filter:none}.modal-card{background:var(--surface);-webkit-backdrop-filter:none}.modal-overlay{-webkit-backdrop-filter:none}}.modal-overlay{-webkit-backdrop-filter:blur(40px)saturate(1.2);z-index:50;background:var(--lightningcss-light,#11131cc7)var(--lightningcss-dark,#000000d1);justify-content:center;align-items:flex-start;padding:8vh 16px;display:flex;position:fixed;inset:0}.modal-card{background:color-mix(in oklab, canvas 92%, transparent);-webkit-backdrop-filter:blur(20px)saturate(1.4);border:1px solid var(--border);border-radius:16px;width:100%;max-width:520px;max-height:84vh;padding:20px;overflow-y:auto;box-shadow:0 8px 32px #0000002e}.modal-head{justify-content:space-between;align-items:center;margin-bottom:16px;display:flex}.modal-head h3{font-size:16px}.card-head{flex-wrap:wrap;align-items:center;gap:8px;min-width:0;padding:14px 16px 6px;display:flex}.card-head strong{overflow-wrap:anywhere;min-width:0}.card-sub{color:var(--muted);overflow-wrap:anywhere;min-width:0;padding:0 16px 10px;font-size:12px}.card-active{border-color:var(--accent-ring)}.card-right{color:var(--faint);align-items:center;gap:4px;margin-left:auto;font-size:11px;display:flex}.btn-icon-danger.card-right{appearance:none;color:var(--red);cursor:pointer;background:0 0;border:1px solid #0000;justify-content:center}.btn-icon-danger.card-right:hover{background:var(--red-soft);border-color:var(--red-soft);color:var(--red)}.card-row{justify-content:space-between;align-items:center;padding:14px 16px;display:flex}.badge-primary{background:var(--accent-soft);color:var(--accent-hover)}.dot-blue{background:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}.dot-muted{background:var(--muted)}.dot-amber{background:var(--amber);box-shadow:0 0 0 3px var(--amber-soft)}.section-sep{align-items:center;gap:10px;margin:20px 0 12px;display:flex}.section-label{color:var(--muted);white-space:nowrap;font-size:12.5px;font-weight:500}.sep-line{background:var(--border);flex:1;height:1px}.quota-compact{gap:4px;padding:2px 16px 12px;display:grid}.quota-row{grid-template-columns:minmax(34px,max-content) 34px minmax(34px,44px) minmax(38px,42px) minmax(58px,1fr) minmax(30px,max-content);align-items:center;gap:8px;min-width:0;display:grid}.quota-label{color:var(--muted);font-size:11px;font-weight:600}.quota-val{font-size:11px;font-family:var(--mono);color:var(--text);text-align:right}.quota-reset-label{text-overflow:ellipsis;white-space:nowrap;color:var(--faint);font-size:11px;overflow:hidden}.quota-reset-day,.quota-reset-time{color:var(--muted);white-space:nowrap;font-size:11px}.quota-reset-time{font-family:var(--mono)}.bar{background:var(--raised);border-radius:3px;min-width:0;height:5px;overflow:hidden}.bar-fill{border-radius:3px;height:100%;transition:width .3s}.bar-green{background:var(--green)}.bar-amber{background:linear-gradient(90deg, var(--green), var(--amber))}.toggle{background:var(--raised);border:1px solid var(--border);cursor:pointer;border-radius:10px;flex-shrink:0;width:36px;height:20px;transition:background .15s;position:relative}.toggle.on{background:var(--accent);border-color:var(--accent)}.toggle-knob{background:#fff;border-radius:50%;width:16px;height:16px;transition:left .15s;position:absolute;top:1px;left:1px}.toggle.on .toggle-knob{left:17px}.notice-warn{border-radius:var(--radius-sm);background:var(--amber-soft);color:var(--amber);align-items:center;gap:6px;margin-bottom:12px;padding:8px 10px;font-size:12px;display:flex}.notice-warn svg{flex-shrink:0;width:14px;height:14px}.modal-desc{color:var(--muted);margin-bottom:14px;font-size:13px}.modal-actions{gap:8px;margin-top:16px;display:flex}.modal-actions .btn{flex:1}.log-reqid{-webkit-line-clamp:2;word-break:break-all;-webkit-box-orient:vertical;max-width:14ch;display:-webkit-box;overflow:hidden}.log-col-model{overflow-wrap:break-word;word-break:normal;max-width:18ch}.log-col-tokens{min-width:10ch}.log-status-cell{flex-direction:column;align-items:flex-start;gap:2px;display:inline-flex}.log-detail-btn{cursor:pointer;color:var(--accent-hover);font:inherit;white-space:nowrap;background:0 0;border:none;padding:0;font-size:11px;text-decoration:underline}.log-detail-grid{grid-template-columns:max-content minmax(0,1fr);gap:6px 14px;font-size:13px;display:grid}.log-detail-break{word-break:break-all}.log-detail-json{background:var(--raised);border:1px solid var(--border-soft);border-radius:var(--radius-sm);font-family:var(--mono);white-space:pre-wrap;word-break:break-all;max-height:40vh;margin:0;padding:10px 12px;font-size:11.5px;line-height:1.5;overflow:auto}.setup-guide{border:1px solid var(--border);border-radius:var(--radius-sm);margin-bottom:4px;padding:8px 12px;font-size:13px}.setup-guide summary{cursor:pointer;color:var(--accent-hover);font-weight:500}.setup-guide summary:hover{text-decoration:underline}.setup-guide a{color:var(--accent-hover)}.list-row{text-align:left;border-radius:var(--radius-sm);border:1px solid var(--border);background:var(--raised);cursor:pointer;width:100%;color:var(--text);font:inherit;justify-content:space-between;align-items:center;gap:10px;padding:11px 13px;transition:background .12s,border-color .12s;display:flex}.list-row:hover{background:var(--raised-hover);border-color:var(--accent-ring)}.list-row .title{font-size:14px;font-weight:600}.list-row .sub{color:var(--muted);margin-top:2px;font-size:12px}.prov-card{overflow:hidden}.prov-card-main{flex-wrap:wrap;justify-content:space-between;align-items:flex-start;gap:12px;padding:15px 16px;display:flex}.prov-card-disabled{opacity:.62}.prov-card-info{flex:360px;align-items:flex-start;gap:11px;min-width:0;display:flex}.prov-card-copy{flex:1;min-width:0}.prov-title{flex-wrap:wrap;align-items:center;gap:8px;min-width:0;margin-bottom:5px;display:flex}.provider-icon{background:var(--raised);border:1px solid var(--border-soft);width:31px;height:31px;color:var(--text);border-radius:7px;flex:none;justify-content:center;align-items:center;display:inline-flex}.provider-icon img{object-fit:contain;width:19px;height:19px;display:block}.prov-meta{flex-wrap:wrap;align-items:center;gap:5px;min-width:0;display:flex}.prov-meta>span{text-overflow:ellipsis;white-space:nowrap;min-width:0;max-width:100%;overflow:hidden}.provider-quota{padding-left:58px}.provider-actions{flex-shrink:0;align-items:center;gap:8px;margin-left:auto;display:flex}.link-btn{color:var(--accent-hover);font:inherit;cursor:pointer;background:0 0;border:none;padding:6px 2px;font-size:13px;text-decoration:underline}.prov-accounts-toggle{border:none;border-top:1px solid var(--border-soft);width:100%;color:var(--muted);font:inherit;cursor:pointer;background:0 0;justify-content:center;align-items:center;gap:6px;min-height:24px;padding:4px 0;font-size:12px;display:flex}.prov-accounts-toggle:hover{color:var(--text);background:var(--raised)}.prov-accounts-toggle .chev{transition:transform .15s;display:inline-flex}.prov-accounts-toggle .chev svg{width:12px;height:12px;transform:rotate(90deg)}.prov-accounts-toggle.open .chev svg{transform:rotate(-90deg)}.prov-accounts-list{border-top:1px solid var(--border-soft);flex-direction:column;gap:2px;padding:6px 16px 10px;display:flex}.prov-account-row{text-align:left;border-radius:var(--radius-xs);width:100%;color:var(--text);font:inherit;cursor:pointer;background:0 0;border:none;align-items:center;gap:8px;min-height:32px;padding:6px 8px;font-size:13px;display:flex}.prov-account-row:hover{background:var(--raised)}.prov-account-row.active{cursor:default}.prov-account-row .prov-account-email{text-overflow:ellipsis;white-space:nowrap;flex:auto;min-width:0;overflow:hidden}.prov-account-row .badge{flex:none}.prov-account-remove{color:var(--muted);cursor:pointer;border-radius:var(--radius-xs);background:0 0;border:none;flex:none;justify-content:center;align-items:center;padding:4px;display:inline-flex}.prov-account-remove:hover{color:var(--red);background:var(--red-soft)}.prov-account-add{color:var(--muted);font-size:12.5px}.prov-account-add:hover{color:var(--accent-hover)}.prov-account-keyform{cursor:default;gap:6px}.prov-account-keyform:hover{background:0 0}.prov-account-keyform .input-sm{flex:auto;min-width:0;height:28px;padding:4px 8px;font-size:12.5px}.oauth-grid{grid-template-columns:minmax(140px,max-content) minmax(0,1fr) max-content;align-items:center;gap:10px 16px;display:grid}.oauth-row{grid-column:1/-1;grid-template-columns:subgrid;align-items:center;min-height:30px;display:grid}.oauth-name{align-items:center;gap:8px;min-width:0;font-size:13px;font-weight:600;display:inline-flex}.oauth-name-text{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.provider-icon-sm{border-radius:6px;width:24px;height:24px}.provider-icon-sm img{width:15px;height:15px}.oauth-status{align-items:center;gap:7px;min-width:0;font-size:13px;display:inline-flex}.oauth-email{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.oauth-actions{justify-content:flex-end;align-items:center;gap:8px;min-width:0;display:inline-flex}.oauth-login-hint{flex-wrap:wrap;grid-column:1/-1;align-items:center;gap:8px;font-size:12px;display:inline-flex}@media (width<=760px){.app{grid-template-columns:1fr}.sidebar{z-index:20;border-right:none;border-bottom:1px solid var(--border);background:var(--rail);flex-flow:wrap;align-items:center;gap:0;min-width:0;height:auto;padding:0 10px;position:sticky;top:0}.brand{flex:auto;order:1;width:auto;padding:10px 4px}.sidebar-foot{flex-direction:row;flex:none;order:2;gap:4px;margin:0;padding:0}.sidebar-foot .sidebar-link{display:none}.theme-toggle{justify-content:center;min-width:44px;min-height:44px;padding:8px}.theme-toggle .mode{display:none}.sidebar nav{border-top:1px solid var(--border-soft);flex:100%;order:3;grid-template-columns:repeat(4,minmax(0,1fr));gap:2px;min-width:0;margin:0;padding:4px 0 8px;display:grid}.nav-item{text-align:center;text-overflow:ellipsis;white-space:nowrap;flex-direction:column;justify-content:center;gap:3px;width:auto;min-height:44px;padding:6px 4px;font-size:12px;overflow:hidden}.nav-item svg{width:16px;height:16px}.main-inner{padding:22px 18px 48px}.setting-row{flex-wrap:wrap}.setting-row .setting-copy{flex:100%!important}.setting-row .setting-controls{width:100%}.setting-row .setting-controls .select-sm{flex:1 1 0;min-width:0}.setting-row>.select-sm{width:100%}.api-form-row{flex-direction:column;align-items:stretch}.api-form-row .btn{width:100%;min-height:40px}.stat-row>.stat{min-width:100px}.tbl{min-width:460px}.usage-cards{grid-template-columns:repeat(2,minmax(0,1fr))!important}.provider-quota{padding-left:16px}.prov-meta{grid-template-columns:max-content minmax(0,1fr);width:100%;display:grid}.prov-meta .chip~span:not(:last-child){display:none}.oauth-grid{grid-template-columns:minmax(0,auto) minmax(0,1fr) max-content;column-gap:8px}.lang-toggle .select-dropdown-beside{top:calc(100% + 6px);left:auto;right:0}}.usage-cards{grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin-top:8px;display:grid}.usage-cards .stat-value{margin-top:4px;font-size:20px;font-weight:600}.usage-range{border:1px solid var(--border);background:var(--surface);border-radius:999px;gap:2px;padding:2px;display:inline-flex}.usage-range-btn{color:var(--muted);cursor:pointer;font:inherit;background:0 0;border:none;border-radius:999px;padding:4px 12px}.usage-range-btn.active{background:var(--raised);color:var(--text);font-weight:600}.panel-title{color:var(--text);margin:0 0 12px;font-size:14px;font-weight:600}.panel-head{justify-content:space-between;align-items:center;gap:12px;margin-bottom:12px;display:flex}.panel-head .panel-title{margin:0}.panel-head .input{max-width:220px}.heatmap{--hm-cell:11px;--hm-gap:3px;flex-direction:column;gap:6px;padding-bottom:4px;display:flex;overflow-x:auto}.heatmap-months{color:var(--muted);width:max-content;margin-bottom:-2px;font-size:11px;display:grid}.heatmap-day-spacer{grid-column:1}.heatmap-month{white-space:nowrap}.heatmap-body{gap:var(--hm-gap);width:max-content;display:flex}.heatmap-days{grid-template-rows:repeat(7, var(--hm-cell));row-gap:var(--hm-gap);color:var(--muted);background:var(--surface);z-index:1;flex-shrink:0;align-items:center;width:25px;font-size:10px;display:grid;position:sticky;left:0}.heatmap-grid{gap:var(--hm-gap);display:grid}.heatmap-week{grid-template-rows:repeat(7, var(--hm-cell));gap:var(--hm-gap);display:grid}.heatmap-cell{width:var(--hm-cell);height:var(--hm-cell);background:var(--border);border-radius:2px}.heatmap-cell-0{background:var(--border)}.heatmap-cell-1{background:color-mix(in oklch, var(--green) 25%, var(--surface))}.heatmap-cell-2{background:color-mix(in oklch, var(--green) 50%, var(--surface))}.heatmap-cell-3{background:color-mix(in oklch, var(--green) 75%, var(--surface))}.heatmap-cell-4{background:var(--green)}.heatmap-legend{align-self:flex-end;align-items:center;gap:4px;font-size:12px;display:inline-flex;position:sticky;right:0}.heatmap-legend .heatmap-cell{width:10px;height:10px}.heatmap-tip{z-index:10;pointer-events:none;background:var(--surface);border:1px solid var(--border);white-space:nowrap;border-radius:8px;padding:6px 10px;font-size:12px;position:fixed;transform:translate(-50%,-100%)translateY(-8px);box-shadow:0 6px 20px #00000059}.heatmap-tip-date{color:var(--text);margin-bottom:2px;font-weight:600}.heatmap-tip-val{color:var(--text);font-variant-numeric:tabular-nums}.heatmap-tip-req{font-size:11px}.usage-bar{background:var(--border);border-radius:999px;width:100%;min-width:60px;height:6px;overflow:hidden}.usage-bar-fill{background:var(--green);border-radius:999px;height:100%}.daybars{grid-template-columns:repeat(7,1fr);align-items:end;gap:10px;height:180px;padding-top:8px;display:grid}.daybar{flex-direction:column;justify-content:flex-end;align-items:center;gap:6px;height:100%;display:flex;position:relative}.daybar-track{background:var(--border);border-radius:6px;flex:1;align-items:flex-end;width:100%;max-width:48px;display:flex;overflow:hidden}.daybar-stack{border-radius:6px 6px 0 0;flex-direction:column-reverse;width:100%;min-height:2px;transition:height .2s;display:flex;overflow:hidden}.daybar-seg{width:100%;min-height:1px}.daybar-count{color:var(--text);font-size:12px;font-weight:600}.daybar-label{white-space:nowrap;font-size:11px}.daybar:hover .daybar-track{outline:1px solid var(--border)}.daybar-tip{z-index:5;background:var(--surface);border:1px solid var(--border);pointer-events:none;border-radius:8px;min-width:160px;padding:8px 10px;position:absolute;bottom:calc(100% + 6px);left:50%;transform:translate(-50%);box-shadow:0 6px 20px #00000059}.daybar-tip-date{color:var(--text);white-space:nowrap;margin-bottom:6px;font-size:12px;font-weight:600}.daybar-tip-row{align-items:center;gap:8px;font-size:12px;line-height:1.7;display:flex}.daybar-tip-swatch{border-radius:2px;flex-shrink:0;width:10px;height:10px}.daybar-tip-name{color:var(--text);white-space:nowrap;text-overflow:ellipsis;flex:1;max-width:160px;overflow:hidden}.daybar-tip-val{color:var(--muted);font-variant-numeric:tabular-nums}.usage-scroll{max-height:360px;overflow-y:auto}.usage-scroll thead th{z-index:1;background:var(--surface);position:sticky;top:0}.toggle{width:var(--toggle-w);height:var(--toggle-h);flex-shrink:0;display:inline-block;position:relative}.toggle input{opacity:0;width:0;height:0;position:absolute}.toggle .slider{background:var(--toggle-off-bg);border-radius:var(--radius-pill);cursor:pointer;transition:background .15s;position:absolute;inset:0}.toggle .slider:after{content:"";width:var(--toggle-dot);height:var(--toggle-dot);background:var(--toggle-dot-color);border-radius:50%;transition:transform .15s;position:absolute;top:3px;left:3px}.toggle input:checked+.slider{background:var(--toggle-on-bg)}.toggle input:checked+.slider:after{transform:translateX(calc(var(--toggle-w) - var(--toggle-dot) - 6px))}.toggle input:focus-visible+.slider{outline:2px solid var(--accent-ring);outline-offset:2px}.setting-row{justify-content:space-between;align-items:center;gap:16px;padding:12px 16px;display:flex}.setting-row+.setting-row{border-top:1px solid var(--border-soft)}.setting-label{flex-direction:column;gap:2px;min-width:0;display:flex}.setting-label .title{color:var(--text);font-size:13.5px;font-weight:550}.setting-label .desc{color:var(--muted);font-size:12px;line-height:1.4}
|