Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ Documentation must describe verified behavior, not plausible behavior.

### Adding a New Provider

0. Check `docs/reference/REMOVED_PROVIDERS.md` first — providers removed at their operator's request must never be reintroduced (guarded by `tests/unit/removed-providers-blocklist.test.ts`)
1. Register in `src/shared/constants/providers.ts` (Zod-validated at load)
2. Add executor in `open-sse/executors/` if custom logic needed (extend `BaseExecutor`)
3. Add translator in `open-sse/translator/` if non-OpenAI format
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ Lookup material — API surface, environment variables, CLI flags, provider cata

- [API_REFERENCE.md](reference/API_REFERENCE.md) — REST API endpoints and shapes.
- [PROVIDER_REFERENCE.md](reference/PROVIDER_REFERENCE.md) — auto-generated provider catalog (do not edit by hand).
- [REMOVED_PROVIDERS.md](reference/REMOVED_PROVIDERS.md) — providers removed at their operator's request; never reintroduce without written permission.
- [PROVIDER_PLUGIN_MANIFEST.md](reference/PROVIDER_PLUGIN_MANIFEST.md) — sidecar-safe provider plugin contract for Bifrost and CLIProxyAPI migration.
- [openapi.yaml](openapi.yaml) — OpenAPI spec for the public API.
- [ENVIRONMENT.md](reference/ENVIRONMENT.md) — environment variables reference.
Expand Down
60 changes: 60 additions & 0 deletions docs/reference/REMOVED_PROVIDERS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Providers removed at their operator's request

Some services were integrated into OmniRoute and later removed because the people who run
them asked for it. This page is the durable record of those removals. Its only purpose is to
keep them from coming back by accident: a contributor who finds an old fork, a cached npm
tarball, an archived issue or a "restore provider X" request needs one place that says **do
not reintroduce**.

This page is **not** a list of dead or discontinued services. Those are tracked in
[`FREE_TIERS.md`](FREE_TIERS.md) ("Removed / no free tier") and can come back if the service
does. The entries below can only come back with written permission from the operator named in
the request, and that permission must be linked from the entry.

## Policy

1. **A takedown request from a service operator is honored, not negotiated.** OmniRoute is
not affiliated with any upstream service. When the operator of a service asks for the
integration to go, it goes, whether the integration used an official API or not.
2. **"Removed" means every surface OmniRoute controls.** Executor, registry entry, provider
id and alias, model list, endpoints, environment variables, icon, dashboard cards, the
generated provider reference, `FREE_TIERS.md`, the environment reference, README counts,
`llm.txt` mirrors, dedicated tests and golden snapshots, code comments, CHANGELOG bullets
(with a ledgered reconciliation, see `config/release/changelog-reconciliations.json`),
GitHub Releases notes, the wiki, and the GitHub issues, discussions and pull requests whose
subject was that provider (issues and discussions deleted; pull requests retitled, their
description replaced and the thread locked, because GitHub cannot delete pull requests).
3. **Never reintroduce an entry on this page without written permission.** That includes
adding the id or alias back to any provider catalog, adding the domains to an executor,
accepting a contributor PR that "restores" it, adding it to the free-model catalog, or
documenting a manual way to reach it through OmniRoute. Close such PRs and issues with a
link to this page.
4. **Keep the entry minimal.** Record only what a reviewer needs to recognize a
reintroduction: identifiers, domains, dates and the pull request that did the removal.
Do not describe how the integration worked.
5. **The regression guard is `tests/unit/removed-providers-blocklist.test.ts`.** It fails when
any identifier or domain below shows up again in the provider catalogs, the executor map or
the provider registry sources. Add the new identifiers to that test in the same PR that
adds a row here.

## Register

| Removed on | Provider id | Alias | Domains | Requested by | Removal PR | Notes |
| ---------- | ----------- | ------ | --------------------------------------- | ------------------------------------ | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| 2026-08-12 | `puter` | `pu` | `puter.com` | Puter's owner (Nariman Jelveh) | [#10210](https://github.com/diegosouzapw/OmniRoute/pull/10210) | API-key provider. Migration `152_remove_puter_provider.sql` cleans stored config. |
| 2026-09-02 | `theoldllm` | `tllm` | `theoldllm.com`, `theoldllm.vercel.app` | The service operator (support email) | [#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440) | Keyless provider. Written request received 2026-08-30. Dedicated issues and discussion deleted, PRs retitled. |

## Adding an entry

When a new takedown request arrives:

1. Confirm the request comes from the operator of the service (their support address or a
domain they control), and keep the message privately.
2. Remove the integration following the checklist in policy item 2. Use
[#12440](https://github.com/diegosouzapw/OmniRoute/pull/12440) as the reference for a
keyless provider and [#10210](https://github.com/diegosouzapw/OmniRoute/pull/10210) for an
API-key provider with stored connections (add a migration).
3. Add one row to the table above and the identifiers to
`tests/unit/removed-providers-blocklist.test.ts`, in the same PR.
4. Reply to the operator once the PR is merged, listing what was removed and what OmniRoute
cannot change (already-published npm and Docker versions, git history, third-party forks).
1 change: 1 addition & 0 deletions docs/reference/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"FREE_TIERS",
"FREE_PROXIES_API",
"PROVIDER_REFERENCE",
"REMOVED_PROVIDERS",
"PROVIDER_PLUGIN_MANIFEST",
"RELAY_BACKEND_STRATEGY",
"RELAY_TROUBLESHOOTING"
Expand Down
107 changes: 107 additions & 0 deletions tests/unit/removed-providers-blocklist.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/**
* Regression guard for docs/reference/REMOVED_PROVIDERS.md.
*
* Providers removed at their operator's request must never come back: not in the
* provider catalogs, not in the executor map, not in the registry sources and not
* as an upstream domain in any executor. Keep this list in sync with the table in
* the doc; add the identifiers of a new takedown here in the same PR.
*/
import { test } from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";

const { REGISTRY } = await import("../../open-sse/config/providerRegistry.ts");
const { getProviderById, getProviderByAlias } =
await import("../../src/shared/constants/providers.ts");
const { hasSpecializedExecutor } = await import("../../open-sse/executors/index.ts");
const { FREE_MODEL_BUDGETS } = await import("../../open-sse/config/freeModelCatalog.data.ts");

interface RemovedProvider {
id: string;
alias: string;
domains: string[];
removalPr: number;
}

export const REMOVED_PROVIDERS: readonly RemovedProvider[] = [
{ id: "puter", alias: "pu", domains: ["puter.com"], removalPr: 10210 },
{
id: "theoldllm",
alias: "tllm",
domains: ["theoldllm.com", "theoldllm.vercel.app"],
removalPr: 12440,
},
];

// Source trees where a reintroduction would land. Scanned for ids, aliases and domains.
const SCANNED_DIRS = [
"open-sse/config/providers",
"open-sse/executors",
"src/shared/constants/providers",
];

function walk(dir: string, out: string[] = []): string[] {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) walk(full, out);
else if (/\.(ts|tsx|mts|js|mjs|json)$/.test(entry.name)) out.push(full);
}
return out;
}

const ROOT = process.cwd();
const scannedFiles = SCANNED_DIRS.flatMap((dir) => walk(path.join(ROOT, dir)));

for (const removed of REMOVED_PROVIDERS) {
test(`removed provider "${removed.id}" (PR #${removed.removalPr}) stays out of the chat registry`, () => {
assert.equal(REGISTRY[removed.id], undefined, `${removed.id} must not be in REGISTRY`);
assert.equal(REGISTRY[removed.alias], undefined, `${removed.alias} must not be in REGISTRY`);
});

test(`removed provider "${removed.id}" stays out of the provider catalogs`, () => {
assert.equal(getProviderById(removed.id), undefined, `${removed.id} must not be a provider`);
assert.equal(
getProviderByAlias(removed.alias),
null,
`alias ${removed.alias} must not be reused by any provider`
);
});

test(`removed provider "${removed.id}" has no executor (id or alias)`, () => {
assert.equal(hasSpecializedExecutor(removed.id), false);
assert.equal(hasSpecializedExecutor(removed.alias), false);
});

test(`removed provider "${removed.id}" has no free-model catalog entries`, () => {
assert.deepEqual(
FREE_MODEL_BUDGETS.filter((b) => b.provider === removed.id),
[],
`${removed.id} must not appear in FREE_MODEL_BUDGETS`
);
});

test(`removed provider "${removed.id}" identifiers and domains are absent from registry/executor sources`, () => {
const needles = [`"${removed.id}"`, `"${removed.alias}"`, ...removed.domains];
const offenders: string[] = [];
for (const file of scannedFiles) {
const text = fs.readFileSync(file, "utf8");
for (const needle of needles) {
if (text.includes(needle)) offenders.push(`${path.relative(ROOT, file)} :: ${needle}`);
}
}
assert.deepEqual(
offenders,
[],
`reintroduction of "${removed.id}" detected — see docs/reference/REMOVED_PROVIDERS.md`
);
});
}

test("the REMOVED_PROVIDERS doc lists every guarded id", () => {
const doc = fs.readFileSync(path.join(ROOT, "docs/reference/REMOVED_PROVIDERS.md"), "utf8");
for (const removed of REMOVED_PROVIDERS) {
assert.ok(doc.includes(`\`${removed.id}\``), `${removed.id} must have a row in the doc`);
assert.ok(doc.includes(`#${removed.removalPr}`), `PR #${removed.removalPr} must be linked`);
}
});
Loading