anymodel 1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 antonoly
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,142 @@
1
+ # anymodel
2
+
3
+ **Run Claude Code with any AI model — OpenRouter, Ollama, or any LLM provider.**
4
+
5
+ [![npm version](https://img.shields.io/npm/v/anymodel)](https://www.npmjs.com/package/anymodel)
6
+ [![license](https://img.shields.io/npm/l/anymodel)](https://github.com/antonoly/anymodel/blob/main/LICENSE)
7
+ [![node](https://img.shields.io/node/v/anymodel)](https://nodejs.org)
8
+
9
+ Use Claude Code's powerful agentic coding with **any model** — GPT-4o, Gemini, Llama, Mistral, DeepSeek, and hundreds more. anymodel is a lightweight proxy that sits between Claude Code and your preferred AI provider, translating requests on the fly.
10
+
11
+ **Zero dependencies.** Just Node.js.
12
+
13
+ ## Quick Start
14
+
15
+ ```bash
16
+ npx anymodel
17
+ ```
18
+
19
+ That's it. anymodel auto-detects your available provider and starts a local proxy.
20
+
21
+ ## Usage
22
+
23
+ ```bash
24
+ # Auto-detect provider (checks OPENROUTER_API_KEY, then local Ollama)
25
+ npx anymodel
26
+
27
+ # Explicitly use OpenRouter
28
+ npx anymodel openrouter
29
+
30
+ # Use local Ollama
31
+ npx anymodel ollama
32
+
33
+ # Specify a model
34
+ npx anymodel --model google/gemini-2.5-flash
35
+
36
+ # Custom port
37
+ npx anymodel --port 8080
38
+
39
+ # Combine options
40
+ npx anymodel openrouter --model deepseek/deepseek-r1 --port 3000
41
+ ```
42
+
43
+ Then in another terminal:
44
+
45
+ ```bash
46
+ ANTHROPIC_BASE_URL=http://localhost:9090 claude
47
+ ```
48
+
49
+ ## How It Works
50
+
51
+ ```
52
+ ┌─────────────┐ ┌──────────────┐ ┌──────────────────┐
53
+ │ Claude Code │────>│ anymodel │────>│ OpenRouter / │
54
+ │ │<────│ :9090 │<────│ Ollama / etc. │
55
+ └─────────────┘ └──────────────┘ └──────────────────┘
56
+
57
+ │ (non-/v1/messages)
58
+ v
59
+ ┌──────────────┐
60
+ │ Anthropic │
61
+ │ API │
62
+ └──────────────┘
63
+ ```
64
+
65
+ anymodel intercepts `/v1/messages` requests from Claude Code and routes them to your chosen provider. All other requests (auth, config) pass through to Anthropic's API unchanged.
66
+
67
+ The proxy automatically:
68
+ - Strips Anthropic-specific fields (`cache_control`, `betas`, `metadata`, `thinking`, etc.)
69
+ - Normalizes `tool_choice` format for cross-provider compatibility
70
+ - Retries failed requests with exponential backoff (3 attempts, max 8s delay)
71
+ - Streams responses back in real-time
72
+
73
+ ## Supported Providers
74
+
75
+ | Provider | Command | Requirements |
76
+ |----------|---------|-------------|
77
+ | [OpenRouter](https://openrouter.ai) | `anymodel openrouter` | `OPENROUTER_API_KEY` |
78
+ | [Ollama](https://ollama.ai) | `anymodel ollama` | Ollama running locally |
79
+
80
+ ### OpenRouter
81
+
82
+ Access 200+ models through a single API. [Get your API key](https://openrouter.ai/keys).
83
+
84
+ ```bash
85
+ export OPENROUTER_API_KEY=sk-or-v1-...
86
+ npx anymodel openrouter --model google/gemini-2.5-flash
87
+ ```
88
+
89
+ Popular models: `google/gemini-2.5-flash`, `deepseek/deepseek-r1`, `meta-llama/llama-4-maverick`, `openai/gpt-4o`
90
+
91
+ ### Ollama
92
+
93
+ Run models locally with zero cloud dependency.
94
+
95
+ ```bash
96
+ ollama serve
97
+ npx anymodel ollama --model llama3
98
+ ```
99
+
100
+ ## Configuration
101
+
102
+ ### Environment Variables
103
+
104
+ | Variable | Description | Default |
105
+ |----------|-------------|---------|
106
+ | `OPENROUTER_API_KEY` | OpenRouter API key | — |
107
+ | `OPENROUTER_MODEL` | Default model override | passthrough |
108
+ | `PROXY_PORT` | Proxy listen port | `9090` |
109
+
110
+ ### .env File
111
+
112
+ anymodel auto-loads a `.env` file from the current directory:
113
+
114
+ ```env
115
+ OPENROUTER_API_KEY=sk-or-v1-...
116
+ OPENROUTER_MODEL=google/gemini-2.5-flash
117
+ ```
118
+
119
+ ## CLI Reference
120
+
121
+ ```
122
+ anymodel [provider] [options]
123
+
124
+ Providers:
125
+ openrouter Route through OpenRouter
126
+ ollama Route through local Ollama
127
+
128
+ Options:
129
+ --model, -m Model to use (e.g., google/gemini-2.5-flash)
130
+ --port, -p Proxy port (default: 9090)
131
+ --help, -h Show help
132
+ ```
133
+
134
+ ## Links
135
+
136
+ - [anymodel.dev](https://anymodel.dev) — Project homepage
137
+ - [OpenRouter](https://openrouter.ai) — Multi-model API gateway
138
+ - [GitHub](https://github.com/antonoly/anymodel) — Source code
139
+
140
+ ## License
141
+
142
+ MIT
package/cli.mjs ADDED
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env node
2
+
3
+ // anymodel CLI — Run Claude Code with any AI model
4
+ // Usage:
5
+ // npx anymodel # auto-detect provider
6
+ // npx anymodel openrouter # use OpenRouter
7
+ // npx anymodel ollama # use local Ollama
8
+ // npx anymodel --model google/gemini-2.5-flash --port 8080
9
+
10
+ import { createProxy, loadEnv } from './proxy.mjs';
11
+
12
+ const PROVIDERS = ['openrouter', 'ollama'];
13
+
14
+ export function parseArgs(argv) {
15
+ const opts = { provider: 'auto', port: 9090, model: null, help: false };
16
+
17
+ for (let i = 0; i < argv.length; i++) {
18
+ const arg = argv[i];
19
+ if (arg === '--help' || arg === '-h') {
20
+ opts.help = true;
21
+ } else if (arg === '--model' || arg === '-m') {
22
+ opts.model = argv[++i] || null;
23
+ } else if (arg === '--port' || arg === '-p') {
24
+ opts.port = parseInt(argv[++i], 10) || 9090;
25
+ } else if (!arg.startsWith('-') && PROVIDERS.includes(arg)) {
26
+ opts.provider = arg;
27
+ }
28
+ }
29
+
30
+ return opts;
31
+ }
32
+
33
+ export async function detectProvider() {
34
+ if (process.env.OPENROUTER_API_KEY) return 'openrouter';
35
+
36
+ const { default: ollama } = await import('./providers/ollama.mjs');
37
+ if (await ollama.detect()) return 'ollama';
38
+
39
+ return null;
40
+ }
41
+
42
+ function printHelp() {
43
+ console.log(`
44
+ \x1b[35m anymodel\x1b[0m — Run Claude Code with any AI model
45
+
46
+ \x1b[1mUsage:\x1b[0m
47
+ anymodel [provider] [options]
48
+
49
+ \x1b[1mProviders:\x1b[0m
50
+ openrouter Route through OpenRouter (needs OPENROUTER_API_KEY)
51
+ ollama Route through local Ollama instance
52
+
53
+ \x1b[1mOptions:\x1b[0m
54
+ --model, -m Model to use (e.g., google/gemini-2.5-flash)
55
+ --port, -p Proxy port (default: 9090)
56
+ --help, -h Show this help
57
+
58
+ \x1b[1mExamples:\x1b[0m
59
+ anymodel # auto-detect provider
60
+ anymodel openrouter # use OpenRouter
61
+ anymodel ollama --model llama3 # use Ollama with llama3
62
+ anymodel --model google/gemini-2.5-flash # specific model via OpenRouter
63
+
64
+ \x1b[1mEnvironment:\x1b[0m
65
+ OPENROUTER_API_KEY Your OpenRouter API key (https://openrouter.ai/keys)
66
+ OPENROUTER_MODEL Default model override
67
+ PROXY_PORT Default port override
68
+
69
+ https://anymodel.dev
70
+ `);
71
+ }
72
+
73
+ async function main() {
74
+ loadEnv();
75
+
76
+ const opts = parseArgs(process.argv.slice(2));
77
+
78
+ if (opts.help) {
79
+ printHelp();
80
+ process.exit(0);
81
+ }
82
+
83
+ // Resolve provider
84
+ let providerName = opts.provider;
85
+ if (providerName === 'auto') {
86
+ providerName = await detectProvider();
87
+ if (!providerName) {
88
+ console.error('\x1b[31mError: Could not auto-detect a provider.\x1b[0m');
89
+ console.error('');
90
+ console.error(' Set OPENROUTER_API_KEY for OpenRouter:');
91
+ console.error(' export OPENROUTER_API_KEY=sk-or-...');
92
+ console.error(' Get your key at https://openrouter.ai/keys');
93
+ console.error('');
94
+ console.error(' Or start Ollama for local models:');
95
+ console.error(' ollama serve');
96
+ console.error('');
97
+ process.exit(1);
98
+ }
99
+ console.log(`\x1b[36m[AUTO]\x1b[0m Detected provider: ${providerName}`);
100
+ }
101
+
102
+ // Validate OpenRouter has API key
103
+ if (providerName === 'openrouter' && !process.env.OPENROUTER_API_KEY) {
104
+ console.error('\x1b[31mError: OPENROUTER_API_KEY environment variable is required\x1b[0m');
105
+ console.error('Get your key at https://openrouter.ai/keys');
106
+ process.exit(1);
107
+ }
108
+
109
+ // Load provider
110
+ const { default: provider } = await import(`./providers/${providerName}.mjs`);
111
+
112
+ // Model override: CLI flag > env var > none
113
+ const model = opts.model || process.env.OPENROUTER_MODEL || null;
114
+ const port = opts.port || parseInt(process.env.PROXY_PORT, 10) || 9090;
115
+
116
+ createProxy(provider, { port, model });
117
+ }
118
+
119
+ // Only run main when executed directly (not imported for testing)
120
+ const isMain = process.argv[1] && (
121
+ process.argv[1].endsWith('/cli.mjs') ||
122
+ process.argv[1].endsWith('\\cli.mjs') ||
123
+ process.argv[1].endsWith('/anymodel')
124
+ );
125
+ if (isMain) main();
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "anymodel",
3
+ "version": "1.0.0",
4
+ "description": "Run Claude Code with any AI model — OpenRouter, Ollama, or any LLM provider",
5
+ "type": "module",
6
+ "bin": { "anymodel": "./cli.mjs" },
7
+ "main": "proxy.mjs",
8
+ "files": ["cli.mjs", "proxy.mjs", "providers/", "README.md", "LICENSE"],
9
+ "scripts": {
10
+ "test": "node --test test/*.test.mjs",
11
+ "start": "node cli.mjs"
12
+ },
13
+ "keywords": ["claude", "claude-code", "openrouter", "ollama", "llm", "proxy", "ai", "cli"],
14
+ "author": "antonoly",
15
+ "license": "MIT",
16
+ "repository": { "type": "git", "url": "https://github.com/antonoly/anymodel" },
17
+ "homepage": "https://anymodel.dev",
18
+ "engines": { "node": ">=18.0.0" }
19
+ }
@@ -0,0 +1,37 @@
1
+ // Ollama provider for anymodel
2
+ // Routes requests to local Ollama instance (OpenAI-compatible endpoint)
3
+
4
+ import http from 'http';
5
+
6
+ export default {
7
+ name: 'ollama',
8
+
9
+ buildRequest(url, payload) {
10
+ return {
11
+ hostname: 'localhost',
12
+ port: 11434,
13
+ path: url,
14
+ method: 'POST',
15
+ headers: {
16
+ 'content-type': 'application/json',
17
+ 'content-length': Buffer.byteLength(payload),
18
+ },
19
+ };
20
+ },
21
+
22
+ displayInfo(model) {
23
+ return model ? `(${model} @ localhost:11434)` : '(localhost:11434)';
24
+ },
25
+
26
+ // Check if Ollama is running locally
27
+ detect() {
28
+ return new Promise(resolve => {
29
+ const req = http.get('http://localhost:11434', res => {
30
+ res.resume();
31
+ resolve(true);
32
+ });
33
+ req.on('error', () => resolve(false));
34
+ req.setTimeout(1000, () => { req.destroy(); resolve(false); });
35
+ });
36
+ },
37
+ };
@@ -0,0 +1,27 @@
1
+ // OpenRouter provider for anymodel
2
+ // Routes requests to openrouter.ai/api with Bearer auth
3
+
4
+ export default {
5
+ name: 'openrouter',
6
+
7
+ buildRequest(url, payload, apiKey) {
8
+ return {
9
+ hostname: 'openrouter.ai',
10
+ port: 443,
11
+ path: '/api' + url,
12
+ method: 'POST',
13
+ headers: {
14
+ 'content-type': 'application/json',
15
+ 'authorization': `Bearer ${apiKey}`,
16
+ 'anthropic-version': '2023-06-01',
17
+ 'content-length': Buffer.byteLength(payload),
18
+ 'http-referer': 'https://github.com/antonoly/anymodel',
19
+ 'x-title': 'anymodel',
20
+ },
21
+ };
22
+ },
23
+
24
+ displayInfo(model) {
25
+ return model ? `(${model})` : '(passthrough model)';
26
+ },
27
+ };
package/proxy.mjs ADDED
@@ -0,0 +1,245 @@
1
+ // Core proxy server for anymodel
2
+ // Routes /v1/messages → provider, everything else → api.anthropic.com
3
+
4
+ import http from 'http';
5
+ import https from 'https';
6
+ import { readFileSync } from 'fs';
7
+
8
+ const pkg = JSON.parse(readFileSync(new URL('package.json', import.meta.url), 'utf8'));
9
+
10
+ export const MAX_RETRIES = 3;
11
+
12
+ // ANSI colors
13
+ const C = {
14
+ cyan: s => `\x1b[36m${s}\x1b[0m`,
15
+ green: s => `\x1b[32m${s}\x1b[0m`,
16
+ red: s => `\x1b[31m${s}\x1b[0m`,
17
+ yellow: s => `\x1b[33m${s}\x1b[0m`,
18
+ magenta: s => `\x1b[35m${s}\x1b[0m`,
19
+ bold: s => `\x1b[1m${s}\x1b[0m`,
20
+ };
21
+
22
+ // Strip Anthropic-specific fields that break non-Anthropic providers
23
+ export function sanitizeBody(body) {
24
+ delete body.betas;
25
+ delete body.metadata;
26
+ delete body.speed;
27
+ delete body.output_config;
28
+ delete body.context_management;
29
+ delete body.thinking;
30
+
31
+ // Strip cache_control from system blocks
32
+ if (Array.isArray(body.system)) {
33
+ body.system = body.system.map(block => {
34
+ if (block && typeof block === 'object' && block.cache_control) {
35
+ const { cache_control, ...rest } = block;
36
+ return rest;
37
+ }
38
+ return block;
39
+ });
40
+ }
41
+
42
+ // Strip cache_control from message content blocks
43
+ if (Array.isArray(body.messages)) {
44
+ for (const msg of body.messages) {
45
+ if (Array.isArray(msg.content)) {
46
+ msg.content = msg.content.map(block => {
47
+ if (block && typeof block === 'object' && block.cache_control) {
48
+ const { cache_control, ...rest } = block;
49
+ return rest;
50
+ }
51
+ return block;
52
+ });
53
+ }
54
+ }
55
+ }
56
+
57
+ // Strip Anthropic-only tool fields
58
+ if (Array.isArray(body.tools)) {
59
+ body.tools = body.tools.map(tool => {
60
+ const { cache_control, defer_loading, eager_input_streaming, strict, ...rest } = tool;
61
+ return rest;
62
+ });
63
+ }
64
+
65
+ // Normalize tool_choice: providers expect object, Claude Code may send string
66
+ if (typeof body.tool_choice === 'string') {
67
+ body.tool_choice = { type: body.tool_choice };
68
+ }
69
+
70
+ return body;
71
+ }
72
+
73
+ // Calculate exponential backoff delay, capped at 8s
74
+ export function calcDelay(attempt) {
75
+ return Math.min(1000 * Math.pow(2, attempt - 1), 8000);
76
+ }
77
+
78
+ // Check if a URL path should be routed to the provider
79
+ export function isProviderRoute(url) {
80
+ return url.startsWith('/v1/messages');
81
+ }
82
+
83
+ export function sleep(ms) {
84
+ return new Promise(r => setTimeout(r, ms));
85
+ }
86
+
87
+ // Load .env file if present (from given dir, or cwd)
88
+ export function loadEnv(dir) {
89
+ try {
90
+ const envPath = dir ? `${dir}/.env` : `${process.cwd()}/.env`;
91
+ const envFile = readFileSync(envPath, 'utf8');
92
+ for (const line of envFile.split('\n')) {
93
+ const trimmed = line.trim();
94
+ if (!trimmed || trimmed.startsWith('#')) continue;
95
+ const eq = trimmed.indexOf('=');
96
+ if (eq > 0) {
97
+ const key = trimmed.slice(0, eq).trim();
98
+ let val = trimmed.slice(eq + 1).trim();
99
+ // Strip surrounding quotes
100
+ if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
101
+ val = val.slice(1, -1);
102
+ }
103
+ if (!process.env[key]) process.env[key] = val;
104
+ }
105
+ }
106
+ } catch {}
107
+ }
108
+
109
+ function sendRequest(provider, url, payload) {
110
+ const opts = provider.buildRequest(url, payload, process.env.OPENROUTER_API_KEY);
111
+
112
+ return new Promise((resolve, reject) => {
113
+ const transport = opts.port === 443 || opts.protocol === 'https:' ? https : http;
114
+ const req = transport.request(opts, upstream => resolve(upstream));
115
+ req.on('error', reject);
116
+ req.write(payload);
117
+ req.end();
118
+ });
119
+ }
120
+
121
+ async function handleMessages(req, res, provider, model) {
122
+ const chunks = [];
123
+ req.on('data', c => chunks.push(c));
124
+ await new Promise(r => req.on('end', r));
125
+ const raw = Buffer.concat(chunks);
126
+
127
+ let parsed;
128
+ try {
129
+ parsed = JSON.parse(raw.toString());
130
+ } catch {
131
+ res.writeHead(400, { 'content-type': 'application/json' });
132
+ res.end(JSON.stringify({ error: { type: 'invalid_request', message: 'Invalid JSON' } }));
133
+ return;
134
+ }
135
+
136
+ const originalModel = parsed.model;
137
+ if (model) parsed.model = model;
138
+
139
+ sanitizeBody(parsed);
140
+
141
+ const payload = JSON.stringify(parsed);
142
+ const modelDisplay = model ? `${originalModel} \u2192 ${model}` : originalModel;
143
+ console.log(`${C.cyan(`[${provider.name.toUpperCase()}]`)} ${req.method} ${req.url} model=${modelDisplay} stream=${parsed.stream}`);
144
+
145
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
146
+ try {
147
+ const upstream = await sendRequest(provider, req.url, payload);
148
+
149
+ if (upstream.statusCode === 429 || upstream.statusCode >= 500) {
150
+ const errChunks = [];
151
+ upstream.on('data', c => errChunks.push(c));
152
+ await new Promise(r => upstream.on('end', r));
153
+ const errBody = Buffer.concat(errChunks).toString();
154
+
155
+ const delay = calcDelay(attempt);
156
+ console.log(`${C.red(`[${provider.name.toUpperCase()}]`)} ${upstream.statusCode} on attempt ${attempt}/${MAX_RETRIES}, retrying in ${delay}ms`);
157
+ console.log(`${C.red(`[${provider.name.toUpperCase()}]`)} ${errBody.slice(0, 200)}`);
158
+
159
+ if (attempt === MAX_RETRIES) {
160
+ res.writeHead(upstream.statusCode, upstream.headers);
161
+ res.end(errBody);
162
+ return;
163
+ }
164
+ await sleep(delay);
165
+ continue;
166
+ }
167
+
168
+ if (upstream.statusCode !== 200) {
169
+ const errChunks = [];
170
+ upstream.on('data', c => errChunks.push(c));
171
+ await new Promise(r => upstream.on('end', r));
172
+ const errBody = Buffer.concat(errChunks).toString();
173
+ console.log(`${C.red(`[${provider.name.toUpperCase()}]`)} ${upstream.statusCode}: ${errBody.slice(0, 300)}`);
174
+ res.writeHead(upstream.statusCode, upstream.headers);
175
+ res.end(errBody);
176
+ return;
177
+ }
178
+
179
+ console.log(`${C.green(`[${provider.name.toUpperCase()}]`)} 200 \u2190 streaming response (attempt ${attempt})`);
180
+ res.writeHead(200, upstream.headers);
181
+ upstream.pipe(res);
182
+ return;
183
+
184
+ } catch (e) {
185
+ console.error(`${C.red(`[${provider.name.toUpperCase()}]`)} Connection error on attempt ${attempt}: ${e.message}`);
186
+ if (attempt === MAX_RETRIES) {
187
+ res.writeHead(502, { 'content-type': 'application/json' });
188
+ res.end(JSON.stringify({ error: { type: 'proxy_error', message: e.message } }));
189
+ return;
190
+ }
191
+ await sleep(calcDelay(attempt));
192
+ }
193
+ }
194
+ }
195
+
196
+ function proxyToAnthropic(req, res) {
197
+ const body = [];
198
+ req.on('data', c => body.push(c));
199
+ req.on('end', () => {
200
+ const opts = {
201
+ hostname: 'api.anthropic.com',
202
+ port: 443,
203
+ path: req.url,
204
+ method: req.method,
205
+ headers: { ...req.headers, host: 'api.anthropic.com' },
206
+ };
207
+ const pr = https.request(opts, upstream => {
208
+ res.writeHead(upstream.statusCode, upstream.headers);
209
+ upstream.pipe(res);
210
+ });
211
+ pr.on('error', e => { res.writeHead(502); res.end(e.message); });
212
+ if (body.length) pr.write(Buffer.concat(body));
213
+ pr.end();
214
+ });
215
+ }
216
+
217
+ export function createProxy(provider, { port = 9090, model } = {}) {
218
+ const server = http.createServer((req, res) => {
219
+ if (isProviderRoute(req.url)) {
220
+ handleMessages(req, res, provider, model);
221
+ } else {
222
+ console.log(`${C.yellow('[ANTHROPIC]')} ${req.method} ${req.url}`);
223
+ proxyToAnthropic(req, res);
224
+ }
225
+ });
226
+
227
+ server.listen(port, () => {
228
+ console.log('');
229
+ console.log(C.magenta(` anymodel v${pkg.version}`));
230
+ console.log('');
231
+ console.log(` ${C.cyan('\u2194')} Proxy on :${port}`);
232
+ console.log(` /v1/messages \u2192 ${C.bold(provider.name)} ${provider.displayInfo(model)}`);
233
+ console.log(` everything else \u2192 api.anthropic.com`);
234
+ console.log(` Retries: ${MAX_RETRIES} with exponential backoff`);
235
+ if (model) {
236
+ console.log(` Model override: ${C.cyan(model)}`);
237
+ }
238
+ console.log('');
239
+ console.log(` ${C.green('Run in another terminal:')}`);
240
+ console.log(` ${C.bold(`ANTHROPIC_BASE_URL=http://localhost:${port} claude`)}`);
241
+ console.log('');
242
+ });
243
+
244
+ return server;
245
+ }