UNPKG

@cosmara-ai/community-sdk

Version:

COSMARA Community SDK - Multi-provider AI client with intelligent routing and 1,000 free requests/month

1,557 lines (1,552 loc) 53.1 kB
// src/types.ts var APIProvider = /* @__PURE__ */ ((APIProvider2) => { APIProvider2["OPENAI"] = "OPENAI"; APIProvider2["ANTHROPIC"] = "ANTHROPIC"; APIProvider2["GOOGLE"] = "GOOGLE"; return APIProvider2; })(APIProvider || {}); var AIError = class extends Error { constructor(config) { super(config.message); this.name = "AIError"; this.code = config.code; this.type = config.type; this.provider = config.provider; this.retryable = config.retryable; this.details = config.details || {}; } }; var MODEL_EQUIVALENTS = { "chat-small": { ["OPENAI" /* OPENAI */]: "gpt-3.5-turbo", ["ANTHROPIC" /* ANTHROPIC */]: "claude-3-haiku-20240307", ["GOOGLE" /* GOOGLE */]: "gemini-1.5-flash" }, "chat-medium": { ["OPENAI" /* OPENAI */]: "gpt-4", ["ANTHROPIC" /* ANTHROPIC */]: "claude-sonnet-4-20250514", ["GOOGLE" /* GOOGLE */]: "gemini-1.5-pro" }, "chat-large": { ["OPENAI" /* OPENAI */]: "gpt-4-turbo", ["ANTHROPIC" /* ANTHROPIC */]: "claude-3-opus-20240229", ["GOOGLE" /* GOOGLE */]: "gemini-1.5-pro" } }; var COMMUNITY_LIMITS = { requestsPerMonth: 1e3, requestsPerDay: 100, requestsPerMinute: 10, maxUniqueUsers: 10, commercialUseDetection: true }; var PERFORMANCE_TARGETS = { MAX_RESPONSE_TIME: 3e4, // 30 seconds for community MAX_STREAM_FIRST_TOKEN: 5e3 // 5 seconds for first token }; var COST_THRESHOLDS = { CHEAP_REQUEST: 1e-3, EXPENSIVE_REQUEST: 0.1 }; var UPGRADE_MESSAGES = { RATE_LIMIT: "\u26A1 You've reached your Community Edition limits. Upgrade to Developer tier for 50x more requests and ML-powered optimization!", COMMERCIAL_USE: "\u{1F3E2} Commercial usage detected. Upgrade to Developer tier to unlock commercial licensing and priority support!", FEATURE_LOCKED: "\u{1F512} This feature requires Developer tier. Upgrade for ML routing, analytics, and cost optimization!", UPGRADE_CTA: "\u{1F680} Ready to unlock the full potential? Upgrade at https://ai-marketplace.dev/pricing" }; // src/providers/openai.ts var OpenAIProvider = class { constructor(baseUrl) { this.provider = "OPENAI" /* OPENAI */; this.defaultHeaders = { "Content-Type": "application/json", "User-Agent": "AI-Marketplace-Community-SDK/1.0" }; this.models = [ { id: "gpt-3.5-turbo", provider: "OPENAI" /* OPENAI */, name: "gpt-3.5-turbo", displayName: "GPT-3.5 Turbo", description: "Fast, cost-effective model for most conversational tasks", maxTokens: 4096, inputCostPer1K: 15e-4, outputCostPer1K: 2e-3, supportsStreaming: true, supportsTools: true, contextWindow: 16385, isActive: true }, { id: "gpt-4", provider: "OPENAI" /* OPENAI */, name: "gpt-4", displayName: "GPT-4", description: "Most capable model for complex reasoning tasks", maxTokens: 8192, inputCostPer1K: 0.03, outputCostPer1K: 0.06, supportsStreaming: true, supportsTools: true, contextWindow: 8192, isActive: true }, { id: "gpt-4o-mini", provider: "OPENAI" /* OPENAI */, name: "gpt-4o-mini", displayName: "GPT-4 Omni Mini", description: "Efficient and cost-effective model for simple tasks", maxTokens: 16384, inputCostPer1K: 15e-5, outputCostPer1K: 6e-4, supportsStreaming: true, supportsTools: true, contextWindow: 128e3, isActive: true } ]; this.baseUrl = baseUrl || "https://api.openai.com/v1"; } async getModels() { return this.models.filter((model) => model.isActive); } async getModel(modelId) { return this.models.find((model) => model.id === modelId) || null; } async validateApiKey(apiKey) { try { const response = await fetch(`${this.baseUrl}/models`, { method: "GET", headers: { ...this.defaultHeaders, "Authorization": `Bearer ${apiKey}` } }); return response.ok; } catch (error) { return false; } } async estimateCost(request) { const model = await this.getModel(request.model); if (!model) { throw new AIError({ code: "MODEL_NOT_FOUND", message: `Model ${request.model} not found`, type: "invalid_request", provider: this.provider, retryable: false }); } const inputText = request.messages.map((m) => m.content).join(" "); const estimatedInputTokens = Math.ceil(inputText.length / 4); const estimatedOutputTokens = request.maxTokens || Math.min(1e3, model.maxTokens * 0.1); const inputCost = estimatedInputTokens / 1e3 * model.inputCostPer1K; const outputCost = estimatedOutputTokens / 1e3 * model.outputCostPer1K; return inputCost + outputCost; } async chat(request, apiKey) { const openaiRequest = this.transformRequest(request); const response = await fetch(`${this.baseUrl}/chat/completions`, { method: "POST", headers: { ...this.defaultHeaders, "Authorization": `Bearer ${apiKey}` }, body: JSON.stringify(openaiRequest) }); if (!response.ok) { throw await this.handleHttpError(response); } const data = await response.json(); return this.transformResponse(data, request); } async *chatStream(request, apiKey) { const openaiRequest = this.transformRequest(request, true); const response = await fetch(`${this.baseUrl}/chat/completions`, { method: "POST", headers: { ...this.defaultHeaders, "Authorization": `Bearer ${apiKey}` }, body: JSON.stringify(openaiRequest) }); if (!response.ok) { throw await this.handleHttpError(response); } if (!response.body) { throw new AIError({ code: "NO_RESPONSE_BODY", message: "No response body received for streaming request", type: "api_error", provider: this.provider, retryable: false }); } const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ""; try { while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split("\n"); buffer = lines.pop() || ""; for (const line of lines) { const trimmed = line.trim(); if (trimmed === "" || trimmed === "data: [DONE]") continue; if (trimmed.startsWith("data: ")) { try { const data = JSON.parse(trimmed.slice(6)); const chunk = this.transformStreamChunk(data, request); if (chunk) yield chunk; } catch (e) { continue; } } } } } finally { reader.releaseLock(); } } transformRequest(request, stream = false) { const openaiRequest = { model: request.model, messages: request.messages.map((msg) => ({ role: msg.role, content: msg.content, ...msg.name && { name: msg.name } })), stream }; if (request.maxTokens !== void 0) { openaiRequest.max_tokens = request.maxTokens; } if (request.temperature !== void 0) { openaiRequest.temperature = request.temperature; } if (request.topP !== void 0) { openaiRequest.top_p = request.topP; } if (request.tools) { openaiRequest.tools = request.tools; } if (request.toolChoice) { if (typeof request.toolChoice === "string") { openaiRequest.tool_choice = request.toolChoice; } else { openaiRequest.tool_choice = { type: "function", function: { name: request.toolChoice.name } }; } } return openaiRequest; } transformResponse(response, originalRequest) { const model = this.models.find((m) => m.name === response.model || m.id === response.model); const usage = this.calculateUsage(response.usage, model); return { id: response.id, model: response.model, provider: this.provider, choices: response.choices.map((choice) => ({ index: choice.index, message: { role: "assistant", content: choice.message.content || "", metadata: originalRequest.metadata || {} }, finishReason: choice.finish_reason, toolCalls: choice.message.tool_calls?.map((tc) => ({ id: tc.id, type: tc.type, function: tc.function })) || [] })), usage, created: response.created, metadata: originalRequest.metadata || {} }; } transformStreamChunk(chunk, _originalRequest) { if (!chunk.choices || chunk.choices.length === 0) return null; return { id: chunk.id, model: chunk.model, provider: this.provider, choices: chunk.choices.map((choice) => ({ index: choice.index, delta: { role: choice.delta.role, content: choice.delta.content, toolCalls: choice.delta.tool_calls?.map((tc) => ({ id: tc.id || "", type: "function", function: { name: tc.function?.name || "", arguments: tc.function?.arguments || "" } })) }, finishReason: choice.finish_reason })), usage: chunk.usage ? this.calculateUsage(chunk.usage, this.models.find((m) => m.name === chunk.model)) : { promptTokens: 0, completionTokens: 0, totalTokens: 0, cost: 0 }, created: chunk.created }; } calculateUsage(usage, model) { const inputCost = model ? usage.prompt_tokens / 1e3 * model.inputCostPer1K : 0; const outputCost = model ? usage.completion_tokens / 1e3 * model.outputCostPer1K : 0; return { promptTokens: usage.prompt_tokens, completionTokens: usage.completion_tokens, totalTokens: usage.total_tokens, cost: inputCost + outputCost }; } async handleHttpError(response) { let errorData = {}; try { errorData = await response.json(); } catch { } const baseError = { provider: this.provider, retryable: response.status >= 500 || response.status === 429, details: { status: response.status, statusText: response.statusText, ...errorData } }; switch (response.status) { case 401: return new AIError({ ...baseError, code: "AUTHENTICATION_ERROR", message: "Invalid OpenAI API key or authentication failed", type: "authentication", retryable: false }); case 429: return new AIError({ ...baseError, code: "RATE_LIMIT_ERROR", message: "OpenAI rate limit exceeded", type: "rate_limit", retryable: true }); case 400: return new AIError({ ...baseError, code: "INVALID_REQUEST", message: errorData.error?.message || "Invalid request parameters", type: "invalid_request", retryable: false }); default: return new AIError({ ...baseError, code: "HTTP_ERROR", message: `HTTP ${response.status}: ${response.statusText}`, type: "api_error", retryable: response.status >= 500 }); } } }; // src/providers/anthropic.ts var AnthropicProvider = class { constructor(baseUrl) { this.provider = "ANTHROPIC" /* ANTHROPIC */; this.defaultHeaders = { "Content-Type": "application/json", "User-Agent": "AI-Marketplace-Community-SDK/1.0", "anthropic-version": "2023-06-01" }; this.models = [ { id: "claude-3-haiku-20240307", provider: "ANTHROPIC" /* ANTHROPIC */, name: "claude-3-haiku-20240307", displayName: "Claude 3 Haiku", description: "Fast and cost-effective model for simple tasks", maxTokens: 4096, inputCostPer1K: 25e-5, outputCostPer1K: 125e-5, supportsStreaming: true, supportsTools: false, contextWindow: 2e5, isActive: true }, { id: "claude-sonnet-4-20250514", provider: "ANTHROPIC" /* ANTHROPIC */, name: "claude-sonnet-4-20250514", displayName: "Claude Sonnet 4", description: "Balanced performance for most conversational tasks", maxTokens: 8192, inputCostPer1K: 3e-3, outputCostPer1K: 0.015, supportsStreaming: true, supportsTools: true, contextWindow: 2e5, isActive: true }, { id: "claude-3-opus-20240229", provider: "ANTHROPIC" /* ANTHROPIC */, name: "claude-3-opus-20240229", displayName: "Claude 3 Opus", description: "Most capable model for complex reasoning and analysis", maxTokens: 4096, inputCostPer1K: 0.015, outputCostPer1K: 0.075, supportsStreaming: true, supportsTools: true, contextWindow: 2e5, isActive: true } ]; this.baseUrl = baseUrl || "https://api.anthropic.com/v1"; } async getModels() { return this.models.filter((model) => model.isActive); } async getModel(modelId) { return this.models.find((model) => model.id === modelId) || null; } async validateApiKey(apiKey) { try { const response = await fetch(`${this.baseUrl}/messages`, { method: "POST", headers: { ...this.defaultHeaders, "x-api-key": apiKey }, body: JSON.stringify({ model: "claude-3-haiku-20240307", max_tokens: 1, messages: [{ role: "user", content: "Hi" }] }) }); return response.status !== 401; } catch (error) { return false; } } async estimateCost(request) { const model = await this.getModel(request.model); if (!model) { throw new AIError({ code: "MODEL_NOT_FOUND", message: `Model ${request.model} not found`, type: "invalid_request", provider: this.provider, retryable: false }); } const inputText = request.messages.map((m) => m.content).join(" "); const estimatedInputTokens = Math.ceil(inputText.length / 4); const estimatedOutputTokens = request.maxTokens || Math.min(1e3, model.maxTokens * 0.1); const inputCost = estimatedInputTokens / 1e3 * model.inputCostPer1K; const outputCost = estimatedOutputTokens / 1e3 * model.outputCostPer1K; return inputCost + outputCost; } async chat(request, apiKey) { const anthropicRequest = this.transformRequest(request); const response = await fetch(`${this.baseUrl}/messages`, { method: "POST", headers: { ...this.defaultHeaders, "x-api-key": apiKey }, body: JSON.stringify(anthropicRequest) }); if (!response.ok) { throw await this.handleHttpError(response); } const data = await response.json(); return this.transformResponse(data, request); } async *chatStream(request, apiKey) { const anthropicRequest = this.transformRequest(request, true); const response = await fetch(`${this.baseUrl}/messages`, { method: "POST", headers: { ...this.defaultHeaders, "x-api-key": apiKey }, body: JSON.stringify(anthropicRequest) }); if (!response.ok) { throw await this.handleHttpError(response); } if (!response.body) { throw new AIError({ code: "NO_RESPONSE_BODY", message: "No response body received for streaming request", type: "api_error", provider: this.provider, retryable: false }); } const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ""; try { while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split("\n"); buffer = lines.pop() || ""; for (const line of lines) { const trimmed = line.trim(); if (trimmed === "" || !trimmed.startsWith("data: ")) continue; try { const data = JSON.parse(trimmed.slice(6)); const chunk = this.transformStreamChunk(data, request); if (chunk) yield chunk; } catch (e) { continue; } } } } finally { reader.releaseLock(); } } transformRequest(request, stream = false) { const systemMessage = request.messages.find((msg) => msg.role === "system"); const userMessages = request.messages.filter((msg) => msg.role !== "system"); const anthropicRequest = { model: request.model, messages: userMessages.map((msg) => ({ role: msg.role, content: msg.content })), max_tokens: request.maxTokens || 1024, stream }; if (systemMessage) { anthropicRequest.system = systemMessage.content; } if (request.temperature !== void 0) { anthropicRequest.temperature = request.temperature; } if (request.topP !== void 0) { anthropicRequest.top_p = request.topP; } return anthropicRequest; } transformResponse(response, originalRequest) { const model = this.models.find((m) => m.name === response.model || m.id === response.model); const usage = this.calculateUsage(response.usage, model); return { id: response.id, model: response.model, provider: this.provider, choices: [{ index: 0, message: { role: "assistant", content: response.content[0]?.text || "", metadata: originalRequest.metadata || {} }, finishReason: this.mapFinishReason(response.stop_reason) }], usage, created: Date.now(), metadata: originalRequest.metadata }; } transformStreamChunk(chunk, originalRequest) { if (chunk.type === "content_block_delta" && chunk.delta?.text) { return { id: chunk.id || `chunk_${Date.now()}`, model: originalRequest.model, provider: this.provider, choices: [{ index: 0, delta: { role: "assistant", content: chunk.delta.text }, finishReason: null }], created: Date.now() }; } if (chunk.type === "message_stop") { return { id: chunk.id || `chunk_${Date.now()}`, model: originalRequest.model, provider: this.provider, choices: [{ index: 0, delta: {}, finishReason: "stop" }], created: Date.now() }; } return null; } calculateUsage(usage, model) { const inputCost = model ? usage.input_tokens / 1e3 * model.inputCostPer1K : 0; const outputCost = model ? usage.output_tokens / 1e3 * model.outputCostPer1K : 0; return { promptTokens: usage.input_tokens, completionTokens: usage.output_tokens, totalTokens: usage.input_tokens + usage.output_tokens, cost: inputCost + outputCost }; } mapFinishReason(reason) { switch (reason) { case "end_turn": return "stop"; case "max_tokens": return "length"; default: return null; } } async handleHttpError(response) { let errorData = {}; try { errorData = await response.json(); } catch { } const baseError = { provider: this.provider, retryable: response.status >= 500 || response.status === 429, details: { status: response.status, statusText: response.statusText, ...errorData } }; switch (response.status) { case 401: return new AIError({ ...baseError, code: "AUTHENTICATION_ERROR", message: "Invalid Anthropic API key or authentication failed", type: "authentication", retryable: false }); case 429: return new AIError({ ...baseError, code: "RATE_LIMIT_ERROR", message: "Anthropic rate limit exceeded", type: "rate_limit", retryable: true }); case 400: return new AIError({ ...baseError, code: "INVALID_REQUEST", message: errorData.error?.message || "Invalid request parameters", type: "invalid_request", retryable: false }); default: return new AIError({ ...baseError, code: "HTTP_ERROR", message: `HTTP ${response.status}: ${response.statusText}`, type: "api_error", retryable: response.status >= 500 }); } } }; // src/providers/google.ts var GoogleProvider = class { constructor(baseUrl) { this.provider = "GOOGLE" /* GOOGLE */; this.defaultHeaders = { "Content-Type": "application/json", "User-Agent": "AI-Marketplace-Community-SDK/1.0" }; this.models = [ { id: "gemini-1.5-flash", provider: "GOOGLE" /* GOOGLE */, name: "gemini-1.5-flash", displayName: "Gemini 1.5 Flash", description: "Fast and efficient model for most tasks", maxTokens: 8192, inputCostPer1K: 15e-5, outputCostPer1K: 6e-4, supportsStreaming: true, supportsTools: false, contextWindow: 1e6, isActive: true }, { id: "gemini-1.5-pro", provider: "GOOGLE" /* GOOGLE */, name: "gemini-1.5-pro", displayName: "Gemini 1.5 Pro", description: "High-performance model for complex reasoning tasks", maxTokens: 8192, inputCostPer1K: 35e-4, outputCostPer1K: 0.0105, supportsStreaming: true, supportsTools: true, contextWindow: 2e6, isActive: true } ]; this.baseUrl = baseUrl || "https://generativelanguage.googleapis.com/v1beta"; } async getModels() { return this.models.filter((model) => model.isActive); } async getModel(modelId) { return this.models.find((model) => model.id === modelId) || null; } async validateApiKey(apiKey) { try { const response = await fetch(`${this.baseUrl}/models?key=${apiKey}`, { method: "GET", headers: this.defaultHeaders }); return response.ok; } catch (error) { return false; } } async estimateCost(request) { const model = await this.getModel(request.model); if (!model) { throw new AIError({ code: "MODEL_NOT_FOUND", message: `Model ${request.model} not found`, type: "invalid_request", provider: this.provider, retryable: false }); } const inputText = request.messages.map((m) => m.content).join(" "); const estimatedInputTokens = Math.ceil(inputText.length / 4); const estimatedOutputTokens = request.maxTokens || Math.min(1e3, model.maxTokens * 0.1); const inputCost = estimatedInputTokens / 1e3 * model.inputCostPer1K; const outputCost = estimatedOutputTokens / 1e3 * model.outputCostPer1K; return inputCost + outputCost; } async chat(request, apiKey) { const googleRequest = this.transformRequest(request); const url = `${this.baseUrl}/models/${request.model}:generateContent?key=${apiKey}`; const response = await fetch(url, { method: "POST", headers: this.defaultHeaders, body: JSON.stringify(googleRequest) }); if (!response.ok) { throw await this.handleHttpError(response); } const data = await response.json(); return this.transformResponse(data, request); } async *chatStream(request, apiKey) { const googleRequest = this.transformRequest(request); const url = `${this.baseUrl}/models/${request.model}:streamGenerateContent?key=${apiKey}`; const response = await fetch(url, { method: "POST", headers: this.defaultHeaders, body: JSON.stringify(googleRequest) }); if (!response.ok) { throw await this.handleHttpError(response); } if (!response.body) { throw new AIError({ code: "NO_RESPONSE_BODY", message: "No response body received for streaming request", type: "api_error", provider: this.provider, retryable: false }); } const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ""; try { while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split("\n"); buffer = lines.pop() || ""; for (const line of lines) { const trimmed = line.trim(); if (trimmed === "" || trimmed === "[" || trimmed === "]" || trimmed === ",") continue; try { const data = JSON.parse(trimmed); const chunk = this.transformStreamChunk(data, request); if (chunk) yield chunk; } catch (e) { continue; } } } } finally { reader.releaseLock(); } } transformRequest(request) { const systemMessage = request.messages.find((msg) => msg.role === "system"); const userMessages = request.messages.filter((msg) => msg.role !== "system"); const googleRequest = { contents: userMessages.map((msg) => ({ role: msg.role === "assistant" ? "model" : "user", parts: [{ text: msg.content }] })) }; if (systemMessage) { googleRequest.systemInstruction = { parts: [{ text: systemMessage.content }] }; } if (request.temperature !== void 0 || request.topP !== void 0 || request.maxTokens !== void 0) { googleRequest.generationConfig = {}; if (request.temperature !== void 0) { googleRequest.generationConfig.temperature = request.temperature; } if (request.topP !== void 0) { googleRequest.generationConfig.topP = request.topP; } if (request.maxTokens !== void 0) { googleRequest.generationConfig.maxOutputTokens = request.maxTokens; } } return googleRequest; } transformResponse(response, originalRequest) { const model = this.models.find((m) => m.name === originalRequest.model || m.id === originalRequest.model); const usage = this.calculateUsage(response.usageMetadata, model); return { id: `google_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, model: originalRequest.model, provider: this.provider, choices: response.candidates.map((candidate) => ({ index: candidate.index, message: { role: "assistant", content: candidate.content.parts[0]?.text || "", metadata: originalRequest.metadata }, finishReason: this.mapFinishReason(candidate.finishReason) })), usage, created: Date.now(), metadata: originalRequest.metadata }; } transformStreamChunk(chunk, originalRequest) { if (!chunk.candidates || chunk.candidates.length === 0) return null; const candidate = chunk.candidates[0]; if (!candidate.content?.parts?.[0]?.text) return null; return { id: `google_chunk_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, model: originalRequest.model, provider: this.provider, choices: [{ index: 0, delta: { role: "assistant", content: candidate.content.parts[0].text }, finishReason: candidate.finishReason === "STOP" ? "stop" : null }], usage: chunk.usageMetadata ? this.calculateUsage(chunk.usageMetadata, this.models.find((m) => m.id === originalRequest.model)) : void 0, created: Date.now() }; } calculateUsage(usage, model) { const inputCost = model ? usage.promptTokenCount / 1e3 * model.inputCostPer1K : 0; const outputCost = model ? usage.candidatesTokenCount / 1e3 * model.outputCostPer1K : 0; return { promptTokens: usage.promptTokenCount, completionTokens: usage.candidatesTokenCount, totalTokens: usage.totalTokenCount, cost: inputCost + outputCost }; } mapFinishReason(reason) { switch (reason) { case "STOP": return "stop"; case "MAX_TOKENS": return "length"; case "SAFETY": case "RECITATION": return "content_filter"; default: return null; } } async handleHttpError(response) { let errorData = {}; try { errorData = await response.json(); } catch { } const baseError = { provider: this.provider, retryable: response.status >= 500 || response.status === 429, details: { status: response.status, statusText: response.statusText, ...errorData } }; switch (response.status) { case 401: case 403: return new AIError({ ...baseError, code: "AUTHENTICATION_ERROR", message: "Invalid Google API key or authentication failed", type: "authentication", retryable: false }); case 429: return new AIError({ ...baseError, code: "RATE_LIMIT_ERROR", message: "Google AI rate limit exceeded", type: "rate_limit", retryable: true }); case 400: return new AIError({ ...baseError, code: "INVALID_REQUEST", message: errorData.error?.message || "Invalid request parameters", type: "invalid_request", retryable: false }); default: return new AIError({ ...baseError, code: "HTTP_ERROR", message: `HTTP ${response.status}: ${response.statusText}`, type: "api_error", retryable: response.status >= 500 }); } } }; // src/license.ts var CommunityUsageTracker = class { constructor(limits = COMMUNITY_LIMITS, userId) { this.usageHistory = []; this.storageKey = "ai-marketplace-community-usage"; this.limits = limits; this.userFingerprint = userId || this.generateFingerprint(); this.loadUsageHistory(); this.startCleanup(); } /** * Check if current request would exceed usage limits */ async checkLimits() { const now = Date.now(); const oneMinute = 60 * 1e3; const oneDay = 24 * 60 * 60 * 1e3; const oneMonth = 30 * 24 * 60 * 60 * 1e3; const recentMinute = this.usageHistory.filter( (record) => now - record.timestamp < oneMinute ); if (recentMinute.length >= this.limits.requestsPerMinute) { throw new AIError({ code: "RATE_LIMIT_MINUTE", message: `${UPGRADE_MESSAGES.RATE_LIMIT} (${recentMinute.length}/${this.limits.requestsPerMinute} requests per minute)`, type: "usage_limit", provider: "OPENAI" /* OPENAI */, retryable: true, details: { limit: this.limits.requestsPerMinute, current: recentMinute.length, resetIn: oneMinute - (now - Math.min(...recentMinute.map((r) => r.timestamp))), upgradeUrl: "https://ai-marketplace.dev/pricing" } }); } const recentDay = this.usageHistory.filter( (record) => now - record.timestamp < oneDay ); if (recentDay.length >= this.limits.requestsPerDay) { throw new AIError({ code: "RATE_LIMIT_DAILY", message: `${UPGRADE_MESSAGES.RATE_LIMIT} (${recentDay.length}/${this.limits.requestsPerDay} requests per day)`, type: "usage_limit", provider: "OPENAI" /* OPENAI */, retryable: true, details: { limit: this.limits.requestsPerDay, current: recentDay.length, resetIn: oneDay - (now - Math.min(...recentDay.map((r) => r.timestamp))), upgradeUrl: "https://ai-marketplace.dev/pricing" } }); } const recentMonth = this.usageHistory.filter( (record) => now - record.timestamp < oneMonth ); if (recentMonth.length >= this.limits.requestsPerMonth) { throw new AIError({ code: "RATE_LIMIT_MONTHLY", message: `${UPGRADE_MESSAGES.RATE_LIMIT} (${recentMonth.length}/${this.limits.requestsPerMonth} requests per month)`, type: "usage_limit", provider: "OPENAI" /* OPENAI */, retryable: true, details: { limit: this.limits.requestsPerMonth, current: recentMonth.length, resetIn: oneMonth - (now - Math.min(...recentMonth.map((r) => r.timestamp))), upgradeUrl: "https://ai-marketplace.dev/pricing" } }); } const uniqueUsers = new Set(this.usageHistory.map((record) => record.userId)); if (uniqueUsers.size >= this.limits.maxUniqueUsers) { throw new AIError({ code: "USER_LIMIT_EXCEEDED", message: `${UPGRADE_MESSAGES.RATE_LIMIT} (${uniqueUsers.size}/${this.limits.maxUniqueUsers} unique users)`, type: "usage_limit", provider: "OPENAI" /* OPENAI */, retryable: false, details: { limit: this.limits.maxUniqueUsers, current: uniqueUsers.size, upgradeUrl: "https://ai-marketplace.dev/pricing" } }); } if (this.limits.commercialUseDetection && this.detectCommercialUsage()) { throw new AIError({ code: "COMMERCIAL_USE_DETECTED", message: UPGRADE_MESSAGES.COMMERCIAL_USE, type: "usage_limit", provider: "OPENAI" /* OPENAI */, retryable: false, details: { commercialIndicators: this.getCommercialIndicators(), upgradeUrl: "https://ai-marketplace.dev/pricing", contactSales: "https://ai-marketplace.dev/contact" } }); } } /** * Record usage after successful request */ recordUsage(provider, model, tokensUsed, cost, userId) { const record = { timestamp: Date.now(), provider, model, tokensUsed, cost, userId: userId || this.userFingerprint }; this.usageHistory.push(record); this.saveUsageHistory(); } /** * Get current usage statistics */ getUsageStats() { const now = Date.now(); const oneMinute = 60 * 1e3; const oneDay = 24 * 60 * 60 * 1e3; const oneMonth = 30 * 24 * 60 * 60 * 1e3; const recentMinute = this.usageHistory.filter((r) => now - r.timestamp < oneMinute); const recentDay = this.usageHistory.filter((r) => now - r.timestamp < oneDay); const recentMonth = this.usageHistory.filter((r) => now - r.timestamp < oneMonth); return { requestsThisMinute: recentMinute.length, requestsThisDay: recentDay.length, requestsThisMonth: recentMonth.length, uniqueUsers: new Set(this.usageHistory.map((r) => r.userId)).size, totalCost: this.usageHistory.reduce((sum, r) => sum + r.cost, 0), commercialIndicators: this.getCommercialIndicators() }; } /** * Check if usage patterns indicate commercial use */ detectCommercialUsage() { const indicators = this.getCommercialIndicators(); const indicatorCount = Object.values(indicators).filter(Boolean).length; return indicatorCount >= 3; } /** * Analyze usage patterns for commercial indicators */ getCommercialIndicators() { const now = Date.now(); const oneWeek = 7 * 24 * 60 * 60 * 1e3; const recentWeek = this.usageHistory.filter((r) => now - r.timestamp < oneWeek); const avgDailyRequests = recentWeek.length / 7; const highRequestVolume = avgDailyRequests > 20; const businessHoursRequests = recentWeek.filter((record) => { const date = new Date(record.timestamp); const hour = date.getHours(); const dayOfWeek = date.getDay(); return dayOfWeek >= 1 && dayOfWeek <= 5 && hour >= 9 && hour <= 18; }); const businessHours = businessHoursRequests.length / recentWeek.length > 0.7; const uniqueUsers = new Set(recentWeek.map((r) => r.userId)); const multipleUniqueUsers = uniqueUsers.size > 3; const uniqueDays = new Set(recentWeek.map((r) => { const date = new Date(r.timestamp); return `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`; })); const consistentUsagePattern = uniqueDays.size >= 5; const providersUsed = new Set(recentWeek.map((r) => r.provider)); const apiKeyPatterns = providersUsed.size >= 2; return { highRequestVolume, businessHours, multipleUniqueUsers, consistentUsagePattern, apiKeyPatterns }; } /** * Generate unique fingerprint for user tracking */ generateFingerprint() { const factors = [ typeof window !== "undefined" ? window.navigator?.userAgent : "node", typeof window !== "undefined" ? window.screen?.width : "server", Date.now().toString(36), Math.random().toString(36).substr(2, 9) ]; return `user_${factors.join("_").replace(/[^a-zA-Z0-9_]/g, "_")}`; } /** * Load usage history from storage */ loadUsageHistory() { try { if (typeof localStorage !== "undefined") { const stored = localStorage.getItem(this.storageKey); if (stored) { this.usageHistory = JSON.parse(stored); } } } catch (error) { console.warn("Failed to load usage history:", error); this.usageHistory = []; } } /** * Save usage history to storage */ saveUsageHistory() { try { if (typeof localStorage !== "undefined") { const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1e3; const recentHistory = this.usageHistory.filter((r) => r.timestamp > thirtyDaysAgo); localStorage.setItem(this.storageKey, JSON.stringify(recentHistory)); this.usageHistory = recentHistory; } } catch (error) { console.warn("Failed to save usage history:", error); } } /** * Start cleanup interval to remove old records */ startCleanup() { setInterval(() => { const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1e3; this.usageHistory = this.usageHistory.filter((r) => r.timestamp > thirtyDaysAgo); this.saveUsageHistory(); }, 60 * 60 * 1e3); } }; var CommunityLicenseValidator = class { /** * Validate that usage complies with Community Edition license */ validateUsage(usageStats) { const violations = []; const recommendations = []; const indicators = usageStats.commercialIndicators; if (Object.values(indicators).filter(Boolean).length >= 2) { violations.push("Commercial usage patterns detected"); recommendations.push("Upgrade to Developer tier for commercial licensing"); } if (usageStats.requestsThisMonth > COMMUNITY_LIMITS.requestsPerMonth * 0.8) { recommendations.push("Approaching monthly limit - consider upgrading for higher limits"); } if (usageStats.uniqueUsers > COMMUNITY_LIMITS.maxUniqueUsers * 0.8) { recommendations.push("Approaching user limit - upgrade for unlimited users"); } return { isValid: violations.length === 0, violations, recommendations }; } /** * Display upgrade prompt to user */ displayUpgradePrompt(reason) { const messages = { limits: UPGRADE_MESSAGES.RATE_LIMIT, commercial: UPGRADE_MESSAGES.COMMERCIAL_USE, features: UPGRADE_MESSAGES.FEATURE_LOCKED }; console.warn(` \u{1F680} AI Marketplace SDK - Upgrade Notice `); console.warn(messages[reason]); console.warn(UPGRADE_MESSAGES.UPGRADE_CTA); console.warn(` \u{1F4B0} Developer Tier Benefits:`); console.warn(` \u2022 50,000 requests/month (50x more than Community)`); console.warn(` \u2022 ML-powered routing for cost optimization`); console.warn(` \u2022 Advanced analytics and insights`); console.warn(` \u2022 Intelligent fallbacks and error handling`); console.warn(` \u2022 Commercial usage rights`); console.warn(` \u2022 Priority support `); } }; // src/client.ts var AIMarketplaceCommunityClient = class { constructor(config) { this.providers = {}; this.cache = /* @__PURE__ */ new Map(); if (!config.apiKeys || Object.keys(config.apiKeys).length === 0) { throw new Error("At least one API key is required (openai, anthropic, or google)"); } this.apiKeys = config.apiKeys; this.userId = config.userId || this.generateUserId(); this.enableUsageTracking = config.enableUsageTracking ?? true; this.usageTracker = new CommunityUsageTracker(void 0, this.userId); this.licenseValidator = new CommunityLicenseValidator(); this.initializeProviders(config.baseUrls); this.startCacheCleanup(); this.showWelcomeMessage(); } /** * Send a chat completion request (Community Edition) * Note: No ML routing - manual provider selection only */ async chat(request, options = {}) { if (this.enableUsageTracking) { await this.usageTracker.checkLimits(); } const userId = options.userId || this.userId; const cachedResponse = this.getCachedResponse(request); if (cachedResponse) { return cachedResponse; } let selectedProvider; if (options.provider) { selectedProvider = options.provider; } else { selectedProvider = this.getRandomAvailableProvider(); } const provider = this.providers[selectedProvider]; const apiKey = this.getApiKey(selectedProvider); if (!provider) { throw new AIError({ code: "PROVIDER_NOT_AVAILABLE", message: `Provider ${selectedProvider} is not available. Community Edition supports OpenAI, Anthropic, and Google.`, type: "invalid_request", provider: selectedProvider, retryable: false }); } if (!apiKey) { throw new AIError({ code: "API_KEY_MISSING", message: `API key for ${selectedProvider} is not provided. Please add it to your configuration.`, type: "authentication", provider: selectedProvider, retryable: false }); } try { const finalRequest = { ...request, model: request.model || this.getDefaultModelForProvider(selectedProvider) }; const response = await provider.chat(finalRequest, apiKey); if (this.enableUsageTracking && response.usage) { this.usageTracker.recordUsage( selectedProvider, finalRequest.model, response.usage.totalTokens, response.usage.cost, userId ); } this.setCachedResponse(request, response); this.maybeShowUpgradePrompt(); return response; } catch (error) { if (error instanceof AIError && error.code === "RATE_LIMIT_ERROR") { this.licenseValidator.displayUpgradePrompt("limits"); } throw error; } } /** * Send a streaming chat completion request (Community Edition) */ async *chatStream(request, options = {}) { if (this.enableUsageTracking) { await this.usageTracker.checkLimits(); } const userId = options.userId || this.userId; let selectedProvider; if (options.provider) { selectedProvider = options.provider; } else { selectedProvider = this.getRandomAvailableProvider(); } const provider = this.providers[selectedProvider]; const apiKey = this.getApiKey(selectedProvider); if (!provider || !apiKey) { throw new AIError({ code: "PROVIDER_NOT_AVAILABLE", message: `Provider ${selectedProvider} is not available or API key is missing`, type: "invalid_request", provider: selectedProvider, retryable: false }); } const finalRequest = { ...request, model: request.model || this.getDefaultModelForProvider(selectedProvider), stream: true }; let totalTokens = 0; let totalCost = 0; try { for await (const chunk of provider.chatStream(finalRequest, apiKey)) { if (chunk.usage) { totalTokens = chunk.usage.totalTokens; totalCost = chunk.usage.cost; } yield chunk; } if (this.enableUsageTracking && totalTokens > 0) { this.usageTracker.recordUsage( selectedProvider, finalRequest.model, totalTokens, totalCost, userId ); } } catch (error) { throw error; } } /** * Get available models from specified provider */ async getModels(provider) { if (provider) { const providerInstance = this.providers[provider]; const apiKey = this.getApiKey(provider); if (!providerInstance || !apiKey) { return []; } try { return await providerInstance.getModels(); } catch (error) { console.error(`Failed to get models from ${provider}:`, error); return []; } } const allModels = []; for (const [providerType, providerInstance] of Object.entries(this.providers)) { const apiKey = this.getApiKey(providerType); if (apiKey) { try { const models = await providerInstance.getModels(); allModels.push(...models); } catch (error) { console.error(`Failed to get models from ${providerType}:`, error); } } } return allModels; } /** * Validate API keys for all providers */ async validateApiKeys() { const results = {}; for (const [provider, providerInstance] of Object.entries(this.providers)) { const apiKey = this.getApiKey(provider); if (apiKey) { try { results[provider] = await providerInstance.validateApiKey(apiKey); } catch (error) { results[provider] = false; } } else { results[provider] = false; } } return results; } /** * Get usage statistics (Community Edition) */ getUsageStats() { if (!this.enableUsageTracking) { return { message: "Usage tracking is disabled", enableInstructions: "Set enableUsageTracking: true in config to track usage" }; } const stats = this.usageTracker.getUsageStats(); const validation = this.licenseValidator.validateUsage(stats); return { ...stats, limits: { requestsPerMinute: 10, requestsPerDay: 100, requestsPerMonth: 1e3, maxUniqueUsers: 10 }, validation, upgradeMessage: validation.violations.length > 0 ? UPGRADE_MESSAGES.COMMERCIAL_USE : void 0 }; } /** * Estimate cost for a request (basic estimation only) */ async estimateCost(request, provider) { const estimates = []; const providersToCheck = provider ? [provider] : this.getAvailableProviders(); for (const providerType of providersToCheck) { const providerInstance = this.providers[providerType]; const apiKey = this.getApiKey(providerType); if (providerInstance && apiKey) { try { const cost = await providerInstance.estimateCost(request); estimates.push({ provider: providerType, cost }); } catch (error) { console.error(`Failed to estimate cost for ${providerType}:`, error); } } } return estimates.sort((a, b) => a.cost - b.cost); } /** * Clear cache */ clearCache() { this.cache.clear(); } // Private methods initializeProviders(baseUrls) { if (this.apiKeys.openai) { this.providers["OPENAI" /* OPENAI */] = new OpenAIProvider(baseUrls?.openai); } if (this.apiKeys.anthropic) { this.providers["ANTHROPIC" /* ANTHROPIC */] = new AnthropicProvider(baseUrls?.anthropic); } if (this.apiKeys.google) { this.providers["GOOGLE" /* GOOGLE */] = new GoogleProvider(baseUrls?.google); } } getAvailableProviders() { return Object.keys(this.providers); } getRandomAvailableProvider() { const available = this.getAvailableProviders(); if (available.length === 0) { throw new AIError({ code: "NO_PROVIDERS_AVAILABLE", message: "No API providers are configured. Please provide at least one API key.", type: "invalid_request", provider: "OPENAI" /* OPENAI */, retryable: false }); } return available[Math.floor(Math.random() * available.length)]; } getApiKey(provider) { switch (provider) { case "OPENAI" /* OPENAI */: return this.apiKeys.openai; case "ANTHROPIC" /* ANTHROPIC */: return this.apiKeys.anthropic; case "GOOGLE" /* GOOGLE */: return this.apiKeys.google; default: return void 0; } } getDefaultModelForProvider(provider) { switch (provider) { case "OPENAI" /* OPENAI */: return "gpt-4o-mini"; case "ANTHROPIC" /* ANTHROPIC */: return "claude-3-haiku-20240307"; case "GOOGLE" /* GOOGLE */: return "gemini-1.5-flash"; default: return "gpt-4o-mini"; } } generateCacheKey(request) { return JSON.stringify({ messages: request.messages, temperature: request.temperature, maxTokens: request.maxTokens, tools: request.tools }); } getCachedResponse(request) { const key = this.generateCacheKey(request); const entry = this.cache.get(key); const cacheTTL = 5 * 60 * 1e3; if (entry && Date.now() - entry.timestamp < cacheTTL) { return entry.response; } if (entry) { this.cache.delete(key); } return null; } setCachedResponse(request, response) { if (this.cache.size >= 100) { const oldestKey = this.cache.keys().next().value; if (oldestKey) { this.cache.delete(oldestKey); } } const key = this.generateCacheKey(request); this.cache.set(key