ruvector 0.2.28 → 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 (41) 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/parallel-workers.js +439 -439
  20. package/dist/workers/benchmark.js +15 -15
  21. package/package.json +122 -122
  22. package/src/decompiler/api-prober.js +302 -302
  23. package/src/decompiler/index.js +463 -463
  24. package/src/decompiler/metrics.js +86 -86
  25. package/src/decompiler/model-decompiler.js +423 -423
  26. package/src/decompiler/module-splitter.js +498 -498
  27. package/src/decompiler/module-tree.js +142 -142
  28. package/src/decompiler/name-predictor.js +400 -400
  29. package/src/decompiler/npm-fetch.js +176 -176
  30. package/src/decompiler/reconstructor.js +499 -499
  31. package/src/decompiler/reference-tracker.js +285 -285
  32. package/src/decompiler/statement-parser.js +285 -285
  33. package/src/decompiler/style-improver.js +438 -438
  34. package/src/decompiler/subcategories.js +339 -339
  35. package/src/decompiler/validator.js +379 -379
  36. package/src/decompiler/witness.js +140 -140
  37. package/wasm/package.json +26 -26
  38. package/wasm/ruvector_decompiler_wasm.d.ts +27 -27
  39. package/wasm/ruvector_decompiler_wasm.js +220 -220
  40. package/wasm/ruvector_decompiler_wasm_bg.wasm.d.ts +16 -16
  41. 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 };