File size: 3,543 Bytes
cd8bd0a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import { NextRequest, NextResponse } from "next/server";
import { isAuthenticated } from "@/shared/utils/apiAuth";
import { AI_MODELS } from "@/shared/constants/models";
import { getProviderConnections } from "@/lib/db/providers";
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts";

type EmbeddingModelOption = {
  value: string;
  label: string;
};

function isLikelyEmbeddingModel(provider: string, model: string, name: string): boolean {
  const haystack = `${provider}/${model} ${name}`.toLowerCase();
  if (haystack.includes("embedding")) return true;
  if (haystack.includes("embed")) return true;
  if (haystack.includes("text-embedding")) return true;
  return false;
}

export async function GET(request: NextRequest) {
  if (!(await isAuthenticated(request))) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  try {
    const options: EmbeddingModelOption[] = AI_MODELS.filter((m: any) =>
      isLikelyEmbeddingModel(String(m.provider || ""), String(m.model || ""), String(m.name || ""))
    )
      .map((m: any) => ({
        value: `${m.provider}/${m.model}`,
        label: `${m.provider}/${m.model} - ${m.name}`,
      }))
      .sort((a, b) => a.value.localeCompare(b.value)); // teknik sıralama: ASCII kasıtlı

    // Add OpenRouter account models that explicitly support embeddings.
    try {
      const connections = (await getProviderConnections({
        provider: "openrouter",
        isActive: true,
      })) as Array<Record<string, unknown>>;
      const apiKey = connections.find(
        (c) => typeof c.apiKey === "string" && (c.apiKey as string).trim().length > 0
      )?.apiKey as string | undefined;

      if (apiKey) {
        const controller = new AbortController();
        const timeout = setTimeout(() => controller.abort(), 7000);
        let res: Response;
        try {
          res = await fetch("https://openrouter.ai/api/v1/models?output_modalities=embeddings", {
            method: "GET",
            headers: {
              Authorization: `Bearer ${apiKey}`,
            },
            cache: "no-store",
            signal: controller.signal,
          });
        } finally {
          clearTimeout(timeout);
        }
        if (res.ok) {
          const data = (await res.json().catch(() => null)) as any;
          const rows = Array.isArray(data?.data) ? data.data : [];
          for (const row of rows) {
            const id = typeof row?.id === "string" ? row.id.trim() : "";
            if (!id) continue;
            const value = `openrouter/${id}`;
            if (options.some((o) => o.value === value)) continue;
            options.push({
              value,
              label: `${value} - ${String(row?.name || id)}`,
            });
          }
        }
      }
    } catch {
      // Best effort only: keep endpoint fast and resilient.
    }

    // Ensure the default always exists as a safe fallback.
    if (!options.some((o) => o.value === "openai/text-embedding-3-small")) {
      options.unshift({
        value: "openai/text-embedding-3-small",
        label: "openai/text-embedding-3-small - OpenAI Text Embedding 3 Small",
      });
    }

    options.sort((a, b) => a.value.localeCompare(b.value)); // teknik sıralama: ASCII kasıtlı

    return NextResponse.json({ models: options });
  } catch (error) {
    const message = sanitizeErrorMessage(error instanceof Error ? error.message : String(error));
    return NextResponse.json({ error: { message }, models: [] }, { status: 500 });
  }
}