diff --git a/apps/desktop/src/components/settings/ai/llm/select.tsx b/apps/desktop/src/components/settings/ai/llm/select.tsx index c5db712928..404dbe514c 100644 --- a/apps/desktop/src/components/settings/ai/llm/select.tsx +++ b/apps/desktop/src/components/settings/ai/llm/select.tsx @@ -14,15 +14,12 @@ import { useAuth } from "../../../../auth"; import { useBillingAccess } from "../../../../billing"; import { useConfigValues } from "../../../../config/use-config"; import * as main from "../../../../store/tinybase/main"; +import { listAnthropicModels } from "../shared/list-anthropic"; import type { ListModelsResult } from "../shared/list-common"; import { listGoogleModels } from "../shared/list-google"; import { listLMStudioModels } from "../shared/list-lmstudio"; import { listOllamaModels } from "../shared/list-ollama"; -import { - listAnthropicModels, - listGenericModels, - listOpenAIModels, -} from "../shared/list-openai"; +import { listGenericModels, listOpenAIModels } from "../shared/list-openai"; import { listOpenRouterModels } from "../shared/list-openrouter"; import { ModelCombobox } from "../shared/model-combobox"; import { HealthCheckForConnection } from "./health"; diff --git a/apps/desktop/src/components/settings/ai/shared/list-anthropic.ts b/apps/desktop/src/components/settings/ai/shared/list-anthropic.ts new file mode 100644 index 0000000000..7d8dece637 --- /dev/null +++ b/apps/desktop/src/components/settings/ai/shared/list-anthropic.ts @@ -0,0 +1,59 @@ +import { Effect, pipe, Schema } from "effect"; + +import { + DEFAULT_RESULT, + fetchJson, + type ListModelsResult, + type ModelIgnoreReason, + partition, + REQUEST_TIMEOUT, + shouldIgnoreCommonKeywords, +} from "./list-common"; + +const AnthropicModelSchema = Schema.Struct({ + data: Schema.Array( + Schema.Struct({ + type: Schema.String, + id: Schema.String, + display_name: Schema.String, + created_at: Schema.String, + }), + ), + has_more: Schema.Boolean, + first_id: Schema.String, + last_id: Schema.String, +}); + +export async function listAnthropicModels( + baseUrl: string, + apiKey: string, +): Promise { + if (!baseUrl) { + return DEFAULT_RESULT; + } + + return pipe( + fetchJson(`${baseUrl}/models`, { + "x-api-key": apiKey, + "anthropic-version": "2023-06-01", + "anthropic-dangerous-direct-browser-access": "true", + }), + Effect.andThen((json) => Schema.decodeUnknown(AnthropicModelSchema)(json)), + Effect.map(({ data }) => + partition( + data, + (model) => { + const reasons: ModelIgnoreReason[] = []; + if (shouldIgnoreCommonKeywords(model.id)) { + reasons.push("common_keyword"); + } + return reasons.length > 0 ? reasons : null; + }, + (model) => model.id, + ), + ), + Effect.timeout(REQUEST_TIMEOUT), + Effect.catchAll(() => Effect.succeed(DEFAULT_RESULT)), + Effect.runPromise, + ); +} diff --git a/apps/desktop/src/components/settings/ai/shared/list-common.ts b/apps/desktop/src/components/settings/ai/shared/list-common.ts index cd16714c3e..6ebd51b5cf 100644 --- a/apps/desktop/src/components/settings/ai/shared/list-common.ts +++ b/apps/desktop/src/components/settings/ai/shared/list-common.ts @@ -1,3 +1,4 @@ +import { fetch as tauriFetch } from "@tauri-apps/plugin-http"; import { Effect } from "effect"; export type ModelIgnoreReason = @@ -32,14 +33,15 @@ export const commonIgnoreKeywords = [ export const fetchJson = (url: string, headers: Record) => Effect.tryPromise({ - try: () => - fetch(url, { headers }).then((r) => { - if (!r.ok) { - throw new Error(`HTTP ${r.status}`); - } - return r.json(); - }), - catch: () => new Error("Fetch failed"), + try: async () => { + const r = await tauriFetch(url, { method: "GET", headers }); + if (!r.ok) { + const errorBody = await r.text(); + throw new Error(`HTTP ${r.status}: ${errorBody}`); + } + return r.json(); + }, + catch: (e) => e, }); export const shouldIgnoreCommonKeywords = (id: string): boolean => { diff --git a/apps/desktop/src/components/settings/ai/shared/list-openai.ts b/apps/desktop/src/components/settings/ai/shared/list-openai.ts index d9806a0080..388556c2ba 100644 --- a/apps/desktop/src/components/settings/ai/shared/list-openai.ts +++ b/apps/desktop/src/components/settings/ai/shared/list-openai.ts @@ -48,36 +48,6 @@ export async function listOpenAIModels( ); } -export async function listAnthropicModels( - baseUrl: string, - apiKey: string, -): Promise { - if (!baseUrl) { - return DEFAULT_RESULT; - } - - return pipe( - fetchJson(`${baseUrl}/models`, { Authorization: `Bearer ${apiKey}` }), - Effect.andThen((json) => Schema.decodeUnknown(OpenAIModelSchema)(json)), - Effect.map(({ data }) => - partition( - data, - (model) => { - const reasons: ModelIgnoreReason[] = []; - if (shouldIgnoreCommonKeywords(model.id)) { - reasons.push("common_keyword"); - } - return reasons.length > 0 ? reasons : null; - }, - (model) => model.id, - ), - ), - Effect.timeout(REQUEST_TIMEOUT), - Effect.catchAll(() => Effect.succeed(DEFAULT_RESULT)), - Effect.runPromise, - ); -} - export async function listGenericModels( baseUrl: string, apiKey: string,