<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced support for the Morph provider in the copilot module, enabling integration with the Morph LLM API. - Added a new document editing tool that allows users to propose and apply edits to existing documents via AI assistance. - **Configuration** - Added configuration options for the Morph provider in both backend and admin interfaces. - **Enhancements** - Expanded tool support to include document editing capabilities within the copilot feature set. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
164 lines
4.1 KiB
TypeScript
164 lines
4.1 KiB
TypeScript
import {
|
|
createOpenAICompatible,
|
|
OpenAICompatibleProvider as VercelOpenAICompatibleProvider,
|
|
} from '@ai-sdk/openai-compatible';
|
|
import { AISDKError, generateText, streamText } from 'ai';
|
|
|
|
import {
|
|
CopilotProviderSideError,
|
|
metrics,
|
|
UserFriendlyError,
|
|
} from '../../../base';
|
|
import { CopilotProvider } from './provider';
|
|
import type {
|
|
CopilotChatOptions,
|
|
ModelConditions,
|
|
PromptMessage,
|
|
} from './types';
|
|
import { CopilotProviderType, ModelInputType, ModelOutputType } from './types';
|
|
import { chatToGPTMessage, CitationParser, TextStreamParser } from './utils';
|
|
|
|
export const DEFAULT_DIMENSIONS = 256;
|
|
|
|
export type MorphConfig = {
|
|
apiKey?: string;
|
|
};
|
|
|
|
export class MorphProvider extends CopilotProvider<MorphConfig> {
|
|
readonly type = CopilotProviderType.Morph;
|
|
|
|
readonly models = [
|
|
{
|
|
id: 'morph-v2',
|
|
capabilities: [
|
|
{
|
|
input: [ModelInputType.Text],
|
|
output: [ModelOutputType.Text],
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
#instance!: VercelOpenAICompatibleProvider;
|
|
|
|
override configured(): boolean {
|
|
return !!this.config.apiKey;
|
|
}
|
|
|
|
protected override setup() {
|
|
super.setup();
|
|
this.#instance = createOpenAICompatible({
|
|
name: this.type,
|
|
apiKey: this.config.apiKey,
|
|
baseURL: 'https://api.morphllm.com/v1',
|
|
});
|
|
}
|
|
|
|
private handleError(e: any) {
|
|
if (e instanceof UserFriendlyError) {
|
|
return e;
|
|
} else if (e instanceof AISDKError) {
|
|
return new CopilotProviderSideError({
|
|
provider: this.type,
|
|
kind: e.name || 'unknown',
|
|
message: e.message,
|
|
});
|
|
} else {
|
|
return new CopilotProviderSideError({
|
|
provider: this.type,
|
|
kind: 'unexpected_response',
|
|
message: e?.message || 'Unexpected morph response',
|
|
});
|
|
}
|
|
}
|
|
|
|
async text(
|
|
cond: ModelConditions,
|
|
messages: PromptMessage[],
|
|
options: CopilotChatOptions = {}
|
|
): Promise<string> {
|
|
const fullCond = {
|
|
...cond,
|
|
outputType: ModelOutputType.Text,
|
|
};
|
|
await this.checkParams({ messages, cond: fullCond, options });
|
|
const model = this.selectModel(fullCond);
|
|
|
|
try {
|
|
metrics.ai.counter('chat_text_calls').add(1, { model: model.id });
|
|
|
|
const [system, msgs] = await chatToGPTMessage(messages);
|
|
|
|
const modelInstance = this.#instance(model.id);
|
|
|
|
const { text } = await generateText({
|
|
model: modelInstance,
|
|
system,
|
|
messages: msgs,
|
|
abortSignal: options.signal,
|
|
});
|
|
|
|
return text.trim();
|
|
} catch (e: any) {
|
|
metrics.ai.counter('chat_text_errors').add(1, { model: model.id });
|
|
throw this.handleError(e);
|
|
}
|
|
}
|
|
|
|
async *streamText(
|
|
cond: ModelConditions,
|
|
messages: PromptMessage[],
|
|
options: CopilotChatOptions = {}
|
|
): AsyncIterable<string> {
|
|
const fullCond = {
|
|
...cond,
|
|
outputType: ModelOutputType.Text,
|
|
};
|
|
await this.checkParams({ messages, cond: fullCond, options });
|
|
const model = this.selectModel(fullCond);
|
|
|
|
try {
|
|
metrics.ai.counter('chat_text_stream_calls').add(1, { model: model.id });
|
|
const [system, msgs] = await chatToGPTMessage(messages);
|
|
|
|
const modelInstance = this.#instance(model.id);
|
|
|
|
const { fullStream } = streamText({
|
|
model: modelInstance,
|
|
system,
|
|
messages: msgs,
|
|
abortSignal: options.signal,
|
|
});
|
|
|
|
const citationParser = new CitationParser();
|
|
const textParser = new TextStreamParser();
|
|
for await (const chunk of fullStream) {
|
|
switch (chunk.type) {
|
|
case 'text-delta': {
|
|
let result = textParser.parse(chunk);
|
|
result = citationParser.parse(result);
|
|
yield result;
|
|
break;
|
|
}
|
|
case 'finish': {
|
|
const result = citationParser.end();
|
|
yield result;
|
|
break;
|
|
}
|
|
default: {
|
|
yield textParser.parse(chunk);
|
|
break;
|
|
}
|
|
}
|
|
if (options.signal?.aborted) {
|
|
await fullStream.cancel();
|
|
break;
|
|
}
|
|
}
|
|
} catch (e: any) {
|
|
metrics.ai.counter('chat_text_stream_errors').add(1, { model: model.id });
|
|
throw this.handleError(e);
|
|
}
|
|
}
|
|
}
|