import {
  buildNonStreamingCreateParams,
  buildStreamingCreateParams,
  extractThinking,
  getBackendCapabilities,
  getModelProfile,
  mapFinishReason
} from "./chunk-HGAMIPFO.js";
import {
  ensureVisionFallbackConfigLoaded
} from "./chunk-SZKEGZIB.js";
import {
  logger
} from "./chunk-K44MW7JJ.js";

// src/server/llm/models.ts
var modelCache = /* @__PURE__ */ new Map();
var llmStatus = "unknown";
var lastActiveUrl = null;
var CACHE_TTL_MS = 3e4;
function getCacheKey(url) {
  return url.replace(/\/v1\/?$/, "");
}
async function detectModel(llmBaseUrl, retries = 3, silent = false) {
  const cacheKey = getCacheKey(llmBaseUrl);
  const now = Date.now();
  const cached = modelCache.get(cacheKey);
  if (cached && now - cached.timestamp < CACHE_TTL_MS) {
    lastActiveUrl = cacheKey;
    llmStatus = "connected";
    return cached.model;
  }
  const url = llmBaseUrl.includes("/v1") ? `${llmBaseUrl}/models` : `${llmBaseUrl}/v1/models`;
  for (let attempt = 1; attempt <= retries; attempt++) {
    try {
      if (silent) {
        logger.debug("Fetching models from LLM server", { url, attempt });
      }
      const response = await fetch(url, {
        signal: AbortSignal.timeout(1e4)
      });
      if (!response.ok) {
        if (silent) {
          logger.debug("Failed to fetch models from LLM server", { status: response.status, attempt });
        } else {
          logger.warn("Failed to fetch models from LLM server", { status: response.status, attempt });
        }
        if (attempt < retries) {
          await new Promise((r) => setTimeout(r, 1e3 * attempt));
          continue;
        }
        llmStatus = "disconnected";
        return cached?.model ?? null;
      }
      const data = await response.json();
      if (data.data && data.data.length > 0) {
        const modelData = data.data[0];
        const modelId = modelData.id;
        modelCache.set(cacheKey, {
          model: modelId,
          modelInfo: modelData,
          timestamp: now
        });
        lastActiveUrl = cacheKey;
        llmStatus = "connected";
        if (silent) {
          logger.debug("Detected LLM model", {
            model: modelId,
            maxLen: modelData.max_model_len,
            root: modelData.root
          });
        } else {
          logger.info("Detected LLM model", {
            model: modelId,
            maxLen: modelData.max_model_len,
            root: modelData.root
          });
        }
        return modelId;
      }
      if (silent) {
        logger.debug("LLM server returned empty models list");
      } else {
        logger.warn("LLM server returned empty models list");
      }
      llmStatus = "disconnected";
      return null;
    } catch (error) {
      const errMsg = error instanceof Error ? error.message : String(error);
      if (silent) {
        logger.debug("Could not detect model from LLM server", { error: errMsg, attempt });
      } else {
        logger.warn("Could not detect model from LLM server", { error: errMsg, attempt });
      }
      if (attempt < retries) {
        await new Promise((r) => setTimeout(r, 1e3 * attempt));
        continue;
      }
    }
  }
  llmStatus = "disconnected";
  return cached?.model ?? null;
}
function getLlmStatus() {
  return llmStatus;
}
function clearModelCache(url) {
  if (url) {
    modelCache.delete(getCacheKey(url));
  } else {
    modelCache.clear();
  }
  llmStatus = "unknown";
}

// src/server/llm/client.ts
import OpenAI from "openai";

// src/server/utils/errors.ts
var OpenFoxError = class extends Error {
  constructor(message, code, details) {
    super(message);
    this.code = code;
    this.details = details;
    this.name = "OpenFoxError";
  }
};
var SessionNotFoundError = class extends OpenFoxError {
  constructor(sessionId) {
    super(`Session not found: ${sessionId}`, "SESSION_NOT_FOUND", { sessionId });
    this.name = "SessionNotFoundError";
  }
};
var LLMError = class extends OpenFoxError {
  constructor(message, details) {
    super(message, "LLM_ERROR", details);
    this.name = "LLMError";
  }
};

// src/server/llm/client.ts
function createLLMClient(config, initialBackend = "unknown") {
  const baseURL = config.llm.baseUrl.includes("/v1") ? config.llm.baseUrl : `${config.llm.baseUrl}/v1`;
  const openai = new OpenAI({
    baseURL,
    apiKey: config.llm.apiKey ?? "not-needed"
  });
  let model = config.llm.model;
  let profile = getModelProfile(model);
  let backend = initialBackend;
  let capabilities = getBackendCapabilities(backend);
  const disableThinking = config.llm.disableThinking ?? false;
  const idleTimeout = config.llm.idleTimeout ?? 3e4;
  return {
    getModel() {
      return model;
    },
    getProfile() {
      return profile;
    },
    getBackend() {
      return backend;
    },
    setBackend(newBackend) {
      logger.debug("Setting LLM backend", { from: backend, to: newBackend });
      backend = newBackend;
      capabilities = getBackendCapabilities(newBackend);
    },
    setModel(newModel) {
      const newProfile = getModelProfile(newModel);
      logger.debug("Switching model", {
        from: model,
        to: newModel,
        profile: newProfile.name,
        temperature: newProfile.temperature,
        supportsReasoning: newProfile.supportsReasoning
      });
      model = newModel;
      profile = newProfile;
    },
    async complete(request) {
      logger.debug("LLM complete request", {
        messageCount: request.messages.length,
        hasTools: !!request.tools?.length,
        profile: profile.name,
        disableThinking
      });
      try {
        const shouldDisableThinking = disableThinking || request.disableThinking === true;
        await ensureVisionFallbackConfigLoaded();
        const { isVisionFallbackEnabled } = await import("./vision-fallback-3MIZZ6NQ.js");
        const paramsOptions = {
          model,
          request,
          profile,
          capabilities,
          disableThinking: shouldDisableThinking,
          visionFallbackEnabled: isVisionFallbackEnabled()
        };
        if (request.onVisionFallbackStart) {
          paramsOptions.onVisionFallbackStart = request.onVisionFallbackStart;
        }
        if (request.onVisionFallbackDone) {
          paramsOptions.onVisionFallbackDone = request.onVisionFallbackDone;
        }
        const { params: createParams } = await buildNonStreamingCreateParams(paramsOptions);
        const response = await openai.chat.completions.create(createParams, {
          signal: request.signal
        });
        const choice = response.choices[0];
        if (!choice) {
          throw new LLMError("No completion choice returned");
        }
        const message = choice.message;
        let content = message.content ?? "";
        let thinkingContent = "";
        if (profile.supportsReasoning) {
          if (capabilities.supportsReasoningField) {
            thinkingContent = message.reasoning_content ?? message.reasoning ?? "";
          } else {
            const extracted = extractThinking(content);
            content = extracted.content;
            thinkingContent = extracted.thinkingContent ?? "";
          }
          if (profile.reasoningAsContent && thinkingContent.trim()) {
            content = thinkingContent;
            thinkingContent = "";
          }
        }
        if (!content.trim() && thinkingContent.trim()) {
          content = thinkingContent;
          thinkingContent = "";
        }
        const toolCalls = message.tool_calls?.map((tc) => ({
          id: tc.id,
          name: tc.function.name,
          arguments: JSON.parse(tc.function.arguments)
        }));
        return {
          id: response.id,
          content,
          ...thinkingContent ? { thinkingContent } : {},
          ...toolCalls && toolCalls.length > 0 ? { toolCalls } : {},
          finishReason: mapFinishReason(choice.finish_reason),
          usage: {
            promptTokens: response.usage?.prompt_tokens ?? 0,
            completionTokens: response.usage?.completion_tokens ?? 0,
            totalTokens: response.usage?.total_tokens ?? 0
          }
        };
      } catch (error) {
        logger.error("LLM complete error", { error: String(error) });
        throw new LLMError(error instanceof Error ? error.message : "Unknown LLM error", {
          originalError: error instanceof Error ? error : void 0
        });
      }
    },
    async *stream(request) {
      logger.debug("LLM stream request", {
        messageCount: request.messages.length,
        hasTools: !!request.tools?.length,
        profile: profile.name,
        disableThinking,
        idleTimeout
      });
      try {
        await ensureVisionFallbackConfigLoaded();
        const { isVisionFallbackEnabled } = await import("./vision-fallback-3MIZZ6NQ.js");
        const shouldDisableThinking = disableThinking || request.disableThinking === true;
        const createParams = await buildStreamingCreateParams({
          model,
          request,
          profile,
          capabilities,
          disableThinking: shouldDisableThinking,
          visionFallbackEnabled: isVisionFallbackEnabled(),
          onVisionFallbackStart: request.onVisionFallbackStart,
          onVisionFallbackDone: request.onVisionFallbackDone
        });
        const { params: streamingParams } = createParams;
        const stream = await openai.chat.completions.create(streamingParams, {
          signal: request.signal
        });
        let fullContent = "";
        let fullThinking = "";
        let inThinking = false;
        let tagBuffer = "";
        const toolCalls = /* @__PURE__ */ new Map();
        let finishReason = "stop";
        let usage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
        let responseId = "";
        let lastChunkTime = Date.now();
        const idleTimeoutController = new AbortController();
        const idleTimer = setInterval(() => {
          const idleDuration = Date.now() - lastChunkTime;
          if (idleDuration > idleTimeout) {
            logger.warn("LLM stream idle timeout triggered", { idleDuration, idleTimeout });
            idleTimeoutController.abort();
          }
        }, 100);
        try {
          for await (const chunk of stream) {
            if (idleTimeoutController.signal.aborted) {
              throw new Error(`LLM stream idle timeout: no chunks received for ${idleTimeout}ms`);
            }
            lastChunkTime = Date.now();
            responseId = chunk.id;
            if (chunk.usage) {
              usage = {
                promptTokens: chunk.usage.prompt_tokens,
                completionTokens: chunk.usage.completion_tokens,
                totalTokens: chunk.usage.total_tokens
              };
            }
            const choice = chunk.choices[0];
            if (!choice) continue;
            if (choice.finish_reason) {
              finishReason = mapFinishReason(choice.finish_reason);
            }
            const delta = choice.delta;
            if (capabilities.supportsReasoningField) {
              const reasoning = delta.reasoning_content ?? delta.reasoning;
              if (reasoning) {
                if (profile.supportsReasoning && !profile.reasoningAsContent) {
                  fullThinking += reasoning;
                  yield { type: "thinking_delta", content: reasoning };
                } else {
                  fullContent += reasoning;
                  yield { type: "text_delta", content: reasoning };
                }
              }
            }
            if (delta.content) {
              fullContent += delta.content;
              if (!capabilities.supportsReasoningField && profile.supportsReasoning && !profile.reasoningAsContent) {
                tagBuffer += delta.content;
                while (tagBuffer.length > 0) {
                  if (inThinking) {
                    const closeIdx = tagBuffer.indexOf("</think>");
                    if (closeIdx !== -1) {
                      const thinkText = tagBuffer.slice(0, closeIdx);
                      if (thinkText) {
                        fullThinking += thinkText;
                        yield { type: "thinking_delta", content: thinkText };
                      }
                      tagBuffer = tagBuffer.slice(closeIdx + "</think>".length);
                      inThinking = false;
                    } else if (tagBuffer.includes("</")) {
                      const partialIdx = tagBuffer.lastIndexOf("</");
                      const before = tagBuffer.slice(0, partialIdx);
                      if (before) {
                        fullThinking += before;
                        yield { type: "thinking_delta", content: before };
                      }
                      tagBuffer = tagBuffer.slice(partialIdx);
                      break;
                    } else {
                      fullThinking += tagBuffer;
                      yield { type: "thinking_delta", content: tagBuffer };
                      tagBuffer = "";
                    }
                  } else {
                    const openIdx = tagBuffer.indexOf("<think>");
                    if (openIdx !== -1) {
                      const textBefore = tagBuffer.slice(0, openIdx);
                      if (textBefore) {
                        yield { type: "text_delta", content: textBefore };
                      }
                      tagBuffer = tagBuffer.slice(openIdx + "<think>".length);
                      inThinking = true;
                    } else if (tagBuffer.includes("<")) {
                      const partialIdx = tagBuffer.lastIndexOf("<");
                      const before = tagBuffer.slice(0, partialIdx);
                      if (before) {
                        yield { type: "text_delta", content: before };
                      }
                      tagBuffer = tagBuffer.slice(partialIdx);
                      break;
                    } else {
                      yield { type: "text_delta", content: tagBuffer };
                      tagBuffer = "";
                    }
                  }
                }
              } else {
                yield { type: "text_delta", content: delta.content };
              }
            }
            if (delta.tool_calls) {
              for (const tc of delta.tool_calls) {
                const existing = toolCalls.get(tc.index);
                if (!existing) {
                  toolCalls.set(tc.index, {
                    id: tc.id ?? "",
                    name: tc.function?.name ?? "",
                    arguments: tc.function?.arguments ?? ""
                  });
                } else {
                  if (tc.id) existing.id = tc.id;
                  if (tc.function?.name) existing.name += tc.function.name;
                  if (tc.function?.arguments) existing.arguments += tc.function.arguments;
                }
                yield {
                  type: "tool_call_delta",
                  index: tc.index,
                  ...tc.id ? { id: tc.id } : {},
                  ...tc.function?.name ? { name: tc.function.name } : {},
                  ...tc.function?.arguments ? { arguments: tc.function.arguments } : {}
                };
              }
            }
          }
        } finally {
          clearInterval(idleTimer);
        }
        if (tagBuffer) {
          if (inThinking) {
            fullThinking += tagBuffer;
          }
          tagBuffer = "";
        }
        let finalContent = fullContent.trim();
        let finalThinking = fullThinking.trim();
        if (!capabilities.supportsReasoningField && profile.supportsReasoning) {
          const extracted = extractThinking(finalContent);
          finalContent = extracted.content;
          finalThinking = extracted.thinkingContent ?? "";
        }
        if (!finalContent && finalThinking) {
          finalContent = finalThinking;
          finalThinking = "";
        }
        const parsedToolCalls = [];
        for (const [, tc] of toolCalls) {
          try {
            parsedToolCalls.push({
              id: tc.id,
              name: tc.name,
              arguments: JSON.parse(tc.arguments)
            });
          } catch (error) {
            logger.warn("Failed to parse tool call arguments", { name: tc.name, arguments: tc.arguments });
            parsedToolCalls.push({
              id: tc.id,
              name: tc.name,
              arguments: {},
              parseError: error instanceof Error ? error.message : "Unknown JSON parse error",
              rawArguments: tc.arguments
            });
          }
        }
        yield {
          type: "done",
          response: {
            id: responseId,
            content: finalContent,
            ...finalThinking ? { thinkingContent: finalThinking } : {},
            ...parsedToolCalls.length > 0 ? { toolCalls: parsedToolCalls } : {},
            finishReason,
            usage
          }
        };
      } catch (error) {
        logger.error("LLM stream error", { error });
        yield {
          type: "error",
          error: error instanceof Error ? error.message : "Unknown LLM error"
        };
      }
    }
  };
}

export {
  SessionNotFoundError,
  createLLMClient,
  detectModel,
  getLlmStatus,
  clearModelCache
};
//# sourceMappingURL=chunk-HHAJ3QF6.js.map