Skip to content
Closed
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
32 changes: 32 additions & 0 deletions apps/gateway/src/api.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,38 @@ describe("api", () => {
);
});

test("/v1/chat/completions rejects direct provider pinning for dev-plan orgs even with allowAllModels", async () => {
await db.insert(tables.apiKey).values({
id: "token-id",
token: "real-token",
projectId: "project-id",
description: "Test API Key",
createdBy: "user-id",
});

// allowAllModels only widens which models are available — it must never
// let a coding plan pin a specific provider/mapping.
await harness.setDevPlan({ devPlan: "pro", allowAllModels: true });

const res = await app.request("/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer real-token",
},
body: JSON.stringify({
model: "openai/gpt-4o",
messages: [{ role: "user", content: "hi" }],
}),
});

expect(res.status).toBe(403);
const json = await res.json();
expect(JSON.stringify(json)).toContain(
"Direct provider routing is not available on coding plans",
);
});

test("/v1/chat/completions e2e success", async () => {
await db.insert(tables.apiKey).values({
id: "token-id",
Expand Down
20 changes: 12 additions & 8 deletions apps/gateway/src/chat/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2348,26 +2348,30 @@ chat.openapi(completions, async (c) => {
});
}

if (isDevPlanRestricted) {
if (!isCodingModel(modelInfo)) {
throw new HTTPException(403, {
message: `Model ${modelInfo.id} is not available for coding plans. Coding plans only include models optimized for coding tasks with prompt caching, tool calling, JSON output, and streaming support. You can enable access to all models in your dashboard settings at devpass.llmgateway.io/dashboard, though this may significantly increase costs due to lack of prompt caching.`,
});
}
if (isDevPlanRestricted && !isCodingModel(modelInfo)) {
throw new HTTPException(403, {
message: `Model ${modelInfo.id} is not available for coding plans. Coding plans only include models optimized for coding tasks with prompt caching, tool calling, JSON output, and streaming support. You can enable access to all models in your dashboard settings at devpass.llmgateway.io/dashboard, though this may significantly increase costs due to lack of prompt caching.`,
});
}

// Direct provider/mapping pinning is never allowed on coding plans,
// regardless of `devPlanAllowAllModels` — that toggle only controls which
// models are available, not whether routing can be pinned to a single
// provider. The gateway must own routing so it can prefer cached mappings.
if (isDevPlan) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the original provider when blocking pins

This guard still uses requestedProvider after resolveModelInfo() has normalized it, and that helper clears a pinned provider when its mapping is deactivated. In that scenario (for example azure/gpt-4o-mini, whose Azure mapping is deactivated while the OpenAI mapping remains active), a coding-plan org with devPlanAllowAllModels can send the prefixed model, requestedProvider becomes undefined, and the request proceeds through normal routing instead of returning the intended 403. Check the original parsed provider before it can be cleared to make the new direct-pin block complete.

Useful? React with 👍 / 👎.

if (
requestedProvider &&
requestedProvider !== "llmgateway" &&
requestedProvider !== "custom"
) {
throw new HTTPException(403, {
message: `Direct provider routing is not available on coding plans. Use the root model id (e.g. \`${modelInfo.id}\`) without a provider prefix and let the gateway handle routing. You can enable access to all models in your dashboard settings at code.llmgateway.io/dashboard.`,
message: `Direct provider routing is not available on coding plans. Use the root model id (e.g. \`${modelInfo.id}\`) without a provider prefix and let the gateway handle routing.`,
});
}

if (requestedProvider === "custom") {
throw new HTTPException(403, {
message: `Custom provider routing is not available on coding plans. Use the root model id (e.g. \`${modelInfo.id}\`) without a provider prefix and let the gateway handle routing. You can enable access to all models in your dashboard settings at code.llmgateway.io/dashboard.`,
message: `Custom provider routing is not available on coding plans. Use the root model id (e.g. \`${modelInfo.id}\`) without a provider prefix and let the gateway handle routing.`,
});
}
}
Expand Down
Loading