8000 Custom dashboards and unified ai no playground by aadesh18 · Pull Request #1243 · stack-auth/stack-auth · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
195 commits
Select commit Hold shift + click to select a range
9e41309
added external-db-sync
aadesh18 Nov 30, 2025
3879001
stopped using yupArray
aadesh18 Nov 30, 2025
937db90
fixed build errors
aadesh18 Nov 30, 2025
3f2a8ef
Revert "fixed build errors"
aadesh18 Nov 30, 2025
dcda402
Update apps/backend/scripts/run-cron-jobs.ts
aadesh18 Dec 1, 2025
9e954b9
Update apps/backend/src/app/api/latest/internal/external-db-sync/poll…
aadesh18 Dec 1, 2025
f0cc311
Update apps/backend/src/app/api/latest/internal/external-db-sync/poll…
aadesh18 Dec 1, 2025
1d7a0de
pr changes
aadesh18 Dec 2, 2025
8c2d1c3
merge dev
BilalG1 Jan 28, 2026
409d89b
Merge remote-tracking branch 'origin/dev' into external-db-sync
BilalG1 Jan 29, 2026
0d2b3b9
fix pnpm lock
BilalG1 Jan 29, 2026
ef8f74e
fix typecheck
BilalG1 Jan 29, 2026
8cdd107
prisma fix
BilalG1 Jan 29, 2026
871fe12
fix tests
BilalG1 Jan 29, 2026
5d6bde2
fix lint
BilalG1 Jan 29, 2026
c4ed09a
fix
BilalG1 Jan 29, 2026
b102db3
fix tests
BilalG1 Jan 29, 2026
2eeb537
fix tests
BilalG1 Jan 29, 2026
c61a2b9
fix test
BilalG1 Jan 30, 2026
ac261d2
increase session test time
BilalG1 Jan 30, 2026
0c1a02e
fix flaky test and external sot sync
BilalG1 Jan 30, 2026
79e330f
comment resolving
BilalG1 Jan 30, 2026
939b1a9
resolve pr comments
BilalG1 Jan 30, 2026
2974c83
retry
BilalG1 Jan 30, 2026
0799a0b
attempt test fixes
BilalG1 Jan 30, 2026
1f67742
fix env
BilalG1 Jan 30, 2026
bd788b4
fix env
BilalG1 Jan 30, 2026
4b7dd53
attempt test fixes
BilalG1 Jan 30, 2026
044377e
fix tests
BilalG1 Jan 30, 2026
744b871
Merge branch 'dev' into external-db-sync
BilalG1 Jan 30, 2026
74c634b
fix lint
BilalG1 Jan 30, 2026
ce5a1bb
fix tests
BilalG1 Jan 30, 2026
2c5440b
fix tests
BilalG1 Jan 30, 2026
f726f61
Merge remote-tracking branch 'origin/dev' into external-db-sync
BilalG1 Jan 31, 2026
665c084
fix tests
BilalG1 Jan 31, 2026
85bb893
Merge branch 'dev' into external-db-sync
BilalG1 Jan 31, 2026
856aaf3
fix tests
BilalG1 Jan 31, 2026
8f3ad45
m
BilalG1 Jan 31, 2026
fe393e9
merge dev
BilalG1 Feb 2, 2026
b9c0ef4
fix lockfile
BilalG1 Feb 2, 2026
b5781a1
Merge remote-tracking branch 'origin/dev' into external-db-sync
BilalG1 Feb 2, 2026
b841f7b
fix tests
BilalG1 Feb 2, 2026
2e0d8de
m
BilalG1 Feb 2, 2026
e5cbc1a
Merge branch 'dev' into external-db-sync
BilalG1 Feb 2, 2026
ba1df26
fix tests
BilalG1 Feb 2, 2026
093eaf0
e2e: isolate external DB sync cleanup per suite (#1148)
BilalG1 Feb 3, 2026
5192875
CI: run dev tests single-worker (#1153)
BilalG1 Feb 3, 2026
686a1e6
Merge branch 'dev' into external-db-sync
BilalG1 Feb 3, 2026
8910138
fixes
BilalG1 Feb 3, 2026
62171dc
single test run
BilalG1 Feb 3, 2026
1038d1a
Merge branch 'dev' into external-db-sync
BilalG1 Feb 3, 2026
3370e63
test fixes (#1155)
BilalG1 Feb 3, 2026
d04e944
concurrent fix
BilalG1 Feb 3, 2026
61f2b79
Merge branch 'external-db-sync' of https://github.com/stack-auth/stac…
BilalG1 Feb 3, 2026
bbac70e
Merge branch 'dev' into external-db-sync
BilalG1 Feb 3, 2026
8e92205
fix routes, external-db dashboard
BilalG1 Feb 4, 2026
3b9c22e
merge dev
BilalG1 Feb 4, 2026
c0a3f7a
replace trigger
BilalG1 Feb 4, 2026
d34a2c7
fusebox
BilalG1 Feb 4, 2026
c91998e
add tracing
BilalG1 Feb 4, 2026
64d9d93
remove old tests
BilalG1 Feb 4, 2026
7fd7886
fix sot test
BilalG1 Feb 4, 2026
719d1c2
Merge remote-tracking branch 'origin/dev' into external-db-sync
BilalG1 Feb 4, 2026
43c1f15
clickhouse user sync
BilalG1 Feb 4, 2026
04970c2
Update vercel.json
N2D4 Feb 4, 2026
4abd410
Various fixes
N2D4 Feb 4, 2026
8247481
test: disable direct external db sync in CI
BilalG1 Feb 5, 2026
362e1fb
merge dev
BilalG1 Feb 5, 2026
98d451d
increase test timeout
BilalG1 Feb 5, 2026
6b89370
cmd k query analytics (#1160)
BilalG1 Feb 5, 2026
ff370b6
Merge branch 'external-db-sync' into external-db-sync-clickhouse-default
BilalG1 Feb 5, 2026
8be639d
Merge branch 'dev' into external-db-sync
BilalG1 Feb 5, 2026
caa3dca
remove fusebox sync engine col
BilalG1 Feb 5, 2026
445e889
Merge branch 'external-db-sync' of https://github.com/stack-auth/stac…
BilalG1 Feb 5, 2026
63df87a
fix test
BilalG1 Feb 5, 2026
9c340f4
Merge branch 'external-db-sync' into external-db-sync-clickhouse-default
BilalG1 Feb 5, 2026
45e7d2d
merge dev
BilalG1 Feb 5, 2026
925a865
fix zod version
BilalG1 Feb 5, 2026
b83063c
move test
BilalG1 Feb 5, 2026
b673ab5
fix lint
BilalG1 Feb 5, 2026
8000
fcf493e
Merge branch 'dev' into external-db-sync-clickhouse-default
BilalG1 Feb 6, 2026
8534caa
Merge branch 'dev' into external-db-sync-clickhouse-default
BilalG1 Feb 9, 2026
2f7acb4
Add STACK_OPENAI_API_KEY to .env.development
N2D4 Feb 10, 2026
cf5558e
pr comment fixes
BilalG1 Feb 12, 2026
4ab4be8
merge dev
BilalG1 Feb 12, 2026
1421461
Add TEST_TIMEOUT to ClickHouse sync tests
BilalG1 Feb 12, 2026
90eb9ae
Merge branch 'dev' into external-db-sync-clickhouse-default
BilalG1 Feb 12, 2026
afa76be
token refresh event change (#1162)
BilalG1 Feb 12, 2026
a509be3
fix type issue
BilalG1 Feb 12, 2026
02b6ef6
Merge branch 'dev' into external-db-sync-clickhouse-default
BilalG1 Feb 12, 2026
62d054e
fix tests
BilalG1 Feb 12, 2026
558d0f6
Merge branch 'external-db-sync-clickhouse-default' of https://github.…
BilalG1 Feb 12, 2026
0dc2e94
fix clickhouse needsResync
BilalG1 Feb 12, 2026
3db832e
Increase ClickHouse poll interval from 500ms to 2s
BilalG1 Feb 12, 2026
3cde6fa
fix test ordering
BilalG1 Feb 13, 2026
a2033bc
added ai custom dashboards
aadesh18 Feb 12, 2026
b01d17b
Update apps/dashboard/src/components/commands/create-dashboard/dashbo…
aadesh18 Feb 12, 2026
0fcc37c
Update apps/dashboard/src/components/commands/create-dashboard/dashbo…
aadesh18 Feb 12, 2026
7c6173e
Update apps/dashboard/src/components/commands/create-dashboard/dashbo…
aadesh18 Feb 12, 2026
416051f
Update apps/dashboard/src/components/commands/create-dashboard/create…
aadesh18 Feb 12, 2026
8cff361
added greptile suggestion
aadesh18 Feb 12, 2026
e107078
fixed conflict
aadesh18 Feb 12, 2026
5accb8b
updated imports
aadesh18 Feb 12, 2026
c52bdd1
Merge commit 'd09a180dfe800a471ca1fb8ea101fd0cdeef0d4d' into custom-d…
N2D4 Feb 17, 2026
ad6eb03
Merge branch 'custom-dashboards-last-from-bilal-2' into custom-dashbo…
N2D4 Feb 17, 2026
579c723
Update apps/dashboard/scripts/bundle-type-definitions.ts
aadesh18 Feb 17, 2026
59f6998
Update apps/dashboard/src/app/api/create-dashboard/route.ts
aadesh18 Feb 17, 2026
4799b34
Update apps/dashboard/src/lib/ai-dashboard/contracts.ts
aadesh18 Feb 17, 2026
bb94e48
Apply suggestion from @N2D4
aadesh18 Feb 17, 2026
3dac1cd
Apply suggestion from @N2D4
aadesh18 Feb 17, 2026
805a78f
Apply suggestion from @N2D4
aadesh18 Feb 17, 2026
e89989e
Apply suggestion from @N2D4
aadesh18 Feb 17, 2026
183ce10
Apply suggestion from @N2D4
aadesh18 Feb 17, 2026
593cb26
dynamic esm versioning
aadesh18 Feb 17, 2026
7c96c52
changes based on the pr review
aadesh18 Feb 17, 2026
64cc8ab
minor bug fix
aadesh18 Feb 17, 2026
4309859
bug fixes
aadesh18 Feb 18, 2026
00c01b3
pnpm lock
aadesh18 Feb 18, 2026
f764f50
Merge remote-tracking branch 'origin/dev' into custom-dashboards
aadesh18 Feb 18, 2026
aaeab15
removed temp file
aadesh18 Feb 18, 2026
7afa78b
v1 working
aadesh18 Feb 19, 2026
a677486
cmd center ai chat working
aadesh18 Feb 19, 2026
0e71470
email templates, drafts and theme working
aadesh18 Feb 20, 2026
592c2c0
docs ai working
aadesh18 Feb 20, 2026
44f4303
saving dashboards + dashboard package
aadesh18 Feb 21, 2026
241f646
Merge remote-tracking branch 'origin/dev' into unified-ai-endpoint
aadesh18 Feb 21, 2026
fc45436
bug fixes
aadesh18 Feb 23, 2026
90701e8
Merge branch 'unified-ai-endpoint' into custom-dashboards
aadesh18 Feb 23, 2026
5acb916
using unified ai endpoint for custom dashboards as well
aadesh18 Feb 23, 2026
91c6bcb
bug fixes
aadesh18 Feb 24, 2026
bca8547
bug fix
aadesh18 Feb 24, 2026
8dbbf9b
pnpm update
aadesh18 Feb 24, 2026
4379976
More stringent caching mitigation
N2D4 Feb 23, 2026
68a5d8d
Merge branch 'dev' into unified-ai-endpoint
aadesh18 Feb 24, 2026
87c18e4
minor issues fixed
aadesh18 Feb 24, 2026
97dead3
minor bug fixes
aadesh18 Feb 24, 2026
c6caf88
bug fixes
aadesh18 Feb 24, 2026
56c4951
Merge remote-tracking branch 'origin/unified-ai-endpoint' into custom…
aadesh18 Feb 24, 2026
66ba625
using unified endpoint
aadesh18 Feb 24, 2026
0b895f9
removed local testing
aadesh18 Feb 24, 2026
261feaf
minor changes
aadesh18 Feb 24, 2026
c031124
more changes
aadesh18 Feb 24, 2026
3fa91d5
minor bug fix
aadesh18 Feb 24, 2026
bcb2534
pnpm changes
aadesh18 Feb 24, 2026
a50240c
removed comment
aadesh18 Feb 25, 2026
e066cac
pr review changes
aadesh18 Feb 25, 2026
0c23229
refactoring changes
aadesh18 Feb 25, 2026
2b6bd54
pnpm changes
aadesh18 Feb 25, 2026
1b8652b
minor changes
aadesh18 Feb 25, 2026
a72c5bd
add comment
aadesh18 Feb 25, 2026
f2ec371
minor bug fix
aadesh18 Feb 25, 2026
fad9ef4
Merge remote-tracking branch 'origin/unified-ai-endpoint' into custom…
aadesh18 Feb 25, 2026
b92a8e9
merge changes
aadesh18 Feb 25, 2026
b412b68
ui changes
aadesh18 Feb 25, 2026
93f7984
fixed bug
aadesh18 Feb 25, 2026
33d9e44
unused file
aadesh18 Feb 25, 2026
a7aa24c
refactoring changes
aadesh18 Feb 25, 2026
6d39d36
Fix casing of allowTransparency attribute
aadesh18 Feb 25, 2026
d866df4
Type cast dashboard variable for better type safety
aadesh18 Feb 25, 2026
e418165
Update allowTransparency prop to use boolean syntax
aadesh18 Feb 25, 2026
2485c1b
working version 1
aadesh18 Feb 26, 2026
957cef3
pnpm changes
aadesh18 Feb 26, 2026
782e592
good version v1
aadesh18 Mar 2, 2026
24e5620
merged unified ai and custom-dashboard-features
aadesh18 Mar 9, 2026
b1ce9 8000 f3
Merge branch 'dev' into custom-dashboards-and-unified-ai
aadesh18 Mar 10, 2026
21e21c8
wip 1
aadesh18 Mar 10, 2026
4d50344
everything working v1
aadesh18 Mar 10, 2026
d45f99d
buggy version
aadesh18 Mar 11, 2026
998d670
slight improvements
aadesh18 Mar 11, 2026
94457fb
working
aadesh18 Mar 12, 2026
d5955b9
Delete .cursor/debug-9f43b8.log
aadesh18 Mar 12, 2026
ec1d658
bug fix
aadesh18 Mar 12, 2026
95b5500
sdk update
aadesh18 Mar 12, 2026
bef05a9
pr changes
aadesh18 Mar 12, 2026
2d0923a
reverse playground changes
aadesh18 Mar 12, 2026
9ee952b
bug fixes
aadesh18 Mar 12, 2026
b039e6a
Merge remote-tracking branch 'origin/dev' into custom-dashboards-and-…
aadesh18 Mar 12, 2026
cfd714f
minor changes
aadesh18 Mar 12, 2026
1680c7e
Merge branch 'dev' into custom-dashboards-and-unified-ai-no-playground
aadesh18 Mar 12, 2026
1ee0b99
more pr changes
aadesh18 Mar 12, 2026
c2baf80
type def changes
aadesh18 Mar 12, 2026
48fdc6e
more pr changes
aadesh18 Mar 13, 2026
0ae952d
type changes
aadesh18 Mar 13, 2026
d6de4e6
test and docs fix
aadesh18 Mar 13, 2026
316cfda
edited test
aadesh18 Mar 13, 2026
b0091ee
fix chat
aadesh18 Mar 13, 2026
3406272
removed 35k lines :]
aadesh18 Mar 13, 2026
0fddfd0
pr changes
aadesh18 Mar 13, 2026
d06ee3f
bug fix
aadesh18 Mar 13, 2026
a74c401
Merge remote-tracking branch 'origin/dev' into custom-dashboards-and-…
aadesh18 Mar 13, 2026
2afa321
bug fix
aadesh18 Mar 13, 2026
d67cbd4
test fix
aadesh18 Mar 13, 2026
91546bf
test fix
aadesh18 Mar 13, 2026
46e7fb0
Merge branch 'dev' into custom-dashboards-and-unified-ai-no-playground
aadesh18 Mar 13, 2026
e2d57c0
dev merge bug fixes
aadesh18 Mar 13, 2026
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
4 changes: 1 addition & 3 deletions apps/backend/.env.development
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,7 @@ STACK_VERCEL_SANDBOX_TOKEN=vercel_sandbox_disabled_for_local_development
STACK_OPENAI_API_KEY=mock_openai_api_key
STACK_STRIPE_SECRET_KEY=sk_test_mockstripekey
STACK_STRIPE_WEBHOOK_SECRET=mock_stripe_webhook_secret

STACK_OPENROUTER_API_KEY=mock-openrouter-api-key

STACK_OPENROUTER_API_KEY=FORWARD_TO_PRODUCTION
# Email monitor configuration for tests
STACK_EMAIL_MONITOR_VERIFICATION_CALLBACK_URL=http://localhost:8101/handler/email-verification
STACK_EMAIL_MONITOR_PROJECT_ID=internal
Expand Down
6 changes: 4 additions & 2 deletions apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,12 @@
"seed": "pnpm run db-seed-script"
},
"dependencies": {
"@ai-sdk/openai": "^1.3.23",
"@ai-sdk/mcp": "^1.0.21",
"@ai-sdk/openai": "^3.0.29",
"@aws-sdk/client-s3": "^3.855.0",
"@clickhouse/client": "^1.14.0",
"@node-oauth/oauth2-server": "^5.1.0",
"@openrouter/ai-sdk-provider": "2.2.3",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/api-logs": "^0.53.0",
"@opentelemetry/auto-instrumentations-node": "^0.67.3",
Expand Down Expand Up @@ -83,7 +85,7 @@
"@vercel/functions": "^2.0.0",
"@vercel/otel": "^1.10.4",
"@vercel/sandbox": "^1.2.0",
"ai": "^4.3.17",
"ai": "^6.0.0",
"bcrypt": "^5.1.1",
"cel-js": "^0.8.2",
"chokidar-cli": "^3.0.0",
Expand Down
136 changes: 136 additions & 0 deletions apps/backend/src/app/api/latest/ai/query/[mode]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { forwardToProduction } from "@/lib/ai/forward";
import { selectModel } from "@/lib/ai/models";
import { getFullSystemPrompt } from "@/lib/ai/prompts";
import { requestBodySchema } from "@/lib/ai/schema";
import { getTools, validateToolNames } from "@/lib/ai/tools";
import { listManagedProjectIds } from "@/lib/projects";
import { SmartResponse } from "@/route-handlers/smart-response";
import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler";
import { yupMixed, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env";
import { StatusError } from "@stackframe/stack-shared/dist/utils/errors";
import { Json } from "@stackframe/stack-shared/dist/utils/json";
import { generateText, ModelMessage, stepCountIs, streamText } from "ai";

export const POST = createSmartRouteHandler({
metadata: {
hidden: true,
},
request: yupObject({
params: yupObject({
mode: yupString().oneOf(["stream", "generate"]).defined(),
}),
body: requestBodySchema,
}),
response: yupMixed<SmartResponse>().defined(),
async handler({ params, body }, fullReq) {
const { mode } = params;

if (!validateToolNames(body.tools)) {
throw new StatusError(StatusError.BadRequest, `Invalid tool names in request.`);
}

const apiKey = getEnvVariable("STACK_OPENROUTER_API_KEY");


if (apiKey === "FORWARD_TO_PRODUCTION") {
const prodResponse = await forwardToProduction(mode, body);
return {
statusCode: prodResponse.status,
bodyType: "response" as const,
body: prodResponse,
};
}

const isAuthenticated = fullReq.auth != null;
const { quality, speed, systemPrompt: systemPromptId, tools: toolNames, messages, projectId } = body;

// Verify user has access to the target project
if (projectId != null) {
const user = fullReq.auth?.user;
if (user == null) {
throw new StatusError(StatusError.Forbidden, "You do not have access to this project");
}
const managedProjectIds = await listManagedProjectIds(user);
if (!managedProjectIds.includes(projectId)) {
throw new StatusError(StatusError.Forbidden, "You do not have access to this project");
}
}

const model = selectModel(quality, speed, isAuthenticated);
const systemPrompt = getFullSystemPrompt(systemPromptId);
const tools = await getTools(toolNames, { auth: fullReq.auth, targetProjectId: projectId });
const toolsArg = Object.keys(tools).length > 0 ? tools : undefined;
const isDocsOrSearch = systemPromptId === "docs-ask-ai" || systemPromptId === "command-center-ask-ai";
const stepLimit = toolsArg == null ? 1 : isDocsOrSearch ? 50 : 5;

if (mode === "stream") {
const result = streamText({
model,
system: systemPrompt,
messages: messages as ModelMessage[],
tools: toolsArg,
stopWhen: stepCountIs(stepLimit),
});
return {
statusCode: 200,
bodyType: "response" as const,
body: result.toUIMessageStreamResponse(),
};
} else {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 120_000);
const result = await generateText({
model,
system: systemPrompt,
messages: messages as ModelMessage[],
tools: toolsArg,
abortSignal: controller.signal,
stopWhen: stepCountIs(stepLimit),
}).finally(() => clearTimeout(timeoutId));

const contentBlocks: Array<
| { type: "text", text: string }
| {
type: "tool-call",
toolName: string,
toolCallId: string,
args: Json,
argsText: string,
result: Json,
}
> = [];

result.steps.forEach((step) => {
if (step.text) {
contentBlocks.push({
type: "text",
text: step.text,
});
}

const toolResultsByCallId = new Map(
step.toolResults.map((r) => [r.toolCallId, r])
);

step.toolCalls.forEach((toolCall) => {
const toolResult = toolResultsByCallId.get(toolCall.toolCallId);
contentBlocks.push({
type: "tool-call",
toolName: toolCall.toolName,
toolCallId: toolCall.toolCallId,
args: toolCall.input,
argsText: JSON.stringify(toolCall.input),
result: (toolResult?.output ?? null) as Json,
});
});
});

return {
statusCode: 200,
bodyType: "json" as const,
body: { content: contentBlocks },
};
}
},
});
140 changes: 1 addition & 139 deletions apps/backend/src/app/api/latest/internal/ai-chat/[threadId]/route.tsx
Original file line number Diff line number Diff line change
@@ -1,144 +1,6 @@
import { getChatAdapter } from "@/lib/ai-chat/adapter-registry";
import { globalPrismaClient } from "@/prisma-client";
import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler";
import { createOpenAI } from "@ai-sdk/openai";
import { adaptSchema, yupArray, yupMixed, yupNumber, yupObject, yupString, yupUnion } from "@stackframe/stack-shared/dist/schema-fields";
import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env";
import { generateText } from "ai";
import { InferType } from "yup";

const textContentSchema = yupObject({
type: yupString().oneOf(["text"]).defined(),
text: yupString().defined(),
});

const toolCallContentSchema = yupObject({
type: yupString().oneOf(["tool-call"]).defined(),
toolName: yupString().defined(),
toolCallId: yupString().defined(),
args: yupMixed().defined(),
argsText: yupString().defined(),
result: yupMixed().defined(),
});

const contentSchema = yupArray(yupUnion(textContentSchema, toolCallContentSchema)).defined();

const messageSchema = yupObject({
role: yupString().oneOf(["user", "assistant", "tool"]).defined(),
content: yupMixed().defined(),
});

// Mock mode sentinel value - when API key is not configured, we return mock responses
const MOCK_API_KEY_SENTINEL = "mock-openrouter-api-key";
const apiKey = getEnvVariable("STACK_OPENROUTER_API_KEY", MOCK_API_KEY_SENTINEL);
const isMockMode = apiKey === MOCK_API_KEY_SENTINEL;

// Only create OpenAI client if not in mock mode
const openai = isMockMode ? null : createOpenAI({
apiKey,
baseURL: "https://openrouter.ai/api/v1",
});

// AI request timeout in milliseconds (2 minutes)
const AI_REQUEST_TIMEOUT_MS = 120_000;

export const POST = createSmartRouteHandler({
metadata: {
hidden: true,
},
request: yupObject({
auth: yupObject({
type: yupString().oneOf(["admin"]).defined(),
tenancy: adaptSchema,
}),
params: yupObject({
threadId: yupString().defined(),
}),
body: yupObject({
context_type: yupString().oneOf(["email-theme", "email-template", "email-draft"]).defined(),
messages: yupArray(messageSchema).defined().min(1),
}),
}),
response: yupObject({
statusCode: yupNumber().oneOf([200]).defined(),
bodyType: yupString().oneOf(["json"]).defined(),
body: yupObject({
content: contentSchema,
}).defined(),
}),
async handler({ body, params, auth: { tenancy } }) {
// Mock mode: return a simple text response without calling AI
if (isMockMode) {
return {
statusCode: 200,
bodyType: "json",
body: {
content: [{
type: "text",
text: "This is a mock AI response. Configure a real API key to enable AI features.",
}],
},
};
}

const adapter = getChatAdapter(body.context_type, tenancy, params.threadId);
// Model is configurable via env var; no default to surface missing config errors
const modelName = getEnvVariable("STACK_AI_MODEL");

if (!openai) {
// This shouldn't happen since we check isMockMode above, but guard anyway
throw new Error("OpenAI client not initialized - STACK_OPENROUTER_API_KEY may be missing");
}

// Validate messages structure before passing to AI
const validatedMessages = body.messages.map(msg => ({
role: msg.role,
content: msg.content,
})) as any; // Cast needed: content is a mixed type from yup schema that doesn't map to AI SDK's strict typing

// Create abort controller for timeout
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), AI_REQUEST_TIMEOUT_MS);

try {
const result = await generateText({
model: openai(modelName),
system: adapter.systemPrompt,
messages: validatedMessages,
tools: adapter.tools,
abortSignal: controller.signal,
});

const contentBlocks: InferType<typeof contentSchema> = [];
result.steps.forEach((step) => {
if (step.text) {
contentBlocks.push({
type: "text",
text: step.text,
});
}
step.toolCalls.forEach(toolCall => {
contentBlocks.push({
type: "tool-call",
toolName: toolCall.toolName,
toolCallId: toolCall.toolCallId,
args: toolCall.args,
argsText: JSON.stringify(toolCall.args),
result: "success",
});
});
});

return {
statusCode: 200,
bodyType: "json",
body: { content: contentBlocks },
};
} finally {
clearTimeout(timeoutId);
}
},
});
import { adaptSchema, yupArray, yupMixed, yupNumber, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";

export const PATCH = createSmartRouteHandler({
metadata: {
Expand Down
Loading
Loading
0