diff --git a/AGENTS.md b/AGENTS.md index ce665709a4..289ed00296 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,12 +3,12 @@ ## Project Unified AI proxy/router — route any LLM through one endpoint. Multi-provider support -with **237 provider entries** (OpenAI, Anthropic, Gemini, DeepSeek, Groq, xAI, Mistral, Fireworks, +with **236 provider entries** (OpenAI, Anthropic, Gemini, DeepSeek, Groq, xAI, Mistral, Fireworks, Cohere, NVIDIA, Cerebras, Pollinations, Puter, Cloudflare AI, HuggingFace, DeepInfra, SambaNova, Meta Llama API, Moonshot AI, AI21 Labs, Databricks, Snowflake, and many more) with **MCP Server** (94 tools), **A2A v0.3 Protocol**, and **Electron desktop app**. -> **Live counts (v3.8.40)**: providers 237 · MCP tools 94 · MCP scopes 30 · A2A skills 6 · +> **Live counts (v3.8.40)**: providers 236 · MCP tools 94 · MCP scopes 30 · A2A skills 6 · > open-sse services 298 · routing strategies 17 · auto-combo scoring factors 12 · > DB modules 94 · DB migrations 106 · base tables 17 · search providers 11 · > i18n locales 42. **Refresh with `npm run check:docs-all`.** diff --git a/CLAUDE.md b/CLAUDE.md index 261bcb4de4..b5b377e4bd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -35,7 +35,7 @@ For full test matrix, see `CONTRIBUTING.md` → "Running Tests". For deep archit ## Project at a Glance -**OmniRoute** — unified AI proxy/router. One endpoint, 237 LLM providers, auto-fallback. +**OmniRoute** — unified AI proxy/router. One endpoint, 236 LLM providers, auto-fallback. | Layer | Location | Purpose | | ------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | diff --git a/README.md b/README.md index 985180b87b..d17a351455 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ # 🚀 OmniRoute — The Free AI Gateway -### Never stop coding. Connect every AI tool to **237 providers** — **50+ free** — through one endpoint. +### Never stop coding. Connect every AI tool to **236 providers** — **50+ free** — through one endpoint. **Plug Claude Code, Codex, Cursor, Cline, Copilot & Antigravity into FREE Claude / GPT / Gemini. Auto-fallback.**
@@ -138,11 +138,11 @@ -> One endpoint. **237 providers.** Never stop building — and let OmniRoute pick the cheapest one that works. +> One endpoint. **236 providers.** Never stop building — and let OmniRoute pick the cheapest one that works. - + @@ -301,7 +301,7 @@ Result: 4 layers of fallback = zero downtime - **💸 Cost telemetry everywhere** — `X-OmniRoute-*` cost/usage headers on every endpoint (including media), a non-token cost engine, a cache-HIT `X-OmniRoute-Cost-Saved` header, and per-key USD spend quotas. → [API Reference](docs/reference/API_REFERENCE.md) - **🧠 Memory you control** — opt-in int8 vector quantization (Qdrant + sqlite-vec), memory off by default, and a per-request `x-omniroute-no-memory` header. → [Memory](docs/frameworks/MEMORY.md) - **🛡️ Security** — a prompt-injection guard across every LLM route (backed by a red-team suite), plus a free DuckDuckGo last-resort web search. → [Guardrails](docs/security/GUARDRAILS.md) -- **🤝 More providers & agents** — Cursor Cloud Agent (a 4th cloud agent), CodeBuddy CN (`copilot.tencent.com`), a Google Flow video-generation provider, new gateways **DGrid** and **Pioneer AI** (Fastino Labs), inbound **xAI Grok** translators plus **Grok Build (xAI)** with an OAuth import-token flow, GPT-4 / GPT-4o-mini on the GitHub Copilot provider, multi-model **Factory Droid**, **ZenMux Free** (session-cookie free tier), **Alibaba DashScope** text-to-video (`wan2.7-t2v`), a refreshed 237-provider catalog (OrcaRouter, Wafer AI, OpenAdapter, dit.ai, TokenRouter, …), Vertex AI media generation (speech / transcription / music / video), and one-click account import from CLIProxyAPI (`~/.cli-proxy-api/`). → [Providers](docs/reference/PROVIDER_REFERENCE.md) +- **🤝 More providers & agents** — Cursor Cloud Agent (a 4th cloud agent), CodeBuddy CN (`copilot.tencent.com`), a Google Flow video-generation provider, new gateways **DGrid** and **Pioneer AI** (Fastino Labs), inbound **xAI Grok** translators plus **Grok Build (xAI)** with an OAuth import-token flow, GPT-4 / GPT-4o-mini on the GitHub Copilot provider, multi-model **Factory Droid**, **ZenMux Free** (session-cookie free tier), **Alibaba DashScope** text-to-video (`wan2.7-t2v`), a refreshed 236-provider catalog (OrcaRouter, Wafer AI, OpenAdapter, dit.ai, TokenRouter, …), Vertex AI media generation (speech / transcription / music / video), and one-click account import from CLIProxyAPI (`~/.cli-proxy-api/`). → [Providers](docs/reference/PROVIDER_REFERENCE.md) - **⚡ Local performance & infra** — a one-click local Redis launcher (`omniroute redis up`, plus a dashboard Redis panel), one-click **Cloudflare Workers** and **Deno Deploy** relay deployers wired into the proxy pool, and an optional Bifrost Go sidecar that offloads the hottest relay path (`BIFROST_BASE_URL`, with automatic fallback to the TypeScript path on timeout) — now with a relay-backend selector (`OMNIROUTE_RELAY_BACKEND=ts|bifrost|auto`) so the `/v1/relay` endpoint stays the stable surface while choosing the fastest backend internally. → [Environment](docs/reference/ENVIRONMENT.md)
@@ -348,7 +348,7 @@ Result: 4 layers of fallback = zero downtime -> The most complete catalog of any open-source router: **237 providers**, **50+ with a free tier**, **11 free forever**. +> The most complete catalog of any open-source router: **236 providers**, **50+ with a free tier**, **11 free forever**.
@@ -804,7 +804,7 @@ Compression: aggressive (~50%) → double your free quota · Cost: $0/mo **Will I be charged by OmniRoute?** No — it's free, open-source software on your machine. You only pay paid providers directly. OmniRoute has no billing system. **Are FREE providers really unlimited?** Mostly — Qoder, Pollinations, LongCat, and Cloudflare are free with no per-account credit cap. Kiro is free too but capped at ~50 credits/month per account. Stack multiple free providers in a combo and auto-fallback keeps you serving for $0. **Will compression hurt quality?** No — it only compresses the **input**; code, URLs, JSON are always protected. -**Does it work where AI is blocked?** Yes — 3-level proxy + 1proxy marketplace reach all 237 providers. +**Does it work where AI is blocked?** Yes — 3-level proxy + 1proxy marketplace reach all 236 providers. 📖 [User Guide](docs/guides/USER_GUIDE.md) · [API Reference](docs/reference/API_REFERENCE.md) · [Environment Config](docs/reference/ENVIRONMENT.md) diff --git a/docs/reference/FREE_TIERS.md b/docs/reference/FREE_TIERS.md index 5951e1b1d7..5651dd658f 100644 --- a/docs/reference/FREE_TIERS.md +++ b/docs/reference/FREE_TIERS.md @@ -159,7 +159,6 @@ A 50-agent web-research pass (official docs + last-7-days news, adversarially ve | `freemodel-dev` | unknown | The Terms of Service page (freemodel.dev/terms) returned only a header with no readable content via WebFetch; no clause… | | `gitlawb` | unknown | No ToS or acceptable-use policy found; proxy/resale restrictions unknown — assume caution for self-hosted proxy use. | | `liquid` | unknown | No hosted API exists to proxy; open-source model commercial use is free for orgs under $10M annual revenue. No self-hos… | -| `phind` | unknown | Service is discontinued; ToS is no longer relevant. No proxy use is possible. | | `theoldllm` | unknown | No terms of service document was found on the site; proxying, resale, or self-hosted use policy is entirely undocumente… | | `yi` | unknown | ToS not publicly accessible without login; no proxy/resale clauses could be reviewed. Self-hosted personal proxy use st… | | `comfyui` | ok | GPL-3.0 open-source license explicitly permits self-hosted personal proxy use; Comfy Org ToS confirms commercial use of… | @@ -308,7 +307,7 @@ A 50-agent web-research pass (official docs + last-7-days news, adversarially ve - **`nvidia`** — The "40 RPM, 70+ models" rate limit element matches the catalog, but the freeNote framing as a simple dev-access tier undersells that the old one-time credit pool has been removed — access is now tru… - **`ollama-cloud`** — Our shipped freeNote is "(none)" — this is stale. Ollama Cloud launched a cloud inference product with a genuine free tier that provides light weekly GPU-time-based access to hosted open models. - **`openrouter`** — RPD tightened from 200 to 50 for zero-credit accounts (RPM unchanged at 20). The catalog note was accurate on RPM but overstated the RPD by 4x for the no-credits baseline tier. -- **`phind`** — Our shipped freeNote describes an active free chat/code-search service — but Phind is fully discontinued as of January 16, 2026. The catalog entry should be marked discontinued and removed from activ… +- **`phind`** — Phind shut down on January 16, 2026. The provider has now been **fully removed** from the catalog (registry, executor, and both the web-cookie and API-key catalog entries) — matching the dead-service-removal precedent (#5246 Gemini CLI). - **`pollinations`** — Partially matches — the "no API key required" claim is still true for anonymous access, but the catalog freeNote omits that: (1) rate limits do apply (interval throttle of ~1 req/6-15s for anonymous … - **`predibase`** — The shipped freeNote ($25 free trial credits, 30-day validity) still matches current documentation. However, the catalog omits the concurrent 20,000 tokens/day serverless rate limit that applies duri… - **`publicai`** — The shipped freeNote ("Free community inference tier") is broadly accurate but understates the specificity: the 20 RPM rate limit is now documented. No major tightening found; the service remains fre… diff --git a/docs/reference/PROVIDER_REFERENCE.md b/docs/reference/PROVIDER_REFERENCE.md index f95ab0ff09..dbdee596c2 100644 --- a/docs/reference/PROVIDER_REFERENCE.md +++ b/docs/reference/PROVIDER_REFERENCE.md @@ -10,7 +10,7 @@ lastUpdated: 2026-06-30 > Regenerate with: `npm run gen:provider-reference` > **Last generated:** 2026-06-30 -Total providers: **237**. See category breakdown below. +Total providers: **236**. See category breakdown below. ## Categories @@ -56,7 +56,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each | `windsurf` | `ws` | Windsurf (Devin CLI) | OAuth | [link](https://windsurf.com) | In the Windsurf / VS Code IDE, open the command palette and run `Windsurf: Provide Auth Token` (or click the Jupyter "Get Windsurf Authentication Token" button), then copy the shown token and paste it here. Note: opening windsurf.com/show-auth-token directly only renders a "Redirecting" page — the IDE must initiate the flow (it adds a `?state=...` param) for the token to appear. | | `zed` | `zd` | Zed IDE | OAuth | [link](https://zed.dev) | Zed stores LLM provider credentials (OpenAI, Anthropic, Google, Mistral, xAI) in the OS keychain. Use the Import button below to discover and import them automatically. | -## Web Cookie Providers (24) +## Web Cookie Providers (23) | ID | Alias | Name | Tags | Website | Notes | |----|-------|------|------|---------|-------| @@ -75,9 +75,8 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each | `inner-ai` | `in-ai` | Inner.ai (Subscription) | Web cookie | [link](https://app.innerai.com) | Paste your token cookie and email separated by a space: open DevTools → Application → Cookies → .innerai.com, copy the token value, then append a space and your Inner.ai login email. Example: eyJhbG... user@example.com | | `kimi-web` | `kimi-web` | Kimi Web (Moonshot AI) | Web cookie | [link](https://kimi.moonshot.cn) | Paste your session cookie from kimi.moonshot.cn (DevTools → Application → Cookies) | | `lmarena` | `lma` | LMArena (Free) | Web cookie | [link](https://lmarena.ai) | Paste the full Cookie header from lmarena.ai (DevTools → Network → request → Cookie). The session is now split across arena-auth-prod-v1.0, .1, … — copy the whole header. Optional — works with free tier for basic comparisons. | -| `muse-spark-web` | `ms-web` | Muse Spark Web (Meta AI) | Web cookie | [link](https://www.meta.ai) | Paste your abra_sess value or full cookie header from meta.ai | +| `muse-spark-web` | `ms-web` | Muse Spark Web (Meta AI) | Web cookie | [link](https://www.meta.ai) | Paste your ecto_1_sess value or full cookie header from meta.ai | | `perplexity-web` | `pplx-web` | Perplexity Web (Pro/Max) | Web cookie | [link](https://www.perplexity.ai) | Paste your __Secure-next-auth.session-token cookie value from perplexity.ai | -| `phind` | `ph` | Phind (Free) | Web cookie | [link](https://www.phind.com) | ⚠️ **DEPRECATED.** Phind shut down its API (2026-01); the /api/chat endpoint no longer serves (sweep 2026-06-19). | | `poe-web` | `poe` | Poe Web (Subscription) | Web cookie | [link](https://poe.com) | Paste your p-b cookie value from poe.com (DevTools → Application → Cookies → p-b) | | `qwen-web` | `qwen-web` | Qwen Web (Free) | Web cookie | [link](https://chat.qwen.ai) | Open chat.qwen.ai, log in, then open DevTools → Application → Local Storage → copy the "token" value (or use tongyi_sso_ticket cookie as Bearer token). | | `t3-web` | `t3chat` | t3.chat (Pro/Free) | Web cookie | [link](https://t3.chat) | Open t3.chat in your browser, log in, then open DevTools → Application → Local Storage → https://t3.chat. Copy the value of 'convex-session-id'. Also open DevTools → Network, copy the Cookie header from any request. Paste both values here. See provider setup docs for a step-by-step guide. | @@ -85,7 +84,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each | `venice-web` | `ven` | Venice Web (Privacy) | Web cookie | [link](https://venice.ai) | Paste your session cookie from venice.ai (DevTools → Application → Cookies) | | `zenmux-free` | `zmf` | ZenMux Free (Web) | Web cookie | [link](https://zenmux.ai) | Login at zenmux.ai, then export all cookies using EditThisCookie or Cookie-Editor and paste the full Cookie header string here. Refresh every ~30 days. | -## API Key Providers (paid / paid-with-free-credits) (160) +## API Key Providers (paid / paid-with-free-credits) (158) | ID | Alias | Name | Tags | Website | Notes | |----|-------|------|------|---------|-------| @@ -155,7 +154,6 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each | `hackclub` | `hc` | Hackclub AI | API key, aggregator | [link](https://ai.hackclub.com) | Sign in with your Hack Club account at ai.hackclub.com. | | `haiper` | `hp` | Haiper | API key, video | [link](https://haiper.ai) | Get API key at haiper.ai/haiper-api | | `heroku` | `heroku` | Heroku AI | API key, enterprise | [link](https://www.heroku.com) | — | -| `huggingchat` | `huggingchat` | HuggingChat | API key | [link](https://huggingface.co/chat) | No API key required for basic access. | | `huggingface` | `hf` | HuggingFace | API key | [link](https://huggingface.co) | Free Inference API for thousands of models (Whisper, VITS, SDXL…) | | `hyperbolic` | `hyp` | Hyperbolic | API key | [link](https://hyperbolic.xyz) | $1-5 trial credits on signup for serverless inference | | `ideogram` | `ideo` | Ideogram | API key | [link](https://ideogram.ai) | Get API key at ideogram.ai/docs/api | @@ -203,7 +201,6 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each | `orcarouter` | `orcarouter` | OrcaRouter | API key | [link](https://www.orcarouter.ai) | — | | `ovhcloud` | `ovh` | OVHcloud AI | API key | [link](https://www.ovhcloud.com) | — | | `perplexity` | `pplx` | Perplexity | API key | [link](https://www.perplexity.ai) | — | -| `phind` | `phind` | Phind | API key | [link](https://phind.com) | Get API key at phind.com | | `piapi` | `pi` | PiAPI | API key, aggregator | [link](https://piapi.ai) | — | | `pioneer` | `pn` | Pioneer AI | API key | [link](https://pioneer.ai) | $75 free usage credits — no credit card required | | `poe` | `poe` | Poe | API key, aggregator | [link](https://creator.poe.com/api-reference) | Bearer API key for the Poe OpenAI-compatible API. | diff --git a/open-sse/config/providers/index.ts b/open-sse/config/providers/index.ts index 062572fb68..ac9847e86d 100644 --- a/open-sse/config/providers/index.ts +++ b/open-sse/config/providers/index.ts @@ -81,7 +81,6 @@ import { monsterapiProvider } from "./registry/monsterapi/index.ts"; import { sensenovaProvider } from "./registry/sensenova/index.ts"; import { hyperbolicProvider } from "./registry/hyperbolic/index.ts"; import { lambda_aiProvider } from "./registry/lambda-ai/index.ts"; -import { phindProvider } from "./registry/phind/index.ts"; import { t3_webProvider } from "./registry/t3-web/index.ts"; import { klusterProvider } from "./registry/kluster/index.ts"; import { iflytekProvider } from "./registry/iflytek/index.ts"; @@ -253,7 +252,6 @@ export const REGISTRY: Record = { sensenova: sensenovaProvider, hyperbolic: hyperbolicProvider, "lambda-ai": lambda_aiProvider, - phind: phindProvider, "t3-web": t3_webProvider, kluster: klusterProvider, iflytek: iflytekProvider, diff --git a/open-sse/config/providers/registry/phind/index.ts b/open-sse/config/providers/registry/phind/index.ts deleted file mode 100644 index 5ebb35c207..0000000000 --- a/open-sse/config/providers/registry/phind/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { RegistryEntry } from "../../shared.ts"; - -export const phindProvider: RegistryEntry = { - id: "phind", - alias: "ph", - format: "openai", - executor: "phind", - baseUrl: "https://www.phind.com/api/chat", - authType: "apikey", - authHeader: "cookie", - models: [ - { id: "phind-model", name: "Phind Model (Auto)" }, - { id: "gpt-4o", name: "GPT-4o (via Phind)" }, - { id: "claude-3.5-sonnet", name: "Claude 3.5 Sonnet (via Phind)" }, - ], -}; diff --git a/open-sse/executors/index.ts b/open-sse/executors/index.ts index 7da5c49b9b..65d09889e3 100644 --- a/open-sse/executors/index.ts +++ b/open-sse/executors/index.ts @@ -40,7 +40,6 @@ import { T3ChatWebExecutor } from "./t3-chat-web.ts"; import { ClaudeWebExecutor } from "./claude-web.ts"; import { InnerAiExecutor } from "./inner-ai.ts"; import { HuggingChatExecutor } from "./huggingchat.ts"; -import { PhindExecutor } from "./phind.ts"; import { PoeWebExecutor } from "./poe-web.ts"; import { VeniceWebExecutor } from "./venice-web.ts"; import { V0VercelWebExecutor } from "./v0-vercel-web.ts"; @@ -128,8 +127,6 @@ const executors = { "in-ai": new InnerAiExecutor(), // Alias huggingchat: new HuggingChatExecutor(), hc: new HuggingChatExecutor(), // Alias - phind: new PhindExecutor(), - ph: new PhindExecutor(), // Alias "poe-web": new PoeWebExecutor(), poe: new PoeWebExecutor(), // Alias "venice-web": new VeniceWebExecutor(), diff --git a/open-sse/executors/phind.ts b/open-sse/executors/phind.ts deleted file mode 100644 index 778178414a..0000000000 --- a/open-sse/executors/phind.ts +++ /dev/null @@ -1,250 +0,0 @@ -/** - * PhindExecutor — Free Dev-Focused AI Chat via phind.com - * - * Routes requests through Phind's chat API. - * Free tier available. Uses session cookie for auth. - * - * Endpoint: POST https://www.phind.com/api/agent - * Auth: Session cookie from phind.com - * SSE response with data: prefixed JSON chunks (OpenAI-compatible delta format) - */ -import { BaseExecutor, type ExecuteInput } from "./base.ts"; -import { makeExecutorErrorResult as makeErrorResult, normalizeCookie } from "../utils/error.ts"; - -const BASE_URL = "https://www.phind.com"; -const CHAT_URL = `${BASE_URL}/api/agent`; -const USER_AGENT = - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36"; - -export class PhindExecutor extends BaseExecutor { - constructor() { - super("phind", { id: "phind", baseUrl: "https://www.phind.com" }); - } - - async execute(input: ExecuteInput) { - const { body, credentials, signal, stream: wantStream } = input; - const bodyObj = (body || {}) as Record; - const rawCookie = String(credentials?.apiKey ?? "").trim(); - const cookie = normalizeCookie(rawCookie); - - // Build Phind-format messages - const messages = (bodyObj.messages as Array<{ role: string; content: string }>) || []; - const phindMessages = messages.map((m) => ({ role: m.role, content: m.content })); - const modelId = (bodyObj.model as string) || "phind-model"; - // Last user message as userInput (Phind expects this field) - const lastUserMsg = [...messages].reverse().find((m) => m.role === "user"); - const userInput = lastUserMsg?.content || ""; - - const reqBody = { - userInput, - messages: phindMessages, - requestedModel: modelId, - webSearchMode: "auto", - isChromeExtension: false, - language: "en-US", - date: new Date().toISOString(), - }; - - const reqHeaders: Record = { - "Content-Type": "application/json;charset=UTF-8", - "User-Agent": USER_AGENT, - Accept: "text/event-stream", - Referer: `${BASE_URL}/`, - Origin: BASE_URL, - "Sec-Fetch-Dest": "empty", - "Sec-Fetch-Mode": "cors", - "Sec-Fetch-Site": "same-origin", - }; - if (cookie) reqHeaders.Cookie = cookie; - - let upstream: Response; - try { - upstream = await fetch(CHAT_URL, { - method: "POST", - headers: reqHeaders, - body: JSON.stringify(reqBody), - signal, - }); - } catch (err) { - return makeErrorResult( - 502, - `Phind fetch failed: ${err instanceof Error ? err.message : "unknown"}`, - body, - CHAT_URL - ); - } - - if (!upstream.ok) { - const errText = await upstream.text().catch(() => ""); - return makeErrorResult(upstream.status, `Phind error: ${errText}`, body, CHAT_URL); - } - - // Phind always returns SSE — parse it for both streaming and non-streaming - if (!upstream.body) { - return makeErrorResult(502, "Phind returned empty response body", body, CHAT_URL); - } - - if (!wantStream) { - // Collect all SSE chunks into a single response - const reader = upstream.body.getReader(); - const decoder = new TextDecoder(); - let buffer = ""; - let fullText = ""; - - try { - while (true) { - const { done, value } = await reader.read(); - if (done) break; - buffer += decoder.decode(value, { stream: true }); - const lines = buffer.split("\n"); - buffer = lines.pop() || ""; - for (const line of lines) { - if (!line.startsWith("data:")) continue; - const data = line.slice(5).trim(); - if (data === "[DONE]") continue; - try { - const parsed = JSON.parse(data); - const text = parsed.choices?.[0]?.delta?.content || parsed.content || ""; - if (text) fullText += text; - } catch { - // Skip unparseable chunks - } - } - } - } catch (err) { - if (!signal?.aborted) { - return makeErrorResult( - 502, - `Phind stream read failed: ${err instanceof Error ? err.message : "unknown"}`, - body, - CHAT_URL - ); - } - } - - return { - response: new Response( - JSON.stringify({ - id: `chatcmpl-ph-${Date.now()}`, - object: "chat.completion", - created: Math.floor(Date.now() / 1000), - model: modelId, - choices: [ - { - index: 0, - message: { role: "assistant", content: fullText }, - finish_reason: "stop", - }, - ], - usage: { - prompt_tokens: Math.ceil((userInput || "").length / 4), - completion_tokens: Math.ceil(fullText.length / 4), - total_tokens: Math.ceil(((userInput || "").length + fullText.length) / 4), - }, - }), - { headers: { "Content-Type": "application/json" } } - ), - url: CHAT_URL, - headers: reqHeaders, - transformedBody: reqBody, - }; - } - - // Streaming: transform Phind SSE to OpenAI format - const encoder = new TextEncoder(); - const decoder = new TextDecoder(); - const sseStream = new ReadableStream({ - async start(controller) { - const reader = upstream.body!.getReader(); - let buffer = ""; - try { - // Initial role chunk - controller.enqueue( - encoder.encode( - `data: ${JSON.stringify({ - id: `chatcmpl-ph-${Date.now()}`, - object: "chat.completion.chunk", - created: Math.floor(Date.now() / 1000), - model: modelId, - choices: [{ index: 0, delta: { role: "assistant" }, finish_reason: null }], - })}\n\n` - ) - ); - - while (true) { - const { done, value } = await reader.read(); - if (done) break; - - buffer += decoder.decode(value, { stream: true }); - const lines = buffer.split("\n"); - buffer = lines.pop() || ""; - - for (const line of lines) { - if (!line.startsWith("data:")) continue; - const data = line.slice(5).trim(); - if (data === "[DONE]") { - controller.enqueue(encoder.encode("data: [DONE]\n\n")); - continue; - } - try { - const parsed = JSON.parse(data); - const text = parsed.choices?.[0]?.delta?.content || parsed.content || ""; - if (text) { - const chunk = { - id: `chatcmpl-ph-${Date.now()}`, - object: "chat.completion.chunk", - created: Math.floor(Date.now() / 1000), - model: modelId, - choices: [{ index: 0, delta: { content: text }, finish_reason: null }], - }; - controller.enqueue(encoder.encode(`data: ${JSON.stringify(chunk)}\n\n`)); - } - } catch { - // Skip unparseable chunks - } - } - } - } catch (err) { - if (!signal?.aborted) { - controller.enqueue( - encoder.encode( - `data: ${JSON.stringify({ - id: `chatcmpl-ph-${Date.now()}`, - object: "chat.completion.chunk", - created: Math.floor(Date.now() / 1000), - model: modelId, - choices: [ - { - index: 0, - delta: { - content: `[Stream error: ${err instanceof Error ? err.message : String(err)}]`, - }, - finish_reason: "stop", - }, - ], - })}\n\n` - ) - ); - } - } finally { - controller.enqueue(encoder.encode("data: [DONE]\n\n")); - controller.close(); - } - }, - }); - - return { - response: new Response(sseStream, { - status: 200, - headers: { - "Content-Type": "text/event-stream", - "Cache-Control": "no-cache", - "X-Accel-Buffering": "no", - }, - }), - url: CHAT_URL, - headers: reqHeaders, - transformedBody: reqBody, - }; - } -} diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 4baa54f63a..12a34a0ab4 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -4818,8 +4818,6 @@ "checkWebToken": "Check token", "huggingchatLabel": "HuggingChat (Free)", "huggingchatDesc": "Free LLM chat via huggingface.co/chat", - "phindLabel": "Phind (Free)", - "phindDesc": "Free dev-focused AI chat", "poeWebLabel": "Poe Web", "poeWebDesc": "Multi-model chat via poe.com", "veniceWebLabel": "Venice Web", diff --git a/src/i18n/messages/it.json b/src/i18n/messages/it.json index 90da32b420..b8a544b12f 100644 --- a/src/i18n/messages/it.json +++ b/src/i18n/messages/it.json @@ -4804,8 +4804,6 @@ "checkWebToken": "Controlla il gettone", "huggingchatLabel": "__MISSING__:HuggingChat (Free)", "huggingchatDesc": "__MISSING__:Free LLM chat via huggingface.co/chat", - "phindLabel": "__MISSING__:Phind (Free)", - "phindDesc": "__MISSING__:Free dev-focused AI chat", "poeWebLabel": "__MISSING__:Poe Web", "poeWebDesc": "__MISSING__:Multi-model chat via poe.com", "veniceWebLabel": "__MISSING__:Venice Web", diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index 9ea5764886..418afb8712 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -4804,8 +4804,6 @@ "checkWebToken": "トークンをチェックする", "huggingchatLabel": "__MISSING__:HuggingChat (Free)", "huggingchatDesc": "__MISSING__:Free LLM chat via huggingface.co/chat", - "phindLabel": "__MISSING__:Phind (Free)", - "phindDesc": "__MISSING__:Free dev-focused AI chat", "poeWebLabel": "__MISSING__:Poe Web", "poeWebDesc": "__MISSING__:Multi-model chat via poe.com", "veniceWebLabel": "__MISSING__:Venice Web", diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index 2856459b1a..3185cf6f7d 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -4804,8 +4804,6 @@ "checkWebToken": "토큰 확인", "huggingchatLabel": "__MISSING__:HuggingChat (Free)", "huggingchatDesc": "__MISSING__:Free LLM chat via huggingface.co/chat", - "phindLabel": "__MISSING__:Phind (Free)", - "phindDesc": "__MISSING__:Free dev-focused AI chat", "poeWebLabel": "__MISSING__:Poe Web", "poeWebDesc": "__MISSING__:Multi-model chat via poe.com", "veniceWebLabel": "__MISSING__:Venice Web", diff --git a/src/i18n/messages/mr.json b/src/i18n/messages/mr.json index f32ca30f44..619f356739 100644 --- a/src/i18n/messages/mr.json +++ b/src/i18n/messages/mr.json @@ -4804,8 +4804,6 @@ "checkWebToken": "टोकन तपासा", "huggingchatLabel": "__MISSING__:HuggingChat (Free)", "huggingchatDesc": "__MISSING__:Free LLM chat via huggingface.co/chat", - "phindLabel": "__MISSING__:Phind (Free)", - "phindDesc": "__MISSING__:Free dev-focused AI chat", "poeWebLabel": "__MISSING__:Poe Web", "poeWebDesc": "__MISSING__:Multi-model chat via poe.com", "veniceWebLabel": "__MISSING__:Venice Web", diff --git a/src/i18n/messages/ms.json b/src/i18n/messages/ms.json index 7a8bfabdfb..f77759ef3a 100644 --- a/src/i18n/messages/ms.json +++ b/src/i18n/messages/ms.json @@ -4804,8 +4804,6 @@ "checkWebToken": "Semak token", "huggingchatLabel": "__MISSING__:HuggingChat (Free)", "huggingchatDesc": "__MISSING__:Free LLM chat via huggingface.co/chat", - "phindLabel": "__MISSING__:Phind (Free)", - "phindDesc": "__MISSING__:Free dev-focused AI chat", "poeWebLabel": "__MISSING__:Poe Web", "poeWebDesc": "__MISSING__:Multi-model chat via poe.com", "veniceWebLabel": "__MISSING__:Venice Web", diff --git a/src/i18n/messages/nl.json b/src/i18n/messages/nl.json index 3d7b367ddf..9c2d5c7a2e 100644 --- a/src/i18n/messages/nl.json +++ b/src/i18n/messages/nl.json @@ -4804,8 +4804,6 @@ "checkWebToken": "Controleer token", "huggingchatLabel": "__MISSING__:HuggingChat (Free)", "huggingchatDesc": "__MISSING__:Free LLM chat via huggingface.co/chat", - "phindLabel": "__MISSING__:Phind (Free)", - "phindDesc": "__MISSING__:Free dev-focused AI chat", "poeWebLabel": "__MISSING__:Poe Web", "poeWebDesc": "__MISSING__:Multi-model chat via poe.com", "veniceWebLabel": "__MISSING__:Venice Web", diff --git a/src/i18n/messages/no.json b/src/i18n/messages/no.json index 9251bbb62f..1d2a66826a 100644 --- a/src/i18n/messages/no.json +++ b/src/i18n/messages/no.json @@ -4804,8 +4804,6 @@ "checkWebToken": "Sjekk token", "huggingchatLabel": "__MISSING__:HuggingChat (Free)", "huggingchatDesc": "__MISSING__:Free LLM chat via huggingface.co/chat", - "phindLabel": "__MISSING__:Phind (Free)", - "phindDesc": "__MISSING__:Free dev-focused AI chat", "poeWebLabel": "__MISSING__:Poe Web", "poeWebDesc": "__MISSING__:Multi-model chat via poe.com", "veniceWebLabel": "__MISSING__:Venice Web", diff --git a/src/i18n/messages/phi.json b/src/i18n/messages/phi.json index d185e38eeb..20903e812e 100644 --- a/src/i18n/messages/phi.json +++ b/src/i18n/messages/phi.json @@ -4804,8 +4804,6 @@ "checkWebToken": "Suriin ang token", "huggingchatLabel": "__MISSING__:HuggingChat (Free)", "huggingchatDesc": "__MISSING__:Free LLM chat via huggingface.co/chat", - "phindLabel": "__MISSING__:Phind (Free)", - "phindDesc": "__MISSING__:Free dev-focused AI chat", "poeWebLabel": "__MISSING__:Poe Web", "poeWebDesc": "__MISSING__:Multi-model chat via poe.com", "veniceWebLabel": "__MISSING__:Venice Web", diff --git a/src/i18n/messages/pl.json b/src/i18n/messages/pl.json index 0d85a35810..105eabf5d0 100644 --- a/src/i18n/messages/pl.json +++ b/src/i18n/messages/pl.json @@ -4804,8 +4804,6 @@ "checkWebToken": "Sprawdź token", "huggingchatLabel": "__MISSING__:HuggingChat (Free)", "huggingchatDesc": "__MISSING__:Free LLM chat via huggingface.co/chat", - "phindLabel": "__MISSING__:Phind (Free)", - "phindDesc": "__MISSING__:Free dev-focused AI chat", "poeWebLabel": "__MISSING__:Poe Web", "poeWebDesc": "__MISSING__:Multi-model chat via poe.com", "veniceWebLabel": "__MISSING__:Venice Web", diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json index 6a176be8e5..805d8c03a9 100644 --- a/src/i18n/messages/pt-BR.json +++ b/src/i18n/messages/pt-BR.json @@ -4804,8 +4804,6 @@ "checkWebToken": "Verifique o token", "huggingchatLabel": "__MISSING__:HuggingChat (Free)", "huggingchatDesc": "__MISSING__:Free LLM chat via huggingface.co/chat", - "phindLabel": "__MISSING__:Phind (Free)", - "phindDesc": "__MISSING__:Free dev-focused AI chat", "poeWebLabel": "__MISSING__:Poe Web", "poeWebDesc": "__MISSING__:Multi-model chat via poe.com", "veniceWebLabel": "__MISSING__:Venice Web", diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index 6970574c44..8bcae514c7 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -4804,8 +4804,6 @@ "checkWebToken": "Verifique o token", "huggingchatLabel": "__MISSING__:HuggingChat (Free)", "huggingchatDesc": "__MISSING__:Free LLM chat via huggingface.co/chat", - "phindLabel": "__MISSING__:Phind (Free)", - "phindDesc": "__MISSING__:Free dev-focused AI chat", "poeWebLabel": "__MISSING__:Poe Web", "poeWebDesc": "__MISSING__:Multi-model chat via poe.com", "veniceWebLabel": "__MISSING__:Venice Web", diff --git a/src/i18n/messages/ro.json b/src/i18n/messages/ro.json index 20d7e28ed0..2668ee7132 100644 --- a/src/i18n/messages/ro.json +++ b/src/i18n/messages/ro.json @@ -4804,8 +4804,6 @@ "checkWebToken": "Verificați jetonul", "huggingchatLabel": "__MISSING__:HuggingChat (Free)", "huggingchatDesc": "__MISSING__:Free LLM chat via huggingface.co/chat", - "phindLabel": "__MISSING__:Phind (Free)", - "phindDesc": "__MISSING__:Free dev-focused AI chat", "poeWebLabel": "__MISSING__:Poe Web", "poeWebDesc": "__MISSING__:Multi-model chat via poe.com", "veniceWebLabel": "__MISSING__:Venice Web", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index e35c7ed60c..2c5c0f7568 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -4804,8 +4804,6 @@ "checkWebToken": "Проверить токен", "huggingchatLabel": "HuggingChat (Бесплатно)", "huggingchatDesc": "Бесплатный LLM чат через huggingface.co/chat", - "phindLabel": "Phind (Бесплатно)", - "phindDesc": "Бесплатный AI-чат для разработчиков", "poeWebLabel": "Poe Web", "poeWebDesc": "Многофункциональный чат через poe.com", "veniceWebLabel": "Венеция Веб", diff --git a/src/i18n/messages/sk.json b/src/i18n/messages/sk.json index 5bcb0b2e13..bf043d2359 100644 --- a/src/i18n/messages/sk.json +++ b/src/i18n/messages/sk.json @@ -4804,8 +4804,6 @@ "checkWebToken": "Kontrolný token", "huggingchatLabel": "__MISSING__:HuggingChat (Free)", "huggingchatDesc": "__MISSING__:Free LLM chat via huggingface.co/chat", - "phindLabel": "__MISSING__:Phind (Free)", - "phindDesc": "__MISSING__:Free dev-focused AI chat", "poeWebLabel": "__MISSING__:Poe Web", "poeWebDesc": "__MISSING__:Multi-model chat via poe.com", "veniceWebLabel": "__MISSING__:Venice Web", diff --git a/src/i18n/messages/sv.json b/src/i18n/messages/sv.json index 566b27dcd0..3639ea65d6 100644 --- a/src/i18n/messages/sv.json +++ b/src/i18n/messages/sv.json @@ -4804,8 +4804,6 @@ "checkWebToken": "Kontrollera token", "huggingchatLabel": "__MISSING__:HuggingChat (Free)", "huggingchatDesc": "__MISSING__:Free LLM chat via huggingface.co/chat", - "phindLabel": "__MISSING__:Phind (Free)", - "phindDesc": "__MISSING__:Free dev-focused AI chat", "poeWebLabel": "__MISSING__:Poe Web", "poeWebDesc": "__MISSING__:Multi-model chat via poe.com", "veniceWebLabel": "__MISSING__:Venice Web", diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index 6b22afbf50..df4cedc1ad 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -4660,8 +4660,6 @@ "checkWebToken": "检查令牌", "huggingchatLabel": "HuggingChat(免费)", "huggingchatDesc": "通过 huggingface.co/chat 免费使用 LLM 聊天", - "phindLabel": "Phind(免费)", - "phindDesc": "免费的开发者专用 AI 聊天", "poeWebLabel": "Poe 网页", "poeWebDesc": "通过 poe.com 进行多模型聊天", "veniceWebLabel": "威尼斯网络", diff --git a/src/lib/arenaEloSync.ts b/src/lib/arenaEloSync.ts index c57b1106e9..1e24148716 100644 --- a/src/lib/arenaEloSync.ts +++ b/src/lib/arenaEloSync.ts @@ -132,7 +132,6 @@ const VENDOR_PREFIXES = [ "alibaba/", "nvidia/", "01-ai/", - "phind/", "zerox/", "together/", "fireworks/", diff --git a/src/shared/components/lobeProviderIcons.ts b/src/shared/components/lobeProviderIcons.ts index e37bf7f5e3..d709e2a5c4 100644 --- a/src/shared/components/lobeProviderIcons.ts +++ b/src/shared/components/lobeProviderIcons.ts @@ -118,7 +118,6 @@ import OpenCodeMonoIcon from "@lobehub/icons/es/OpenCode/components/Mono"; import OpenRouterMonoIcon from "@lobehub/icons/es/OpenRouter/components/Mono"; import PerplexityColorIcon from "@lobehub/icons/es/Perplexity/components/Color"; import PerplexityMonoIcon from "@lobehub/icons/es/Perplexity/components/Mono"; -import PhindMonoIcon from "@lobehub/icons/es/Phind/components/Mono"; import PoeColorIcon from "@lobehub/icons/es/Poe/components/Color"; import PoeMonoIcon from "@lobehub/icons/es/Poe/components/Mono"; import PollinationsMonoIcon from "@lobehub/icons/es/Pollinations/components/Mono"; @@ -268,7 +267,6 @@ const LOBE_ICON_COMPONENTS = { OpenCode: { mono: OpenCodeMonoIcon }, OpenRouter: { mono: OpenRouterMonoIcon }, Perplexity: { mono: PerplexityMonoIcon, color: PerplexityColorIcon }, - Phind: { mono: PhindMonoIcon }, Poe: { mono: PoeMonoIcon, color: PoeColorIcon }, Pollinations: { mono: PollinationsMonoIcon }, Qoder: { mono: QoderMonoIcon, color: QoderColorIcon }, @@ -426,7 +424,6 @@ const LOBE_PROVIDER_ALIASES = { "perplexity-search": "Perplexity", "perplexity-web": "Perplexity", poe: "Poe", - phind: "Phind", pollinations: "Pollinations", qoder: "Qoder", qwen: "Qwen", diff --git a/src/shared/constants/providers/apikey/frontier-labs.ts b/src/shared/constants/providers/apikey/frontier-labs.ts index dc99ff4be1..ca3783c995 100644 --- a/src/shared/constants/providers/apikey/frontier-labs.ts +++ b/src/shared/constants/providers/apikey/frontier-labs.ts @@ -223,19 +223,6 @@ export const APIKEY_PROVIDERS_FRONTIER = { hasFree: true, freeNote: "Free tier: 50 RPM, 500,000 TPM — no credit card", }, - phind: { - id: "phind", - alias: "phind", - name: "Phind", - icon: "search", - color: "#EC4899", - textIcon: "PH", - website: "https://phind.com", - hasFree: false, - freeNote: "Discontinued 2026 — phind.com shut down (2026-01); no free tier.", - passthroughModels: true, - authHint: "Get API key at phind.com", - }, "arcee-ai": { id: "arcee-ai", alias: "arcee", diff --git a/src/shared/constants/providers/apikey/inference-hosts.ts b/src/shared/constants/providers/apikey/inference-hosts.ts index fa6c7b30db..67af6afe56 100644 --- a/src/shared/constants/providers/apikey/inference-hosts.ts +++ b/src/shared/constants/providers/apikey/inference-hosts.ts @@ -249,19 +249,6 @@ export const APIKEY_PROVIDERS_INFERENCE = { hasFree: true, freeNote: "$1 free credits, refreshes every 4 weeks", }, - huggingchat: { - id: "huggingchat", - alias: "huggingchat", - name: "HuggingChat", - icon: "chat", - color: "#FFD21E", - textIcon: "HC", - website: "https://huggingface.co/chat", - hasFree: true, - freeNote: "Free chat with open models (Llama, Mistral, etc.).", - passthroughModels: true, - authHint: "No API key required for basic access.", - }, monsterapi: { id: "monsterapi", alias: "monster", diff --git a/src/shared/constants/providers/web-cookie.ts b/src/shared/constants/providers/web-cookie.ts index 901996777e..17ef4a1f6d 100644 --- a/src/shared/constants/providers/web-cookie.ts +++ b/src/shared/constants/providers/web-cookie.ts @@ -200,24 +200,6 @@ export const WEB_COOKIE_PROVIDERS = { "Paste your hf-chat cookie value from huggingface.co/chat (DevTools → Application → Cookies → hf-chat). Optional — works without auth for basic use.", riskNoticeVariant: "webCookie", }, - phind: { - id: "phind", - alias: "ph", - name: "Phind (Free)", - icon: "auto_awesome", - color: "#000000", - textIcon: "PH", - website: "https://www.phind.com", - hasFree: false, - freeNote: "Discontinued 2026 — phind.com shut down (2026-01); no free tier.", - authHint: - "Paste your session cookie from phind.com (DevTools → Application → Cookies). Optional — works with free tier.", - subscriptionRisk: true, - riskNoticeVariant: "deprecated", - deprecated: true, - deprecationReason: - "Phind shut down its API (2026-01); the /api/chat endpoint no longer serves (sweep 2026-06-19).", - }, "poe-web": { id: "poe-web", alias: "poe", diff --git a/src/shared/providers/webSessionCredentials.ts b/src/shared/providers/webSessionCredentials.ts index 84990e6ac2..e8eebb03fe 100644 --- a/src/shared/providers/webSessionCredentials.ts +++ b/src/shared/providers/webSessionCredentials.ts @@ -129,13 +129,6 @@ export const WEB_SESSION_CREDENTIAL_REQUIREMENTS = { acceptsFullCookieHeader: true, storageKeys: ["cookie", "hf-chat"], }, - phind: { - kind: "cookie", - credentialName: "phind_session", - placeholder: "phind_session=... or full Cookie header from phind.com", - acceptsFullCookieHeader: true, - storageKeys: ["cookie", "phind_session"], - }, "poe-web": { kind: "cookie", credentialName: "p-b", diff --git a/tests/snapshots/provider/translate-path.json b/tests/snapshots/provider/translate-path.json index e1952f24d3..a2139d4a66 100644 --- a/tests/snapshots/provider/translate-path.json +++ b/tests/snapshots/provider/translate-path.json @@ -3175,29 +3175,6 @@ "stream": "https://www.perplexity.ai/rest/sse/perplexity_ask" } }, - "phind": { - "format": "openai", - "headers": { - "apiKey": { - "Accept": "text/event-stream", - "Authorization": "Bearer ", - "Content-Type": "application/json" - }, - "nonStream": { - "Authorization": "Bearer ", - "Content-Type": "application/json" - }, - "oauth": { - "Accept": "text/event-stream", - "Authorization": "Bearer ", - "Content-Type": "application/json" - } - }, - "url": { - "nonStream": "https://www.phind.com/api/chat", - "stream": "https://www.phind.com/api/chat" - } - }, "pioneer": { "format": "openai", "headers": { diff --git a/tests/unit/discontinued-providers-2026.test.ts b/tests/unit/discontinued-providers-2026.test.ts index 0af6bf5e2f..79fef1ead5 100644 --- a/tests/unit/discontinued-providers-2026.test.ts +++ b/tests/unit/discontinued-providers-2026.test.ts @@ -9,18 +9,23 @@ import assert from "node:assert"; describe("2026 discontinued free tiers — providers.ts hasFree reconciliation", () => { it("APIKEY_PROVIDERS dead tiers no longer advertise a free tier", async () => { const { APIKEY_PROVIDERS } = await import("../../src/shared/constants/providers.ts"); - for (const id of ["chutes", "kluster", "glhf", "phind", "gitlawb", "gitlawb-gmi", "aimlapi", "yi"]) { + // These providers still operate (an API key works) but lost their free tier, so + // they are KEPT with hasFree:false. phind is NOT here: the whole phind.com service + // shut down 2026-01-16, so it was removed entirely (registry/executor/catalogs), + // matching the dead-service-removal precedent (#5246 Gemini CLI). + for (const id of ["chutes", "kluster", "glhf", "gitlawb", "gitlawb-gmi", "aimlapi", "yi"]) { const p = (APIKEY_PROVIDERS as Record)[id]; assert.ok(p, `${id} should still exist in APIKEY_PROVIDERS (provider not removed, only its free flag)`); assert.strictEqual(p.hasFree, false, `${id} should have hasFree:false (discontinued in 2026)`); } }); - it("WEB_COOKIE_PROVIDERS phind (web/cookie path) no longer advertises a free tier", async () => { - const { WEB_COOKIE_PROVIDERS } = await import("../../src/shared/constants/providers.ts"); - const p = (WEB_COOKIE_PROVIDERS as Record)["phind"]; - assert.ok(p, "phind should still exist in WEB_COOKIE_PROVIDERS"); - assert.strictEqual(p.hasFree, false, "phind web/cookie should have hasFree:false (phind.com shut down 2026-01)"); + it("phind is fully removed (service shut down 2026-01) from both catalogs", async () => { + const { APIKEY_PROVIDERS, WEB_COOKIE_PROVIDERS } = await import( + "../../src/shared/constants/providers.ts" + ); + assert.ok(!("phind" in APIKEY_PROVIDERS), "phind must not be in APIKEY_PROVIDERS"); + assert.ok(!("phind" in WEB_COOKIE_PROVIDERS), "phind must not be in WEB_COOKIE_PROVIDERS"); }); it("intentionally-kept providers still advertise free (genuinely free / ToS-flagged, not flipped)", async () => { diff --git a/tests/unit/executor-phind.test.ts b/tests/unit/executor-phind.test.ts deleted file mode 100644 index da1a844cdc..0000000000 --- a/tests/unit/executor-phind.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { describe, it } from "node:test"; -import assert from "node:assert/strict"; - -const mod = await import("../../open-sse/executors/phind.ts"); - -describe("PhindExecutor", () => { - it("can be instantiated", () => { - const executor = new mod.PhindExecutor(); - assert.ok(executor); - }); - - it("execute returns proper shape on missing cookie (fetch fails)", async () => { - const executor = new mod.PhindExecutor(); - try { - const result = await executor.execute({ - model: "phind-model", - body: { messages: [{ role: "user", content: "hi" }] }, - stream: false, - credentials: { apiKey: "" }, - signal: null, - }); - assert.ok(result.response instanceof Response); - assert.ok(typeof result.url === "string"); - assert.ok(typeof result.headers === "object"); - } catch { - // Network error expected in test env - } - }); - - it("execute builds correct URL", async () => { - const executor = new mod.PhindExecutor(); - try { - const result = await executor.execute({ - model: "test", - body: { messages: [{ role: "user", content: "hi" }] }, - stream: false, - credentials: { apiKey: "fake" }, - signal: null, - }); - assert.ok(result.url.includes("phind.com/api/agent")); - } catch { - // expected - } - }); -}); diff --git a/tests/unit/executor-web-cookie-sweep.test.ts b/tests/unit/executor-web-cookie-sweep.test.ts index 49050e4a8d..94bcd67f63 100644 --- a/tests/unit/executor-web-cookie-sweep.test.ts +++ b/tests/unit/executor-web-cookie-sweep.test.ts @@ -60,7 +60,6 @@ const FAKE_CREDS: Record = { "inner-ai": "fake-audit-sweep user@example.com", "adapta-web": "__client=fake-audit-sweep", huggingchat: "hf-chat=fake-audit-sweep", - phind: "fake-audit-sweep", "poe-web": "p-b=fake-audit-sweep", "venice-web": "fake-audit-sweep", "v0-vercel-web": "fake-audit-sweep", diff --git a/tests/unit/provider-alias-uniqueness.test.ts b/tests/unit/provider-alias-uniqueness.test.ts index 7aeb68bdae..25628a84cb 100644 --- a/tests/unit/provider-alias-uniqueness.test.ts +++ b/tests/unit/provider-alias-uniqueness.test.ts @@ -18,7 +18,12 @@ import test from "node:test"; import assert from "node:assert/strict"; import { PROVIDER_ID_TO_ALIAS } from "../../open-sse/config/providerModels.ts"; -import { resolveProviderId, getProviderAlias } from "../../src/shared/constants/providers.ts"; +import { + resolveProviderId, + getProviderAlias, + APIKEY_PROVIDERS, + WEB_COOKIE_PROVIDERS, +} from "../../src/shared/constants/providers.ts"; test("no two provider IDs share the same alias in the open-sse registry", () => { const aliasToIds = new Map(); @@ -62,3 +67,19 @@ test("src/shared providers map resolves the same aliases unambiguously", () => { assert.equal(getProviderAlias("kimi"), "kimi"); assert.equal(getProviderAlias("hackclub"), "hc"); }); + +test("no provider id is registered in both the API-key and web-cookie catalogs", () => { + // A provider belongs to exactly one auth category; the same id in both catalogs + // renders the provider twice in the dashboard (once per section). huggingchat + // regressed this way (its API-key counterpart is the separate `huggingface` + // Inference API id), so it must live ONLY in WEB_COOKIE_PROVIDERS. + const apikeyIds = new Set(Object.keys(APIKEY_PROVIDERS)); + const overlap = Object.keys(WEB_COOKIE_PROVIDERS).filter((id) => apikeyIds.has(id)); + assert.deepEqual(overlap, [], `Providers duplicated across catalogs: ${overlap.join(", ")}`); + + assert.ok("huggingchat" in WEB_COOKIE_PROVIDERS, "huggingchat must be in the web-cookie catalog"); + assert.ok( + !("huggingchat" in APIKEY_PROVIDERS), + "huggingchat must NOT be in the API-key catalog (use `huggingface` for the API key path)" + ); +}); diff --git a/tests/unit/providers-constants-split.test.ts b/tests/unit/providers-constants-split.test.ts index 5888538492..eb4e425673 100644 --- a/tests/unit/providers-constants-split.test.ts +++ b/tests/unit/providers-constants-split.test.ts @@ -31,12 +31,12 @@ test("barrel still exports every catalog + key helpers", () => { } }); -test("APIKEY_PROVIDERS merges the 6 family files into 160 entries (no loss / no dup)", async () => { +test("APIKEY_PROVIDERS merges the 6 family files into 158 entries (no loss / no dup)", async () => { const keys = Object.keys((P as Record).APIKEY_PROVIDERS); - assert.equal(keys.length, 160); - assert.equal(new Set(keys).size, 160, "duplicate keys after spread-merge"); + assert.equal(keys.length, 158); + assert.equal(new Set(keys).size, 158, "duplicate keys after spread-merge"); // the merged object's entry-count equals the sum of the 6 semantic family files; families are a - // strict partition (every provider in exactly one), so the sum must be exactly 160. + // strict partition (every provider in exactly one), so the sum must be exactly 158. const families: [string, string][] = [ ["gateways", "APIKEY_PROVIDERS_GATEWAYS"], ["frontier-labs", "APIKEY_PROVIDERS_FRONTIER"], @@ -56,7 +56,7 @@ test("APIKEY_PROVIDERS merges the 6 family files into 160 entries (no loss / no seen.add(k); } } - assert.equal(famTotal, 160, "families must partition all 160 providers"); + assert.equal(famTotal, 158, "families must partition all 158 providers"); }); test("AI_PROVIDERS Proxy aggregates all sections; lookups resolve", () => { diff --git a/tests/unit/web-cookie-providers-new.test.ts b/tests/unit/web-cookie-providers-new.test.ts index 2baa240f33..fcaff2a212 100644 --- a/tests/unit/web-cookie-providers-new.test.ts +++ b/tests/unit/web-cookie-providers-new.test.ts @@ -2,7 +2,6 @@ import test from "node:test"; import assert from "node:assert/strict"; const { HuggingChatExecutor } = await import("../../open-sse/executors/huggingchat.ts"); -const { PhindExecutor } = await import("../../open-sse/executors/phind.ts"); const { PoeWebExecutor } = await import("../../open-sse/executors/poe-web.ts"); const { VeniceWebExecutor } = await import("../../open-sse/executors/venice-web.ts"); const { V0VercelWebExecutor } = await import("../../open-sse/executors/v0-vercel-web.ts"); @@ -97,13 +96,6 @@ test("HuggingChat executor is registered", () => { assert.ok(executor instanceof HuggingChatExecutor); }); -test("Phind executor is registered", () => { - assert.ok(hasSpecializedExecutor("phind")); - assert.ok(hasSpecializedExecutor("ph")); - const executor = getExecutor("phind"); - assert.ok(executor instanceof PhindExecutor); -}); - test("Poe Web executor is registered", () => { assert.ok(hasSpecializedExecutor("poe-web")); assert.ok(hasSpecializedExecutor("poe")); @@ -149,11 +141,6 @@ test("HuggingChat sets correct provider", () => { assert.equal(executor.getProvider(), "huggingchat"); }); -test("Phind sets correct provider", () => { - const executor = new PhindExecutor(); - assert.equal(executor.getProvider(), "phind"); -}); - test("Poe Web sets correct provider", () => { const executor = new PoeWebExecutor(); assert.equal(executor.getProvider(), "poe-web"); @@ -314,41 +301,6 @@ test("HuggingChat: fetch failure returns 502", async () => { } }); -// ── Phind Execution Tests ──────────────────────────────────────────────────── - -test("Phind: streaming returns SSE chunks", async () => { - const sseData = [ - 'data: {"choices":[{"delta":{"content":"Hello "}}]}', - 'data: {"choices":[{"delta":{"content":"world"}}]}', - ]; - const restore = mockFetchCapture(200, mockSSEStream(sseData)); - try { - const executor = new PhindExecutor(); - const result = await executor.execute({ - ...noopExecuteInput, - model: "phind-model", - }); - assert.ok(result.response instanceof Response); - assert.ok(result.url.includes("phind.com")); - const text = await result.response.text(); - assert.ok(text.includes("data:")); - } finally { - restore.restore(); - } -}); - -test("Phind: error response returns error result", async () => { - const restore = mockFetchCapture(403, "Forbidden"); - try { - const executor = new PhindExecutor(); - const result = await executor.execute(noopExecuteInput); - assert.ok(result.response instanceof Response); - assert.equal(result.response.status, 403); - } finally { - restore.restore(); - } -}); - // ── Poe Web Execution Tests ────────────────────────────────────────────────── test("Poe Web: non-streaming returns JSON completion", async () => { @@ -515,7 +467,6 @@ test("Doubao Web: error response returns error result", async () => { test("All executors handle Cookie: prefix", async () => { const executors = [ new HuggingChatExecutor(), - new PhindExecutor(), new PoeWebExecutor(), new VeniceWebExecutor(), new V0VercelWebExecutor(), @@ -553,7 +504,6 @@ test("All executors handle Cookie: prefix", async () => { test("All executors handle bare cookie value", async () => { const executors = [ new HuggingChatExecutor(), - new PhindExecutor(), new PoeWebExecutor(), new VeniceWebExecutor(), new V0VercelWebExecutor(),
🚫 Never hit limits
Auto-fallback across 237 providers in milliseconds. Quota out? Next provider takes over — zero downtime.
🚫 Never hit limits
Auto-fallback across 236 providers in milliseconds. Quota out? Next provider takes over — zero downtime.
💸 Save up to 95% tokens
RTK + Caveman stacked compression cuts 15–95% of eligible tokens (~89% avg on tool-heavy sessions).
🆓 $0 to start
50+ providers with a free tier, 11 free forever (Kiro, Qoder, Pollinations, LongCat…). No card needed.