/* * Copyright (C) 2024 EclipseSource GmbH. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0. * * This Source Code may also be made available under the following Secondary * Licenses when the conditions for such availability set forth in the Eclipse * Public License v. 2.0 are satisfied: GNU General Public License, version 2 * with the GNU Classpath Exception which is available at * https://www.gnu.org/software/classpath/license.html. * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 */ import { MutableChatRequestModel, ChatResponseContent, CodeChatResponseContentImpl, MarkdownChatResponseContentImpl } from './chat-model'; import { injectable } from '@theia/core/shared/inversify'; export type ResponseContentFactory = (content: string, request: MutableChatRequestModel) => ChatResponseContent; export const MarkdownContentFactory: ResponseContentFactory = (content: string, request: MutableChatRequestModel) => new MarkdownChatResponseContentImpl(content); /** * Default response content factory used if no other `ResponseContentMatcher` applies. * By default, this factory creates a markdown content object. * * @see MarkdownChatResponseContentImpl */ @injectable() export class DefaultResponseContentFactory { create(content: string, request: MutableChatRequestModel): ChatResponseContent { return MarkdownContentFactory(content, request); } } /** * Clients can contribute response content matchers to parse a chat response into specific * `ChatResponseContent` instances. */ export interface ResponseContentMatcher { /** Regular expression for finding the start delimiter. */ start: RegExp; /** Regular expression for finding the start delimiter. */ end: RegExp; /** * The factory creating a response content from the matching content, * from start index to end index of the match (including delimiters). */ contentFactory: ResponseContentFactory; /** * Optional factory for creating a response content when only the start delimiter has been matched, * but not yet the end delimiter. Used during streaming to provide better visual feedback. * If not provided, the default content factory will be used until the end delimiter is matched. */ incompleteContentFactory?: ResponseContentFactory; } export const CodeContentMatcher: ResponseContentMatcher = { // Only match when we have the complete first line ending with a newline // This ensures we have the full language specification before creating the editor start: /^```.*\n/m, end: /^```$/m, contentFactory: (content: string, request: MutableChatRequestModel) => { const language = content.match(/^```(\w+)/)?.[1] || ''; const code = content.replace(/^```(\w+)?\n|```$/g, ''); return new CodeChatResponseContentImpl(code.trim(), language); }, incompleteContentFactory: (content: string, request: MutableChatRequestModel) => { // By this point, we know we have at least the complete first line with ``` const firstLine = content.split('\n')[0]; const language = firstLine.match(/^```(\w+)/)?.[1] || ''; // Remove the first line to get just the code content const code = content.substring(content.indexOf('\n') + 1); return new CodeChatResponseContentImpl(code.trim(), language); } }; /** * Clients can contribute response content matchers to parse the response content. * * The default chat user interface will collect all contributed matchers and use them * to parse the response into structured content parts (e.g. code blocks, markdown blocks), * which are then rendered with a `ChatResponsePartRenderer` registered for the respective * content part type. * * ### Example * ```ts * bind(ResponseContentMatcherProvider).to(MyResponseContentMatcherProvider); * ... * @injectable() * export class MyResponseContentMatcherProvider implements ResponseContentMatcherProvider { * readonly matchers: ResponseContentMatcher[] = [{ * start: /^$/m, * end: /^$/m, * contentFactory: (content: string) => { * const command = content.replace(/^\n|<\/command>$/g, ''); * return new MyChatResponseContentImpl(command.trim()); * } * }]; * } * ``` * * @see ResponseContentMatcher */ export const ResponseContentMatcherProvider = Symbol('ResponseContentMatcherProvider'); export interface ResponseContentMatcherProvider { readonly matchers: ResponseContentMatcher[]; } @injectable() export class DefaultResponseContentMatcherProvider implements ResponseContentMatcherProvider { readonly matchers: ResponseContentMatcher[] = [CodeContentMatcher]; }