@bitkyc08/opencodex 2.7.1 → 2.7.2
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-DGFbiUtS.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 +148 -10
- 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,16 +90,44 @@ 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
|
/**
|
|
@@ -124,14 +152,84 @@ export function isV1CollabSurface(parsed: OcxParsedRequest): boolean {
|
|
|
124
152
|
* calls (codex-rs validates spawn efforts by catalog membership; unsupported rungs
|
|
125
153
|
* are clamped on the wire). An effort WITHOUT a model changes nothing — the gate
|
|
126
154
|
* and the base prompt stay exactly as before.
|
|
155
|
+
*
|
|
156
|
+
* V2 surface (flat spawn_agent — sol/terra under the default mode, EVERY model when
|
|
157
|
+
* `ocx v2 mode v2` forces the pins): codex-rs already emits its own Proactive text
|
|
158
|
+
* there, so the proxy never duplicates it — it adds only model-designation guidance,
|
|
159
|
+
* and only when it has something to designate: an injectionModel and/or a
|
|
160
|
+
* subagentModels roster entry that resolves in the injected catalog. v2 rejects
|
|
161
|
+
* model/effort overrides on a full-history fork (multi_agents_v2/spawn.rs
|
|
162
|
+
* reject_full_fork_spawn_overrides), so the prompt mandates fork_turns "none" or a
|
|
163
|
+
* partial fork plus a self-contained task message.
|
|
164
|
+
* The published spawn_agent schema HIDES model/reasoning_effort by
|
|
165
|
+
* default (hide_spawn_agent_metadata=true upstream — and it must STAY hidden: the
|
|
166
|
+
* ChatGPT backend treats collaboration.spawn_agent as a reserved function and
|
|
167
|
+
* rejects any request whose declared schema deviates, "Invalid Value: 'tools'").
|
|
168
|
+
* That is prompt-workable: SpawnAgentArgs always parses model/reasoning_effort
|
|
169
|
+
* regardless of the flag (spawn.rs), so the prompt tells the model to pass the
|
|
170
|
+
* arguments even though the schema does not list them.
|
|
171
|
+
*
|
|
172
|
+
* Prompt structure follows the OpenAI prompt-engineering guidance (developers.openai.com):
|
|
173
|
+
* explicit rule lists first, a concrete few-shot example call, reference context (the
|
|
174
|
+
* roster) last. A user-configured `injectionPrompt` replaces the built-in body with
|
|
175
|
+
* {{model}}/{{effort}}/{{roster}} placeholder substitution; firing gates are unchanged.
|
|
176
|
+
*
|
|
177
|
+
* Featured roster: whenever guidance fires (either surface), the configured
|
|
178
|
+
* `subagentModels` (the 5-list the catalog features for spawn_agent) are appended
|
|
179
|
+
* with the effort ladder each advertises in the injected catalog — the same list
|
|
180
|
+
* codex-rs validates spawn efforts against — so the agent can pick per-task models
|
|
181
|
+
* and honest effort rungs instead of flying blind behind the hidden schema.
|
|
127
182
|
*/
|
|
128
|
-
export async function multiAgentGuidanceText(parsed: OcxParsedRequest, injectionModel?: string, injectionEffort?: string): Promise<string | null> {
|
|
129
|
-
|
|
183
|
+
export async function multiAgentGuidanceText(parsed: OcxParsedRequest, injectionModel?: string, injectionEffort?: string, subagentModels?: string[], injectionPrompt?: string): Promise<string | null> {
|
|
184
|
+
const surface = collabSurface(parsed);
|
|
185
|
+
if (surface === null) return null;
|
|
186
|
+
|
|
187
|
+
if (surface === "v2") {
|
|
188
|
+
// codex-rs supplies the Proactive text on v2; the proxy only adds model-designation
|
|
189
|
+
// guidance, and only when there is something concrete to designate: a configured
|
|
190
|
+
// injectionModel and/or a roster entry that resolves in the injected catalog.
|
|
191
|
+
const roster = await subagentRosterText(subagentModels);
|
|
192
|
+
if (!injectionModel && roster === "") return null;
|
|
193
|
+
if (injectionPrompt) {
|
|
194
|
+
return `<multi_agent_mode>${applyInjectionPlaceholders(injectionPrompt, injectionModel, injectionEffort, roster)}</multi_agent_mode>`;
|
|
195
|
+
}
|
|
196
|
+
const rules = [
|
|
197
|
+
"- This spawn_agent tool ACCEPTS optional \"model\" and \"reasoning_effort\" string arguments even though its published schema does not list them. The runtime parses and applies both. Never claim sub-agent models cannot be selected.",
|
|
198
|
+
"- When the user names a sub-agent model, pass it in the \"model\" argument exactly as given.",
|
|
199
|
+
"- Any spawn_agent call that sets \"model\" or \"reasoning_effort\" MUST also set \"fork_turns\" to \"none\" (or a positive integer string such as \"3\" for a partial fork); overrides are rejected on a full-history fork.",
|
|
200
|
+
"- With fork_turns \"none\", the child receives NO parent context: write a fully self-contained task message with paths, goals, and constraints.",
|
|
201
|
+
];
|
|
202
|
+
if (injectionModel) {
|
|
203
|
+
rules.push(`- A preferred sub-agent model is configured: "${injectionModel}". Use it for independent sub-tasks unless the user explicitly asks for another model.`);
|
|
204
|
+
}
|
|
205
|
+
if (injectionEffort) {
|
|
206
|
+
rules.push(`- A preferred sub-agent reasoning effort is configured: "${injectionEffort}". Pass it in the "reasoning_effort" argument of those spawn_agent calls.`);
|
|
207
|
+
}
|
|
208
|
+
const exampleModel = injectionModel ?? "gpt-5.6-terra";
|
|
209
|
+
const example = "## Example spawn_agent call\n"
|
|
210
|
+
+ "```json\n"
|
|
211
|
+
+ JSON.stringify({
|
|
212
|
+
task_name: "example_task",
|
|
213
|
+
message: "Self-contained task description with paths, goals, and constraints.",
|
|
214
|
+
fork_turns: "none",
|
|
215
|
+
model: exampleModel,
|
|
216
|
+
...(injectionEffort ? { reasoning_effort: injectionEffort } : {}),
|
|
217
|
+
}, null, 2)
|
|
218
|
+
+ "\n```";
|
|
219
|
+
const text = `## Sub-agent model selection rules\n${rules.join("\n")}\n\n${example}${roster}`;
|
|
220
|
+
return `<multi_agent_mode>${text}</multi_agent_mode>`;
|
|
221
|
+
}
|
|
222
|
+
|
|
130
223
|
const effort = parsed.options.reasoning;
|
|
131
224
|
// When the user has selected a specific injection model, fire the delegation prompt
|
|
132
225
|
// at ANY effort level. Otherwise preserve the original gate: top tier only (max/ultra).
|
|
133
226
|
if (!injectionModel && effort !== "max" && effort !== "ultra") return null;
|
|
134
227
|
|
|
228
|
+
if (injectionPrompt) {
|
|
229
|
+
const roster = await subagentRosterText(subagentModels);
|
|
230
|
+
return `<multi_agent_mode>${applyInjectionPlaceholders(injectionPrompt, injectionModel, injectionEffort, roster)}</multi_agent_mode>`;
|
|
231
|
+
}
|
|
232
|
+
|
|
135
233
|
let text = PROACTIVE_MULTI_AGENT_MODE_TEXT;
|
|
136
234
|
|
|
137
235
|
// Append the selected model when the user has configured a specific injection target.
|
|
@@ -145,9 +243,37 @@ export async function multiAgentGuidanceText(parsed: OcxParsedRequest, injection
|
|
|
145
243
|
}
|
|
146
244
|
}
|
|
147
245
|
|
|
246
|
+
text += await subagentRosterText(subagentModels);
|
|
148
247
|
return `<multi_agent_mode>${text}</multi_agent_mode>`;
|
|
149
248
|
}
|
|
150
249
|
|
|
250
|
+
/** {{model}}/{{effort}}/{{roster}} substitution for the user-configured injectionPrompt. */
|
|
251
|
+
function applyInjectionPlaceholders(prompt: string, model?: string, effort?: string, roster?: string): string {
|
|
252
|
+
return prompt
|
|
253
|
+
.replaceAll("{{model}}", model ?? "")
|
|
254
|
+
.replaceAll("{{effort}}", effort ?? "")
|
|
255
|
+
.replaceAll("{{roster}}", roster ?? "");
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* "\n\nOther available sub-agent models..." roster block, or "" when no configured
|
|
260
|
+
* model resolves to a catalog entry. Efforts come from the injected catalog
|
|
261
|
+
* (catalogModelEfforts) so only rungs codex-rs will actually accept are advertised.
|
|
262
|
+
*/
|
|
263
|
+
async function subagentRosterText(subagentModels?: string[]): Promise<string> {
|
|
264
|
+
const featured = (subagentModels ?? []).filter(id => typeof id === "string" && id.trim().length > 0);
|
|
265
|
+
if (featured.length === 0) return "";
|
|
266
|
+
const { catalogModelEfforts } = await import("../codex/catalog");
|
|
267
|
+
const efforts = catalogModelEfforts(featured);
|
|
268
|
+
const lines = featured
|
|
269
|
+
.filter(id => efforts.has(id))
|
|
270
|
+
.map(id => `- "${id}" (reasoning_effort options: ${efforts.get(id)!.join(", ")})`);
|
|
271
|
+
if (lines.length === 0) return "";
|
|
272
|
+
return "\n\nConfigured sub-agent model roster (valid values for spawn_agent's \"model\" argument, "
|
|
273
|
+
+ "with the reasoning_effort each supports):\n"
|
|
274
|
+
+ lines.join("\n");
|
|
275
|
+
}
|
|
276
|
+
|
|
151
277
|
/**
|
|
152
278
|
* Append a developer message to BOTH request shapes: parsed.context.messages feeds the
|
|
153
279
|
* routed adapters, while the ChatGPT passthrough serializes _rawBody verbatim (same
|
|
@@ -358,8 +484,10 @@ export async function handleResponses(
|
|
|
358
484
|
logCtx.provider = route.providerName;
|
|
359
485
|
|
|
360
486
|
// Multi-agent guidance shim: codex-rs emits its Proactive delegation developer
|
|
361
|
-
// message only on the v2 surface.
|
|
362
|
-
//
|
|
487
|
+
// message only on the v2 surface. The proxy fills both gaps: the Proactive text
|
|
488
|
+
// for v1 collab surfaces at the top tier, and the sub-agent model designation on
|
|
489
|
+
// BOTH surfaces when an injectionModel is configured (v2 additionally gets the
|
|
490
|
+
// fork_turns override rules). The surface is judged from the request's own tool list.
|
|
363
491
|
// Runs BEFORE the mock-max clamp below so the synthetic top tier (ultra arrives
|
|
364
492
|
// as max on the codex wire) is still visible. Both request shapes are rewritten.
|
|
365
493
|
{
|
|
@@ -372,8 +500,13 @@ export async function handleResponses(
|
|
|
372
500
|
const rewritten = sanitizeEncryptedContentInPlace(raw?.input);
|
|
373
501
|
if (rewritten > 0) console.warn(`[opencodex] ${route.modelId}: rewrote ${rewritten} plaintext encrypted_content part(s) to input_text (routed-parent spawn compatibility)`);
|
|
374
502
|
}
|
|
375
|
-
const guidance = await multiAgentGuidanceText(parsed, config.injectionModel, config.injectionEffort);
|
|
376
|
-
if (guidance)
|
|
503
|
+
const guidance = await multiAgentGuidanceText(parsed, config.injectionModel, config.injectionEffort, config.subagentModels, config.injectionPrompt);
|
|
504
|
+
if (guidance) {
|
|
505
|
+
injectDeveloperMessage(parsed, guidance);
|
|
506
|
+
console.log(`[opencodex] ${route.modelId}: multi-agent guidance injected (surface=${collabSurface(parsed)}, ${guidance.length} chars)`);
|
|
507
|
+
} else if (collabSurface(parsed) !== null) {
|
|
508
|
+
console.log(`[opencodex] ${route.modelId}: collab surface=${collabSurface(parsed)}, guidance silent (effort=${parsed.options.reasoning ?? "unset"}, injectionModel=${config.injectionModel ?? "unset"})`);
|
|
509
|
+
}
|
|
377
510
|
}
|
|
378
511
|
|
|
379
512
|
// Mock-max clamp: native models whose real ladder stops below max (gpt-5.5/5.4/…)
|
|
@@ -788,8 +921,13 @@ export async function handleResponses(
|
|
|
788
921
|
: await fetchWithHeaderTimeout(retryRequest.url, {
|
|
789
922
|
method: retryRequest.method, headers: retryRequest.headers, body: retryRequest.body,
|
|
790
923
|
}, upstream.signal, connectMs);
|
|
791
|
-
} catch {
|
|
792
|
-
|
|
924
|
+
} catch (err) {
|
|
925
|
+
cleanupUpstreamAbort();
|
|
926
|
+
upstream.abort();
|
|
927
|
+
const msg = err instanceof Error && err.name === "TimeoutError"
|
|
928
|
+
? `Provider connect timeout after ${connectMs}ms`
|
|
929
|
+
: `Provider unreachable: ${err instanceof Error ? err.message : String(err)}`;
|
|
930
|
+
return formatErrorResponse(502, "upstream_error", msg);
|
|
793
931
|
}
|
|
794
932
|
}
|
|
795
933
|
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}
|