Skip to content

Commit 2d2f782

Browse files
committed
Fix persistence
1 parent 3ef87e5 commit 2d2f782

File tree

17 files changed

+1427
-22
lines changed

17 files changed

+1427
-22
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import { type NextRequest, NextResponse } from 'next/server'
2+
import { getSession } from '@/lib/auth'
3+
import { env } from '@/lib/core/config/env'
4+
5+
const ENV_URLS: Record<string, string | undefined> = {
6+
dev: env.MOTHERSHIP_DEV_URL,
7+
staging: env.MOTHERSHIP_STAGING_URL,
8+
prod: env.MOTHERSHIP_PROD_URL,
9+
}
10+
11+
function getMothershipUrl(environment: string): string | null {
12+
return ENV_URLS[environment] ?? null
13+
}
14+
15+
/**
16+
* Proxy to the mothership admin API.
17+
*
18+
* Query params:
19+
* env - "dev" | "staging" | "prod"
20+
* endpoint - the admin endpoint path, e.g. "requests", "licenses", "traces"
21+
*
22+
* The request body (for POST) is forwarded as-is. Additional query params
23+
* (e.g. requestId for GET /traces) are forwarded.
24+
*/
25+
export async function POST(req: NextRequest) {
26+
const session = await getSession()
27+
if (!session?.user || session.user.role !== 'admin') {
28+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
29+
}
30+
31+
const adminKey = env.MOTHERSHIP_API_ADMIN_KEY
32+
if (!adminKey) {
33+
return NextResponse.json({ error: 'MOTHERSHIP_API_ADMIN_KEY not configured' }, { status: 500 })
34+
}
35+
36+
const { searchParams } = new URL(req.url)
37+
const environment = searchParams.get('env') || 'dev'
38+
const endpoint = searchParams.get('endpoint')
39+
40+
if (!endpoint) {
41+
return NextResponse.json({ error: 'endpoint query param required' }, { status: 400 })
42+
}
43+
44+
const baseUrl = getMothershipUrl(environment)
45+
if (!baseUrl) {
46+
return NextResponse.json(
47+
{ error: `No URL configured for environment: ${environment}` },
48+
{ status: 400 }
49+
)
50+
}
51+
52+
const targetUrl = `${baseUrl}/api/admin/${endpoint}`
53+
54+
try {
55+
const body = await req.text()
56+
const upstream = await fetch(targetUrl, {
57+
method: 'POST',
58+
headers: {
59+
'Content-Type': 'application/json',
60+
'x-api-key': adminKey,
61+
},
62+
...(body ? { body } : {}),
63+
})
64+
65+
const data = await upstream.json()
66+
return NextResponse.json(data, { status: upstream.status })
67+
} catch (error) {
68+
return NextResponse.json(
69+
{
70+
error: `Failed to reach mothership (${environment}): ${error instanceof Error ? error.message : 'Unknown error'}`,
71+
},
72+
{ status: 502 }
73+
)
74+
}
75+
}
76+
77+
export async function GET(req: NextRequest) {
78+
const session = await getSession()
79+
if (!session?.user || session.user.role !== 'admin') {
80+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
81+
}
82+
83+
const adminKey = env.MOTHERSHIP_API_ADMIN_KEY
84+
if (!adminKey) {
85+
return NextResponse.json({ error: 'MOTHERSHIP_API_ADMIN_KEY not configured' }, { status: 500 })
86+
}
87+
88+
const { searchParams } = new URL(req.url)
89+
const environment = searchParams.get('env') || 'dev'
90+
const endpoint = searchParams.get('endpoint')
91+
92+
if (!endpoint) {
93+
return NextResponse.json({ error: 'endpoint query param required' }, { status: 400 })
94+
}
95+
96+
const baseUrl = getMothershipUrl(environment)
97+
if (!baseUrl) {
98+
return NextResponse.json(
99+
{ error: `No URL configured for environment: ${environment}` },
100+
{ status: 400 }
101+
)
102+
}
103+
104+
const forwardParams = new URLSearchParams()
105+
searchParams.forEach((value, key) => {
106+
if (key !== 'env' && key !== 'endpoint') {
107+
forwardParams.set(key, value)
108+
}
109+
})
110+
111+
const qs = forwardParams.toString()
112+
const targetUrl = `${baseUrl}/api/admin/${endpoint}${qs ? `?${qs}` : ''}`
113+
114+
try {
115+
const upstream = await fetch(targetUrl, {
116+
method: 'GET',
117+
headers: { 'x-api-key': adminKey },
118+
})
119+
120+
const data = await upstream.json()
121+
return NextResponse.json(data, { status: upstream.status })
122+
} catch (error) {
123+
return NextResponse.json(
124+
{
125+
error: `Failed to reach mothership (${environment}): ${error instanceof Error ? error.message : 'Unknown error'}`,
126+
},
127+
{ status: 502 }
128+
)
129+
}
130+
}

apps/sim/app/api/copilot/chat/stream/route.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
readEvents,
1414
SSE_RESPONSE_HEADERS,
1515
} from '@/lib/copilot/request/session'
16+
import { toStreamBatchEvent } from '@/lib/copilot/request/session/types'
1617

1718
export const maxDuration = 3600
1819

@@ -113,11 +114,7 @@ export async function GET(request: NextRequest) {
113114
if (batchMode) {
114115
const afterSeq = afterCursor || '0'
115116
const events = await readEvents(streamId, afterSeq)
116-
const batchEvents = events.map((envelope) => ({
117-
eventId: envelope.seq,
118-
streamId: envelope.stream.streamId,
119-
event: envelope,
120-
}))
117+
const batchEvents = events.map(toStreamBatchEvent)
121118
logger.info('[Resume] Batch response', {
122119
streamId,
123120
afterCursor: afterSeq,

apps/sim/app/api/mothership/chats/[chatId]/route.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
createUnauthorizedResponse,
1313
} from '@/lib/copilot/request/http'
1414
import { readEvents } from '@/lib/copilot/request/session/buffer'
15+
import { type StreamBatchEvent, toStreamBatchEvent } from '@/lib/copilot/request/session/types'
1516
import { taskPubSub } from '@/lib/copilot/tasks'
1617
import { captureServerEvent } from '@/lib/posthog/server'
1718

@@ -47,7 +48,7 @@ export async function GET(
4748
}
4849

4950
let streamSnapshot: {
50-
events: unknown[]
51+
events: StreamBatchEvent[]
5152
status: string
5253
} | null = null
5354

@@ -56,7 +57,7 @@ export async function GET(
5657
const events = await readEvents(chat.conversationId, '0')
5758

5859
streamSnapshot = {
59-
events: events || [],
60+
events: events.map(toStreamBatchEvent),
6061
status: events.length > 0 ? 'active' : 'unknown',
6162
}
6263
} catch (error) {

apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
ToolSearchToolRegex,
3636
WorkspaceFile,
3737
} from '@/lib/copilot/generated/tool-catalog-v1'
38+
import type { StreamBatchEvent } from '@/lib/copilot/request/session/types'
3839
import {
3940
extractResourcesFromToolResult,
4041
isResourceToolName,
@@ -148,12 +149,6 @@ type StreamingFilePreview = {
148149
content: string
149150
}
150151

151-
type StreamBatchEvent = {
152-
eventId: number
153-
streamId: string
154-
event: Record<string, unknown>
155-
}
156-
157152
type StreamBatchResponse = {
158153
success: boolean
159154
events: StreamBatchEvent[]

apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,13 @@ const Admin = dynamic(
142142
import('@/app/workspace/[workspaceId]/settings/components/admin/admin').then((m) => m.Admin),
143143
{ loading: () => <AdminSkeleton /> }
144144
)
145+
const Mothership = dynamic(
146+
() =>
147+
import('@/app/workspace/[workspaceId]/settings/components/mothership/mothership').then(
148+
(m) => m.Mothership
149+
),
150+
{ loading: () => <SettingsSectionSkeleton /> }
151+
)
145152
const RecentlyDeleted = dynamic(
146153
() =>
147154
import(
@@ -175,7 +182,9 @@ export function SettingsPage({ section }: SettingsPageProps) {
175182
? 'general'
176183
: section === 'admin' && !sessionLoading && !isAdminRole
177184
? 'general'
178-
: section
185+
: section === 'mothership' && !sessionLoading && !isAdminRole
186+
? 'general'
187+
: section
179188

180189
const label =
181190
allNavigationItems.find((item) => item.id === effectiveSection)?.label ?? effectiveSection
@@ -207,6 +216,7 @@ export function SettingsPage({ section }: SettingsPageProps) {
207216
{effectiveSection === 'inbox' && <Inbox />}
208217
{effectiveSection === 'recently-deleted' && <RecentlyDeleted />}
209218
{effectiveSection === 'admin' && <Admin />}
219+
{effectiveSection === 'mothership' && <Mothership />}
210220
</div>
211221
)
212222
}

0 commit comments

Comments
 (0)