From 8386ab3084bd069291cf5aa1ace95fb4bc30683c Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Tue, 2 Jun 2026 00:23:51 -0300 Subject: [PATCH] fix: complete #3054 dead-provider removal (petals executor + stale tests) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #3054 ("remove 9 dead/unreachable free providers") removed the petals/nanobanana configs, registry entries and validators but left dangling references that broke the build and the unit suite on release/v3.8.8: - open-sse/executors/petals.ts imported the deleted ../config/petals.ts (webpack "Module not found" → `next build` failed). Removed the executor, its registration + re-export in executors/index.ts, and the leftover `providerId === "petals"` branch in providerAllowsOptionalApiKey. - Removed tests for the now-deleted providers: executor-petals.test.ts and poolside-provider.test.ts (REGISTRY.poolside was removed), and the petals / nanobanana validator assertions in provider-validation-specialty.test.ts, plus the stale petals catalog assertions in providers-page-utils.test.ts, proxy-connection-test.test.ts and providers-route-managed-catalog.test.ts. The image/video/embed registries for nanobanana/replicate/nomic are real and untouched — only the dead chat/api-key surfaces were removed. 146/146 affected tests pass; typecheck / build clean. --- open-sse/executors/index.ts | 3 - open-sse/executors/petals.ts | 385 ------------------ src/shared/constants/providers.ts | 1 - tests/unit/executor-petals.test.ts | 148 ------- tests/unit/poolside-provider.test.ts | 79 ---- .../provider-validation-specialty.test.ts | 67 +-- tests/unit/providers-page-utils.test.ts | 9 - .../providers-route-managed-catalog.test.ts | 7 - tests/unit/proxy-connection-test.test.ts | 4 - 9 files changed, 2 insertions(+), 701 deletions(-) delete mode 100644 open-sse/executors/petals.ts delete mode 100644 tests/unit/executor-petals.test.ts delete mode 100644 tests/unit/poolside-provider.test.ts diff --git a/open-sse/executors/index.ts b/open-sse/executors/index.ts index bce527ce08..d46c7f0d13 100644 --- a/open-sse/executors/index.ts +++ b/open-sse/executors/index.ts @@ -26,7 +26,6 @@ import { AzureOpenAIExecutor } from "./azure-openai.ts"; import { CommandCodeExecutor } from "./commandCode.ts"; import { GitlabExecutor } from "./gitlab.ts"; import { NlpCloudExecutor } from "./nlpcloud.ts"; -import { PetalsExecutor } from "./petals.ts"; import { WindsurfExecutor } from "./windsurf.ts"; import { DevinCliExecutor } from "./devin-cli.ts"; import { DeepSeekWebExecutor } from "./deepseek-web.ts"; @@ -70,7 +69,6 @@ const executors = { gitlab: new GitlabExecutor(), "gitlab-duo": new GitlabExecutor("gitlab-duo"), nlpcloud: new NlpCloudExecutor(), - petals: new PetalsExecutor(), pollinations: new PollinationsExecutor(), pol: new PollinationsExecutor(), // Alias "cloudflare-ai": new CloudflareAIExecutor(), @@ -177,7 +175,6 @@ export { AzureOpenAIExecutor } from "./azure-openai.ts"; export { CommandCodeExecutor } from "./commandCode.ts"; export { GitlabExecutor } from "./gitlab.ts"; export { NlpCloudExecutor } from "./nlpcloud.ts"; -export { PetalsExecutor } from "./petals.ts"; export { WindsurfExecutor } from "./windsurf.ts"; export { DevinCliExecutor } from "./devin-cli.ts"; export { CopilotWebExecutor } from "./copilot-web.ts"; diff --git a/open-sse/executors/petals.ts b/open-sse/executors/petals.ts deleted file mode 100644 index fb0dfd7b21..0000000000 --- a/open-sse/executors/petals.ts +++ /dev/null @@ -1,385 +0,0 @@ -import { randomUUID } from "node:crypto"; - -import { - BaseExecutor, - mergeUpstreamExtraHeaders, - type ExecuteInput, - type ProviderCredentials, -} from "./base.ts"; -import { - PETALS_DEFAULT_BASE_URL, - PETALS_DEFAULT_MODEL, - normalizePetalsBaseUrl, -} from "../config/petals.ts"; -import { PROVIDERS } from "../config/constants.ts"; - -type JsonRecord = Record; -type OpenAIMessage = { - role?: string; - content?: unknown; -}; - -function asRecord(value: unknown): JsonRecord { - return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {}; -} - -function extractTextContent(content: unknown): string { - if (typeof content === "string") { - return content.trim(); - } - - if (!Array.isArray(content)) { - return ""; - } - - return content - .map((part) => { - if (!part || typeof part !== "object") return ""; - const item = part as Record; - if (item.type === "text" && typeof item.text === "string") { - return item.text; - } - if (item.type === "input_text" && typeof item.text === "string") { - return item.text; - } - return ""; - }) - .filter((text) => text.trim().length > 0) - .join("\n") - .trim(); -} - -function resolvePrompt(body: unknown): string { - const payload = asRecord(body); - - const directPrompt = extractTextContent(payload.prompt); - if (directPrompt) { - return directPrompt; - } - - const directInput = extractTextContent(payload.input); - if (directInput) { - return directInput; - } - - const messages = Array.isArray(payload.messages) ? (payload.messages as OpenAIMessage[]) : []; - if (messages.length === 0) return ""; - - const systemParts: string[] = []; - const transcript: string[] = []; - let lastRole = ""; - - for (const message of messages) { - const role = String(message?.role || "user").toLowerCase(); - const text = extractTextContent(message?.content); - if (!text) continue; - - if (role === "system" || role === "developer") { - systemParts.push(text); - continue; - } - - if (role === "assistant") { - transcript.push(`Assistant: ${text}`); - lastRole = "assistant"; - continue; - } - - transcript.push(`User: ${text}`); - lastRole = "user"; - } - - if (transcript.length === 0) { - return systemParts.join("\n\n").trim(); - } - - const parts: string[] = []; - if (systemParts.length > 0) { - parts.push(`System:\n${systemParts.join("\n\n")}`); - } - parts.push(transcript.join("\n\n")); - if (lastRole !== "assistant") { - parts.push("Assistant:"); - } - - return parts.join("\n\n").trim(); -} - -function resolveMaxNewTokens(body: unknown): number { - const payload = asRecord(body); - const candidates = [ - payload.max_new_tokens, - payload.max_completion_tokens, - payload.max_output_tokens, - payload.max_tokens, - ]; - - for (const value of candidates) { - if (typeof value === "number" && Number.isFinite(value) && value > 0) { - return Math.max(1, Math.min(4096, Math.floor(value))); - } - } - - return 256; -} - -function buildRequestPayload(model: string, body: unknown): URLSearchParams | null { - const payload = asRecord(body); - const prompt = resolvePrompt(payload); - if (!prompt) return null; - - const form = new URLSearchParams(); - form.set("model", model || PETALS_DEFAULT_MODEL); - form.set("inputs", prompt); - form.set("max_new_tokens", String(resolveMaxNewTokens(payload))); - - const hasSampling = - typeof payload.temperature === "number" || - typeof payload.top_k === "number" || - typeof payload.top_p === "number"; - - if (hasSampling) { - form.set("do_sample", "1"); - } - - if (typeof payload.temperature === "number") { - form.set("temperature", String(payload.temperature)); - } - if (typeof payload.top_k === "number") { - form.set("top_k", String(Math.max(1, Math.floor(payload.top_k)))); - } - if (typeof payload.top_p === "number") { - form.set("top_p", String(payload.top_p)); - } - if (typeof payload.repetition_penalty === "number") { - form.set("repetition_penalty", String(payload.repetition_penalty)); - } - - return form; -} - -function estimateTokens(text: string): number { - return Math.max(1, Math.ceil(text.length / 4)); -} - -function buildSseChunk(data: unknown): string { - return `data: ${JSON.stringify(data)}\n\n`; -} - -function buildOpenAiJsonCompletion( - content: string, - model: string, - id: string, - created: number -): Response { - const completionTokens = estimateTokens(content); - - return new Response( - JSON.stringify({ - id, - object: "chat.completion", - created, - model, - choices: [ - { - index: 0, - message: { role: "assistant", content }, - finish_reason: "stop", - }, - ], - usage: { - prompt_tokens: completionTokens, - completion_tokens: completionTokens, - total_tokens: completionTokens * 2, - }, - }), - { - status: 200, - headers: { "Content-Type": "application/json" }, - } - ); -} - -function buildSynthesizedStream( - content: string, - model: string, - id: string, - created: number -): Response { - const encoder = new TextEncoder(); - - const body = new ReadableStream({ - start(controller) { - controller.enqueue( - encoder.encode( - buildSseChunk({ - id, - object: "chat.completion.chunk", - created, - model, - choices: [{ index: 0, delta: { role: "assistant" }, finish_reason: null }], - }) - ) - ); - - if (content) { - controller.enqueue( - encoder.encode( - buildSseChunk({ - id, - object: "chat.completion.chunk", - created, - model, - choices: [{ index: 0, delta: { content }, finish_reason: null }], - }) - ) - ); - } - - controller.enqueue( - encoder.encode( - buildSseChunk({ - id, - object: "chat.completion.chunk", - created, - model, - choices: [{ index: 0, delta: {}, finish_reason: "stop" }], - }) - ) - ); - controller.enqueue(encoder.encode("data: [DONE]\n\n")); - controller.close(); - }, - }); - - return new Response(body, { - status: 200, - headers: { "Content-Type": "text/event-stream" }, - }); -} - -function toOpenAiError(status: number, message: string): Response { - return new Response( - JSON.stringify({ - error: { - message, - type: - status === 401 || status === 403 - ? "authentication_error" - : status === 429 - ? "rate_limit_error" - : "api_error", - }, - }), - { - status, - headers: { "Content-Type": "application/json" }, - } - ); -} - -export class PetalsExecutor extends BaseExecutor { - constructor() { - super("petals", PROVIDERS.petals || { format: "openai", baseUrl: PETALS_DEFAULT_BASE_URL }); - } - - buildUrl( - _model: string, - _stream: boolean, - _urlIndex = 0, - credentials: ProviderCredentials | null = null - ): string { - const rawBaseUrl = - typeof credentials?.providerSpecificData?.baseUrl === "string" - ? credentials.providerSpecificData.baseUrl - : this.config.baseUrl; - return normalizePetalsBaseUrl(rawBaseUrl); - } - - buildHeaders(credentials: ProviderCredentials | null): Record { - const token = credentials?.apiKey || credentials?.accessToken; - return { - "Content-Type": "application/x-www-form-urlencoded", - Accept: "application/json", - ...(token ? { Authorization: `Bearer ${token}` } : {}), - }; - } - - async execute({ model, body, stream, credentials, signal, upstreamExtraHeaders }: ExecuteInput) { - const resolvedModel = model || PETALS_DEFAULT_MODEL; - const payload = buildRequestPayload(resolvedModel, body); - const url = this.buildUrl(resolvedModel, stream, 0, credentials); - const headers = this.buildHeaders(credentials); - mergeUpstreamExtraHeaders(headers, upstreamExtraHeaders); - - if (!payload) { - return { - response: toOpenAiError(400, "Petals requests require at least one user prompt."), - url, - headers, - transformedBody: body, - }; - } - - const transformedBody = Object.fromEntries(payload.entries()); - - try { - const response = await fetch(url, { - method: "POST", - headers, - body: payload.toString(), - signal, - }); - - if (!response.ok) { - const errorText = await response.text(); - return { - response: toOpenAiError( - response.status, - `Petals API failed with status ${response.status}: ${errorText || "Unknown error"}` - ), - url, - headers, - transformedBody, - }; - } - - const json = asRecord(await response.json()); - if (json.ok === false) { - const traceback = - typeof json.traceback === "string" && json.traceback.trim() - ? json.traceback.trim() - : "Unknown Petals upstream error"; - return { - response: toOpenAiError(502, `Petals API error: ${traceback}`), - url, - headers, - transformedBody, - }; - } - - const content = typeof json.outputs === "string" ? json.outputs : ""; - const id = `chatcmpl-petals-${randomUUID()}`; - const created = Math.floor(Date.now() / 1000); - - return { - response: stream - ? buildSynthesizedStream(content, resolvedModel, id, created) - : buildOpenAiJsonCompletion(content, resolvedModel, id, created), - url, - headers, - transformedBody, - }; - } catch (error) { - const message = error instanceof Error ? error.message : String(error || "Unknown error"); - return { - response: toOpenAiError(502, `Petals fetch error: ${message}`), - url, - headers, - transformedBody, - }; - } - } -} - -export default PetalsExecutor; diff --git a/src/shared/constants/providers.ts b/src/shared/constants/providers.ts index d30a90c893..0e419d1eb6 100644 --- a/src/shared/constants/providers.ts +++ b/src/shared/constants/providers.ts @@ -2766,7 +2766,6 @@ export function isSelfHostedChatProvider(providerId: unknown): boolean { export function providerAllowsOptionalApiKey(providerId: unknown): boolean { return ( providerId === "searxng-search" || - providerId === "petals" || providerId === "pollinations" || providerId === "copilot-web" || providerId === "duckduckgo-web" || diff --git a/tests/unit/executor-petals.test.ts b/tests/unit/executor-petals.test.ts deleted file mode 100644 index d56306ea76..0000000000 --- a/tests/unit/executor-petals.test.ts +++ /dev/null @@ -1,148 +0,0 @@ -import test from "node:test"; -import assert from "node:assert/strict"; - -import { getExecutor, hasSpecializedExecutor } from "../../open-sse/executors/index.ts"; -import { PetalsExecutor } from "../../open-sse/executors/petals.ts"; - -function jsonResponse(body: unknown, status = 200) { - return new Response(JSON.stringify(body), { - status, - headers: { "Content-Type": "application/json" }, - }); -} - -test("PetalsExecutor is registered in the executor index", () => { - assert.equal(hasSpecializedExecutor("petals"), true); - assert.ok(getExecutor("petals") instanceof PetalsExecutor); -}); - -test("PetalsExecutor converts OpenAI messages into form data and wraps JSON responses", async () => { - const executor = new PetalsExecutor(); - const originalFetch = globalThis.fetch; - const calls: Array<{ - url: string; - body: URLSearchParams; - headers: Record; - }> = []; - - globalThis.fetch = async (url, init = {}) => { - calls.push({ - url: String(url), - body: new URLSearchParams(String(init.body || "")), - headers: init.headers as Record, - }); - - return jsonResponse({ - ok: true, - outputs: "Hi back from Petals.", - }); - }; - - try { - const result = await executor.execute({ - model: "stabilityai/StableBeluga2", - body: { - messages: [ - { role: "system", content: "You are concise." }, - { role: "user", content: "Hello" }, - { role: "assistant", content: "Hi there!" }, - { role: "user", content: "How are you?" }, - ], - max_tokens: 32, - temperature: 0.7, - top_p: 0.9, - }, - stream: false, - credentials: { apiKey: "" }, - signal: AbortSignal.timeout(10_000), - log: null, - }); - - assert.equal(calls.length, 1); - assert.equal(calls[0].url, "https://chat.petals.dev/api/v1/generate"); - assert.equal(calls[0].headers.Authorization, undefined); - assert.equal(calls[0].headers["Content-Type"], "application/x-www-form-urlencoded"); - assert.equal(calls[0].body.get("model"), "stabilityai/StableBeluga2"); - assert.equal(calls[0].body.get("max_new_tokens"), "32"); - assert.equal(calls[0].body.get("temperature"), "0.7"); - assert.equal(calls[0].body.get("top_p"), "0.9"); - assert.match( - calls[0].body.get("inputs") || "", - /System:\nYou are concise\.\n\nUser: Hello\n\nAssistant: Hi there!\n\nUser: How are you\?\n\nAssistant:/ - ); - - assert.deepEqual(result.transformedBody, { - model: "stabilityai/StableBeluga2", - inputs: - "System:\nYou are concise.\n\nUser: Hello\n\nAssistant: Hi there!\n\nUser: How are you?\n\nAssistant:", - max_new_tokens: "32", - do_sample: "1", - temperature: "0.7", - top_p: "0.9", - }); - - const body = (await result.response.json()) as any; - assert.equal(body.object, "chat.completion"); - assert.equal(body.choices[0].message.role, "assistant"); - assert.equal(body.choices[0].message.content, "Hi back from Petals."); - assert.equal(body.model, "stabilityai/StableBeluga2"); - } finally { - globalThis.fetch = originalFetch; - } -}); - -test("PetalsExecutor synthesizes OpenAI-compatible SSE responses for streaming requests", async () => { - const executor = new PetalsExecutor(); - const originalFetch = globalThis.fetch; - - globalThis.fetch = async () => - jsonResponse({ - ok: true, - outputs: "Petals stream output", - }); - - try { - const result = await executor.execute({ - model: "stabilityai/StableBeluga2", - body: { - messages: [{ role: "user", content: "Say hello" }], - }, - stream: true, - credentials: { apiKey: "" }, - signal: AbortSignal.timeout(10_000), - log: null, - }); - - assert.equal(result.response.headers.get("Content-Type"), "text/event-stream"); - const text = await result.response.text(); - assert.match(text, /data: \{\"id\":\"chatcmpl-petals-/); - assert.match(text, /Petals stream output/); - assert.match(text, /data: \[DONE\]/); - } finally { - globalThis.fetch = originalFetch; - } -}); - -test("PetalsExecutor maps upstream failures to OpenAI-style errors", async () => { - const executor = new PetalsExecutor(); - const originalFetch = globalThis.fetch; - - globalThis.fetch = async () => jsonResponse({ ok: false, traceback: "petals exploded" }, 200); - - try { - const result = await executor.execute({ - model: "stabilityai/StableBeluga2", - body: { messages: [{ role: "user", content: "hi" }] }, - stream: false, - credentials: { apiKey: "" }, - signal: AbortSignal.timeout(10_000), - log: null, - }); - - assert.equal(result.response.status, 502); - const body = (await result.response.json()) as any; - assert.match(body.error.message, /Petals API error: petals exploded/); - } finally { - globalThis.fetch = originalFetch; - } -}); diff --git a/tests/unit/poolside-provider.test.ts b/tests/unit/poolside-provider.test.ts deleted file mode 100644 index 13baa4130e..0000000000 --- a/tests/unit/poolside-provider.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -import test from "node:test"; -import assert from "node:assert/strict"; - -import { REGISTRY } from "../../open-sse/config/providerRegistry.ts"; -import { DefaultExecutor } from "../../open-sse/executors/default.ts"; - -test("poolside registry uses /v1/chat/completions baseUrl consumed directly by default executor", () => { - const entry = REGISTRY.poolside; - - assert.ok(entry, "poolside should exist in registry"); - assert.equal(entry.baseUrl, "https://api.poolside.ai/v1/chat/completions"); - assert.equal(entry.format, "openai"); - assert.equal(entry.authType, "apikey"); - assert.equal(entry.authHeader, "bearer"); -}); - -test("poolside default executor returns the chat endpoint directly", () => { - const executor = new DefaultExecutor("poolside"); - - assert.equal( - executor.buildUrl("poolside-model", true, 0, {}), - "https://api.poolside.ai/v1/chat/completions" - ); -}); - -test("poolside specialty validator returns valid=true on non-auth chat probe responses", async () => { - const calls: Array<{ url: string; status: number }> = []; - const originalFetch = globalThis.fetch; - globalThis.fetch = (async (url: any, init: any) => { - void init; - const u = String(url); - calls.push({ url: u, status: 400 }); - // Poolside returns 400 for minimal probe — that means auth passed - return new Response(JSON.stringify({ error: { message: "invalid model" } }), { - status: 400, - headers: { "content-type": "application/json" }, - }); - }) as typeof fetch; - - try { - const { validateProviderApiKey } = await import("../../src/lib/providers/validation.ts"); - const result = await validateProviderApiKey({ - provider: "poolside", - apiKey: "sky_validkey", - providerSpecificData: {}, - }); - assert.equal(result.valid, true); - assert.equal(result.error, null); - // Should hit /chat/completions only — no /models probe - assert.ok( - calls.every((c) => c.url.endsWith("/chat/completions")), - `expected only /chat/completions probes, got ${JSON.stringify(calls.map((c) => c.url))}` - ); - } finally { - globalThis.fetch = originalFetch; - } -}); - -test("poolside specialty validator returns Invalid API key on 401", async () => { - const originalFetch = globalThis.fetch; - globalThis.fetch = (async () => - new Response(JSON.stringify({ error: "unauthorized" }), { - status: 401, - headers: { "content-type": "application/json" }, - })) as typeof fetch; - - try { - const { validateProviderApiKey } = await import("../../src/lib/providers/validation.ts"); - const result = await validateProviderApiKey({ - provider: "poolside", - apiKey: "sky_badkey", - providerSpecificData: {}, - }); - assert.equal(result.valid, false); - assert.equal(result.error, "Invalid API key"); - } finally { - globalThis.fetch = originalFetch; - } -}); diff --git a/tests/unit/provider-validation-specialty.test.ts b/tests/unit/provider-validation-specialty.test.ts index e29019ba94..d30be44f0c 100644 --- a/tests/unit/provider-validation-specialty.test.ts +++ b/tests/unit/provider-validation-specialty.test.ts @@ -48,7 +48,7 @@ data: `; } -test("specialty provider validators cover Deepgram, AssemblyAI, NanoBanana, ElevenLabs and Inworld branches", async () => { +test("specialty provider validators cover Deepgram, AssemblyAI, ElevenLabs and Inworld branches", async () => { globalThis.fetch = async (url, init = {}) => { const target = String(url); const headers = init.headers || {}; @@ -61,9 +61,6 @@ test("specialty provider validators cover Deepgram, AssemblyAI, NanoBanana, Elev assert.equal(headers.Authorization, "aa-key"); return new Response(JSON.stringify({ error: "unauthorized" }), { status: 403 }); } - if (target.match(/nanobanana/i)) { - return new Response(JSON.stringify({ error: "unauthorized" }), { status: 401 }); - } if (target.match(/elevenlabs/i)) { return new Response(JSON.stringify({ voices: [] }), { status: 200 }); } @@ -76,13 +73,11 @@ test("specialty provider validators cover Deepgram, AssemblyAI, NanoBanana, Elev const deepgram = await validateProviderApiKey({ provider: "deepgram", apiKey: "dg-key" }); const assembly = await validateProviderApiKey({ provider: "assemblyai", apiKey: "aa-key" }); - const banana = await validateProviderApiKey({ provider: "nanobanana", apiKey: "nb-key" }); const eleven = await validateProviderApiKey({ provider: "elevenlabs", apiKey: "el-key" }); const inworld = await validateProviderApiKey({ provider: "inworld", apiKey: "iw-key" }); assert.equal(deepgram.valid, true); assert.equal(assembly.error, "Invalid API key"); - assert.equal(banana.error, "Invalid API key"); assert.equal(eleven.valid, true); assert.equal(inworld.valid, true); }); @@ -127,9 +122,6 @@ test("specialty providers surface network failures and non-auth upstream failure if (target.match(/deepgram/i)) { throw new Error("deepgram offline"); } - if (target.match(/nanobanana/i)) { - throw new Error("nanobanana offline"); - } if (target.match(/elevenlabs/i)) { return new Response(JSON.stringify({ error: "server" }), { status: 500 }); } @@ -143,13 +135,11 @@ test("specialty providers surface network failures and non-auth upstream failure }; const deepgram = await validateProviderApiKey({ provider: "deepgram", apiKey: "dg-key" }); - const banana = await validateProviderApiKey({ provider: "nanobanana", apiKey: "nb-key" }); const eleven = await validateProviderApiKey({ provider: "elevenlabs", apiKey: "el-key" }); const inworld = await validateProviderApiKey({ provider: "inworld", apiKey: "iw-key" }); const longcat = await validateProviderApiKey({ provider: "longcat", apiKey: "lc-key" }); assert.equal(deepgram.error, "deepgram offline"); - assert.equal(banana.error, "nanobanana offline"); assert.equal(eleven.error, "Validation failed: 500"); assert.equal(inworld.error, "Invalid API key"); assert.equal(longcat.error, "longcat offline"); @@ -1123,7 +1113,7 @@ test("registry providers cover remaining OpenAI-like and Claude-like validation assert.equal(calls[1].headers["x-api-key"], "sk-claude"); }); -test("specialty validators cover remaining status branches for Deepgram, AssemblyAI, NanoBanana, ElevenLabs, Inworld, Bailian and LongCat", async () => { +test("specialty validators cover remaining status branches for Deepgram, AssemblyAI, ElevenLabs, Inworld, Bailian and LongCat", async () => { globalThis.fetch = async (url) => { const target = String(url); if (target.match(/deepgram/i)) { @@ -1132,9 +1122,6 @@ test("specialty validators cover remaining status branches for Deepgram, Assembl if (target.match(/assemblyai/i)) { return new Response(JSON.stringify({ transcripts: [] }), { status: 200 }); } - if (target.match(/nanobanana/i)) { - return new Response(JSON.stringify({ error: "bad request" }), { status: 400 }); - } if (target.match(/elevenlabs/i)) { return new Response(JSON.stringify({ error: "forbidden" }), { status: 403 }); } @@ -1152,7 +1139,6 @@ test("specialty validators cover remaining status branches for Deepgram, Assembl const deepgram = await validateProviderApiKey({ provider: "deepgram", apiKey: "dg-key" }); const assembly = await validateProviderApiKey({ provider: "assemblyai", apiKey: "aa-key" }); - const banana = await validateProviderApiKey({ provider: "nanobanana", apiKey: "nb-key" }); const eleven = await validateProviderApiKey({ provider: "elevenlabs", apiKey: "el-key" }); const inworld = await validateProviderApiKey({ provider: "inworld", apiKey: "iw-key" }); const bailian = await validateProviderApiKey({ @@ -1179,7 +1165,6 @@ test("specialty validators cover remaining status branches for Deepgram, Assembl assert.equal(deepgram.error, "Validation failed: 500"); assert.equal(assembly.valid, true); - assert.equal(banana.error, "Validation failed: 400"); assert.equal(eleven.error, "Invalid API key"); assert.equal(inworld.error, "inworld offline"); assert.equal(bailian.error, "Validation failed: 500"); @@ -1722,54 +1707,6 @@ test("specialty validator rejects invalid Nous Research credentials", async () = assert.equal(nous.error, "Invalid API key"); }); -test("specialty validator accepts the public Petals generate endpoint without an API key", async () => { - globalThis.fetch = async (url, init = {}) => { - const target = String(url); - - if (target === "https://chat.petals.dev/api/v1/generate") { - const headers = init.headers as Record; - const body = new URLSearchParams(String(init.body)); - assert.equal(headers.Authorization, undefined); - assert.equal(headers["Content-Type"], "application/x-www-form-urlencoded"); - assert.equal(body.get("model"), "stabilityai/StableBeluga2"); - assert.equal(body.get("inputs"), "test"); - assert.equal(body.get("max_new_tokens"), "1"); - return new Response(JSON.stringify({ ok: true, outputs: "hi" }), { status: 200 }); - } - - throw new Error(`unexpected fetch: ${target}`); - }; - - const petals = await validateProviderApiKey({ - provider: "petals", - apiKey: "", - }); - - assert.equal(petals.valid, true); - assert.equal(petals.method, "petals_generate"); -}); - -test("specialty validator surfaces Petals upstream unavailability", async () => { - globalThis.fetch = async (url, init = {}) => { - const target = String(url); - - if (target === "https://chat.petals.dev/api/v1/generate") { - const headers = init.headers as Record; - assert.equal(headers.Authorization, undefined); - return new Response(JSON.stringify({ error: "unavailable" }), { status: 503 }); - } - - throw new Error(`unexpected fetch: ${target}`); - }; - - const petals = await validateProviderApiKey({ - provider: "petals", - apiKey: "", - }); - - assert.equal(petals.error, "Provider unavailable (503)"); -}); - test("specialty validator rejects invalid Poe credentials", async () => { globalThis.fetch = async (url, init = {}) => { const target = String(url); diff --git a/tests/unit/providers-page-utils.test.ts b/tests/unit/providers-page-utils.test.ts index 31fa453024..fc508582fb 100644 --- a/tests/unit/providers-page-utils.test.ts +++ b/tests/unit/providers-page-utils.test.ts @@ -249,7 +249,6 @@ test("static catalog entries resolve local, search, audio, web-cookie and upstre const clarifaiProvider = providerPageUtils.resolveDashboardProviderInfo("clarifai"); const empowerProvider = providerPageUtils.resolveDashboardProviderInfo("empower"); const nousProvider = providerPageUtils.resolveDashboardProviderInfo("nous-research"); - const petalsProvider = providerPageUtils.resolveDashboardProviderInfo("petals"); const poeProvider = providerPageUtils.resolveDashboardProviderInfo("poe"); const azureOpenAiProvider = providerPageUtils.resolveDashboardProviderInfo("azure-openai"); const azureAiProvider = providerPageUtils.resolveDashboardProviderInfo("azure-ai"); @@ -303,8 +302,6 @@ test("static catalog entries resolve local, search, audio, web-cookie and upstre assert.equal(empowerProvider?.name, providers.APIKEY_PROVIDERS.empower.name); assert.equal(nousProvider?.category, "apikey"); assert.equal(nousProvider?.name, providers.APIKEY_PROVIDERS["nous-research"].name); - assert.equal(petalsProvider?.category, "apikey"); - assert.equal(petalsProvider?.name, providers.APIKEY_PROVIDERS.petals.name); assert.equal(poeProvider?.category, "apikey"); assert.equal(poeProvider?.name, providers.APIKEY_PROVIDERS.poe.name); assert.equal(azureOpenAiProvider?.category, "apikey"); @@ -363,7 +360,6 @@ test("managed provider connection ids include supported static categories and ex assert.equal(providerCatalog.isManagedProviderConnectionId("clarifai"), true); assert.equal(providerCatalog.isManagedProviderConnectionId("empower"), true); assert.equal(providerCatalog.isManagedProviderConnectionId("nous-research"), true); - assert.equal(providerCatalog.isManagedProviderConnectionId("petals"), true); assert.equal(providerCatalog.isManagedProviderConnectionId("poe"), true); assert.equal(providerCatalog.isManagedProviderConnectionId("azure-openai"), true); assert.equal(providerCatalog.isManagedProviderConnectionId("azure-ai"), true); @@ -424,7 +420,6 @@ test("grok-web taxonomy stays web-cookie only and does not leak into api-key ent assert.equal("clarifai" in providers.APIKEY_PROVIDERS, true); assert.equal("empower" in providers.APIKEY_PROVIDERS, true); assert.equal("nous-research" in providers.APIKEY_PROVIDERS, true); - assert.equal("petals" in providers.APIKEY_PROVIDERS, true); assert.equal("poe" in providers.APIKEY_PROVIDERS, true); assert.equal("azure-ai" in providers.APIKEY_PROVIDERS, true); assert.equal("bedrock" in providers.APIKEY_PROVIDERS, true); @@ -512,10 +507,6 @@ test("grok-web taxonomy stays web-cookie only and does not leak into api-key ent apiKeyEntries.some((entry) => entry.providerId === "nous-research"), true ); - assert.equal( - apiKeyEntries.some((entry) => entry.providerId === "petals"), - true - ); assert.equal( apiKeyEntries.some((entry) => entry.providerId === "poe"), true diff --git a/tests/unit/providers-route-managed-catalog.test.ts b/tests/unit/providers-route-managed-catalog.test.ts index d2db1dbfcd..b56d12004f 100644 --- a/tests/unit/providers-route-managed-catalog.test.ts +++ b/tests/unit/providers-route-managed-catalog.test.ts @@ -110,13 +110,6 @@ test("providers route accepts managed local, audio, web-cookie and search provid name: "Nous Research Primary", }, }, - { - provider: "petals", - body: { - provider: "petals", - name: "Petals Public Endpoint", - }, - }, { provider: "poe", body: { diff --git a/tests/unit/proxy-connection-test.test.ts b/tests/unit/proxy-connection-test.test.ts index 4f5c0f9a49..475b2ae2e7 100644 --- a/tests/unit/proxy-connection-test.test.ts +++ b/tests/unit/proxy-connection-test.test.ts @@ -333,10 +333,6 @@ test("testApiKeyConnection: searxng-search with empty API key does NOT require A assert.equal(providerAllowsOptionalApiKey("searxng-search"), true); }); -test("testApiKeyConnection: petals with empty API key does NOT require API key", () => { - assert.equal(providerAllowsOptionalApiKey("petals"), true); -}); - test("testApiKeyConnection: self-hosted chat providers with empty API key do NOT require API key", () => { for (const provider of SELF_HOSTED_CHAT_PROVIDER_IDS) { assert.equal(