@bitkyc08/opencodex 2.7.1 → 2.7.3
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/README.md +1 -2
- package/gui/dist/assets/index-D7o1qwy-.css +1 -0
- package/gui/dist/assets/index-DT4C-vKW.js +40 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic.ts +14 -3
- package/src/adapters/azure.ts +16 -11
- package/src/adapters/cursor/live-models.ts +36 -24
- package/src/adapters/cursor/live-transport.ts +5 -17
- package/src/adapters/cursor/native-exec.ts +11 -7
- package/src/adapters/cursor/transport-retry.ts +1 -4
- package/src/adapters/google.ts +32 -9
- package/src/adapters/kiro-retry.ts +6 -2
- package/src/adapters/kiro.ts +14 -9
- package/src/adapters/openai-chat.ts +38 -28
- package/src/codex/catalog.ts +67 -32
- package/src/config.ts +25 -2
- package/src/oauth/index.ts +4 -3
- package/src/oauth/key-providers.ts +1 -1
- package/src/oauth/kiro-credentials.ts +13 -3
- package/src/providers/derive.ts +4 -0
- package/src/providers/kiro-models.ts +1 -1
- package/src/providers/registry.ts +129 -40
- package/src/responses/parser.ts +12 -0
- package/src/router.ts +13 -1
- package/src/server/management-api.ts +10 -2
- package/src/server/request-log.ts +25 -1
- package/src/server/responses.ts +141 -43
- package/src/types.ts +12 -2
- package/src/update/index.ts +1 -1
- package/gui/dist/assets/index-BUAMcKFd.css +0 -1
- package/gui/dist/assets/index-CtHGtaW8.js +0 -34
package/src/server/responses.ts
CHANGED
|
@@ -90,62 +90,148 @@ const PROACTIVE_MULTI_AGENT_MODE_TEXT = [
|
|
|
90
90
|
* flat spawn_agent. A flat spawn_agent vetoes so an ambiguous mix never counts as v1.
|
|
91
91
|
*/
|
|
92
92
|
export function isV1CollabSurface(parsed: OcxParsedRequest): boolean {
|
|
93
|
+
return collabSurface(parsed) === "v1";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Which multi-agent collab surface this turn carries, judged from the request's own
|
|
98
|
+
* tool list. Real wire shapes (codex-rs spec_plan.rs add_collaboration_tools):
|
|
99
|
+
* - v1: tools under the "multi_agent_v1" namespace, always accompanied by v1-only
|
|
100
|
+
* names (send_input / resume_agent / close_agent).
|
|
101
|
+
* - v2 on namespace_tools providers (e.g. the ChatGPT backend): tools under the
|
|
102
|
+
* "collaboration" namespace (config.multi_agent_v2.tool_namespace — user-settable,
|
|
103
|
+
* so the name is not hardcoded here), with v2-only companions (send_message /
|
|
104
|
+
* followup_task / interrupt_agent / list_agents).
|
|
105
|
+
* - v2 without namespace support: a flat spawn_agent.
|
|
106
|
+
* Companion tools are the primary discriminator; a companionless namespaced spawn
|
|
107
|
+
* falls back to "v1" (legacy behavior) and a companionless flat spawn to "v2".
|
|
108
|
+
* Contradictory markers count as neither — never inject on unclear ground.
|
|
109
|
+
*/
|
|
110
|
+
export function collabSurface(parsed: OcxParsedRequest): "v1" | "v2" | null {
|
|
93
111
|
let namespacedSpawn = false;
|
|
94
112
|
let flatSpawn = false;
|
|
95
113
|
let v1Only = false;
|
|
114
|
+
let v2Only = false;
|
|
96
115
|
for (const t of parsed.context.tools ?? []) {
|
|
97
116
|
if (t.name === "spawn_agent") {
|
|
98
117
|
if (t.namespace) namespacedSpawn = true;
|
|
99
118
|
else flatSpawn = true;
|
|
100
|
-
} else if (t.name === "send_input" || t.name === "close_agent")
|
|
119
|
+
} else if (t.name === "send_input" || t.name === "resume_agent" || t.name === "close_agent") {
|
|
120
|
+
v1Only = true;
|
|
121
|
+
} else if (t.name === "send_message" || t.name === "followup_task" || t.name === "interrupt_agent" || t.name === "list_agents") {
|
|
122
|
+
v2Only = true;
|
|
123
|
+
}
|
|
101
124
|
}
|
|
102
|
-
|
|
125
|
+
if (!namespacedSpawn && !flatSpawn) return null; // no spawn_agent -> no collab surface
|
|
126
|
+
if (namespacedSpawn && flatSpawn) return null; // contradictory spawn shapes
|
|
127
|
+
if (v1Only && v2Only) return null; // contradictory companions
|
|
128
|
+
if (v1Only) return "v1";
|
|
129
|
+
if (v2Only) return "v2";
|
|
130
|
+
return namespacedSpawn ? "v1" : "v2"; // companionless fallbacks (legacy defaults)
|
|
103
131
|
}
|
|
104
132
|
|
|
105
133
|
/**
|
|
106
134
|
* Multi-agent guidance for this turn, or null when nothing applies.
|
|
107
135
|
*
|
|
108
|
-
* codex-rs only emits its Proactive delegation developer message on the
|
|
109
|
-
* so when a v1-surface turn arrives at the synthetic top tier (codex
|
|
110
|
-
* ultra -> max on the wire, so max arrival means the user picked the top
|
|
111
|
-
* proxy supplies the same one-liner, wrapped in codex's own
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
* toggle.
|
|
115
|
-
*
|
|
116
|
-
* Dynamic model injection: when the user has configured a specific injectionModel,
|
|
117
|
-
* the prompt names it so the agent knows which routed model to delegate to.
|
|
136
|
+
* V1 surface: codex-rs only emits its Proactive delegation developer message on the
|
|
137
|
+
* v2 surface, so when a v1-surface turn arrives at the synthetic top tier (codex
|
|
138
|
+
* converts ultra -> max on the wire, so max arrival means the user picked the top
|
|
139
|
+
* rung) the proxy supplies the same one-liner, wrapped in codex's own
|
|
140
|
+
* <multi_agent_mode> tags. That is ALL v1 gets — no model designation, no roster
|
|
141
|
+
* (kept lean by request, devlog 260710): ultra-tier injection is sufficient there.
|
|
118
142
|
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
143
|
+
* V2 surface (flat spawn_agent — sol/terra under the default mode, EVERY model when
|
|
144
|
+
* `ocx v2 mode v2` forces the pins): codex-rs already emits its own Proactive text
|
|
145
|
+
* there, so the proxy never duplicates it — it adds only model-designation guidance,
|
|
146
|
+
* and only when it has something to designate: an injectionModel and/or a
|
|
147
|
+
* subagentModels roster entry that resolves in the injected catalog. v2 rejects
|
|
148
|
+
* model/effort overrides on a full-history fork (multi_agents_v2/spawn.rs
|
|
149
|
+
* reject_full_fork_spawn_overrides), so the prompt mandates fork_turns "none" or a
|
|
150
|
+
* partial fork plus a self-contained task message.
|
|
151
|
+
* The published spawn_agent schema HIDES model/reasoning_effort by
|
|
152
|
+
* default (hide_spawn_agent_metadata=true upstream — and it must STAY hidden: the
|
|
153
|
+
* ChatGPT backend treats collaboration.spawn_agent as a reserved function and
|
|
154
|
+
* rejects any request whose declared schema deviates, "Invalid Value: 'tools'").
|
|
155
|
+
* That is prompt-workable: SpawnAgentArgs always parses model/reasoning_effort
|
|
156
|
+
* regardless of the flag (spawn.rs), so the prompt tells the model to pass the
|
|
157
|
+
* arguments even though the schema does not list them.
|
|
121
158
|
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
159
|
+
* The v2 body is budgeted to <= 700 chars (V2_GUIDANCE_CHAR_BUDGET): rules first,
|
|
160
|
+
* then the preferred model, then the compact roster of configured `subagentModels`
|
|
161
|
+
* with the effort ladder each advertises in the injected catalog (the list codex-rs
|
|
162
|
+
* validates spawn efforts against). A user-configured `injectionPrompt` replaces the
|
|
163
|
+
* v2 body with {{model}}/{{effort}}/{{roster}} placeholder substitution (own length,
|
|
164
|
+
* user-owned); firing gates are unchanged.
|
|
127
165
|
*/
|
|
128
|
-
export async function multiAgentGuidanceText(parsed: OcxParsedRequest, injectionModel?: string, injectionEffort?: string): Promise<string | null> {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
166
|
+
export async function multiAgentGuidanceText(parsed: OcxParsedRequest, injectionModel?: string, injectionEffort?: string, subagentModels?: string[], injectionPrompt?: string): Promise<string | null> {
|
|
167
|
+
const surface = collabSurface(parsed);
|
|
168
|
+
if (surface === null) return null;
|
|
169
|
+
|
|
170
|
+
if (surface === "v2") {
|
|
171
|
+
// codex-rs supplies the Proactive text on v2; the proxy only adds model-designation
|
|
172
|
+
// guidance, and only when there is something concrete to designate: a configured
|
|
173
|
+
// injectionModel and/or a roster entry that resolves in the injected catalog.
|
|
174
|
+
const roster = await subagentRosterText(subagentModels);
|
|
175
|
+
if (!injectionModel && roster === "") return null;
|
|
176
|
+
if (injectionPrompt) {
|
|
177
|
+
return `<multi_agent_mode>${applyInjectionPlaceholders(injectionPrompt, injectionModel, injectionEffort, roster)}</multi_agent_mode>`;
|
|
178
|
+
}
|
|
179
|
+
let text = "spawn_agent also accepts hidden \"model\" and \"reasoning_effort\" string arguments "
|
|
180
|
+
+ "(not in the schema, but parsed and applied) — never claim sub-agent models cannot be selected. "
|
|
181
|
+
+ "When setting either, set fork_turns to \"none\" (or e.g. \"3\"; full-history forks reject overrides) "
|
|
182
|
+
+ "and make the message self-contained.";
|
|
183
|
+
if (injectionModel) {
|
|
184
|
+
text += ` Preferred sub-agent: model "${injectionModel}"`
|
|
185
|
+
+ (injectionEffort ? `, reasoning_effort "${injectionEffort}"` : "")
|
|
186
|
+
+ " — use it unless the user names another.";
|
|
145
187
|
}
|
|
188
|
+
text += roster;
|
|
189
|
+
if (text.length > V2_GUIDANCE_CHAR_BUDGET) {
|
|
190
|
+
// Roster is the only unbounded part — drop it before breaking the budget.
|
|
191
|
+
text = text.slice(0, text.length - roster.length);
|
|
192
|
+
}
|
|
193
|
+
return `<multi_agent_mode>${text}</multi_agent_mode>`;
|
|
146
194
|
}
|
|
147
195
|
|
|
148
|
-
|
|
196
|
+
const effort = parsed.options.reasoning;
|
|
197
|
+
// v1 keeps only the upstream-parity behavior: Proactive text at the top tier
|
|
198
|
+
// (ultra arrives as max on the wire). No designation/roster payload here.
|
|
199
|
+
if (effort !== "max" && effort !== "ultra") return null;
|
|
200
|
+
return `<multi_agent_mode>${PROACTIVE_MULTI_AGENT_MODE_TEXT}</multi_agent_mode>`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Hard budget for the built-in v2 guidance body (user request: keep injection lean). */
|
|
204
|
+
export const V2_GUIDANCE_CHAR_BUDGET = 700;
|
|
205
|
+
|
|
206
|
+
/** {{model}}/{{effort}}/{{roster}} substitution for the user-configured injectionPrompt. */
|
|
207
|
+
function applyInjectionPlaceholders(prompt: string, model?: string, effort?: string, roster?: string): string {
|
|
208
|
+
return prompt
|
|
209
|
+
.replaceAll("{{model}}", model ?? "")
|
|
210
|
+
.replaceAll("{{effort}}", effort ?? "")
|
|
211
|
+
.replaceAll("{{roster}}", roster ?? "");
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Compact one-line roster of configured sub-agent models, or "" when no configured
|
|
216
|
+
* model resolves to a catalog entry. Efforts come from the injected catalog
|
|
217
|
+
* (catalogModelEfforts) so only rungs codex-rs will actually accept are advertised.
|
|
218
|
+
*/
|
|
219
|
+
async function subagentRosterText(subagentModels?: string[]): Promise<string> {
|
|
220
|
+
const featured = (subagentModels ?? []).filter(id => typeof id === "string" && id.trim().length > 0);
|
|
221
|
+
if (featured.length === 0) return "";
|
|
222
|
+
const { catalogModelEfforts } = await import("../codex/catalog");
|
|
223
|
+
const efforts = catalogModelEfforts(featured);
|
|
224
|
+
const resolved = featured.filter(id => efforts.has(id));
|
|
225
|
+
if (resolved.length === 0) return "";
|
|
226
|
+
const ladders = new Set(resolved.map(id => efforts.get(id)!.join("/")));
|
|
227
|
+
if (ladders.size === 1) {
|
|
228
|
+
// Shared ladder (the common case: the injected catalog advertises one rung set)
|
|
229
|
+
// -> state it once instead of per model, keeping the roster inside the budget.
|
|
230
|
+
const ids = resolved.map(id => `"${id}"`).join(", ");
|
|
231
|
+
return ` Available models (reasoning_effort ${[...ladders][0]}): ${ids}.`;
|
|
232
|
+
}
|
|
233
|
+
const entries = resolved.map(id => `"${id}" (${efforts.get(id)!.join("/")})`);
|
|
234
|
+
return ` Available models (valid reasoning_effort): ${entries.join(", ")}.`;
|
|
149
235
|
}
|
|
150
236
|
|
|
151
237
|
/**
|
|
@@ -358,8 +444,10 @@ export async function handleResponses(
|
|
|
358
444
|
logCtx.provider = route.providerName;
|
|
359
445
|
|
|
360
446
|
// Multi-agent guidance shim: codex-rs emits its Proactive delegation developer
|
|
361
|
-
// message only on the v2 surface.
|
|
362
|
-
//
|
|
447
|
+
// message only on the v2 surface. The proxy fills both gaps: the Proactive text
|
|
448
|
+
// for v1 collab surfaces at the top tier, and the sub-agent model designation on
|
|
449
|
+
// BOTH surfaces when an injectionModel is configured (v2 additionally gets the
|
|
450
|
+
// fork_turns override rules). The surface is judged from the request's own tool list.
|
|
363
451
|
// Runs BEFORE the mock-max clamp below so the synthetic top tier (ultra arrives
|
|
364
452
|
// as max on the codex wire) is still visible. Both request shapes are rewritten.
|
|
365
453
|
{
|
|
@@ -372,8 +460,13 @@ export async function handleResponses(
|
|
|
372
460
|
const rewritten = sanitizeEncryptedContentInPlace(raw?.input);
|
|
373
461
|
if (rewritten > 0) console.warn(`[opencodex] ${route.modelId}: rewrote ${rewritten} plaintext encrypted_content part(s) to input_text (routed-parent spawn compatibility)`);
|
|
374
462
|
}
|
|
375
|
-
const guidance = await multiAgentGuidanceText(parsed, config.injectionModel, config.injectionEffort);
|
|
376
|
-
if (guidance)
|
|
463
|
+
const guidance = await multiAgentGuidanceText(parsed, config.injectionModel, config.injectionEffort, config.subagentModels, config.injectionPrompt);
|
|
464
|
+
if (guidance) {
|
|
465
|
+
injectDeveloperMessage(parsed, guidance);
|
|
466
|
+
console.log(`[opencodex] ${route.modelId}: multi-agent guidance injected (surface=${collabSurface(parsed)}, ${guidance.length} chars)`);
|
|
467
|
+
} else if (collabSurface(parsed) !== null) {
|
|
468
|
+
console.log(`[opencodex] ${route.modelId}: collab surface=${collabSurface(parsed)}, guidance silent (effort=${parsed.options.reasoning ?? "unset"}, injectionModel=${config.injectionModel ?? "unset"})`);
|
|
469
|
+
}
|
|
377
470
|
}
|
|
378
471
|
|
|
379
472
|
// Mock-max clamp: native models whose real ladder stops below max (gpt-5.5/5.4/…)
|
|
@@ -788,8 +881,13 @@ export async function handleResponses(
|
|
|
788
881
|
: await fetchWithHeaderTimeout(retryRequest.url, {
|
|
789
882
|
method: retryRequest.method, headers: retryRequest.headers, body: retryRequest.body,
|
|
790
883
|
}, upstream.signal, connectMs);
|
|
791
|
-
} catch {
|
|
792
|
-
|
|
884
|
+
} catch (err) {
|
|
885
|
+
cleanupUpstreamAbort();
|
|
886
|
+
upstream.abort();
|
|
887
|
+
const msg = err instanceof Error && err.name === "TimeoutError"
|
|
888
|
+
? `Provider connect timeout after ${connectMs}ms`
|
|
889
|
+
: `Provider unreachable: ${err instanceof Error ? err.message : String(err)}`;
|
|
890
|
+
return formatErrorResponse(502, "upstream_error", msg);
|
|
793
891
|
}
|
|
794
892
|
}
|
|
795
893
|
if (!upstreamResponse.ok) {
|
package/src/types.ts
CHANGED
|
@@ -251,6 +251,14 @@ export interface OcxConfig {
|
|
|
251
251
|
* the Codex ladder (src/reasoning-effort.ts CODEX_REASONING_LEVELS) at the API boundary.
|
|
252
252
|
*/
|
|
253
253
|
injectionEffort?: string;
|
|
254
|
+
/**
|
|
255
|
+
* Custom override for the injected multi-agent guidance body (the text inside the
|
|
256
|
+
* <multi_agent_mode> tags). When set, it replaces the built-in prompt on whichever
|
|
257
|
+
* collab surface would have fired; firing gates are unchanged. Placeholders:
|
|
258
|
+
* `{{model}}` -> injectionModel, `{{effort}}` -> injectionEffort, `{{roster}}` ->
|
|
259
|
+
* the resolved sub-agent roster block ("" when nothing resolves).
|
|
260
|
+
*/
|
|
261
|
+
injectionPrompt?: string;
|
|
254
262
|
/**
|
|
255
263
|
* Models hidden from Codex. Routed ids are namespaced ("<provider>/<model>") and are excluded
|
|
256
264
|
* from the catalog + /v1/models entirely. BARE ids (no "/") are native GPT passthrough slugs:
|
|
@@ -413,6 +421,10 @@ export interface OcxProviderConfig {
|
|
|
413
421
|
* Only the openai-responses adapter implements "forward"; openai-chat uses its own key/token.
|
|
414
422
|
*/
|
|
415
423
|
authMode?: "key" | "forward" | "oauth";
|
|
424
|
+
/** Allow an explicitly key/oauth provider to run without a credential (for keyless local proxies). */
|
|
425
|
+
keyOptional?: boolean;
|
|
426
|
+
/** Strip one trailing bracketed suffix from model ids before sending them upstream. */
|
|
427
|
+
modelSuffixBracketStrip?: boolean;
|
|
416
428
|
/**
|
|
417
429
|
* Override the guardian's proactive-refresh policy for this provider. When unset, the provider's
|
|
418
430
|
* built-in risk-tiered default applies (see OAUTH_PROVIDERS in src/oauth/index.ts). Set "proactive"
|
|
@@ -502,8 +514,6 @@ export interface OcxProviderConfig {
|
|
|
502
514
|
* controlled by their own opt-in config.
|
|
503
515
|
*/
|
|
504
516
|
unsafeAllowNativeLocalExec?: boolean;
|
|
505
|
-
/** @deprecated Use unsafeAllowNativeLocalExec. Kept as a transition alias for local experiments. */
|
|
506
|
-
allowNativeLocalExec?: boolean;
|
|
507
517
|
}
|
|
508
518
|
|
|
509
519
|
export interface CodexAccount {
|
package/src/update/index.ts
CHANGED
|
@@ -62,7 +62,7 @@ function npmSpawnTarget(bin: string): { bin: string; shell: boolean } {
|
|
|
62
62
|
export function latestVersion(tag: string): string | null {
|
|
63
63
|
const npm = npmSpawnTarget("npm");
|
|
64
64
|
const r = spawnSync(npm.bin, ["view", `${PKG}@${tag}`, "version"], { encoding: "utf8", timeout: 12000, windowsHide: true, shell: npm.shell });
|
|
65
|
-
return r.status === 0 ? r.stdout.trim() : null;
|
|
65
|
+
return r.status === 0 ? (r.stdout.trim() || null) : null;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
/** The global-install command opencodex would run to update on this channel. */
|
|
@@ -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)}@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}.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{color:var(--muted);border-radius:var(--radius-sm);padding:5px;transition:background .12s,color .12s}.btn-icon:hover{background:var(--raised-hover);color:var(--text)}.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:center;gap:6px;font-size:12px;display:flex}.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-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{cursor:pointer;background:var(--lightningcss-light,#c5c9d2)var(--lightningcss-dark,#3a3f4b);border:none;border-radius:99px;flex-shrink:0;width:34px;height:19px;padding:0;transition:background .15s;position:relative}.switch.on{background:var(--accent)}.switch:disabled{opacity:.6;cursor:default}.switch .knob{background:#fff;border-radius:50%;width:15px;height:15px;transition:left .15s;position:absolute;top:2px;left:2px;box-shadow:0 1px 2px #1018284d}.switch.on .knob{left:17px}.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{background:var(--rail)}.modal-card{background:var(--surface)}}@media (prefers-reduced-transparency:reduce){body:before{display:none}.sidebar{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}}.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}
|