ruvector 0.2.27 → 0.2.29

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.
Files changed (43) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +2270 -2270
  3. package/bin/cli.js +9570 -9479
  4. package/bin/mcp-server.js +3854 -3854
  5. package/dist/core/intelligence-engine.d.ts +13 -0
  6. package/dist/core/intelligence-engine.d.ts.map +1 -1
  7. package/dist/core/intelligence-engine.js +38 -0
  8. package/dist/core/onnx/bundled-parallel.mjs +164 -164
  9. package/dist/core/onnx/embed-worker.mjs +67 -67
  10. package/dist/core/onnx/loader.js +434 -434
  11. package/dist/core/onnx/package.json +3 -3
  12. package/dist/core/onnx/pkg/LICENSE +21 -21
  13. package/dist/core/onnx/pkg/loader.js +348 -348
  14. package/dist/core/onnx/pkg/package.json +3 -3
  15. package/dist/core/onnx/pkg/ruvector_onnx_embeddings_wasm.d.ts +112 -112
  16. package/dist/core/onnx/pkg/ruvector_onnx_embeddings_wasm.js +5 -5
  17. package/dist/core/onnx/pkg/ruvector_onnx_embeddings_wasm_bg.js +638 -638
  18. package/dist/core/onnx/pkg/ruvector_onnx_embeddings_wasm_bg.wasm.d.ts +29 -29
  19. package/dist/core/onnx-embedder.d.ts.map +1 -1
  20. package/dist/core/onnx-embedder.js +24 -30
  21. package/dist/core/parallel-workers.js +439 -439
  22. package/dist/workers/benchmark.js +15 -15
  23. package/package.json +122 -122
  24. package/src/decompiler/api-prober.js +302 -302
  25. package/src/decompiler/index.js +463 -463
  26. package/src/decompiler/metrics.js +86 -86
  27. package/src/decompiler/model-decompiler.js +423 -423
  28. package/src/decompiler/module-splitter.js +498 -498
  29. package/src/decompiler/module-tree.js +142 -142
  30. package/src/decompiler/name-predictor.js +400 -400
  31. package/src/decompiler/npm-fetch.js +176 -176
  32. package/src/decompiler/reconstructor.js +499 -499
  33. package/src/decompiler/reference-tracker.js +285 -285
  34. package/src/decompiler/statement-parser.js +285 -285
  35. package/src/decompiler/style-improver.js +438 -438
  36. package/src/decompiler/subcategories.js +339 -339
  37. package/src/decompiler/validator.js +379 -379
  38. package/src/decompiler/witness.js +140 -140
  39. package/wasm/package.json +26 -26
  40. package/wasm/ruvector_decompiler_wasm.d.ts +27 -27
  41. package/wasm/ruvector_decompiler_wasm.js +220 -220
  42. package/wasm/ruvector_decompiler_wasm_bg.wasm.d.ts +16 -16
  43. package/dist/core/onnx/pkg/ruvector.db +0 -0
@@ -1,302 +1,302 @@
1
- 'use strict';
2
-
3
- /**
4
- * LLM API prober -- discovers model architecture by probing remote APIs.
5
- * Detects capabilities, token limits, tokenizer behavior, and model fingerprints.
6
- * See ADR-138.
7
- */
8
-
9
- // ── Provider detection ───────────────────────────────────────────────────
10
-
11
- const PROVIDERS = {
12
- anthropic: {
13
- endpoint: 'https://api.anthropic.com/v1/messages',
14
- envKey: 'ANTHROPIC_API_KEY',
15
- models: ['claude-sonnet-4-6', 'claude-sonnet-4-20250514', 'claude-haiku-4-20250414', 'claude-opus-4-20250514'],
16
- },
17
- openai: {
18
- endpoint: 'https://api.openai.com/v1/chat/completions',
19
- envKey: 'OPENAI_API_KEY',
20
- models: ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'o1', 'o1-mini'],
21
- },
22
- google: {
23
- endpoint: 'https://generativelanguage.googleapis.com/v1beta/models',
24
- envKey: 'GOOGLE_AI_API_KEY',
25
- models: ['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.0-flash'],
26
- },
27
- };
28
-
29
- function detectProvider(modelId) {
30
- modelId = modelId.toLowerCase();
31
- if (modelId.startsWith('claude')) return 'anthropic';
32
- if (modelId.startsWith('gpt') || modelId.startsWith('o1') || modelId.startsWith('o3')) return 'openai';
33
- if (modelId.startsWith('gemini')) return 'google';
34
- return 'unknown';
35
- }
36
-
37
- // ── Main probe ───────────────────────────────────────────────────────────
38
-
39
- async function probeModel(modelId, opts = {}) {
40
- const provider = detectProvider(modelId);
41
- const providerConfig = PROVIDERS[provider];
42
- if (!providerConfig && provider === 'unknown') {
43
- throw new Error(`Unknown provider for model: ${modelId}. Supported: claude-*, gpt-*, gemini-*`);
44
- }
45
-
46
- const apiKey = opts.apiKey || process.env[providerConfig?.envKey || ''];
47
- if (!apiKey) {
48
- throw new Error(
49
- `No API key found. Set ${providerConfig?.envKey || 'API_KEY'} env var or pass --api-key`
50
- );
51
- }
52
-
53
- const result = {
54
- model: modelId,
55
- provider,
56
- capabilities: {},
57
- tokenizer: {},
58
- limits: {},
59
- fingerprint: {},
60
- latency: {},
61
- };
62
-
63
- const send = buildSender(provider, modelId, apiKey);
64
-
65
- // 1. Basic probe -- verify model is reachable and measure latency
66
- const start = Date.now();
67
- const basicResp = await send('Say exactly: PROBE_OK');
68
- result.latency.first_token_ms = Date.now() - start;
69
- result.capabilities.reachable = !!basicResp;
70
-
71
- if (!basicResp) {
72
- result.capabilities.error = 'Model unreachable or invalid API key';
73
- return result;
74
- }
75
-
76
- // 2. Capability probes (run in parallel for speed)
77
- const [streamResp, toolResp, sysResp] = await Promise.allSettled([
78
- testStreaming(send),
79
- testToolUse(provider, modelId, apiKey),
80
- send('What is 2+2? Reply with just the number.', { systemPrompt: 'You are a calculator.' }),
81
- ]);
82
-
83
- result.capabilities.streaming = streamResp.status === 'fulfilled' && streamResp.value;
84
- result.capabilities.tools = toolResp.status === 'fulfilled' && toolResp.value;
85
- result.capabilities.system_prompt = sysResp.status === 'fulfilled' && !!sysResp.value;
86
-
87
- // 3. Tokenizer probe -- send known strings, analyze responses
88
- const tokenizerResult = await probeTokenizer(send);
89
- result.tokenizer = tokenizerResult;
90
-
91
- // 4. Model fingerprint -- specific prompts that distinguish families
92
- const fingerprint = await fingerprintModel(send, provider);
93
- result.fingerprint = fingerprint;
94
-
95
- // 5. Measure response speed
96
- const speedStart = Date.now();
97
- const longResp = await send('Count from 1 to 20, one per line.');
98
- const speedMs = Date.now() - speedStart;
99
- const outputTokens = longResp ? longResp.split(/\s+/).length : 0;
100
- result.latency.generation_ms = speedMs;
101
- result.latency.est_tokens_per_sec = speedMs > 0 ? Math.round((outputTokens / speedMs) * 1000) : 0;
102
-
103
- return result;
104
- }
105
-
106
- // ── Provider-specific request builders ───────────────────────────────────
107
-
108
- function buildSender(provider, modelId, apiKey) {
109
- return async (prompt, opts = {}) => {
110
- try {
111
- if (provider === 'anthropic') return await sendAnthropic(modelId, apiKey, prompt, opts);
112
- if (provider === 'openai') return await sendOpenAI(modelId, apiKey, prompt, opts);
113
- if (provider === 'google') return await sendGoogle(modelId, apiKey, prompt, opts);
114
- throw new Error(`Unsupported provider: ${provider}`);
115
- } catch (err) {
116
- // Return null on API errors (model may not support the feature)
117
- if (err.message?.includes('API error')) return null;
118
- throw err;
119
- }
120
- };
121
- }
122
-
123
- async function sendAnthropic(model, apiKey, prompt, opts = {}) {
124
- const body = {
125
- model,
126
- max_tokens: opts.maxTokens || 100,
127
- messages: [{ role: 'user', content: prompt }],
128
- };
129
- if (opts.systemPrompt) body.system = opts.systemPrompt;
130
-
131
- const resp = await fetch('https://api.anthropic.com/v1/messages', {
132
- method: 'POST',
133
- headers: {
134
- 'Content-Type': 'application/json',
135
- 'x-api-key': apiKey,
136
- 'anthropic-version': '2023-06-01',
137
- },
138
- body: JSON.stringify(body),
139
- });
140
- if (!resp.ok) throw new Error(`API error ${resp.status}: ${await resp.text()}`);
141
- const data = await resp.json();
142
- return data.content?.[0]?.text || '';
143
- }
144
-
145
- async function sendOpenAI(model, apiKey, prompt, opts = {}) {
146
- const messages = [];
147
- if (opts.systemPrompt) messages.push({ role: 'system', content: opts.systemPrompt });
148
- messages.push({ role: 'user', content: prompt });
149
-
150
- const resp = await fetch('https://api.openai.com/v1/chat/completions', {
151
- method: 'POST',
152
- headers: {
153
- 'Content-Type': 'application/json',
154
- 'Authorization': `Bearer ${apiKey}`,
155
- },
156
- body: JSON.stringify({ model, messages, max_tokens: opts.maxTokens || 100 }),
157
- });
158
- if (!resp.ok) throw new Error(`API error ${resp.status}: ${await resp.text()}`);
159
- const data = await resp.json();
160
- return data.choices?.[0]?.message?.content || '';
161
- }
162
-
163
- async function sendGoogle(model, apiKey, prompt, opts = {}) {
164
- const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`;
165
- const body = {
166
- contents: [{ parts: [{ text: prompt }] }],
167
- generationConfig: { maxOutputTokens: opts.maxTokens || 100 },
168
- };
169
- if (opts.systemPrompt) {
170
- body.systemInstruction = { parts: [{ text: opts.systemPrompt }] };
171
- }
172
-
173
- const resp = await fetch(url, {
174
- method: 'POST',
175
- headers: { 'Content-Type': 'application/json' },
176
- body: JSON.stringify(body),
177
- });
178
- if (!resp.ok) throw new Error(`API error ${resp.status}: ${await resp.text()}`);
179
- const data = await resp.json();
180
- return data.candidates?.[0]?.content?.parts?.[0]?.text || '';
181
- }
182
-
183
- // ── Feature probes ───────────────────────────────────────────────────────
184
-
185
- async function testStreaming(send) {
186
- // Streaming support is provider-dependent; we just check if the model responds
187
- const resp = await send('Say "stream test"');
188
- return !!resp;
189
- }
190
-
191
- async function testToolUse(provider, modelId, apiKey) {
192
- try {
193
- if (provider === 'anthropic') {
194
- const resp = await fetch('https://api.anthropic.com/v1/messages', {
195
- method: 'POST',
196
- headers: {
197
- 'Content-Type': 'application/json',
198
- 'x-api-key': apiKey,
199
- 'anthropic-version': '2023-06-01',
200
- },
201
- body: JSON.stringify({
202
- model: modelId,
203
- max_tokens: 100,
204
- messages: [{ role: 'user', content: 'What is the weather in SF?' }],
205
- tools: [{
206
- name: 'get_weather',
207
- description: 'Get weather for a location',
208
- input_schema: { type: 'object', properties: { location: { type: 'string' } } },
209
- }],
210
- }),
211
- });
212
- return resp.ok;
213
- }
214
- return true; // Assume supported for other providers
215
- } catch {
216
- return false;
217
- }
218
- }
219
-
220
- // ── Tokenizer probing ────────────────────────────────────────────────────
221
-
222
- async function probeTokenizer(send) {
223
- // Send known strings and analyze how the model interprets them
224
- const testStr = 'antidisestablishmentarianism';
225
- const resp = await send(
226
- `How many tokens does the word "${testStr}" require? Just give the number.`
227
- );
228
- const tokenCount = resp ? parseInt(resp.match(/\d+/)?.[0] || '0', 10) : 0;
229
-
230
- // Detect BPE vs SentencePiece by checking token boundary behavior
231
- const bpeResp = await send(
232
- 'Split "unhappiness" into its BPE tokens. List each token on a line.'
233
- );
234
-
235
- let type = 'unknown';
236
- if (bpeResp) {
237
- if (bpeResp.includes('un') && bpeResp.includes('happiness')) type = 'BPE';
238
- if (bpeResp.includes('_un') || bpeResp.includes('\u2581un')) type = 'SentencePiece';
239
- }
240
-
241
- return {
242
- type,
243
- estimated_tokens_for_test_word: tokenCount,
244
- test_word: testStr,
245
- };
246
- }
247
-
248
- // ── Model fingerprinting ─────────────────────────────────────────────────
249
-
250
- async function fingerprintModel(send, provider) {
251
- // Ask the model to identify itself
252
- const identResp = await send(
253
- 'What LLM are you? Reply in format: "I am [model name] by [company]"'
254
- );
255
-
256
- // Test for specific behaviors
257
- const mathResp = await send('What is 7 * 8? Reply with just the number.');
258
-
259
- return {
260
- self_identification: identResp || 'unknown',
261
- provider_detected: provider,
262
- math_correct: mathResp?.trim() === '56',
263
- timestamp: new Date().toISOString(),
264
- };
265
- }
266
-
267
- // ── Pretty printer ───────────────────────────────────────────────────────
268
-
269
- function printProbeResult(result) {
270
- const _chalk = require('chalk');
271
- const chalk = _chalk.default || _chalk;
272
-
273
- console.log(chalk.bold.cyan('\n LLM API Probe Results'));
274
- console.log(chalk.white(` Model: ${result.model}`));
275
- console.log(chalk.white(` Provider: ${result.provider}`));
276
- console.log('');
277
-
278
- console.log(chalk.bold(' Capabilities:'));
279
- console.log(chalk.white(` Reachable: ${result.capabilities.reachable ? 'Yes' : 'No'}`));
280
- console.log(chalk.white(` Streaming: ${result.capabilities.streaming ? 'Yes' : 'No'}`));
281
- console.log(chalk.white(` Tool use: ${result.capabilities.tools ? 'Yes' : 'No'}`));
282
- console.log(chalk.white(` System prompt: ${result.capabilities.system_prompt ? 'Yes' : 'No'}`));
283
- console.log('');
284
-
285
- console.log(chalk.bold(' Latency:'));
286
- console.log(chalk.white(` First token: ${result.latency.first_token_ms} ms`));
287
- console.log(chalk.white(` Generation: ${result.latency.generation_ms} ms`));
288
- console.log(chalk.white(` Est. tok/sec: ${result.latency.est_tokens_per_sec}`));
289
- console.log('');
290
-
291
- console.log(chalk.bold(' Tokenizer:'));
292
- console.log(chalk.white(` Type: ${result.tokenizer.type}`));
293
- console.log(chalk.white(` Test word: "${result.tokenizer.test_word}" -> ${result.tokenizer.estimated_tokens_for_test_word} tokens`));
294
- console.log('');
295
-
296
- console.log(chalk.bold(' Fingerprint:'));
297
- console.log(chalk.white(` Self-ID: ${result.fingerprint.self_identification?.slice(0, 80)}`));
298
- console.log(chalk.white(` Math correct: ${result.fingerprint.math_correct ? 'Yes' : 'No'}`));
299
- console.log('');
300
- }
301
-
302
- module.exports = { probeModel, printProbeResult, detectProvider };
1
+ 'use strict';
2
+
3
+ /**
4
+ * LLM API prober -- discovers model architecture by probing remote APIs.
5
+ * Detects capabilities, token limits, tokenizer behavior, and model fingerprints.
6
+ * See ADR-138.
7
+ */
8
+
9
+ // ── Provider detection ───────────────────────────────────────────────────
10
+
11
+ const PROVIDERS = {
12
+ anthropic: {
13
+ endpoint: 'https://api.anthropic.com/v1/messages',
14
+ envKey: 'ANTHROPIC_API_KEY',
15
+ models: ['claude-sonnet-4-6', 'claude-sonnet-4-20250514', 'claude-haiku-4-20250414', 'claude-opus-4-20250514'],
16
+ },
17
+ openai: {
18
+ endpoint: 'https://api.openai.com/v1/chat/completions',
19
+ envKey: 'OPENAI_API_KEY',
20
+ models: ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'o1', 'o1-mini'],
21
+ },
22
+ google: {
23
+ endpoint: 'https://generativelanguage.googleapis.com/v1beta/models',
24
+ envKey: 'GOOGLE_AI_API_KEY',
25
+ models: ['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.0-flash'],
26
+ },
27
+ };
28
+
29
+ function detectProvider(modelId) {
30
+ modelId = modelId.toLowerCase();
31
+ if (modelId.startsWith('claude')) return 'anthropic';
32
+ if (modelId.startsWith('gpt') || modelId.startsWith('o1') || modelId.startsWith('o3')) return 'openai';
33
+ if (modelId.startsWith('gemini')) return 'google';
34
+ return 'unknown';
35
+ }
36
+
37
+ // ── Main probe ───────────────────────────────────────────────────────────
38
+
39
+ async function probeModel(modelId, opts = {}) {
40
+ const provider = detectProvider(modelId);
41
+ const providerConfig = PROVIDERS[provider];
42
+ if (!providerConfig && provider === 'unknown') {
43
+ throw new Error(`Unknown provider for model: ${modelId}. Supported: claude-*, gpt-*, gemini-*`);
44
+ }
45
+
46
+ const apiKey = opts.apiKey || process.env[providerConfig?.envKey || ''];
47
+ if (!apiKey) {
48
+ throw new Error(
49
+ `No API key found. Set ${providerConfig?.envKey || 'API_KEY'} env var or pass --api-key`
50
+ );
51
+ }
52
+
53
+ const result = {
54
+ model: modelId,
55
+ provider,
56
+ capabilities: {},
57
+ tokenizer: {},
58
+ limits: {},
59
+ fingerprint: {},
60
+ latency: {},
61
+ };
62
+
63
+ const send = buildSender(provider, modelId, apiKey);
64
+
65
+ // 1. Basic probe -- verify model is reachable and measure latency
66
+ const start = Date.now();
67
+ const basicResp = await send('Say exactly: PROBE_OK');
68
+ result.latency.first_token_ms = Date.now() - start;
69
+ result.capabilities.reachable = !!basicResp;
70
+
71
+ if (!basicResp) {
72
+ result.capabilities.error = 'Model unreachable or invalid API key';
73
+ return result;
74
+ }
75
+
76
+ // 2. Capability probes (run in parallel for speed)
77
+ const [streamResp, toolResp, sysResp] = await Promise.allSettled([
78
+ testStreaming(send),
79
+ testToolUse(provider, modelId, apiKey),
80
+ send('What is 2+2? Reply with just the number.', { systemPrompt: 'You are a calculator.' }),
81
+ ]);
82
+
83
+ result.capabilities.streaming = streamResp.status === 'fulfilled' && streamResp.value;
84
+ result.capabilities.tools = toolResp.status === 'fulfilled' && toolResp.value;
85
+ result.capabilities.system_prompt = sysResp.status === 'fulfilled' && !!sysResp.value;
86
+
87
+ // 3. Tokenizer probe -- send known strings, analyze responses
88
+ const tokenizerResult = await probeTokenizer(send);
89
+ result.tokenizer = tokenizerResult;
90
+
91
+ // 4. Model fingerprint -- specific prompts that distinguish families
92
+ const fingerprint = await fingerprintModel(send, provider);
93
+ result.fingerprint = fingerprint;
94
+
95
+ // 5. Measure response speed
96
+ const speedStart = Date.now();
97
+ const longResp = await send('Count from 1 to 20, one per line.');
98
+ const speedMs = Date.now() - speedStart;
99
+ const outputTokens = longResp ? longResp.split(/\s+/).length : 0;
100
+ result.latency.generation_ms = speedMs;
101
+ result.latency.est_tokens_per_sec = speedMs > 0 ? Math.round((outputTokens / speedMs) * 1000) : 0;
102
+
103
+ return result;
104
+ }
105
+
106
+ // ── Provider-specific request builders ───────────────────────────────────
107
+
108
+ function buildSender(provider, modelId, apiKey) {
109
+ return async (prompt, opts = {}) => {
110
+ try {
111
+ if (provider === 'anthropic') return await sendAnthropic(modelId, apiKey, prompt, opts);
112
+ if (provider === 'openai') return await sendOpenAI(modelId, apiKey, prompt, opts);
113
+ if (provider === 'google') return await sendGoogle(modelId, apiKey, prompt, opts);
114
+ throw new Error(`Unsupported provider: ${provider}`);
115
+ } catch (err) {
116
+ // Return null on API errors (model may not support the feature)
117
+ if (err.message?.includes('API error')) return null;
118
+ throw err;
119
+ }
120
+ };
121
+ }
122
+
123
+ async function sendAnthropic(model, apiKey, prompt, opts = {}) {
124
+ const body = {
125
+ model,
126
+ max_tokens: opts.maxTokens || 100,
127
+ messages: [{ role: 'user', content: prompt }],
128
+ };
129
+ if (opts.systemPrompt) body.system = opts.systemPrompt;
130
+
131
+ const resp = await fetch('https://api.anthropic.com/v1/messages', {
132
+ method: 'POST',
133
+ headers: {
134
+ 'Content-Type': 'application/json',
135
+ 'x-api-key': apiKey,
136
+ 'anthropic-version': '2023-06-01',
137
+ },
138
+ body: JSON.stringify(body),
139
+ });
140
+ if (!resp.ok) throw new Error(`API error ${resp.status}: ${await resp.text()}`);
141
+ const data = await resp.json();
142
+ return data.content?.[0]?.text || '';
143
+ }
144
+
145
+ async function sendOpenAI(model, apiKey, prompt, opts = {}) {
146
+ const messages = [];
147
+ if (opts.systemPrompt) messages.push({ role: 'system', content: opts.systemPrompt });
148
+ messages.push({ role: 'user', content: prompt });
149
+
150
+ const resp = await fetch('https://api.openai.com/v1/chat/completions', {
151
+ method: 'POST',
152
+ headers: {
153
+ 'Content-Type': 'application/json',
154
+ 'Authorization': `Bearer ${apiKey}`,
155
+ },
156
+ body: JSON.stringify({ model, messages, max_tokens: opts.maxTokens || 100 }),
157
+ });
158
+ if (!resp.ok) throw new Error(`API error ${resp.status}: ${await resp.text()}`);
159
+ const data = await resp.json();
160
+ return data.choices?.[0]?.message?.content || '';
161
+ }
162
+
163
+ async function sendGoogle(model, apiKey, prompt, opts = {}) {
164
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`;
165
+ const body = {
166
+ contents: [{ parts: [{ text: prompt }] }],
167
+ generationConfig: { maxOutputTokens: opts.maxTokens || 100 },
168
+ };
169
+ if (opts.systemPrompt) {
170
+ body.systemInstruction = { parts: [{ text: opts.systemPrompt }] };
171
+ }
172
+
173
+ const resp = await fetch(url, {
174
+ method: 'POST',
175
+ headers: { 'Content-Type': 'application/json' },
176
+ body: JSON.stringify(body),
177
+ });
178
+ if (!resp.ok) throw new Error(`API error ${resp.status}: ${await resp.text()}`);
179
+ const data = await resp.json();
180
+ return data.candidates?.[0]?.content?.parts?.[0]?.text || '';
181
+ }
182
+
183
+ // ── Feature probes ───────────────────────────────────────────────────────
184
+
185
+ async function testStreaming(send) {
186
+ // Streaming support is provider-dependent; we just check if the model responds
187
+ const resp = await send('Say "stream test"');
188
+ return !!resp;
189
+ }
190
+
191
+ async function testToolUse(provider, modelId, apiKey) {
192
+ try {
193
+ if (provider === 'anthropic') {
194
+ const resp = await fetch('https://api.anthropic.com/v1/messages', {
195
+ method: 'POST',
196
+ headers: {
197
+ 'Content-Type': 'application/json',
198
+ 'x-api-key': apiKey,
199
+ 'anthropic-version': '2023-06-01',
200
+ },
201
+ body: JSON.stringify({
202
+ model: modelId,
203
+ max_tokens: 100,
204
+ messages: [{ role: 'user', content: 'What is the weather in SF?' }],
205
+ tools: [{
206
+ name: 'get_weather',
207
+ description: 'Get weather for a location',
208
+ input_schema: { type: 'object', properties: { location: { type: 'string' } } },
209
+ }],
210
+ }),
211
+ });
212
+ return resp.ok;
213
+ }
214
+ return true; // Assume supported for other providers
215
+ } catch {
216
+ return false;
217
+ }
218
+ }
219
+
220
+ // ── Tokenizer probing ────────────────────────────────────────────────────
221
+
222
+ async function probeTokenizer(send) {
223
+ // Send known strings and analyze how the model interprets them
224
+ const testStr = 'antidisestablishmentarianism';
225
+ const resp = await send(
226
+ `How many tokens does the word "${testStr}" require? Just give the number.`
227
+ );
228
+ const tokenCount = resp ? parseInt(resp.match(/\d+/)?.[0] || '0', 10) : 0;
229
+
230
+ // Detect BPE vs SentencePiece by checking token boundary behavior
231
+ const bpeResp = await send(
232
+ 'Split "unhappiness" into its BPE tokens. List each token on a line.'
233
+ );
234
+
235
+ let type = 'unknown';
236
+ if (bpeResp) {
237
+ if (bpeResp.includes('un') && bpeResp.includes('happiness')) type = 'BPE';
238
+ if (bpeResp.includes('_un') || bpeResp.includes('\u2581un')) type = 'SentencePiece';
239
+ }
240
+
241
+ return {
242
+ type,
243
+ estimated_tokens_for_test_word: tokenCount,
244
+ test_word: testStr,
245
+ };
246
+ }
247
+
248
+ // ── Model fingerprinting ─────────────────────────────────────────────────
249
+
250
+ async function fingerprintModel(send, provider) {
251
+ // Ask the model to identify itself
252
+ const identResp = await send(
253
+ 'What LLM are you? Reply in format: "I am [model name] by [company]"'
254
+ );
255
+
256
+ // Test for specific behaviors
257
+ const mathResp = await send('What is 7 * 8? Reply with just the number.');
258
+
259
+ return {
260
+ self_identification: identResp || 'unknown',
261
+ provider_detected: provider,
262
+ math_correct: mathResp?.trim() === '56',
263
+ timestamp: new Date().toISOString(),
264
+ };
265
+ }
266
+
267
+ // ── Pretty printer ───────────────────────────────────────────────────────
268
+
269
+ function printProbeResult(result) {
270
+ const _chalk = require('chalk');
271
+ const chalk = _chalk.default || _chalk;
272
+
273
+ console.log(chalk.bold.cyan('\n LLM API Probe Results'));
274
+ console.log(chalk.white(` Model: ${result.model}`));
275
+ console.log(chalk.white(` Provider: ${result.provider}`));
276
+ console.log('');
277
+
278
+ console.log(chalk.bold(' Capabilities:'));
279
+ console.log(chalk.white(` Reachable: ${result.capabilities.reachable ? 'Yes' : 'No'}`));
280
+ console.log(chalk.white(` Streaming: ${result.capabilities.streaming ? 'Yes' : 'No'}`));
281
+ console.log(chalk.white(` Tool use: ${result.capabilities.tools ? 'Yes' : 'No'}`));
282
+ console.log(chalk.white(` System prompt: ${result.capabilities.system_prompt ? 'Yes' : 'No'}`));
283
+ console.log('');
284
+
285
+ console.log(chalk.bold(' Latency:'));
286
+ console.log(chalk.white(` First token: ${result.latency.first_token_ms} ms`));
287
+ console.log(chalk.white(` Generation: ${result.latency.generation_ms} ms`));
288
+ console.log(chalk.white(` Est. tok/sec: ${result.latency.est_tokens_per_sec}`));
289
+ console.log('');
290
+
291
+ console.log(chalk.bold(' Tokenizer:'));
292
+ console.log(chalk.white(` Type: ${result.tokenizer.type}`));
293
+ console.log(chalk.white(` Test word: "${result.tokenizer.test_word}" -> ${result.tokenizer.estimated_tokens_for_test_word} tokens`));
294
+ console.log('');
295
+
296
+ console.log(chalk.bold(' Fingerprint:'));
297
+ console.log(chalk.white(` Self-ID: ${result.fingerprint.self_identification?.slice(0, 80)}`));
298
+ console.log(chalk.white(` Math correct: ${result.fingerprint.math_correct ? 'Yes' : 'No'}`));
299
+ console.log('');
300
+ }
301
+
302
+ module.exports = { probeModel, printProbeResult, detectProvider };