fix: cap maxTokens in OpenRouter calls to prevent 402 billing errors#133
fix: cap maxTokens in OpenRouter calls to prevent 402 billing errors#133pritpatel2412 wants to merge 4 commits into
Conversation
📝 WalkthroughWalkthroughThis PR adds three major features to the Bigset system. First, it introduces Possibly Related PRs
Suggested Reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 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 |
…n-limit # Conflicts: # backend/src/mastra/agents/populate.ts
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
backend/src/mastra/model-wrapper.ts (1)
13-13: 💤 Low valueConsider deep cloning options to prevent shared nested object mutations.
The shallow spread
{ ...options }only clones the top-level properties. Ifoptionscontains nested objects (e.g., configuration objects), modifications to those nested properties by the underlying model would affect both the original and modified options objects.While this is acceptable for the current use case (you're only modifying the primitive
maxTokensfield), consider usingstructuredClone(options)for safer isolation:- const modifiedOptions = { ...options }; + const modifiedOptions = structuredClone(options);This would prevent any potential side effects from nested object mutations.
Also applies to: 24-24
🤖 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 `@backend/src/mastra/model-wrapper.ts` at line 13, The code currently creates a shallow clone via "const modifiedOptions = { ...options }" which can allow nested objects to be shared and mutated; replace this with a deep clone (e.g., use structuredClone(options) to produce modifiedOptions) so changes like setting maxTokens do not mutate nested properties on the original options object; update the same pattern wherever used (the modifiedOptions variable in this module) and, if runtime compatibility requires, add a small fallback deep-clone strategy when structuredClone is unavailable.
🤖 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 `@frontend/convex/runStats.ts`:
- Line 84: Clamp and validate the incoming args.limit (used to compute limit and
a second page-size variable) so negative values and NaN cannot pass:
parse/normalize args.limit to a finite non‑negative integer (e.g., use
Number.isFinite and Math.max(0, Math.floor(Number(args.limit) || 0))) then apply
Math.min(..., MAX_PAGE_SIZE) with DEFAULT_PAGE_SIZE as fallback; update the
computations that set limit and the other page-size variable (the places using
args.limit, DEFAULT_PAGE_SIZE and MAX_PAGE_SIZE) to use this validated positive
safe integer.
In `@scripts/verify-authz.sh`:
- Line 79: The anonymous authz test is failing to exercise the auth check
because the mutation payload for datasets:create is missing the required
maxRowCount field (frontend/convex/datasets.ts validates maxRowCount via
v.number()), so Convex rejects the args before auth runs; update the mutation
call in scripts/verify-authz.sh (the line invoking mutation
'{"path":"datasets:create","args":{...}}') to include a valid numeric
"maxRowCount" (e.g., 100) inside args so the request passes schema validation
and reaches the authentication check that should return "Not authenticated".
---
Nitpick comments:
In `@backend/src/mastra/model-wrapper.ts`:
- Line 13: The code currently creates a shallow clone via "const modifiedOptions
= { ...options }" which can allow nested objects to be shared and mutated;
replace this with a deep clone (e.g., use structuredClone(options) to produce
modifiedOptions) so changes like setting maxTokens do not mutate nested
properties on the original options object; update the same pattern wherever used
(the modifiedOptions variable in this module) and, if runtime compatibility
requires, add a small fallback deep-clone strategy when structuredClone is
unavailable.
🪄 Autofix (Beta)
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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 7189a61f-780f-4663-957e-276578721c40
📒 Files selected for processing (17)
backend/src/mastra/agents/investigate.tsbackend/src/mastra/agents/populate.tsbackend/src/mastra/agents/refresh.tsbackend/src/mastra/model-wrapper.test.tsbackend/src/mastra/model-wrapper.tsbackend/src/mastra/tools/dataset-tools.tsbackend/src/mastra/workflows/populate.tsbackend/src/pipeline/schema-inference.tsfrontend/app/dataset/[id]/page.tsxfrontend/components/SideSheet.tsxfrontend/components/table/DataRow.tsxfrontend/components/table/types.tsfrontend/components/table/use-row-change-detection.tsfrontend/convex/datasetRows.tsfrontend/convex/runStats.tsfrontend/convex/schema.tsscripts/verify-authz.sh
| }, | ||
| handler: async (ctx, args) => { | ||
| const runs = await ctx.db | ||
| const limit = Math.min(args.limit ?? DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE); |
There was a problem hiding this comment.
Validate limit to prevent negative or NaN values.
The current limit clamping allows negative numbers or NaN to pass through (e.g., Math.min(-5, 200) returns -5). Convex's paginate() may not handle these values gracefully.
🛡️ Proposed fix to ensure limit is always a positive safe integer
- const limit = Math.min(args.limit ?? DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE);
+ const limit = Math.max(1, Math.min(args.limit ?? DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE));Apply this change on both Line 84 and Line 110.
Also applies to: 110-110
🤖 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 `@frontend/convex/runStats.ts` at line 84, Clamp and validate the incoming
args.limit (used to compute limit and a second page-size variable) so negative
values and NaN cannot pass: parse/normalize args.limit to a finite non‑negative
integer (e.g., use Number.isFinite and Math.max(0, Math.floor(Number(args.limit)
|| 0))) then apply Math.min(..., MAX_PAGE_SIZE) with DEFAULT_PAGE_SIZE as
fallback; update the computations that set limit and the other page-size
variable (the places using args.limit, DEFAULT_PAGE_SIZE and MAX_PAGE_SIZE) to
use this validated positive safe integer.
| "$(query '{"path":"datasets:listMine","args":{},"format":"json"}' | assert_error_contains 'Not authenticated')" | ||
| run_test "anon datasets.create -> Not authenticated" \ | ||
| "$(mutation '{"path":"datasets:create","args":{"name":"x","description":"x","cadence":"daily","columns":[]},"format":"json"}' | assert_error_contains 'Not authenticated')" | ||
| "$(mutation '{"path":"datasets:create","args":{"name":"x","description":"x","refreshCadence":"daily","columns":[]},"format":"json"}' | assert_error_contains 'Not authenticated')" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify that the anonymous datasets.create test produces "Not authenticated" error
CONVEX="${CONVEX_URL:-http://localhost:3210}"
echo "Testing anonymous datasets.create with current payload..."
RESULT=$(curl -s "$CONVEX/api/mutation" -X POST \
-H 'Content-Type: application/json' \
-d '{"path":"datasets:create","args":{"name":"x","description":"x","refreshCadence":"daily","columns":[]},"format":"json"}')
echo "Response: $RESULT"
# Check if error message contains "Not authenticated"
if echo "$RESULT" | python3 -c "import json,sys; d=json.load(sys.stdin); exit(0 if 'Not authenticated' in d.get('errorMessage','') else 1)"; then
echo "✓ Test passes - returns 'Not authenticated' as expected"
else
echo "✗ Test may fail - check if error is about missing maxRowCount instead"
echo ""
echo "If validation fails, update line 79 to include maxRowCount:"
echo ' {"path":"datasets:create","args":{"name":"x","description":"x","refreshCadence":"daily","maxRowCount":100,"columns":[]},"format":"json"}'
fiRepository: tinyfish-io/bigset
Length of output: 1141
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Show scripts/verify-authz.sh around the referenced line 79
echo "---- scripts/verify-authz.sh: around line 79 ----"
if [ -f scripts/verify-authz.sh ]; then
sed -n '60,95p' scripts/verify-authz.sh | nl -ba
else
echo "File scripts/verify-authz.sh not found"
fi
# 2) Locate the datasets:create / datasets.create mutation definition and show the args validator
echo
echo "---- Search for 'datasets:create' and 'maxRowCount' in datasets mutation code ----"
# Prefer file name datasets.ts; but search repo to be safe
rg -n --hidden --no-ignore-vcs "datasets:create|datasets\.create" .
rg -n --hidden --no-ignore-vcs "maxRowCount" frontend/convex || true
rg -n --hidden --no-ignore-vcs "maxRowCount" . || trueRepository: tinyfish-io/bigset
Length of output: 173
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- scripts/verify-authz.sh existence ----"
ls -la scripts/verify-authz.sh || true
echo
echo "---- scripts/verify-authz.sh: around line 79 (show with line numbers) ----"
if [ -f scripts/verify-authz.sh ]; then
# Use cat -n (available) instead of nl
sed -n '70,90p' scripts/verify-authz.sh | cat -n
fi
echo
echo "---- Search for datasets:create / datasets.create ----"
rg -n --hidden --no-ignore-vcs "datasets:create|datasets\.create" .
echo
echo "---- Search for maxRowCount ----"
rg -n --hidden --no-ignore-vcs "maxRowCount" .Repository: tinyfish-io/bigset
Length of output: 10470
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- frontend/convex/datasets.ts around datasets.create (maxRowCount v.number()) ----"
sed -n '340,470p' frontend/convex/datasets.ts | cat -n
echo
echo "---- frontend/convex/schema.ts around maxRowCount ----"
sed -n '1,120p' frontend/convex/schema.ts | cat -n
echo
echo "---- find auth / Not authenticated usage in datasets.ts ----"
rg -n --hidden --no-ignore-vcs "require.*auth|Not authenticated|unauth" frontend/convex/datasets.ts
echo
echo "---- inspect convex quota pre-flight / requireQuotaRemaining usage in datasets.ts ----"
rg -n --hidden --no-ignore-vcs "requireQuotaRemaining|quota" frontend/convex/datasets.ts frontend/convex/lib/quota.tsRepository: tinyfish-io/bigset
Length of output: 10580
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n --hidden --no-ignore-vcs "DEFAULT_MAX_ROW_COUNT|FREE_TIER_MONTHLY_QUOTA" frontend/convex/datasets.ts frontend/convex/lib/quota.ts frontend/convex/*.tsRepository: tinyfish-io/bigset
Length of output: 1533
🌐 Web query:
Convex validate mutation args before handler is called order of validation vs handler execution
💡 Result:
In Convex, argument validation is performed before the mutation (handler) is invoked. If the client passes arguments that don’t match the args validator, Convex throws an error rather than calling the handler with bad inputs.[1][2] Code-path evidence: Convex’s server implementation calls a generic invokeFunction(...), then runs validateReturnValue(result) afterwards for return validation.[3][4]. The presence of explicit return validation happening after the handler return strongly implies argument validation happens in (or before) the invokeFunction step, i.e., prior to handler execution. Additionally, Convex’s docs describe validation as ensuring functions are “called with the correct types of arguments” and that supplying unexpected arguments causes an error.[1][5] So the effective order is: 1) Validate input arguments against the args validators. 2) If valid, call your handler(ctx, args). 3) After the handler returns, validate the return value against returns (if provided).[1][3][4] If you want, share the exact Convex function definition (query/mutation/action) and your args/returns setup and I can map it to this order more concretely.
Citations:
- 1: https://docs.convex.dev/functions/validation
- 2: https://docs.convex.dev/api/interfaces/server.ValidatedFunction
- 3: https://github.com/get-convex/convex-js/blob/0ac156b0/src/server/impl/registration_impl.ts
- 4: https://github.com/get-convex/convex-js/blob/main/src/server/impl/registration_impl.ts
- 5: https://get-convex-convex-backend.mintlify.app/api/server/mutations
Fix anonymous datasets:create authz test payload to include required maxRowCount (scripts/verify-authz.sh line 79)
datasets.create requires maxRowCount: v.number() in frontend/convex/datasets.ts, and Convex validates mutation args before running the handler—so this request won’t reach the auth check that would produce “Not authenticated”. Include a valid maxRowCount so the test actually exercises authentication.
Update payload
run_test "anon datasets.create -> Not authenticated" \
- "$(mutation '{"path":"datasets:create","args":{"name":"x","description":"x","refreshCadence":"daily","columns":[]},"format":"json"}' | assert_error_contains 'Not authenticated')"
+ "$(mutation '{"path":"datasets:create","args":{"name":"x","description":"x","refreshCadence":"daily","maxRowCount":100,"columns":[]},"format":"json"}' | assert_error_contains 'Not authenticated')"🤖 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 `@scripts/verify-authz.sh` at line 79, The anonymous authz test is failing to
exercise the auth check because the mutation payload for datasets:create is
missing the required maxRowCount field (frontend/convex/datasets.ts validates
maxRowCount via v.number()), so Convex rejects the args before auth runs; update
the mutation call in scripts/verify-authz.sh (the line invoking mutation
'{"path":"datasets:create","args":{...}}') to include a valid numeric
"maxRowCount" (e.g., 100) inside args so the request passes schema validation
and reaches the authentication check that should return "Not authenticated".
Problem
When running populate and refresh workflows, @mastra/core's Agent class defaults to requesting the model's maximum output token limit (e.g. 65,535 tokens for Gemini 2.5 Flash). On OpenRouter, this causes pre-request credit checks to fail with a
402 Payment Requirederror if using the free tier or a low-credit wallet, even if the actual generation uses very few tokens.Changes
wrapModelWithTokenLimit) inbackend/src/mastra/model-wrapper.tsthat intercepts and capsmaxTokensto a safe limit of8192.populate,investigate,refresh), workflows (enumeratestep), and pipeline components (inferSchema).backend/src/mastra/model-wrapper.test.ts) using Node's native runner to verify token capping, property forwarding, and binding preservation.