Skip to content

Commit 9681bee

Browse files
committed
refactor(fork): remove redundant outer try/catch, clean indentation
1 parent 4228b6e commit 9681bee

1 file changed

Lines changed: 109 additions & 124 deletions

File tree

  • apps/sim/app/api/mothership/chats/[chatId]/fork

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

Lines changed: 109 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -32,139 +32,124 @@ const logger = createLogger('ForkChatAPI')
3232
*/
3333
export const POST = withRouteHandler(
3434
async (request: NextRequest, context: { params: Promise<{ chatId: string }> }) => {
35-
try {
36-
const { userId, isAuthenticated } = await authenticateCopilotRequestSessionOnly()
37-
if (!isAuthenticated || !userId) {
38-
return createUnauthorizedResponse()
39-
}
35+
const { userId, isAuthenticated } = await authenticateCopilotRequestSessionOnly()
36+
if (!isAuthenticated || !userId) {
37+
return createUnauthorizedResponse()
38+
}
4039

41-
const parsed = await parseRequest(forkMothershipChatContract, request, context, {
42-
validationErrorResponse: () => createBadRequestResponse('upToMessageId is required'),
43-
})
44-
if (!parsed.success) return parsed.response
45-
const { chatId } = parsed.data.params
46-
const { upToMessageId } = parsed.data.body
47-
48-
// Load parent chat and verify ownership.
49-
const [parent] = await db
50-
.select()
51-
.from(copilotChats)
52-
.where(eq(copilotChats.id, chatId))
53-
.limit(1)
54-
55-
if (!parent || parent.userId !== userId || parent.type !== 'mothership') {
56-
return createNotFoundResponse('Chat not found')
57-
}
40+
const parsed = await parseRequest(forkMothershipChatContract, request, context, {
41+
validationErrorResponse: () => createBadRequestResponse('upToMessageId is required'),
42+
})
43+
if (!parsed.success) return parsed.response
44+
const { chatId } = parsed.data.params
45+
const { upToMessageId } = parsed.data.body
46+
47+
const [parent] = await db
48+
.select()
49+
.from(copilotChats)
50+
.where(eq(copilotChats.id, chatId))
51+
.limit(1)
52+
53+
if (!parent || parent.userId !== userId || parent.type !== 'mothership') {
54+
return createNotFoundResponse('Chat not found')
55+
}
5856

59-
if (parent.workspaceId) {
60-
await assertActiveWorkspaceAccess(parent.workspaceId, userId)
61-
}
57+
if (parent.workspaceId) {
58+
await assertActiveWorkspaceAccess(parent.workspaceId, userId)
59+
}
6260

63-
if (parent.conversationId) {
64-
return createBadRequestResponse('Cannot fork a chat with an active stream')
65-
}
61+
if (parent.conversationId) {
62+
return createBadRequestResponse('Cannot fork a chat with an active stream')
63+
}
6664

67-
// Find the fork point in the Sim-side messages array.
68-
const messages = Array.isArray(parent.messages) ? (parent.messages as PersistedMessage[]) : []
69-
const forkIdx = messages.findIndex((m) => m.id === upToMessageId)
70-
if (forkIdx < 0) {
71-
return createBadRequestResponse('Message not found in chat')
72-
}
73-
const forkedMessages = messages.slice(0, forkIdx + 1)
74-
75-
// Resources are stored as a jsonb array on the chat row — copy them directly.
76-
const parentResources = Array.isArray(parent.resources)
77-
? (parent.resources as MothershipResource[])
78-
: []
79-
80-
const newId = generateId()
81-
const baseTitle = (parent.title ?? 'New task').replace(/^Fork \| /, '')
82-
const title = `Fork | ${baseTitle}`
83-
const now = new Date()
84-
85-
// Clone copilot-service conversation state first. If this fails we never
86-
// insert the Sim row, so there is no orphaned UI entry to clean up.
87-
// (The inverse order — Sim INSERT first — required a compensating delete
88-
// and still left a brief window where the row was visible but Go state
89-
// wasn't ready.)
90-
const copilotHeaders: Record<string, string> = { 'Content-Type': 'application/json' }
91-
if (env.COPILOT_API_KEY) {
92-
copilotHeaders['x-api-key'] = env.COPILOT_API_KEY
93-
}
94-
try {
95-
const copilotRes = await fetchGo(`${SIM_AGENT_API_URL}/api/chats/fork`, {
96-
method: 'POST',
97-
headers: copilotHeaders,
98-
body: JSON.stringify({
99-
sourceChatId: chatId,
100-
newChatId: newId,
101-
keepCount: forkedMessages.length,
102-
userId,
103-
}),
104-
spanName: 'sim → go /api/chats/fork',
105-
operation: 'fork_chat',
106-
})
107-
if (!copilotRes.ok) {
108-
const text = await copilotRes.text().catch(() => '')
109-
logger.error('Copilot fork returned non-OK', { status: copilotRes.status, body: text })
110-
return createInternalServerErrorResponse('Failed to fork chat')
111-
}
112-
} catch (err) {
113-
logger.error('Failed to call copilot fork endpoint', { err })
114-
return createInternalServerErrorResponse('Failed to fork chat')
115-
}
65+
const messages = Array.isArray(parent.messages) ? (parent.messages as PersistedMessage[]) : []
66+
const forkIdx = messages.findIndex((m) => m.id === upToMessageId)
67+
if (forkIdx < 0) {
68+
return createBadRequestResponse('Message not found in chat')
69+
}
70+
const forkedMessages = messages.slice(0, forkIdx + 1)
11671

117-
// Go state is ready — now persist the Sim metadata row. If this insert
118-
// fails the Go conversation is orphaned but permanently inaccessible
119-
// (no Sim row = no UI entry), which is harmless.
120-
const [newChat] = await db
121-
.insert(copilotChats)
122-
.values({
123-
id: newId,
124-
userId,
125-
workspaceId: parent.workspaceId,
126-
workflowId: parent.workflowId,
127-
type: parent.type,
128-
title,
129-
model: parent.model,
130-
messages: forkedMessages,
131-
resources: parentResources,
132-
previewYaml: parent.previewYaml,
133-
planArtifact: parent.planArtifact,
134-
config: parent.config,
135-
conversationId: null,
136-
updatedAt: now,
137-
lastSeenAt: now,
138-
})
139-
.returning({ id: copilotChats.id, workspaceId: copilotChats.workspaceId })
140-
141-
if (!newChat) {
142-
logger.error('Failed to insert forked chat row after successful Go fork', {
143-
newId,
144-
chatId,
145-
})
146-
return createInternalServerErrorResponse('Failed to create forked chat')
147-
}
72+
const parentResources = Array.isArray(parent.resources)
73+
? (parent.resources as MothershipResource[])
74+
: []
14875

149-
if (newChat.workspaceId) {
150-
taskPubSub?.publishStatusChanged({
151-
workspaceId: newChat.workspaceId,
152-
chatId: newId,
153-
type: 'created',
154-
})
76+
const newId = generateId()
77+
const baseTitle = (parent.title ?? 'New task').replace(/^Fork \| /, '')
78+
const now = new Date()
79+
80+
// Clone copilot-service conversation state first. If this fails we never
81+
// insert the Sim row, so there is no orphaned UI entry to clean up.
82+
const copilotHeaders: Record<string, string> = { 'Content-Type': 'application/json' }
83+
if (env.COPILOT_API_KEY) {
84+
copilotHeaders['x-api-key'] = env.COPILOT_API_KEY
85+
}
86+
try {
87+
const copilotRes = await fetchGo(`${SIM_AGENT_API_URL}/api/chats/fork`, {
88+
method: 'POST',
89+
headers: copilotHeaders,
90+
body: JSON.stringify({
91+
sourceChatId: chatId,
92+
newChatId: newId,
93+
keepCount: forkedMessages.length,
94+
userId,
95+
}),
96+
spanName: 'sim → go /api/chats/fork',
97+
operation: 'fork_chat',
98+
})
99+
if (!copilotRes.ok) {
100+
const text = await copilotRes.text().catch(() => '')
101+
logger.error('Copilot fork returned non-OK', { status: copilotRes.status, body: text })
102+
return createInternalServerErrorResponse('Failed to fork chat')
155103
}
104+
} catch (err) {
105+
logger.error('Failed to call copilot fork endpoint', { err })
106+
return createInternalServerErrorResponse('Failed to fork chat')
107+
}
156108

157-
captureServerEvent(
109+
// Go state is ready — now persist the Sim metadata row. If this insert
110+
// fails the Go conversation is orphaned but permanently inaccessible
111+
// (no Sim row = no UI entry), which is harmless.
112+
const [newChat] = await db
113+
.insert(copilotChats)
114+
.values({
115+
id: newId,
158116
userId,
159-
'task_forked',
160-
{ workspace_id: parent.workspaceId ?? '', source_chat_id: chatId },
161-
{ groups: { workspace: parent.workspaceId ?? '' } }
162-
)
163-
164-
return NextResponse.json({ success: true, id: newId })
165-
} catch (error) {
166-
logger.error('Error forking chat:', error)
167-
return createInternalServerErrorResponse('Failed to fork chat')
117+
workspaceId: parent.workspaceId,
118+
workflowId: parent.workflowId,
119+
type: parent.type,
120+
title: `Fork | ${baseTitle}`,
121+
model: parent.model,
122+
messages: forkedMessages,
123+
resources: parentResources,
124+
previewYaml: parent.previewYaml,
125+
planArtifact: parent.planArtifact,
126+
config: parent.config,
127+
conversationId: null,
128+
updatedAt: now,
129+
lastSeenAt: now,
130+
})
131+
.returning({ id: copilotChats.id, workspaceId: copilotChats.workspaceId })
132+
133+
if (!newChat) {
134+
logger.error('Failed to insert forked chat row after successful Go fork', { newId, chatId })
135+
return createInternalServerErrorResponse('Failed to create forked chat')
136+
}
137+
138+
if (newChat.workspaceId) {
139+
taskPubSub?.publishStatusChanged({
140+
workspaceId: newChat.workspaceId,
141+
chatId: newId,
142+
type: 'created',
143+
})
168144
}
145+
146+
captureServerEvent(
147+
userId,
148+
'task_forked',
149+
{ workspace_id: parent.workspaceId ?? '', source_chat_id: chatId },
150+
{ groups: { workspace: parent.workspaceId ?? '' } }
151+
)
152+
153+
return NextResponse.json({ success: true, id: newId })
169154
}
170155
)

0 commit comments

Comments
 (0)