mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
improvement(copilot): drop unused columns from mothership chat detail reads (#4640)
This commit is contained in:
@@ -47,8 +47,8 @@ vi.mock('drizzle-orm', () => ({
|
||||
vi.mock('@/lib/copilot/request/http', () => copilotHttpMock)
|
||||
|
||||
vi.mock('@/lib/copilot/chat/lifecycle', () => ({
|
||||
getAccessibleCopilotChat: mockGetAccessibleCopilotChat,
|
||||
getAccessibleCopilotChatAuth: mockGetAccessibleCopilotChat,
|
||||
getAccessibleCopilotChatWithMessages: mockGetAccessibleCopilotChat,
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/copilot/chat/stream-liveness', () => ({
|
||||
|
||||
@@ -13,8 +13,8 @@ import { parseRequest } from '@/lib/api/server'
|
||||
import { getLatestRunForStream } from '@/lib/copilot/async-runs/repository'
|
||||
import { buildEffectiveChatTranscript } from '@/lib/copilot/chat/effective-transcript'
|
||||
import {
|
||||
getAccessibleCopilotChat,
|
||||
getAccessibleCopilotChatAuth,
|
||||
getAccessibleCopilotChatWithMessages,
|
||||
} from '@/lib/copilot/chat/lifecycle'
|
||||
import { normalizeMessage } from '@/lib/copilot/chat/persisted-message'
|
||||
import { reconcileChatStreamMarkers } from '@/lib/copilot/chat/stream-liveness'
|
||||
@@ -45,7 +45,7 @@ export const GET = withRouteHandler(
|
||||
if (!paramsResult.success) return paramsResult.response
|
||||
const { chatId } = paramsResult.data.params
|
||||
|
||||
const chat = await getAccessibleCopilotChat(chatId, userId)
|
||||
const chat = await getAccessibleCopilotChatWithMessages(chatId, userId)
|
||||
if (!chat || chat.type !== 'mothership') {
|
||||
return NextResponse.json({ success: false, error: 'Chat not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ const logger = createLogger('CopilotChatLifecycle')
|
||||
|
||||
export interface ChatLoadResult {
|
||||
chatId: string
|
||||
chat: typeof copilotChats.$inferSelect | null
|
||||
chat: CopilotChatDetailRow | null
|
||||
conversationHistory: unknown[]
|
||||
isNew: boolean
|
||||
}
|
||||
@@ -34,11 +34,43 @@ const copilotChatAuthColumns = {
|
||||
type: copilotChats.type,
|
||||
} as const
|
||||
|
||||
/**
|
||||
* Column set for chat-detail callers that need the conversation transcript but
|
||||
* not the copilot-only TOAST-able fields (`previewYaml`, `planArtifact`,
|
||||
* `config`) or unused metadata (`model`, `pinned`, `lastSeenAt`). Selecting
|
||||
* only these columns avoids the Postgres detoast cost on the dropped fields,
|
||||
* which dominates latency for chats with large message histories.
|
||||
*/
|
||||
const copilotChatDetailColumns = {
|
||||
...copilotChatAuthColumns,
|
||||
title: copilotChats.title,
|
||||
messages: copilotChats.messages,
|
||||
conversationId: copilotChats.conversationId,
|
||||
resources: copilotChats.resources,
|
||||
createdAt: copilotChats.createdAt,
|
||||
updatedAt: copilotChats.updatedAt,
|
||||
} as const
|
||||
|
||||
type CopilotChatAuthRow = Pick<
|
||||
typeof copilotChats.$inferSelect,
|
||||
'id' | 'userId' | 'workflowId' | 'workspaceId' | 'type'
|
||||
>
|
||||
|
||||
export type CopilotChatDetailRow = Pick<
|
||||
typeof copilotChats.$inferSelect,
|
||||
| 'id'
|
||||
| 'userId'
|
||||
| 'workflowId'
|
||||
| 'workspaceId'
|
||||
| 'type'
|
||||
| 'title'
|
||||
| 'messages'
|
||||
| 'conversationId'
|
||||
| 'resources'
|
||||
| 'createdAt'
|
||||
| 'updatedAt'
|
||||
>
|
||||
|
||||
async function authorizeCopilotChatRow<T extends CopilotChatAuthRow>(
|
||||
chat: T | undefined,
|
||||
chatId: string,
|
||||
@@ -99,8 +131,10 @@ export async function getAccessibleCopilotChatAuth(
|
||||
|
||||
/**
|
||||
* Load the full copilot chat row after authorization. Use this only when the
|
||||
* caller actually consumes the heavy columns (`messages`, `planArtifact`,
|
||||
* `config`, etc.) — for example, chat resume or the GET-by-id endpoint.
|
||||
* caller actually consumes copilot-only TOAST-able columns (`previewYaml`,
|
||||
* `planArtifact`, `config`) or other extended metadata — for example the
|
||||
* legacy copilot chat detail endpoint. Mothership chats and other consumers
|
||||
* that only need the transcript should prefer `getAccessibleCopilotChatWithMessages`.
|
||||
*/
|
||||
export async function getAccessibleCopilotChat(chatId: string, userId: string) {
|
||||
const [chat] = await db
|
||||
@@ -112,6 +146,27 @@ export async function getAccessibleCopilotChat(chatId: string, userId: string) {
|
||||
return authorizeCopilotChatRow(chat, chatId, userId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a copilot chat with the conversation transcript and resources after
|
||||
* authorization, omitting copilot-only TOAST-able fields (`previewYaml`,
|
||||
* `planArtifact`, `config`) and unused metadata (`model`, `pinned`,
|
||||
* `lastSeenAt`). Use this for the mothership chat detail endpoint and the
|
||||
* shared `resolveOrCreateChat` path — every column read here is consumed
|
||||
* downstream, and dropping the others avoids per-request detoast overhead.
|
||||
*/
|
||||
export async function getAccessibleCopilotChatWithMessages(
|
||||
chatId: string,
|
||||
userId: string
|
||||
): Promise<CopilotChatDetailRow | null> {
|
||||
const [chat] = await db
|
||||
.select(copilotChatDetailColumns)
|
||||
.from(copilotChats)
|
||||
.where(and(eq(copilotChats.id, chatId), eq(copilotChats.userId, userId)))
|
||||
.limit(1)
|
||||
|
||||
return authorizeCopilotChatRow(chat, chatId, userId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve or create a copilot chat session.
|
||||
* If chatId is provided, loads the existing chat. Otherwise creates a new one.
|
||||
@@ -132,7 +187,7 @@ export async function resolveOrCreateChat(params: {
|
||||
}
|
||||
|
||||
if (chatId) {
|
||||
const chat = await getAccessibleCopilotChat(chatId, userId)
|
||||
const chat = await getAccessibleCopilotChatWithMessages(chatId, userId)
|
||||
|
||||
if (chat) {
|
||||
if (workflowId && chat.workflowId !== workflowId) {
|
||||
@@ -189,7 +244,7 @@ export async function resolveOrCreateChat(params: {
|
||||
messages: [],
|
||||
lastSeenAt: now,
|
||||
})
|
||||
.returning()
|
||||
.returning(copilotChatDetailColumns)
|
||||
|
||||
if (!newChat) {
|
||||
logger.warn('Failed to create new copilot chat row', { userId, workflowId, workspaceId })
|
||||
|
||||
@@ -228,8 +228,8 @@ async function processPastChatFromDb(
|
||||
currentWorkspaceId?: string
|
||||
): Promise<AgentContext | null> {
|
||||
try {
|
||||
const { getAccessibleCopilotChat } = await import('./lifecycle')
|
||||
const chat = await getAccessibleCopilotChat(chatId, userId)
|
||||
const { getAccessibleCopilotChatWithMessages } = await import('./lifecycle')
|
||||
const chat = await getAccessibleCopilotChatWithMessages(chatId, userId)
|
||||
if (!chat) {
|
||||
return null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user