fix: complete #3054 dead-provider removal (petals executor + stale tests)

#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.
This commit is contained in:
diegosouzapw
2026-06-02 00:23:51 -03:00
parent c711ac62a7
commit 8386ab3084
9 changed files with 2 additions and 701 deletions

View File

@@ -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";

View File

@@ -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<string, unknown>;
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<string, unknown>;
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<Uint8Array>({
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<string, string> {
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;

View File

@@ -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" ||

View File

@@ -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<string, string>;
}> = [];
globalThis.fetch = async (url, init = {}) => {
calls.push({
url: String(url),
body: new URLSearchParams(String(init.body || "")),
headers: init.headers as Record<string, string>,
});
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;
}
});

View File

@@ -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;
}
});

View File

@@ -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<string, string>;
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<string, string>;
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);

View File

@@ -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

View File

@@ -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: {

View File

@@ -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(