@careob/llm-gateway 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 ADDED
@@ -0,0 +1,202 @@
1
+ # @careob/llm-gateway
2
+
3
+ Lightweight LLM gateway for Node.js with multi-key rotation, rate-limit handling, provider failover, and spend tracking.
4
+
5
+ ## Features
6
+
7
+ - **12+ providers** — OpenAI, Anthropic, Gemini, OpenRouter, DeepSeek, Groq, Mistral, Together, Qwen, Zhipu, Moonshot, Yi
8
+ - **Key rotation** — round-robin across multiple API keys per provider
9
+ - **Rate-limit handling** — automatic cooldown, exponential backoff with jitter on 429s
10
+ - **Retry classification** — 400s fail fast, 401/403 quarantine keys, 429/5xx retry with backoff
11
+ - **Provider failover** — capability-aware fallback to alternate providers
12
+ - **Safe errors** — API keys and raw provider bodies are never exposed in error messages
13
+ - **AbortSignal** — cancel requests with a caller-provided `AbortSignal`
14
+ - **Spend tracking** — per-request cost estimation and usage logging
15
+ - **Streaming** — SSE streaming with stream-idle timeout detection
16
+ - **Express middleware** — drop-in proxy for Express apps
17
+ - **Auto-discovery** — detects API keys from environment variables
18
+ - **Zero dependencies** — only uses `express` as an optional peer dependency
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ npm install @careob/llm-gateway
24
+ ```
25
+
26
+ ## Quick Start
27
+
28
+ ```typescript
29
+ import { LLMGateway } from '@careob/llm-gateway';
30
+
31
+ const gateway = new LLMGateway({
32
+ keys: [
33
+ 'sk-proj-your-openai-key',
34
+ { key: 'sk-ant-your-anthropic-key', provider: 'anthropic' },
35
+ ],
36
+ defaultModel: 'gpt-4o',
37
+ });
38
+
39
+ const response = await gateway.chat({
40
+ messages: [{ role: 'user', content: 'Hello!' }],
41
+ });
42
+
43
+ console.log(response.choices[0].message.content);
44
+ ```
45
+
46
+ ### Auto-discover keys from environment
47
+
48
+ ```typescript
49
+ // Reads OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY, etc.
50
+ const gateway = LLMGateway.fromEnv({ defaultModel: 'gpt-4o' });
51
+ ```
52
+
53
+ ## Multi-Key Rotation
54
+
55
+ Pass multiple keys for the same provider to distribute load:
56
+
57
+ ```typescript
58
+ const gateway = new LLMGateway({
59
+ keys: [
60
+ { key: 'sk-proj-key-1', provider: 'openai', label: 'prod-1' },
61
+ { key: 'sk-proj-key-2', provider: 'openai', label: 'prod-2' },
62
+ { key: 'sk-proj-key-3', provider: 'openai', label: 'prod-3' },
63
+ ],
64
+ defaultModel: 'gpt-4o',
65
+ });
66
+ ```
67
+
68
+ Keys are rotated round-robin. When a key hits a 429, it's automatically cooled down and the next key is used.
69
+
70
+ ## Streaming
71
+
72
+ ```typescript
73
+ for await (const chunk of gateway.chatStream({
74
+ model: 'gpt-4o',
75
+ messages: [{ role: 'user', content: 'Write a poem' }],
76
+ })) {
77
+ process.stdout.write(chunk.choices[0]?.delta?.content ?? '');
78
+ }
79
+ ```
80
+
81
+ ## OpenRouter
82
+
83
+ Access 200+ models through a single API key via [OpenRouter](https://openrouter.ai):
84
+
85
+ ```typescript
86
+ const gateway = new LLMGateway({
87
+ keys: ['sk-or-v1-your-openrouter-key'],
88
+ defaultModel: 'openai/gpt-4o',
89
+ openRouterReferer: 'https://your-app.com',
90
+ openRouterTitle: 'Your App Name',
91
+ });
92
+
93
+ // Use namespaced model identifiers
94
+ const response = await gateway.chat({
95
+ model: 'anthropic/claude-sonnet-4',
96
+ messages: [{ role: 'user', content: 'Hello!' }],
97
+ });
98
+ ```
99
+
100
+ OpenRouter keys (`sk-or-*`) are auto-detected. Models use namespaced identifiers like `openai/gpt-4o`, `anthropic/claude-sonnet-4`, `google/gemini-2.5-flash`, etc.
101
+
102
+ ## Express Proxy
103
+
104
+ Mount the gateway as an API endpoint in your Express app:
105
+
106
+ ```typescript
107
+ import express from 'express';
108
+ import { LLMGateway, createExpressProxy } from '@careob/llm-gateway';
109
+
110
+ const app = express();
111
+ app.use(express.json());
112
+
113
+ const gateway = LLMGateway.fromEnv({ defaultModel: 'gpt-4o' });
114
+ const proxy = createExpressProxy(gateway, {
115
+ authorize: (req) => req.headers['x-api-key'] === 'your-secret',
116
+ extractMetadata: (req) => ({ userId: req.headers['x-user-id'] as string }),
117
+ });
118
+
119
+ app.post('/v1/chat/completions', proxy.chatCompletions);
120
+ app.get('/v1/health', proxy.health);
121
+
122
+ app.listen(3000);
123
+ ```
124
+
125
+ ## Spend Tracking
126
+
127
+ ```typescript
128
+ const gateway = new LLMGateway({
129
+ keys: ['sk-proj-...'],
130
+ defaultModel: 'gpt-4o',
131
+ onUsage: (log) => {
132
+ console.log(`${log.model} | ${log.totalTokens} tokens | $${log.estimatedCostUsd.toFixed(6)}`);
133
+ // Save to your database, send to analytics, etc.
134
+ },
135
+ onAllKeysExhausted: (provider) => {
136
+ console.warn(`All ${provider} keys exhausted!`);
137
+ },
138
+ });
139
+ ```
140
+
141
+ ## Configuration
142
+
143
+ | Option | Default | Description |
144
+ |---|---|---|
145
+ | `keys` | *required* | Array of API key strings or `KeyInput` objects |
146
+ | `defaultModel` | — | Model to use when not specified in request |
147
+ | `maxRetries` | `3` | Retries across keys before failing |
148
+ | `cooldownMs` | `60000` | Cooldown duration for rate-limited keys (ms) |
149
+ | `timeoutMs` | `30000` | Connection timeout (ms) |
150
+ | `streamTimeoutMs` | `15000` | Stream idle timeout — no data received (ms) |
151
+ | `onUsage` | — | Callback for usage/spend logging |
152
+ | `onAllKeysExhausted` | — | Callback when all keys for a provider are exhausted |
153
+ | `openRouterReferer` | — | Your site URL for OpenRouter rankings (sent as `HTTP-Referer`) |
154
+ | `openRouterTitle` | — | Your app name for OpenRouter rankings (sent as `X-Title`) |
155
+
156
+ ### KeyInput
157
+
158
+ ```typescript
159
+ {
160
+ key: string;
161
+ provider?: string; // Auto-detected from key prefix if omitted
162
+ rpm?: number; // Requests per minute limit
163
+ tpm?: number; // Tokens per minute limit
164
+ label?: string; // Human-readable label (e.g. "prod-key-1")
165
+ }
166
+ ```
167
+
168
+ ## Supported Providers
169
+
170
+ | Provider | Format | Models |
171
+ |---|---|---|
172
+ | OpenAI | openai | gpt-4o, gpt-4o-mini, o1, o3-mini, o4-mini, ... |
173
+ | Anthropic | anthropic | claude-opus-4, claude-sonnet-4, claude-haiku-3.5 |
174
+ | Google Gemini | gemini | gemini-2.5-pro, gemini-2.5-flash, ... |
175
+ | OpenRouter | openai | openai/gpt-4o, anthropic/claude-sonnet-4, google/gemini-2.5-flash, ... |
176
+ | DeepSeek | openai | deepseek-chat, deepseek-coder, deepseek-reasoner |
177
+ | Groq | openai | llama-3.3-70b, mixtral-8x7b, ... |
178
+ | Mistral | openai | mistral-large, mistral-small, codestral |
179
+ | Together AI | openai | Llama-3.3-70B, DeepSeek-R1, ... |
180
+ | Qwen | openai | qwen-turbo, qwen-plus, qwen-max |
181
+ | Zhipu | openai | glm-4, glm-4-flash, glm-4-plus |
182
+ | Moonshot | openai | moonshot-v1-8k/32k/128k |
183
+ | Yi | openai | yi-large, yi-medium, yi-spark |
184
+
185
+ ## Health Check
186
+
187
+ ```typescript
188
+ const stats = gateway.getStats();
189
+ // [{ provider: 'openai', label: 'prod-1', healthy: true, requestsThisMinute: 12, ... }]
190
+ ```
191
+
192
+ ## Development
193
+
194
+ ```bash
195
+ npm test # run tests
196
+ npm run test:coverage # run tests with coverage report
197
+ npm run ci # typecheck + build + test
198
+ ```
199
+
200
+ ## License
201
+
202
+ MIT
package/dist/gateway.d.ts CHANGED
@@ -3,15 +3,11 @@ export declare class LLMGateway {
3
3
  private pool;
4
4
  private config;
5
5
  private providersByKey;
6
+ private openRouterDef;
6
7
  constructor(config: GatewayConfig);
7
- /**
8
- * Auto-discover keys from environment variables.
9
- * Scans for OPENAI_API_KEY, ANTHROPIC_KEY, GEMINI_API_KEY_1, DEEPSEEK_KEY, etc.
10
- */
11
8
  static fromEnv(overrides?: Omit<GatewayConfig, 'keys'>, env?: Record<string, string | undefined>): LLMGateway;
12
9
  chat(request: ChatRequest): Promise<ChatResponse>;
13
10
  chatStream(request: ChatRequest): AsyncGenerator<StreamChunk, void, unknown>;
14
- /** Get health/stats for all registered keys */
15
11
  getStats(): {
16
12
  provider: string;
17
13
  label: string;
@@ -21,10 +17,10 @@ export declare class LLMGateway {
21
17
  totalErrors: number;
22
18
  cooldownUntil: number;
23
19
  }[];
24
- /** List all registered providers */
25
20
  getProviders(): string[];
26
21
  private registerKeys;
27
- private resolveProviders;
22
+ private getProviderDef;
23
+ private resolveTargets;
28
24
  private logUsage;
29
25
  }
30
26
  export type GatewayErrorCode = 'CONFIG_ERROR' | 'PROVIDER_ERROR' | 'NETWORK_ERROR' | 'TIMEOUT' | 'ALL_EXHAUSTED' | 'UNSUPPORTED';
@@ -1 +1 @@
1
- {"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EACV,aAAa,EAEb,WAAW,EACX,YAAY,EACZ,WAAW,EAIZ,MAAM,YAAY,CAAC;AAEpB,qBAAa,UAAU;IACrB,OAAO,CAAC,IAAI,CAAU;IACtB,OAAO,CAAC,MAAM,CAAgF;IAC9F,OAAO,CAAC,cAAc,CAAkC;gBAE5C,MAAM,EAAE,aAAa;IAWjC;;;OAGG;IACH,MAAM,CAAC,OAAO,CACZ,SAAS,GAAE,IAAI,CAAC,aAAa,EAAE,MAAM,CAAM,EAC3C,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GACvC,UAAU;IAWP,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IAwEhD,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,cAAc,CAAC,WAAW,EAAE,IAAI,EAAE,OAAO,CAAC;IAwFnF,+CAA+C;IAC/C,QAAQ;;;;;;;;;IAIR,oCAAoC;IACpC,YAAY;IAMZ,OAAO,CAAC,YAAY;IA8BpB,OAAO,CAAC,gBAAgB;IAqBxB,OAAO,CAAC,QAAQ;CAoCjB;AAED,MAAM,MAAM,gBAAgB,GACxB,cAAc,GACd,gBAAgB,GAChB,eAAe,GACf,SAAS,GACT,eAAe,GACf,aAAa,CAAC;AAElB,qBAAa,YAAa,SAAQ,KAAK;IAG5B,IAAI,EAAE,gBAAgB;IACtB,UAAU,CAAC,EAAE,MAAM;gBAF1B,OAAO,EAAE,MAAM,EACR,IAAI,EAAE,gBAAgB,EACtB,UAAU,CAAC,EAAE,MAAM,YAAA;CAK7B"}
1
+ {"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EACV,aAAa,EAEb,WAAW,EACX,YAAY,EACZ,WAAW,EAIZ,MAAM,YAAY,CAAC;AAEpB,qBAAa,UAAU;IACrB,OAAO,CAAC,IAAI,CAAU;IACtB,OAAO,CAAC,MAAM,CAAyG;IACvH,OAAO,CAAC,cAAc,CAAkC;IACxD,OAAO,CAAC,aAAa,CAA4B;gBAErC,MAAM,EAAE,aAAa;IAYjC,MAAM,CAAC,OAAO,CACZ,SAAS,GAAE,IAAI,CAAC,aAAa,EAAE,MAAM,CAAM,EAC3C,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GACvC,UAAU;IAWP,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IAkGhD,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,cAAc,CAAC,WAAW,EAAE,IAAI,EAAE,OAAO,CAAC;IAuInF,QAAQ;;;;;;;;;IAIR,YAAY;IAMZ,OAAO,CAAC,YAAY;IAqCpB,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,cAAc;IA4CtB,OAAO,CAAC,QAAQ;CAoCjB;AA6FD,MAAM,MAAM,gBAAgB,GACxB,cAAc,GACd,gBAAgB,GAChB,eAAe,GACf,SAAS,GACT,eAAe,GACf,aAAa,CAAC;AAElB,qBAAa,YAAa,SAAQ,KAAK;IAG5B,IAAI,EAAE,gBAAgB;IACtB,UAAU,CAAC,EAAE,MAAM;gBAF1B,OAAO,EAAE,MAAM,EACR,IAAI,EAAE,gBAAgB,EACtB,UAAU,CAAC,EAAE,MAAM,YAAA;CAK7B"}