ai-xray 2.0.0 → 2.0.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/dist/cli.js CHANGED
@@ -23,6 +23,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  mod
24
24
  ));
25
25
 
26
+ // src/cli.ts
27
+ var import_node_fs = require("fs");
28
+ var import_node_path = require("path");
29
+
26
30
  // src/client.ts
27
31
  var fs = __toESM(require("fs"));
28
32
  var path = __toESM(require("path"));
@@ -650,7 +654,7 @@ async function compare(providers, options) {
650
654
  }
651
655
 
652
656
  // src/cli.ts
653
- var VERSION = "2.0.0";
657
+ var VERSION = JSON.parse((0, import_node_fs.readFileSync)((0, import_node_path.join)(__dirname, "..", "package.json"), "utf8")).version;
654
658
  function printHelp() {
655
659
  outputSuccess({
656
660
  name: "ai-xray",
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts","../src/utils/http.ts","../src/utils/output.ts","../src/commands/ping.ts","../src/commands/id.ts","../src/utils/timer.ts","../src/commands/probe.ts","../src/commands/bench.ts","../src/commands/tokenize.ts","../src/commands/compare.ts","../src/cli.ts"],"sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport * as os from 'os';\nimport { requestJson } from './utils/http';\n\nexport interface ProviderConfig {\n baseUrl: string;\n apiKey?: string;\n model: string;\n}\n\ninterface ConfigFile {\n providers?: Record<string, ProviderConfig>;\n}\n\nexport function loadConfig(providerName?: string): ProviderConfig {\n // 1. Check environment variables first\n const envBaseUrl = process.env.AI_XRAY_BASE_URL || 'https://api.openai.com/v1';\n const envApiKey = process.env.AI_XRAY_API_KEY;\n const envModel = process.env.AI_XRAY_MODEL || 'gpt-4o';\n\n // 2. If provider specified, check config file\n if (providerName) {\n const configFile = loadConfigFile();\n if (configFile?.providers?.[providerName]) {\n const providerConfig = configFile.providers[providerName];\n return {\n baseUrl: providerConfig.baseUrl || envBaseUrl,\n apiKey: providerConfig.apiKey || envApiKey,\n model: providerConfig.model || envModel,\n };\n }\n }\n\n // 3. Default to env vars\n return {\n baseUrl: envBaseUrl,\n apiKey: envApiKey,\n model: envModel,\n };\n}\n\nfunction loadConfigFile(): ConfigFile | null {\n const configPath = path.join(os.homedir(), '.ai-xray.json');\n try {\n if (fs.existsSync(configPath)) {\n const content = fs.readFileSync(configPath, 'utf-8');\n return JSON.parse(content) as ConfigFile;\n }\n } catch {\n // Ignore config file errors\n }\n return null;\n}\n\nexport interface ChatMessage {\n role: 'system' | 'user' | 'assistant';\n content: string | Array<{ type: string; [key: string]: unknown }>;\n}\n\nexport interface ChatRequest {\n model: string;\n messages: ChatMessage[];\n max_tokens?: number;\n temperature?: number;\n stream?: boolean;\n response_format?: { type: string };\n tools?: unknown[];\n}\n\nexport interface ChatResponse {\n id: string;\n model: string;\n choices: Array<{\n message: { role: string; content: string };\n finish_reason: string;\n }>;\n usage?: {\n prompt_tokens: number;\n completion_tokens: number;\n total_tokens: number;\n };\n headers: Record<string, string>;\n latency_ms: number;\n}\n\nexport async function chat(\n config: ProviderConfig,\n request: ChatRequest\n): Promise<ChatResponse> {\n const startTime = performance.now();\n\n const url = `${config.baseUrl}/chat/completions`;\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Accept': 'application/json',\n };\n\n if (config.apiKey) {\n headers['Authorization'] = `Bearer ${config.apiKey}`;\n }\n\n try {\n const { response, data } = await requestJson(url, {\n method: 'POST',\n headers,\n body: JSON.stringify(request),\n });\n\n const latencyMs = performance.now() - startTime;\n\n return {\n id: (data as any).id || '',\n model: (data as any).model || config.model,\n choices: (data as any).choices || [],\n usage: (data as any).usage,\n headers: response.headers,\n latency_ms: latencyMs,\n };\n } catch (error) {\n const latencyMs = performance.now() - startTime;\n throw new Error(`Chat request failed: ${(error as Error).message}`);\n }\n}\n\nexport async function chatStream(\n config: ProviderConfig,\n request: ChatRequest,\n onChunk: (content: string) => void\n): Promise<ChatResponse> {\n const startTime = performance.now();\n\n const url = `${config.baseUrl}/chat/completions`;\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Accept': 'text/event-stream',\n };\n\n if (config.apiKey) {\n headers['Authorization'] = `Bearer ${config.apiKey}`;\n }\n\n const { response } = await request(url, {\n method: 'POST',\n headers,\n body: JSON.stringify({ ...request, stream: true }),\n });\n\n // For streaming, we'd need to handle the SSE stream\n // For now, return a simplified response\n const latencyMs = performance.now() - startTime;\n\n return {\n id: '',\n model: config.model,\n choices: [],\n headers: response.headers,\n latency_ms: latencyMs,\n };\n}\n\nfunction request(\n urlString: string,\n options: {\n method?: string;\n headers?: Record<string, string>;\n body?: string;\n }\n): Promise<{ response: { headers: Record<string, string> }; data?: unknown }> {\n return new Promise((resolve, reject) => {\n const https = require('https');\n const url = new URL(urlString);\n\n const req = https.request({\n hostname: url.hostname,\n port: url.port || 443,\n path: url.pathname + url.search,\n method: options.method || 'GET',\n headers: options.headers || {},\n }, (res: any) => {\n let body = '';\n res.on('data', (chunk: string) => { body += chunk; });\n res.on('end', () => {\n const headers: Record<string, string> = {};\n for (const [key, value] of Object.entries(res.headers)) {\n if (typeof value === 'string') {\n headers[key] = value;\n } else if (Array.isArray(value)) {\n headers[key] = value.join(', ');\n }\n }\n resolve({\n response: { headers },\n data: undefined,\n });\n });\n });\n\n req.on('error', reject);\n req.write(options.body || '');\n req.end();\n });\n}\n","import * as http from 'http';\nimport * as https from 'https';\nimport { URL } from 'url';\n\nexport interface HttpRequestOptions {\n method?: string;\n headers?: Record<string, string>;\n body?: string;\n timeout?: number;\n}\n\nexport interface HttpResponse {\n statusCode: number;\n headers: Record<string, string>;\n body: string;\n}\n\nexport function request(\n urlString: string,\n options: HttpRequestOptions = {}\n): Promise<HttpResponse> {\n return new Promise((resolve, reject) => {\n const url = new URL(urlString);\n const isHttps = url.protocol === 'https:';\n const client = isHttps ? https : http;\n\n const requestOptions: http.RequestOptions = {\n hostname: url.hostname,\n port: url.port || (isHttps ? 443 : 80),\n path: url.pathname + url.search,\n method: options.method || 'GET',\n headers: {\n 'Content-Type': 'application/json',\n 'Accept': 'application/json',\n ...options.headers,\n },\n timeout: options.timeout || 30000,\n };\n\n const req = client.request(requestOptions, (res) => {\n let body = '';\n res.on('data', (chunk) => { body += chunk; });\n res.on('end', () => {\n const headers: Record<string, string> = {};\n for (const [key, value] of Object.entries(res.headers)) {\n if (typeof value === 'string') {\n headers[key] = value;\n } else if (Array.isArray(value)) {\n headers[key] = value.join(', ');\n }\n }\n resolve({\n statusCode: res.statusCode || 0,\n headers,\n body,\n });\n });\n });\n\n req.on('error', reject);\n req.on('timeout', () => {\n req.destroy();\n reject(new Error('Request timeout'));\n });\n\n if (options.body) {\n req.write(options.body);\n }\n req.end();\n });\n}\n\nexport function requestJson<T = unknown>(\n urlString: string,\n options: HttpRequestOptions = {}\n): Promise<{ response: HttpResponse; data: T }> {\n return request(urlString, options).then((response) => {\n let data: T;\n try {\n data = JSON.parse(response.body) as T;\n } catch {\n throw new Error(`Invalid JSON response: ${response.body.slice(0, 100)}`);\n }\n return { response, data };\n });\n}\n","/**\n * Core output management for ai-xray v2.0.0\n * Guarantees Machine-First protocol: Pure JSON on stdout, structured error on stderr.\n */\n\nlet prettyMode = false;\n\nexport function setPrettyMode(enabled: boolean): void {\n prettyMode = enabled;\n}\n\nexport function isPrettyMode(): boolean {\n return prettyMode;\n}\n\nexport function outputSuccess(data: unknown): void {\n const output = JSON.stringify(data, null, prettyMode ? 2 : undefined) + '\\n';\n process.stdout.write(output);\n process.exit(0);\n}\n\nexport function outputError(error: unknown, code = 1): void {\n const message = error instanceof Error ? error.message : String(error);\n const errorObj = {\n error: message,\n code\n };\n const output = JSON.stringify(errorObj, null, prettyMode ? 2 : undefined) + '\\n';\n process.stderr.write(output);\n process.exit(code);\n}\n\nexport function outputJson(data: unknown): void {\n const output = JSON.stringify(data, null, prettyMode ? 2 : undefined) + '\\n';\n process.stdout.write(output);\n}\n","import { ProviderConfig, chat } from '../client';\n\nexport interface PingResult {\n reachable: boolean;\n latency_ms: number;\n model: string | null;\n rate_limit: {\n remaining: number | null;\n reset_at: string | null;\n };\n error?: string;\n}\n\nexport async function ping(config: ProviderConfig): Promise<PingResult> {\n const startTime = performance.now();\n\n try {\n const response = await chat(config, {\n model: config.model,\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 1,\n });\n\n const latencyMs = performance.now() - startTime;\n\n // Extract rate limit info from headers\n const headers = response.headers;\n const remaining = headers['x-ratelimit-remaining']\n ? parseInt(headers['x-ratelimit-remaining'], 10)\n : null;\n const resetAt = headers['x-ratelimit-reset'] || null;\n\n return {\n reachable: true,\n latency_ms: Math.round(latencyMs),\n model: response.model || null,\n rate_limit: {\n remaining,\n reset_at: resetAt,\n },\n };\n } catch (error) {\n const latencyMs = performance.now() - startTime;\n return {\n reachable: false,\n latency_ms: Math.round(latencyMs),\n model: null,\n rate_limit: {\n remaining: null,\n reset_at: null,\n },\n error: (error as Error).message,\n };\n }\n}\n","import { ProviderConfig, chat } from '../client';\n\nexport interface IdResult {\n self_reported: {\n model: string | null;\n cutoff: string | null;\n context_window: number | null;\n };\n api_reported: {\n model: string | null;\n organization: string | null;\n };\n fingerprint: {\n provider: string;\n confidence: number;\n };\n}\n\nfunction extractModelName(content: string): string | null {\n // Try to extract just the model identifier\n const lines = content.trim().split('\\n');\n const firstLine = lines[0].trim();\n if (firstLine.length > 0 && firstLine.length < 200) {\n return firstLine;\n }\n return null;\n}\n\nfunction extractCutoff(content: string): string | null {\n // Look for YYYY-MM pattern\n const match = content.match(/\\d{4}-\\d{2}/);\n return match ? match[0] : null;\n}\n\nfunction extractContextWindow(content: string): number | null {\n // Look for numbers that could be context window\n const match = content.match(/(\\d{3,6})\\s*(?:tokens?)?/i);\n if (match) {\n const num = parseInt(match[1], 10);\n // Reasonable context windows are between 4K and 2M\n if (num >= 4000 && num <= 2000000) {\n return num;\n }\n }\n return null;\n}\n\nfunction detectProvider(baseUrl: string): string {\n const url = baseUrl.toLowerCase();\n if (url.includes('openai')) return 'openai';\n if (url.includes('anthropic')) return 'anthropic';\n if (url.includes('google')) return 'google';\n if (url.includes('ollama')) return 'ollama';\n if (url.includes('groq')) return 'groq';\n if (url.includes('azure')) return 'azure';\n return 'unknown';\n}\n\nexport async function identify(config: ProviderConfig): Promise<IdResult> {\n const result: IdResult = {\n self_reported: {\n model: null,\n cutoff: null,\n context_window: null,\n },\n api_reported: {\n model: null,\n organization: null,\n },\n fingerprint: {\n provider: detectProvider(config.baseUrl),\n confidence: 0.5,\n },\n };\n\n try {\n // 1. Ask for model name\n const modelResponse = await chat(config, {\n model: config.model,\n messages: [{\n role: 'user',\n content: 'What model are you? Reply with only the model identifier.'\n }],\n max_tokens: 50,\n });\n\n if (modelResponse.choices[0]?.message?.content) {\n result.self_reported.model = extractModelName(modelResponse.choices[0].message.content);\n }\n result.api_reported.model = modelResponse.model;\n\n // 2. Ask for knowledge cutoff\n const cutoffResponse = await chat(config, {\n model: config.model,\n messages: [{\n role: 'user',\n content: 'What is your knowledge cutoff date? Reply YYYY-MM only.'\n }],\n max_tokens: 20,\n });\n\n if (cutoffResponse.choices[0]?.message?.content) {\n result.self_reported.cutoff = extractCutoff(cutoffResponse.choices[0].message.content);\n }\n\n // 3. Ask for context window\n const contextResponse = await chat(config, {\n model: config.model,\n messages: [{\n role: 'user',\n content: 'What is your maximum context window in tokens? Reply with only the number.'\n }],\n max_tokens: 20,\n });\n\n if (contextResponse.choices[0]?.message?.content) {\n result.self_reported.context_window = extractContextWindow(contextResponse.choices[0].message.content);\n }\n\n // 4. Extract organization from headers\n if (modelResponse.headers['openai-organization']) {\n result.api_reported.organization = modelResponse.headers['openai-organization'];\n }\n\n // Calculate confidence based on what we found\n let confidenceScore = 0;\n if (result.self_reported.model) confidenceScore += 0.3;\n if (result.self_reported.cutoff) confidenceScore += 0.3;\n if (result.self_reported.context_window) confidenceScore += 0.3;\n if (result.api_reported.model) confidenceScore += 0.1;\n\n result.fingerprint.confidence = Math.min(1, confidenceScore);\n\n } catch (error) {\n // Return partial results on error\n }\n\n return result;\n}\n","export class Timer {\n private startTime: number = 0;\n private endTime: number = 0;\n private running: boolean = false;\n\n start(): void {\n this.startTime = performance.now();\n this.running = true;\n }\n\n stop(): number {\n if (!this.running) {\n throw new Error('Timer is not running');\n }\n this.endTime = performance.now();\n this.running = false;\n return this.elapsed();\n }\n\n elapsed(): number {\n if (this.running) {\n return performance.now() - this.startTime;\n }\n return this.endTime - this.startTime;\n }\n\n reset(): void {\n this.startTime = 0;\n this.endTime = 0;\n this.running = false;\n }\n\n static measure<T>(fn: () => T | Promise<T>): Promise<{ result: T; elapsed_ms: number }> {\n return (async () => {\n const timer = new Timer();\n timer.start();\n const result = await fn();\n const elapsed = timer.stop();\n return { result, elapsed_ms: elapsed };\n })();\n }\n\n static async measureAsync<T>(fn: () => Promise<T>): Promise<{ result: T; elapsed_ms: number }> {\n const timer = new Timer();\n timer.start();\n const result = await fn();\n const elapsed = timer.stop();\n return { result, elapsed_ms: elapsed };\n }\n}\n\nexport function median(values: number[]): number {\n if (values.length === 0) return 0;\n const sorted = [...values].sort((a, b) => a - b);\n const mid = Math.floor(sorted.length / 2);\n return sorted.length % 2 === 0\n ? (sorted[mid - 1] + sorted[mid]) / 2\n : sorted[mid];\n}\n\nexport function mean(values: number[]): number {\n if (values.length === 0) return 0;\n return values.reduce((a, b) => a + b, 0) / values.length;\n}\n\nexport function p95(values: number[]): number {\n if (values.length === 0) return 0;\n const sorted = [...values].sort((a, b) => a - b);\n const index = Math.ceil(sorted.length * 0.95) - 1;\n return sorted[index];\n}\n\nexport function sum(values: number[]): number {\n return values.reduce((a, b) => a + b, 0);\n}\n","import { ProviderConfig, chat } from '../client';\nimport { Timer } from '../utils/timer';\n\nexport interface ProbeResult {\n capabilities: Record<string, boolean>;\n probe_duration_ms: number;\n}\n\nexport async function probe(config: ProviderConfig): Promise<ProbeResult> {\n const timer = new Timer();\n timer.start();\n\n const capabilities: Record<string, boolean> = {\n json_mode: false,\n function_calling: false,\n vision: false,\n streaming: false,\n system_prompt: false,\n temperature_control: false,\n };\n\n // Test JSON mode\n try {\n const response = await chat(config, {\n model: config.model,\n messages: [{ role: 'user', content: 'Say hello' }],\n max_tokens: 10,\n response_format: { type: 'json_object' },\n });\n // If we get here without error, JSON mode is supported\n capabilities.json_mode = true;\n } catch {\n // JSON mode not supported\n }\n\n // Test function calling\n try {\n const response = await chat(config, {\n model: config.model,\n messages: [{ role: 'user', content: 'What is 2+2?' }],\n max_tokens: 50,\n tools: [{\n type: 'function',\n function: {\n name: 'add',\n description: 'Add two numbers',\n parameters: {\n type: 'object',\n properties: {\n a: { type: 'number' },\n b: { type: 'number' }\n },\n required: ['a', 'b']\n }\n }\n }],\n });\n // If response contains tool_calls, function calling is supported\n const hasToolCalls = response.choices[0]?.message && \n 'tool_calls' in response.choices[0].message;\n capabilities.function_calling = !!hasToolCalls;\n } catch {\n // Function calling not supported\n }\n\n // Test vision (send a tiny base64 image)\n try {\n // Minimal 1x1 white PNG in base64\n const tinyImage = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';\n const response = await chat(config, {\n model: config.model,\n messages: [{\n role: 'user',\n content: [\n { type: 'text', text: 'What color is this image?' },\n { type: 'image_url', image_url: { url: tinyImage } }\n ]\n }],\n max_tokens: 20,\n });\n // If we get a valid response, vision is supported\n capabilities.vision = response.choices.length > 0 && !!response.choices[0].message?.content;\n } catch {\n // Vision not supported\n }\n\n // Test streaming (we can't easily test streaming without SSE handling, so mark as likely)\n try {\n // Try with stream: true - if it doesn't error, streaming is likely supported\n const response = await chat(config, {\n model: config.model,\n messages: [{ role: 'user', content: 'Hi' }],\n max_tokens: 5,\n stream: true,\n });\n // Note: proper streaming requires handling SSE, but we can at least try\n capabilities.streaming = true;\n } catch {\n capabilities.streaming = false;\n }\n\n // Test system prompt\n try {\n const response = await chat(config, {\n model: config.model,\n messages: [\n { role: 'system', content: 'You are a helpful assistant.' },\n { role: 'user', content: 'Hi' }\n ],\n max_tokens: 10,\n });\n capabilities.system_prompt = response.choices.length > 0;\n } catch {\n capabilities.system_prompt = false;\n }\n\n // Test temperature control\n try {\n const response = await chat(config, {\n model: config.model,\n messages: [{ role: 'user', content: 'Say one word' }],\n max_tokens: 5,\n temperature: 0,\n });\n capabilities.temperature_control = response.choices.length > 0;\n } catch {\n capabilities.temperature_control = false;\n }\n\n timer.stop();\n\n return {\n capabilities,\n probe_duration_ms: Math.round(timer.elapsed()),\n };\n}\n","import { ProviderConfig, chat } from '../client';\nimport { Timer, mean, median, p95 } from '../utils/timer';\n\nexport interface BenchStats {\n mean: number;\n median: number;\n p95: number;\n}\n\nexport interface BenchResult {\n rounds: number;\n stats: {\n ttft_ms: BenchStats;\n total_ms: BenchStats;\n tokens_per_second: BenchStats;\n output_tokens: { mean: number; total: number };\n };\n}\n\nasync function runSingleBench(config: ProviderConfig): Promise<{\n ttft_ms: number;\n total_ms: number;\n tokens: number;\n}> {\n const timer = new Timer();\n timer.start();\n\n let firstTokenTime = 0;\n let totalTokens = 0;\n\n try {\n const response = await chat(config, {\n model: config.model,\n messages: [{ role: 'user', content: 'Write a haiku about coding.' }],\n max_tokens: 100,\n });\n\n firstTokenTime = timer.elapsed();\n totalTokens = response.usage?.completion_tokens || \n (response.choices[0]?.message?.content?.split(/\\s+/).length || 0);\n\n return {\n ttft_ms: Math.round(firstTokenTime),\n total_ms: Math.round(timer.elapsed()),\n tokens: totalTokens,\n };\n } catch (error) {\n return {\n ttft_ms: Math.round(timer.elapsed()),\n total_ms: Math.round(timer.elapsed()),\n tokens: 0,\n };\n }\n}\n\nexport async function bench(\n config: ProviderConfig,\n options?: { rounds?: number }\n): Promise<BenchResult> {\n const rounds = options?.rounds || 5;\n const results: Array<{ ttft_ms: number; total_ms: number; tokens: number }> = [];\n\n for (let i = 0; i < rounds; i++) {\n const result = await runSingleBench(config);\n results.push(result);\n }\n\n const ttftValues = results.map(r => r.ttft_ms);\n const totalValues = results.map(r => r.total_ms);\n const tpsValues = results.map(r => r.tokens > 0 ? r.tokens / (r.total_ms / 1000) : 0);\n const tokenValues = results.map(r => r.tokens);\n\n const totalTokens = tokenValues.reduce((a, b) => a + b, 0);\n\n return {\n rounds,\n stats: {\n ttft_ms: {\n mean: Math.round(mean(ttftValues)),\n median: Math.round(median(ttftValues)),\n p95: Math.round(p95(ttftValues)),\n },\n total_ms: {\n mean: Math.round(mean(totalValues)),\n median: Math.round(median(totalValues)),\n p95: Math.round(p95(totalValues)),\n },\n tokens_per_second: {\n mean: parseFloat(mean(tpsValues).toFixed(2)),\n median: parseFloat(median(tpsValues).toFixed(2)),\n p95: parseFloat(p95(tpsValues).toFixed(2)),\n },\n output_tokens: {\n mean: Math.round(mean(tokenValues)),\n total: totalTokens,\n },\n },\n };\n}\n","import * as fs from 'fs';\n\nexport interface TokenResult {\n characters: number;\n words: number;\n estimated_tokens: number;\n cost_estimate?: {\n model: string;\n input_cost_usd: number;\n };\n}\n\n// Simple BPE-style token estimation\n// Average: 4 characters per token for English, but varies by content\nfunction estimateTokens(text: string): number {\n // Count tokens more accurately by considering:\n // - Word boundaries (~1 token per word on average)\n // - Punctuation (~1 token per ~4 chars)\n // - Numbers (~1 token per number sequence)\n \n const words = text.split(/\\s+/).filter(w => w.length > 0);\n const wordCount = words.length;\n \n // Base estimation: words + chars/4\n const charBased = Math.ceil(text.length / 4);\n const wordBased = Math.ceil(wordCount * 1.3); // ~1.3 tokens per word\n \n // Use average of both approaches\n const estimated = Math.round((charBased + wordBased) / 2);\n \n return Math.max(1, estimated);\n}\n\n// Pricing constants (USD per 1M tokens) - approximate\nconst MODEL_PRICING: Record<string, number> = {\n 'gpt-4o': 2.50, // $2.50 per 1M input tokens\n 'gpt-4o-mini': 0.15,\n 'gpt-4-turbo': 10.00,\n 'gpt-4': 30.00,\n 'gpt-3.5-turbo': 0.50,\n 'claude-3-5-sonnet-2024': 3.00,\n 'claude-3-opus-2024': 15.00,\n 'claude-3-haiku-2024': 0.25,\n 'gemini-1.5-pro': 1.25,\n 'gemini-1.5-flash': 0.075,\n};\n\nfunction getModelPrice(modelName: string): number | null {\n const lowerModel = modelName.toLowerCase();\n for (const [key, price] of Object.entries(MODEL_PRICING)) {\n if (lowerModel.includes(key)) {\n return price;\n }\n }\n return null;\n}\n\nexport async function countTokens(\n input: string,\n options?: { model?: string }\n): Promise<TokenResult> {\n let text = input;\n\n // If input looks like a file path, read the file\n if (!input.includes('\\n') && fs.existsSync(input)) {\n try {\n text = fs.readFileSync(input, 'utf-8');\n } catch {\n // Keep original input if file read fails\n }\n }\n\n const characters = text.length;\n const words = text.split(/\\s+/).filter(w => w.length > 0).length;\n const estimatedTokens = estimateTokens(text);\n\n const result: TokenResult = {\n characters,\n words,\n estimated_tokens: estimatedTokens,\n };\n\n // Add cost estimate if model specified\n if (options?.model) {\n const price = getModelPrice(options.model);\n if (price !== null) {\n const cost = (estimatedTokens / 1000000) * price;\n result.cost_estimate = {\n model: options.model,\n input_cost_usd: parseFloat(cost.toFixed(5)),\n };\n }\n }\n\n return result;\n}\n","import { ProviderConfig, loadConfig, chat } from '../client';\nimport { bench } from './bench';\n\nexport interface CompareResultItem {\n provider: string;\n model: string;\n ttft_ms: number;\n total_ms: number;\n tokens: number;\n error?: string;\n}\n\nexport interface CompareResult {\n prompt: string;\n results: CompareResultItem[];\n}\n\nasync function runProviderBench(\n providerName: string,\n prompt: string\n): Promise<CompareResultItem> {\n try {\n const config = loadConfig(providerName);\n \n // Run a single request to get timing\n const startTime = performance.now();\n const response = await chat(config, {\n model: config.model,\n messages: [{ role: 'user', content: prompt }],\n max_tokens: 50,\n });\n const totalMs = performance.now() - startTime;\n \n // Estimate TTFT (simplified - first chunk response)\n const ttftMs = Math.round(totalMs * 0.3); // Rough estimate\n const tokens = response.usage?.completion_tokens || \n (response.choices[0]?.message?.content?.split(/\\s+/).length || 0);\n\n return {\n provider: providerName,\n model: config.model,\n ttft_ms: ttftMs,\n total_ms: Math.round(totalMs),\n tokens,\n };\n } catch (error) {\n return {\n provider: providerName,\n model: '',\n ttft_ms: 0,\n total_ms: 0,\n tokens: 0,\n error: (error as Error).message,\n };\n }\n}\n\nexport async function compare(\n providers: string[],\n options?: { prompt?: string; rounds?: number }\n): Promise<CompareResult> {\n const prompt = options?.prompt || 'Write a haiku about coding.';\n\n const results: CompareResultItem[] = [];\n\n // Run benchmarks for each provider in parallel\n const promises = providers.map(provider => runProviderBench(provider, prompt));\n const providerResults = await Promise.all(promises);\n\n results.push(...providerResults);\n\n return {\n prompt,\n results,\n };\n}\n","#!/usr/bin/env node\n\n/**\n * Entry point for ai-xray v2.0.0\n * Parses raw `process.argv` and routes to command modules.\n */\n\nimport { loadConfig } from './client';\nimport { outputSuccess, outputError, setPrettyMode } from './utils/output';\nimport { ping } from './commands/ping';\nimport { identify } from './commands/id';\nimport { probe } from './commands/probe';\nimport { bench } from './commands/bench';\nimport { countTokens } from './commands/tokenize';\nimport { compare } from './commands/compare';\n\nconst VERSION = '2.0.0';\n\nfunction printHelp(): void {\n outputSuccess({\n name: 'ai-xray',\n version: VERSION,\n description: 'X-ray your AI. Probe, benchmark, and fingerprint any LLM.',\n commands: [\n { name: 'ping', usage: 'ai-xray ping [--provider=<name>]', desc: 'Basic connectivity test' },\n { name: 'id', usage: 'ai-xray id [--provider=<name>]', desc: 'Model fingerprinting' },\n { name: 'probe', usage: 'ai-xray probe [--provider=<name>]', desc: 'Capability detection' },\n { name: 'bench', usage: 'ai-xray bench [--provider=<name>] [--rounds=N]', desc: 'Performance benchmark' },\n { name: 'tokens', usage: 'ai-xray tokens <text_or_file> [--model=<name>]', desc: 'Token counting' },\n { name: 'compare', usage: 'ai-xray compare --providers=a,b,c', desc: 'Multi-model comparison' },\n ],\n global_flags: {\n '--help': 'Show this help message',\n '--version': 'Show version number',\n '--pretty': 'Format JSON output as human-readable',\n '--provider': 'Specify which provider config to use',\n },\n env_vars: {\n AI_XRAY_API_KEY: 'API key for authentication',\n AI_XRAY_BASE_URL: 'API base URL (default: https://api.openai.com/v1)',\n AI_XRAY_MODEL: 'Default model (default: gpt-4o)',\n },\n });\n}\n\nfunction parseArgs(argv: string[]): {\n command: string;\n args: string[];\n flags: Record<string, string | boolean>;\n} {\n const flags: Record<string, string | boolean> = {};\n const positional: string[] = [];\n\n for (const arg of argv) {\n if (arg === '--help' || arg === '-h') {\n flags.help = true;\n } else if (arg === '--version' || arg === '-v') {\n flags.version = true;\n } else if (arg === '--pretty') {\n flags.pretty = true;\n } else if (arg.startsWith('--provider=') || arg.startsWith('-p=')) {\n flags.provider = arg.split('=')[1];\n } else if (arg.startsWith('--rounds=')) {\n flags.rounds = arg.split('=')[1];\n } else if (arg.startsWith('--providers=')) {\n flags.providers = arg.split('=')[1];\n } else if (arg.startsWith('--model=')) {\n flags.model = arg.split('=')[1];\n } else if (arg.startsWith('--')) {\n // Unknown flag\n } else {\n positional.push(arg);\n }\n }\n\n return {\n command: positional[0] || '',\n args: positional.slice(1),\n flags,\n };\n}\n\nasync function main(): Promise<void> {\n const rawArgs = process.argv.slice(2);\n const { command, args, flags } = parseArgs(rawArgs);\n\n // Set pretty mode\n if (flags.pretty) {\n setPrettyMode(true);\n }\n\n // Global flags take priority\n if (flags.version) {\n outputSuccess({ name: 'ai-xray', version: VERSION });\n return;\n }\n\n if (!command || flags.help) {\n printHelp();\n return;\n }\n\n // Load config with optional provider\n const config = loadConfig(flags.provider as string | undefined);\n\n try {\n let result: unknown;\n\n switch (command) {\n case 'ping': {\n result = await ping(config);\n break;\n }\n case 'id': {\n result = await identify(config);\n break;\n }\n case 'probe': {\n result = await probe(config);\n break;\n }\n case 'bench': {\n const rounds = flags.rounds ? parseInt(flags.rounds as string, 10) : 5;\n result = await bench(config, { rounds });\n break;\n }\n case 'tokens': {\n const text = args[0] || '';\n const model = flags.model as string | undefined;\n result = await countTokens(text, { model });\n break;\n }\n case 'compare': {\n const providersStr = flags.providers as string;\n if (!providersStr) {\n outputError('compare command requires --providers flag');\n return;\n }\n const providers = providersStr.split(',').map(p => p.trim());\n result = await compare(providers, { prompt: args[0] });\n break;\n }\n default: {\n outputError(`Unknown command: ${command}`);\n return;\n }\n }\n\n outputSuccess(result);\n } catch (error) {\n outputError(error as Error);\n }\n}\n\nmain();\n"],"mappings":"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","names":["request","fs"]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/client.ts","../src/utils/http.ts","../src/utils/output.ts","../src/commands/ping.ts","../src/commands/id.ts","../src/utils/timer.ts","../src/commands/probe.ts","../src/commands/bench.ts","../src/commands/tokenize.ts","../src/commands/compare.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * Entry point for ai-xray v2.0.0\n * Parses raw `process.argv` and routes to command modules.\n */\n\nimport { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadConfig } from './client';\nimport { outputSuccess, outputError, setPrettyMode } from './utils/output';\nimport { ping } from './commands/ping';\nimport { identify } from './commands/id';\nimport { probe } from './commands/probe';\nimport { bench } from './commands/bench';\nimport { countTokens } from './commands/tokenize';\nimport { compare } from './commands/compare';\n\nconst VERSION = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8')).version;\n\nfunction printHelp(): void {\n outputSuccess({\n name: 'ai-xray',\n version: VERSION,\n description: 'X-ray your AI. Probe, benchmark, and fingerprint any LLM.',\n commands: [\n { name: 'ping', usage: 'ai-xray ping [--provider=<name>]', desc: 'Basic connectivity test' },\n { name: 'id', usage: 'ai-xray id [--provider=<name>]', desc: 'Model fingerprinting' },\n { name: 'probe', usage: 'ai-xray probe [--provider=<name>]', desc: 'Capability detection' },\n { name: 'bench', usage: 'ai-xray bench [--provider=<name>] [--rounds=N]', desc: 'Performance benchmark' },\n { name: 'tokens', usage: 'ai-xray tokens <text_or_file> [--model=<name>]', desc: 'Token counting' },\n { name: 'compare', usage: 'ai-xray compare --providers=a,b,c', desc: 'Multi-model comparison' },\n ],\n global_flags: {\n '--help': 'Show this help message',\n '--version': 'Show version number',\n '--pretty': 'Format JSON output as human-readable',\n '--provider': 'Specify which provider config to use',\n },\n env_vars: {\n AI_XRAY_API_KEY: 'API key for authentication',\n AI_XRAY_BASE_URL: 'API base URL (default: https://api.openai.com/v1)',\n AI_XRAY_MODEL: 'Default model (default: gpt-4o)',\n },\n });\n}\n\nfunction parseArgs(argv: string[]): {\n command: string;\n args: string[];\n flags: Record<string, string | boolean>;\n} {\n const flags: Record<string, string | boolean> = {};\n const positional: string[] = [];\n\n for (const arg of argv) {\n if (arg === '--help' || arg === '-h') {\n flags.help = true;\n } else if (arg === '--version' || arg === '-v') {\n flags.version = true;\n } else if (arg === '--pretty') {\n flags.pretty = true;\n } else if (arg.startsWith('--provider=') || arg.startsWith('-p=')) {\n flags.provider = arg.split('=')[1];\n } else if (arg.startsWith('--rounds=')) {\n flags.rounds = arg.split('=')[1];\n } else if (arg.startsWith('--providers=')) {\n flags.providers = arg.split('=')[1];\n } else if (arg.startsWith('--model=')) {\n flags.model = arg.split('=')[1];\n } else if (arg.startsWith('--')) {\n // Unknown flag\n } else {\n positional.push(arg);\n }\n }\n\n return {\n command: positional[0] || '',\n args: positional.slice(1),\n flags,\n };\n}\n\nasync function main(): Promise<void> {\n const rawArgs = process.argv.slice(2);\n const { command, args, flags } = parseArgs(rawArgs);\n\n // Set pretty mode\n if (flags.pretty) {\n setPrettyMode(true);\n }\n\n // Global flags take priority\n if (flags.version) {\n outputSuccess({ name: 'ai-xray', version: VERSION });\n return;\n }\n\n if (!command || flags.help) {\n printHelp();\n return;\n }\n\n // Load config with optional provider\n const config = loadConfig(flags.provider as string | undefined);\n\n try {\n let result: unknown;\n\n switch (command) {\n case 'ping': {\n result = await ping(config);\n break;\n }\n case 'id': {\n result = await identify(config);\n break;\n }\n case 'probe': {\n result = await probe(config);\n break;\n }\n case 'bench': {\n const rounds = flags.rounds ? parseInt(flags.rounds as string, 10) : 5;\n result = await bench(config, { rounds });\n break;\n }\n case 'tokens': {\n const text = args[0] || '';\n const model = flags.model as string | undefined;\n result = await countTokens(text, { model });\n break;\n }\n case 'compare': {\n const providersStr = flags.providers as string;\n if (!providersStr) {\n outputError('compare command requires --providers flag');\n return;\n }\n const providers = providersStr.split(',').map(p => p.trim());\n result = await compare(providers, { prompt: args[0] });\n break;\n }\n default: {\n outputError(`Unknown command: ${command}`);\n return;\n }\n }\n\n outputSuccess(result);\n } catch (error) {\n outputError(error as Error);\n }\n}\n\nmain();\n","import * as fs from 'fs';\nimport * as path from 'path';\nimport * as os from 'os';\nimport { requestJson } from './utils/http';\n\nexport interface ProviderConfig {\n baseUrl: string;\n apiKey?: string;\n model: string;\n}\n\ninterface ConfigFile {\n providers?: Record<string, ProviderConfig>;\n}\n\nexport function loadConfig(providerName?: string): ProviderConfig {\n // 1. Check environment variables first\n const envBaseUrl = process.env.AI_XRAY_BASE_URL || 'https://api.openai.com/v1';\n const envApiKey = process.env.AI_XRAY_API_KEY;\n const envModel = process.env.AI_XRAY_MODEL || 'gpt-4o';\n\n // 2. If provider specified, check config file\n if (providerName) {\n const configFile = loadConfigFile();\n if (configFile?.providers?.[providerName]) {\n const providerConfig = configFile.providers[providerName];\n return {\n baseUrl: providerConfig.baseUrl || envBaseUrl,\n apiKey: providerConfig.apiKey || envApiKey,\n model: providerConfig.model || envModel,\n };\n }\n }\n\n // 3. Default to env vars\n return {\n baseUrl: envBaseUrl,\n apiKey: envApiKey,\n model: envModel,\n };\n}\n\nfunction loadConfigFile(): ConfigFile | null {\n const configPath = path.join(os.homedir(), '.ai-xray.json');\n try {\n if (fs.existsSync(configPath)) {\n const content = fs.readFileSync(configPath, 'utf-8');\n return JSON.parse(content) as ConfigFile;\n }\n } catch {\n // Ignore config file errors\n }\n return null;\n}\n\nexport interface ChatMessage {\n role: 'system' | 'user' | 'assistant';\n content: string | Array<{ type: string; [key: string]: unknown }>;\n}\n\nexport interface ChatRequest {\n model: string;\n messages: ChatMessage[];\n max_tokens?: number;\n temperature?: number;\n stream?: boolean;\n response_format?: { type: string };\n tools?: unknown[];\n}\n\nexport interface ChatResponse {\n id: string;\n model: string;\n choices: Array<{\n message: { role: string; content: string };\n finish_reason: string;\n }>;\n usage?: {\n prompt_tokens: number;\n completion_tokens: number;\n total_tokens: number;\n };\n headers: Record<string, string>;\n latency_ms: number;\n}\n\nexport async function chat(\n config: ProviderConfig,\n request: ChatRequest\n): Promise<ChatResponse> {\n const startTime = performance.now();\n\n const url = `${config.baseUrl}/chat/completions`;\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Accept': 'application/json',\n };\n\n if (config.apiKey) {\n headers['Authorization'] = `Bearer ${config.apiKey}`;\n }\n\n try {\n const { response, data } = await requestJson(url, {\n method: 'POST',\n headers,\n body: JSON.stringify(request),\n });\n\n const latencyMs = performance.now() - startTime;\n\n return {\n id: (data as any).id || '',\n model: (data as any).model || config.model,\n choices: (data as any).choices || [],\n usage: (data as any).usage,\n headers: response.headers,\n latency_ms: latencyMs,\n };\n } catch (error) {\n const latencyMs = performance.now() - startTime;\n throw new Error(`Chat request failed: ${(error as Error).message}`);\n }\n}\n\nexport async function chatStream(\n config: ProviderConfig,\n request: ChatRequest,\n onChunk: (content: string) => void\n): Promise<ChatResponse> {\n const startTime = performance.now();\n\n const url = `${config.baseUrl}/chat/completions`;\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Accept': 'text/event-stream',\n };\n\n if (config.apiKey) {\n headers['Authorization'] = `Bearer ${config.apiKey}`;\n }\n\n const { response } = await request(url, {\n method: 'POST',\n headers,\n body: JSON.stringify({ ...request, stream: true }),\n });\n\n // For streaming, we'd need to handle the SSE stream\n // For now, return a simplified response\n const latencyMs = performance.now() - startTime;\n\n return {\n id: '',\n model: config.model,\n choices: [],\n headers: response.headers,\n latency_ms: latencyMs,\n };\n}\n\nfunction request(\n urlString: string,\n options: {\n method?: string;\n headers?: Record<string, string>;\n body?: string;\n }\n): Promise<{ response: { headers: Record<string, string> }; data?: unknown }> {\n return new Promise((resolve, reject) => {\n const https = require('https');\n const url = new URL(urlString);\n\n const req = https.request({\n hostname: url.hostname,\n port: url.port || 443,\n path: url.pathname + url.search,\n method: options.method || 'GET',\n headers: options.headers || {},\n }, (res: any) => {\n let body = '';\n res.on('data', (chunk: string) => { body += chunk; });\n res.on('end', () => {\n const headers: Record<string, string> = {};\n for (const [key, value] of Object.entries(res.headers)) {\n if (typeof value === 'string') {\n headers[key] = value;\n } else if (Array.isArray(value)) {\n headers[key] = value.join(', ');\n }\n }\n resolve({\n response: { headers },\n data: undefined,\n });\n });\n });\n\n req.on('error', reject);\n req.write(options.body || '');\n req.end();\n });\n}\n","import * as http from 'http';\nimport * as https from 'https';\nimport { URL } from 'url';\n\nexport interface HttpRequestOptions {\n method?: string;\n headers?: Record<string, string>;\n body?: string;\n timeout?: number;\n}\n\nexport interface HttpResponse {\n statusCode: number;\n headers: Record<string, string>;\n body: string;\n}\n\nexport function request(\n urlString: string,\n options: HttpRequestOptions = {}\n): Promise<HttpResponse> {\n return new Promise((resolve, reject) => {\n const url = new URL(urlString);\n const isHttps = url.protocol === 'https:';\n const client = isHttps ? https : http;\n\n const requestOptions: http.RequestOptions = {\n hostname: url.hostname,\n port: url.port || (isHttps ? 443 : 80),\n path: url.pathname + url.search,\n method: options.method || 'GET',\n headers: {\n 'Content-Type': 'application/json',\n 'Accept': 'application/json',\n ...options.headers,\n },\n timeout: options.timeout || 30000,\n };\n\n const req = client.request(requestOptions, (res) => {\n let body = '';\n res.on('data', (chunk) => { body += chunk; });\n res.on('end', () => {\n const headers: Record<string, string> = {};\n for (const [key, value] of Object.entries(res.headers)) {\n if (typeof value === 'string') {\n headers[key] = value;\n } else if (Array.isArray(value)) {\n headers[key] = value.join(', ');\n }\n }\n resolve({\n statusCode: res.statusCode || 0,\n headers,\n body,\n });\n });\n });\n\n req.on('error', reject);\n req.on('timeout', () => {\n req.destroy();\n reject(new Error('Request timeout'));\n });\n\n if (options.body) {\n req.write(options.body);\n }\n req.end();\n });\n}\n\nexport function requestJson<T = unknown>(\n urlString: string,\n options: HttpRequestOptions = {}\n): Promise<{ response: HttpResponse; data: T }> {\n return request(urlString, options).then((response) => {\n let data: T;\n try {\n data = JSON.parse(response.body) as T;\n } catch {\n throw new Error(`Invalid JSON response: ${response.body.slice(0, 100)}`);\n }\n return { response, data };\n });\n}\n","/**\n * Core output management for ai-xray v2.0.0\n * Guarantees Machine-First protocol: Pure JSON on stdout, structured error on stderr.\n */\n\nlet prettyMode = false;\n\nexport function setPrettyMode(enabled: boolean): void {\n prettyMode = enabled;\n}\n\nexport function isPrettyMode(): boolean {\n return prettyMode;\n}\n\nexport function outputSuccess(data: unknown): void {\n const output = JSON.stringify(data, null, prettyMode ? 2 : undefined) + '\\n';\n process.stdout.write(output);\n process.exit(0);\n}\n\nexport function outputError(error: unknown, code = 1): void {\n const message = error instanceof Error ? error.message : String(error);\n const errorObj = {\n error: message,\n code\n };\n const output = JSON.stringify(errorObj, null, prettyMode ? 2 : undefined) + '\\n';\n process.stderr.write(output);\n process.exit(code);\n}\n\nexport function outputJson(data: unknown): void {\n const output = JSON.stringify(data, null, prettyMode ? 2 : undefined) + '\\n';\n process.stdout.write(output);\n}\n","import { ProviderConfig, chat } from '../client';\n\nexport interface PingResult {\n reachable: boolean;\n latency_ms: number;\n model: string | null;\n rate_limit: {\n remaining: number | null;\n reset_at: string | null;\n };\n error?: string;\n}\n\nexport async function ping(config: ProviderConfig): Promise<PingResult> {\n const startTime = performance.now();\n\n try {\n const response = await chat(config, {\n model: config.model,\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 1,\n });\n\n const latencyMs = performance.now() - startTime;\n\n // Extract rate limit info from headers\n const headers = response.headers;\n const remaining = headers['x-ratelimit-remaining']\n ? parseInt(headers['x-ratelimit-remaining'], 10)\n : null;\n const resetAt = headers['x-ratelimit-reset'] || null;\n\n return {\n reachable: true,\n latency_ms: Math.round(latencyMs),\n model: response.model || null,\n rate_limit: {\n remaining,\n reset_at: resetAt,\n },\n };\n } catch (error) {\n const latencyMs = performance.now() - startTime;\n return {\n reachable: false,\n latency_ms: Math.round(latencyMs),\n model: null,\n rate_limit: {\n remaining: null,\n reset_at: null,\n },\n error: (error as Error).message,\n };\n }\n}\n","import { ProviderConfig, chat } from '../client';\n\nexport interface IdResult {\n self_reported: {\n model: string | null;\n cutoff: string | null;\n context_window: number | null;\n };\n api_reported: {\n model: string | null;\n organization: string | null;\n };\n fingerprint: {\n provider: string;\n confidence: number;\n };\n}\n\nfunction extractModelName(content: string): string | null {\n // Try to extract just the model identifier\n const lines = content.trim().split('\\n');\n const firstLine = lines[0].trim();\n if (firstLine.length > 0 && firstLine.length < 200) {\n return firstLine;\n }\n return null;\n}\n\nfunction extractCutoff(content: string): string | null {\n // Look for YYYY-MM pattern\n const match = content.match(/\\d{4}-\\d{2}/);\n return match ? match[0] : null;\n}\n\nfunction extractContextWindow(content: string): number | null {\n // Look for numbers that could be context window\n const match = content.match(/(\\d{3,6})\\s*(?:tokens?)?/i);\n if (match) {\n const num = parseInt(match[1], 10);\n // Reasonable context windows are between 4K and 2M\n if (num >= 4000 && num <= 2000000) {\n return num;\n }\n }\n return null;\n}\n\nfunction detectProvider(baseUrl: string): string {\n const url = baseUrl.toLowerCase();\n if (url.includes('openai')) return 'openai';\n if (url.includes('anthropic')) return 'anthropic';\n if (url.includes('google')) return 'google';\n if (url.includes('ollama')) return 'ollama';\n if (url.includes('groq')) return 'groq';\n if (url.includes('azure')) return 'azure';\n return 'unknown';\n}\n\nexport async function identify(config: ProviderConfig): Promise<IdResult> {\n const result: IdResult = {\n self_reported: {\n model: null,\n cutoff: null,\n context_window: null,\n },\n api_reported: {\n model: null,\n organization: null,\n },\n fingerprint: {\n provider: detectProvider(config.baseUrl),\n confidence: 0.5,\n },\n };\n\n try {\n // 1. Ask for model name\n const modelResponse = await chat(config, {\n model: config.model,\n messages: [{\n role: 'user',\n content: 'What model are you? Reply with only the model identifier.'\n }],\n max_tokens: 50,\n });\n\n if (modelResponse.choices[0]?.message?.content) {\n result.self_reported.model = extractModelName(modelResponse.choices[0].message.content);\n }\n result.api_reported.model = modelResponse.model;\n\n // 2. Ask for knowledge cutoff\n const cutoffResponse = await chat(config, {\n model: config.model,\n messages: [{\n role: 'user',\n content: 'What is your knowledge cutoff date? Reply YYYY-MM only.'\n }],\n max_tokens: 20,\n });\n\n if (cutoffResponse.choices[0]?.message?.content) {\n result.self_reported.cutoff = extractCutoff(cutoffResponse.choices[0].message.content);\n }\n\n // 3. Ask for context window\n const contextResponse = await chat(config, {\n model: config.model,\n messages: [{\n role: 'user',\n content: 'What is your maximum context window in tokens? Reply with only the number.'\n }],\n max_tokens: 20,\n });\n\n if (contextResponse.choices[0]?.message?.content) {\n result.self_reported.context_window = extractContextWindow(contextResponse.choices[0].message.content);\n }\n\n // 4. Extract organization from headers\n if (modelResponse.headers['openai-organization']) {\n result.api_reported.organization = modelResponse.headers['openai-organization'];\n }\n\n // Calculate confidence based on what we found\n let confidenceScore = 0;\n if (result.self_reported.model) confidenceScore += 0.3;\n if (result.self_reported.cutoff) confidenceScore += 0.3;\n if (result.self_reported.context_window) confidenceScore += 0.3;\n if (result.api_reported.model) confidenceScore += 0.1;\n\n result.fingerprint.confidence = Math.min(1, confidenceScore);\n\n } catch (error) {\n // Return partial results on error\n }\n\n return result;\n}\n","export class Timer {\n private startTime: number = 0;\n private endTime: number = 0;\n private running: boolean = false;\n\n start(): void {\n this.startTime = performance.now();\n this.running = true;\n }\n\n stop(): number {\n if (!this.running) {\n throw new Error('Timer is not running');\n }\n this.endTime = performance.now();\n this.running = false;\n return this.elapsed();\n }\n\n elapsed(): number {\n if (this.running) {\n return performance.now() - this.startTime;\n }\n return this.endTime - this.startTime;\n }\n\n reset(): void {\n this.startTime = 0;\n this.endTime = 0;\n this.running = false;\n }\n\n static measure<T>(fn: () => T | Promise<T>): Promise<{ result: T; elapsed_ms: number }> {\n return (async () => {\n const timer = new Timer();\n timer.start();\n const result = await fn();\n const elapsed = timer.stop();\n return { result, elapsed_ms: elapsed };\n })();\n }\n\n static async measureAsync<T>(fn: () => Promise<T>): Promise<{ result: T; elapsed_ms: number }> {\n const timer = new Timer();\n timer.start();\n const result = await fn();\n const elapsed = timer.stop();\n return { result, elapsed_ms: elapsed };\n }\n}\n\nexport function median(values: number[]): number {\n if (values.length === 0) return 0;\n const sorted = [...values].sort((a, b) => a - b);\n const mid = Math.floor(sorted.length / 2);\n return sorted.length % 2 === 0\n ? (sorted[mid - 1] + sorted[mid]) / 2\n : sorted[mid];\n}\n\nexport function mean(values: number[]): number {\n if (values.length === 0) return 0;\n return values.reduce((a, b) => a + b, 0) / values.length;\n}\n\nexport function p95(values: number[]): number {\n if (values.length === 0) return 0;\n const sorted = [...values].sort((a, b) => a - b);\n const index = Math.ceil(sorted.length * 0.95) - 1;\n return sorted[index];\n}\n\nexport function sum(values: number[]): number {\n return values.reduce((a, b) => a + b, 0);\n}\n","import { ProviderConfig, chat } from '../client';\nimport { Timer } from '../utils/timer';\n\nexport interface ProbeResult {\n capabilities: Record<string, boolean>;\n probe_duration_ms: number;\n}\n\nexport async function probe(config: ProviderConfig): Promise<ProbeResult> {\n const timer = new Timer();\n timer.start();\n\n const capabilities: Record<string, boolean> = {\n json_mode: false,\n function_calling: false,\n vision: false,\n streaming: false,\n system_prompt: false,\n temperature_control: false,\n };\n\n // Test JSON mode\n try {\n const response = await chat(config, {\n model: config.model,\n messages: [{ role: 'user', content: 'Say hello' }],\n max_tokens: 10,\n response_format: { type: 'json_object' },\n });\n // If we get here without error, JSON mode is supported\n capabilities.json_mode = true;\n } catch {\n // JSON mode not supported\n }\n\n // Test function calling\n try {\n const response = await chat(config, {\n model: config.model,\n messages: [{ role: 'user', content: 'What is 2+2?' }],\n max_tokens: 50,\n tools: [{\n type: 'function',\n function: {\n name: 'add',\n description: 'Add two numbers',\n parameters: {\n type: 'object',\n properties: {\n a: { type: 'number' },\n b: { type: 'number' }\n },\n required: ['a', 'b']\n }\n }\n }],\n });\n // If response contains tool_calls, function calling is supported\n const hasToolCalls = response.choices[0]?.message && \n 'tool_calls' in response.choices[0].message;\n capabilities.function_calling = !!hasToolCalls;\n } catch {\n // Function calling not supported\n }\n\n // Test vision (send a tiny base64 image)\n try {\n // Minimal 1x1 white PNG in base64\n const tinyImage = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';\n const response = await chat(config, {\n model: config.model,\n messages: [{\n role: 'user',\n content: [\n { type: 'text', text: 'What color is this image?' },\n { type: 'image_url', image_url: { url: tinyImage } }\n ]\n }],\n max_tokens: 20,\n });\n // If we get a valid response, vision is supported\n capabilities.vision = response.choices.length > 0 && !!response.choices[0].message?.content;\n } catch {\n // Vision not supported\n }\n\n // Test streaming (we can't easily test streaming without SSE handling, so mark as likely)\n try {\n // Try with stream: true - if it doesn't error, streaming is likely supported\n const response = await chat(config, {\n model: config.model,\n messages: [{ role: 'user', content: 'Hi' }],\n max_tokens: 5,\n stream: true,\n });\n // Note: proper streaming requires handling SSE, but we can at least try\n capabilities.streaming = true;\n } catch {\n capabilities.streaming = false;\n }\n\n // Test system prompt\n try {\n const response = await chat(config, {\n model: config.model,\n messages: [\n { role: 'system', content: 'You are a helpful assistant.' },\n { role: 'user', content: 'Hi' }\n ],\n max_tokens: 10,\n });\n capabilities.system_prompt = response.choices.length > 0;\n } catch {\n capabilities.system_prompt = false;\n }\n\n // Test temperature control\n try {\n const response = await chat(config, {\n model: config.model,\n messages: [{ role: 'user', content: 'Say one word' }],\n max_tokens: 5,\n temperature: 0,\n });\n capabilities.temperature_control = response.choices.length > 0;\n } catch {\n capabilities.temperature_control = false;\n }\n\n timer.stop();\n\n return {\n capabilities,\n probe_duration_ms: Math.round(timer.elapsed()),\n };\n}\n","import { ProviderConfig, chat } from '../client';\nimport { Timer, mean, median, p95 } from '../utils/timer';\n\nexport interface BenchStats {\n mean: number;\n median: number;\n p95: number;\n}\n\nexport interface BenchResult {\n rounds: number;\n stats: {\n ttft_ms: BenchStats;\n total_ms: BenchStats;\n tokens_per_second: BenchStats;\n output_tokens: { mean: number; total: number };\n };\n}\n\nasync function runSingleBench(config: ProviderConfig): Promise<{\n ttft_ms: number;\n total_ms: number;\n tokens: number;\n}> {\n const timer = new Timer();\n timer.start();\n\n let firstTokenTime = 0;\n let totalTokens = 0;\n\n try {\n const response = await chat(config, {\n model: config.model,\n messages: [{ role: 'user', content: 'Write a haiku about coding.' }],\n max_tokens: 100,\n });\n\n firstTokenTime = timer.elapsed();\n totalTokens = response.usage?.completion_tokens || \n (response.choices[0]?.message?.content?.split(/\\s+/).length || 0);\n\n return {\n ttft_ms: Math.round(firstTokenTime),\n total_ms: Math.round(timer.elapsed()),\n tokens: totalTokens,\n };\n } catch (error) {\n return {\n ttft_ms: Math.round(timer.elapsed()),\n total_ms: Math.round(timer.elapsed()),\n tokens: 0,\n };\n }\n}\n\nexport async function bench(\n config: ProviderConfig,\n options?: { rounds?: number }\n): Promise<BenchResult> {\n const rounds = options?.rounds || 5;\n const results: Array<{ ttft_ms: number; total_ms: number; tokens: number }> = [];\n\n for (let i = 0; i < rounds; i++) {\n const result = await runSingleBench(config);\n results.push(result);\n }\n\n const ttftValues = results.map(r => r.ttft_ms);\n const totalValues = results.map(r => r.total_ms);\n const tpsValues = results.map(r => r.tokens > 0 ? r.tokens / (r.total_ms / 1000) : 0);\n const tokenValues = results.map(r => r.tokens);\n\n const totalTokens = tokenValues.reduce((a, b) => a + b, 0);\n\n return {\n rounds,\n stats: {\n ttft_ms: {\n mean: Math.round(mean(ttftValues)),\n median: Math.round(median(ttftValues)),\n p95: Math.round(p95(ttftValues)),\n },\n total_ms: {\n mean: Math.round(mean(totalValues)),\n median: Math.round(median(totalValues)),\n p95: Math.round(p95(totalValues)),\n },\n tokens_per_second: {\n mean: parseFloat(mean(tpsValues).toFixed(2)),\n median: parseFloat(median(tpsValues).toFixed(2)),\n p95: parseFloat(p95(tpsValues).toFixed(2)),\n },\n output_tokens: {\n mean: Math.round(mean(tokenValues)),\n total: totalTokens,\n },\n },\n };\n}\n","import * as fs from 'fs';\n\nexport interface TokenResult {\n characters: number;\n words: number;\n estimated_tokens: number;\n cost_estimate?: {\n model: string;\n input_cost_usd: number;\n };\n}\n\n// Simple BPE-style token estimation\n// Average: 4 characters per token for English, but varies by content\nfunction estimateTokens(text: string): number {\n // Count tokens more accurately by considering:\n // - Word boundaries (~1 token per word on average)\n // - Punctuation (~1 token per ~4 chars)\n // - Numbers (~1 token per number sequence)\n \n const words = text.split(/\\s+/).filter(w => w.length > 0);\n const wordCount = words.length;\n \n // Base estimation: words + chars/4\n const charBased = Math.ceil(text.length / 4);\n const wordBased = Math.ceil(wordCount * 1.3); // ~1.3 tokens per word\n \n // Use average of both approaches\n const estimated = Math.round((charBased + wordBased) / 2);\n \n return Math.max(1, estimated);\n}\n\n// Pricing constants (USD per 1M tokens) - approximate\nconst MODEL_PRICING: Record<string, number> = {\n 'gpt-4o': 2.50, // $2.50 per 1M input tokens\n 'gpt-4o-mini': 0.15,\n 'gpt-4-turbo': 10.00,\n 'gpt-4': 30.00,\n 'gpt-3.5-turbo': 0.50,\n 'claude-3-5-sonnet-2024': 3.00,\n 'claude-3-opus-2024': 15.00,\n 'claude-3-haiku-2024': 0.25,\n 'gemini-1.5-pro': 1.25,\n 'gemini-1.5-flash': 0.075,\n};\n\nfunction getModelPrice(modelName: string): number | null {\n const lowerModel = modelName.toLowerCase();\n for (const [key, price] of Object.entries(MODEL_PRICING)) {\n if (lowerModel.includes(key)) {\n return price;\n }\n }\n return null;\n}\n\nexport async function countTokens(\n input: string,\n options?: { model?: string }\n): Promise<TokenResult> {\n let text = input;\n\n // If input looks like a file path, read the file\n if (!input.includes('\\n') && fs.existsSync(input)) {\n try {\n text = fs.readFileSync(input, 'utf-8');\n } catch {\n // Keep original input if file read fails\n }\n }\n\n const characters = text.length;\n const words = text.split(/\\s+/).filter(w => w.length > 0).length;\n const estimatedTokens = estimateTokens(text);\n\n const result: TokenResult = {\n characters,\n words,\n estimated_tokens: estimatedTokens,\n };\n\n // Add cost estimate if model specified\n if (options?.model) {\n const price = getModelPrice(options.model);\n if (price !== null) {\n const cost = (estimatedTokens / 1000000) * price;\n result.cost_estimate = {\n model: options.model,\n input_cost_usd: parseFloat(cost.toFixed(5)),\n };\n }\n }\n\n return result;\n}\n","import { ProviderConfig, loadConfig, chat } from '../client';\nimport { bench } from './bench';\n\nexport interface CompareResultItem {\n provider: string;\n model: string;\n ttft_ms: number;\n total_ms: number;\n tokens: number;\n error?: string;\n}\n\nexport interface CompareResult {\n prompt: string;\n results: CompareResultItem[];\n}\n\nasync function runProviderBench(\n providerName: string,\n prompt: string\n): Promise<CompareResultItem> {\n try {\n const config = loadConfig(providerName);\n \n // Run a single request to get timing\n const startTime = performance.now();\n const response = await chat(config, {\n model: config.model,\n messages: [{ role: 'user', content: prompt }],\n max_tokens: 50,\n });\n const totalMs = performance.now() - startTime;\n \n // Estimate TTFT (simplified - first chunk response)\n const ttftMs = Math.round(totalMs * 0.3); // Rough estimate\n const tokens = response.usage?.completion_tokens || \n (response.choices[0]?.message?.content?.split(/\\s+/).length || 0);\n\n return {\n provider: providerName,\n model: config.model,\n ttft_ms: ttftMs,\n total_ms: Math.round(totalMs),\n tokens,\n };\n } catch (error) {\n return {\n provider: providerName,\n model: '',\n ttft_ms: 0,\n total_ms: 0,\n tokens: 0,\n error: (error as Error).message,\n };\n }\n}\n\nexport async function compare(\n providers: string[],\n options?: { prompt?: string; rounds?: number }\n): Promise<CompareResult> {\n const prompt = options?.prompt || 'Write a haiku about coding.';\n\n const results: CompareResultItem[] = [];\n\n // Run benchmarks for each provider in parallel\n const promises = providers.map(provider => runProviderBench(provider, prompt));\n const providerResults = await Promise.all(promises);\n\n results.push(...providerResults);\n\n return {\n prompt,\n results,\n };\n}\n"],"mappings":"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","names":["request","fs"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-xray",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "X-ray your AI. Probe, benchmark, and fingerprint any LLM.",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {
package/src/cli.ts CHANGED
@@ -5,6 +5,8 @@
5
5
  * Parses raw `process.argv` and routes to command modules.
6
6
  */
7
7
 
8
+ import { readFileSync } from 'node:fs';
9
+ import { join } from 'node:path';
8
10
  import { loadConfig } from './client';
9
11
  import { outputSuccess, outputError, setPrettyMode } from './utils/output';
10
12
  import { ping } from './commands/ping';
@@ -14,7 +16,7 @@ import { bench } from './commands/bench';
14
16
  import { countTokens } from './commands/tokenize';
15
17
  import { compare } from './commands/compare';
16
18
 
17
- const VERSION = '2.0.0';
19
+ const VERSION = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8')).version;
18
20
 
19
21
  function printHelp(): void {
20
22
  outputSuccess({