litellmts-core 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -30
- package/dist/auth/copilot.js +3 -3
- package/dist/auth/store.d.ts +1 -0
- package/dist/auth/store.js +45 -5
- package/dist/handlers/anthropic.js +89 -43
- package/dist/handlers/cohere.js +84 -39
- package/dist/handlers/deepinfra.js +3 -1
- package/dist/handlers/gemini.js +12 -7
- package/dist/handlers/mistral.js +3 -1
- package/dist/handlers/mistralEmbedding.js +3 -1
- package/dist/handlers/ollama.js +4 -2
- package/dist/handlers/ollamaEmbedding.js +4 -2
- package/dist/handlers/openai.js +20 -8
- package/dist/handlers/openaiEmbedding.js +6 -1
- package/dist/handlers/replicate.js +26 -14
- package/dist/utils/sse.js +0 -1
- package/package.json +11 -17
package/README.md
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
<p align="center">
|
|
2
|
-
<img src="https://raw.githubusercontent.com/madkoding/litellmTS/main/.github/banner.png" alt="LiteLLM.ts" width="600"/>
|
|
3
|
-
</p>
|
|
4
|
-
|
|
5
1
|
<p align="center">
|
|
6
2
|
<strong>Unified TypeScript interface for 45+ LLM providers</strong><br>
|
|
7
3
|
One API. Every model. Zero boilerplate.
|
|
@@ -11,46 +7,39 @@
|
|
|
11
7
|
<a href="https://github.com/madkoding/litellmTS/blob/main/LICENSE">
|
|
12
8
|
<img src="https://img.shields.io/badge/license-GPLv3-blue.svg" alt="License"/>
|
|
13
9
|
</a>
|
|
14
|
-
<a href="https://
|
|
15
|
-
<img src="https://img.shields.io/
|
|
10
|
+
<a href="https://www.npmjs.com/package/litellmts-core">
|
|
11
|
+
<img src="https://img.shields.io/npm/v/litellmts-core" alt="npm"/>
|
|
16
12
|
</a>
|
|
17
13
|
<a href="https://nodejs.org/">
|
|
18
14
|
<img src="https://img.shields.io/badge/node-%3E%3D22-brightgreen" alt="Node"/>
|
|
19
15
|
</a>
|
|
16
|
+
<a href="https://github.com/madkoding/litellmTS/actions">
|
|
17
|
+
<img src="https://img.shields.io/github/actions/workflow/status/madkoding/litellmTS/ci.yml" alt="CI"/>
|
|
18
|
+
</a>
|
|
20
19
|
</p>
|
|
21
20
|
|
|
22
21
|
---
|
|
23
22
|
|
|
24
23
|
## Installation
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
```bash
|
|
26
|
+
npm install litellmts-core
|
|
27
|
+
```
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
### From GitHub (alternative)
|
|
29
30
|
|
|
30
31
|
```json
|
|
31
32
|
{
|
|
32
33
|
"dependencies": {
|
|
33
|
-
"
|
|
34
|
+
"litellmts-core": "github:madkoding/litellmTS"
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
```
|
|
37
38
|
|
|
38
|
-
Then install:
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
npm install
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
### Alternative — npm (when published)
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
npm install @litellmts/core
|
|
48
|
-
```
|
|
49
|
-
|
|
50
39
|
## Quick Start
|
|
51
40
|
|
|
52
41
|
```ts
|
|
53
|
-
import { completion } from '
|
|
42
|
+
import { completion } from 'litellmts-core';
|
|
54
43
|
|
|
55
44
|
const response = await completion({
|
|
56
45
|
model: 'gpt-4o-mini',
|
|
@@ -77,15 +66,15 @@ await completion({ model: 'deepseek/deepseek-chat', ... });
|
|
|
77
66
|
- **TypeScript first** — full type safety with auto-completion
|
|
78
67
|
- **45+ providers** — from OpenAI to niche OpenAI-compatible APIs
|
|
79
68
|
- **No SDK sprawl** — one dependency replaces 10+ vendor SDKs
|
|
80
|
-
- **CLI auth** — built-in OAuth device flow for GitHub Copilot
|
|
81
|
-
- **
|
|
69
|
+
- **CLI auth** — built-in OAuth device flow for GitHub Copilot & API key setup for Anthropic
|
|
70
|
+
- **Encrypted auth store** — `~/.litellm/auth.json` protected with AES-256-GCM (key derived from machine + user)
|
|
82
71
|
|
|
83
72
|
## Usage
|
|
84
73
|
|
|
85
74
|
### Non-streaming
|
|
86
75
|
|
|
87
76
|
```ts
|
|
88
|
-
import { completion } from '
|
|
77
|
+
import { completion } from 'litellmts-core';
|
|
89
78
|
|
|
90
79
|
const response = await completion({
|
|
91
80
|
model: 'gpt-4o-mini',
|
|
@@ -120,7 +109,7 @@ for await (const chunk of stream) {
|
|
|
120
109
|
### Embeddings
|
|
121
110
|
|
|
122
111
|
```ts
|
|
123
|
-
import { embedding } from '
|
|
112
|
+
import { embedding } from 'litellmts-core';
|
|
124
113
|
|
|
125
114
|
const result = await embedding({
|
|
126
115
|
model: 'text-embedding-3-small',
|
|
@@ -229,10 +218,10 @@ npx litellm login anthropic
|
|
|
229
218
|
└──────────────┘ └──────────────┘ └─────────────────┘
|
|
230
219
|
│
|
|
231
220
|
┌──────┴──────┐
|
|
232
|
-
│
|
|
233
|
-
│ groq/ →
|
|
234
|
-
│ claude- →
|
|
235
|
-
│ gpt- →
|
|
221
|
+
│ Registry │
|
|
222
|
+
│ groq/ → . │
|
|
223
|
+
│ claude- → │
|
|
224
|
+
│ gpt- → .. │
|
|
236
225
|
└─────────────┘
|
|
237
226
|
```
|
|
238
227
|
|
package/dist/auth/copilot.js
CHANGED
|
@@ -18,13 +18,13 @@ function openBrowser(url) {
|
|
|
18
18
|
const platform = process.platform;
|
|
19
19
|
try {
|
|
20
20
|
if (platform === 'darwin') {
|
|
21
|
-
(0, node_child_process_1.
|
|
21
|
+
(0, node_child_process_1.execFileSync)('open', [url], { stdio: 'ignore' });
|
|
22
22
|
}
|
|
23
23
|
else if (platform === 'win32') {
|
|
24
|
-
(0, node_child_process_1.
|
|
24
|
+
(0, node_child_process_1.execFileSync)('cmd', ['/c', 'start', '', url], { stdio: 'ignore' });
|
|
25
25
|
}
|
|
26
26
|
else {
|
|
27
|
-
(0, node_child_process_1.
|
|
27
|
+
(0, node_child_process_1.execFileSync)('xdg-open', [url], { stdio: 'ignore' });
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
catch {
|
package/dist/auth/store.d.ts
CHANGED
package/dist/auth/store.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.decrypt = decrypt;
|
|
3
4
|
exports.getProviderCredentials = getProviderCredentials;
|
|
4
5
|
exports.setProviderCredentials = setProviderCredentials;
|
|
5
6
|
exports.getCopilotCredentials = getCopilotCredentials;
|
|
@@ -10,6 +11,38 @@ exports.clearCredentials = clearCredentials;
|
|
|
10
11
|
const promises_1 = require("node:fs/promises");
|
|
11
12
|
const node_path_1 = require("node:path");
|
|
12
13
|
const node_os_1 = require("node:os");
|
|
14
|
+
const node_crypto_1 = require("node:crypto");
|
|
15
|
+
const ALGORITHM = 'aes-256-gcm';
|
|
16
|
+
const KEY_LENGTH = 32;
|
|
17
|
+
const IV_LENGTH = 16;
|
|
18
|
+
const PEPPER = 'litellmts-core@v1';
|
|
19
|
+
function deriveKey() {
|
|
20
|
+
const seed = `${(0, node_os_1.hostname)()}-${process.getuid?.() ?? process.pid}-${PEPPER}`;
|
|
21
|
+
return (0, node_crypto_1.scryptSync)(seed, 'credentials-key-salt', KEY_LENGTH);
|
|
22
|
+
}
|
|
23
|
+
function encrypt(plaintext) {
|
|
24
|
+
const key = deriveKey();
|
|
25
|
+
const iv = (0, node_crypto_1.randomBytes)(IV_LENGTH);
|
|
26
|
+
const cipher = (0, node_crypto_1.createCipheriv)(ALGORITHM, key, iv);
|
|
27
|
+
let encrypted = cipher.update(plaintext, 'utf-8', 'hex');
|
|
28
|
+
encrypted += cipher.final('hex');
|
|
29
|
+
const tag = cipher.getAuthTag().toString('hex');
|
|
30
|
+
return `${iv.toString('hex')}:${tag}:${encrypted}`;
|
|
31
|
+
}
|
|
32
|
+
function decrypt(payload) {
|
|
33
|
+
const parts = payload.split(':');
|
|
34
|
+
if (parts.length < 3)
|
|
35
|
+
throw new Error('Invalid encrypted payload');
|
|
36
|
+
const iv = Buffer.from(parts.shift(), 'hex');
|
|
37
|
+
const tag = Buffer.from(parts.shift(), 'hex');
|
|
38
|
+
const encrypted = parts.join(':');
|
|
39
|
+
const key = deriveKey();
|
|
40
|
+
const decipher = (0, node_crypto_1.createDecipheriv)(ALGORITHM, key, iv);
|
|
41
|
+
decipher.setAuthTag(tag);
|
|
42
|
+
let plaintext = decipher.update(encrypted, 'hex', 'utf-8');
|
|
43
|
+
plaintext += decipher.final('utf-8');
|
|
44
|
+
return plaintext;
|
|
45
|
+
}
|
|
13
46
|
const STORE_DIR = (0, node_path_1.join)((0, node_os_1.homedir)(), '.litellm');
|
|
14
47
|
const STORE_PATH = (0, node_path_1.join)(STORE_DIR, 'auth.json');
|
|
15
48
|
function isNotFound(err) {
|
|
@@ -20,8 +53,11 @@ async function ensureDir() {
|
|
|
20
53
|
}
|
|
21
54
|
async function readStore() {
|
|
22
55
|
try {
|
|
23
|
-
const
|
|
24
|
-
|
|
56
|
+
const raw = await (0, promises_1.readFile)(STORE_PATH, 'utf-8');
|
|
57
|
+
if (!raw.startsWith('{')) {
|
|
58
|
+
return JSON.parse(decrypt(raw));
|
|
59
|
+
}
|
|
60
|
+
return JSON.parse(raw);
|
|
25
61
|
}
|
|
26
62
|
catch (err) {
|
|
27
63
|
if (isNotFound(err))
|
|
@@ -29,6 +65,11 @@ async function readStore() {
|
|
|
29
65
|
throw err;
|
|
30
66
|
}
|
|
31
67
|
}
|
|
68
|
+
async function writeStore(data) {
|
|
69
|
+
const plaintext = JSON.stringify(data);
|
|
70
|
+
const encrypted = encrypt(plaintext);
|
|
71
|
+
await (0, promises_1.writeFile)(STORE_PATH, encrypted, 'utf-8');
|
|
72
|
+
}
|
|
32
73
|
async function getProviderCredentials(provider) {
|
|
33
74
|
try {
|
|
34
75
|
const store = await readStore();
|
|
@@ -47,9 +88,8 @@ async function setProviderCredentials(provider, creds) {
|
|
|
47
88
|
await ensureDir();
|
|
48
89
|
const store = await readStore();
|
|
49
90
|
store[provider] = creds;
|
|
50
|
-
await (
|
|
91
|
+
await writeStore(store);
|
|
51
92
|
}
|
|
52
|
-
// Backward-compat old single-provider format
|
|
53
93
|
async function getCopilotCredentials() {
|
|
54
94
|
const legacy = await getProviderCredentials('github-copilot');
|
|
55
95
|
if (legacy)
|
|
@@ -83,7 +123,7 @@ async function setAnthropicCredentials(creds) {
|
|
|
83
123
|
}
|
|
84
124
|
async function clearCredentials() {
|
|
85
125
|
try {
|
|
86
|
-
await (
|
|
126
|
+
await writeStore({});
|
|
87
127
|
}
|
|
88
128
|
catch {
|
|
89
129
|
// ignore
|
|
@@ -6,58 +6,98 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.AnthropicHandler = AnthropicHandler;
|
|
7
7
|
const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
|
|
8
8
|
const getUnixTimestamp_1 = require("../utils/getUnixTimestamp");
|
|
9
|
-
const toUsage_1 = require("../utils/toUsage");
|
|
10
9
|
const auth_1 = require("../auth");
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (msg.role === '
|
|
16
|
-
|
|
10
|
+
function toMessages(input) {
|
|
11
|
+
let system;
|
|
12
|
+
const messages = [];
|
|
13
|
+
for (const msg of input) {
|
|
14
|
+
if (msg.role === 'system') {
|
|
15
|
+
system = (system ? system + '\n' : '') + (msg.content ?? '');
|
|
16
|
+
continue;
|
|
17
17
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
if (msg.role === 'user' || msg.role === 'assistant') {
|
|
19
|
+
messages.push({
|
|
20
|
+
role: msg.role,
|
|
21
|
+
content: msg.content ?? '',
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return { system, messages };
|
|
21
26
|
}
|
|
22
|
-
function
|
|
23
|
-
if (
|
|
27
|
+
function toFinishReason(reason) {
|
|
28
|
+
if (reason === 'max_tokens') {
|
|
24
29
|
return 'length';
|
|
25
30
|
}
|
|
26
31
|
return 'stop';
|
|
27
32
|
}
|
|
28
|
-
function
|
|
33
|
+
function getTextContent(content) {
|
|
34
|
+
return content
|
|
35
|
+
.filter((block) => block.type === 'text')
|
|
36
|
+
.map((block) => block.text)
|
|
37
|
+
.join('');
|
|
38
|
+
}
|
|
39
|
+
function toResponse(message) {
|
|
29
40
|
return {
|
|
30
|
-
model:
|
|
41
|
+
model: message.model,
|
|
31
42
|
created: (0, getUnixTimestamp_1.getUnixTimestamp)(),
|
|
32
|
-
usage:
|
|
43
|
+
usage: {
|
|
44
|
+
prompt_tokens: message.usage.input_tokens,
|
|
45
|
+
completion_tokens: message.usage.output_tokens,
|
|
46
|
+
total_tokens: message.usage.input_tokens + message.usage.output_tokens,
|
|
47
|
+
},
|
|
33
48
|
choices: [
|
|
34
49
|
{
|
|
35
50
|
message: {
|
|
36
|
-
content:
|
|
51
|
+
content: getTextContent(message.content),
|
|
37
52
|
role: 'assistant',
|
|
38
53
|
},
|
|
39
|
-
finish_reason:
|
|
40
|
-
index: 0,
|
|
41
|
-
},
|
|
42
|
-
],
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
function toStreamingChunk(anthropicResponse) {
|
|
46
|
-
return {
|
|
47
|
-
model: anthropicResponse.model,
|
|
48
|
-
created: (0, getUnixTimestamp_1.getUnixTimestamp)(),
|
|
49
|
-
choices: [
|
|
50
|
-
{
|
|
51
|
-
delta: { content: anthropicResponse.completion, role: 'assistant' },
|
|
52
|
-
finish_reason: toFinishReson(anthropicResponse.stop_reason),
|
|
54
|
+
finish_reason: toFinishReason(message.stop_reason),
|
|
53
55
|
index: 0,
|
|
54
56
|
},
|
|
55
57
|
],
|
|
56
58
|
};
|
|
57
59
|
}
|
|
58
60
|
async function* toStreamingResponse(stream) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
let model = '';
|
|
62
|
+
let stopReason;
|
|
63
|
+
for await (const event of stream) {
|
|
64
|
+
switch (event.type) {
|
|
65
|
+
case 'message_start':
|
|
66
|
+
model = event.message.model;
|
|
67
|
+
stopReason = event.message.stop_reason;
|
|
68
|
+
break;
|
|
69
|
+
case 'content_block_delta':
|
|
70
|
+
if (event.delta.type === 'text_delta') {
|
|
71
|
+
yield {
|
|
72
|
+
model,
|
|
73
|
+
created: (0, getUnixTimestamp_1.getUnixTimestamp)(),
|
|
74
|
+
choices: [
|
|
75
|
+
{
|
|
76
|
+
delta: { content: event.delta.text, role: 'assistant' },
|
|
77
|
+
finish_reason: null,
|
|
78
|
+
index: 0,
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
break;
|
|
84
|
+
case 'message_delta':
|
|
85
|
+
stopReason = event.delta.stop_reason;
|
|
86
|
+
break;
|
|
87
|
+
case 'message_stop':
|
|
88
|
+
yield {
|
|
89
|
+
model,
|
|
90
|
+
created: (0, getUnixTimestamp_1.getUnixTimestamp)(),
|
|
91
|
+
choices: [
|
|
92
|
+
{
|
|
93
|
+
delta: { content: '', role: 'assistant' },
|
|
94
|
+
finish_reason: toFinishReason(stopReason),
|
|
95
|
+
index: 0,
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
61
101
|
}
|
|
62
102
|
}
|
|
63
103
|
async function AnthropicHandler(params) {
|
|
@@ -65,21 +105,27 @@ async function AnthropicHandler(params) {
|
|
|
65
105
|
const anthropic = new sdk_1.default({
|
|
66
106
|
apiKey: apiKey,
|
|
67
107
|
});
|
|
68
|
-
const
|
|
108
|
+
const { system, messages } = toMessages(params.messages);
|
|
69
109
|
const anthropicParams = {
|
|
70
110
|
model: params.model,
|
|
71
|
-
|
|
72
|
-
|
|
111
|
+
max_tokens: params.max_tokens ?? 300,
|
|
112
|
+
messages,
|
|
113
|
+
...(system ? { system } : {}),
|
|
73
114
|
};
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
115
|
+
try {
|
|
116
|
+
if (params.stream) {
|
|
117
|
+
const stream = await anthropic.messages.create({
|
|
118
|
+
...anthropicParams,
|
|
119
|
+
stream: true,
|
|
120
|
+
});
|
|
121
|
+
return toStreamingResponse(stream);
|
|
122
|
+
}
|
|
123
|
+
const message = await anthropic.messages.create(anthropicParams);
|
|
124
|
+
return toResponse(message);
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
throw new Error(`Anthropic API error: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
80
128
|
}
|
|
81
|
-
const completion = await anthropic.completions.create(anthropicParams);
|
|
82
|
-
return toResponse(completion, prompt);
|
|
83
129
|
}
|
|
84
130
|
const registry_1 = require("../registry");
|
|
85
131
|
(0, registry_1.registerCompletionHandler)('claude-', AnthropicHandler);
|
package/dist/handlers/cohere.js
CHANGED
|
@@ -2,56 +2,106 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CohereHandler = CohereHandler;
|
|
4
4
|
const cohere_ai_1 = require("cohere-ai");
|
|
5
|
-
const combinePrompts_1 = require("../utils/combinePrompts");
|
|
6
5
|
const getUnixTimestamp_1 = require("../utils/getUnixTimestamp");
|
|
7
|
-
|
|
6
|
+
function toChatHistory(messages) {
|
|
7
|
+
let system;
|
|
8
|
+
const chatMessages = [];
|
|
9
|
+
for (const msg of messages) {
|
|
10
|
+
if (msg.role === 'system') {
|
|
11
|
+
system = (system ? system + '\n' : '') + (msg.content ?? '');
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
chatMessages.push(msg);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
let lastUserMessage = '';
|
|
18
|
+
const chatHistory = [];
|
|
19
|
+
for (let i = 0; i < chatMessages.length; i++) {
|
|
20
|
+
const msg = chatMessages[i];
|
|
21
|
+
const isLastUser = i === chatMessages.length - 1 && msg.role === 'user';
|
|
22
|
+
if (isLastUser) {
|
|
23
|
+
lastUserMessage = msg.content ?? '';
|
|
24
|
+
}
|
|
25
|
+
else if (msg.role === 'user') {
|
|
26
|
+
chatHistory.push({ role: 'USER', message: msg.content ?? '' });
|
|
27
|
+
}
|
|
28
|
+
else if (msg.role === 'assistant') {
|
|
29
|
+
chatHistory.push({ role: 'CHATBOT', message: msg.content ?? '' });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (!lastUserMessage && chatMessages.length > 0) {
|
|
33
|
+
const last = chatMessages[chatMessages.length - 1];
|
|
34
|
+
lastUserMessage = last.content ?? '';
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
message: lastUserMessage,
|
|
38
|
+
...(chatHistory.length > 0 ? { chatHistory } : {}),
|
|
39
|
+
...(system ? { preamble: system } : {}),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
8
42
|
async function CohereHandler(params) {
|
|
9
43
|
const apiKey = params.apiKey ?? process.env.COHERE_API_KEY;
|
|
10
44
|
if (!apiKey)
|
|
11
45
|
throw new Error('Cohere requires an API key. Set COHERE_API_KEY environment variable or pass apiKey in params.');
|
|
12
46
|
const cohere = new cohere_ai_1.CohereClient({ token: apiKey });
|
|
13
|
-
const
|
|
14
|
-
const
|
|
47
|
+
const { message, chatHistory, preamble } = toChatHistory(params.messages);
|
|
48
|
+
const chatParams = {
|
|
15
49
|
model: params.model,
|
|
16
|
-
|
|
17
|
-
|
|
50
|
+
message,
|
|
51
|
+
...(chatHistory ? { chatHistory } : {}),
|
|
52
|
+
...(preamble ? { preamble } : {}),
|
|
53
|
+
maxTokens: params.max_tokens ?? 50,
|
|
18
54
|
temperature: params.temperature ?? 1,
|
|
19
55
|
};
|
|
20
|
-
|
|
21
|
-
|
|
56
|
+
try {
|
|
57
|
+
if (params.stream) {
|
|
58
|
+
const stream = await cohere.chatStream({
|
|
59
|
+
...chatParams,
|
|
60
|
+
});
|
|
61
|
+
return toStreamingResponse(stream, params.model);
|
|
62
|
+
}
|
|
63
|
+
const { text, finishReason, meta } = await cohere.chat(chatParams);
|
|
64
|
+
return {
|
|
22
65
|
model: params.model,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
66
|
+
created: (0, getUnixTimestamp_1.getUnixTimestamp)(),
|
|
67
|
+
usage: meta?.tokens
|
|
68
|
+
? {
|
|
69
|
+
prompt_tokens: meta.tokens.inputTokens ?? 0,
|
|
70
|
+
completion_tokens: meta.tokens.outputTokens ?? 0,
|
|
71
|
+
total_tokens: (meta.tokens.inputTokens ?? 0) + (meta.tokens.outputTokens ?? 0),
|
|
72
|
+
}
|
|
73
|
+
: undefined,
|
|
74
|
+
choices: [
|
|
75
|
+
{
|
|
76
|
+
message: {
|
|
77
|
+
content: text,
|
|
78
|
+
role: 'assistant',
|
|
79
|
+
},
|
|
80
|
+
finish_reason: toFinishReason(finishReason),
|
|
81
|
+
index: 0,
|
|
39
82
|
},
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
throw new Error(`Cohere API error: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
88
|
+
}
|
|
45
89
|
}
|
|
46
|
-
|
|
47
|
-
|
|
90
|
+
function toFinishReason(reason) {
|
|
91
|
+
if (reason === 'MAX_TOKENS' || reason === 'ERROR_LIMIT') {
|
|
92
|
+
return 'length';
|
|
93
|
+
}
|
|
94
|
+
if (reason === 'ERROR_TOXIC') {
|
|
95
|
+
return 'content_filter';
|
|
96
|
+
}
|
|
97
|
+
return 'stop';
|
|
98
|
+
}
|
|
99
|
+
async function* toStreamingResponse(stream, model) {
|
|
48
100
|
for await (const event of stream) {
|
|
49
101
|
if (event.eventType === 'text-generation') {
|
|
50
|
-
fullText += event.text ?? '';
|
|
51
102
|
yield {
|
|
52
103
|
model,
|
|
53
104
|
created: (0, getUnixTimestamp_1.getUnixTimestamp)(),
|
|
54
|
-
usage: (0, toUsage_1.toUsage)(prompt, fullText),
|
|
55
105
|
choices: [
|
|
56
106
|
{
|
|
57
107
|
delta: { content: event.text, role: 'assistant' },
|
|
@@ -65,20 +115,15 @@ async function* toRealStream(stream, model, prompt) {
|
|
|
65
115
|
yield {
|
|
66
116
|
model,
|
|
67
117
|
created: (0, getUnixTimestamp_1.getUnixTimestamp)(),
|
|
68
|
-
usage: (0, toUsage_1.toUsage)(prompt, fullText),
|
|
69
118
|
choices: [
|
|
70
119
|
{
|
|
71
120
|
delta: { content: '', role: 'assistant' },
|
|
72
|
-
finish_reason:
|
|
121
|
+
finish_reason: toFinishReason(event.finishReason),
|
|
73
122
|
index: 0,
|
|
74
123
|
},
|
|
75
124
|
],
|
|
76
125
|
};
|
|
77
126
|
}
|
|
78
|
-
else if (event.eventType === 'stream-error') {
|
|
79
|
-
const msg = event.message ?? 'unknown';
|
|
80
|
-
throw new Error(`Cohere stream error: ${msg}`);
|
|
81
|
-
}
|
|
82
127
|
}
|
|
83
128
|
}
|
|
84
129
|
const registry_1 = require("../registry");
|
|
@@ -21,7 +21,9 @@ async function DeepInfraHandler(params) {
|
|
|
21
21
|
const apiKey = params.apiKey ?? process.env.DEEPINFRA_API_KEY;
|
|
22
22
|
if (!apiKey)
|
|
23
23
|
throw new Error('DeepInfra requires an API key. Set DEEPINFRA_API_KEY environment variable or pass apiKey in params.');
|
|
24
|
-
const model = params.model.
|
|
24
|
+
const model = params.model.startsWith('deepinfra/')
|
|
25
|
+
? params.model.slice(10)
|
|
26
|
+
: params.model;
|
|
25
27
|
const res = await getDeepInfraResponse(model, params.messages, baseUrl, apiKey, params.stream ?? false);
|
|
26
28
|
if (!res.ok) {
|
|
27
29
|
throw new Error(`DeepInfra API error: ${res.status} ${res.statusText}`);
|
package/dist/handlers/gemini.js
CHANGED
|
@@ -52,12 +52,12 @@ function toResponse(response, model) {
|
|
|
52
52
|
],
|
|
53
53
|
};
|
|
54
54
|
}
|
|
55
|
-
async function* toStreamingResponse(stream) {
|
|
55
|
+
async function* toStreamingResponse(stream, model) {
|
|
56
56
|
for await (const chunk of stream) {
|
|
57
57
|
const candidate = chunk.candidates?.[0];
|
|
58
58
|
const deltaContent = candidate?.content.parts.map((p) => 'text' in p ? p.text : '').join('') ?? '';
|
|
59
59
|
yield {
|
|
60
|
-
model
|
|
60
|
+
model,
|
|
61
61
|
created: (0, getUnixTimestamp_1.getUnixTimestamp)(),
|
|
62
62
|
usage: toUsage(chunk.usageMetadata),
|
|
63
63
|
choices: [
|
|
@@ -91,12 +91,17 @@ async function GeminiHandler(params) {
|
|
|
91
91
|
},
|
|
92
92
|
});
|
|
93
93
|
const contents = toGeminiContent(params.messages);
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
try {
|
|
95
|
+
if (params.stream) {
|
|
96
|
+
const result = await model.generateContentStream({ contents });
|
|
97
|
+
return toStreamingResponse(result.stream, modelName);
|
|
98
|
+
}
|
|
99
|
+
const result = await model.generateContent({ contents });
|
|
100
|
+
return toResponse(result.response, modelName);
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
throw new Error(`Gemini API error: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
97
104
|
}
|
|
98
|
-
const result = await model.generateContent({ contents });
|
|
99
|
-
return toResponse(result.response, modelName);
|
|
100
105
|
}
|
|
101
106
|
const registry_1 = require("../registry");
|
|
102
107
|
(0, registry_1.registerCompletionHandler)('gemini/', GeminiHandler);
|
package/dist/handlers/mistral.js
CHANGED
|
@@ -21,7 +21,9 @@ async function MistralHandler(params) {
|
|
|
21
21
|
const apiKey = params.apiKey ?? process.env.MISTRAL_API_KEY;
|
|
22
22
|
if (!apiKey)
|
|
23
23
|
throw new Error('Mistral requires an API key. Set MISTRAL_API_KEY environment variable or pass apiKey in params.');
|
|
24
|
-
const model = params.model.
|
|
24
|
+
const model = params.model.startsWith('mistral/')
|
|
25
|
+
? params.model.slice(8)
|
|
26
|
+
: params.model;
|
|
25
27
|
const res = await getMistralResponse(model, params.messages, baseUrl, apiKey, params.stream ?? false);
|
|
26
28
|
if (!res.ok) {
|
|
27
29
|
throw new Error(`Mistral API error: ${res.status} ${res.statusText}`);
|
|
@@ -15,7 +15,9 @@ async function getMistralResponse(model, input, baseUrl, apiKey) {
|
|
|
15
15
|
});
|
|
16
16
|
}
|
|
17
17
|
async function MistralEmbeddingHandler(params) {
|
|
18
|
-
const model = params.model.
|
|
18
|
+
const model = params.model.startsWith('mistral/')
|
|
19
|
+
? params.model.slice(8)
|
|
20
|
+
: params.model;
|
|
19
21
|
const baseUrl = params.baseUrl ?? 'https://api.mistral.ai';
|
|
20
22
|
const apiKey = params.apiKey ?? process.env.MISTRAL_API_KEY;
|
|
21
23
|
if (!apiKey)
|
package/dist/handlers/ollama.js
CHANGED
|
@@ -67,7 +67,9 @@ async function getOllamaResponse(model, prompt, baseUrl) {
|
|
|
67
67
|
}
|
|
68
68
|
async function OllamaHandler(params) {
|
|
69
69
|
const baseUrl = params.baseUrl ?? 'http://127.0.0.1:11434';
|
|
70
|
-
const model = params.model.
|
|
70
|
+
const model = params.model.startsWith('ollama/')
|
|
71
|
+
? params.model.slice(7)
|
|
72
|
+
: params.model;
|
|
71
73
|
const prompt = (0, combinePrompts_1.combinePrompts)(params.messages);
|
|
72
74
|
const res = await getOllamaResponse(model, prompt, baseUrl);
|
|
73
75
|
if (!res.ok) {
|
|
@@ -81,7 +83,7 @@ async function OllamaHandler(params) {
|
|
|
81
83
|
chunks.push(chunk);
|
|
82
84
|
}
|
|
83
85
|
const message = chunks.reduce((acc, chunk) => {
|
|
84
|
-
return
|
|
86
|
+
return acc + chunk.choices[0].delta.content;
|
|
85
87
|
}, '');
|
|
86
88
|
return toResponse(message, model, prompt);
|
|
87
89
|
}
|
|
@@ -16,11 +16,13 @@ async function getOllamaResponse(model, input, baseUrl) {
|
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
18
|
async function OllamaEmbeddingHandler(params) {
|
|
19
|
-
const model = params.model.
|
|
19
|
+
const model = params.model.startsWith('ollama/')
|
|
20
|
+
? params.model.slice(7)
|
|
21
|
+
: params.model;
|
|
20
22
|
const baseUrl = params.baseUrl ?? 'http://127.0.0.1:11434';
|
|
21
23
|
const input = typeof params.input === 'string'
|
|
22
24
|
? params.input
|
|
23
|
-
: params.input.reduce((acc, curr) =>
|
|
25
|
+
: params.input.reduce((acc, curr) => acc + curr, '');
|
|
24
26
|
const response = await getOllamaResponse(model, input, baseUrl);
|
|
25
27
|
if (!response.ok) {
|
|
26
28
|
throw new Error(`Received an error with code ${response.status} from Ollama API.`);
|
package/dist/handlers/openai.js
CHANGED
|
@@ -37,18 +37,30 @@ async function OpenAIHandler(params) {
|
|
|
37
37
|
});
|
|
38
38
|
const messages = toOpenAIMessages(completionsParams.messages);
|
|
39
39
|
if (params.stream) {
|
|
40
|
-
|
|
40
|
+
let response;
|
|
41
|
+
try {
|
|
42
|
+
response = await openai.chat.completions.create({
|
|
43
|
+
...completionsParams,
|
|
44
|
+
stream: true,
|
|
45
|
+
messages,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
throw new Error(`OpenAI API error: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
50
|
+
}
|
|
51
|
+
return toStreamingResponse(response);
|
|
52
|
+
}
|
|
53
|
+
let response;
|
|
54
|
+
try {
|
|
55
|
+
response = await openai.chat.completions.create({
|
|
41
56
|
...completionsParams,
|
|
42
|
-
stream:
|
|
57
|
+
stream: false,
|
|
43
58
|
messages,
|
|
44
59
|
});
|
|
45
|
-
return toStreamingResponse(response);
|
|
46
60
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
messages,
|
|
51
|
-
});
|
|
61
|
+
catch (err) {
|
|
62
|
+
throw new Error(`OpenAI API error: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
63
|
+
}
|
|
52
64
|
const result = {
|
|
53
65
|
created: response.created,
|
|
54
66
|
model: response.model,
|
|
@@ -12,7 +12,12 @@ async function OpenAIEmbeddingHandler(params) {
|
|
|
12
12
|
apiKey: apiKey,
|
|
13
13
|
baseURL: baseUrl,
|
|
14
14
|
});
|
|
15
|
-
|
|
15
|
+
try {
|
|
16
|
+
return await openai.embeddings.create({ input: params.input, model: params.model });
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
throw new Error(`OpenAI embedding API error: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
20
|
+
}
|
|
16
21
|
}
|
|
17
22
|
const registry_1 = require("../registry");
|
|
18
23
|
(0, registry_1.registerEmbeddingHandler)('text-embedding-', OpenAIEmbeddingHandler);
|
|
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.ReplicateHandler = ReplicateHandler;
|
|
7
7
|
const replicate_1 = __importDefault(require("replicate"));
|
|
8
|
-
const eventsource_1 =
|
|
8
|
+
const eventsource_1 = require("eventsource");
|
|
9
9
|
const combinePrompts_1 = require("../utils/combinePrompts");
|
|
10
10
|
const toUsage_1 = require("../utils/toUsage");
|
|
11
11
|
const getUnixTimestamp_1 = require("../utils/getUnixTimestamp");
|
|
@@ -16,10 +16,11 @@ async function sleep(time) {
|
|
|
16
16
|
}, time);
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
|
-
async function handleNonStreamingPrediction(prompt, prediction, replicate) {
|
|
19
|
+
async function handleNonStreamingPrediction(prompt, prediction, replicate, modelName) {
|
|
20
20
|
const pred = await replicate.wait(prediction, {});
|
|
21
|
-
const output = pred.output.reduce((acc, curr) =>
|
|
21
|
+
const output = pred.output.reduce((acc, curr) => acc + curr, '');
|
|
22
22
|
return {
|
|
23
|
+
model: modelName,
|
|
23
24
|
usage: (0, toUsage_1.toUsage)(prompt, output),
|
|
24
25
|
created: (0, getUnixTimestamp_1.getUnixTimestamp)(),
|
|
25
26
|
choices: [
|
|
@@ -38,7 +39,7 @@ async function* handleStreamingPrediction(prompt, prediction) {
|
|
|
38
39
|
if (!prediction?.urls?.stream) {
|
|
39
40
|
throw new Error('Prediction does not support streaming');
|
|
40
41
|
}
|
|
41
|
-
const source = new eventsource_1.
|
|
42
|
+
const source = new eventsource_1.EventSource(prediction.urls.stream, {
|
|
42
43
|
withCredentials: true,
|
|
43
44
|
});
|
|
44
45
|
let results = [];
|
|
@@ -57,7 +58,7 @@ async function* handleStreamingPrediction(prompt, prediction) {
|
|
|
57
58
|
while (!done) {
|
|
58
59
|
await promise;
|
|
59
60
|
await sleep(500);
|
|
60
|
-
const combined = results.reduce((acc, curr) =>
|
|
61
|
+
const combined = results.reduce((acc, curr) => acc + curr, '');
|
|
61
62
|
yield {
|
|
62
63
|
created: (0, getUnixTimestamp_1.getUnixTimestamp)(),
|
|
63
64
|
usage: (0, toUsage_1.toUsage)(prompt, combined),
|
|
@@ -80,19 +81,30 @@ async function ReplicateHandler(params) {
|
|
|
80
81
|
const replicate = new replicate_1.default({
|
|
81
82
|
auth: apiKey,
|
|
82
83
|
});
|
|
83
|
-
const
|
|
84
|
-
|
|
84
|
+
const modelName = params.model.startsWith('replicate/')
|
|
85
|
+
? params.model.slice(10)
|
|
86
|
+
: params.model;
|
|
87
|
+
const version = modelName.split(':')[1];
|
|
88
|
+
if (!version) {
|
|
89
|
+
throw new Error(`Invalid Replicate model format: ${params.model}. Expected format: replicate/<owner>/<name>:<version>`);
|
|
90
|
+
}
|
|
85
91
|
const prompt = (0, combinePrompts_1.combinePrompts)(params.messages);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
+
let prediction;
|
|
93
|
+
try {
|
|
94
|
+
prediction = await replicate.predictions.create({
|
|
95
|
+
version: version,
|
|
96
|
+
input: {
|
|
97
|
+
prompt,
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
throw new Error(`Replicate API error: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
103
|
+
}
|
|
92
104
|
if (params.stream) {
|
|
93
105
|
return handleStreamingPrediction(prompt, prediction);
|
|
94
106
|
}
|
|
95
|
-
return handleNonStreamingPrediction(prompt, prediction, replicate);
|
|
107
|
+
return handleNonStreamingPrediction(prompt, prediction, replicate, modelName);
|
|
96
108
|
}
|
|
97
109
|
const registry_1 = require("../registry");
|
|
98
110
|
(0, registry_1.registerCompletionHandler)('replicate/', ReplicateHandler);
|
package/dist/utils/sse.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "litellmts-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "TypeScript implementation of LiteLLM — unified interface for 45+ LLM providers",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -25,34 +25,28 @@
|
|
|
25
25
|
"author": "madkoding",
|
|
26
26
|
"license": "GPL-3.0-only",
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@eslint/js": "^
|
|
29
|
-
"@types/eventsource": "^1.1.15",
|
|
28
|
+
"@eslint/js": "^10.0.1",
|
|
30
29
|
"@types/jest": "^29.5.14",
|
|
31
30
|
"@types/node": "^25.9.3",
|
|
32
|
-
"dotenv": "^
|
|
33
|
-
"eslint": "^
|
|
31
|
+
"dotenv": "^17.4.2",
|
|
32
|
+
"eslint": "^10.4.1",
|
|
34
33
|
"eslint-config-prettier": "^10.0.1",
|
|
35
|
-
"jest": "^
|
|
34
|
+
"jest": "^30.4.2",
|
|
36
35
|
"jest-runner-groups": "^2.2.0",
|
|
37
36
|
"prettier": "^3.4.2",
|
|
38
|
-
"ts-jest": "^29.
|
|
37
|
+
"ts-jest": "^29.4.11",
|
|
39
38
|
"typescript": "~5.9.3",
|
|
40
|
-
"typescript-eslint": "^8.
|
|
39
|
+
"typescript-eslint": "^8.61.0"
|
|
41
40
|
},
|
|
42
41
|
"dependencies": {
|
|
43
|
-
"@anthropic-ai/sdk": "^0.
|
|
42
|
+
"@anthropic-ai/sdk": "^0.104.1",
|
|
44
43
|
"@google/generative-ai": "^0.24.1",
|
|
45
|
-
"cohere-ai": "^
|
|
46
|
-
"eventsource": "^
|
|
44
|
+
"cohere-ai": "^8.0.0",
|
|
45
|
+
"eventsource": "^4.1.0",
|
|
47
46
|
"js-tiktoken": "^1.0.21",
|
|
48
|
-
"openai": "^
|
|
47
|
+
"openai": "^6.42.0",
|
|
49
48
|
"replicate": "^1.4.0"
|
|
50
49
|
},
|
|
51
|
-
"overrides": {
|
|
52
|
-
"jest-runner-groups": {
|
|
53
|
-
"jest-runner": "29.7.0"
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
50
|
"engines": {
|
|
57
51
|
"node": ">=22"
|
|
58
52
|
},
|