litellmts-core 1.0.1 → 2.0.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.
Files changed (46) hide show
  1. package/README.md +46 -21
  2. package/dist/auth/copilot.js +3 -3
  3. package/dist/auth/store.d.ts +1 -0
  4. package/dist/auth/store.js +45 -5
  5. package/dist/handlers/ai21.js +7 -5
  6. package/dist/handlers/anthropic.d.ts +1 -1
  7. package/dist/handlers/anthropic.js +36 -71
  8. package/dist/handlers/cohere.js +104 -43
  9. package/dist/handlers/copilot.js +4 -2
  10. package/dist/handlers/deepinfra.js +18 -3
  11. package/dist/handlers/gemini.d.ts +1 -1
  12. package/dist/handlers/gemini.js +47 -90
  13. package/dist/handlers/geminiEmbedding.d.ts +1 -1
  14. package/dist/handlers/geminiEmbedding.js +6 -9
  15. package/dist/handlers/mistral.js +18 -3
  16. package/dist/handlers/mistralEmbedding.js +3 -1
  17. package/dist/handlers/ollama.js +14 -4
  18. package/dist/handlers/ollamaEmbedding.js +4 -2
  19. package/dist/handlers/openai.js +38 -11
  20. package/dist/handlers/openaiEmbedding.js +10 -2
  21. package/dist/handlers/openaiLike.d.ts +1 -1
  22. package/dist/handlers/openaiLike.js +26 -3
  23. package/dist/handlers/openaiLikeEmbedding.d.ts +1 -1
  24. package/dist/handlers/openaiLikeEmbedding.js +6 -2
  25. package/dist/handlers/replicate.js +41 -16
  26. package/dist/handlers/vertexAnthropic.d.ts +2 -0
  27. package/dist/handlers/vertexAnthropic.js +43 -0
  28. package/dist/handlers/vertexai.d.ts +2 -0
  29. package/dist/handlers/vertexai.js +51 -0
  30. package/dist/handlers/vertexaiEmbedding.d.ts +2 -0
  31. package/dist/handlers/vertexaiEmbedding.js +31 -0
  32. package/dist/index.d.ts +1 -0
  33. package/dist/index.js +5 -1
  34. package/dist/mappings/openaiLike.js +0 -5
  35. package/dist/models/index.d.ts +2 -0
  36. package/dist/models/index.js +7 -0
  37. package/dist/models/registry.d.ts +13 -0
  38. package/dist/models/registry.js +32 -0
  39. package/dist/models/types.d.ts +9 -0
  40. package/dist/models/types.js +2 -0
  41. package/dist/utils/anthropic.d.ts +10 -0
  42. package/dist/utils/anthropic.js +99 -0
  43. package/dist/utils/gemini.d.ts +12 -0
  44. package/dist/utils/gemini.js +73 -0
  45. package/dist/utils/sse.js +0 -1
  46. package/package.json +12 -18
package/README.md CHANGED
@@ -42,7 +42,7 @@ npm install litellmts-core
42
42
  import { completion } from 'litellmts-core';
43
43
 
44
44
  const response = await completion({
45
- model: 'gpt-4o-mini',
45
+ model: 'openai/gpt-4o-mini',
46
46
  messages: [{ role: 'user', content: 'Hello!' }],
47
47
  });
48
48
 
@@ -53,7 +53,7 @@ Swap providers by changing just the model string:
53
53
 
54
54
  ```ts
55
55
  // Same code, different provider:
56
- await completion({ model: 'claude-sonnet-4-20250514', ... });
56
+ await completion({ model: 'anthropic/claude-sonnet-4-20250514', ... });
57
57
  await completion({ model: 'gemini/gemini-2.5-pro', ... });
58
58
  await completion({ model: 'groq/llama-3.3-70b', ... });
59
59
  await completion({ model: 'deepseek/deepseek-chat', ... });
@@ -63,11 +63,13 @@ await completion({ model: 'deepseek/deepseek-chat', ... });
63
63
 
64
64
  - **Unified API** — same `completion()` / `embedding()` for every provider
65
65
  - **Streaming** — all providers support `stream: true`
66
+ - **Model listing** — `listModels('openai')` fetches available models from each provider's API
67
+ - **Provider discovery** — `listProviders()` returns all configured providers
66
68
  - **TypeScript first** — full type safety with auto-completion
67
69
  - **45+ providers** — from OpenAI to niche OpenAI-compatible APIs
68
70
  - **No SDK sprawl** — one dependency replaces 10+ vendor SDKs
69
- - **CLI auth** — built-in OAuth device flow for GitHub Copilot
70
- - **Persistent auth store** — `~/.litellm/auth.json`
71
+ - **CLI auth** — built-in OAuth device flow for GitHub Copilot & API key setup for Anthropic
72
+ - **Encrypted auth store** — `~/.litellm/auth.json` protected with AES-256-GCM (key derived from machine + user)
71
73
 
72
74
  ## Usage
73
75
 
@@ -77,7 +79,7 @@ await completion({ model: 'deepseek/deepseek-chat', ... });
77
79
  import { completion } from 'litellmts-core';
78
80
 
79
81
  const response = await completion({
80
- model: 'gpt-4o-mini',
82
+ model: 'openai/gpt-4o-mini',
81
83
  messages: [
82
84
  { role: 'system', content: 'You are a helpful assistant.' },
83
85
  { role: 'user', content: 'What is TypeScript?' },
@@ -96,7 +98,7 @@ console.log(response.usage);
96
98
 
97
99
  ```ts
98
100
  const stream = await completion({
99
- model: 'claude-sonnet-4-20250514',
101
+ model: 'anthropic/claude-sonnet-4-20250514',
100
102
  messages: [{ role: 'user', content: 'Write a poem' }],
101
103
  stream: true,
102
104
  });
@@ -112,13 +114,33 @@ for await (const chunk of stream) {
112
114
  import { embedding } from 'litellmts-core';
113
115
 
114
116
  const result = await embedding({
115
- model: 'text-embedding-3-small',
117
+ model: 'openai/text-embedding-3-small',
116
118
  input: 'Hello world',
117
119
  });
118
120
 
119
121
  console.log(result.data[0].embedding); // number[]
120
122
  ```
121
123
 
124
+ ### Model Discovery
125
+
126
+ ```ts
127
+ import { listModels, listProviders, clearModelCache } from 'litellmts-core';
128
+
129
+ // List all available models for a provider (fetched live from their API)
130
+ const models = await listModels('openai');
131
+ // => [{ id: 'gpt-4o', provider: 'openai', created: 1700000000 }, ...]
132
+
133
+ // List all configured providers
134
+ const providers = listProviders();
135
+ // => [{ name: 'openai', hasModelList: true }, { name: 'groq', hasModelList: true }, ...]
136
+
137
+ // Get models for a specific provider with apiKey override
138
+ const groqModels = await listModels('groq', { apiKey: 'gsk_...' });
139
+
140
+ // Clear cached model lists (re-fetches on next call)
141
+ clearModelCache();
142
+ ```
143
+
122
144
  ### API Keys
123
145
 
124
146
  Keys are read from environment variables by default:
@@ -153,17 +175,17 @@ npx litellm login anthropic
153
175
 
154
176
  ### Dedicated Handlers
155
177
 
156
- | Provider | Prefix | Completion | Streaming | Embedding | API Key Env |
157
- |---|---|---|---|---|---|
158
- | OpenAI | `gpt-*`, `openai/` | ✅ | ✅ | ✅ | `OPENAI_API_KEY` |
159
- | Anthropic | `claude-*` | ✅ | ✅ | ❌ | `ANTHROPIC_API_KEY` |
178
+ | Provider | Model prefix | Completion | Streaming | Embedding | API Key Env |
179
+ |---|---|---|---|---|---|---|
180
+ | OpenAI | `openai/` | ✅ | ✅ | ✅ | `OPENAI_API_KEY` |
181
+ | Anthropic | `anthropic/` | ✅ | ✅ | ❌ | `ANTHROPIC_API_KEY` |
160
182
  | Google Gemini | `gemini/` | ✅ | ✅ | ✅ | `GEMINI_API_KEY` |
161
183
  | GitHub Copilot | `copilot/` | ✅ | ✅ | ❌ | (OAuth) |
162
184
  | Mistral | `mistral/` | ✅ | ✅ | ✅ | `MISTRAL_API_KEY` |
163
- | Cohere | `command*` | ✅ | ✅ | ❌ | `COHERE_API_KEY` |
185
+ | Cohere | `cohere/` | ✅ | ✅ | ❌ | `COHERE_API_KEY` |
164
186
  | DeepInfra | `deepinfra/` | ✅ | ✅ | ❌ | `DEEPINFRA_API_KEY` |
165
187
  | Replicate | `replicate/` | ✅ | ✅ | ❌ | `REPLICATE_API_KEY` |
166
- | AI21 Labs | `j2-*`, `ai21/` | ✅ | ✅ | ❌ | `AI21_API_KEY` |
188
+ | AI21 Labs | `ai21/` | ✅ | ✅ | ❌ | `AI21_API_KEY` |
167
189
  | Ollama (local) | `ollama/` | ✅ | ✅ | ✅ | — |
168
190
 
169
191
  ### OpenAI-Compatible (38 providers)
@@ -213,16 +235,19 @@ npx litellm login anthropic
213
235
  ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐
214
236
  │ completion() │────▶│ getHandler() │────▶│ OpenAIHandler │
215
237
  │ embedding() │ │ (prefix │ │ AnthropicHandler│
216
- │ │ matching) │ │ GeminiHandler │
217
- │ │ │ │ OpenAILikeHandler│
238
+ listModels() │ │ matching) │ │ GeminiHandler │
239
+ listProviders│ │ │ │ OpenAILikeHandler│
218
240
  └──────────────┘ └──────────────┘ └─────────────────┘
219
241
 
220
- ┌──────┴──────┐
221
- │ Registry │
222
- groq/ → .
223
- claude-
224
- gpt- → ..
225
- └─────────────┘
242
+ ┌──────┴──────┐ ┌──────────────────┐
243
+ │ Registry │ │ Model Registry │
244
+ openai/ → │ │ (in-memory │
245
+ anthropic/ │ cache + TTL) │
246
+ groq/ → .. │ │
247
+ └─────────────┘ │ listModels() │
248
+ │ listProviders() │
249
+ │ clearModelCache()│
250
+ └──────────────────┘
226
251
  ```
227
252
 
228
253
  ## Development
@@ -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
@@ -56,9 +56,11 @@ async function AI21Handler(params) {
56
56
  const apiKey = params.apiKey ?? process.env.AI21_API_KEY;
57
57
  if (!apiKey)
58
58
  throw new Error('AI21 requires an API key. Set AI21_API_KEY environment variable or pass apiKey in params.');
59
- const model = params.model;
59
+ const modelName = params.model.startsWith('ai21/')
60
+ ? params.model.slice(5)
61
+ : params.model;
60
62
  const prompt = (0, combinePrompts_1.combinePrompts)(params.messages);
61
- const res = await getAI21Response(model, prompt, baseUrl, apiKey, params.stream ?? false);
63
+ const res = await getAI21Response(modelName, prompt, baseUrl, apiKey, params.stream ?? false);
62
64
  if (!res.ok) {
63
65
  throw new Error(`Received an error with code ${res.status} from AI21 API.`);
64
66
  }
@@ -66,7 +68,7 @@ async function AI21Handler(params) {
66
68
  return (0, sse_1.iterateSSEStream)(res, (payload) => {
67
69
  const parsed = JSON.parse(payload);
68
70
  return {
69
- model,
71
+ model: modelName,
70
72
  created: (0, getUnixTimestamp_1.getUnixTimestamp)(),
71
73
  choices: [
72
74
  {
@@ -81,7 +83,7 @@ async function AI21Handler(params) {
81
83
  });
82
84
  }
83
85
  const body = (await res.json());
84
- return toResponse(body, model);
86
+ return toResponse(body, modelName);
85
87
  }
86
88
  const registry_1 = require("../registry");
87
- (0, registry_1.registerCompletionHandler)('j2-', AI21Handler);
89
+ (0, registry_1.registerCompletionHandler)('ai21/', AI21Handler);
@@ -1,2 +1,2 @@
1
- import { HandlerParams, ResultStreaming, ResultNotStreaming } from '../types';
1
+ import type { HandlerParams, ResultNotStreaming, ResultStreaming } from '../types';
2
2
  export declare function AnthropicHandler(params: HandlerParams): Promise<ResultNotStreaming | ResultStreaming>;
@@ -5,81 +5,46 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.AnthropicHandler = AnthropicHandler;
7
7
  const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
8
- const getUnixTimestamp_1 = require("../utils/getUnixTimestamp");
9
- const toUsage_1 = require("../utils/toUsage");
8
+ const anthropic_1 = require("../utils/anthropic");
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}`;
17
- }
18
- return `${sdk_1.default.HUMAN_PROMPT} ${content}`;
19
- })
20
- .join('') + sdk_1.default.AI_PROMPT;
21
- }
22
- function toFinishReson(string) {
23
- if (string === 'max_tokens') {
24
- return 'length';
25
- }
26
- return 'stop';
27
- }
28
- function toResponse(anthropicResponse, prompt) {
29
- return {
30
- model: anthropicResponse.model,
31
- created: (0, getUnixTimestamp_1.getUnixTimestamp)(),
32
- usage: (0, toUsage_1.toUsage)(prompt, anthropicResponse.completion),
33
- choices: [
34
- {
35
- message: {
36
- content: anthropicResponse.completion,
37
- role: 'assistant',
38
- },
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),
53
- index: 0,
54
- },
55
- ],
56
- };
57
- }
58
- async function* toStreamingResponse(stream) {
59
- for await (const chunk of stream) {
60
- yield toStreamingChunk(chunk);
61
- }
62
- }
10
+ const registry_1 = require("../models/registry");
63
11
  async function AnthropicHandler(params) {
64
12
  const apiKey = params.apiKey ?? process.env.ANTHROPIC_API_KEY ?? (await (0, auth_1.getAnthropicKey)());
65
- const anthropic = new sdk_1.default({
66
- apiKey: apiKey,
67
- });
68
- const prompt = toAnthropicPrompt(params.messages);
13
+ const modelName = params.model.startsWith('anthropic/')
14
+ ? params.model.slice(10)
15
+ : params.model;
16
+ const anthropic = new sdk_1.default({ apiKey });
17
+ const { system, messages } = (0, anthropic_1.toAnthropicMessages)(params.messages);
69
18
  const anthropicParams = {
70
- model: params.model,
71
- max_tokens_to_sample: params.max_tokens ?? 300,
72
- prompt,
19
+ model: modelName,
20
+ max_tokens: params.max_tokens ?? 300,
21
+ messages,
22
+ ...(system ? { system } : {}),
73
23
  };
74
- if (params.stream) {
75
- const completionStream = await anthropic.completions.create({
76
- ...anthropicParams,
77
- stream: params.stream,
78
- });
79
- return toStreamingResponse(completionStream);
24
+ try {
25
+ if (params.stream) {
26
+ const stream = await anthropic.messages.create({
27
+ ...anthropicParams,
28
+ stream: true,
29
+ });
30
+ return (0, anthropic_1.toAnthropicStreamingResponse)(stream);
31
+ }
32
+ const message = await anthropic.messages.create(anthropicParams);
33
+ return (0, anthropic_1.toAnthropicResponse)(message);
34
+ }
35
+ catch (err) {
36
+ throw new Error(`Anthropic API error: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
80
37
  }
81
- const completion = await anthropic.completions.create(anthropicParams);
82
- return toResponse(completion, prompt);
83
38
  }
84
- const registry_1 = require("../registry");
85
- (0, registry_1.registerCompletionHandler)('claude-', AnthropicHandler);
39
+ (0, registry_1.registerModelProvider)('anthropic', async ({ apiKey } = {}) => {
40
+ const key = apiKey ?? process.env.ANTHROPIC_API_KEY;
41
+ if (!key)
42
+ return [];
43
+ const res = await fetch('https://api.anthropic.com/v1/models', {
44
+ headers: { 'x-api-key': key, 'anthropic-version': '2023-06-01' },
45
+ });
46
+ const { data } = await res.json();
47
+ return data.map((m) => ({ id: m.id, provider: 'anthropic' }));
48
+ });
49
+ const registry_2 = require("../registry");
50
+ (0, registry_2.registerCompletionHandler)('anthropic/', AnthropicHandler);
@@ -2,56 +2,110 @@
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
+ const registry_1 = require("../models/registry");
7
+ function toChatHistory(messages) {
8
+ let system;
9
+ const chatMessages = [];
10
+ for (const msg of messages) {
11
+ if (msg.role === 'system') {
12
+ system = (system ? system + '\n' : '') + (msg.content ?? '');
13
+ }
14
+ else {
15
+ chatMessages.push(msg);
16
+ }
17
+ }
18
+ let lastUserMessage = '';
19
+ const chatHistory = [];
20
+ for (let i = 0; i < chatMessages.length; i++) {
21
+ const msg = chatMessages[i];
22
+ const isLastUser = i === chatMessages.length - 1 && msg.role === 'user';
23
+ if (isLastUser) {
24
+ lastUserMessage = msg.content ?? '';
25
+ }
26
+ else if (msg.role === 'user') {
27
+ chatHistory.push({ role: 'USER', message: msg.content ?? '' });
28
+ }
29
+ else if (msg.role === 'assistant') {
30
+ chatHistory.push({ role: 'CHATBOT', message: msg.content ?? '' });
31
+ }
32
+ }
33
+ if (!lastUserMessage && chatMessages.length > 0) {
34
+ const last = chatMessages[chatMessages.length - 1];
35
+ lastUserMessage = last.content ?? '';
36
+ }
37
+ return {
38
+ message: lastUserMessage,
39
+ ...(chatHistory.length > 0 ? { chatHistory } : {}),
40
+ ...(system ? { preamble: system } : {}),
41
+ };
42
+ }
8
43
  async function CohereHandler(params) {
9
44
  const apiKey = params.apiKey ?? process.env.COHERE_API_KEY;
10
45
  if (!apiKey)
11
46
  throw new Error('Cohere requires an API key. Set COHERE_API_KEY environment variable or pass apiKey in params.');
47
+ const modelName = params.model.startsWith('cohere/')
48
+ ? params.model.slice(7)
49
+ : params.model;
12
50
  const cohere = new cohere_ai_1.CohereClient({ token: apiKey });
13
- const textsCombined = (0, combinePrompts_1.combinePrompts)(params.messages);
14
- const config = {
15
- model: params.model,
16
- prompt: textsCombined,
17
- max_tokens: params.max_tokens ?? 50,
51
+ const { message, chatHistory, preamble } = toChatHistory(params.messages);
52
+ const chatParams = {
53
+ model: modelName,
54
+ message,
55
+ ...(chatHistory ? { chatHistory } : {}),
56
+ ...(preamble ? { preamble } : {}),
57
+ maxTokens: params.max_tokens ?? 50,
18
58
  temperature: params.temperature ?? 1,
19
59
  };
20
- if (params.stream) {
21
- const stream = await cohere.generateStream({
22
- 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',
60
+ try {
61
+ if (params.stream) {
62
+ const stream = await cohere.chatStream({
63
+ ...chatParams,
64
+ });
65
+ return toStreamingResponse(stream, modelName);
66
+ }
67
+ const { text, finishReason, meta } = await cohere.chat(chatParams);
68
+ return {
69
+ model: modelName,
70
+ created: (0, getUnixTimestamp_1.getUnixTimestamp)(),
71
+ usage: meta?.tokens
72
+ ? {
73
+ prompt_tokens: meta.tokens.inputTokens ?? 0,
74
+ completion_tokens: meta.tokens.outputTokens ?? 0,
75
+ total_tokens: (meta.tokens.inputTokens ?? 0) + (meta.tokens.outputTokens ?? 0),
76
+ }
77
+ : undefined,
78
+ choices: [
79
+ {
80
+ message: {
81
+ content: text,
82
+ role: 'assistant',
83
+ },
84
+ finish_reason: toFinishReason(finishReason),
85
+ index: 0,
39
86
  },
40
- finish_reason: 'stop',
41
- index: 0,
42
- },
43
- ],
44
- };
87
+ ],
88
+ };
89
+ }
90
+ catch (err) {
91
+ throw new Error(`Cohere API error: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
92
+ }
45
93
  }
46
- async function* toRealStream(stream, model, prompt) {
47
- let fullText = '';
94
+ function toFinishReason(reason) {
95
+ if (reason === 'MAX_TOKENS' || reason === 'ERROR_LIMIT') {
96
+ return 'length';
97
+ }
98
+ if (reason === 'ERROR_TOXIC') {
99
+ return 'content_filter';
100
+ }
101
+ return 'stop';
102
+ }
103
+ async function* toStreamingResponse(stream, model) {
48
104
  for await (const event of stream) {
49
105
  if (event.eventType === 'text-generation') {
50
- fullText += event.text ?? '';
51
106
  yield {
52
107
  model,
53
108
  created: (0, getUnixTimestamp_1.getUnixTimestamp)(),
54
- usage: (0, toUsage_1.toUsage)(prompt, fullText),
55
109
  choices: [
56
110
  {
57
111
  delta: { content: event.text, role: 'assistant' },
@@ -65,21 +119,28 @@ async function* toRealStream(stream, model, prompt) {
65
119
  yield {
66
120
  model,
67
121
  created: (0, getUnixTimestamp_1.getUnixTimestamp)(),
68
- usage: (0, toUsage_1.toUsage)(prompt, fullText),
69
122
  choices: [
70
123
  {
71
124
  delta: { content: '', role: 'assistant' },
72
- finish_reason: 'stop',
125
+ finish_reason: toFinishReason(event.finishReason),
73
126
  index: 0,
74
127
  },
75
128
  ],
76
129
  };
77
130
  }
78
- else if (event.eventType === 'stream-error') {
79
- const msg = event.message ?? 'unknown';
80
- throw new Error(`Cohere stream error: ${msg}`);
81
- }
82
131
  }
83
132
  }
84
- const registry_1 = require("../registry");
85
- (0, registry_1.registerCompletionHandler)('command', CohereHandler);
133
+ (0, registry_1.registerModelProvider)('cohere', async ({ apiKey } = {}) => {
134
+ const key = apiKey ?? process.env.COHERE_API_KEY;
135
+ if (!key)
136
+ return [];
137
+ const res = await fetch('https://api.cohere.com/v1/models', {
138
+ headers: { Authorization: `Bearer ${key}` },
139
+ });
140
+ if (!res.ok)
141
+ return [];
142
+ const json = await res.json();
143
+ return (json.models ?? []).map((m) => ({ id: m.id, provider: 'cohere' }));
144
+ });
145
+ const registry_2 = require("../registry");
146
+ (0, registry_2.registerCompletionHandler)('cohere/', CohereHandler);
@@ -132,5 +132,7 @@ async function CopilotHandler(params) {
132
132
  };
133
133
  return result;
134
134
  }
135
- const registry_1 = require("../registry");
136
- (0, registry_1.registerCompletionHandler)('copilot/', CopilotHandler);
135
+ const registry_1 = require("../models/registry");
136
+ (0, registry_1.registerModelProvider)('copilot', async () => []);
137
+ const registry_2 = require("../registry");
138
+ (0, registry_2.registerCompletionHandler)('copilot/', CopilotHandler);
@@ -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,5 +54,18 @@ async function DeepInfraHandler(params) {
52
54
  };
53
55
  return result;
54
56
  }
55
- const registry_1 = require("../registry");
56
- (0, registry_1.registerCompletionHandler)('deepinfra/', DeepInfraHandler);
57
+ const registry_1 = require("../models/registry");
58
+ (0, registry_1.registerModelProvider)('deepinfra', async ({ apiKey } = {}) => {
59
+ const key = apiKey ?? process.env.DEEPINFRA_API_KEY;
60
+ if (!key)
61
+ return [];
62
+ const res = await fetch('https://api.deepinfra.com/v1/openai/models', {
63
+ headers: { Authorization: `Bearer ${key}` },
64
+ });
65
+ if (!res.ok)
66
+ return [];
67
+ const { data } = await res.json();
68
+ return (data ?? []).map((m) => ({ id: m.id, provider: 'deepinfra' }));
69
+ });
70
+ const registry_2 = require("../registry");
71
+ (0, registry_2.registerCompletionHandler)('deepinfra/', DeepInfraHandler);
@@ -1,2 +1,2 @@
1
- import { HandlerParams, ResultNotStreaming, ResultStreaming } from '../types';
1
+ import type { HandlerParams, ResultNotStreaming, ResultStreaming } from '../types';
2
2
  export declare function GeminiHandler(params: HandlerParams): Promise<ResultNotStreaming | ResultStreaming>;