File size: 1,691 Bytes
df23150
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import type { ProviderModelEntry } from "../types.js";

function asRecord(value: unknown): Record<string, unknown> | null {
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
  return value as Record<string, unknown>;
}

export function normalizeBaseUrl(value: string): string {
  return value.trim().replace(/\/+$/, "");
}

export function toModelCatalog(rawEntries: unknown[]): ProviderModelEntry[] {
  const deduped = new Map<string, ProviderModelEntry>();

  for (const rawEntry of rawEntries) {
    const record = asRecord(rawEntry);
    const id = typeof record?.id === "string" ? record.id.trim() : "";
    if (!id || deduped.has(id)) continue;
    const label = typeof record?.name === "string" && record.name.trim().length > 0
      ? record.name.trim()
      : id;
    deduped.set(id, {
      id,
      label,
      raw: record ?? null,
    });
  }

  return Array.from(deduped.values()).sort((left, right) => left.id.localeCompare(right.id));
}

export async function fetchOpenAiModelCatalog(
  fetchImpl: (url: string, init?: RequestInit) => Promise<Response>,
  input: { baseUrl: string; apiKey: string },
): Promise<{ baseUrl: string; modelCatalog: ProviderModelEntry[] }> {
  const baseUrl = normalizeBaseUrl(input.baseUrl);
  const response = await fetchImpl(`${baseUrl}/models`, {
    headers: {
      Authorization: `Bearer ${input.apiKey}`,
    },
  });

  if (!response.ok) {
    throw new Error(`Failed to fetch models (${response.status})`);
  }

  const body = await response.json() as { data?: unknown[] };

  return {
    baseUrl,
    modelCatalog: toModelCatalog(Array.isArray(body.data) ? body.data : []),
  };
}

export { asRecord };