129 lines
4.1 KiB
TypeScript
129 lines
4.1 KiB
TypeScript
|
import * as plugins from './plugins.js';
|
||
|
import * as paths from './paths.js';
|
||
|
import { MultiModalModel } from './abstract.classes.multimodal.js';
|
||
|
import type { ChatOptions, ChatResponse, ChatMessage } from './abstract.classes.multimodal.js';
|
||
|
import type { ChatCompletionMessageParam } from 'openai/resources/chat/completions';
|
||
|
|
||
|
export interface IExoProviderOptions {
|
||
|
exoBaseUrl?: string;
|
||
|
apiKey?: string;
|
||
|
}
|
||
|
|
||
|
export class ExoProvider extends MultiModalModel {
|
||
|
private options: IExoProviderOptions;
|
||
|
public openAiApiClient: plugins.openai.default;
|
||
|
|
||
|
constructor(optionsArg: IExoProviderOptions = {}) {
|
||
|
super();
|
||
|
this.options = {
|
||
|
exoBaseUrl: 'http://localhost:8080/v1', // Default Exo API endpoint
|
||
|
...optionsArg
|
||
|
};
|
||
|
}
|
||
|
|
||
|
public async start() {
|
||
|
this.openAiApiClient = new plugins.openai.default({
|
||
|
apiKey: this.options.apiKey || 'not-needed', // Exo might not require an API key for local deployment
|
||
|
baseURL: this.options.exoBaseUrl,
|
||
|
});
|
||
|
}
|
||
|
|
||
|
public async stop() {}
|
||
|
|
||
|
public async chatStream(input: ReadableStream<Uint8Array>): Promise<ReadableStream<string>> {
|
||
|
// Create a TextDecoder to handle incoming chunks
|
||
|
const decoder = new TextDecoder();
|
||
|
let buffer = '';
|
||
|
let currentMessage: { role: string; content: string; } | null = null;
|
||
|
|
||
|
// Create a TransformStream to process the input
|
||
|
const transform = new TransformStream<Uint8Array, string>({
|
||
|
async transform(chunk, controller) {
|
||
|
buffer += decoder.decode(chunk, { stream: true });
|
||
|
|
||
|
// Try to parse complete JSON messages from the buffer
|
||
|
while (true) {
|
||
|
const newlineIndex = buffer.indexOf('\n');
|
||
|
if (newlineIndex === -1) break;
|
||
|
|
||
|
const line = buffer.slice(0, newlineIndex);
|
||
|
buffer = buffer.slice(newlineIndex + 1);
|
||
|
|
||
|
if (line.trim()) {
|
||
|
try {
|
||
|
const message = JSON.parse(line);
|
||
|
currentMessage = message;
|
||
|
|
||
|
// Process the message based on its type
|
||
|
if (message.type === 'message') {
|
||
|
const response = await this.chat({
|
||
|
systemMessage: '',
|
||
|
userMessage: message.content,
|
||
|
messageHistory: [{ role: message.role as 'user' | 'assistant' | 'system', content: message.content }]
|
||
|
});
|
||
|
|
||
|
controller.enqueue(JSON.stringify(response) + '\n');
|
||
|
}
|
||
|
} catch (error) {
|
||
|
console.error('Error processing message:', error);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
flush(controller) {
|
||
|
if (buffer) {
|
||
|
try {
|
||
|
const message = JSON.parse(buffer);
|
||
|
currentMessage = message;
|
||
|
} catch (error) {
|
||
|
console.error('Error processing remaining buffer:', error);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
return input.pipeThrough(transform);
|
||
|
}
|
||
|
|
||
|
public async chat(options: ChatOptions): Promise<ChatResponse> {
|
||
|
const messages: ChatCompletionMessageParam[] = [
|
||
|
{ role: 'system', content: options.systemMessage },
|
||
|
...options.messageHistory,
|
||
|
{ role: 'user', content: options.userMessage }
|
||
|
];
|
||
|
|
||
|
try {
|
||
|
const response = await this.openAiApiClient.chat.completions.create({
|
||
|
model: 'local-model', // Exo uses local models
|
||
|
messages: messages,
|
||
|
stream: false
|
||
|
});
|
||
|
|
||
|
return {
|
||
|
role: 'assistant',
|
||
|
message: response.choices[0]?.message?.content || ''
|
||
|
};
|
||
|
} catch (error) {
|
||
|
console.error('Error in chat completion:', error);
|
||
|
throw error;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public async audio(optionsArg: { message: string }): Promise<NodeJS.ReadableStream> {
|
||
|
throw new Error('Audio generation is not supported by Exo provider');
|
||
|
}
|
||
|
|
||
|
public async vision(optionsArg: { image: Buffer; prompt: string }): Promise<string> {
|
||
|
throw new Error('Vision processing is not supported by Exo provider');
|
||
|
}
|
||
|
|
||
|
public async document(optionsArg: {
|
||
|
systemMessage: string;
|
||
|
userMessage: string;
|
||
|
pdfDocuments: Uint8Array[];
|
||
|
messageHistory: ChatMessage[];
|
||
|
}): Promise<{ message: any }> {
|
||
|
throw new Error('Document processing is not supported by Exo provider');
|
||
|
}
|
||
|
}
|