Skip to content

fix(usage): price new models without waiting a day for the rate table - #9202

Merged
t3dotgg merged 2 commits into
mainfrom
fix/usage-rates-refresh
Sep 3, 2026
Merged

t3dotgg merged 2 commits into
mainfrom
fix/usage-rates-refresh

fix(usage): refetch rates through a dedicated RPC so refresh keeps th…

d378d2e
Select commit
Loading
Failed to load commit list.
MacroscopeApp / Macroscope - Effect Service Conventions succeeded Sep 2, 2026 in 1m 52s

Effect Service Conventions: All clear

All clear

Details

Note

Your check run agent prompt is: .macroscope/check-run-agents/effect-service-conventions.md
More information about how Check Run Agents work can be found in our Help Center.

Reviewed the changed TypeScript in scope (apps/server/src/usage/UsageService.ts, apps/server/src/usage/UsageService.test.ts, apps/server/src/usage/usagePricing.ts, apps/server/src/ws.ts, apps/server/src/auth/RpcAuthorization.ts, apps/web/src/state/usage.ts, apps/mobile/src/state/usage.ts, packages/client-runtime/src/state/server.ts, packages/contracts/src/rpc.ts) against the Effect service conventions.

Checked and found conforming:

  • Namespace subpath imports for Effect modules (effect/Semaphore, effect/Duration, effect/testing/TestClock), matching existing repo usage; named imports retained for @t3tools/contracts types and the pure runAtomCommand helper.
  • UsageService keeps the canonical module order (imports, schemas, Context.Service tag with inline interface, make, layer); refreshRates is added to the inline interface with no standalone shape type.
  • make acquires all dependencies from the environment (FileSystem, Path, ServerConfig, ServerSettingsService, HttpClient, HostProcessEnvironment, Semaphore.make); no service-instance parameters, module-global runtimes, ManagedRuntime.make, or runPromise introduced in service code. layerTest's Layer.succeed remains a pure test seam.
  • Client-side runAtomCommand use stays at the React hook boundary.
  • RPC surface follows existing patterns (Schema.Struct({}) payload, EnvironmentAuthorizationError channel, registered in WsRpcGroup and RPC_REQUIRED_SCOPES); no new error types, predicates, or lint suppressions were added.
  • Behavior change is covered by a focused server test that stubs only the external LiteLLM HTTP response.

🔍 Agent activity — 10 tool calls

💭 I'm checking the changed files for Effect service convention violations, specifically noting the semaphore and test clock imports which suggest this repo uses a newer Effect version with different module paths. That seems fine, so I'll move on to examine the actual files.

1. browse_code — apps/server/src/usage/UsageService.ts

2. git_tools — 70cd258..d378d2e

💭 I'm checking whether the Semaphore import path is valid for this effect version, and reviewing other conventions like Effect.fn, withSpan usage, and inline service interfaces to make sure everything matches existing repo patterns.

3. git_tools — effect/Semaphore

4. git_tools — makeSemaphore|Semaphore

💭 The semaphore usage looks idiomatic. I'm checking the ws.ts handler and RPC error handling, which seem fine since refreshRates never fails. Now I'm looking at whether combining it.live with a TestClock.layer() causes conflicts, since running under the live clock while also providing a TestClock and calling TestClock.adjust seems like it could be a real issue. Tracing through the timing math though, it actually checks out: starting at time 0, then adjusting by two minutes puts currentTimeMillis at 120000, which correctly triggers a refresh past the 60-second floor while staying well under the 24-hour staleness threshold. This seems more like a test-correctness question than an Effect service convention issue, so it might be out of scope for what I'm checking. The main convention items to verify: refreshRates matches the Effect.Effect pattern seen elsewhere, the semaphore properly serializes and re-checks timestamps after waiting (addressing the earlier coalescing concern), and the disk-load path correctly uses maxAgeMs now. On the client side, I want to confirm whether runAtomCommand returns a Promise that supports .finally, and whether using runPromise at a React hook boundary is an acceptable pattern here. Now let me check the client-runtime state and runtime setup.

5. git_tools — export function runAtomCommand|export const runAtomCommand

6. git_tools — runAtomCommand

7. browse_code — packages/client-runtime/src/state/runtime.ts

💭 The runAtomCommand promise resolves rather than rejecting, so .finally should work fine here. I want to verify the shape passed to createEnvironmentRpcCommand matches other commands, and check how empty payload schemas are typically defined elsewhere, like in serverRetryRes.

8. git_tools — WsServerRetryResourceTelemetryRpc = Rpc.make

9. git_tools — mode: "singleFlight"

💭 I'm checking for actual Effect service convention violations now, verifying the ws.ts handler's error channel typing and confirming the module ordering in the service definition file—imports, schema, tag, constants, layerTest, make, layer—all looks correctly structured, with layerTest's pure implementation being an acceptable pattern. Looking closer at refreshRates, I notice Effect.map(pricing) runs after the lock releases, so it could read state mutated by a concurrent fetch—minor but not a real convention violation, and the spans and test coverage look properly set up. I'm now wondering whether combining it.live with a provided TestClock is actually a valid pattern in @effect/vitest, since it.effect normally supplies TestClock automatically. That combination seems intentional here since the service touches real filesystem/timing, though I want to double check that UsagePricing is exported from the contracts module as both a schema value and usable as a type, since it's imported as type UsagePricing in the usage service.

10. git_tools — UsagePricing

💭 No violations found so far. I'm double-checking the mobile/web useUsage import placement and the ws.ts handler naming, both of which seem fine against the conventions, and now looking at how readSummary handles its scope. The refreshRates path isn't deduped through the deferred cache key, but the semaphore already guards concurrent access, and the pricing map returned outside the lock still matches the interface's error-free contract. Module-level mutable state follows an existing pattern, so nothing stands out there either. I'm now weighing how to phrase my final response—there's tension between an instruction to reply with a fixed phrase when nothing is found and the requirement to output valid JSON, so I'll fold that phrase into the JSON summary field instead. Before finalizing, I want to make sure a test is warranted here since this is a behavior change rather than a pure mechanical refactor, so adding one seems appropriate. I'm double-checking the TestClock usage pattern with the live test runner and confirming the semaphore setup inside the generator doesn't need scoping—both look fine, so I'll conclude there are no findings and mark this as a success with a clean summary.


Agent Credits: 84 credits

Token usage: 16 uncached input · 398,075 cache-read input · 72,957 cache-write input · 5,619 output
Agent Credits may also include non-token charges from external tools such as web research.