Unified · Split
| @@ -1,5 +1,10 @@ | |||
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | import { ProviderDriverKind, ProviderInstanceId, type UsageDay } from "@t3tools/contracts"; | 2 | import { |
| 3 | ProviderDriverKind, | ||
| 4 | ProviderInstanceEnvironmentVariableName, | ||
| 5 | ProviderInstanceId, | ||
| 6 | type UsageDay, | ||
| 7 | } from "@t3tools/contracts"; | ||
| 3 | import { expect, it } from "@effect/vitest"; | 8 | import { expect, it } from "@effect/vitest"; |
| 4 | import * as Effect from "effect/Effect"; | 9 | import * as Effect from "effect/Effect"; |
| 5 | import * as Layer from "effect/Layer"; | 10 | import * as Layer from "effect/Layer"; |
| @@ -85,12 +90,26 @@ it.layer(NodeServices.layer)("UsageService", (it) => { · 79 unmodified lines | |||
| 85 | [ProviderInstanceId.make("codex-work")]: { | 90 | [ProviderInstanceId.make("codex-work")]: { |
| 86 | driver: ProviderDriverKind.make("codex"), | 91 | driver: ProviderDriverKind.make("codex"), |
| 87 | enabled: true, | 92 | enabled: true, |
| 88 | config: { homePath: "/virtual/codex-work" }, | 93 | config: {}, |
| 94 | environment: [ | ||
| 95 | { | ||
| 96 | name: ProviderInstanceEnvironmentVariableName.make("CODEX_HOME"), | ||
| 97 | value: "/virtual/codex-work", | ||
| 98 | sensitive: false, | ||
| 99 | }, | ||
| 100 | ], | ||
| 89 | }, | 101 | }, |
| 90 | [ProviderInstanceId.make("codex-work-copy")]: { | 102 | [ProviderInstanceId.make("codex-work-copy")]: { |
| 91 | driver: ProviderDriverKind.make("codex"), | 103 | driver: ProviderDriverKind.make("codex"), |
| 92 | enabled: true, | 104 | enabled: true, |
| 93 | config: { homePath: "/virtual/codex-work" }, | 105 | config: {}, |
| 106 | environment: [ | ||
| 107 | { | ||
| 108 | name: ProviderInstanceEnvironmentVariableName.make("CODEX_HOME"), | ||
| 109 | value: "/virtual/codex-work", | ||
| 110 | sensitive: false, | ||
| 111 | }, | ||
| 112 | ], | ||
| 94 | }, | 113 | }, |
| 95 | }, | 114 | }, |
| 96 | }), | 115 | }), |
| @@ -41,6 +41,7 @@ import { ServerConfig } from "../config.ts"; · 40 unmodified lines | |||
| 41 | import * as ServerSettings from "../serverSettings.ts"; | 41 | import * as ServerSettings from "../serverSettings.ts"; |
| 42 | import { resolveClaudeHomePath } from "../provider/Drivers/ClaudeHome.ts"; | 42 | import { resolveClaudeHomePath } from "../provider/Drivers/ClaudeHome.ts"; |
| 43 | import { resolveCodexHomeLayout } from "../provider/Drivers/CodexHomeLayout.ts"; | 43 | import { resolveCodexHomeLayout } from "../provider/Drivers/CodexHomeLayout.ts"; |
| 44 | import { mergeProviderInstanceEnvironment } from "../provider/ProviderInstanceEnvironment.ts"; | ||
| 44 | import { UsageAggregator } from "./usageAggregation.ts"; | 45 | import { UsageAggregator } from "./usageAggregation.ts"; |
| 45 | import { parseRateTable, type RateTable } from "./usagePricing.ts"; | 46 | import { parseRateTable, type RateTable } from "./usagePricing.ts"; |
| 46 | import { | 47 | import { |
| @@ -236,7 +237,10 @@ export const make = Effect.gen(function* () { · 189 unmodified lines | |||
| 236 | )) { | 237 | )) { |
| 237 | const config = decodeClaudeSettings(instance.config ?? {}); | 238 | const config = decodeClaudeSettings(instance.config ?? {}); |
| 238 | if (Option.isNone(config)) continue; | 239 | if (Option.isNone(config)) continue; |
| 239 | const claudeHome = yield* resolveClaudeHomePath(config.value); | 240 | const processEnv = mergeProviderInstanceEnvironment(instance.environment); |
| 241 | const claudeHome = yield* resolveClaudeHomePath({ | ||
| 242 | homePath: config.value.homePath || processEnv.CLAUDE_CONFIG_DIR || "", | ||
| 243 | }); | ||
| 240 | addDir("claude", yield* resolveClaudeTranscriptDir(claudeHome)); | 244 | addDir("claude", yield* resolveClaudeTranscriptDir(claudeHome)); |
| 241 | } | 245 | } |
| 242 | 246 | ||
| @@ -246,7 +250,11 @@ export const make = Effect.gen(function* () { · 3 unmodified lines | |||
| 246 | )) { | 250 | )) { |
| 247 | const config = decodeCodexSettings(instance.config ?? {}); | 251 | const config = decodeCodexSettings(instance.config ?? {}); |
| 248 | if (Option.isNone(config)) continue; | 252 | if (Option.isNone(config)) continue; |
| 249 | const codexLayout = yield* resolveCodexHomeLayout(config.value); | 253 | const processEnv = mergeProviderInstanceEnvironment(instance.environment); |
| 254 | const codexLayout = yield* resolveCodexHomeLayout({ | ||
| 255 | ...config.value, | ||
| 256 | homePath: config.value.homePath || processEnv.CODEX_HOME || "", | ||
| 257 | }); | ||
| 250 | addDir("codex", path.join(codexLayout.sharedHomePath, "sessions")); | 258 | addDir("codex", path.join(codexLayout.sharedHomePath, "sessions")); |
| 251 | } | 259 | } |
| 252 | return dirs; | 260 | return dirs; |
| @@ -62,9 +62,6 @@ export function UsagePage() { · 61 unmodified lines | |||
| 62 | } | 62 | } |
| 63 | return PROVIDER_ORDER.filter((provider) => enabled.has(provider)); | 63 | return PROVIDER_ORDER.filter((provider) => enabled.has(provider)); |
| 64 | }, [environments, merged.staleEnvironments]); | 64 | }, [environments, merged.staleEnvironments]); |
| 65 | const skeletonProviders = environments.some((environment) => environment.summary !== null) | ||
| 66 | ? providers | ||
| 67 | : PROVIDER_ORDER; | ||
| 68 | 65 | ||
| 69 | // Hold the content until every environment is terminal. Rendering merged | 66 | // Hold the content until every environment is terminal. Rendering merged |
| 70 | // totals while devices are still answering makes every number on the page | 67 | // totals while devices are still answering makes every number on the page |
| @@ -217,7 +214,7 @@ export function UsagePage() { · 146 unmodified lines | |||
| 217 | {settling ? ( | 214 | {settling ? ( |
| 218 | <> | 215 | <> |
| 219 | {environments.length > 1 ? <UsageDeviceStrip environments={environments} /> : null} | 216 | {environments.length > 1 ? <UsageDeviceStrip environments={environments} /> : null} |
| 220 | <UsageSkeleton providers={skeletonProviders} /> | 217 | <UsageSkeleton /> |
| 221 | </> | 218 | </> |
| 222 | ) : ( | 219 | ) : ( |
| 223 | <> | 220 | <> |
| @@ -591,7 +588,7 @@ function UsageDeviceStrip({ · 367 unmodified lines | |||
| 591 | * Static stand-in with the loaded page's shape. No shimmer; blocks fill in | 588 | * Static stand-in with the loaded page's shape. No shimmer; blocks fill in |
| 592 | * exactly once when the last device answers. | 589 | * exactly once when the last device answers. |
| 593 | */ | 590 | */ |
| 594 | function UsageSkeleton({ providers }: { readonly providers: readonly UsageProviderKind[] }) { | 591 | function UsageSkeleton() { |
| 595 | return ( | 592 | return ( |
| 596 | <> | 593 | <> |
| 597 | <section className="grid gap-6 lg:grid-cols-[minmax(0,16rem)_minmax(0,1fr)]"> | 594 | <section className="grid gap-6 lg:grid-cols-[minmax(0,16rem)_minmax(0,1fr)]"> |
| @@ -600,7 +597,7 @@ function UsageSkeleton({ providers }: { readonly providers: readonly UsageProvid · 2 unmodified lines | |||
| 600 | <div className="h-10 w-36 rounded-sm bg-muted" /> | 597 | <div className="h-10 w-36 rounded-sm bg-muted" /> |
| 601 | <div className="h-4 w-32 rounded-sm bg-muted" /> | 598 | <div className="h-4 w-32 rounded-sm bg-muted" /> |
| 602 | </div> | 599 | </div> |
| 603 | {providers.map((provider) => ( | 600 | {PROVIDER_ORDER.map((provider) => ( |
| 604 | <div key={provider} className="flex flex-col gap-1"> | 601 | <div key={provider} className="flex flex-col gap-1"> |
| 605 | <div className="flex min-h-5 items-center justify-between gap-4"> | 602 | <div className="flex min-h-5 items-center justify-between gap-4"> |
| 606 | <span className="flex items-center gap-2"> | 603 | <span className="flex items-center gap-2"> |