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 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://github.com/madkoding/litellmTS">
15
- <img src="https://img.shields.io/github/v/release/madkoding/litellmTS" alt="GitHub Release"/>
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
- ### From GitHub (recommended)
25
+ ```bash
26
+ npm install litellmts-core
27
+ ```
27
28
 
28
- Add this to your `package.json`:
29
+ ### From GitHub (alternative)
29
30
 
30
31
  ```json
31
32
  {
32
33
  "dependencies": {
33
- "@litellmts/core": "github:madkoding/litellmTS"
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 '@litellmts/core';
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
- - **Persistent auth store** — `~/.litellm/auth.json`
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 '@litellmts/core';
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 '@litellmts/core';
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
- Model Map
233
- │ groq/ → ...
234
- │ claude- → .
235
- │ gpt- → ...
221
+ Registry
222
+ │ groq/ → .
223
+ │ claude- →
224
+ │ gpt- → ..
236
225
  └─────────────┘
237
226
  ```
238
227
 
@@ -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.execSync)(`open "${url}"`, { stdio: 'ignore' });
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.execSync)(`start "" "${url}"`, { stdio: 'ignore' });
24
+ (0, node_child_process_1.execFileSync)('cmd', ['/c', 'start', '', url], { stdio: 'ignore' });
25
25
  }
26
26
  else {
27
- (0, node_child_process_1.execSync)(`xdg-open "${url}" 2>/dev/null || sensible-browser "${url}" 2>/dev/null || x-www-browser "${url}"`, { stdio: 'ignore' });
27
+ (0, node_child_process_1.execFileSync)('xdg-open', [url], { stdio: 'ignore' });
28
28
  }
29
29
  }
30
30
  catch {
@@ -1,3 +1,4 @@
1
+ export declare function decrypt(payload: string): string;
1
2
  /** GitHub Copilot OAuth credentials. */
2
3
  export interface CopilotCredentials {
3
4
  githubToken: string;
@@ -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 data = await (0, promises_1.readFile)(STORE_PATH, 'utf-8');
24
- return JSON.parse(data);
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 (0, promises_1.writeFile)(STORE_PATH, JSON.stringify(store, null, 2), 'utf-8');
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 (0, promises_1.writeFile)(STORE_PATH, JSON.stringify({}), 'utf-8');
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 toAnthropicPrompt(messages) {
12
- return messages
13
- .map((msg) => {
14
- const content = msg.content ?? '';
15
- if (msg.role === 'assistant') {
16
- return `${sdk_1.default.AI_PROMPT} ${content}`;
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
- return `${sdk_1.default.HUMAN_PROMPT} ${content}`;
19
- })
20
- .join('') + sdk_1.default.AI_PROMPT;
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 toFinishReson(string) {
23
- if (string === 'max_tokens') {
27
+ function toFinishReason(reason) {
28
+ if (reason === 'max_tokens') {
24
29
  return 'length';
25
30
  }
26
31
  return 'stop';
27
32
  }
28
- function toResponse(anthropicResponse, prompt) {
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: anthropicResponse.model,
41
+ model: message.model,
31
42
  created: (0, getUnixTimestamp_1.getUnixTimestamp)(),
32
- usage: (0, toUsage_1.toUsage)(prompt, anthropicResponse.completion),
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: anthropicResponse.completion,
51
+ content: getTextContent(message.content),
37
52
  role: 'assistant',
38
53
  },
39
- finish_reason: toFinishReson(anthropicResponse.stop_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
- for await (const chunk of stream) {
60
- yield toStreamingChunk(chunk);
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 prompt = toAnthropicPrompt(params.messages);
108
+ const { system, messages } = toMessages(params.messages);
69
109
  const anthropicParams = {
70
110
  model: params.model,
71
- max_tokens_to_sample: params.max_tokens ?? 300,
72
- prompt,
111
+ max_tokens: params.max_tokens ?? 300,
112
+ messages,
113
+ ...(system ? { system } : {}),
73
114
  };
74
- if (params.stream) {
75
- const completionStream = await anthropic.completions.create({
76
- ...anthropicParams,
77
- stream: params.stream,
78
- });
79
- return toStreamingResponse(completionStream);
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);
@@ -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
- const toUsage_1 = require("../utils/toUsage");
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 textsCombined = (0, combinePrompts_1.combinePrompts)(params.messages);
14
- const config = {
47
+ const { message, chatHistory, preamble } = toChatHistory(params.messages);
48
+ const chatParams = {
15
49
  model: params.model,
16
- prompt: textsCombined,
17
- max_tokens: params.max_tokens ?? 50,
50
+ message,
51
+ ...(chatHistory ? { chatHistory } : {}),
52
+ ...(preamble ? { preamble } : {}),
53
+ maxTokens: params.max_tokens ?? 50,
18
54
  temperature: params.temperature ?? 1,
19
55
  };
20
- if (params.stream) {
21
- const stream = await cohere.generateStream({
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
- prompt: textsCombined,
24
- maxTokens: params.max_tokens ?? 50,
25
- temperature: params.temperature ?? 1,
26
- });
27
- return toRealStream(stream, params.model, textsCombined);
28
- }
29
- const response = await cohere.generate(config);
30
- return {
31
- model: params.model,
32
- created: (0, getUnixTimestamp_1.getUnixTimestamp)(),
33
- usage: (0, toUsage_1.toUsage)(textsCombined, response.generations[0].text),
34
- choices: [
35
- {
36
- message: {
37
- content: response.generations[0].text,
38
- role: 'assistant',
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
- finish_reason: 'stop',
41
- index: 0,
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
- async function* toRealStream(stream, model, prompt) {
47
- let fullText = '';
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: 'stop',
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.split('deepinfra/')[1];
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}`);
@@ -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: undefined,
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
- if (params.stream) {
95
- const result = await model.generateContentStream({ contents });
96
- return toStreamingResponse(result.stream);
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);
@@ -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.split('mistral/')[1];
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.split('mistral/')[1];
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)
@@ -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.split('ollama/')[1];
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 (acc += chunk.choices[0].delta.content);
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.split('ollama/')[1];
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) => (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.`);
@@ -37,18 +37,30 @@ async function OpenAIHandler(params) {
37
37
  });
38
38
  const messages = toOpenAIMessages(completionsParams.messages);
39
39
  if (params.stream) {
40
- const response = await openai.chat.completions.create({
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: true,
57
+ stream: false,
43
58
  messages,
44
59
  });
45
- return toStreamingResponse(response);
46
60
  }
47
- const response = await openai.chat.completions.create({
48
- ...completionsParams,
49
- stream: false,
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
- return openai.embeddings.create({ input: params.input, model: params.model });
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 = __importDefault(require("eventsource"));
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) => (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.default(prediction.urls.stream, {
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) => (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 model = params.model.split('replicate/')[1];
84
- const version = model.split(':')[1];
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
- const prediction = await replicate.predictions.create({
87
- version: version,
88
- input: {
89
- prompt,
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
@@ -30,7 +30,6 @@ async function* iterateSSEStream(response, parseChunk, doneToken = '[DONE]') {
30
30
  continue;
31
31
  const payload = trimmed.slice(6);
32
32
  if (payload === doneToken) {
33
- done = true;
34
33
  return;
35
34
  }
36
35
  yield parseChunk(payload);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "litellmts-core",
3
- "version": "1.0.0",
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": "^9.19.0",
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": "^16.4.7",
33
- "eslint": "^9.19.0",
31
+ "dotenv": "^17.4.2",
32
+ "eslint": "^10.4.1",
34
33
  "eslint-config-prettier": "^10.0.1",
35
- "jest": "^29.7.0",
34
+ "jest": "^30.4.2",
36
35
  "jest-runner-groups": "^2.2.0",
37
36
  "prettier": "^3.4.2",
38
- "ts-jest": "^29.2.5",
37
+ "ts-jest": "^29.4.11",
39
38
  "typescript": "~5.9.3",
40
- "typescript-eslint": "^8.24.0"
39
+ "typescript-eslint": "^8.61.0"
41
40
  },
42
41
  "dependencies": {
43
- "@anthropic-ai/sdk": "^0.39.0",
42
+ "@anthropic-ai/sdk": "^0.104.1",
44
43
  "@google/generative-ai": "^0.24.1",
45
- "cohere-ai": "^7.21.0",
46
- "eventsource": "^2.0.2",
44
+ "cohere-ai": "^8.0.0",
45
+ "eventsource": "^4.1.0",
47
46
  "js-tiktoken": "^1.0.21",
48
- "openai": "^4.104.0",
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
  },