Skip to content

Commit c2a5b5b

Browse files
authored
universal ai route through ai-proxy (#1312)
<!-- Make sure you've read the CONTRIBUTING.md guidelines: https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Performance Improvements** * Redesigned AI request processing to operate efficiently at the backend level, providing faster responses and improved reliability for AI-powered features * **Enhanced AI Capabilities** * Improved model validation and selection with explicit allowlist of supported models, enabling broader model availability and better feature stability * **Developer Updates** * Refreshed local development environment configuration <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent e8428f5 commit c2a5b5b

6 files changed

Lines changed: 26 additions & 39 deletions

File tree

apps/backend/src/app/api/latest/ai/query/[mode]/route.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { forwardToProduction } from "@/lib/ai/forward";
21
import { selectModel } from "@/lib/ai/models";
32
import { getFullSystemPrompt } from "@/lib/ai/prompts";
43
import { requestBodySchema } from "@/lib/ai/schema";
@@ -7,7 +6,6 @@ import { listManagedProjectIds } from "@/lib/projects";
76
import { SmartResponse } from "@/route-handlers/smart-response";
87
import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler";
98
import { yupMixed, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
10-
import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env";
119
import { StatusError } from "@stackframe/stack-shared/dist/utils/errors";
1210
import { Json } from "@stackframe/stack-shared/dist/utils/json";
1311
import { generateText, ModelMessage, stepCountIs, streamText } from "ai";
@@ -30,18 +28,6 @@ export const POST = createSmartRouteHandler({
3028
throw new StatusError(StatusError.BadRequest, `Invalid tool names in request.`);
3129
}
3230

33-
const apiKey = getEnvVariable("STACK_OPENROUTER_API_KEY");
34-
35-
36-
if (apiKey === "FORWARD_TO_PRODUCTION") {
37-
const prodResponse = await forwardToProduction(mode, body);
38-
return {
39-
statusCode: prodResponse.status,
40-
bodyType: "response" as const,
41-
body: prodResponse,
42-
};
43-
}
44-
4531
const isAuthenticated = fullReq.auth != null;
4632
const { quality, speed, systemPrompt: systemPromptId, tools: toolNames, messages, projectId } = body;
4733

apps/backend/src/app/api/latest/integrations/ai-proxy/[[...path]]/route.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
import { ALLOWED_MODEL_IDS } from "@/lib/ai/models";
12
import { handleApiRequest } from "@/route-handlers/smart-route-handler";
23
import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env";
34
import { StatusError } from "@stackframe/stack-shared/dist/utils/errors";
45
import { NextRequest } from "next/server";
56

67
const OPENROUTER_BASE_URL = "https://openrouter.ai/api";
7-
const OPENROUTER_MODEL = "anthropic/claude-sonnet-4.6";
8+
const OPENROUTER_DEFAULT_MODEL = "anthropic/claude-sonnet-4.6";
89

910
function sanitizeBody(raw: ArrayBuffer): Uint8Array {
1011
const text = new TextDecoder().decode(raw);
@@ -19,7 +20,9 @@ function sanitizeBody(raw: ArrayBuffer): Uint8Array {
1920
throw new StatusError(400, "Request body must be a JSON object");
2021
}
2122

22-
parsed.model = OPENROUTER_MODEL;
23+
if (!parsed.model || !ALLOWED_MODEL_IDS.has(parsed.model)) {
24+
parsed.model = OPENROUTER_DEFAULT_MODEL;
25+
}
2326

2427
// OpenRouter limits metadata.user_id to 128 characters
2528
if (parsed.metadata?.user_id && parsed.metadata.user_id.length > 128) {

apps/backend/src/lib/ai/forward.ts

Lines changed: 0 additions & 17 deletions
This file was deleted.

apps/backend/src/lib/ai/models.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
2-
import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env";
2+
import { getNodeEnvironment } from "@stackframe/stack-shared/dist/utils/env";
33

44
export type ModelQuality = "dumb" | "smart" | "smartest";
55
export type ModelSpeed = "slow" | "fast";
@@ -48,9 +48,24 @@ const MODEL_SELECTION_MATRIX: Record<
4848
},
4949
};
5050

51+
// All unique model IDs referenced in the selection matrix, plus sonnet as the proxy default
52+
export const ALLOWED_MODEL_IDS: ReadonlySet<string> = new Set([
53+
"anthropic/claude-sonnet-4.6",
54+
...Object.values(MODEL_SELECTION_MATRIX).flatMap(quality =>
55+
Object.values(quality).flatMap(speed =>
56+
Object.values(speed).map(config => config.modelId)
57+
)
58+
),
59+
]);
60+
5161
export function createOpenRouterProvider() {
52-
const apiKey = getEnvVariable("STACK_OPENROUTER_API_KEY");
53-
return createOpenRouter({ apiKey });
62+
const baseURL = getNodeEnvironment() === "development"
63+
? "http://localhost:8102/api/latest/integrations/ai-proxy/v1"
64+
: "https://api.stack-auth.com/api/latest/integrations/ai-proxy/v1";
65+
return createOpenRouter({
66+
apiKey: "forwarded",
67+
baseURL,
68+
});
5469
}
5570

5671
export function selectModel(

apps/backend/src/lib/ai/tools/docs.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import { getNodeEnvironment } from "@stackframe/stack-shared/dist/utils/env";
44
/**
55
* Creates an MCP client connected to the Stack Auth documentation server.
66
*
7-
* In development: connects to local docs server at http://localhost:8104
7+
* In development: connects to local docs server at http://localhost:8126
88
* In production: connects to production docs server at https://mcp.stack-auth.com
99
*/
1010
export async function createDocsTools() {
1111
const mcpUrl =
1212
getNodeEnvironment() === "development"
13-
? new URL("/api/internal/mcp", "http://localhost:8104")
13+
? new URL("/api/internal/mcp", "http://localhost:8126")
1414
: new URL("/api/internal/mcp", "https://mcp.stack-auth.com");
1515

1616
const stackAuthMcp = await createMCPClient({

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
"db:migrate": "pnpm pre && pnpm run --filter=@stackframe/backend db:migrate",
5252
"fern": "pnpm pre && pnpm run --filter=@stackframe/docs fern",
5353
"dev:full": "pnpm pre && concurrently -k \"pnpm run generate-sdks:watch\" \"turbo run dev --concurrency 99999\"",
54-
"dev": "pnpm pre && concurrently -k \"pnpm run generate-sdks:watch\" \"pnpm run generate-openapi-docs:watch\" \"turbo run dev --concurrency 99999 --filter=./apps/* --filter=@stackframe/docs-mintlify --filter=./packages/* --filter=./examples/demo \"",
54+
"dev": "pnpm pre && concurrently -k \"pnpm run generate-sdks:watch\" \"pnpm run generate-openapi-docs:watch\" \"turbo run dev --concurrency 99999 --filter=./apps/* --filter=@stackframe/docs-mintlify --filter=@stackframe/stack-docs --filter=./packages/* --filter=./examples/demo \"",
5555
"dev:tui": "pnpm pre && (trap 'kill 0' EXIT; pnpm run generate-sdks:watch & pnpm run generate-openapi-docs:watch & turbo run dev --ui tui --concurrency 99999 --filter=./apps/* --filter=@stackframe/stack-docs --filter=./packages/* --filter=./examples/demo)",
5656
"dev:inspect": "pnpm pre && STACK_BACKEND_DEV_EXTRA_ARGS=\"--inspect\" pnpm run dev",
5757
"dev:profile": "pnpm pre && STACK_BACKEND_DEV_EXTRA_ARGS=\"--experimental-cpu-prof\" pnpm run dev",

0 commit comments

Comments
 (0)