mirror of
https://github.com/larsbaunwall/vscode-copilot-bridge.git
synced 2025-10-05 22:22:59 +00:00
91 lines
3.4 KiB
TypeScript
91 lines
3.4 KiB
TypeScript
import * as vscode from 'vscode';
|
|
|
|
export interface ChatMessage {
|
|
readonly role: 'system' | 'user' | 'assistant';
|
|
readonly content: string | MessageContent[];
|
|
}
|
|
|
|
export interface MessageContent {
|
|
readonly type: string;
|
|
readonly text?: string;
|
|
readonly [key: string]: unknown;
|
|
}
|
|
|
|
export interface ChatCompletionRequest {
|
|
readonly model?: string;
|
|
readonly messages: ChatMessage[];
|
|
readonly stream?: boolean;
|
|
readonly [key: string]: unknown;
|
|
}
|
|
|
|
const VALID_ROLES = ['system', 'user', 'assistant'] as const;
|
|
type Role = typeof VALID_ROLES[number];
|
|
const isValidRole = (role: unknown): role is Role => typeof role === 'string' && VALID_ROLES.includes(role as Role);
|
|
|
|
export const isChatMessage = (msg: unknown): msg is ChatMessage => {
|
|
if (typeof msg !== 'object' || msg === null) return false;
|
|
const candidate = msg as Record<string, unknown>;
|
|
if (!('role' in candidate) || !('content' in candidate)) return false;
|
|
return isValidRole(candidate.role) && candidate.content !== undefined && candidate.content !== null;
|
|
};
|
|
|
|
export const isChatCompletionRequest = (body: unknown): body is ChatCompletionRequest => {
|
|
if (typeof body !== 'object' || body === null) return false;
|
|
const candidate = body as Record<string, unknown>;
|
|
if (!('messages' in candidate)) return false;
|
|
const messages = candidate.messages;
|
|
return Array.isArray(messages) && messages.length > 0 && messages.every(isChatMessage);
|
|
};
|
|
|
|
const toText = (content: unknown): string => {
|
|
if (typeof content === 'string') return content;
|
|
if (Array.isArray(content)) return content.map(toText).join('\n');
|
|
if (content && typeof content === 'object' && 'text' in content) {
|
|
const textVal = (content as { text?: unknown }).text;
|
|
if (typeof textVal === 'string') return textVal;
|
|
}
|
|
try {
|
|
return JSON.stringify(content);
|
|
} catch {
|
|
return String(content);
|
|
}
|
|
};
|
|
|
|
export const normalizeMessagesLM = (
|
|
messages: readonly ChatMessage[],
|
|
histWindow: number
|
|
): (vscode.LanguageModelChatMessage | { role: 'user' | 'assistant'; content: string })[] => {
|
|
const systemMessages = messages.filter((m) => m.role === 'system');
|
|
const systemMessage = systemMessages[systemMessages.length - 1];
|
|
const conversationMessages = messages.filter((m) => m.role === 'user' || m.role === 'assistant').slice(-histWindow * 2);
|
|
|
|
const lmMsg = (vscode as unknown as { LanguageModelChatMessage?: typeof vscode.LanguageModelChatMessage }).LanguageModelChatMessage;
|
|
const UserFactory = lmMsg?.User;
|
|
const AssistantFactory = lmMsg?.Assistant;
|
|
|
|
const result: (vscode.LanguageModelChatMessage | { role: 'user' | 'assistant'; content: string })[] = [];
|
|
let firstUserSeen = false;
|
|
|
|
for (const m of conversationMessages) {
|
|
if (m.role === 'user') {
|
|
let text = toText(m.content);
|
|
if (!firstUserSeen && systemMessage) {
|
|
text = `[SYSTEM]\n${toText(systemMessage.content)}\n\n[DIALOG]\nuser: ${text}`;
|
|
firstUserSeen = true;
|
|
}
|
|
result.push(UserFactory ? UserFactory(text) : { role: 'user', content: text });
|
|
} else {
|
|
const text = toText(m.content);
|
|
result.push(AssistantFactory ? AssistantFactory(text) : { role: 'assistant', content: text });
|
|
}
|
|
}
|
|
|
|
if (!firstUserSeen && systemMessage) {
|
|
const text = `[SYSTEM]\n${toText(systemMessage.content)}`;
|
|
result.unshift(UserFactory ? UserFactory(text) : { role: 'user', content: text });
|
|
}
|
|
|
|
if (result.length === 0) result.push(UserFactory ? UserFactory('') : { role: 'user', content: '' });
|
|
|
|
return result;
|
|
};
|