vertex-ai-proxy 1.2.0 → 1.3.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/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +355 -51
- package/dist/cli.js.map +1 -1
- package/dist/discover.d.ts +19 -0
- package/dist/discover.d.ts.map +1 -0
- package/dist/discover.js +99 -0
- package/dist/discover.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +284 -36
- package/dist/index.js.map +1 -1
- package/dist/regions.d.ts +54 -0
- package/dist/regions.d.ts.map +1 -0
- package/dist/regions.js +328 -0
- package/dist/regions.js.map +1 -0
- package/package.json +1 -1
package/dist/cli.d.ts
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* vertex-ai-proxy restart Restart the daemon
|
|
10
10
|
* vertex-ai-proxy status Show proxy status
|
|
11
11
|
* vertex-ai-proxy logs Show proxy logs
|
|
12
|
+
* vertex-ai-proxy test Run proxy test suite
|
|
13
|
+
* vertex-ai-proxy update Update from npm
|
|
12
14
|
* vertex-ai-proxy models List all available models
|
|
13
15
|
* vertex-ai-proxy models fetch Fetch/verify models from Vertex AI
|
|
14
16
|
* vertex-ai-proxy models info <model> Show detailed model info
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG"}
|
package/dist/cli.js
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* vertex-ai-proxy restart Restart the daemon
|
|
10
10
|
* vertex-ai-proxy status Show proxy status
|
|
11
11
|
* vertex-ai-proxy logs Show proxy logs
|
|
12
|
+
* vertex-ai-proxy test Run proxy test suite
|
|
13
|
+
* vertex-ai-proxy update Update from npm
|
|
12
14
|
* vertex-ai-proxy models List all available models
|
|
13
15
|
* vertex-ai-proxy models fetch Fetch/verify models from Vertex AI
|
|
14
16
|
* vertex-ai-proxy models info <model> Show detailed model info
|
|
@@ -32,7 +34,7 @@ import * as path from 'path';
|
|
|
32
34
|
import * as os from 'os';
|
|
33
35
|
import * as yaml from 'js-yaml';
|
|
34
36
|
import * as readline from 'readline';
|
|
35
|
-
const VERSION = '1.
|
|
37
|
+
const VERSION = '1.3.0';
|
|
36
38
|
const CONFIG_DIR = path.join(os.homedir(), '.vertex-proxy');
|
|
37
39
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.yaml');
|
|
38
40
|
const DATA_DIR = path.join(os.homedir(), '.vertex_proxy');
|
|
@@ -40,44 +42,32 @@ const PID_FILE = path.join(DATA_DIR, 'proxy.pid');
|
|
|
40
42
|
const LOG_FILE = path.join(DATA_DIR, 'proxy.log');
|
|
41
43
|
const STATS_FILE = path.join(DATA_DIR, 'stats.json');
|
|
42
44
|
// ============================================================================
|
|
43
|
-
// Model Catalog
|
|
45
|
+
// Model Catalog (Updated with correct token counts from Vertex AI docs)
|
|
44
46
|
// ============================================================================
|
|
45
47
|
const MODEL_CATALOG = {
|
|
46
|
-
// Claude Models
|
|
48
|
+
// Claude Models (all: 200k input, 64k output)
|
|
47
49
|
'claude-opus-4-5@20251101': {
|
|
48
50
|
id: 'claude-opus-4-5@20251101',
|
|
49
51
|
name: 'Claude Opus 4.5',
|
|
50
52
|
provider: 'anthropic',
|
|
51
53
|
description: 'Most capable Claude. Best for complex reasoning.',
|
|
52
54
|
contextWindow: 200000,
|
|
53
|
-
maxTokens:
|
|
54
|
-
inputPrice: 15,
|
|
55
|
-
outputPrice: 75,
|
|
56
|
-
regions: ['us-east5', 'europe-west1'],
|
|
57
|
-
capabilities: ['text', 'vision', 'tools', 'thinking']
|
|
58
|
-
},
|
|
59
|
-
'claude-opus-4-1@20250410': {
|
|
60
|
-
id: 'claude-opus-4-1@20250410',
|
|
61
|
-
name: 'Claude Opus 4.1',
|
|
62
|
-
provider: 'anthropic',
|
|
63
|
-
description: 'Previous Opus generation.',
|
|
64
|
-
contextWindow: 200000,
|
|
65
|
-
maxTokens: 8192,
|
|
55
|
+
maxTokens: 64000,
|
|
66
56
|
inputPrice: 15,
|
|
67
57
|
outputPrice: 75,
|
|
68
|
-
regions: ['us-east5', 'europe-west1'],
|
|
69
|
-
capabilities: ['text', 'vision', 'tools']
|
|
58
|
+
regions: ['us-east5', 'europe-west1', 'asia-southeast1', 'global'],
|
|
59
|
+
capabilities: ['text', 'vision', 'tools', 'computer-use']
|
|
70
60
|
},
|
|
71
|
-
'claude-sonnet-4-5@
|
|
72
|
-
id: 'claude-sonnet-4-5@
|
|
61
|
+
'claude-sonnet-4-5@20250929': {
|
|
62
|
+
id: 'claude-sonnet-4-5@20250929',
|
|
73
63
|
name: 'Claude Sonnet 4.5',
|
|
74
64
|
provider: 'anthropic',
|
|
75
|
-
description: 'Balanced performance and cost.',
|
|
65
|
+
description: 'Balanced performance and cost. Great for coding.',
|
|
76
66
|
contextWindow: 200000,
|
|
77
|
-
maxTokens:
|
|
67
|
+
maxTokens: 64000,
|
|
78
68
|
inputPrice: 3,
|
|
79
69
|
outputPrice: 15,
|
|
80
|
-
regions: ['us-east5', 'europe-west1'],
|
|
70
|
+
regions: ['us-east5', 'europe-west1', 'asia-southeast1', 'global'],
|
|
81
71
|
capabilities: ['text', 'vision', 'tools', 'thinking']
|
|
82
72
|
},
|
|
83
73
|
'claude-sonnet-4@20250514': {
|
|
@@ -86,37 +76,61 @@ const MODEL_CATALOG = {
|
|
|
86
76
|
provider: 'anthropic',
|
|
87
77
|
description: 'Previous Sonnet generation.',
|
|
88
78
|
contextWindow: 200000,
|
|
89
|
-
maxTokens:
|
|
79
|
+
maxTokens: 64000,
|
|
90
80
|
inputPrice: 3,
|
|
91
81
|
outputPrice: 15,
|
|
92
|
-
regions: ['us-east5', 'europe-west1'],
|
|
93
|
-
capabilities: ['text', 'vision', 'tools']
|
|
82
|
+
regions: ['us-east5', 'europe-west1', 'asia-east1', 'global'],
|
|
83
|
+
capabilities: ['text', 'vision', 'tools', 'thinking']
|
|
94
84
|
},
|
|
95
85
|
'claude-haiku-4-5@20251001': {
|
|
96
86
|
id: 'claude-haiku-4-5@20251001',
|
|
97
87
|
name: 'Claude Haiku 4.5',
|
|
98
88
|
provider: 'anthropic',
|
|
99
|
-
description: 'Fastest and most affordable.',
|
|
89
|
+
description: 'Fastest and most affordable. Great for coding.',
|
|
100
90
|
contextWindow: 200000,
|
|
101
|
-
maxTokens:
|
|
102
|
-
inputPrice: 0.
|
|
103
|
-
outputPrice:
|
|
104
|
-
regions: ['us-east5', 'europe-west1'],
|
|
91
|
+
maxTokens: 64000,
|
|
92
|
+
inputPrice: 0.80,
|
|
93
|
+
outputPrice: 4,
|
|
94
|
+
regions: ['us-east5', 'europe-west1', 'asia-east1', 'global'],
|
|
95
|
+
capabilities: ['text', 'vision', 'tools', 'thinking']
|
|
96
|
+
},
|
|
97
|
+
'claude-opus-4@20250410': {
|
|
98
|
+
id: 'claude-opus-4@20250410',
|
|
99
|
+
name: 'Claude Opus 4',
|
|
100
|
+
provider: 'anthropic',
|
|
101
|
+
description: 'Previous Opus generation.',
|
|
102
|
+
contextWindow: 200000,
|
|
103
|
+
maxTokens: 64000,
|
|
104
|
+
inputPrice: 15,
|
|
105
|
+
outputPrice: 75,
|
|
106
|
+
regions: ['us-east5', 'europe-west1', 'asia-southeast1', 'global'],
|
|
105
107
|
capabilities: ['text', 'vision', 'tools']
|
|
106
108
|
},
|
|
107
109
|
// Gemini Models
|
|
108
|
-
'gemini-3-pro': {
|
|
109
|
-
id: 'gemini-3-pro',
|
|
110
|
+
'gemini-3-pro-preview': {
|
|
111
|
+
id: 'gemini-3-pro-preview',
|
|
110
112
|
name: 'Gemini 3 Pro',
|
|
111
113
|
provider: 'google',
|
|
112
114
|
description: 'Latest Gemini with multimodal.',
|
|
113
|
-
contextWindow:
|
|
114
|
-
maxTokens:
|
|
115
|
+
contextWindow: 1048576,
|
|
116
|
+
maxTokens: 65536,
|
|
115
117
|
inputPrice: 2.5,
|
|
116
118
|
outputPrice: 15,
|
|
117
|
-
regions: ['
|
|
119
|
+
regions: ['global'],
|
|
118
120
|
capabilities: ['text', 'vision', 'audio', 'video', 'tools']
|
|
119
121
|
},
|
|
122
|
+
'gemini-3-pro-image-preview': {
|
|
123
|
+
id: 'gemini-3-pro-image-preview',
|
|
124
|
+
name: 'Gemini 3 Pro Image',
|
|
125
|
+
provider: 'google',
|
|
126
|
+
description: 'Native image generation.',
|
|
127
|
+
contextWindow: 65536,
|
|
128
|
+
maxTokens: 32768,
|
|
129
|
+
inputPrice: 2.5,
|
|
130
|
+
outputPrice: 15,
|
|
131
|
+
regions: ['global'],
|
|
132
|
+
capabilities: ['text', 'vision', 'image-generation']
|
|
133
|
+
},
|
|
120
134
|
'gemini-2.5-pro': {
|
|
121
135
|
id: 'gemini-2.5-pro',
|
|
122
136
|
name: 'Gemini 2.5 Pro',
|
|
@@ -206,7 +220,7 @@ function loadConfig() {
|
|
|
206
220
|
google_region: 'us-central1',
|
|
207
221
|
model_aliases: {},
|
|
208
222
|
fallback_chains: {},
|
|
209
|
-
default_model: 'claude-sonnet-4-5@
|
|
223
|
+
default_model: 'claude-sonnet-4-5@20250929',
|
|
210
224
|
enabled_models: [],
|
|
211
225
|
auto_truncate: true,
|
|
212
226
|
reserve_output_tokens: 4096
|
|
@@ -291,7 +305,8 @@ function formatPrice(input, output) {
|
|
|
291
305
|
function formatCapabilities(caps) {
|
|
292
306
|
const icons = {
|
|
293
307
|
'text': '📝', 'vision': '👁️', 'audio': '🎵', 'video': '🎬',
|
|
294
|
-
'tools': '🔧', 'thinking': '🧠', 'image-generation': '🎨', 'image-edit': '✏️'
|
|
308
|
+
'tools': '🔧', 'thinking': '🧠', 'image-generation': '🎨', 'image-edit': '✏️',
|
|
309
|
+
'computer-use': '🖥️'
|
|
295
310
|
};
|
|
296
311
|
return caps.map(c => icons[c] || c).join(' ');
|
|
297
312
|
}
|
|
@@ -549,12 +564,216 @@ async function showLogs(options) {
|
|
|
549
564
|
}
|
|
550
565
|
}
|
|
551
566
|
// ============================================================================
|
|
567
|
+
// Update Command
|
|
568
|
+
// ============================================================================
|
|
569
|
+
async function runUpdate(options) {
|
|
570
|
+
console.log(chalk.blue.bold('\n📦 Updating Vertex AI Proxy\n'));
|
|
571
|
+
const spinner = ora('Checking for updates...').start();
|
|
572
|
+
try {
|
|
573
|
+
// Check current version
|
|
574
|
+
const currentVersion = VERSION;
|
|
575
|
+
// Check npm for latest version
|
|
576
|
+
let latestVersion;
|
|
577
|
+
try {
|
|
578
|
+
const npmInfo = execSync('npm view vertex-ai-proxy version 2>/dev/null', { encoding: 'utf8' }).trim();
|
|
579
|
+
latestVersion = npmInfo;
|
|
580
|
+
}
|
|
581
|
+
catch (e) {
|
|
582
|
+
spinner.fail('Failed to check npm registry');
|
|
583
|
+
console.log(chalk.gray(' Ensure you have npm access'));
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
if (currentVersion === latestVersion) {
|
|
587
|
+
spinner.succeed(`Already at latest version (${currentVersion})`);
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
spinner.text = `Updating ${currentVersion} → ${latestVersion}...`;
|
|
591
|
+
// Stop daemon if running
|
|
592
|
+
const pid = getPid();
|
|
593
|
+
if (pid && isRunning(pid)) {
|
|
594
|
+
spinner.text = 'Stopping daemon...';
|
|
595
|
+
await stopDaemon();
|
|
596
|
+
}
|
|
597
|
+
// Run npm update
|
|
598
|
+
spinner.text = 'Installing update...';
|
|
599
|
+
const installCmd = options.global
|
|
600
|
+
? 'npm install -g vertex-ai-proxy@latest'
|
|
601
|
+
: 'npm install vertex-ai-proxy@latest';
|
|
602
|
+
execSync(installCmd, { stdio: 'pipe' });
|
|
603
|
+
spinner.succeed(`Updated to version ${latestVersion}`);
|
|
604
|
+
// Restart if was running
|
|
605
|
+
if (pid && isRunning(pid)) {
|
|
606
|
+
console.log(chalk.gray(' Restarting daemon...'));
|
|
607
|
+
await startDaemon({});
|
|
608
|
+
}
|
|
609
|
+
console.log();
|
|
610
|
+
console.log(chalk.gray('Tip: Run `vertex-ai-proxy status` to verify'));
|
|
611
|
+
}
|
|
612
|
+
catch (e) {
|
|
613
|
+
spinner.fail(`Update failed: ${e.message}`);
|
|
614
|
+
console.log(chalk.gray('\nTry manually: npm install -g vertex-ai-proxy@latest'));
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
// ============================================================================
|
|
618
|
+
// Test Command
|
|
619
|
+
// ============================================================================
|
|
620
|
+
async function runTest(options) {
|
|
621
|
+
console.log(chalk.blue.bold('\n🧪 Running Proxy Tests\n'));
|
|
622
|
+
const stats = loadStats();
|
|
623
|
+
const port = options.port || stats?.port || 8001;
|
|
624
|
+
const proxyUrl = `http://localhost:${port}`;
|
|
625
|
+
// Check if proxy is running
|
|
626
|
+
const pid = getPid();
|
|
627
|
+
if (!pid || !isRunning(pid)) {
|
|
628
|
+
console.log(chalk.yellow('⚠️ Proxy not running. Starting...'));
|
|
629
|
+
await startDaemon({ port: port.toString() });
|
|
630
|
+
// Wait for startup
|
|
631
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
632
|
+
}
|
|
633
|
+
// Define tests
|
|
634
|
+
const tests = [
|
|
635
|
+
{ name: 'Health endpoint', test: testHealth },
|
|
636
|
+
{ name: 'Models endpoint', test: testModels },
|
|
637
|
+
{ name: 'Gemini text', test: testGeminiText },
|
|
638
|
+
{ name: 'Gemini vision', test: testGeminiVision },
|
|
639
|
+
];
|
|
640
|
+
if (options.all) {
|
|
641
|
+
tests.push({ name: 'Claude text', test: testClaudeText }, { name: 'Imagen generation', test: testImagen }, { name: 'Gemini native image', test: testGeminiImage });
|
|
642
|
+
}
|
|
643
|
+
let passed = 0;
|
|
644
|
+
let failed = 0;
|
|
645
|
+
for (const { name, test } of tests) {
|
|
646
|
+
const spinner = ora(name).start();
|
|
647
|
+
try {
|
|
648
|
+
const result = await test(proxyUrl);
|
|
649
|
+
spinner.succeed(`${name}: ${result}`);
|
|
650
|
+
passed++;
|
|
651
|
+
}
|
|
652
|
+
catch (e) {
|
|
653
|
+
spinner.fail(`${name}: ${e.message}`);
|
|
654
|
+
failed++;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
console.log();
|
|
658
|
+
console.log(chalk.gray('─'.repeat(40)));
|
|
659
|
+
console.log(`Results: ${chalk.green(`${passed} passed`)}, ${failed > 0 ? chalk.red(`${failed} failed`) : '0 failed'}`);
|
|
660
|
+
if (!options.all) {
|
|
661
|
+
console.log(chalk.gray('\nTip: vertex-ai-proxy test --all (include Claude, Imagen)'));
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
async function testHealth(url) {
|
|
665
|
+
const response = await fetch(`${url}/health`);
|
|
666
|
+
if (!response.ok)
|
|
667
|
+
throw new Error(`HTTP ${response.status}`);
|
|
668
|
+
const data = await response.json();
|
|
669
|
+
return `uptime ${data.uptime}s`;
|
|
670
|
+
}
|
|
671
|
+
async function testModels(url) {
|
|
672
|
+
const response = await fetch(`${url}/v1/models`);
|
|
673
|
+
if (!response.ok)
|
|
674
|
+
throw new Error(`HTTP ${response.status}`);
|
|
675
|
+
const data = await response.json();
|
|
676
|
+
return `${data.data?.length || 0} models`;
|
|
677
|
+
}
|
|
678
|
+
async function testGeminiText(url) {
|
|
679
|
+
const response = await fetch(`${url}/v1/chat/completions`, {
|
|
680
|
+
method: 'POST',
|
|
681
|
+
headers: { 'Content-Type': 'application/json' },
|
|
682
|
+
body: JSON.stringify({
|
|
683
|
+
model: 'gemini-2.5-flash',
|
|
684
|
+
messages: [{ role: 'user', content: 'Say "ok" and nothing else' }],
|
|
685
|
+
max_tokens: 10
|
|
686
|
+
})
|
|
687
|
+
});
|
|
688
|
+
if (!response.ok)
|
|
689
|
+
throw new Error(`HTTP ${response.status}`);
|
|
690
|
+
const data = await response.json();
|
|
691
|
+
const text = data.choices?.[0]?.message?.content || '';
|
|
692
|
+
return `"${text.slice(0, 20)}"`;
|
|
693
|
+
}
|
|
694
|
+
async function testGeminiVision(url) {
|
|
695
|
+
const response = await fetch(`${url}/v1/chat/completions`, {
|
|
696
|
+
method: 'POST',
|
|
697
|
+
headers: { 'Content-Type': 'application/json' },
|
|
698
|
+
body: JSON.stringify({
|
|
699
|
+
model: 'gemini-3-pro-preview',
|
|
700
|
+
messages: [{
|
|
701
|
+
role: 'user',
|
|
702
|
+
content: [
|
|
703
|
+
{ type: 'text', text: 'What logo? One word.' },
|
|
704
|
+
{ type: 'image_url', image_url: { url: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png' } }
|
|
705
|
+
]
|
|
706
|
+
}],
|
|
707
|
+
max_tokens: 100
|
|
708
|
+
})
|
|
709
|
+
});
|
|
710
|
+
if (!response.ok)
|
|
711
|
+
throw new Error(`HTTP ${response.status}`);
|
|
712
|
+
const data = await response.json();
|
|
713
|
+
const text = data.choices?.[0]?.message?.content || '';
|
|
714
|
+
return `"${text.slice(0, 20)}"`;
|
|
715
|
+
}
|
|
716
|
+
async function testClaudeText(url) {
|
|
717
|
+
const response = await fetch(`${url}/v1/chat/completions`, {
|
|
718
|
+
method: 'POST',
|
|
719
|
+
headers: { 'Content-Type': 'application/json' },
|
|
720
|
+
body: JSON.stringify({
|
|
721
|
+
model: 'claude-haiku-4-5@20251001',
|
|
722
|
+
messages: [{ role: 'user', content: 'Say "ok"' }],
|
|
723
|
+
max_tokens: 10
|
|
724
|
+
})
|
|
725
|
+
});
|
|
726
|
+
if (!response.ok)
|
|
727
|
+
throw new Error(`HTTP ${response.status}`);
|
|
728
|
+
const data = await response.json();
|
|
729
|
+
const text = data.choices?.[0]?.message?.content || '';
|
|
730
|
+
return `"${text.slice(0, 20)}"`;
|
|
731
|
+
}
|
|
732
|
+
async function testImagen(url) {
|
|
733
|
+
const response = await fetch(`${url}/v1/images/generations`, {
|
|
734
|
+
method: 'POST',
|
|
735
|
+
headers: { 'Content-Type': 'application/json' },
|
|
736
|
+
body: JSON.stringify({
|
|
737
|
+
model: 'imagen-4.0-generate-001',
|
|
738
|
+
prompt: 'red circle',
|
|
739
|
+
n: 1
|
|
740
|
+
})
|
|
741
|
+
});
|
|
742
|
+
if (!response.ok)
|
|
743
|
+
throw new Error(`HTTP ${response.status}`);
|
|
744
|
+
const data = await response.json();
|
|
745
|
+
const size = data.data?.[0]?.b64_json?.length || 0;
|
|
746
|
+
if (size === 0)
|
|
747
|
+
throw new Error('No image returned');
|
|
748
|
+
return `${Math.round(size / 1024)}KB`;
|
|
749
|
+
}
|
|
750
|
+
async function testGeminiImage(url) {
|
|
751
|
+
const response = await fetch(`${url}/v1/chat/completions`, {
|
|
752
|
+
method: 'POST',
|
|
753
|
+
headers: { 'Content-Type': 'application/json' },
|
|
754
|
+
body: JSON.stringify({
|
|
755
|
+
model: 'gemini-3-pro-image-preview',
|
|
756
|
+
messages: [{ role: 'user', content: 'Draw a blue square' }],
|
|
757
|
+
max_tokens: 8000
|
|
758
|
+
})
|
|
759
|
+
});
|
|
760
|
+
if (!response.ok)
|
|
761
|
+
throw new Error(`HTTP ${response.status}`);
|
|
762
|
+
const data = await response.json();
|
|
763
|
+
const size = data.images?.[0]?.b64_json?.length || 0;
|
|
764
|
+
if (size === 0) {
|
|
765
|
+
const text = data.choices?.[0]?.message?.content || '';
|
|
766
|
+
return `text only: "${text.slice(0, 20)}"`;
|
|
767
|
+
}
|
|
768
|
+
return `${Math.round(size / 1024)}KB`;
|
|
769
|
+
}
|
|
770
|
+
// ============================================================================
|
|
552
771
|
// Commands
|
|
553
772
|
// ============================================================================
|
|
554
773
|
const program = new Command();
|
|
555
774
|
program
|
|
556
775
|
.name('vertex-ai-proxy')
|
|
557
|
-
.description('Proxy server for Vertex AI models with
|
|
776
|
+
.description('Proxy server for Vertex AI models with OpenAI-compatible API')
|
|
558
777
|
.version(VERSION);
|
|
559
778
|
// --- Daemon management commands ---
|
|
560
779
|
program.command('start')
|
|
@@ -583,6 +802,16 @@ program.command('logs')
|
|
|
583
802
|
.option('-f, --follow', 'Follow log output (tail -f style)')
|
|
584
803
|
.option('-n, --lines <number>', 'Number of lines to show', '50')
|
|
585
804
|
.action(showLogs);
|
|
805
|
+
// --- New commands ---
|
|
806
|
+
program.command('update')
|
|
807
|
+
.description('Update vertex-ai-proxy from npm')
|
|
808
|
+
.option('-g, --global', 'Update global installation')
|
|
809
|
+
.action(runUpdate);
|
|
810
|
+
program.command('test')
|
|
811
|
+
.description('Run proxy test suite')
|
|
812
|
+
.option('-p, --port <port>', 'Proxy port')
|
|
813
|
+
.option('-a, --all', 'Run all tests including Claude and Imagen')
|
|
814
|
+
.action(runTest);
|
|
586
815
|
// --- models command ---
|
|
587
816
|
const modelsCmd = program.command('models').description('List and manage models');
|
|
588
817
|
modelsCmd
|
|
@@ -605,6 +834,80 @@ modelsCmd.command('enable <model>')
|
|
|
605
834
|
modelsCmd.command('disable <model>')
|
|
606
835
|
.description('Disable a model')
|
|
607
836
|
.action(disableModel);
|
|
837
|
+
modelsCmd.command("discover")
|
|
838
|
+
.description("Probe Vertex AI to discover available Claude models per region")
|
|
839
|
+
.action(async () => {
|
|
840
|
+
console.log(chalk.blue.bold("\n🔍 Discovering Available Claude Models\n"));
|
|
841
|
+
const config = loadConfig();
|
|
842
|
+
if (!config.project_id) {
|
|
843
|
+
console.log(chalk.red("No project ID. Run: vertex-ai-proxy config set"));
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
const { GoogleAuth } = await import("google-auth-library");
|
|
847
|
+
const auth = new GoogleAuth({ scopes: "https://www.googleapis.com/auth/cloud-platform" });
|
|
848
|
+
const client = await auth.getClient();
|
|
849
|
+
const tokenResponse = await client.getAccessToken();
|
|
850
|
+
const accessToken = tokenResponse.token;
|
|
851
|
+
const REGIONS = ["us-east5", "europe-west1", "asia-southeast1", "asia-east1"];
|
|
852
|
+
const CLAUDE_MODELS = [
|
|
853
|
+
"claude-opus-4-5@20251101",
|
|
854
|
+
"claude-sonnet-4-5@20250929",
|
|
855
|
+
"claude-sonnet-4@20250514",
|
|
856
|
+
"claude-haiku-4-5@20251001",
|
|
857
|
+
"claude-3-haiku@20240307",
|
|
858
|
+
"claude-3-5-sonnet@20240620",
|
|
859
|
+
"claude-3-5-sonnet-v2@20241022",
|
|
860
|
+
];
|
|
861
|
+
const results = {};
|
|
862
|
+
for (const modelId of CLAUDE_MODELS) {
|
|
863
|
+
results[modelId] = [];
|
|
864
|
+
process.stdout.write(chalk.cyan(` ${modelId}:`));
|
|
865
|
+
for (const region of REGIONS) {
|
|
866
|
+
const url = `https://${region}-aiplatform.googleapis.com/v1/projects/${config.project_id}/locations/${region}/publishers/anthropic/models/${modelId}:rawPredict`;
|
|
867
|
+
try {
|
|
868
|
+
const response = await fetch(url, {
|
|
869
|
+
method: "POST",
|
|
870
|
+
headers: {
|
|
871
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
872
|
+
"Content-Type": "application/json"
|
|
873
|
+
},
|
|
874
|
+
body: JSON.stringify({
|
|
875
|
+
anthropic_version: "vertex-2023-10-16",
|
|
876
|
+
max_tokens: 1,
|
|
877
|
+
messages: [{ role: "user", content: "hi" }]
|
|
878
|
+
})
|
|
879
|
+
});
|
|
880
|
+
if (response.ok) {
|
|
881
|
+
results[modelId].push(region);
|
|
882
|
+
process.stdout.write(chalk.green(` ${region}✓`));
|
|
883
|
+
}
|
|
884
|
+
else {
|
|
885
|
+
process.stdout.write(chalk.gray(` ${region}✗`));
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
catch (e) {
|
|
889
|
+
process.stdout.write(chalk.red(` ${region}!`));
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
console.log();
|
|
893
|
+
}
|
|
894
|
+
// Save to cache
|
|
895
|
+
ensureDataDir();
|
|
896
|
+
const cacheFile = path.join(DATA_DIR, "model_regions.json");
|
|
897
|
+
fs.writeFileSync(cacheFile, JSON.stringify({ updated: Date.now(), models: results }, null, 2));
|
|
898
|
+
console.log(chalk.green(`\n✓ Saved to ${cacheFile}`));
|
|
899
|
+
// Summary
|
|
900
|
+
console.log(chalk.yellow.bold("\n📊 Available Models:\n"));
|
|
901
|
+
for (const [modelId, regions] of Object.entries(results)) {
|
|
902
|
+
if (regions.length > 0) {
|
|
903
|
+
console.log(` ${chalk.green("✓")} ${modelId}: ${regions.join(", ")}`);
|
|
904
|
+
}
|
|
905
|
+
else {
|
|
906
|
+
console.log(` ${chalk.red("✗")} ${modelId}: not available`);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
console.log(chalk.gray("\nNote: Enable Claude at https://console.cloud.google.com/vertex-ai/model-garden"));
|
|
910
|
+
});
|
|
608
911
|
modelsCmd.action(() => listModels({}));
|
|
609
912
|
// --- config command ---
|
|
610
913
|
const configCmd = program.command('config').description('Manage configuration');
|
|
@@ -693,6 +996,7 @@ async function listModels(options) {
|
|
|
693
996
|
console.log(` ${chalk.gray(model.name)} - ${model.description}`);
|
|
694
997
|
if (options.all) {
|
|
695
998
|
console.log(` ${chalk.cyan('Context:')} ${(model.contextWindow / 1000).toFixed(0)}K`);
|
|
999
|
+
console.log(` ${chalk.cyan('Max out:')} ${(model.maxTokens / 1000).toFixed(0)}K`);
|
|
696
1000
|
console.log(` ${chalk.cyan('Price:')} ${formatPrice(model.inputPrice, model.outputPrice)} /1M tok`);
|
|
697
1001
|
console.log(` ${chalk.cyan('Regions:')} ${model.regions.join(', ')}`);
|
|
698
1002
|
console.log(` ${chalk.cyan('Caps:')} ${formatCapabilities(model.capabilities)}`);
|
|
@@ -764,7 +1068,7 @@ async function showModelInfo(modelId) {
|
|
|
764
1068
|
console.log(`${chalk.cyan('Description:')} ${model.description}`);
|
|
765
1069
|
console.log();
|
|
766
1070
|
console.log(`${chalk.cyan('Context:')} ${(model.contextWindow / 1000).toFixed(0)}K tokens`);
|
|
767
|
-
console.log(`${chalk.cyan('Max Output:')} ${model.maxTokens} tokens`);
|
|
1071
|
+
console.log(`${chalk.cyan('Max Output:')} ${(model.maxTokens / 1000).toFixed(0)}K tokens`);
|
|
768
1072
|
console.log(`${chalk.cyan('Price:')} $${model.inputPrice} in / $${model.outputPrice} out (per 1M)`);
|
|
769
1073
|
console.log();
|
|
770
1074
|
console.log(`${chalk.cyan('Regions:')} ${model.regions.join(', ')}`);
|
|
@@ -860,25 +1164,25 @@ async function interactiveConfig() {
|
|
|
860
1164
|
console.log(chalk.yellow('\n📦 Select default model:\n'));
|
|
861
1165
|
const modelOptions = [
|
|
862
1166
|
'claude-opus-4-5@20251101 - Most capable ($$)',
|
|
863
|
-
'claude-sonnet-4-5@
|
|
1167
|
+
'claude-sonnet-4-5@20250929 - Balanced ($)',
|
|
864
1168
|
'claude-haiku-4-5@20251001 - Fast & cheap',
|
|
865
|
-
'gemini-
|
|
1169
|
+
'gemini-3-pro-preview - Google\'s best',
|
|
866
1170
|
'gemini-2.5-flash - Fast Gemini'
|
|
867
1171
|
];
|
|
868
1172
|
const modelIds = [
|
|
869
|
-
'claude-opus-4-5@20251101', 'claude-sonnet-4-5@
|
|
870
|
-
'gemini-
|
|
1173
|
+
'claude-opus-4-5@20251101', 'claude-sonnet-4-5@20250929', 'claude-haiku-4-5@20251001',
|
|
1174
|
+
'gemini-3-pro-preview', 'gemini-2.5-flash'
|
|
871
1175
|
];
|
|
872
1176
|
const modelChoice = await promptSelect('', modelOptions);
|
|
873
1177
|
config.default_model = modelIds[modelChoice];
|
|
874
1178
|
// Enable models
|
|
875
1179
|
if (await promptYesNo(chalk.cyan('\nEnable all Claude models?'))) {
|
|
876
|
-
['claude-opus-4-5@20251101', 'claude-sonnet-4-5@
|
|
1180
|
+
['claude-opus-4-5@20251101', 'claude-sonnet-4-5@20250929', 'claude-haiku-4-5@20251001']
|
|
877
1181
|
.forEach(m => { if (!config.enabled_models.includes(m))
|
|
878
1182
|
config.enabled_models.push(m); });
|
|
879
1183
|
}
|
|
880
1184
|
if (await promptYesNo(chalk.cyan('Enable Gemini models?'))) {
|
|
881
|
-
['gemini-
|
|
1185
|
+
['gemini-3-pro-preview', 'gemini-2.5-flash']
|
|
882
1186
|
.forEach(m => { if (!config.enabled_models.includes(m))
|
|
883
1187
|
config.enabled_models.push(m); });
|
|
884
1188
|
}
|
|
@@ -887,20 +1191,20 @@ async function interactiveConfig() {
|
|
|
887
1191
|
config.model_aliases = {
|
|
888
1192
|
...config.model_aliases,
|
|
889
1193
|
opus: 'claude-opus-4-5@20251101',
|
|
890
|
-
sonnet: 'claude-sonnet-4-5@
|
|
1194
|
+
sonnet: 'claude-sonnet-4-5@20250929',
|
|
891
1195
|
haiku: 'claude-haiku-4-5@20251001',
|
|
892
|
-
gemini: 'gemini-
|
|
1196
|
+
gemini: 'gemini-3-pro-preview',
|
|
893
1197
|
'gemini-flash': 'gemini-2.5-flash',
|
|
894
1198
|
'gpt-4': 'claude-opus-4-5@20251101',
|
|
895
|
-
'gpt-4o': 'claude-sonnet-4-5@
|
|
1199
|
+
'gpt-4o': 'claude-sonnet-4-5@20250929',
|
|
896
1200
|
'gpt-4o-mini': 'claude-haiku-4-5@20251001'
|
|
897
1201
|
};
|
|
898
1202
|
}
|
|
899
1203
|
// Fallbacks
|
|
900
1204
|
if (await promptYesNo(chalk.cyan('Set up fallback chains?'))) {
|
|
901
1205
|
config.fallback_chains = {
|
|
902
|
-
'claude-opus-4-5@20251101': ['claude-sonnet-4-5@
|
|
903
|
-
'claude-sonnet-4-5@
|
|
1206
|
+
'claude-opus-4-5@20251101': ['claude-sonnet-4-5@20250929', 'gemini-3-pro-preview'],
|
|
1207
|
+
'claude-sonnet-4-5@20250929': ['claude-haiku-4-5@20251001', 'gemini-2.5-flash'],
|
|
904
1208
|
'claude-haiku-4-5@20251001': ['gemini-2.5-flash-lite']
|
|
905
1209
|
};
|
|
906
1210
|
}
|