diff --git a/src/globals.ts b/src/globals.ts index 1e7e5289b..97d1cd704 100644 --- a/src/globals.ts +++ b/src/globals.ts @@ -44,6 +44,10 @@ export const SEGMIND: string = 'segmind'; export const JINA: string = 'jina'; export const FIREWORKS_AI: string = 'fireworks-ai'; export const WORKERS_AI: string = 'workers-ai'; +export const MOONSHOT: string = 'moonshot'; +export const OPENROUTER: string = 'openrouter'; +export const LINGYI: string = 'lingyi'; +export const ZHIPU: string = 'zhipu'; export const VALID_PROVIDERS = [ ANTHROPIC, @@ -68,6 +72,10 @@ export const VALID_PROVIDERS = [ JINA, FIREWORKS_AI, WORKERS_AI, + MOONSHOT, + OPENROUTER, + LINGYI, + ZHIPU, ]; export const CONTENT_TYPES = { diff --git a/src/providers/index.ts b/src/providers/index.ts index f5ed8021c..7b6c65e82 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -21,6 +21,10 @@ import SegmindConfig from './segmind'; import JinaConfig from './jina'; import FireworksAIConfig from './fireworks-ai'; import WorkersAiConfig from './workers-ai'; +import MoonshotConfig from './moonshot'; +import OpenrouterConfig from './openrouter'; +import LingYiConfig from './lingyi'; +import ZhipuConfig from './zhipu'; const Providers: { [key: string]: ProviderConfigs } = { openai: OpenAIConfig, @@ -45,6 +49,10 @@ const Providers: { [key: string]: ProviderConfigs } = { jina: JinaConfig, 'fireworks-ai': FireworksAIConfig, 'workers-ai': WorkersAiConfig, + moonshot: MoonshotConfig, + openrouter: OpenrouterConfig, + lingyi: LingYiConfig, + zhipu: ZhipuConfig, }; export default Providers; diff --git a/src/providers/lingyi/api.ts b/src/providers/lingyi/api.ts new file mode 100644 index 000000000..9fa8f4d27 --- /dev/null +++ b/src/providers/lingyi/api.ts @@ -0,0 +1,18 @@ +import { ProviderAPIConfig } from '../types'; + +const LingYiAPIConfig: ProviderAPIConfig = { + getBaseURL: () => 'https://api.lingyiwanwu.com', + headers: ({ providerOptions }) => { + return { Authorization: `Bearer ${providerOptions.apiKey}` }; // https://platform.lingyiwanwu.com/apikeys + }, + getEndpoint: ({ fn }) => { + switch (fn) { + case 'chatComplete': + return '/v1/chat/completions'; + default: + return ''; + } + }, +}; + +export default LingYiAPIConfig; diff --git a/src/providers/lingyi/chatComplete.ts b/src/providers/lingyi/chatComplete.ts new file mode 100644 index 000000000..f803af955 --- /dev/null +++ b/src/providers/lingyi/chatComplete.ts @@ -0,0 +1,148 @@ +import { LINGYI } from '../../globals'; +import { + ChatCompletionResponse, + ErrorResponse, + ProviderConfig, +} from '../types'; +import { + generateErrorResponse, + generateInvalidProviderResponseError, +} from '../utils'; + +export const LingyiChatCompleteConfig: ProviderConfig = { + model: { + param: 'model', + required: true, + default: 'yi-34b-chat-0205', + }, + messages: { + param: 'messages', + default: '', + }, + max_tokens: { + param: 'max_tokens', + default: 100, + min: 0, + }, + temperature: { + param: 'temperature', + default: 1, + min: 0, + max: 2, + }, + top_p: { + param: 'top_p', + default: 1, + min: 0, + max: 1, + }, + stream: { + param: 'stream', + default: false, + }, +}; + +interface LingyiChatCompleteResponse extends ChatCompletionResponse { + id: string; + object: string; + created: number; + model: string; + usage: { + prompt_tokens: number; + completion_tokens: number; + total_tokens: number; + }; +} + +export interface LingyiErrorResponse { + object: string; + message: string; + type: string; + param: string | null; + code: string; +} + +interface LingyiStreamChunk { + id: string; + object: string; + created: number; + model: string; + choices: { + delta: { + role?: string | null; + content?: string; + }; + index: number; + finish_reason: string | null; + }[]; +} + +export const LingyiChatCompleteResponseTransform: ( + response: LingyiChatCompleteResponse | LingyiErrorResponse, + responseStatus: number +) => ChatCompletionResponse | ErrorResponse = (response, responseStatus) => { + if ('message' in response && responseStatus !== 200) { + return generateErrorResponse( + { + message: response.message, + type: response.type, + param: response.param, + code: response.code, + }, + LINGYI + ); + } + + if ('choices' in response) { + return { + id: response.id, + object: response.object, + created: response.created, + model: response.model, + provider: LINGYI, + choices: response.choices.map((c) => ({ + index: c.index, + message: { + role: c.message.role, + content: c.message.content, + }, + finish_reason: c.finish_reason, + })), + usage: { + prompt_tokens: response.usage?.prompt_tokens, + completion_tokens: response.usage?.completion_tokens, + total_tokens: response.usage?.total_tokens, + }, + }; + } + + return generateInvalidProviderResponseError(response, LINGYI); +}; + +export const LingyiChatCompleteStreamChunkTransform: ( + response: string +) => string = (responseChunk) => { + let chunk = responseChunk.trim(); + chunk = chunk.replace(/^data: /, ''); + chunk = chunk.trim(); + if (chunk === '[DONE]') { + return `data: ${chunk}\n\n`; + } + const parsedChunk: LingyiStreamChunk = JSON.parse(chunk); + return ( + `data: ${JSON.stringify({ + id: parsedChunk.id, + object: parsedChunk.object, + created: parsedChunk.created, + model: parsedChunk.model, + provider: LINGYI, + choices: [ + { + index: parsedChunk.choices[0].index, + delta: parsedChunk.choices[0].delta, + finish_reason: parsedChunk.choices[0].finish_reason, + }, + ], + })}` + '\n\n' + ); +}; diff --git a/src/providers/lingyi/index.ts b/src/providers/lingyi/index.ts new file mode 100644 index 000000000..d0c89372c --- /dev/null +++ b/src/providers/lingyi/index.ts @@ -0,0 +1,18 @@ +import { ProviderConfigs } from '../types'; +import LingyiAPIConfig from './api'; +import { + LingyiChatCompleteConfig, + LingyiChatCompleteResponseTransform, + LingyiChatCompleteStreamChunkTransform, +} from './chatComplete'; + +const LingyiConfig: ProviderConfigs = { + chatComplete: LingyiChatCompleteConfig, + api: LingyiAPIConfig, + responseTransforms: { + chatComplete: LingyiChatCompleteResponseTransform, + 'stream-chatComplete': LingyiChatCompleteStreamChunkTransform, + }, +}; + +export default LingyiConfig; diff --git a/src/providers/moonshot/api.ts b/src/providers/moonshot/api.ts new file mode 100644 index 000000000..e3e735fc7 --- /dev/null +++ b/src/providers/moonshot/api.ts @@ -0,0 +1,18 @@ +import { ProviderAPIConfig } from '../types'; + +const MoonshotAPIConfig: ProviderAPIConfig = { + getBaseURL: () => 'https://api.moonshot.cn', + headers: ({ providerOptions }) => { + return { Authorization: `Bearer ${providerOptions.apiKey}` }; // https://platform.moonshot.cn/console/api-keys + }, + getEndpoint: ({ fn }) => { + switch (fn) { + case 'chatComplete': + return '/v1/chat/completions'; + default: + return ''; + } + }, +}; + +export default MoonshotAPIConfig; diff --git a/src/providers/moonshot/chatComplete.ts b/src/providers/moonshot/chatComplete.ts new file mode 100644 index 000000000..cd02edfbc --- /dev/null +++ b/src/providers/moonshot/chatComplete.ts @@ -0,0 +1,149 @@ +import { MOONSHOT } from '../../globals'; + +import { + ChatCompletionResponse, + ErrorResponse, + ProviderConfig, +} from '../types'; +import { + generateErrorResponse, + generateInvalidProviderResponseError, +} from '../utils'; + +export const MoonshotChatCompleteConfig: ProviderConfig = { + model: { + param: 'model', + required: true, + default: 'moonshot-v1-8k', + }, + messages: { + param: 'messages', + default: '', + }, + max_tokens: { + param: 'max_tokens', + default: 100, + min: 0, + }, + temperature: { + param: 'temperature', + default: 1, + min: 0, + max: 2, + }, + top_p: { + param: 'top_p', + default: 1, + min: 0, + max: 1, + }, + stream: { + param: 'stream', + default: false, + }, +}; + +interface MoonshotChatCompleteResponse extends ChatCompletionResponse { + id: string; + object: string; + created: number; + model: 'moonshot-v1-8k' | 'moonshot-v1-32k' | 'moonshot-v1-128k'; + usage: { + prompt_tokens: number; + completion_tokens: number; + total_tokens: number; + }; +} + +export interface MoonshotErrorResponse { + object: string; + message: string; + type: string; + param: string | null; + code: string; +} + +interface MoonshotStreamChunk { + id: string; + object: string; + created: number; + model: string; + choices: { + delta: { + role?: string | null; + content?: string; + }; + index: number; + finish_reason: string | null; + }[]; +} + +export const MoonshotChatCompleteResponseTransform: ( + response: MoonshotChatCompleteResponse | MoonshotErrorResponse, + responseStatus: number +) => ChatCompletionResponse | ErrorResponse = (response, responseStatus) => { + if ('message' in response && responseStatus !== 200) { + return generateErrorResponse( + { + message: response.message, + type: response.type, + param: response.param, + code: response.code, + }, + MOONSHOT + ); + } + + if ('choices' in response) { + return { + id: response.id, + object: response.object, + created: response.created, + model: response.model, + provider: MOONSHOT, + choices: response.choices.map((c) => ({ + index: c.index, + message: { + role: c.message.role, + content: c.message.content, + }, + finish_reason: c.finish_reason, + })), + usage: { + prompt_tokens: response.usage?.prompt_tokens, + completion_tokens: response.usage?.completion_tokens, + total_tokens: response.usage?.total_tokens, + }, + }; + } + + return generateInvalidProviderResponseError(response, MOONSHOT); +}; + +export const MoonshotChatCompleteStreamChunkTransform: ( + response: string +) => string = (responseChunk) => { + let chunk = responseChunk.trim(); + chunk = chunk.replace(/^data: /, ''); + chunk = chunk.trim(); + if (chunk === '[DONE]') { + return `data: ${chunk}\n\n`; + } + const parsedChunk: MoonshotStreamChunk = JSON.parse(chunk); + return ( + `data: ${JSON.stringify({ + id: parsedChunk.id, + object: parsedChunk.object, + created: parsedChunk.created, + model: parsedChunk.model, + provider: MOONSHOT, + choices: [ + { + index: parsedChunk.choices[0].index, + delta: parsedChunk.choices[0].delta, + finish_reason: parsedChunk.choices[0].finish_reason, + }, + ], + })}` + '\n\n' + ); +}; diff --git a/src/providers/moonshot/index.ts b/src/providers/moonshot/index.ts new file mode 100644 index 000000000..a6b13edb4 --- /dev/null +++ b/src/providers/moonshot/index.ts @@ -0,0 +1,18 @@ +import { ProviderConfigs } from '../types'; +import MoonshotAPIConfig from './api'; +import { + MoonshotChatCompleteConfig, + MoonshotChatCompleteResponseTransform, + MoonshotChatCompleteStreamChunkTransform, +} from './chatComplete'; + +const MoonshotConfig: ProviderConfigs = { + chatComplete: MoonshotChatCompleteConfig, + api: MoonshotAPIConfig, + responseTransforms: { + chatComplete: MoonshotChatCompleteResponseTransform, + 'stream-chatComplete': MoonshotChatCompleteStreamChunkTransform, + }, +}; + +export default MoonshotConfig; diff --git a/src/providers/openrouter/api.ts b/src/providers/openrouter/api.ts new file mode 100644 index 000000000..b5d82f107 --- /dev/null +++ b/src/providers/openrouter/api.ts @@ -0,0 +1,18 @@ +import { ProviderAPIConfig } from '../types'; + +const OpenrouterAPIConfig: ProviderAPIConfig = { + getBaseURL: () => 'https://openrouter.ai/api', + headers: ({ providerOptions }) => { + return { Authorization: `Bearer ${providerOptions.apiKey}` }; // https://openrouter.ai/keys + }, + getEndpoint: ({ fn }) => { + switch (fn) { + case 'chatComplete': + return '/v1/chat/completions'; + default: + return ''; + } + }, +}; + +export default OpenrouterAPIConfig; diff --git a/src/providers/openrouter/chatComplete.ts b/src/providers/openrouter/chatComplete.ts new file mode 100644 index 000000000..43bbfd035 --- /dev/null +++ b/src/providers/openrouter/chatComplete.ts @@ -0,0 +1,163 @@ +import { OPENROUTER } from '../../globals'; +import { + ChatCompletionResponse, + ErrorResponse, + ProviderConfig, +} from '../types'; +import { + generateErrorResponse, + generateInvalidProviderResponseError, +} from '../utils'; + +export const OpenrouterChatCompleteConfig: ProviderConfig = { + model: { + param: 'model', + required: true, + default: 'openrouter/auto', + }, + messages: { + param: 'messages', + default: '', + }, + max_tokens: { + param: 'max_tokens', + default: 100, + min: 0, + }, + temperature: { + param: 'temperature', + default: 1, + min: 0, + max: 2, + }, + top_p: { + param: 'top_p', + default: 1, + min: 0, + max: 1, + }, + stream: { + param: 'stream', + default: false, + }, +}; + +interface OpenrouterChatCompleteResponse extends ChatCompletionResponse { + id: string; + object: string; + created: number; + model: string; + usage: { + prompt_tokens: number; + completion_tokens: number; + total_tokens: number; + }; +} + +export interface OpenrouterErrorResponse { + object: string; + message: string; + type: string; + param: string | null; + code: string; +} + +interface OpenrouterStreamChunk { + id: string; + object: string; + created: number; + model: string; + choices: { + delta: { + role?: string | null; + content?: string; + }; + index: number; + finish_reason: string | null; + }[]; +} + +export const OpenrouterChatCompleteResponseTransform: ( + response: OpenrouterChatCompleteResponse | OpenrouterErrorResponse, + responseStatus: number +) => ChatCompletionResponse | ErrorResponse = (response, responseStatus) => { + if ('message' in response && responseStatus !== 200) { + return generateErrorResponse( + { + message: response.message, + type: response.type, + param: response.param, + code: response.code, + }, + OPENROUTER + ); + } + + if ('choices' in response) { + return { + id: response.id, + object: response.object, + created: response.created, + model: response.model, + provider: OPENROUTER, + choices: response.choices.map((c) => ({ + index: c.index, + message: { + role: c.message.role, + content: c.message.content, + }, + finish_reason: c.finish_reason, + })), + usage: { + prompt_tokens: response.usage?.prompt_tokens, + completion_tokens: response.usage?.completion_tokens, + total_tokens: response.usage?.total_tokens, + }, + }; + } + + return generateInvalidProviderResponseError(response, OPENROUTER); +}; + +export const OpenrouterChatCompleteStreamChunkTransform: ( + response: string +) => string = (responseChunk) => { + let chunk = responseChunk.trim(); + chunk = chunk.replace(/^data: /, ''); + chunk = chunk.trim(); + if (chunk === '[DONE]') { + return `data: ${chunk}\n\n`; + } + if (chunk.includes('OPENROUTER PROCESSING')) { + chunk = JSON.stringify({ + id: `${Date.now()}`, + model: '', + object: 'chat.completion.chunk', + created: Date.now(), + choices: [ + { + index: 0, + delta: { role: 'assistant', content: '' }, + finish_reason: null, + }, + ], + }); + } + const parsedChunk: OpenrouterStreamChunk = JSON.parse(chunk); + return ( + `data: ${JSON.stringify({ + id: parsedChunk.id, + object: parsedChunk.object, + created: parsedChunk.created, + model: parsedChunk.model, + provider: OPENROUTER, + choices: [ + { + index: parsedChunk.choices[0].index, + delta: parsedChunk.choices[0].delta, + finish_reason: parsedChunk.choices[0].finish_reason, + }, + ], + })}` + '\n\n' + ); +}; diff --git a/src/providers/openrouter/index.ts b/src/providers/openrouter/index.ts new file mode 100644 index 000000000..78ff0bcf9 --- /dev/null +++ b/src/providers/openrouter/index.ts @@ -0,0 +1,18 @@ +import { ProviderConfigs } from '../types'; +import OpenrouterAPIConfig from './api'; +import { + OpenrouterChatCompleteConfig, + OpenrouterChatCompleteResponseTransform, + OpenrouterChatCompleteStreamChunkTransform, +} from './chatComplete'; + +const OpenrouterConfig: ProviderConfigs = { + chatComplete: OpenrouterChatCompleteConfig, + api: OpenrouterAPIConfig, + responseTransforms: { + chatComplete: OpenrouterChatCompleteResponseTransform, + 'stream-chatComplete': OpenrouterChatCompleteStreamChunkTransform, + }, +}; + +export default OpenrouterConfig; diff --git a/src/providers/zhipu/api.ts b/src/providers/zhipu/api.ts new file mode 100644 index 000000000..eac118e46 --- /dev/null +++ b/src/providers/zhipu/api.ts @@ -0,0 +1,20 @@ +import { ProviderAPIConfig } from '../types'; + +const ZhipuAPIConfig: ProviderAPIConfig = { + getBaseURL: () => 'https://open.bigmodel.cn/api/paas/v4', + headers: ({ providerOptions }) => { + return { Authorization: `${providerOptions.apiKey}` }; // https://open.bigmodel.cn/usercenter/apikeys + }, + getEndpoint: ({ fn }) => { + switch (fn) { + case 'chatComplete': + return '/chat/completions'; + case 'embed': + return '/embeddings'; + default: + return ''; + } + }, +}; + +export default ZhipuAPIConfig; diff --git a/src/providers/zhipu/chatComplete.ts b/src/providers/zhipu/chatComplete.ts new file mode 100644 index 000000000..6a3363eec --- /dev/null +++ b/src/providers/zhipu/chatComplete.ts @@ -0,0 +1,148 @@ +import { ZHIPU } from '../../globals'; +import { + ChatCompletionResponse, + ErrorResponse, + ProviderConfig, +} from '../types'; +import { + generateErrorResponse, + generateInvalidProviderResponseError, +} from '../utils'; + +export const ZhipuChatCompleteConfig: ProviderConfig = { + model: { + param: 'model', + required: true, + default: 'glm-3-turbo', + }, + messages: { + param: 'messages', + default: '', + }, + max_tokens: { + param: 'max_tokens', + default: 100, + min: 0, + }, + temperature: { + param: 'temperature', + default: 1, + min: 0, + max: 2, + }, + top_p: { + param: 'top_p', + default: 1, + min: 0, + max: 1, + }, + stream: { + param: 'stream', + default: false, + }, +}; + +interface ZhipuChatCompleteResponse extends ChatCompletionResponse { + id: string; + object: string; + created: number; + model: string; + usage: { + prompt_tokens: number; + completion_tokens: number; + total_tokens: number; + }; +} + +export interface ZhipuErrorResponse { + object: string; + message: string; + type: string; + param: string | null; + code: string; +} + +interface ZhipuStreamChunk { + id: string; + object: string; + created: number; + model: string; + choices: { + delta: { + role?: string | null; + content?: string; + }; + index: number; + finish_reason: string | null; + }[]; +} + +export const ZhipuChatCompleteResponseTransform: ( + response: ZhipuChatCompleteResponse | ZhipuErrorResponse, + responseStatus: number +) => ChatCompletionResponse | ErrorResponse = (response, responseStatus) => { + if ('message' in response && responseStatus !== 200) { + return generateErrorResponse( + { + message: response.message, + type: response.type, + param: response.param, + code: response.code, + }, + ZHIPU + ); + } + + if ('choices' in response) { + return { + id: response.id, + object: response.object, + created: response.created, + model: response.model, + provider: ZHIPU, + choices: response.choices.map((c) => ({ + index: c.index, + message: { + role: c.message.role, + content: c.message.content, + }, + finish_reason: c.finish_reason, + })), + usage: { + prompt_tokens: response.usage?.prompt_tokens, + completion_tokens: response.usage?.completion_tokens, + total_tokens: response.usage?.total_tokens, + }, + }; + } + + return generateInvalidProviderResponseError(response, ZHIPU); +}; + +export const ZhipuChatCompleteStreamChunkTransform: ( + response: string +) => string = (responseChunk) => { + let chunk = responseChunk.trim(); + chunk = chunk.replace(/^data: /, ''); + chunk = chunk.trim(); + if (chunk === '[DONE]') { + return `data: ${chunk}\n\n`; + } + const parsedChunk: ZhipuStreamChunk = JSON.parse(chunk); + return ( + `data: ${JSON.stringify({ + id: parsedChunk.id, + object: parsedChunk.object, + created: parsedChunk.created, + model: parsedChunk.model, + provider: ZHIPU, + choices: [ + { + index: parsedChunk.choices[0].index, + delta: parsedChunk.choices[0].delta, + finish_reason: parsedChunk.choices[0].finish_reason, + }, + ], + })}` + '\n\n' + ); +}; diff --git a/src/providers/zhipu/embed.ts b/src/providers/zhipu/embed.ts new file mode 100644 index 000000000..cdd4fb994 --- /dev/null +++ b/src/providers/zhipu/embed.ts @@ -0,0 +1,37 @@ +import { ZHIPU } from '../../globals'; +import { EmbedResponse } from '../../types/embedRequestBody'; +import { ErrorResponse, ProviderConfig } from '../types'; +import { generateErrorResponse } from '../utils'; + +export const ZhipuEmbedConfig: ProviderConfig = { + model: { + param: 'model', + required: true, + default: 'embedding-2', + }, + input: { + param: 'input', + required: true, + }, +}; + +interface ZhipuEmbedResponse extends EmbedResponse {} + +export const ZhipuEmbedResponseTransform: ( + response: ZhipuEmbedResponse | ErrorResponse, + responseStatus: number +) => EmbedResponse | ErrorResponse = (response, responseStatus) => { + if (responseStatus !== 200 && 'error' in response) { + return generateErrorResponse( + { + message: response.error.message, + type: response.error.type, + param: response.error.param, + code: response.error.code, + }, + ZHIPU + ); + } + + return response; +}; diff --git a/src/providers/zhipu/index.ts b/src/providers/zhipu/index.ts new file mode 100644 index 000000000..db3828686 --- /dev/null +++ b/src/providers/zhipu/index.ts @@ -0,0 +1,21 @@ +import { ProviderConfigs } from '../types'; +import ZhipuAPIConfig from './api'; +import { + ZhipuChatCompleteConfig, + ZhipuChatCompleteResponseTransform, + ZhipuChatCompleteStreamChunkTransform, +} from './chatComplete'; +import { ZhipuEmbedConfig, ZhipuEmbedResponseTransform } from './embed'; + +const ZhipuConfig: ProviderConfigs = { + chatComplete: ZhipuChatCompleteConfig, + embed: ZhipuEmbedConfig, + api: ZhipuAPIConfig, + responseTransforms: { + chatComplete: ZhipuChatCompleteResponseTransform, + 'stream-chatComplete': ZhipuChatCompleteStreamChunkTransform, + embed: ZhipuEmbedResponseTransform, + }, +}; + +export default ZhipuConfig; diff --git a/wrangler.toml b/wrangler.toml index 971fb1a06..91bcbf6d4 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -28,4 +28,3 @@ logpush=true [env.production.vars] ENVIRONMENT = 'production' CUSTOM_HEADERS_TO_IGNORE = [] -