voyageai-cli 1.12.1 → 1.13.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 +3 -3
- package/demo-readme.gif +0 -0
- package/package.json +1 -1
- package/.github/workflows/ci.yml +0 -22
- package/CONTRIBUTING.md +0 -81
- package/demo.gif +0 -0
- package/demo.tape +0 -39
- package/scripts/record-demo.sh +0 -63
- package/test/commands/about.test.js +0 -23
- package/test/commands/benchmark.test.js +0 -319
- package/test/commands/completions.test.js +0 -166
- package/test/commands/config.test.js +0 -35
- package/test/commands/demo.test.js +0 -46
- package/test/commands/embed.test.js +0 -42
- package/test/commands/explain.test.js +0 -207
- package/test/commands/ingest.test.js +0 -261
- package/test/commands/models.test.js +0 -132
- package/test/commands/ping.test.js +0 -172
- package/test/commands/playground.test.js +0 -137
- package/test/commands/rerank.test.js +0 -32
- package/test/commands/similarity.test.js +0 -79
- package/test/commands/store.test.js +0 -26
- package/test/fixtures/sample.csv +0 -6
- package/test/fixtures/sample.json +0 -7
- package/test/fixtures/sample.jsonl +0 -5
- package/test/fixtures/sample.txt +0 -5
- package/test/lib/api.test.js +0 -133
- package/test/lib/banner.test.js +0 -44
- package/test/lib/catalog.test.js +0 -99
- package/test/lib/config.test.js +0 -124
- package/test/lib/explanations.test.js +0 -141
- package/test/lib/format.test.js +0 -75
- package/test/lib/input.test.js +0 -48
- package/test/lib/math.test.js +0 -43
- package/test/lib/ui.test.js +0 -79
- package/voyageai-cli-playground.png +0 -0
- package/voyageai-cli.png +0 -0
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { describe, it, after } = require('node:test');
|
|
4
|
-
const assert = require('node:assert/strict');
|
|
5
|
-
const http = require('http');
|
|
6
|
-
const { Command } = require('commander');
|
|
7
|
-
const { registerPlayground, createPlaygroundServer } = require('../../src/commands/playground');
|
|
8
|
-
|
|
9
|
-
describe('playground command', () => {
|
|
10
|
-
let server;
|
|
11
|
-
let port;
|
|
12
|
-
|
|
13
|
-
// Start server once for all tests
|
|
14
|
-
const serverReady = new Promise((resolve) => {
|
|
15
|
-
server = createPlaygroundServer();
|
|
16
|
-
server.listen(0, () => {
|
|
17
|
-
port = server.address().port;
|
|
18
|
-
resolve();
|
|
19
|
-
});
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
after(() => {
|
|
23
|
-
return new Promise((resolve) => {
|
|
24
|
-
if (server) server.close(resolve);
|
|
25
|
-
else resolve();
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('registers correctly on a program', () => {
|
|
30
|
-
const program = new Command();
|
|
31
|
-
registerPlayground(program);
|
|
32
|
-
const cmd = program.commands.find(c => c.name() === 'playground');
|
|
33
|
-
assert.ok(cmd, 'playground command should be registered');
|
|
34
|
-
assert.ok(cmd.description().includes('playground') || cmd.description().includes('Playground') || cmd.description().includes('web'),
|
|
35
|
-
'should have a relevant description');
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('serves HTML on GET /', async () => {
|
|
39
|
-
await serverReady;
|
|
40
|
-
const body = await httpGet(`http://localhost:${port}/`);
|
|
41
|
-
assert.ok(body.includes('<!DOCTYPE html>'), 'should return HTML');
|
|
42
|
-
assert.ok(body.includes('Voyage AI Playground'), 'should include playground title');
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('returns JSON from GET /api/models', async () => {
|
|
46
|
-
await serverReady;
|
|
47
|
-
const body = await httpGet(`http://localhost:${port}/api/models`);
|
|
48
|
-
const data = JSON.parse(body);
|
|
49
|
-
assert.ok(Array.isArray(data.models), 'models should be an array');
|
|
50
|
-
assert.ok(data.models.length > 0, 'should have at least one model');
|
|
51
|
-
assert.ok(data.models.every(m => !m.legacy), 'should not include legacy models');
|
|
52
|
-
// Check a known model exists
|
|
53
|
-
const names = data.models.map(m => m.name);
|
|
54
|
-
assert.ok(names.includes('voyage-4-large'), 'should include voyage-4-large');
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('returns config with hasKey boolean from GET /api/config', async () => {
|
|
58
|
-
await serverReady;
|
|
59
|
-
const body = await httpGet(`http://localhost:${port}/api/config`);
|
|
60
|
-
const data = JSON.parse(body);
|
|
61
|
-
assert.ok(typeof data.hasKey === 'boolean', 'hasKey should be a boolean');
|
|
62
|
-
assert.ok(typeof data.baseUrl === 'string', 'baseUrl should be a string');
|
|
63
|
-
// Should never expose the actual key
|
|
64
|
-
assert.ok(!data.apiKey, 'should not expose apiKey');
|
|
65
|
-
assert.ok(!data.key, 'should not expose key');
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('returns 404 for unknown routes', async () => {
|
|
69
|
-
await serverReady;
|
|
70
|
-
const { statusCode } = await httpGetFull(`http://localhost:${port}/nonexistent`);
|
|
71
|
-
assert.equal(statusCode, 404);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('returns 400 for POST /api/embed with invalid body', async () => {
|
|
75
|
-
await serverReady;
|
|
76
|
-
const { statusCode, body } = await httpPostFull(`http://localhost:${port}/api/embed`, { texts: 'not-an-array' });
|
|
77
|
-
assert.equal(statusCode, 400);
|
|
78
|
-
const data = JSON.parse(body);
|
|
79
|
-
assert.ok(data.error, 'should return an error message');
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Simple HTTP GET that returns the body string.
|
|
85
|
-
*/
|
|
86
|
-
function httpGet(url) {
|
|
87
|
-
return new Promise((resolve, reject) => {
|
|
88
|
-
http.get(url, (res) => {
|
|
89
|
-
const chunks = [];
|
|
90
|
-
res.on('data', c => chunks.push(c));
|
|
91
|
-
res.on('end', () => resolve(Buffer.concat(chunks).toString()));
|
|
92
|
-
res.on('error', reject);
|
|
93
|
-
}).on('error', reject);
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* HTTP GET returning { statusCode, body }.
|
|
99
|
-
*/
|
|
100
|
-
function httpGetFull(url) {
|
|
101
|
-
return new Promise((resolve, reject) => {
|
|
102
|
-
http.get(url, (res) => {
|
|
103
|
-
const chunks = [];
|
|
104
|
-
res.on('data', c => chunks.push(c));
|
|
105
|
-
res.on('end', () => resolve({ statusCode: res.statusCode, body: Buffer.concat(chunks).toString() }));
|
|
106
|
-
res.on('error', reject);
|
|
107
|
-
}).on('error', reject);
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* HTTP POST returning { statusCode, body }.
|
|
113
|
-
*/
|
|
114
|
-
function httpPostFull(url, data) {
|
|
115
|
-
return new Promise((resolve, reject) => {
|
|
116
|
-
const payload = JSON.stringify(data);
|
|
117
|
-
const parsed = new URL(url);
|
|
118
|
-
const req = http.request({
|
|
119
|
-
hostname: parsed.hostname,
|
|
120
|
-
port: parsed.port,
|
|
121
|
-
path: parsed.pathname,
|
|
122
|
-
method: 'POST',
|
|
123
|
-
headers: {
|
|
124
|
-
'Content-Type': 'application/json',
|
|
125
|
-
'Content-Length': Buffer.byteLength(payload),
|
|
126
|
-
},
|
|
127
|
-
}, (res) => {
|
|
128
|
-
const chunks = [];
|
|
129
|
-
res.on('data', c => chunks.push(c));
|
|
130
|
-
res.on('end', () => resolve({ statusCode: res.statusCode, body: Buffer.concat(chunks).toString() }));
|
|
131
|
-
res.on('error', reject);
|
|
132
|
-
});
|
|
133
|
-
req.on('error', reject);
|
|
134
|
-
req.write(payload);
|
|
135
|
-
req.end();
|
|
136
|
-
});
|
|
137
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { describe, it } = require('node:test');
|
|
4
|
-
const assert = require('node:assert/strict');
|
|
5
|
-
const { Command } = require('commander');
|
|
6
|
-
const { registerRerank } = require('../../src/commands/rerank');
|
|
7
|
-
|
|
8
|
-
describe('rerank command', () => {
|
|
9
|
-
it('registers correctly on a program', () => {
|
|
10
|
-
const program = new Command();
|
|
11
|
-
registerRerank(program);
|
|
12
|
-
const rerankCmd = program.commands.find(c => c.name() === 'rerank');
|
|
13
|
-
assert.ok(rerankCmd, 'rerank command should be registered');
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('has --truncation flag', () => {
|
|
17
|
-
const program = new Command();
|
|
18
|
-
registerRerank(program);
|
|
19
|
-
const rerankCmd = program.commands.find(c => c.name() === 'rerank');
|
|
20
|
-
const optionNames = rerankCmd.options.map(o => o.long);
|
|
21
|
-
assert.ok(optionNames.includes('--truncation'), 'should have --truncation option');
|
|
22
|
-
assert.ok(optionNames.includes('--no-truncation'), 'should have --no-truncation option');
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('has --return-documents flag', () => {
|
|
26
|
-
const program = new Command();
|
|
27
|
-
registerRerank(program);
|
|
28
|
-
const rerankCmd = program.commands.find(c => c.name() === 'rerank');
|
|
29
|
-
const optionNames = rerankCmd.options.map(o => o.long);
|
|
30
|
-
assert.ok(optionNames.includes('--return-documents'), 'should have --return-documents option');
|
|
31
|
-
});
|
|
32
|
-
});
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { describe, it, beforeEach, afterEach, mock } = require('node:test');
|
|
4
|
-
const assert = require('node:assert/strict');
|
|
5
|
-
const { Command } = require('commander');
|
|
6
|
-
const { registerSimilarity } = require('../../src/commands/similarity');
|
|
7
|
-
|
|
8
|
-
describe('similarity command', () => {
|
|
9
|
-
let originalLog, originalError, originalExit;
|
|
10
|
-
let output, errors;
|
|
11
|
-
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
originalLog = console.log;
|
|
14
|
-
originalError = console.error;
|
|
15
|
-
originalExit = process.exit;
|
|
16
|
-
output = [];
|
|
17
|
-
errors = [];
|
|
18
|
-
console.log = (...args) => output.push(args.join(' '));
|
|
19
|
-
console.error = (...args) => errors.push(args.join(' '));
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
afterEach(() => {
|
|
23
|
-
console.log = originalLog;
|
|
24
|
-
console.error = originalError;
|
|
25
|
-
process.exit = originalExit;
|
|
26
|
-
mock.restoreAll();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('registers correctly on a program', () => {
|
|
30
|
-
const program = new Command();
|
|
31
|
-
registerSimilarity(program);
|
|
32
|
-
const cmd = program.commands.find(c => c.name() === 'similarity');
|
|
33
|
-
assert.ok(cmd, 'similarity command should be registered');
|
|
34
|
-
assert.ok(cmd.description().includes('similarity'), 'should have a description about similarity');
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('requires at least two inputs', async () => {
|
|
38
|
-
let exitCode;
|
|
39
|
-
process.exit = (code) => { exitCode = code; throw new Error('exit'); };
|
|
40
|
-
|
|
41
|
-
const program = new Command();
|
|
42
|
-
program.exitOverride();
|
|
43
|
-
registerSimilarity(program);
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
await program.parseAsync(['node', 'test', 'similarity', 'only-one-text']);
|
|
47
|
-
} catch {
|
|
48
|
-
// expected
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
assert.equal(exitCode, 1, 'should exit with code 1');
|
|
52
|
-
const combined = errors.join('\n');
|
|
53
|
-
assert.ok(combined.includes('two texts') || combined.includes('second text'), 'should mention needing two texts');
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('accepts --against flag with multiple values', () => {
|
|
57
|
-
const program = new Command();
|
|
58
|
-
registerSimilarity(program);
|
|
59
|
-
const cmd = program.commands.find(c => c.name() === 'similarity');
|
|
60
|
-
const againstOpt = cmd.options.find(o => o.long === '--against');
|
|
61
|
-
assert.ok(againstOpt, '--against option should exist');
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('accepts --json flag', () => {
|
|
65
|
-
const program = new Command();
|
|
66
|
-
registerSimilarity(program);
|
|
67
|
-
const cmd = program.commands.find(c => c.name() === 'similarity');
|
|
68
|
-
const jsonOpt = cmd.options.find(o => o.long === '--json');
|
|
69
|
-
assert.ok(jsonOpt, '--json option should exist');
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('cosineSimilarity integration — identical texts would score 1.0', () => {
|
|
73
|
-
// Direct test of math module integration
|
|
74
|
-
const { cosineSimilarity } = require('../../src/lib/math');
|
|
75
|
-
const vec = [0.1, 0.2, 0.3, 0.4];
|
|
76
|
-
const result = cosineSimilarity(vec, vec);
|
|
77
|
-
assert.ok(Math.abs(result - 1.0) < 1e-10);
|
|
78
|
-
});
|
|
79
|
-
});
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { describe, it } = require('node:test');
|
|
4
|
-
const assert = require('node:assert/strict');
|
|
5
|
-
const { Command } = require('commander');
|
|
6
|
-
const { registerStore } = require('../../src/commands/store');
|
|
7
|
-
|
|
8
|
-
describe('store command', () => {
|
|
9
|
-
it('registers correctly on a program', () => {
|
|
10
|
-
const program = new Command();
|
|
11
|
-
registerStore(program);
|
|
12
|
-
const storeCmd = program.commands.find(c => c.name() === 'store');
|
|
13
|
-
assert.ok(storeCmd, 'store command should be registered');
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('has --input-type flag defaulting to document', () => {
|
|
17
|
-
const program = new Command();
|
|
18
|
-
registerStore(program);
|
|
19
|
-
const storeCmd = program.commands.find(c => c.name() === 'store');
|
|
20
|
-
const optionNames = storeCmd.options.map(o => o.long);
|
|
21
|
-
assert.ok(optionNames.includes('--input-type'), 'should have --input-type option');
|
|
22
|
-
// Check the default value
|
|
23
|
-
const inputTypeOpt = storeCmd.options.find(o => o.long === '--input-type');
|
|
24
|
-
assert.equal(inputTypeOpt.defaultValue, 'document', '--input-type should default to document');
|
|
25
|
-
});
|
|
26
|
-
});
|
package/test/fixtures/sample.csv
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
title,content,category
|
|
2
|
-
"MongoDB Overview","MongoDB is a document database designed for ease of development and scaling.",databases
|
|
3
|
-
"Vector Search","Atlas Vector Search enables semantic search over your data using embeddings.",search
|
|
4
|
-
"Voyage AI","Voyage AI provides state-of-the-art embedding models for retrieval.",ai
|
|
5
|
-
"RAG Explained","RAG combines retrieval with generation for more accurate AI responses.",ai
|
|
6
|
-
"Node.js Intro","Node.js is a JavaScript runtime built on Chrome's V8 engine.",runtime
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
[
|
|
2
|
-
{"text": "MongoDB is a document database designed for ease of development and scaling.", "source": "docs"},
|
|
3
|
-
{"text": "Atlas Vector Search enables semantic search over your data using embeddings.", "source": "atlas"},
|
|
4
|
-
{"text": "Voyage AI provides state-of-the-art embedding models for retrieval.", "source": "voyage"},
|
|
5
|
-
{"text": "RAG combines retrieval with generation for more accurate AI responses.", "source": "ai"},
|
|
6
|
-
{"text": "Node.js is a JavaScript runtime built on Chrome's V8 engine.", "source": "nodejs"}
|
|
7
|
-
]
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
{"text": "MongoDB is a document database designed for ease of development and scaling.", "source": "docs"}
|
|
2
|
-
{"text": "Atlas Vector Search enables semantic search over your data using embeddings.", "source": "atlas"}
|
|
3
|
-
{"text": "Voyage AI provides state-of-the-art embedding models for retrieval.", "source": "voyage"}
|
|
4
|
-
{"text": "RAG combines retrieval with generation for more accurate AI responses.", "source": "ai"}
|
|
5
|
-
{"text": "Node.js is a JavaScript runtime built on Chrome's V8 engine.", "source": "nodejs"}
|
package/test/fixtures/sample.txt
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
MongoDB is a document database designed for ease of development and scaling.
|
|
2
|
-
Atlas Vector Search enables semantic search over your data using embeddings.
|
|
3
|
-
Voyage AI provides state-of-the-art embedding models for retrieval.
|
|
4
|
-
RAG combines retrieval with generation for more accurate AI responses.
|
|
5
|
-
Node.js is a JavaScript runtime built on Chrome's V8 engine.
|
package/test/lib/api.test.js
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { describe, it, beforeEach, afterEach, mock } = require('node:test');
|
|
4
|
-
const assert = require('node:assert/strict');
|
|
5
|
-
|
|
6
|
-
describe('api', () => {
|
|
7
|
-
let originalKey;
|
|
8
|
-
let originalExit;
|
|
9
|
-
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
originalKey = process.env.VOYAGE_API_KEY;
|
|
12
|
-
originalExit = process.exit;
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
afterEach(() => {
|
|
16
|
-
if (originalKey !== undefined) {
|
|
17
|
-
process.env.VOYAGE_API_KEY = originalKey;
|
|
18
|
-
} else {
|
|
19
|
-
delete process.env.VOYAGE_API_KEY;
|
|
20
|
-
}
|
|
21
|
-
process.exit = originalExit;
|
|
22
|
-
mock.restoreAll();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
describe('requireApiKey', () => {
|
|
26
|
-
it('throws/exits when VOYAGE_API_KEY is not set', () => {
|
|
27
|
-
delete process.env.VOYAGE_API_KEY;
|
|
28
|
-
// Re-require to get fresh modules (clear config cache too)
|
|
29
|
-
delete require.cache[require.resolve('../../src/lib/api')];
|
|
30
|
-
delete require.cache[require.resolve('../../src/lib/config')];
|
|
31
|
-
const config = require('../../src/lib/config');
|
|
32
|
-
const originalGetConfigValue = config.getConfigValue;
|
|
33
|
-
config.getConfigValue = () => undefined;
|
|
34
|
-
|
|
35
|
-
const { requireApiKey } = require('../../src/lib/api');
|
|
36
|
-
|
|
37
|
-
let exitCode = null;
|
|
38
|
-
process.exit = (code) => {
|
|
39
|
-
exitCode = code;
|
|
40
|
-
throw new Error('process.exit called');
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
assert.throws(() => requireApiKey(), /process\.exit called/);
|
|
45
|
-
assert.equal(exitCode, 1);
|
|
46
|
-
} finally {
|
|
47
|
-
config.getConfigValue = originalGetConfigValue;
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('returns key when VOYAGE_API_KEY is set', () => {
|
|
52
|
-
process.env.VOYAGE_API_KEY = 'test-key-123';
|
|
53
|
-
delete require.cache[require.resolve('../../src/lib/api')];
|
|
54
|
-
const { requireApiKey } = require('../../src/lib/api');
|
|
55
|
-
|
|
56
|
-
const key = requireApiKey();
|
|
57
|
-
assert.equal(key, 'test-key-123');
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
describe('apiRequest', () => {
|
|
62
|
-
it('returns parsed JSON on success', async () => {
|
|
63
|
-
process.env.VOYAGE_API_KEY = 'test-key';
|
|
64
|
-
delete require.cache[require.resolve('../../src/lib/api')];
|
|
65
|
-
const { apiRequest } = require('../../src/lib/api');
|
|
66
|
-
|
|
67
|
-
const mockResponse = {
|
|
68
|
-
ok: true,
|
|
69
|
-
status: 200,
|
|
70
|
-
json: async () => ({ data: [{ embedding: [1, 2, 3] }] }),
|
|
71
|
-
headers: new Map(),
|
|
72
|
-
};
|
|
73
|
-
mock.method(global, 'fetch', async () => mockResponse);
|
|
74
|
-
|
|
75
|
-
const result = await apiRequest('/embeddings', { input: ['test'], model: 'voyage-4-lite' });
|
|
76
|
-
assert.deepEqual(result, { data: [{ embedding: [1, 2, 3] }] });
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('exits on non-200 response', async () => {
|
|
80
|
-
process.env.VOYAGE_API_KEY = 'test-key';
|
|
81
|
-
delete require.cache[require.resolve('../../src/lib/api')];
|
|
82
|
-
const { apiRequest } = require('../../src/lib/api');
|
|
83
|
-
|
|
84
|
-
let exitCode = null;
|
|
85
|
-
process.exit = (code) => {
|
|
86
|
-
exitCode = code;
|
|
87
|
-
throw new Error('process.exit called');
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
const mockResponse = {
|
|
91
|
-
ok: false,
|
|
92
|
-
status: 400,
|
|
93
|
-
json: async () => ({ detail: 'Bad request' }),
|
|
94
|
-
headers: new Map(),
|
|
95
|
-
};
|
|
96
|
-
mock.method(global, 'fetch', async () => mockResponse);
|
|
97
|
-
|
|
98
|
-
await assert.rejects(
|
|
99
|
-
() => apiRequest('/embeddings', { input: ['test'], model: 'voyage-4-lite' }),
|
|
100
|
-
/API Error \(400\)/
|
|
101
|
-
);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('retries on 429', async () => {
|
|
105
|
-
process.env.VOYAGE_API_KEY = 'test-key';
|
|
106
|
-
delete require.cache[require.resolve('../../src/lib/api')];
|
|
107
|
-
const { apiRequest } = require('../../src/lib/api');
|
|
108
|
-
|
|
109
|
-
let callCount = 0;
|
|
110
|
-
mock.method(global, 'fetch', async () => {
|
|
111
|
-
callCount++;
|
|
112
|
-
if (callCount === 1) {
|
|
113
|
-
return {
|
|
114
|
-
ok: false,
|
|
115
|
-
status: 429,
|
|
116
|
-
headers: { get: () => '0' },
|
|
117
|
-
json: async () => ({ detail: 'Rate limited' }),
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
return {
|
|
121
|
-
ok: true,
|
|
122
|
-
status: 200,
|
|
123
|
-
json: async () => ({ data: 'success' }),
|
|
124
|
-
headers: new Map(),
|
|
125
|
-
};
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
const result = await apiRequest('/embeddings', { input: ['test'], model: 'voyage-4-lite' });
|
|
129
|
-
assert.deepEqual(result, { data: 'success' });
|
|
130
|
-
assert.equal(callCount, 2, 'Should have retried once');
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
});
|
package/test/lib/banner.test.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { describe, it, beforeEach, afterEach } = require('node:test');
|
|
4
|
-
const assert = require('node:assert/strict');
|
|
5
|
-
const { showBanner, showQuickStart, getVersion } = require('../../src/lib/banner');
|
|
6
|
-
|
|
7
|
-
describe('banner', () => {
|
|
8
|
-
let originalLog;
|
|
9
|
-
let output;
|
|
10
|
-
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
originalLog = console.log;
|
|
13
|
-
output = [];
|
|
14
|
-
console.log = (...args) => output.push(args.join(' '));
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
afterEach(() => {
|
|
18
|
-
console.log = originalLog;
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('getVersion returns a semver string', () => {
|
|
22
|
-
const version = getVersion();
|
|
23
|
-
assert.ok(/^\d+\.\d+\.\d+/.test(version), `Expected semver, got: ${version}`);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('showBanner prints box with vai and Voyage AI CLI', () => {
|
|
27
|
-
showBanner();
|
|
28
|
-
const combined = output.join('\n');
|
|
29
|
-
assert.ok(combined.includes('vai'), 'Should include "vai"');
|
|
30
|
-
assert.ok(combined.includes('Voyage AI CLI'), 'Should include "Voyage AI CLI"');
|
|
31
|
-
assert.ok(combined.includes('╭'), 'Should include top border');
|
|
32
|
-
assert.ok(combined.includes('╰'), 'Should include bottom border');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('showQuickStart prints quick start commands', () => {
|
|
36
|
-
showQuickStart();
|
|
37
|
-
const combined = output.join('\n');
|
|
38
|
-
assert.ok(combined.includes('Quick start'), 'Should include header');
|
|
39
|
-
assert.ok(combined.includes('vai ping'), 'Should include ping');
|
|
40
|
-
assert.ok(combined.includes('vai embed'), 'Should include embed');
|
|
41
|
-
assert.ok(combined.includes('vai models'), 'Should include models');
|
|
42
|
-
assert.ok(combined.includes('vai demo'), 'Should include demo');
|
|
43
|
-
});
|
|
44
|
-
});
|
package/test/lib/catalog.test.js
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { describe, it } = require('node:test');
|
|
4
|
-
const assert = require('node:assert/strict');
|
|
5
|
-
const {
|
|
6
|
-
MODEL_CATALOG,
|
|
7
|
-
DEFAULT_EMBED_MODEL,
|
|
8
|
-
DEFAULT_RERANK_MODEL,
|
|
9
|
-
DEFAULT_DIMENSIONS,
|
|
10
|
-
} = require('../../src/lib/catalog');
|
|
11
|
-
|
|
12
|
-
describe('catalog', () => {
|
|
13
|
-
it('MODEL_CATALOG is a non-empty array', () => {
|
|
14
|
-
assert.ok(Array.isArray(MODEL_CATALOG));
|
|
15
|
-
assert.ok(MODEL_CATALOG.length > 0);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('contains expected models', () => {
|
|
19
|
-
const names = MODEL_CATALOG.map(m => m.name);
|
|
20
|
-
assert.ok(names.includes('voyage-4-large'));
|
|
21
|
-
assert.ok(names.includes('voyage-4'));
|
|
22
|
-
assert.ok(names.includes('voyage-4-lite'));
|
|
23
|
-
assert.ok(names.includes('rerank-2.5'));
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('DEFAULT_EMBED_MODEL is a valid embedding model', () => {
|
|
27
|
-
assert.equal(DEFAULT_EMBED_MODEL, 'voyage-4-large');
|
|
28
|
-
const model = MODEL_CATALOG.find(m => m.name === DEFAULT_EMBED_MODEL);
|
|
29
|
-
assert.ok(model);
|
|
30
|
-
assert.equal(model.type, 'embedding');
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('DEFAULT_RERANK_MODEL is a valid reranking model', () => {
|
|
34
|
-
assert.equal(DEFAULT_RERANK_MODEL, 'rerank-2.5');
|
|
35
|
-
const model = MODEL_CATALOG.find(m => m.name === DEFAULT_RERANK_MODEL);
|
|
36
|
-
assert.ok(model);
|
|
37
|
-
assert.equal(model.type, 'reranking');
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('DEFAULT_DIMENSIONS is 1024', () => {
|
|
41
|
-
assert.equal(DEFAULT_DIMENSIONS, 1024);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('all models have required fields', () => {
|
|
45
|
-
const requiredFields = ['name', 'type', 'context', 'dimensions', 'price', 'bestFor'];
|
|
46
|
-
for (const model of MODEL_CATALOG) {
|
|
47
|
-
for (const field of requiredFields) {
|
|
48
|
-
assert.ok(
|
|
49
|
-
model[field] !== undefined && model[field] !== null && model[field] !== '',
|
|
50
|
-
`Model "${model.name}" is missing field "${field}"`
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('has both embedding and reranking types', () => {
|
|
57
|
-
const types = new Set(MODEL_CATALOG.map(m => m.type));
|
|
58
|
-
assert.ok(types.has('embedding'), 'Should have embedding models');
|
|
59
|
-
assert.ok(types.has('reranking'), 'Should have reranking models');
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('embedding models outnumber reranking models', () => {
|
|
63
|
-
const embedCount = MODEL_CATALOG.filter(m => m.type === 'embedding').length;
|
|
64
|
-
const rerankCount = MODEL_CATALOG.filter(m => m.type === 'reranking').length;
|
|
65
|
-
assert.ok(embedCount > rerankCount);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('contains voyage-4-nano as current model', () => {
|
|
69
|
-
const nano = MODEL_CATALOG.find(m => m.name === 'voyage-4-nano');
|
|
70
|
-
assert.ok(nano, 'Should have voyage-4-nano');
|
|
71
|
-
assert.equal(nano.type, 'embedding');
|
|
72
|
-
assert.ok(!nano.legacy, 'voyage-4-nano should not be legacy');
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('contains legacy models with legacy flag', () => {
|
|
76
|
-
const legacyModels = MODEL_CATALOG.filter(m => m.legacy);
|
|
77
|
-
assert.ok(legacyModels.length > 0, 'Should have legacy models');
|
|
78
|
-
|
|
79
|
-
const legacyNames = legacyModels.map(m => m.name);
|
|
80
|
-
assert.ok(legacyNames.includes('voyage-3-large'), 'Should have voyage-3-large');
|
|
81
|
-
assert.ok(legacyNames.includes('voyage-3.5'), 'Should have voyage-3.5');
|
|
82
|
-
assert.ok(legacyNames.includes('voyage-3.5-lite'), 'Should have voyage-3.5-lite');
|
|
83
|
-
assert.ok(legacyNames.includes('voyage-code-2'), 'Should have voyage-code-2');
|
|
84
|
-
assert.ok(legacyNames.includes('voyage-multimodal-3'), 'Should have voyage-multimodal-3');
|
|
85
|
-
assert.ok(legacyNames.includes('rerank-2'), 'Should have rerank-2');
|
|
86
|
-
assert.ok(legacyNames.includes('rerank-2-lite'), 'Should have rerank-2-lite');
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('legacy models have required fields', () => {
|
|
90
|
-
const legacyModels = MODEL_CATALOG.filter(m => m.legacy);
|
|
91
|
-
for (const model of legacyModels) {
|
|
92
|
-
assert.ok(model.name, `Legacy model missing name`);
|
|
93
|
-
assert.ok(model.type, `Legacy model ${model.name} missing type`);
|
|
94
|
-
assert.ok(model.context, `Legacy model ${model.name} missing context`);
|
|
95
|
-
assert.ok(model.price, `Legacy model ${model.name} missing price`);
|
|
96
|
-
assert.ok(model.bestFor, `Legacy model ${model.name} missing bestFor`);
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
});
|