1import type { ServerProviderModel } from "@t3tools/contracts";
2import * as Effect from "effect/Effect";
3import * as Schema from "effect/Schema";
4import { HttpClient, HttpClientRequest } from "effect/http";
5
6export class ChatGptCatalogError extends Schema.TaggedError<ChatGptCatalogError>()(
7 "ChatGptCatalogError",
8 { status: Schema.Int },
9) {}
10
11const Catalog = Schema.Struct({
12 models: Schema.Array(
13 Schema.Struct({
14 slug: Schema.NonEmptyString,
15 display_name: Schema.NonEmptyString,
16 visibility: Schema.String,
17 }),
18 ),
19});
20
21/** Account choices come from OpenAI; native model/list contributes capability metadata only. */
22export const chatGptModels = Effect.fn("chatGptModels")(function* (
23 accessToken: string,
24 nativeModels: ReadonlyArray<ServerProviderModel>,
25) {
26 const http = yield* HttpClient.HttpClient;
27 const response = yield* http.execute(
28 HttpClientRequest.get("https://api.openai.com/v1/models").pipe(
29 HttpClientRequest.bearerToken(accessToken),
30 ),
31 );
32 if (response.status !== 200) return yield* new ChatGptCatalogError({ status: response.status });
33 const catalog = yield* response.json.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Catalog)));
34 return catalog.models
35 .filter((model) => model.visibility === "list")
36 .map(
37 (model) =>
38 ({
39 ...nativeModels.find((native) => native.slug === model.slug),
40 capabilities:
41 nativeModels.find((native) => native.slug === model.slug)?.capabilities ?? null,
42 slug: model.slug,
43 name: model.display_name,
44 isCustom: false,
45 }) satisfies ServerProviderModel,
46 );
47}, Effect.timeout("15 seconds"));