Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/gateway/src/chat/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2852,6 +2852,9 @@ chat.openapi(completions, async (c) => {
: 0;
const totalAvailableCredits = regularCredits + devPlanCreditsRemaining;

// We trust the bare `modelInfo.free` flag here: free models are always
// marked explicitly in the catalog, so a `free: true` model is intended
// to be usable without credits. Do not switch this to isModelTrulyFree.
Comment on lines +2855 to +2857
if (
totalAvailableCredits <= 0 &&
!free_models_only &&
Expand Down
36 changes: 36 additions & 0 deletions apps/gateway/src/chat/tools/resolve-provider-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,40 @@ interface OrgInfo {
devPlanExpiresAt: Date | null;
}

// Mirrors the initial credit gate in chat.ts so retry/fallback paths that
// switch to LLMGateway env-var tokens cannot be used to bill an organization
// with non-positive credits. Free models (explicitly flagged in the catalog)
// are exempt.
function assertOrganizationHasCreditsForEnvFallback(
organization: OrgInfo,
modelInfo: ModelDefinition,
): void {
if (modelInfo.free) {
Comment on lines +116 to +122
return;
}
const regularCredits = parseFloat(organization.credits ?? "0");
const devPlanCreditsRemaining =
organization.devPlan !== "none"
? parseFloat(organization.devPlanCreditsLimit ?? "0") -
parseFloat(organization.devPlanCreditsUsed ?? "0")
: 0;
const totalAvailableCredits = regularCredits + devPlanCreditsRemaining;
if (totalAvailableCredits > 0) {
return;
}
if (organization.devPlan !== "none" && devPlanCreditsRemaining <= 0) {
const renewalDate = organization.devPlanExpiresAt
? new Date(organization.devPlanExpiresAt).toLocaleDateString()
: "your next billing date";
throw new HTTPException(402, {
message: `Dev Plan credit limit reached. Upgrade your plan or wait for renewal on ${renewalDate}.`,
});
}
throw new HTTPException(402, {
message: `Organization ${organization.id} has insufficient credits`,
});
Comment on lines +143 to +145

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid exposing internal organization ID in user-facing error message.

The error message includes organization.id, which leaks an internal identifier to the end user. The equivalent check in chat.ts uses a more generic message without exposing IDs:

"Not enough credits. Please add more credits or contact support."

Consider aligning with the existing pattern for consistency and to avoid exposing internal data.

Suggested fix
 	throw new HTTPException(402, {
-		message: `Organization ${organization.id} has insufficient credits`,
+		message: `Not enough credits. Please add more credits or contact support.`,
 	});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
throw new HTTPException(402, {
message: `Organization ${organization.id} has insufficient credits`,
});
throw new HTTPException(402, {
message: `Not enough credits. Please add more credits or contact support.`,
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/gateway/src/chat/tools/resolve-provider-context.ts` around lines 143 -
145, Replace the user-facing error that exposes organization.id in the
HTTPException thrown in resolve-provider-context.ts: remove the interpolated
organization.id and use a generic, non-identifying message (e.g. "Not enough
credits. Please add more credits or contact support.") when constructing the
HTTPException instance so the thrown HTTPException no longer leaks internal
organization identifiers.

Comment on lines +125 to +145
}

export function formatUsedModelForDisplay(
usedProvider: string,
baseModelName: string,
Expand Down Expand Up @@ -185,6 +219,7 @@ export async function resolveProviderContext(

usedToken = providerKey.token;
} else if (project.mode === "credits") {
assertOrganizationHasCreditsForEnvFallback(organization, modelInfo);
const envResult = getProviderEnv(usedProvider as Provider, {
excludedIndices: options.excludedEnvKeyIndices,
});
Expand All @@ -211,6 +246,7 @@ export async function resolveProviderContext(
if (providerKey) {
usedToken = providerKey.token;
} else {
assertOrganizationHasCreditsForEnvFallback(organization, modelInfo);
const envResult = getProviderEnv(usedProvider as Provider, {
excludedIndices: options.excludedEnvKeyIndices,
});
Comment on lines 219 to 252
Expand Down
Loading