feat: implement AI request budget tracking, token usage limits, concu…#326
Open
A-one-tech wants to merge 1 commit into
Open
feat: implement AI request budget tracking, token usage limits, concu…#326A-one-tech wants to merge 1 commit into
A-one-tech wants to merge 1 commit into
Conversation
…rrency control, and Redis-backed caching system
codeZe-us
self-requested a review
July 16, 2026 22:36
codeZe-us
approved these changes
Jul 16, 2026
Contributor
|
@A-one-tech please resolve workflow issues |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#Closes
#833
What & Why
A burst of identical
POST /api/ai/explainrequests — from multiple browser tabs,client retries, or automated test harnesses — previously triggered independent LLM
calls, wasting tokens and money. There was also no mechanism to cap total spend,
limit concurrency, or invalidate stale explanations when prompts change.
This PR introduces three interlocking subsystems that close all of those gaps:
Request Flow
Files Changed
New —
apps/server/src/redis/client.ts— Singletonioredisclient with typed helpers used throughout the new modules.cacheSet(key, val, ttl?)SET [EX]cacheGet(key)GETcacheDel(...keys)DELacquireLock(key, val, ttlMs)SET NX PXincrBy / decrByINCRBY / DECRBYgetNumber(key)GET→ number (0 on miss)setExpiry(key, ttl)EXPIREscanKeys(pattern)SCANloopNew —
apps/server/src/dedup/dedup.ts— SHA-256 fingerprinting and lock-based request coalescing.tryAcquire(fp)→{ acquired, fingerprint, requestId }publishResult(fp, json)→ writes result for waiters to pollwaitForResult(fp, pollMs?)→ polls at 250 ms intervals, returnsnullon timeoutNew —
apps/server/src/budget/tracker.ts— Auto-expiring Redis counters, pre-flight assertions, concurrency semaphore.assertBudgetAvailable()— throwsBudgetExceededErrororConcurrencyLimitErrorgetBudgetStatus()— non-throwing read of current countersrecordUsage(tokens)— called after every successful LLM responseerrors.ts— CustomErrorsubclasses caught at the route layer and serialised into structured HTTP 429 bodies.New —
apps/server/src/ai/types.ts— TypeScript interfaces shared across the subsystem:prompts.ts— Versioned templates.PROMPT_VERSION_HASH(first 12 chars ofSHA-256(PROMPT_VERSION)) is embedded in every cache key — bumpingPROMPT_VERSIONauto-invalidates all stale cached explanations on next deploy.client.ts—explainError(reportJson, txHash, network)orchestrates the full pipeline: cache → dedup → budget → concurrency → fetch → record → cache → publish.Calls an OpenAI-compatible
/chat/completionsendpoint withresponse_format: json_object. Provider is swapped by changingAI_API_BASE_URL— no code changes required.New —
apps/server/src/cache/manager.ts— Cache management layer.setCache(type, fp, val)getCache(type, fp)invalidateByTxHash(txHash)invalidateEntry(type, fp)clearByType(type)clearAll()grat:cache:*namespaceNew —
apps/server/src/routes/ai.tsFour new Fastify endpoints registered at
/api:POST/api/ai/explainDiagnosticReport, receiveAIExplanation. Full pipeline.GET/api/ai/budgetDELETE/api/ai/cache/:txHashDELETE/api/ai/cacheBudget/concurrency errors surface as:
Modified — existing files
apps/server/src/config.tsai,budget,cacheconfig blocks — all env-var overridableapps/server/src/index.tsaiRoutesat/apiprefixapps/server/src/workers/replay.worker.tsexplainError()after replay completes; AI failure is non-fatalapps/server/tsconfig.json"types": ["node"],"lib": ["ES2022","DOM"]; excluded__tests__/from build outputapps/server/package.jsontest+test:watchscripts;vitestindevDependenciesEnvironment Variables
Only
AI_API_KEYis required. Omitting it leaves all existing routes unchanged.Tests
All Redis and LLM calls are fully mocked —
npm testrequires no external services.cache.test.tsbudget.test.tsassertBudgetAvailablethrows correct error type; counter accumulation; expiry set on first write; semaphore increment/decrement/floor-at-zerodedup.test.tsAcceptance Criteria
cached: truein response; dedup lock coalesces concurrent waiters onto the single in-flight callreset_attimestamp; hourly and daily counters enforced independentlycache/manager.tsReviewer Checklist
AI_API_KEYadded to staging/production secrets before mergingAI_HOURLY_TOKEN_LIMIT,AI_DAILY_TOKEN_LIMIT) for your environmentPROMPT_VERSIONinapps/server/src/ai/prompts.ts— no manual cache flush required/health,/replay,/session) are untouched; new routes are purely additiveioredis(already independencies) is the only new runtime dependency;vitestis the only newdevDependency