Files
OmniRoute/src/lib/providers/modelListingCapability.ts
backryun 7ed8ada432 feat(providers): restore ChatGPT Web via clean-room browser transport (#12239)
Restores ChatGPT Web on a clean-room browser transport, merged on the operator's explicit decision.

Worth stating precisely, because this touches a provenance decision: the PR does not revert #11754. It narrows RETIRED_COMMON_CHATGPT_WEB_PROVIDER_IDS to the single GPL-derived alias cgpt-web and registers chatgpt-web as a separate clean-room id. The old implementation stays retired and blocked; the retirement machinery, its error code and its 410 contract are untouched. All four retirement suites agree with that distinction and pass unchanged.

The 44 protected agent-instruction surfaces this PR touches (AGENTS.md, llm.txt and its 42 mirrors, README) were verified rather than trusted: masking digits and comparing the removed and added line sets gives 264 lines on each side, identical — every change is a provider-count substitution, with no sentence added, removed or reworded.

Reconciled on merge: clean against the tip, with the two chat chokepoints this PR grows (src/sse/handlers/chat.ts +40, open-sse/handlers/chatCore.ts +30) recorded in the file-size baseline under an annotation. Verified that exactly those two caps move and nothing else, so the #12411 ratchet holds. The rebaseline is carried on this branch rather than left in a validation worktree — the propagation mistake that put the 2026-09-02 merge waves base-red in #12434.

Verified: 504/504 across the PR's 44 test files plus all four chatgpt-web retirement suites, check:provider-consistency OK (272 REGISTRY entries, 355 canonical providers), check-file-size OK, and every changed TypeScript file parses.

Thanks @backryun — separating the clean-room id from the retired alias, instead of reopening the old one, is what made this reviewable.
2026-09-02 10:31:41 -03:00

58 lines
2.8 KiB
TypeScript

// #5420 — Tool-only providers (web search / web fetch) do not expose a model
// listing; their "Import Models" button hits the `400 "does not support models
// listing"` route. The old `-search` suffix heuristic caught `brave-search` but
// missed tool-only providers whose id has no suffix (e.g. `firecrawl`, declared
// `serviceKinds: ["webFetch"]`). This pure helper decides, from the provider id
// plus its resolved serviceKinds, whether to hide model listing — without ever
// hiding an LLM or media provider that genuinely lists models. Leaf module: it
// imports nothing, so it cannot create an import cycle with the page.
/** Service kinds that, on their own, mean the provider lists no models. */
const TOOL_ONLY_SERVICE_KINDS = new Set<string>(["webSearch", "webFetch"]);
/** Providers whose registry catalog is the complete, intentional model list.
*
* Volcano Ark plan providers (`volcengine-agent-plan` / `volcengine-coding-plan`)
* are intentionally NOT curated: their model list is discovered live from the
* console API (see volcenginePlanModelDiscovery.ts) and merged into the synced
* catalog, so the static registry only acts as a capability-seed fallback. */
const CURATED_MODEL_ONLY_PROVIDERS = new Set<string>([
"kimi-web",
"zai-web",
// The clean-room browser integration exposes only model/effort routes
// observed in the first-party picker. It has no upstream model-list API.
"chatgpt-web",
]);
export function providerUsesCuratedModelsOnly(providerId: string): boolean {
return CURATED_MODEL_ONLY_PROVIDERS.has(providerId.trim().toLowerCase());
}
/**
* Providers whose non-empty synced AvailableModels catalog fully replaces the
* static registry for dashboard / `/v1/models` / Test All listing. Static rows
* remain offline fallback only when synced is empty.
*
* Cursor-only for now — other authoritative live-catalog providers keep
* coverage-style static preservation (e.g. command-code uncovered static ids).
*/
export function providerUsesExclusiveSyncedListing(providerId: string): boolean {
const id = providerId.trim().toLowerCase();
return id === "cursor" || id === "cu";
}
/**
* True when the provider is tool-only and therefore has no model listing:
* - its id ends in `-search` (legacy search providers), OR
* - it declares at least one serviceKind and EVERY declared kind is a tool-only
* kind (`webSearch` / `webFetch`) — i.e. no `llm` and no media/embedding kind.
*
* Returns false for an empty `kinds` (most LLM providers declare nothing) and for
* any provider that has `llm`/image/video/music/tts/stt/embedding.
*/
export function providerLacksModelListing(providerId: string, kinds: readonly string[]): boolean {
if (providerId.endsWith("-search")) return true;
if (kinds.length === 0) return false;
return kinds.every((kind) => TOOL_ONLY_SERVICE_KINDS.has(kind));
}