Files
sim/apps/sim/lib/copilot/api.ts
Siddharth Ganesan cc0ace7de6 improvement(copilot): version update (#1689)
* Add exa to search online tool

* Larger font size

* Copilot UI improvements

* Fix models options

* Add haiku 4.5 to copilot

* Model ui for haiku

* Fix lint

* Revert

* Only allow one revert to message

* Clear diff on revert

* Fix welcome screen flash

* Add focus onto the user input box when clicked

* Fix grayout of new stream on old edit message

* Lint

* Make edit message submit smoother

* Allow message sent while streaming

* Revert popup improvements: gray out stuff below, show cursor on revert

* Fix lint

* Improve chat history dropdown

* Improve get block metadata tool

* Update update cost route

* Fix env

* Context usage endpoint

* Make chat history scrollable

* Fix lint

* Copilot revert popup updates

* Fix lint

* Fix tests and lint

* Add summary tool

* Fix env.ts
2025-10-18 12:59:48 -07:00

187 lines
4.3 KiB
TypeScript

import { createLogger } from '@/lib/logs/console/logger'
const logger = createLogger('CopilotAPI')
/**
* Citation interface for documentation references
*/
export interface Citation {
id: number
title: string
url: string
similarity?: number
}
/**
* Message interface for copilot conversations
*/
export interface CopilotMessage {
id: string
role: 'user' | 'assistant' | 'system'
content: string
timestamp: string
citations?: Citation[]
}
/**
* Chat interface for copilot conversations
*/
export interface CopilotChat {
id: string
title: string | null
model: string
messages: CopilotMessage[]
messageCount: number
previewYaml: string | null
createdAt: Date
updatedAt: Date
}
/**
* File attachment interface for message requests
*/
export interface MessageFileAttachment {
id: string
key: string
filename: string
media_type: string
size: number
}
/**
* Request interface for sending messages
*/
export interface SendMessageRequest {
message: string
userMessageId?: string // ID from frontend for the user message
chatId?: string
workflowId?: string
mode?: 'ask' | 'agent'
model?:
| 'gpt-5-fast'
| 'gpt-5'
| 'gpt-5-medium'
| 'gpt-5-high'
| 'gpt-4o'
| 'gpt-4.1'
| 'o3'
| 'claude-4-sonnet'
| 'claude-4.5-haiku'
| 'claude-4.5-sonnet'
| 'claude-4.1-opus'
prefetch?: boolean
createNewChat?: boolean
stream?: boolean
implicitFeedback?: string
fileAttachments?: MessageFileAttachment[]
abortSignal?: AbortSignal
contexts?: Array<{
kind: string
label?: string
chatId?: string
workflowId?: string
executionId?: string
}>
}
/**
* Base API response interface
*/
export interface ApiResponse {
success: boolean
error?: string
status?: number
}
/**
* Streaming response interface
*/
export interface StreamingResponse extends ApiResponse {
stream?: ReadableStream
}
/**
* Handle API errors and return user-friendly error messages
*/
async function handleApiError(response: Response, defaultMessage: string): Promise<string> {
try {
const data = await response.json()
return (data && (data.error || data.message)) || defaultMessage
} catch {
return `${defaultMessage} (${response.status})`
}
}
/**
* Send a streaming message to the copilot chat API
* This is the main API endpoint that handles all chat operations
*/
export async function sendStreamingMessage(
request: SendMessageRequest
): Promise<StreamingResponse> {
try {
const { abortSignal, ...requestBody } = request
try {
const preview = Array.isArray((requestBody as any).contexts)
? (requestBody as any).contexts.map((c: any) => ({
kind: c?.kind,
chatId: c?.chatId,
workflowId: c?.workflowId,
label: c?.label,
}))
: undefined
logger.info('Preparing to send streaming message', {
hasContexts: Array.isArray((requestBody as any).contexts),
contextsCount: Array.isArray((requestBody as any).contexts)
? (requestBody as any).contexts.length
: 0,
contextsPreview: preview,
})
} catch {}
const response = await fetch('/api/copilot/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...requestBody, stream: true }),
signal: abortSignal,
credentials: 'include', // Include cookies for session authentication
})
if (!response.ok) {
const errorMessage = await handleApiError(response, 'Failed to send streaming message')
return {
success: false,
error: errorMessage,
status: response.status,
}
}
if (!response.body) {
return {
success: false,
error: 'No response body received',
status: 500,
}
}
return {
success: true,
stream: response.body,
}
} catch (error) {
// Handle AbortError gracefully - this is expected when user aborts
if (error instanceof Error && error.name === 'AbortError') {
logger.info('Streaming message was aborted by user')
return {
success: false,
error: 'Request was aborted',
}
}
logger.error('Failed to send streaming message:', error)
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
}
}
}