voyageai-cli 1.12.0 → 1.12.1
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/package.json +4 -1
- package/src/commands/models.js +3 -3
- package/src/commands/ping.js +32 -2
- package/src/commands/playground.js +1 -1
- package/src/commands/search.js +2 -2
- package/src/commands/store.js +1 -1
- package/src/lib/api.js +18 -14
- package/src/lib/catalog.js +3 -3
- package/test/lib/api.test.js +1 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "voyageai-cli",
|
|
3
|
-
"version": "1.12.
|
|
3
|
+
"version": "1.12.1",
|
|
4
4
|
"description": "CLI for Voyage AI embeddings, reranking, and MongoDB Atlas Vector Search",
|
|
5
5
|
"bin": {
|
|
6
6
|
"vai": "./src/cli.js"
|
|
@@ -40,5 +40,8 @@
|
|
|
40
40
|
"ora": "^9.1.0",
|
|
41
41
|
"picocolors": "^1.1.1",
|
|
42
42
|
"update-notifier": "^7.3.1"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"playwright": "^1.58.1"
|
|
43
46
|
}
|
|
44
47
|
}
|
package/src/commands/models.js
CHANGED
|
@@ -53,7 +53,7 @@ function registerModels(program) {
|
|
|
53
53
|
const legacyModels = models.filter(m => m.legacy);
|
|
54
54
|
|
|
55
55
|
if (opts.type !== 'all') {
|
|
56
|
-
models = models.filter(m => m.type === opts.type);
|
|
56
|
+
models = models.filter(m => opts.type === 'embedding' ? m.type.startsWith('embedding') : m.type === opts.type);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
if (!showLegacy) {
|
|
@@ -84,14 +84,14 @@ function registerModels(program) {
|
|
|
84
84
|
|
|
85
85
|
const formatWideRow = (m) => {
|
|
86
86
|
const name = ui.cyan(m.name);
|
|
87
|
-
const type = m.type
|
|
87
|
+
const type = m.type.startsWith('embedding') ? ui.green(m.type) : ui.yellow(m.type);
|
|
88
88
|
const price = ui.dim(m.price);
|
|
89
89
|
return [name, type, m.context, m.dimensions, price, m.bestFor];
|
|
90
90
|
};
|
|
91
91
|
|
|
92
92
|
const formatCompactRow = (m) => {
|
|
93
93
|
const name = ui.cyan(m.name);
|
|
94
|
-
const type = m.type
|
|
94
|
+
const type = m.type.startsWith('embedding') ? ui.green(m.multimodal ? 'multi' : 'embed') : ui.yellow('rerank');
|
|
95
95
|
const dims = compactDimensions(m.dimensions);
|
|
96
96
|
const price = ui.dim(compactPrice(m.price));
|
|
97
97
|
return [name, type, dims, price, m.shortFor || m.bestFor];
|
package/src/commands/ping.js
CHANGED
|
@@ -13,7 +13,12 @@ function registerPing(program) {
|
|
|
13
13
|
.description('Test connectivity to Voyage AI API (and optionally MongoDB)')
|
|
14
14
|
.option('--json', 'Machine-readable JSON output')
|
|
15
15
|
.option('-q, --quiet', 'Suppress non-essential output')
|
|
16
|
+
.option('--mask', 'Mask sensitive info (cluster hostnames, endpoints) in output. Also enabled by VAI_MASK=1 env var.')
|
|
16
17
|
.action(async (opts) => {
|
|
18
|
+
// Support env var so all recordings are masked without remembering the flag
|
|
19
|
+
if (process.env.VAI_MASK === '1' || process.env.VAI_MASK === 'true') {
|
|
20
|
+
opts.mask = true;
|
|
21
|
+
}
|
|
17
22
|
const results = {};
|
|
18
23
|
|
|
19
24
|
// ── Voyage AI ping ──
|
|
@@ -28,6 +33,31 @@ function registerPing(program) {
|
|
|
28
33
|
const useColor = !opts.json;
|
|
29
34
|
const useSpinner = useColor && !opts.quiet;
|
|
30
35
|
|
|
36
|
+
// Masking helper: "performance.zbcul.mongodb.net" → "perfo*****.mongodb.net"
|
|
37
|
+
const PUBLIC_HOSTS = ['ai.mongodb.com', 'api.voyageai.com'];
|
|
38
|
+
const maskHost = (host) => {
|
|
39
|
+
if (!opts.mask || !host) return host;
|
|
40
|
+
if (PUBLIC_HOSTS.includes(host)) return host;
|
|
41
|
+
const parts = host.split('.');
|
|
42
|
+
if (parts.length >= 3) {
|
|
43
|
+
const name = parts[0];
|
|
44
|
+
const masked = name.slice(0, Math.min(5, name.length)) + '*****';
|
|
45
|
+
return [masked, ...parts.slice(1)].join('.');
|
|
46
|
+
}
|
|
47
|
+
return host.slice(0, 5) + '*****';
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const maskUrl = (url) => {
|
|
51
|
+
if (!opts.mask || !url) return url;
|
|
52
|
+
try {
|
|
53
|
+
const u = new URL(url);
|
|
54
|
+
u.hostname = maskHost(u.hostname);
|
|
55
|
+
return u.toString().replace(/\/$/, '');
|
|
56
|
+
} catch {
|
|
57
|
+
return url;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
31
61
|
const apiBase = getApiBase();
|
|
32
62
|
const model = 'voyage-4-lite';
|
|
33
63
|
const startTime = Date.now();
|
|
@@ -94,7 +124,7 @@ function registerPing(program) {
|
|
|
94
124
|
console.log(`ok ${elapsed}ms`);
|
|
95
125
|
} else {
|
|
96
126
|
console.log(ui.success(`Connected to Voyage AI API ${ui.dim('(' + elapsed + 'ms)')}`));
|
|
97
|
-
console.log(ui.label('Endpoint', apiBase));
|
|
127
|
+
console.log(ui.label('Endpoint', maskUrl(apiBase)));
|
|
98
128
|
console.log(ui.label('Model', model));
|
|
99
129
|
console.log(ui.label('Dimensions', String(dims)));
|
|
100
130
|
console.log(ui.label('Tokens', String(tokens)));
|
|
@@ -145,7 +175,7 @@ function registerPing(program) {
|
|
|
145
175
|
if (!opts.json && !opts.quiet) {
|
|
146
176
|
console.log('');
|
|
147
177
|
console.log(ui.success(`Connected to MongoDB Atlas ${ui.dim('(' + mongoElapsed + 'ms)')}`));
|
|
148
|
-
console.log(ui.label('Cluster', cluster));
|
|
178
|
+
console.log(ui.label('Cluster', maskHost(cluster)));
|
|
149
179
|
}
|
|
150
180
|
|
|
151
181
|
await client.close();
|
|
@@ -84,7 +84,7 @@ function createPlaygroundServer() {
|
|
|
84
84
|
|
|
85
85
|
// API: Models
|
|
86
86
|
if (req.method === 'GET' && req.url === '/api/models') {
|
|
87
|
-
const models = MODEL_CATALOG.filter(m => !m.legacy && !m.local);
|
|
87
|
+
const models = MODEL_CATALOG.filter(m => !m.legacy && !m.local && !m.unreleased);
|
|
88
88
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
89
89
|
res.end(JSON.stringify({ models }));
|
|
90
90
|
return;
|
package/src/commands/search.js
CHANGED
|
@@ -16,8 +16,8 @@ function registerSearch(program) {
|
|
|
16
16
|
.requiredOption('--query <text>', 'Search query text')
|
|
17
17
|
.requiredOption('--db <database>', 'Database name')
|
|
18
18
|
.requiredOption('--collection <name>', 'Collection name')
|
|
19
|
-
.
|
|
20
|
-
.
|
|
19
|
+
.option('--index <name>', 'Vector search index name', 'vector_index')
|
|
20
|
+
.option('--field <name>', 'Embedding field name', 'embedding')
|
|
21
21
|
.option('-m, --model <model>', 'Embedding model', getDefaultModel())
|
|
22
22
|
.option('--input-type <type>', 'Input type for query embedding', 'query')
|
|
23
23
|
.option('-d, --dimensions <n>', 'Output dimensions', (v) => parseInt(v, 10))
|
package/src/commands/store.js
CHANGED
|
@@ -17,7 +17,7 @@ function registerStore(program) {
|
|
|
17
17
|
.description('Embed text and store in MongoDB Atlas')
|
|
18
18
|
.requiredOption('--db <database>', 'Database name')
|
|
19
19
|
.requiredOption('--collection <name>', 'Collection name')
|
|
20
|
-
.
|
|
20
|
+
.option('--field <name>', 'Embedding field name', 'embedding')
|
|
21
21
|
.option('--text <text>', 'Text to embed and store')
|
|
22
22
|
.option('-f, --file <path>', 'File to embed and store (text file or .jsonl for batch mode)')
|
|
23
23
|
.option('-m, --model <model>', 'Embedding model', getDefaultModel())
|
package/src/lib/api.js
CHANGED
|
@@ -96,25 +96,29 @@ async function apiRequest(endpoint, body) {
|
|
|
96
96
|
} catch {
|
|
97
97
|
errorDetail = await response.text();
|
|
98
98
|
}
|
|
99
|
-
|
|
99
|
+
const errMsg = `API Error (${response.status}): ${errorDetail}`;
|
|
100
100
|
|
|
101
101
|
// Help users diagnose endpoint mismatch
|
|
102
|
+
let hint = '';
|
|
102
103
|
if (response.status === 403 && base === ATLAS_API_BASE) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
console.error(' vai config set base-url https://api.voyageai.com/v1/');
|
|
108
|
-
console.error('');
|
|
109
|
-
console.error('Or set VOYAGE_API_BASE=https://api.voyageai.com/v1/ in your environment.');
|
|
104
|
+
hint = '\n\nHint: 403 on ai.mongodb.com often means your key is for the Voyage AI' +
|
|
105
|
+
'\nplatform, not MongoDB Atlas. Try switching the base URL:' +
|
|
106
|
+
'\n\n vai config set base-url https://api.voyageai.com/v1/' +
|
|
107
|
+
'\n\nOr set VOYAGE_API_BASE=https://api.voyageai.com/v1/ in your environment.';
|
|
110
108
|
} else if (response.status === 401 && base === VOYAGE_API_BASE) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
console.error('');
|
|
115
|
-
console.error(' vai config set base-url https://ai.mongodb.com/v1/');
|
|
109
|
+
hint = '\n\nHint: 401 on api.voyageai.com may mean your key is an Atlas AI key.' +
|
|
110
|
+
'\nTry switching back:' +
|
|
111
|
+
'\n\n vai config set base-url https://ai.mongodb.com/v1/';
|
|
116
112
|
}
|
|
117
|
-
|
|
113
|
+
|
|
114
|
+
// Log the error + hint to stderr for CLI users
|
|
115
|
+
console.error(errMsg);
|
|
116
|
+
if (hint) console.error(hint);
|
|
117
|
+
|
|
118
|
+
// Throw instead of process.exit so callers (like playground) can catch gracefully
|
|
119
|
+
const err = new Error(errMsg);
|
|
120
|
+
err.statusCode = response.status;
|
|
121
|
+
throw err;
|
|
118
122
|
}
|
|
119
123
|
|
|
120
124
|
return response.json();
|
package/src/lib/catalog.js
CHANGED
|
@@ -32,8 +32,8 @@ const MODEL_CATALOG = [
|
|
|
32
32
|
{ name: 'voyage-code-3', type: 'embedding', context: '32K', dimensions: '1024 (default), 256, 512, 2048', price: '$0.18/1M tokens', bestFor: 'Code retrieval', shortFor: 'Code' },
|
|
33
33
|
{ name: 'voyage-finance-2', type: 'embedding', context: '32K', dimensions: '1024', price: '$0.12/1M tokens', bestFor: 'Finance', shortFor: 'Finance' },
|
|
34
34
|
{ name: 'voyage-law-2', type: 'embedding', context: '16K', dimensions: '1024', price: '$0.12/1M tokens', bestFor: 'Legal', shortFor: 'Legal' },
|
|
35
|
-
{ name: 'voyage-context-3', type: 'embedding', context: '32K', dimensions: '1024 (default), 256, 512, 2048', price: '$0.18/1M tokens', bestFor: 'Contextualized chunks', shortFor: 'Context chunks' },
|
|
36
|
-
{ name: 'voyage-multimodal-3.5', type: 'embedding', context: '32K', dimensions: '1024 (default), 256, 512, 2048', price: '$0.12/M + $0.60/B px', bestFor: 'Text + images + video', shortFor: 'Multimodal' },
|
|
35
|
+
{ name: 'voyage-context-3', type: 'embedding', context: '32K', dimensions: '1024 (default), 256, 512, 2048', price: '$0.18/1M tokens', bestFor: 'Contextualized chunks', shortFor: 'Context chunks', unreleased: true },
|
|
36
|
+
{ name: 'voyage-multimodal-3.5', type: 'embedding-multimodal', context: '32K', dimensions: '1024 (default), 256, 512, 2048', price: '$0.12/M + $0.60/B px', bestFor: 'Text + images + video', shortFor: 'Multimodal', multimodal: true },
|
|
37
37
|
{ name: 'rerank-2.5', type: 'reranking', context: '32K', dimensions: '—', price: '$0.05/1M tokens', bestFor: 'Best quality reranking', shortFor: 'Best reranker' },
|
|
38
38
|
{ name: 'rerank-2.5-lite', type: 'reranking', context: '32K', dimensions: '—', price: '$0.02/1M tokens', bestFor: 'Fast reranking', shortFor: 'Fast reranker' },
|
|
39
39
|
{ name: 'voyage-4-nano', type: 'embedding', context: '32K', dimensions: '512 (default), 128, 256', price: 'Open-weight', bestFor: 'Open-weight / edge', shortFor: 'Open / edge', local: true },
|
|
@@ -42,7 +42,7 @@ const MODEL_CATALOG = [
|
|
|
42
42
|
{ name: 'voyage-3.5', type: 'embedding', context: '32K', dimensions: '1024 (default), 256, 512, 2048', price: '$0.06/1M tokens', bestFor: 'Previous gen balanced', shortFor: 'Previous gen balanced', legacy: true },
|
|
43
43
|
{ name: 'voyage-3.5-lite', type: 'embedding', context: '32K', dimensions: '1024 (default), 256, 512, 2048', price: '$0.02/1M tokens', bestFor: 'Previous gen budget', shortFor: 'Previous gen budget', legacy: true },
|
|
44
44
|
{ name: 'voyage-code-2', type: 'embedding', context: '16K', dimensions: '1536', price: '$0.12/1M tokens', bestFor: 'Legacy code', shortFor: 'Legacy code', legacy: true },
|
|
45
|
-
{ name: 'voyage-multimodal-3', type: 'embedding', context: '32K', dimensions: '1024', price: '$0.12/1M tokens', bestFor: 'Legacy multimodal', shortFor: 'Legacy multimodal', legacy: true },
|
|
45
|
+
{ name: 'voyage-multimodal-3', type: 'embedding-multimodal', context: '32K', dimensions: '1024', price: '$0.12/1M tokens', bestFor: 'Legacy multimodal', shortFor: 'Legacy multimodal', legacy: true, multimodal: true },
|
|
46
46
|
{ name: 'rerank-2', type: 'reranking', context: '16K', dimensions: '—', price: '$0.05/1M tokens', bestFor: 'Legacy reranker', shortFor: 'Legacy reranker', legacy: true },
|
|
47
47
|
{ name: 'rerank-2-lite', type: 'reranking', context: '8K', dimensions: '—', price: '$0.02/1M tokens', bestFor: 'Legacy fast reranker', shortFor: 'Legacy fast reranker', legacy: true },
|
|
48
48
|
];
|
package/test/lib/api.test.js
CHANGED
|
@@ -97,9 +97,8 @@ describe('api', () => {
|
|
|
97
97
|
|
|
98
98
|
await assert.rejects(
|
|
99
99
|
() => apiRequest('/embeddings', { input: ['test'], model: 'voyage-4-lite' }),
|
|
100
|
-
/
|
|
100
|
+
/API Error \(400\)/
|
|
101
101
|
);
|
|
102
|
-
assert.equal(exitCode, 1);
|
|
103
102
|
});
|
|
104
103
|
|
|
105
104
|
it('retries on 429', async () => {
|