Skip to content

Commit d47d395

Browse files
committed
Merge branch 'dev' into gcp-stuff
2 parents 50fd34a + c2a5b5b commit d47d395

151 files changed

Lines changed: 5342 additions & 5635 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/lint-and-build.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717

1818
strategy:
1919
matrix:
20-
node-version: [latest]
20+
node-version: [24]
2121

2222
steps:
2323
- uses: actions/checkout@v6

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ To see all development ports, refer to the index.html of `apps/dev-launchpad/pub
108108
- Any design components you add or modify in the dashboard, update the Playground page accordingly to showcase the changes.
109109
- Unless very clearly equivalent from types, prefer explicit null/undefinedness checks over boolean checks, eg. `foo == null` instead of `!foo`.
110110
- Ensure **aggressively** that all code has low coupling and high cohesion. This is really important as it makes sure our code remains consistent and maintainable. Eagerly refactor things into better abstractions and look out for them actively.
111+
- Whenever you change the URL of a page in the docs (or remove one), add a redirect in the docs-mintlify/docs.json file to make sure we don't lose any SEO juice.
111112

112113
### Code-related
113114
- Use ES6 maps instead of records wherever you can.

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({

apps/backend/src/lib/emails-low-level.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ export function isSecureEmailPort(port: number | string) {
1818
return parsedPort === 465 || parsedPort === 2465;
1919
}
2020

21+
function is4yzSMTPResponseCode(code: number | undefined) {
22+
if (typeof code !== 'number') {
23+
return false;
24+
}
25+
return code >= 400 && code < 500;
26+
}
27+
2128
export type LowLevelEmailConfig = {
2229
host: string,
2330
port: number,
@@ -168,6 +175,16 @@ async function _lowLevelSendEmailWithoutRetries(options: LowLevelSendEmailOption
168175
message: 'Connection to email server was lost unexpectedly. This could be due to incorrect email server port configuration or a temporary network issue. Please verify your configuration and try again.',
169176
} as const);
170177
}
178+
// 4yz error codes are considered temporary errors in SMTP, so they should be retried anyway
179+
// This is fallback logic for a code we don't explicitly capture but should still be retryable and we have the code
180+
if (is4yzSMTPResponseCode(responseCode)) {
181+
return Result.error({
182+
rawError: error,
183+
errorType: 'TRANSIENT_NEGATIVE_COMPLETION_REPLY',
184+
canRetry: true,
185+
message: 'The email server returned a temporary error. Please try again later.' + getServerResponse(error),
186+
} as const);
187+
}
171188
}
172189

173190
// ============ temporary error ============

apps/backend/src/lib/local-emulator.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ describe("local emulator config", () => {
7777
await writeConfigToFile(absoluteFilePath, { auth: { allowLocalhost: true } });
7878

7979
await expect(fs.readFile(mountedFilePath, "utf-8")).resolves.toBe(
80-
`export const config = {\n "auth": {\n "allowLocalhost": true\n }\n};\n`
80+
`import type { StackConfig } from "@stackframe/js";\n\nexport const config: StackConfig = {\n "auth": {\n "allowLocalhost": true\n }\n};\n`
8181
);
8282
});
8383

apps/backend/src/lib/local-emulator.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { globalPrismaClient } from "@/prisma-client";
2+
import { detectImportPackageFromDir, renderConfigFileContent } from "@stackframe/stack-shared/dist/config-rendering";
23
import { isValidConfig } from "@stackframe/stack-shared/dist/config/format";
34
import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env";
45
import { StatusError } from "@stackframe/stack-shared/dist/utils/errors";
@@ -97,6 +98,7 @@ export async function writeConfigToFile(filePath: string, config: Record<string,
9798
} else {
9899
await fs.mkdir(dir, { recursive: true });
99100
}
100-
const content = `export const config = ${JSON.stringify(config, null, 2)};\n`;
101+
const importPackage = detectImportPackageFromDir(dir);
102+
const content = renderConfigFileContent(config, importPackage);
101103
await fs.writeFile(resolvedPath, content, "utf-8");
102104
}

0 commit comments

Comments
 (0)