feat(copilot): add context7 (#2779)

* Add context7

* Fix edit diff block ring color

* Remove server side impl

* Fix duplicated message on edit old message

* Tables in markdown
This commit is contained in:
Siddharth Ganesan
2026-01-12 17:08:47 -08:00
committed by GitHub
parent 684ad5aeec
commit d55072a45f
6 changed files with 77 additions and 10 deletions

View File

@@ -326,8 +326,8 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend
),
table: ({ children }: React.TableHTMLAttributes<HTMLTableElement>) => (
<div className='my-4 max-w-full overflow-x-auto'>
<table className='min-w-full table-auto border border-[var(--border-1)] font-season text-sm'>
<div className='my-3 max-w-full overflow-x-auto'>
<table className='min-w-full table-auto border border-[var(--border-1)] font-season text-xs'>
{children}
</table>
</div>
@@ -346,12 +346,12 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend
</tr>
),
th: ({ children }: React.ThHTMLAttributes<HTMLTableCellElement>) => (
<th className='border-[var(--border-1)] border-r px-4 py-2 align-top font-base text-[var(--text-secondary)] last:border-r-0 dark:font-[470]'>
<th className='border-[var(--border-1)] border-r px-2.5 py-1.5 align-top font-base text-[var(--text-secondary)] last:border-r-0 dark:font-[470]'>
{children}
</th>
),
td: ({ children }: React.TdHTMLAttributes<HTMLTableCellElement>) => (
<td className='break-words border-[var(--border-1)] border-r px-4 py-2 align-top font-base text-[var(--text-primary)] last:border-r-0 dark:font-[470]'>
<td className='break-words border-[var(--border-1)] border-r px-2.5 py-1.5 align-top font-base text-[var(--text-primary)] last:border-r-0 dark:font-[470]'>
{children}
</td>
),

View File

@@ -246,7 +246,7 @@ export function ThinkingBlock({
)}
>
{/* Render markdown during streaming with thinking text styling */}
<div className='[&_*]:!text-[var(--text-muted)] [&_*]:!text-[12px] [&_*]:!leading-none [&_*]:!m-0 [&_*]:!p-0 [&_*]:!mb-0 [&_*]:!mt-0 [&_p]:!m-0 [&_h1]:!text-[12px] [&_h1]:!font-semibold [&_h2]:!text-[12px] [&_h2]:!font-semibold [&_h3]:!text-[12px] [&_h3]:!font-semibold [&_code]:!text-[11px] [&_ul]:!pl-4 [&_ul]:!my-0 [&_ol]:!pl-4 [&_ol]:!my-0 [&_li]:!my-0 [&_li]:!py-0 [&_br]:!leading-[0.5] whitespace-pre-wrap font-[470] font-season text-[12px] text-[var(--text-muted)] leading-none'>
<div className='[&_*]:!text-[var(--text-muted)] [&_*]:!text-[12px] [&_*]:!leading-[1.3] [&_p]:!m-0 [&_p]:!mb-1 [&_h1]:!text-[12px] [&_h1]:!font-semibold [&_h1]:!m-0 [&_h1]:!mb-1 [&_h2]:!text-[12px] [&_h2]:!font-semibold [&_h2]:!m-0 [&_h2]:!mb-1 [&_h3]:!text-[12px] [&_h3]:!font-semibold [&_h3]:!m-0 [&_h3]:!mb-1 [&_code]:!text-[11px] [&_ul]:!pl-5 [&_ul]:!my-1 [&_ol]:!pl-6 [&_ol]:!my-1 [&_li]:!my-0.5 [&_li]:!py-0 [&_br]:!leading-[0.5] [&_table]:!my-2 [&_th]:!px-2 [&_th]:!py-1 [&_th]:!text-[11px] [&_td]:!px-2 [&_td]:!py-1 [&_td]:!text-[11px] whitespace-pre-wrap font-[470] font-season text-[12px] text-[var(--text-muted)]'>
<CopilotMarkdownRenderer content={content} />
<span className='ml-1 inline-block h-2 w-1 animate-pulse bg-[var(--text-muted)]' />
</div>
@@ -286,7 +286,7 @@ export function ThinkingBlock({
)}
>
{/* Use markdown renderer for completed content */}
<div className='[&_*]:!text-[var(--text-muted)] [&_*]:!text-[12px] [&_*]:!leading-none [&_*]:!m-0 [&_*]:!p-0 [&_*]:!mb-0 [&_*]:!mt-0 [&_p]:!m-0 [&_h1]:!text-[12px] [&_h1]:!font-semibold [&_h2]:!text-[12px] [&_h2]:!font-semibold [&_h3]:!text-[12px] [&_h3]:!font-semibold [&_code]:!text-[11px] [&_ul]:!pl-4 [&_ul]:!my-0 [&_ol]:!pl-4 [&_ol]:!my-0 [&_li]:!my-0 [&_li]:!py-0 [&_br]:!leading-[0.5] whitespace-pre-wrap font-[470] font-season text-[12px] text-[var(--text-muted)] leading-none'>
<div className='[&_*]:!text-[var(--text-muted)] [&_*]:!text-[12px] [&_*]:!leading-[1.3] [&_p]:!m-0 [&_p]:!mb-1 [&_h1]:!text-[12px] [&_h1]:!font-semibold [&_h1]:!m-0 [&_h1]:!mb-1 [&_h2]:!text-[12px] [&_h2]:!font-semibold [&_h2]:!m-0 [&_h2]:!mb-1 [&_h3]:!text-[12px] [&_h3]:!font-semibold [&_h3]:!m-0 [&_h3]:!mb-1 [&_code]:!text-[11px] [&_ul]:!pl-5 [&_ul]:!my-1 [&_ol]:!pl-6 [&_ol]:!my-1 [&_li]:!my-0.5 [&_li]:!py-0 [&_br]:!leading-[0.5] [&_table]:!my-2 [&_th]:!px-2 [&_th]:!py-1 [&_th]:!text-[11px] [&_td]:!px-2 [&_td]:!py-1 [&_td]:!text-[11px] whitespace-pre-wrap font-[470] font-season text-[12px] text-[var(--text-muted)]'>
<CopilotMarkdownRenderer content={content} />
</div>
</div>

View File

@@ -50,7 +50,7 @@ export function getBlockRingStyles(options: BlockRingOptions): {
!isPending &&
!isDeletedBlock &&
diffStatus === 'new' &&
'ring-[var(--brand-tertiary)]',
'ring-[var(--brand-tertiary-2)]',
!isActive &&
!isPending &&
!isDeletedBlock &&

View File

@@ -0,0 +1,50 @@
import { BookOpen, Loader2, MinusCircle, XCircle } from 'lucide-react'
import {
BaseClientTool,
type BaseClientToolMetadata,
ClientToolCallState,
} from '@/lib/copilot/tools/client/base-tool'
export class SearchLibraryDocsClientTool extends BaseClientTool {
static readonly id = 'search_library_docs'
constructor(toolCallId: string) {
super(toolCallId, SearchLibraryDocsClientTool.id, SearchLibraryDocsClientTool.metadata)
}
static readonly metadata: BaseClientToolMetadata = {
displayNames: {
[ClientToolCallState.generating]: { text: 'Reading docs', icon: Loader2 },
[ClientToolCallState.pending]: { text: 'Reading docs', icon: Loader2 },
[ClientToolCallState.executing]: { text: 'Reading docs', icon: Loader2 },
[ClientToolCallState.success]: { text: 'Read docs', icon: BookOpen },
[ClientToolCallState.error]: { text: 'Failed to read docs', icon: XCircle },
[ClientToolCallState.aborted]: { text: 'Aborted reading docs', icon: XCircle },
[ClientToolCallState.rejected]: { text: 'Skipped reading docs', icon: MinusCircle },
},
getDynamicText: (params, state) => {
const libraryName = params?.library_name
if (libraryName && typeof libraryName === 'string') {
switch (state) {
case ClientToolCallState.success:
return `Read ${libraryName} docs`
case ClientToolCallState.executing:
case ClientToolCallState.generating:
case ClientToolCallState.pending:
return `Reading ${libraryName} docs`
case ClientToolCallState.error:
return `Failed to read ${libraryName} docs`
case ClientToolCallState.aborted:
return `Aborted reading ${libraryName} docs`
case ClientToolCallState.rejected:
return `Skipped reading ${libraryName} docs`
}
}
return undefined
},
}
async execute(): Promise<void> {
return
}
}

View File

@@ -42,6 +42,7 @@ import { RememberDebugClientTool } from '@/lib/copilot/tools/client/other/rememb
import { ResearchClientTool } from '@/lib/copilot/tools/client/other/research'
import { SearchDocumentationClientTool } from '@/lib/copilot/tools/client/other/search-documentation'
import { SearchErrorsClientTool } from '@/lib/copilot/tools/client/other/search-errors'
import { SearchLibraryDocsClientTool } from '@/lib/copilot/tools/client/other/search-library-docs'
import { SearchOnlineClientTool } from '@/lib/copilot/tools/client/other/search-online'
import { SearchPatternsClientTool } from '@/lib/copilot/tools/client/other/search-patterns'
import { SleepClientTool } from '@/lib/copilot/tools/client/other/sleep'
@@ -116,6 +117,7 @@ const CLIENT_TOOL_INSTANTIATORS: Record<string, (id: string) => any> = {
get_trigger_blocks: (id) => new GetTriggerBlocksClientTool(id),
search_online: (id) => new SearchOnlineClientTool(id),
search_documentation: (id) => new SearchDocumentationClientTool(id),
search_library_docs: (id) => new SearchLibraryDocsClientTool(id),
search_patterns: (id) => new SearchPatternsClientTool(id),
search_errors: (id) => new SearchErrorsClientTool(id),
remember_debug: (id) => new RememberDebugClientTool(id),
@@ -174,6 +176,7 @@ export const CLASS_TOOL_METADATA: Record<string, BaseClientToolMetadata | undefi
get_trigger_blocks: (GetTriggerBlocksClientTool as any)?.metadata,
search_online: (SearchOnlineClientTool as any)?.metadata,
search_documentation: (SearchDocumentationClientTool as any)?.metadata,
search_library_docs: (SearchLibraryDocsClientTool as any)?.metadata,
search_patterns: (SearchPatternsClientTool as any)?.metadata,
search_errors: (SearchErrorsClientTool as any)?.metadata,
remember_debug: (RememberDebugClientTool as any)?.metadata,
@@ -2433,9 +2436,10 @@ export const useCopilotStore = create<CopilotStore>()(
// If already sending a message, queue this one instead
if (isSendingMessage) {
get().addToQueue(message, { fileAttachments, contexts })
get().addToQueue(message, { fileAttachments, contexts, messageId })
logger.info('[Copilot] Message queued (already sending)', {
queueLength: get().messageQueue.length + 1,
originalMessageId: messageId,
})
return
}
@@ -3161,8 +3165,12 @@ export const useCopilotStore = create<CopilotStore>()(
// Process next message in queue if any
const nextInQueue = get().messageQueue[0]
if (nextInQueue) {
// Use originalMessageId if available (from edit/resend), otherwise use queue entry id
const messageIdToUse = nextInQueue.originalMessageId || nextInQueue.id
logger.info('[Queue] Processing next queued message', {
id: nextInQueue.id,
originalMessageId: nextInQueue.originalMessageId,
messageIdToUse,
queueLength: get().messageQueue.length,
})
// Remove from queue and send
@@ -3173,7 +3181,7 @@ export const useCopilotStore = create<CopilotStore>()(
stream: true,
fileAttachments: nextInQueue.fileAttachments,
contexts: nextInQueue.contexts,
messageId: nextInQueue.id,
messageId: messageIdToUse,
})
}, 100)
}
@@ -3615,10 +3623,12 @@ export const useCopilotStore = create<CopilotStore>()(
fileAttachments: options?.fileAttachments,
contexts: options?.contexts,
queuedAt: Date.now(),
originalMessageId: options?.messageId,
}
set({ messageQueue: [...get().messageQueue, queuedMessage] })
logger.info('[Queue] Message added to queue', {
id: queuedMessage.id,
originalMessageId: options?.messageId,
queueLength: get().messageQueue.length,
})
},
@@ -3659,12 +3669,15 @@ export const useCopilotStore = create<CopilotStore>()(
await new Promise((resolve) => setTimeout(resolve, 50))
}
// Use originalMessageId if available (from edit/resend), otherwise use queue entry id
const messageIdToUse = message.originalMessageId || message.id
// Send the message
await get().sendMessage(message.content, {
stream: true,
fileAttachments: message.fileAttachments,
contexts: message.contexts,
messageId: message.id,
messageId: messageIdToUse,
})
},

View File

@@ -70,6 +70,8 @@ export interface QueuedMessage {
fileAttachments?: MessageFileAttachment[]
contexts?: ChatContext[]
queuedAt: number
/** Original messageId to use when processing (for edit/resend flows) */
originalMessageId?: string
}
// Contexts attached to a user message
@@ -249,6 +251,8 @@ export interface CopilotActions {
options?: {
fileAttachments?: MessageFileAttachment[]
contexts?: ChatContext[]
/** Original messageId to preserve (for edit/resend flows) */
messageId?: string
}
) => void
removeFromQueue: (id: string) => void