UNPKG

@mariozechner/claude-bridge

Version:

Use non-Anthropic models with Claude Code by proxying requests through the lemmy unified interface

1,655 lines (1,650 loc) 96.7 kB
// ../../packages/lemmy/dist/src/clients/anthropic.js import Anthropic from "@anthropic-ai/sdk"; // ../../packages/lemmy/dist/src/tools/zod-converter.js import { zodToJsonSchema } from "zod-to-json-schema"; function convertZodSchema(schema) { return zodToJsonSchema(schema); } function zodToOpenAI(tool) { const jsonSchema = convertZodSchema(tool.schema); return { type: "function", function: { name: tool.name, description: tool.description, parameters: jsonSchema } }; } function zodToAnthropic(tool) { const jsonSchema = convertZodSchema(tool.schema); const inputSchema = { type: "object", ...jsonSchema }; return { name: tool.name, description: tool.description, input_schema: inputSchema }; } function zodToGoogle(tool) { const jsonSchema = convertZodSchema(tool.schema); return { name: tool.name, description: tool.description, parameters: jsonSchema }; } function zodToMCP(tool) { const jsonSchema = convertZodSchema(tool.schema); return { name: tool.name, description: tool.description, inputSchema: jsonSchema }; } // ../../packages/lemmy/dist/src/clients/anthropic.js var AnthropicClient = class { anthropic; config; constructor(config) { this.config = config; const isOAuthToken = config.apiKey.startsWith("sk-ant-oat"); this.anthropic = new Anthropic({ ...isOAuthToken ? { authToken: config.apiKey } : { apiKey: config.apiKey }, baseURL: config.baseURL, maxRetries: config.maxRetries ?? 3 }); } getModel() { return this.config.model; } getProvider() { return "anthropic"; } buildAnthropicParams(options, messages) { const modelData = findModelData(this.config.model); const defaultMaxTokens = options?.maxOutputTokens || modelData?.maxOutputTokens || 4096; const maxThinkingTokens = options?.maxThinkingTokens || this.config.defaults?.maxThinkingTokens || 3e3; const thinkingEnabled = options?.thinkingEnabled ?? this.config.defaults?.thinkingEnabled ?? false; const maxTokens = thinkingEnabled ? Math.max(defaultMaxTokens, maxThinkingTokens + 1e3) : defaultMaxTokens; const params = { model: this.config.model, max_tokens: maxTokens, messages, stream: true }; const systemMessage = options.context?.getSystemMessage(); if (systemMessage) { params.system = systemMessage; } if (options.temperature !== void 0) params.temperature = options.temperature; if (options.topK !== void 0) params.top_k = options.topK; if (options.topP !== void 0) params.top_p = options.topP; if (options.stopSequences !== void 0) params.stop_sequences = [options.stopSequences]; if (options.serviceTier !== void 0) params.service_tier = options.serviceTier; if (options.userId !== void 0) params.metadata = { user_id: options.userId }; if (thinkingEnabled) { params.thinking = { type: "enabled", budget_tokens: maxThinkingTokens }; params.temperature = 1; } if (options.toolChoice !== void 0) { params.tool_choice = { type: options.toolChoice, ...options.disableParallelToolUse !== void 0 && { disable_parallel_tool_use: options.disableParallelToolUse } }; } else if (options.disableParallelToolUse !== void 0) { params.tool_choice = { type: "auto", disable_parallel_tool_use: options.disableParallelToolUse }; } const tools = options?.context?.listTools() || []; const anthropicTools = tools.map((tool) => zodToAnthropic(tool)); if (anthropicTools.length > 0) { params.tools = anthropicTools; } return params; } async ask(input, options) { const startTime = performance.now(); try { if (options?.abortSignal?.aborted) { const modelError = { type: "invalid_request", message: "Request was aborted", retryable: false }; return { type: "error", error: modelError }; } const userInput = typeof input === "string" ? { content: input } : input; const userMessage = { role: "user", ...userInput.content !== void 0 && { content: userInput.content }, ...userInput.toolResults !== void 0 && { toolResults: userInput.toolResults }, ...userInput.attachments !== void 0 && { attachments: userInput.attachments }, timestamp: /* @__PURE__ */ new Date() }; if (options?.context) { options.context.addMessage(userMessage); } const messages = this.convertMessagesToAnthropic(options?.context?.getMessages() || [userMessage]); const mergedOptions = { ...this.config.defaults, ...options }; const requestParams = this.buildAnthropicParams(mergedOptions, messages); if (options?.abortSignal?.aborted) { const modelError = { type: "invalid_request", message: "Request was aborted", retryable: false }; return { type: "error", error: modelError }; } const stream = await this.anthropic.messages.create(requestParams, { signal: options?.abortSignal }); return await this.processStream(stream, options, startTime); } catch (error) { return this.handleError(error); } } convertMessagesToAnthropic(contextMessages) { const messages = []; for (const msg of contextMessages) { if (msg.role === "user") { const contentBlocks = []; if (msg.content?.trim()) { contentBlocks.push({ type: "text", text: msg.content }); } if (msg.toolResults && msg.toolResults.length > 0) { for (const toolResult of msg.toolResults) { contentBlocks.push({ type: "tool_result", tool_use_id: toolResult.toolCallId, content: toolResult.content }); } } if (msg.attachments && msg.attachments.length > 0) { for (const attachment of msg.attachments) { if (attachment.type === "image") { const supportedMimeTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"]; if (!supportedMimeTypes.includes(attachment.mimeType)) { throw new Error(`Unsupported image mime type: ${attachment.mimeType}. Supported types: ${supportedMimeTypes.join(", ")}`); } const dataStr = typeof attachment.data === "string" ? attachment.data : attachment.data.toString("base64"); if (dataStr.startsWith("http://") || dataStr.startsWith("https://")) { contentBlocks.push({ type: "image", source: { type: "url", url: dataStr } }); } else { contentBlocks.push({ type: "image", source: { type: "base64", media_type: attachment.mimeType, data: dataStr } }); } } else { throw new Error(`Unsupported attachment type: ${attachment.type}. Anthropic only supports image attachments.`); } } } if (contentBlocks.length > 0) { messages.push({ role: "user", content: contentBlocks.length === 1 && contentBlocks[0]?.type === "text" ? contentBlocks[0].text : contentBlocks }); } } else if (msg.role === "assistant") { const contentBlocks = []; if (msg.thinking?.trim()) { contentBlocks.push({ type: "thinking", thinking: msg.thinking, signature: msg.thinkingSignature || "" }); } if (msg.content?.trim()) { contentBlocks.push({ type: "text", text: msg.content }); } if (msg.toolCalls && msg.toolCalls.length > 0) { for (const toolCall of msg.toolCalls) { contentBlocks.push({ type: "tool_use", id: toolCall.id, name: toolCall.name, input: toolCall.arguments }); } } if (contentBlocks.length > 0) { messages.push({ role: "assistant", content: contentBlocks.length === 1 && contentBlocks[0]?.type === "text" ? contentBlocks[0].text : contentBlocks }); } } } return messages; } async processStream(stream, options, startTime) { let content = ""; let thinkingContent = ""; let thinkingSignature = ""; let inputTokens = 0; let outputTokens = 0; let stopReason; let toolCalls = []; let currentToolCall = null; let currentBlockType = null; try { for await (const event of stream) { if (options?.abortSignal?.aborted) { const modelError = { type: "invalid_request", message: "Request was aborted during streaming", retryable: false }; return { type: "error", error: modelError }; } switch (event.type) { case "message_start": inputTokens = event.message.usage.input_tokens; outputTokens = event.message.usage.output_tokens; break; case "content_block_delta": if (event.delta.type === "text_delta") { const chunk = event.delta.text; if (currentBlockType === "thinking") { thinkingContent += chunk; options?.onThinkingChunk?.(chunk); } else { content += chunk; options?.onChunk?.(chunk); } } else if (event.delta.type === "thinking_delta") { const thinkingChunk = event.delta.thinking || ""; thinkingContent += thinkingChunk; options?.onThinkingChunk?.(thinkingChunk); } else if (event.delta.type === "signature_delta") { thinkingSignature += event.delta.signature || ""; } else if (event.delta.type === "input_json_delta") { if (currentToolCall) { const currentArgs = typeof currentToolCall.arguments === "string" ? currentToolCall.arguments : ""; currentToolCall.arguments = currentArgs + event.delta.partial_json; } } break; case "content_block_start": if (event.content_block.type === "tool_use") { currentBlockType = "tool_use"; currentToolCall = { id: event.content_block.id, name: event.content_block.name, arguments: "" }; } else if (event.content_block.type === "text") { currentBlockType = "text"; } else if (event.content_block.type === "thinking") { currentBlockType = "thinking"; } break; case "content_block_stop": if (currentBlockType === "tool_use" && currentToolCall && currentToolCall.id && currentToolCall.name) { try { let argsString = typeof currentToolCall.arguments === "string" ? currentToolCall.arguments : "{}"; if (argsString.trim() === "") { argsString = "{}"; } const parsedArgs = JSON.parse(argsString); toolCalls.push({ id: currentToolCall.id, name: currentToolCall.name, arguments: parsedArgs }); } catch (error) { console.error("Failed to parse tool arguments:", error); } currentToolCall = null; } currentBlockType = null; break; case "message_delta": if (event.delta.stop_reason) { stopReason = event.delta.stop_reason; } if (event.usage?.output_tokens !== void 0) { outputTokens = event.usage.output_tokens; } break; case "message_stop": break; } } const tokens = { input: inputTokens, output: outputTokens }; const cost = calculateTokenCost(this.config.model, tokens); const endTime = performance.now(); const took = startTime ? (endTime - startTime) / 1e3 : 0; const assistantMessage = { role: "assistant", ...content && { content }, ...toolCalls.length > 0 && { toolCalls }, ...thinkingContent && { thinking: thinkingContent }, ...thinkingSignature && { thinkingSignature }, usage: tokens, provider: this.getProvider(), model: this.getModel(), timestamp: /* @__PURE__ */ new Date(), took }; if (options?.context) { options.context.addMessage(assistantMessage); } const response = { type: "success", stopReason: this.mapStopReason(stopReason) || "complete", message: assistantMessage, tokens, cost }; return response; } catch (error) { return this.handleError(error); } } mapStopReason(reason) { switch (reason) { case "end_turn": return "complete"; case "max_tokens": return "max_tokens"; case "stop_sequence": return "stop_sequence"; case "tool_use": return "tool_call"; default: return void 0; } } handleError(error) { if (error instanceof DOMException && error.name === "AbortError") { const modelError2 = { type: "invalid_request", message: "Request was aborted", retryable: false }; return { type: "error", error: modelError2 }; } if (error instanceof Error && "status" in error) { const apiError = error; const modelError2 = { type: this.getErrorType(apiError.status), message: apiError.message, retryable: this.isRetryable(apiError.status), ...this.getRetryAfter(apiError) !== void 0 && { retryAfter: this.getRetryAfter(apiError) } }; return { type: "error", error: modelError2 }; } const modelError = { type: "api_error", message: error instanceof Error ? error.message : "Unknown error", retryable: false }; return { type: "error", error: modelError }; } getErrorType(status) { switch (status) { case 401: return "auth"; case 429: return "rate_limit"; case 400: case 404: case 422: return "invalid_request"; default: return "api_error"; } } isRetryable(status) { return status === 429 || status !== void 0 && status >= 500; } getRetryAfter(error) { const retryAfter = error.headers?.["retry-after"]; if (retryAfter) { const seconds = parseInt(retryAfter, 10); return isNaN(seconds) ? void 0 : seconds; } return void 0; } }; // ../../packages/lemmy/dist/src/clients/google.js import { GoogleGenAI } from "@google/genai"; var GoogleClient = class { google; config; nextId = 0; constructor(config) { this.config = config; this.google = new GoogleGenAI({ apiKey: config.apiKey, ...config.projectId && { project: config.projectId }, ...config.baseURL && { apiUrl: config.baseURL } }); } getModel() { return this.config.model; } getProvider() { return "google"; } buildGoogleParams(options) { const modelData = findModelData(this.config.model); const maxOutputTokens = options.maxOutputTokens || this.config.defaults?.maxOutputTokens || modelData?.maxOutputTokens || 4096; const config = { maxOutputTokens: options.maxOutputTokens || maxOutputTokens }; if (options.temperature !== void 0) config.temperature = options.temperature; if (options.topP !== void 0) config.topP = options.topP; if (options.topK !== void 0) config.topK = options.topK; if (options.candidateCount !== void 0) config.candidateCount = options.candidateCount; if (options.stopSequences !== void 0) config.stopSequences = Array.isArray(options.stopSequences) ? options.stopSequences : [options.stopSequences]; if (options.responseLogprobs !== void 0) config.responseLogprobs = options.responseLogprobs; if (options.logprobs !== void 0) config.logprobs = options.logprobs; if (options.presencePenalty !== void 0) config.presencePenalty = options.presencePenalty; if (options.frequencyPenalty !== void 0) config.frequencyPenalty = options.frequencyPenalty; if (options.seed !== void 0) config.seed = options.seed; if (options.responseMimeType !== void 0) config.responseMimeType = options.responseMimeType; const systemMessage = options.context?.getSystemMessage(); if (systemMessage) { config.systemInstruction = systemMessage; } const tools = options?.context?.listTools() || []; const googleTools = tools.map((tool) => zodToGoogle(tool)); if (googleTools && googleTools.length > 0) { config.tools = [ { functionDeclarations: googleTools } ]; } const includeThoughts = options.includeThoughts ?? false; if (includeThoughts) { config.thinkingConfig = { includeThoughts, ...options.thinkingBudget && { thinkingBudget: options.thinkingBudget } }; } return { model: this.config.model, contents: [], config }; } async ask(input, options) { const startTime = performance.now(); try { if (options?.abortSignal?.aborted) { const modelError = { type: "invalid_request", message: "Request was aborted", retryable: false }; return { type: "error", error: modelError }; } const userInput = typeof input === "string" ? { content: input } : input; const userMessage = { role: "user", ...userInput.content !== void 0 && { content: userInput.content }, ...userInput.toolResults !== void 0 && { toolResults: userInput.toolResults }, ...userInput.attachments !== void 0 && { attachments: userInput.attachments }, timestamp: /* @__PURE__ */ new Date() }; if (options?.context) { options.context.addMessage(userMessage); } const contents = this.convertMessagesToGoogle(options?.context?.getMessages() || [userMessage]); const mergedOptions = { ...this.config.defaults, ...options }; const requestParams = this.buildGoogleParams(mergedOptions); requestParams.contents = contents; if (options?.abortSignal?.aborted) { const modelError = { type: "invalid_request", message: "Request was aborted", retryable: false }; return { type: "error", error: modelError }; } if (requestParams.config && options?.abortSignal) { requestParams.config.abortSignal = options.abortSignal; } const stream = await this.google.models.generateContentStream(requestParams); return await this.processStream(stream, options, startTime); } catch (error) { return this.handleError(error); } } convertMessagesToGoogle(contextMessages) { const contents = []; for (const msg of contextMessages) { if (msg.role === "user") { const parts = []; if (msg.content?.trim()) { parts.push({ text: msg.content }); } if (msg.toolResults && msg.toolResults.length > 0) { for (const toolResult of msg.toolResults) { const functionResponse = { name: toolResult.toolCallId, // Google uses function name for tool call ID response: { result: toolResult.content } }; parts.push({ functionResponse }); } } if (msg.attachments && msg.attachments.length > 0) { for (const attachment of msg.attachments) { if (attachment.type === "image") { const supportedMimeTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"]; if (!supportedMimeTypes.includes(attachment.mimeType)) { throw new Error(`Unsupported image mime type: ${attachment.mimeType}. Supported types: ${supportedMimeTypes.join(", ")}`); } const dataStr = typeof attachment.data === "string" ? attachment.data : attachment.data.toString("base64"); if (dataStr.startsWith("http://") || dataStr.startsWith("https://")) { parts.push({ fileData: { mimeType: attachment.mimeType, fileUri: dataStr } }); } else { parts.push({ inlineData: { mimeType: attachment.mimeType, data: dataStr } }); } } else { throw new Error(`Unsupported attachment type: ${attachment.type}. Google AI only supports image attachments.`); } } } if (parts.length > 0) { contents.push({ role: "user", parts }); } } else if (msg.role === "assistant") { const parts = []; if (msg.thinking?.trim()) { parts.push({ text: msg.thinking, thought: true }); } if (msg.content?.trim()) { parts.push({ text: msg.content }); } if (msg.toolCalls && msg.toolCalls.length > 0) { for (const toolCall of msg.toolCalls) { const functionCall = { name: toolCall.name, args: toolCall.arguments }; parts.push({ functionCall }); } } if (parts.length > 0) { contents.push({ role: "model", parts }); } } } return contents; } async processStream(stream, options, startTime) { let content = ""; let thinkingContent = ""; let inputTokens = 0; let outputTokens = 0; let stopReason; let toolCalls = []; try { for await (const chunk of stream) { if (options?.abortSignal?.aborted) { const modelError = { type: "invalid_request", message: "Request was aborted during streaming", retryable: false }; return { type: "error", error: modelError }; } if (chunk.candidates && chunk.candidates.length > 0) { const candidate = chunk.candidates[0]; if (!candidate) { continue; } if (chunk.usageMetadata) { inputTokens = chunk.usageMetadata.promptTokenCount || 0; outputTokens = chunk.usageMetadata.candidatesTokenCount || 0; } if (candidate.finishReason) { stopReason = candidate.finishReason; } if (candidate.content && candidate.content.parts) { for (const part of candidate.content.parts) { if (part.text) { if (part.thought) { thinkingContent += part.text; options?.onThinkingChunk?.(part.text); } else { content += part.text; options?.onChunk?.(part.text); } } else if (part.functionCall) { const toolCall = { id: part.functionCall.name + "_" + this.nextId++, // Generate unique ID name: part.functionCall.name || "unknown", arguments: part.functionCall.args || {} }; toolCalls.push(toolCall); } } } } } const tokens = { input: inputTokens, output: outputTokens }; const cost = calculateTokenCost(this.config.model, tokens); const endTime = performance.now(); const took = startTime ? (endTime - startTime) / 1e3 : 0; const assistantMessage = { role: "assistant", ...content && { content }, ...toolCalls.length > 0 && { toolCalls }, ...thinkingContent && { thinking: thinkingContent }, usage: tokens, provider: this.getProvider(), model: this.getModel(), timestamp: /* @__PURE__ */ new Date(), took }; if (options?.context) { options.context.addMessage(assistantMessage); } let finalStopReason = this.mapStopReason(stopReason) || "complete"; if (toolCalls.length > 0) { finalStopReason = "tool_call"; } const response = { type: "success", stopReason: finalStopReason, message: assistantMessage, tokens, cost }; return response; } catch (error) { return this.handleError(error); } } mapStopReason(reason) { switch (reason) { case "STOP": return "complete"; case "MAX_TOKENS": return "max_tokens"; case "SAFETY": return "stop_sequence"; case "RECITATION": return "stop_sequence"; case "OTHER": return "complete"; default: return void 0; } } handleError(error) { if (error instanceof DOMException && error.name === "AbortError") { const modelError2 = { type: "invalid_request", message: "Request was aborted", retryable: false }; return { type: "error", error: modelError2 }; } if (error && typeof error === "object") { const apiError = error; let status = apiError.status; let message = apiError.message || "Unknown API error"; if (typeof message === "string" && message.includes("API key not valid")) { status = 401; message = "Invalid API key"; } else if (typeof message === "string" && message.includes("quota")) { status = 429; } const modelError2 = { type: this.getErrorType(status), message, retryable: this.isRetryable(status), ...this.getRetryAfter(apiError) !== void 0 && { retryAfter: this.getRetryAfter(apiError) } }; return { type: "error", error: modelError2 }; } const modelError = { type: "api_error", message: error instanceof Error ? error.message : "Unknown error", retryable: false }; return { type: "error", error: modelError }; } getErrorType(status) { switch (status) { case 401: return "auth"; case 429: return "rate_limit"; case 400: case 404: case 422: return "invalid_request"; default: return "api_error"; } } isRetryable(status) { return status === 429 || status !== void 0 && status >= 500; } getRetryAfter(error) { const retryAfter = error.headers?.["retry-after"]; if (retryAfter) { const seconds = parseInt(retryAfter, 10); return isNaN(seconds) ? void 0 : seconds; } return void 0; } }; // ../../packages/lemmy/dist/src/clients/openai.js import OpenAI from "openai"; var OpenAIClient = class { openai; config; constructor(config) { this.config = config; this.openai = new OpenAI({ apiKey: config.apiKey, organization: config.organization, baseURL: config.baseURL, maxRetries: config.maxRetries ?? 3 }); } getModel() { return this.config.model; } getProvider() { return "openai"; } buildOpenAIParams(options, messages) { const params = { model: this.config.model, stream: true, stream_options: { include_usage: true }, messages }; const modelData = findModelData(this.config.model); params.max_completion_tokens = options?.maxOutputTokens || this.config.defaults?.maxOutputTokens || modelData?.maxOutputTokens || 4096; if (options.temperature !== void 0) params.temperature = options.temperature; if (options.topP !== void 0) params.top_p = options.topP; if (options.presencePenalty !== void 0) params.presence_penalty = options.presencePenalty; if (options.frequencyPenalty !== void 0) params.frequency_penalty = options.frequencyPenalty; if (options.logprobs !== void 0) params.logprobs = options.logprobs; if (options.topLogprobs !== void 0) params.top_logprobs = options.topLogprobs; if (options.maxCompletionTokens !== void 0) params.max_completion_tokens = options.maxCompletionTokens; if (options.n !== void 0) params.n = options.n; if (options.parallelToolCalls !== void 0) params.parallel_tool_calls = options.parallelToolCalls; if (options.responseFormat !== void 0) { if (options.responseFormat === "text") { params.response_format = { type: "text" }; } else if (options.responseFormat === "json_object") { params.response_format = { type: "json_object" }; } } if (options.seed !== void 0) params.seed = options.seed; if (options.serviceTier !== void 0) params.service_tier = options.serviceTier; if (options.stop !== void 0) params.stop = options.stop; if (options.store !== void 0) params.store = options.store; if (options.toolChoice !== void 0) params.tool_choice = options.toolChoice; if (options.user !== void 0) params.user = options.user; if (options.reasoningEffort !== void 0) params.reasoning_effort = options.reasoningEffort; const tools = options?.context?.listTools() || []; const openaiTools = tools.map((tool) => zodToOpenAI(tool)); if (openaiTools && openaiTools.length > 0) { params.tools = openaiTools; params.tool_choice = options.toolChoice || "auto"; } return params; } async ask(input, options) { const startTime = performance.now(); try { if (options?.abortSignal?.aborted) { const modelError = { type: "invalid_request", message: "Request was aborted", retryable: false }; return { type: "error", error: modelError }; } const userInput = typeof input === "string" ? { content: input } : input; const userMessage = { role: "user", ...userInput.content !== void 0 && { content: userInput.content }, ...userInput.toolResults !== void 0 && { toolResults: userInput.toolResults }, ...userInput.attachments !== void 0 && { attachments: userInput.attachments }, timestamp: /* @__PURE__ */ new Date() }; if (options?.context) { options.context.addMessage(userMessage); } const messages = this.convertMessages(options?.context?.getMessages() || [userMessage]); const systemMessage = options?.context?.getSystemMessage(); if (systemMessage) { messages.unshift({ role: "system", content: systemMessage }); } const mergedOptions = { ...this.config.defaults, ...options }; const requestParams = this.buildOpenAIParams(mergedOptions, messages); if (options?.abortSignal?.aborted) { const modelError = { type: "invalid_request", message: "Request was aborted", retryable: false }; return { type: "error", error: modelError }; } const stream = await this.openai.chat.completions.create(requestParams, { signal: options?.abortSignal }); return await this.processStream(stream, options, startTime); } catch (error) { return this.handleError(error); } } convertMessages(contextMessages) { const messages = []; for (const msg of contextMessages) { if (msg.role === "user") { const contentBlocks = []; if (msg.content?.trim()) { contentBlocks.push({ type: "text", text: msg.content }); } if (msg.toolResults && msg.toolResults.length > 0) { for (const toolResult of msg.toolResults) { messages.push({ role: "tool", tool_call_id: toolResult.toolCallId, content: toolResult.content }); } } if (msg.attachments && msg.attachments.length > 0) { for (const attachment of msg.attachments) { if (attachment.type === "image") { const dataStr = typeof attachment.data === "string" && (attachment.data.startsWith("http://") || attachment.data.startsWith("https://")) ? attachment.data : `data:${attachment.mimeType};base64,${attachment.data.toString("base64")}`; contentBlocks.push({ type: "image_url", image_url: { url: dataStr // TODO: detail, needs to be piped through somehow, possibly per attachment.. } }); } } } if (msg.content?.trim() || msg.attachments && msg.attachments.length > 0) { messages.push({ role: "user", content: contentBlocks.length === 1 && contentBlocks[0]?.type === "text" ? contentBlocks[0].text : contentBlocks }); } } else if (msg.role === "assistant") { if (msg.toolCalls && msg.toolCalls.length > 0) { const toolCalls = msg.toolCalls.map((toolCall) => ({ id: toolCall.id, type: "function", function: { name: toolCall.name, arguments: JSON.stringify(toolCall.arguments) } })); messages.push({ role: "assistant", content: msg.content || null, tool_calls: toolCalls }); } else if (msg.content) { messages.push({ role: "assistant", content: msg.content }); } } } return messages; } async processStream(stream, options, startTime) { let content = ""; let inputTokens = 0; let outputTokens = 0; let stopReason; let toolCalls = []; const currentToolCalls = /* @__PURE__ */ new Map(); try { for await (const chunk of stream) { if (options?.abortSignal?.aborted) { const modelError = { type: "invalid_request", message: "Request was aborted during streaming", retryable: false }; return { type: "error", error: modelError }; } if (chunk.usage) { inputTokens = chunk.usage.prompt_tokens || 0; outputTokens = chunk.usage.completion_tokens || 0; } const choice = chunk.choices?.[0]; if (!choice) continue; if (choice.delta?.content) { const contentChunk = choice.delta.content; content += contentChunk; options?.onChunk?.(contentChunk); } if (choice.delta?.tool_calls) { for (const toolCallDelta of choice.delta.tool_calls) { const index = toolCallDelta.index; if (!currentToolCalls.has(index)) { currentToolCalls.set(index, {}); } const currentToolCall = currentToolCalls.get(index); if (toolCallDelta.id) { currentToolCall.id = toolCallDelta.id; } if (toolCallDelta.function) { if (toolCallDelta.function.name) { currentToolCall.name = toolCallDelta.function.name; } if (toolCallDelta.function.arguments) { currentToolCall.arguments = (currentToolCall.arguments || "") + toolCallDelta.function.arguments; } } } } if (choice.finish_reason) { stopReason = choice.finish_reason; } } for (const [_, toolCallData] of currentToolCalls) { if (toolCallData.id && toolCallData.name) { try { let argsString = toolCallData.arguments || "{}"; if (argsString.trim() === "") { argsString = "{}"; } const parsedArgs = JSON.parse(argsString); toolCalls.push({ id: toolCallData.id, name: toolCallData.name, arguments: parsedArgs }); } catch (error) { console.error("Failed to parse tool arguments:", error); } } } if (inputTokens === 0 && outputTokens === 0 && content) { inputTokens = Math.ceil(content.length / 6); outputTokens = Math.ceil(content.length / 4); } const tokens = { input: inputTokens, output: outputTokens }; const cost = calculateTokenCost(this.config.model, tokens); const endTime = performance.now(); const took = startTime ? (endTime - startTime) / 1e3 : 0; const assistantMessage = { role: "assistant", ...content && { content }, ...toolCalls.length > 0 && { toolCalls }, usage: tokens, provider: this.getProvider(), model: this.getModel(), timestamp: /* @__PURE__ */ new Date(), took }; if (options?.context) { options.context.addMessage(assistantMessage); } const response = { type: "success", stopReason: this.mapStopReason(stopReason) || "complete", message: assistantMessage, tokens, cost }; return response; } catch (error) { return this.handleError(error); } } mapStopReason(reason) { switch (reason) { case "stop": return "complete"; case "length": return "max_tokens"; case "content_filter": return "stop_sequence"; case "tool_calls": return "tool_call"; default: return void 0; } } handleError(error) { if (error instanceof DOMException && error.name === "AbortError") { const modelError2 = { type: "invalid_request", message: "Request was aborted", retryable: false }; return { type: "error", error: modelError2 }; } if (error instanceof Error && "status" in error) { const apiError = error; const modelError2 = { type: this.getErrorType(apiError.status), message: apiError.message + ":\n" + JSON.stringify(error), retryable: this.isRetryable(apiError.status), ...this.getRetryAfter(apiError) !== void 0 && { retryAfter: this.getRetryAfter(apiError) } }; return { type: "error", error: modelError2 }; } const modelError = { type: "api_error", message: error instanceof Error ? error.message : JSON.stringify(error), retryable: false }; return { type: "error", error: modelError }; } getErrorType(status) { switch (status) { case 401: return "auth"; case 429: return "rate_limit"; case 400: case 404: case 422: return "invalid_request"; default: return "api_error"; } } isRetryable(status) { return status === 429 || status !== void 0 && status >= 500; } getRetryAfter(error) { const retryAfter = error.headers?.["retry-after"]; if (retryAfter) { const seconds = parseInt(retryAfter, 10); return isNaN(seconds) ? void 0 : seconds; } return void 0; } }; // ../../packages/lemmy/dist/src/generated/models.js var AnthropicModelData = { "claude-2.0": { contextWindow: 2e5, maxOutputTokens: 4096, supportsTools: false, supportsImageInput: false, pricing: { inputPerMillion: 3, outputPerMillion: 15 } }, "claude-2.1": { contextWindow: 2e5, maxOutputTokens: 4096, supportsTools: false, supportsImageInput: false, pricing: { inputPerMillion: 3, outputPerMillion: 15 } }, "claude-3-5-haiku-20241022": { contextWindow: 2e5, maxOutputTokens: 8192, supportsTools: true, supportsImageInput: true, pricing: { inputPerMillion: 0.8, outputPerMillion: 4 } }, "claude-3-5-haiku-latest": { contextWindow: 2e5, maxOutputTokens: 8192, supportsTools: true, supportsImageInput: true, pricing: { inputPerMillion: 0.8, outputPerMillion: 4 } }, "claude-3-5-sonnet-20240620": { contextWindow: 2e5, maxOutputTokens: 8192, supportsTools: true, supportsImageInput: true, pricing: { inputPerMillion: 3, outputPerMillion: 15 } }, "claude-3-5-sonnet-20241022": { contextWindow: 2e5, maxOutputTokens: 8192, supportsTools: true, supportsImageInput: true, pricing: { inputPerMillion: 3, outputPerMillion: 15 } }, "claude-3-5-sonnet-latest": { contextWindow: 2e5, maxOutputTokens: 8192, supportsTools: true, supportsImageInput: true, pricing: { inputPerMillion: 3, outputPerMillion: 15 } }, "claude-3-7-sonnet-20250219": { contextWindow: 2e5, maxOutputTokens: 64e3, supportsTools: true, supportsImageInput: true, pricing: { inputPerMillion: 3, outputPerMillion: 15 } }, "claude-3-7-sonnet-latest": { contextWindow: 2e5, maxOutputTokens: 64e3, supportsTools: true, supportsImageInput: true, pricing: { inputPerMillion: 3, outputPerMillion: 15 } }, "claude-3-haiku-20240307": { contextWindow: 2e5, maxOutputTokens: 4096, supportsTools: true, supportsImageInput: true, pricing: { inputPerMillion: 0.25, outputPerMillion: 1.25 } }, "claude-3-opus-20240229": { contextWindow: 2e5, maxOutputTokens: 4096, supportsTools: true, supportsImageInput: true, pricing: { inputPerMillion: 15, outputPerMillion: 75 } }, "claude-3-opus-latest": { contextWindow: 2e5, maxOutputTokens: 4096, supportsTools: true, supportsImageInput: true, pricing: { inputPerMillion: 15, outputPerMillion: 75 } }, "claude-3-sonnet-20240229": { contextWindow: 2e5, maxOutputTokens: 4096, supportsTools: true, supportsImageInput: true, pricing: { inputPerMillion: 3, outputPerMillion: 15 } }, "claude-opus-4-20250514": { contextWindow: 2e5, maxOutputTokens: 32e3, supportsTools: true, supportsImageInput: true, pricing: { inputPerMillion: 15, outputPerMillion: 75 } }, "claude-sonnet-4-20250514": { contextWindow: 2e5, maxOutputTokens: 64e3, supportsTools: true, supportsImageInput: true, pricing: { inputPerMillion: 3, outputPerMillion: 15 } } }; var OpenAIModelData = { "babbage-002": { contextWindow: 0, maxOutputTokens: 16384, supportsTools: false, supportsImageInput: false, pricing: { inputPerMillion: 0.4, outputPerMillion: 0.4 } }, "chatgpt-4o-latest": { contextWindow: 128e3, maxOutputTokens: 16384, supportsTools: false, supportsImageInput: true, pricing: { inputPerMillion: 5, outputPerMillion: 15 } }, "codex-mini-latest": { contextWindow: 2e5, maxOutputTokens: 1e5, supportsTools: false, supportsImageInput: true, pricing: { inputPerMillion: 1.5, outputPerMillion: 6 } }, "computer-use-preview": { contextWindow: 8192, maxOutputTokens: 1024, supportsTools: false, supportsImageInput: true, pricing: { inputPerMillion: 3, outputPerMillion: 12 } }, "computer-use-preview-2025-03-11": { contextWindow: 8192, maxOutputTokens: 1024, supportsTools: false, supportsImageInput: true, pricing: { inputPerMillion: 3, outputPerMillion: 12 } }, "davinci-002": { contextWindow: 0, maxOutputTokens: 16384, supportsTools: false, supportsImageInput: false, pricing: { inputPerMillion: 2, outputPerMillion: 2 } }, "gpt-3.5-turbo": { contextWindow: 16385, maxOutputTokens: 4096, supportsTools: false, supportsImageInput: false, pricing: { inputPerMillion: 0.5, outputPerMillion: 1.5 } }, "gpt-3.5-turbo-0125": { contextWindow: 16385, maxOutputTokens: 4096, supportsTools: false, supportsImageInput: false, pricing: { inputPerMillion: 0.5, outputPerMillion: 1.5 } }, "gpt-3.5-turbo-1106": { contextWindow: 16385, maxOutputTokens: 4096, supportsTools: false, supportsImageInput: false, pricing: { inputPerMillion: 0.5, outputPerMillion: 1.5 } }, "gpt-3.5-turbo-16k": { contextWindow: 16385, maxOutputTokens: 4096, supportsTools: false, supportsImageInput: false, pricing: { inputPerMillion: 0.5, outputPerMillion: 1.5 } }, "gpt-3.5-turbo-instruct": { contextWindow: 16385, maxOutputTokens: 4096, supportsTools: false, supportsImageInput: false, pricing: { inputPerMillion: 0.5, outputPerMillion: 1.5 } }, "gpt-3.5-turbo-instruct-0914": { contextWindow: 16385, maxOutputTokens: 4096, supportsTools: false, supportsImageInput: false, pricing: { inputPerMillion: 0.5, outputPerMillion: 1.5 } }, "gpt-4": { contextWindow: 8192, maxOutputTokens: 8192, supportsTools: false, supportsImageInput: false, pricing: { inputPerMillion: 30, outputPerMillion: 60 } }, "gpt-4-0125-preview": { contextWindow: 4096, maxOutputTokens: 16384, supportsTools: false, supportsImageInput: false, pricing: { inputPerMillion: 0.5, outputPerMillion: 1.5 } }, "gpt-4-0613": { contextWindow: 8192, maxOutputTokens: 8192, supportsTools: false, supportsImageInput: false, pricing: { inputPerMillion: 30, outputPerMillion: 60 } }, "gpt-4-1106-preview": { contextWindow: 4096, maxOutputTokens: 16384, supportsTools: false, supportsImageInput: false, pricing: { inputPerMillion: 0.5, outputPerMillion: 1.5 } }, "gpt-4-turbo": { contextWindow: 128e3, maxOutputTokens: 4096, supportsTools: true, supportsImageInput: true, pricing: { inputPerMillion: 10, outputPerMillion: 30 } }, "gpt-4-turbo-2024-04-09": { contextWindow: 128e3, maxOutputTokens: 4096, supportsTools: true, supportsImageInput: true, pricing: { inputPerMillion: 10, outputPerMillion: 30 } }, "gpt-4-turbo-preview": { contextWindow: 128e3, maxOutputTokens: 4096, supportsTools: true, supportsImageInput: true, pricing: { inputPerMillion: 10, outputPerMillion: 30 } }, "gpt-4.1": { contextWindow: 1047576, maxOutputTokens: 32768, supportsTools: true, supportsImageInput: true, pricing: { inputPerMillion: 2, outputPerMillion: 8 } }, "gpt-4.1-2025-04-14": { contextWindow: 1047576, maxOutputTokens: 32768, supportsTools: true, supportsImageInput: true, pricing: { inputPerMillion: 2, outputPerMillion: 8 } }, "gpt-4.1-mini": { contextWindow: 1047576, maxOutputTokens: 32768, supportsTools: true, supportsImageInput: true, pricing: { inputPerMillion: 0.4, outputPerMillion: 1.6 } }, "gpt-4.1-mini-2025-04-14": { contextWindow: 1047576, maxOutputTokens: 32768, supportsTools: true, supportsImageInput: true, pricing: { inputPerMillion: 0.4, outputPerMillion: 1.6 } }, "gpt-4.1-nano": { contextWindow: 1047576, maxOutputTokens: 32768, supportsTools: true, supportsImageInput: true, pricing: { inputPerMillion: 0.1, outputPerMillion: 0.4 } }, "gpt-4.1-nano-2025-04-14": { contextWindow: 1047576, maxOutputTokens: 32768, supportsTools: true, supportsImageInput: true, pricing: { inputPerMillion: 0.1, outputPerMillion: 0.4 } }, "gpt-4.5-preview": { contextWindow: 128e3, maxOutputTokens: 4096, supportsTools: true, supportsImageInput: true, pricing: { inputPerMillion: 10, outputPerMillion: 30 } }, "gpt-4.5-preview-2025-02-27": { contextWindow: 128e3, maxOutputTokens: 4096, supportsTools: true, supportsImageInput: true, pricing: { inputPerMillion: 10, outputPerMillion: 30 } }, "gpt-4o": { contextWindow: 128e3, maxOutputTokens: 16384, supportsTools: true, supportsImageInput: true, pricing: { inputPerMillion: 2.5, outputPerMillion: 10 } }, "gpt-4o-2024-05-13": { contextWindow: 128e3, maxOutputTokens: 16384, supportsTools: true, supportsImageInput: true, pricing: { inputPerMillion: 2.5, outputPerMillion: 10 } }, "gpt-4o-2024-08-06": { contextWindow: 128e3, maxOutputTokens: 16384, supportsTools: true, supportsImageInput: true, pricing: { inputPerMillion: 2.5, outputPerMillion: 10 } }, "gpt-4o-2024-11-20": { contextWindow: 128e3, maxOutputTokens: 16384, supportsTools: true, supportsImageInput: true, pricing: { inputPerMillion: 2.5, outputPerMillion: 10 } }, "gpt-4o-audio-preview": { contextWindow: