mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-30 21:01:54 +08:00
fix: update AI model selection matrix and custom dashboard generation (#1615)
## What
Refresh the AI model selection matrix and fix a few issues in custom
dashboard generation.
### Models (`apps/backend/src/lib/ai/models.ts`)
- Replace deprecated/placeholder model IDs with current ones:
- `smart/slow` authenticated → `openai/gpt-5.5` (was
`x-ai/grok-build-0.1`)
- `smart/fast` → `google/gemini-3.5-flash`
- `smartest` unauthenticated tiers → `z-ai/glm-5.2` /
`google/gemini-3.5-flash` (was `deepseek/deepseek-v4-flash`)
- `dumb` unauthenticated tiers → `nvidia/nemotron-3-super-120b-a12b`
### Email template rewrite
- Forward `x-stack-*` / `x-hexclave-*` headers from the caller through
the template-source rewrite route so the inner AI call
(`/ai/query/generate`) is authenticated and resolves to the
**authenticated** model tier instead of falling back to the
unauthenticated one.
- Lower rewrite quality to `dumb` / `slow` (sufficient for this task,
cheaper/faster).
### Custom dashboard
- Speed up generation: `smart`/**fast** instead of `smart`/slow (both
`create-dashboard-preview.tsx` and `chat-adapters.ts`).
- Pin `@babel/standalone` to `7.29.7` in the sandbox host (avoid
surprise breakage from `latest`).
- Disable analytics in generated dashboards.
### Misc
- Bump MCP RPC timeout 15s → 45s (`apps/skills/src/mcp-wrapper.ts`).
## Testing
- `pnpm typecheck` ✅
- `pnpm lint` ✅
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Refreshes the model selection matrix, forwards auth headers so template
rewrites use authenticated tiers, and speeds up custom dashboard
generation with a more stable sandbox.
- **Refactors**
- Update model IDs: `openai/gpt-5.5`, `google/gemini-3.5-flash`,
`z-ai/glm-5.2`, `nvidia/nemotron-3-super-120b-a12b`.
- Use `openai/gpt-5.5` for authenticated fast routes.
- Forward `x-stack-*` / `x-hexclave-*` headers; build via Map to avoid
prototype-pollution; inner generate call uses the authenticated tier.
- Lower email template rewrite quality to `dumb`/`slow`.
- Switch dashboard generation to `smart`/`fast` in
`create-dashboard-preview.tsx` and `chat-adapters.ts`.
- Disable analytics in generated dashboards.
- Bump MCP RPC timeout from 15s to 45s.
- **Dependencies**
- Pin `@babel/standalone` to `7.29.7` in the sandbox host.
<sup>Written for commit 94354ae0f6.
Summary will update on new commits.</sup>
<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1615?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
## Release Notes
* **Performance**
* Improved AI generation speed for dashboard creation and related chat
flows by using faster AI routing.
* Increased MCP JSON-RPC request timeout to better handle long-running
operations.
* **Technical**
* Template rewriting with AI now forwards authentication-related headers
to downstream AI calls for more consistent authorized behavior.
* Updated AI model routing/selection used by the proxy layer.
* **UI/Integration**
* Pinned the sandbox Babel CDN script to a specific version.
* Disabled analytics in the sandbox SDK configuration.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
parent
aa6aae4590
commit
81068977ff
@ -26,8 +26,22 @@ export const POST = createSmartRouteHandler({
|
||||
tsx_source: yupString().defined(),
|
||||
}).defined(),
|
||||
}),
|
||||
handler: async ({ body }) => {
|
||||
const rewriteResult = await rewriteTemplateSourceWithAI(body.template_tsx_source);
|
||||
handler: async ({ body }, fullReq) => {
|
||||
// Forward the caller's Hexclave/Stack auth headers so the inner AI call
|
||||
// (which is a fresh HTTP request to /ai/query/generate) is authenticated
|
||||
// and resolves to the authenticated model tier rather than falling back
|
||||
// to the unauthenticated one.
|
||||
const authHeadersMap = new Map<string, string>();
|
||||
for (const [key, value] of Object.entries(fullReq.headers)) {
|
||||
if (value == null) continue;
|
||||
const lower = key.toLowerCase();
|
||||
if (lower.startsWith("x-stack-") || lower.startsWith("x-hexclave-")) {
|
||||
authHeadersMap.set(key, value.join(","));
|
||||
}
|
||||
}
|
||||
const authHeaders: Record<string, string> = Object.fromEntries(authHeadersMap);
|
||||
|
||||
const rewriteResult = await rewriteTemplateSourceWithAI(body.template_tsx_source, authHeaders);
|
||||
if (rewriteResult.status === "error") {
|
||||
throw new KnownErrors.TemplateSourceRewriteError(rewriteResult.error);
|
||||
}
|
||||
|
||||
@ -19,31 +19,31 @@ const MODEL_SELECTION_MATRIX: Record<
|
||||
dumb: {
|
||||
slow: {
|
||||
authenticated: { modelId: "z-ai/glm-4.5-air:free" },
|
||||
unauthenticated: { modelId: "z-ai/glm-4.5-air:free" },
|
||||
unauthenticated: { modelId: "nvidia/nemotron-3-super-120b-a12b" },
|
||||
},
|
||||
fast: {
|
||||
authenticated: { modelId: "openai/gpt-oss-120b:nitro" },
|
||||
unauthenticated: { modelId: "openai/gpt-oss-120b:nitro" },
|
||||
unauthenticated: { modelId: "nvidia/nemotron-3-super-120b-a12b:nitro" },
|
||||
},
|
||||
},
|
||||
smart: {
|
||||
slow: {
|
||||
authenticated: { modelId: "x-ai/grok-build-0.1" },
|
||||
unauthenticated: { modelId: "deepseek/deepseek-v4-flash" },
|
||||
authenticated: { modelId: "openai/gpt-5.5" },
|
||||
unauthenticated: { modelId: "z-ai/glm-5.2" },
|
||||
},
|
||||
fast: {
|
||||
authenticated: { modelId: "x-ai/grok-build-0.1" },
|
||||
unauthenticated: { modelId: "nvidia/nemotron-3-super-120b-a12b:nitro" },
|
||||
authenticated: { modelId: "openai/gpt-5.5" },
|
||||
unauthenticated: { modelId: "google/gemini-3.5-flash" },
|
||||
},
|
||||
},
|
||||
smartest: {
|
||||
slow: {
|
||||
authenticated: { modelId: "openai/gpt-5.5" },
|
||||
unauthenticated: { modelId: "deepseek/deepseek-v4-flash" },
|
||||
unauthenticated: { modelId: "z-ai/glm-5.2" },
|
||||
},
|
||||
fast: {
|
||||
authenticated: { modelId: "openai/gpt-5.5" },
|
||||
unauthenticated: { modelId: "deepseek/deepseek-v4-flash:nitro" },
|
||||
unauthenticated: { modelId: "google/gemini-3.5-flash" },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -13,16 +13,16 @@ function isMockMode() {
|
||||
return key === MOCK_API_KEY_SENTINEL || key === "FORWARD_TO_PRODUCTION";
|
||||
}
|
||||
|
||||
async function rewriteTemplateSourceWithCurrentAIPlumbing(templateTsxSource: string): Promise<Result<string, string>> {
|
||||
async function rewriteTemplateSourceWithCurrentAIPlumbing(templateTsxSource: string, authHeaders: Record<string, string>): Promise<Result<string, string>> {
|
||||
const backendUrl = getEnvVariable("NEXT_PUBLIC_STACK_API_URL");
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), AI_REQUEST_TIMEOUT_MS);
|
||||
try {
|
||||
const response = await fetch(`${backendUrl}/api/latest/ai/query/generate`, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
headers: { "content-type": "application/json", ...authHeaders },
|
||||
body: JSON.stringify({
|
||||
quality: "smart",
|
||||
quality: "dumb",
|
||||
speed: "slow",
|
||||
tools: [],
|
||||
systemPrompt: "rewrite-template-source",
|
||||
@ -115,7 +115,7 @@ function stripCodeFences(text: string): string {
|
||||
return output;
|
||||
}
|
||||
|
||||
export async function rewriteTemplateSourceWithAI(templateTsxSource: string): Promise<Result<string, string>> {
|
||||
export async function rewriteTemplateSourceWithAI(templateTsxSource: string, authHeaders: Record<string, string>): Promise<Result<string, string>> {
|
||||
if (isMockMode()) {
|
||||
const mockRewrittenSource = rewriteTemplateSourceInMockMode(templateTsxSource);
|
||||
const mockRenderResult = await renderEmailWithTemplate(mockRewrittenSource, emptyEmailTheme, {
|
||||
@ -130,7 +130,7 @@ export async function rewriteTemplateSourceWithAI(templateTsxSource: string): Pr
|
||||
|
||||
let lastError = "Unknown rewrite failure";
|
||||
for (let attempt = 0; attempt < MAX_REWRITE_ATTEMPTS; attempt++) {
|
||||
const rewriteResult = await rewriteTemplateSourceWithCurrentAIPlumbing(templateTsxSource);
|
||||
const rewriteResult = await rewriteTemplateSourceWithCurrentAIPlumbing(templateTsxSource, authHeaders);
|
||||
if (rewriteResult.status === "error") {
|
||||
lastError = rewriteResult.error;
|
||||
continue;
|
||||
|
||||
@ -124,7 +124,7 @@ const CreateDashboardPreviewInner = memo(function CreateDashboardPreviewInner({
|
||||
systemPrompt: "create-dashboard",
|
||||
tools: ["update-dashboard"],
|
||||
quality: "smart",
|
||||
speed: "slow",
|
||||
speed: "fast",
|
||||
projectId: projectIdRef.current,
|
||||
transformMessages: async (userMessages) => {
|
||||
const contextMessages = await buildDashboardMessages(
|
||||
|
||||
@ -333,12 +333,7 @@ function getSandboxDocument(artifact: DashboardArtifact, baseUrl: string, dashbo
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
<!-- Babel (for JSX transpilation). crossorigin=anonymous is required so that
|
||||
errors thrown from inside Babel (e.g. JSX SyntaxErrors from AI-generated
|
||||
code) are not sanitized to "Script error." with no message — unpkg sends
|
||||
the matching Access-Control-Allow-Origin header. -->
|
||||
<script src="https://unpkg.com/@babel/standalone/babel.min.js" crossorigin="anonymous"></script>
|
||||
<script src="https://unpkg.com/@babel/standalone@7.29.7/babel.min.js" crossorigin="anonymous"></script>
|
||||
|
||||
<!-- Install a global error listener BEFORE any AI code runs so that Babel parse
|
||||
errors, uncaught runtime throws, and async rejections all reach the parent.
|
||||
@ -464,6 +459,7 @@ function getSandboxDocument(artifact: DashboardArtifact, baseUrl: string, dashbo
|
||||
projectOwnerSession: async () => {
|
||||
return await requestAccessToken();
|
||||
},
|
||||
analytics: { enabled: false },
|
||||
});
|
||||
|
||||
// Expose under both names. AI-generated dashboards (post-PR2 prompt)
|
||||
|
||||
@ -151,7 +151,7 @@ export function createDashboardChatAdapter(
|
||||
systemPrompt: "create-dashboard",
|
||||
tools,
|
||||
quality: "smart",
|
||||
speed: "slow",
|
||||
speed: "fast",
|
||||
projectId,
|
||||
sanitizeContent: sanitizeAiContent,
|
||||
transformMessages: async (messages) => {
|
||||
|
||||
@ -10,7 +10,7 @@ const TOOL_ROUTE_HEADERS = {
|
||||
"Access-Control-Allow-Headers": "*",
|
||||
};
|
||||
|
||||
const MCP_RPC_TIMEOUT_MS = 15_000;
|
||||
const MCP_RPC_TIMEOUT_MS = 45_000;
|
||||
|
||||
type JsonRecord = Record<string, unknown>;
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user