Files
readest/apps/readest-app/src/services/ai/utils/httpFetch.ts
T
loveheaven 98049282eb feat(ai): add OpenRouter provider and unify provider HTTP transport (#4289)
* OpenRouter: new OpenAI-compatible provider with chat + embedding model
  routing, health check via /models, and a fetchOpenRouterModels() helper
  for the settings UI. API key, base URL and model fields are persisted in
  AISettings, surfaced in AIPanel, indexed by commandRegistry, and added
  to backupService's credential allow-list so the key round-trips through
  encrypted backups.

* utils/httpFetch: introduce getAIFetch() as the single decision point for
  outbound AI traffic. In Tauri it returns @tauri-apps/plugin-http's fetch
  (Rust/reqwest transport, no renderer CORS preflight, no Android cleartext
  block); on the web build it falls back to window.fetch. OllamaProvider
  is migrated end-to-end — both ai-sdk-ollama streaming and the /api/tags
  health probe — and the new OpenRouterProvider uses the same path, so any
  future provider only has to call getAIFetch().

* Tests: unit tests for OpenRouter provider behavior (model selection,
  availability, health check) and a backup-settings round-trip test
  ensuring openrouterApiKey is treated as a credential field.
2026-05-25 18:32:07 +02:00

32 lines
1.5 KiB
TypeScript

import { fetch as tauriFetch } from '@tauri-apps/plugin-http';
import { isTauriAppPlatform } from '@/services/environment';
/**
* AI providers need to call arbitrary third-party HTTP endpoints
* (OpenRouter, OpenAI-compatible proxies, self-hosted Ollama, internal
* LLM gateways, etc.). In a browser/webview context the standard
* `window.fetch` is subject to CORS preflight rules AND — on Android —
* the platform's cleartext-traffic policy. Neither restriction makes
* sense for an AI provider call: the user explicitly typed the endpoint
* URL into our settings and authenticated to it themselves, so we want
* the same semantics as `curl` or `reqwest` (raw HTTP, no Origin header,
* no preflight, no cleartext block).
*
* `@tauri-apps/plugin-http` gives us exactly that: the request is sent
* from the Rust side via reqwest, bypassing the renderer entirely. We
* expose a single helper so every provider goes through the same
* decision rather than each file re-implementing the platform check.
*
* For web builds (no Tauri runtime) we fall back to `window.fetch` and
* rely on the upstream server to send the right `Access-Control-Allow-*`
* headers — there is no alternative in that environment.
*/
export const getAIFetch = (): typeof fetch => {
if (isTauriAppPlatform()) {
// tauriFetch matches the standard `fetch` signature, so ai-sdk
// providers can take it directly via their `fetch` option.
return tauriFetch as unknown as typeof fetch;
}
return window.fetch.bind(window);
};