fix(suno): derive current plan from subscription_type + plans[] instead of legacy data.plan#1706
Open
Benjamin-eecs wants to merge 3 commits into
Open
Conversation
There was a problem hiding this comment.
Pull request overview
Fixes Suno session bootstrapping after /api/billing/info/ removed the legacy top-level plan object, restoring functionality for free-tier accounts and preventing read-only commands from failing due to missing planId.
Changes:
- Added a shared
parseSunoBillingInfoparser that derives the current plan fromsubscription_type+plans[](with legacy fallbacks) and preferstotal_credits_leftfor credit totals. - Updated
ensureSunoSessionto use the shared parser and removed the global “planId required” throw so read commands can still run. - Moved the “planId required” validation to
suno generateand added tests for the new behaviors.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| clis/suno/utils.js | Adds parseSunoBillingInfo and reworks ensureSunoSession to parse the new billing/info schema. |
| clis/suno/utils.test.js | Adds unit tests for billing-info parsing and ensures free-tier sessions no longer throw. |
| clis/suno/status.js | Updates status messaging/comments to reflect planId may be null while planKey remains available. |
| clis/suno/generate.js | Enforces planId requirement only for generation (where userTier is needed). |
| clis/suno/generate.test.js | Adds a regression test ensuring generate refuses submission when planId is null. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const session = await ensureSunoSession(page); | ||
| if (!session.planId) { | ||
| throw new CommandExecutionError( | ||
| `Suno generation needs a resolved plan id for the user_tier field, but billing/info did not surface one for this account (subscription_type=${session.planKey}). Verify the account is active at ${SUNO_URL}/account, then retry.`, |
011819b to
4b742c0
Compare
…ad of legacy data.plan
Suno's billing/info response no longer returns a top-level `plan` object.
The current shape is:
{
subscription_type: <false | "pro" | "premier" | ...>,
plans: [{ id, name, plan_key, level, ... }, ...], // all available plans
total_credits_left, monthly_limit, monthly_usage, credit_packs, ...
}
ensureSunoSession parsed `data.plan.id` and `data.plan.plan_key`, both of
which are gone, so every command that called it threw "Suno billing/info
returned no plan id" on free-tier accounts. The throw was also placed in
the shared helper, so `status` / `list` / `download` all failed even
though only `generate` actually needs planId (for the user_tier field).
This change:
- derives currentPlan by matching subscription_type against plans[];
free-tier accounts (subscription_type === false) resolve to the
plans[] entry whose plan_key equals "free", so planId / planKey /
planName are always populated for any logged-in session.
- prefers data.total_credits_left when present (the field the web UI
uses for its "credits remaining" pill), and falls back to summing the
three buckets when absent.
- moves the planId presence check from ensureSunoSession into
generate.js, where the value is actually consumed. status / list /
download no longer need a paid plan to function.
Live-verified on a free-tier account: `suno status` now reports
`Plan: free, Credits: 40, Monthly: 40/50` instead of throwing, and
`suno list` returns clip rows.
Closes jackwener#1704.
The billing/info parsing previously lived inside the in-page IIFE in ensureSunoSession, which made it unreachable from Node-side tests (createSessionPage mocks page.evaluate to return the already-parsed session object, skipping the IIFE body). The new subscription_type vs plans[] derivation had no direct coverage. Pull the parser out as `parseSunoBillingInfo(data)` and inline it into the IIFE via toString(), matching the codex/sidebar.js and gov-policy/search.js pattern. Add direct unit tests covering free-tier, paid-tier, unknown subscription_type, missing plans[], legacy plan fallback, and credit_pack summing. No behaviour change beyond test reach. Live re-verified on a free-tier account: opencli suno status still reports Plan: free, Credits: 40.
JSDoc compresses to the sibling checkSunoCaptcha tone (two lines, the WHY-this-exists hint); the API-shape narrative belongs in the prior commit message, not on every adapter helper. Also drop the planName field from the parser return value: it had no consumer in status / list / download / generate, so it would have been dead session metadata.
4b742c0 to
53782f4
Compare
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.
Description
Suno's
billing/inforesponse no longer returns a top-levelplanobject. The current shape is:ensureSunoSessionparseddata.plan.idanddata.plan.plan_key, both of which are gone. Every command that called the helper threwSuno billing/info returned no plan id ... cannot construct user_tier for generation requeston free-tier accounts. The throw also lived in the shared helper, sostatus/list/downloadall failed even though onlygenerateactually consumesplanId(asuserTier).This PR:
currentPlanby matchingsubscription_typeagainstplans[]. Free-tier accounts (subscription_type === false) resolve to theplans[]entry whoseplan_key === "free", soplanId/planKey/planNameare always populated for any logged-in session.data.total_credits_leftwhen present (the field the web UI uses for its "credits remaining" pill), with the legacy three-bucket sum as fallback.planIdpresence check fromensureSunoSessionintogenerate.js, where the value is actually consumed.status/list/downloadno longer need a paid plan to function.Live-verified on a free-tier account:
suno statusnow reportsPlan: free, Credits: 40, Monthly: 40/50instead of throwing, andsuno listreturns clip rows.Related issue: Closes #1704.
Type of Change
Checklist
Screenshots / Output
Before (
opencli suno statuson free-tier):After: