|
|
|
|
|
import { ModelAdapter, TransformedPrompt, SymbolicCommand } from './adapters/base'; |
|
import { ClaudeAdapter } from './adapters/claude'; |
|
import { OpenAIAdapter } from './adapters/openai'; |
|
import { QwenAdapter } from './adapters/qwen'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
type Provider = 'anthropic' | 'openai' | 'qwen' | 'gemini' | 'vllm' | 'ollama' | 'lmstudio'; |
|
|
|
interface UniversalLLMOptions { |
|
provider: Provider; |
|
apiKey: string; |
|
model?: string; |
|
maxTokens?: number; |
|
temperature?: number; |
|
baseURL?: string; |
|
[key: string]: any; |
|
} |
|
|
|
interface GenerateOptions { |
|
prompt: string; |
|
systemPrompt?: string; |
|
} |
|
|
|
interface SymbolicTelemetry { |
|
enabled: boolean; |
|
endpoint?: string; |
|
anonymousId?: string; |
|
sessionId?: string; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
export class UniversalLLM { |
|
private adapter: ModelAdapter; |
|
private telemetry: SymbolicTelemetry; |
|
private sessionCommands: Map<string, number> = new Map(); |
|
|
|
|
|
|
|
|
|
|
|
constructor(options: UniversalLLMOptions) { |
|
this.adapter = this.createAdapter(options); |
|
|
|
|
|
this.telemetry = { |
|
enabled: options.telemetryEnabled !== false, |
|
endpoint: options.telemetryEndpoint || 'https://telemetry.universal-developer.org/v1/events', |
|
anonymousId: options.anonymousId || this.generateAnonymousId(), |
|
sessionId: options.sessionId || this.generateSessionId() |
|
}; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
public registerCommand(name: string, command: Omit<SymbolicCommand, 'name'>) { |
|
this.adapter.registerCommand({ |
|
name, |
|
...command |
|
}); |
|
|
|
return this; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
public async generate(options: GenerateOptions): Promise<string> { |
|
|
|
const commandMatch = options.prompt.match(/^\/([a-zA-Z0-9_]+)/); |
|
const command = commandMatch ? commandMatch[1] : null; |
|
|
|
|
|
if (command) { |
|
this.trackCommandUsage(command); |
|
} |
|
|
|
|
|
const response = await this.adapter.generate(options); |
|
|
|
|
|
if (this.telemetry.enabled && command) { |
|
this.sendTelemetry(command, options.prompt); |
|
} |
|
|
|
return response; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
public getCommandUsageStats(): Map<string, number> { |
|
return new Map(this.sessionCommands); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
public setTelemetryEnabled(enabled: boolean): void { |
|
this.telemetry.enabled = enabled; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
private createAdapter(options: UniversalLLMOptions): ModelAdapter { |
|
const { provider, apiKey, ...adapterOptions } = options; |
|
|
|
switch (provider) { |
|
case 'anthropic': |
|
return new ClaudeAdapter(apiKey, adapterOptions); |
|
case 'openai': |
|
return new OpenAIAdapter(apiKey, adapterOptions); |
|
case 'qwen': |
|
return new QwenAdapter(apiKey, adapterOptions); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
default: |
|
throw new Error(`Unsupported provider: ${provider}`); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
private trackCommandUsage(command: string): void { |
|
const currentCount = this.sessionCommands.get(command) || 0; |
|
this.sessionCommands.set(command, currentCount + 1); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
private async sendTelemetry(command: string, prompt: string): Promise<void> { |
|
if (!this.telemetry.enabled || !this.telemetry.endpoint) return; |
|
|
|
try { |
|
const data = { |
|
event: 'symbolic_command_used', |
|
properties: { |
|
command, |
|
provider: (this.adapter as any).constructor.name.replace('Adapter', '').toLowerCase(), |
|
timestamp: new Date().toISOString(), |
|
prompt_length: prompt.length, |
|
|
|
}, |
|
anonymousId: this.telemetry.anonymousId, |
|
sessionId: this.telemetry.sessionId |
|
}; |
|
|
|
|
|
if (typeof fetch === 'function') { |
|
await fetch(this.telemetry.endpoint, { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json' |
|
}, |
|
body: JSON.stringify(data) |
|
}); |
|
} else { |
|
|
|
const { default: axios } = await import('axios'); |
|
await axios.post(this.telemetry.endpoint, data); |
|
} |
|
} catch (error) { |
|
|
|
console.warn('Telemetry error:', error); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
private generateAnonymousId(): string { |
|
return Math.random().toString(36).substring(2, 15) + |
|
Math.random().toString(36).substring(2, 15); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
private generateSessionId(): string { |
|
return Date.now().toString(36) + Math.random().toString(36).substring(2, 9); |
|
} |
|
} |
|
|
|
|
|
export * from './adapters/base'; |
|
export * from './adapters/claude'; |
|
export * from './adapters/openai'; |
|
export * from './adapters/qwen'; |
|
|