Allow grandfathered orgs to use platform AI - #259
Conversation
Enable access to the platform's OpenRouter key for grandfathered accounts. Previously, these accounts were blocked from the platform fallback, leaving any user without a custom AI config unable to perform analyses. Monthly budget caps and daily spend limits remain in effect to ensure safe usage. Additionally, switch the default platform model to gemini-3.5-flash-lite for improved cost-efficiency.
|
🚅 Deployed to the reqcore-pr-259 environment in applirank
|
📝 WalkthroughWalkthroughThe PR changes the default platform model to ChangesPlatform AI configuration
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant Organization
participant resolveProvider
participant canUsePlatformAi
participant OpenRouter
Organization->>resolveProvider: Request AI provider without BYOK
resolveProvider->>canUsePlatformAi: Check OpenRouter API key
canUsePlatformAi-->>resolveProvider: Platform AI available
resolveProvider->>OpenRouter: Use platform-key fallback with Gemini model
OpenRouter-->>Organization: Return response under budget controls
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
tests/unit/ai-cost-pricing.test.ts (1)
31-34: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winCheck rate equality, not only price presence.
The test confirms that every registry model has a pricing entry, but it does not compare the registry rates in
server/utils/ai/provider.tsLines 112-113 with the billing rates. A mismatch would pass this test and make displayed cost differ from budget calculation. Compare both input and output rates, or use one source of truth.Suggested assertion
for (const model of PROVIDER_REGISTRY.openrouter.models) { - expect(getModelPrice(model.id), `unpriced model: ${model.id}`).not.toBeNull() + const price = getModelPrice(model.id) + expect(price, `unpriced model: ${model.id}`).not.toBeNull() + expect(price?.inputPer1m).toBe(model.inputPricePer1m) + expect(price?.outputPer1m).toBe(model.outputPricePer1m) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/unit/ai-cost-pricing.test.ts` around lines 31 - 34, Update the test around “prices every model offered in the OpenRouter provider registry” to compare each registry model’s input and output rates against the billing rates returned by getModelPrice, rather than only asserting a non-null result. Ensure both rate values must match so displayed pricing and budget calculation remain consistent, or reuse a shared source of truth for the comparison.server/utils/ai/budget.ts (1)
32-37: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winAdd a regression test for the complete
grandfatheredbudget path.The new cap applies only when
resolveOrgPlanIdreturns exactlygrandfathered;freeexits through a different count-based branch. Assert the$2value and test thatassertPlatformBudgetblocks at the monthly boundary for a grandfathered organization.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/utils/ai/budget.ts` around lines 32 - 37, Add regression coverage for the grandfathered path in the budget tests: mock or configure resolveOrgPlanId to return exactly grandfathered, assert the resolved budget is $2, and verify assertPlatformBudget rejects usage at the monthly $2 boundary. Keep free-plan coverage on its existing count-based branch.server/utils/ai/resolveProvider.ts (1)
72-76: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winAdd the platform budget gate to generate criteria paths.
server/api/ai-config/generate-criteria.post.tsresolvesbillingMode: 'platform'but does not checkassertPlatformBudget. Add the same platform budget check there for normal and grandfathered organizations;assertPlatformBudgetalready includes the per-org monthly cap and global daily cap. Add integration coverage around the$2grandfathered/free boundary.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/utils/ai/resolveProvider.ts` around lines 72 - 76, Update the platform billing path in generate-criteria handling to call assertPlatformBudget for both normal and grandfathered organizations before generation proceeds, matching the existing budget-gating behavior. Add integration coverage for the $2 boundary, verifying grandfathered/free organizations are allowed below the limit and rejected at or above it.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@server/utils/ai/resolveProvider.ts`:
- Around line 72-76: The fallback in resolveProvider must apply only when
loadAiConfig reports genuinely missing AI configuration. In the catch path,
distinguish the expected missing-config condition from database, decryption,
read, or validation failures; re-throw all non-missing-config errors before
constructing or returning the platform-backed OpenRouter configuration.
In `@server/utils/env.ts`:
- Around line 204-205: Update server/utils/env.ts lines 204-205 to validate
OPENROUTER_MODEL against MODEL_PRICING at runtime and fail closed when the
configured model has no pricing entry, preserving the default only if it is
priced. Update .env.example lines 120-123 to document this enforced validation
rather than relying on manual configuration.
---
Nitpick comments:
In `@server/utils/ai/budget.ts`:
- Around line 32-37: Add regression coverage for the grandfathered path in the
budget tests: mock or configure resolveOrgPlanId to return exactly
grandfathered, assert the resolved budget is $2, and verify assertPlatformBudget
rejects usage at the monthly $2 boundary. Keep free-plan coverage on its
existing count-based branch.
In `@server/utils/ai/resolveProvider.ts`:
- Around line 72-76: Update the platform billing path in generate-criteria
handling to call assertPlatformBudget for both normal and grandfathered
organizations before generation proceeds, matching the existing budget-gating
behavior. Add integration coverage for the $2 boundary, verifying
grandfathered/free organizations are allowed below the limit and rejected at or
above it.
In `@tests/unit/ai-cost-pricing.test.ts`:
- Around line 31-34: Update the test around “prices every model offered in the
OpenRouter provider registry” to compare each registry model’s input and output
rates against the billing rates returned by getModelPrice, rather than only
asserting a non-null result. Ensure both rate values must match so displayed
pricing and budget calculation remain consistent, or reuse a shared source of
truth for the comparison.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 71349e21-007b-4f1c-99d2-02ae263007e8
📒 Files selected for processing (9)
.env.exampleserver/utils/ai/budget.tsserver/utils/ai/platformConfig.tsserver/utils/ai/pricing.tsserver/utils/ai/provider.tsserver/utils/ai/resolveProvider.tsserver/utils/env.tstests/unit/ai-cost-pricing.test.tstests/unit/ai-provider-grandfathered.test.ts
| // 2: no org config — fall back to the platform key if one is configured. | ||
| // This applies to grandfathered orgs too: they used to be excluded here, | ||
| // which left any grandfathered org without its own key unable to run | ||
| // analysis at all. Platform spend is capped per-org and globally in | ||
| // budget.ts, so the fallback is money-safe for every tier. |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 8 '\bloadAiConfig\s*\(|No AI config|createError|throw' server testsRepository: reqcore-inc/reqcore
Length of output: 50376
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate resolveProvider and ai config helpers =="
fd -a 'resolveProvider\.ts|.*ai.*|.*config.*' server | sed 's#^\./##' | head -200
echo
echo "== resolveProvider.ts =="
cat -n server/utils/ai/resolveProvider.ts | sed -n '1,180p'
echo
echo "== focused loadAiConfig occurrences in server =="
rg -n -C 6 '\bloadAiConfig\s*\(' server --glob '!**/node_modules/**'
echo
echo "== focused No AI config occurrences =="
rg -n -C 4 'No AI config|aiConfig|AI configuration|custom provider|platform.*key|billingMode' server/utils server/lib 2>/dev/null || trueRepository: reqcore-inc/reqcore
Length of output: 26999
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== loadConfig.ts full =="
cat -n server/utils/ai/loadConfig.ts | sed -n '1,90p'
echo
echo "== createError definition and Nuxt error status handling =="
rg -n -C 8 'export function createError|function createError|statusCode|statusMessage|createError' server --glob '!**/node_modules/**' | head -250
echo
echo "== deterministic behavior of current catch guards for representative errors =="
python3 - <<'PY'
def choose(err, platform_key, platform_override_enabled, platform_override):
class BudgetExceededError(Exception):
pass
# Mimic server/utils/ai/resolveProvider.ts catch body logic.
if platform_key:
if not platform_override_enabled(platform_override):
if isinstance(err, BudgetExceededError):
raise BudgetExceededError("hidden and fallback?")
raise err
if platform_override:
return "platform-pinned"
return "platform-generic"
if isinstance(err, BudgetExceededError):
raise BudgetExceededError("hidden and fallback?")
raise err
def platform_override_enabled(value):
return value is not None and value != "false"
class DatabaseError(Exception): pass
class DecryptError(Exception): pass
class SchemaValidationError(Exception): pass
class MissingConfigError(Exception):
status = 422
for err in [DatabaseError("db"), DecryptError("decrypt"), SchemaValidationError("schema"), MissingConfigError("config")]:
result = choose(err, "key", platform_override_enabled, "override")
print(type(err).__name__, "with platform key:", result)
try:
choose(DatabaseError("db"), None, platform_override_enabled, None)
except Exception as e:
print(type(e).__name__, "without platform key:", type(e).__name__)
PYRepository: reqcore-inc/reqcore
Length of output: 20513
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== package lock/import hints for h3/createError =="
if [ -f package.json ]; then cat package.json | sed -n '1,160p'; fi
if [ -f server/package.json ]; then cat server/package.json | sed -n '1,160p'; fi
rg -n '"h3"|"nuxt|nitropack|createError' package.json server/package.json 2>/dev/null || true
echo
echo "== h3/createError types/imports in repo =="
rg -n "from ['\"]h3['\"]|import createError|createError\\(" server --glob '!**/node_modules/**' | head -120Repository: reqcore-inc/reqcore
Length of output: 17706
Only fall back when AI configuration is truly absent.
The catch path also handles database/read/decrypt/validation errors from loadAiConfig; those errors can be transformed into a platform-paid billingMode: 'platform' run. Re-throw non-missing-config errors before returning the platform-backed OpenRouter config.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/utils/ai/resolveProvider.ts` around lines 72 - 76, The fallback in
resolveProvider must apply only when loadAiConfig reports genuinely missing AI
configuration. In the catch path, distinguish the expected missing-config
condition from database, decryption, read, or validation failures; re-throw all
non-missing-config errors before constructing or returning the platform-backed
OpenRouter configuration.
| /** Default model for platform-paid runs, OpenRouter-prefixed. Defaults to google/gemini-3.5-flash-lite. */ | ||
| OPENROUTER_MODEL: emptyToUndefined.pipe(z.string().min(1)).optional().default('google/gemini-3.5-flash-lite'), |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Enforce the platform model pricing invariant at runtime.
An unpriced model can produce a null cost and bypass the monthly platform budget. Documentation alone cannot preserve the budget guarantee.
server/utils/env.ts#L204-L205: validateOPENROUTER_MODELagainstMODEL_PRICING, or fail closed before the platform request..env.example#L120-L123: document the enforced validation instead of relying on manual configuration.
📍 Affects 2 files
server/utils/env.ts#L204-L205(this comment).env.example#L120-L123
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/utils/env.ts` around lines 204 - 205, Update server/utils/env.ts lines
204-205 to validate OPENROUTER_MODEL against MODEL_PRICING at runtime and fail
closed when the configured model has no pricing entry, preserving the default
only if it is priced. Update .env.example lines 120-123 to document this
enforced validation rather than relying on manual configuration.
Enable access to the platform's OpenRouter key for grandfathered accounts.
Previously, these accounts were blocked from the platform fallback, leaving
any user without a custom AI config unable to perform analyses. Monthly budget caps and daily spend limits remain in effect to ensure safe usage.
Additionally, switch the default platform model to gemini-3.5-flash-lite for improved cost-efficiency.
Summary
Type of change
Validation
DCO
Signed-off-by) viagit commit -sSummary by CodeRabbit
New Features
Improvements