mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-13 18:32:12 +03:00
Rebased onto the current release/v3.8.51 tip as part of a combined provider-retirement/provenance merge batch (Designer Web, Felo Web, Runtime, GPL-derived removal, Qwen Web already landed). Large conflict set (this is the biggest PR in the batch — the common ChatGPT Web provider touches chat, images, count-tokens, session leases, and combos). Conflicts resolved: - `open-sse/config/providers/registry/chatgpt-web/*`, `open-sse/executors/chatgpt-web*`, `open-sse/handlers/imageGeneration/providers/chatgptWeb.ts`, and their tests: kept deleted, matching the PR's stated scope. - `open-sse/config/providers/registry/minimax/web/index.ts`, `open-sse/handlers/imageGeneration/providers/geminiWeb.ts`, `open-sse/executors/gemini-web.ts`'s stale image-mode branch: base-drift collisions against already-merged sibling retirements (#11691, #11708) — kept deleted / dropped the dead code, since this PR's own branch forked before those merged. - `src/shared/constants/reservedProviderPrefixes.ts`, `open-sse/executors/index.ts`, `executorProxy.ts`, `virtualFactory.ts`, `autoStrategy.ts`, `src/lib/db/providers.ts`, `src/sse/handlers/chat.ts`: combined the Designer + Runtime (Felo/Qwen) + common-ChatGPT-Web retirement guard calls at each shared chokepoint — compute-once-then-OR pattern, consistent with prior combinations in this batch. - `src/sse/services/model.ts` / `src/sse/handlers/chatHelpers.ts`: adopted this PR's new `getModelInfoOrRetirementResponse()` central wrapper (a real improvement over ad-hoc try/catch), and extended it to also catch the Designer + Runtime retirement errors it didn't originally cover, so the consolidation doesn't regress the other two mechanisms. - `src/app/api/v1/images/edits/route.ts`: this PR moved the retirement check earlier (before `enforceApiKeyPolicy`) but left the old later call+catch block in place from base drift — removed the now-redundant duplicate `resolveImageRouteModel()` call and merged the Designer catch into the earlier one. - `open-sse/config/imageRegistry.ts`, `tests/snapshots/executors/executor-map.json` (`keyCount` recomputed to 133), `tests/snapshots/provider/translate-path.json`: same "both sides inserted a different retired provider at the same slot" pattern — resolved by dropping both. - `tests/unit/chatcore-executor-proxy.test.ts`, `provider-node-reserved-prefix.test.ts`, `combo-auto-candidate-expansion.test.ts`, `messages-count-tokens-route.test.ts`, `virtual-auto-combo.test.ts`: split into independent per-mechanism test blocks (established pattern); `virtual-auto-combo.test.ts`'s old "includes cookie web-session providers" positive-inclusion test (which used chatgpt-web as its example) was retired along with the provider and replaced by this PR's negative-exclusion test for the same slot. - `docs/architecture/ARCHITECTURE.md`, `CODEBASE_DOCUMENTATION.md` (+ 4 i18n mirrors), `README.md`, `FREE-TIERS-GUIDE.md`, `docs/diagrams/free-tier-budget.svg`, `docs/screenshots/free-tier-budget-card.svg`, `docs/reference/PROVIDER_REFERENCE.md`: recomputed every stale count from the real merged state — 104 executors (`countFiles` gate logic), 351 providers (regenerated via `gen:provider-reference`), 152/351 `hasFree` entries, 445/438/7 free-tier catalog rows, 13 ToS-avoid providers, budget-card regenerated via its real generator script. One doc conflict (`oauth/` module list) needed picking HEAD's side specifically — theirs still listed the already-removed `raycast` module instead of the real `openference`. - `config/quality/test-masking-allowlist.json`: additive merge of the PR's 17 `_deletedWithReplacement` entries alongside the batch's existing ones (one real duplicate-key mistake in my first pass, caught and fixed via a `object_pairs_hook` duplicate-key check before finalizing). Also fixed two real, unrelated-to-my-merge issues surfaced by the focused suite: - `tests/unit/resolve-web-provider-host.test.ts`: the PR's own test had a typo — it asserted `perplexity-web`'s resolved host as `"perplexity.ai"`, but the provider's registered `website` is `"https://www.perplexity.ai"` and the resolver returns the URL's `host` verbatim (no www-stripping), so the correct value is `"www.perplexity.ai"` (consistent with the same test's own `url` assertion). - `tests/unit/hard-session-lease-bypass-inventory.test.ts`: this golden call-site inventory was already stale on the pristine post-#11713 tip (confirmed via a throwaway probe worktree) — `src/lib/db/providers.ts`'s 3 connection-fallback sites and a third `src/app/api/providers/route.ts` site were never added to the golden list by the earlier-merged #11698/#11720 PRs. Updated it to the real current inventory (dated inline comments explain each delta and which PR introduced it), plus this PR's own legitimate deltas (image-edits duplicate-call removal, `ChatGptWebExecutor.execute()` site removed). Focused suite green (433/433 across executor-proxy, reserved-prefix, hard-session-lease-bypass-inventory, resolve-web-provider-host, retirement/runtime-block/source-retirement/management-retirement/image-handler-retirement, migration-168, combo-auto-candidate-expansion, virtual-auto-combo, executor-map-golden and siblings), plus `typecheck:core`, `check-file-size`, and `check-changelog-integrity` clean. Thanks for the thorough provenance-hold retirement work — appreciated.
271 lines
17 KiB
TypeScript
271 lines
17 KiB
TypeScript
import { SEARCH_PROVIDERS } from "../config/searchRegistry.ts";
|
||
import { assertMicrosoftDesignerWebProviderAvailable } from "@/shared/constants/designerWebRetirement";
|
||
import { assertRuntimeProviderAvailable } from "@/shared/constants/providerRetirement";
|
||
import { assertCommonChatGptWebProviderAvailable } from "@/shared/constants/chatgptWebRetirement";
|
||
import {
|
||
registerLazyExecutor,
|
||
loadRegisteredExecutor,
|
||
hasRegisteredExecutor,
|
||
} from "./registry.ts";
|
||
// Type-only: pulls no runtime code, keeps DefaultExecutor the only eager class.
|
||
import type { BaseExecutor } from "./base.ts";
|
||
import { DefaultExecutor } from "./default.ts";
|
||
|
||
// R0.3 — declarative built-in table, made LAZY by #11220.
|
||
//
|
||
// The object literal below stays as the single place built-ins are declared
|
||
// (compile-time duplicate-key safety; the check:known-symbols gate parses this
|
||
// literal from source), but its values are now deferred loaders instead of
|
||
// eagerly-constructed instances. At module load every ALIAS is registered in
|
||
// declaration order; the class import + construction happen on first use via
|
||
// loadRegisteredExecutor() and are cached in the same registry a static
|
||
// registration would have populated.
|
||
//
|
||
// Why: importing this barrel previously pulled all ~100 executor modules and
|
||
// constructed every instance at boot — ~0.7–1.2s and ~35MB of heap measured on
|
||
// top of the minimal set — even for deployments that use a handful of
|
||
// providers. Bundlers split the dynamic imports into on-demand chunks.
|
||
//
|
||
// Contract preserved (pinned by tests/unit/executor-map-golden.test.ts):
|
||
// - keys and their ORDER are byte-stable
|
||
// - each alias still gets its OWN instance (aliases never share)
|
||
// - ctor arguments are unchanged
|
||
const lazyExecutors: Record<string, () => Promise<BaseExecutor>> = {
|
||
antigravity: () => import("./antigravity.ts").then((m) => new m.AntigravityExecutor()),
|
||
agy: () => import("./antigravity.ts").then((m) => new m.AntigravityExecutor()),
|
||
github: () => import("./github.ts").then((m) => new m.GithubExecutor()),
|
||
"ghe-copilot": () => import("./ghe-copilot.ts").then((m) => new m.GheCopilotExecutor()),
|
||
qoder: () => import("./qoder.ts").then((m) => new m.QoderExecutor()),
|
||
kiro: () => import("./kiro.ts").then((m) => new m.KiroExecutor()),
|
||
"amazon-q": () => import("./kiro.ts").then((m) => new m.KiroExecutor("amazon-q")),
|
||
bedrock: () => import("./bedrock.ts").then((m) => new m.BedrockExecutor()),
|
||
codex: () => import("./codex.ts").then((m) => new m.CodexExecutor()),
|
||
"codex-app-server": () =>
|
||
import("./codex-app-server.ts").then(
|
||
(m) => new m.CodexAppServerExecutor({}, "codex-app-server")
|
||
),
|
||
"chatgpt-web-codex": () =>
|
||
import("./chatgpt-web-codex.ts").then((m) => new m.ChatGptWebCodexExecutor()),
|
||
"cgpt-codex": () =>
|
||
import("./chatgpt-web-codex.ts").then((m) => new m.ChatGptWebCodexExecutor()),
|
||
cursor: () => import("./cursor.ts").then((m) => new m.CursorExecutor()),
|
||
trae: () => import("./trae.ts").then((m) => new m.TraeExecutor()),
|
||
glm: () => import("./glm.ts").then((m) => new m.GlmExecutor("glm")),
|
||
"glm-cn": () => import("./glm.ts").then((m) => new m.GlmExecutor("glm-cn")),
|
||
glmt: () => import("./glm.ts").then((m) => new m.GlmExecutor("glmt")),
|
||
cu: () => import("./cursor.ts").then((m) => new m.CursorExecutor()), // Alias for cursor
|
||
"cursor-api": () => import("./cursor.ts").then((m) => new m.CursorExecutor("cursor-api")),
|
||
cua: () => import("./cursor.ts").then((m) => new m.CursorExecutor("cursor-api")),
|
||
"azure-openai": () => import("./azure-openai.ts").then((m) => new m.AzureOpenAIExecutor()),
|
||
"azure-ai": () => import("./azure-ai.ts").then((m) => new m.AzureAiExecutor()),
|
||
"command-code": () => import("./commandCode.ts").then((m) => new m.CommandCodeExecutor()),
|
||
cmd: () => import("./commandCode.ts").then((m) => new m.CommandCodeExecutor()), // Alias
|
||
gitlab: () => import("./gitlab.ts").then((m) => new m.GitlabExecutor()),
|
||
"gitlab-duo": () => import("./gitlab.ts").then((m) => new m.GitlabExecutor("gitlab-duo")),
|
||
nlpcloud: () => import("./nlpcloud.ts").then((m) => new m.NlpCloudExecutor()),
|
||
oneminai: () => import("./oneminai.ts").then((m) => new m.OneMinAiExecutor()),
|
||
"1min": () => import("./oneminai.ts").then((m) => new m.OneMinAiExecutor()), // Alias
|
||
pollinations: () => import("./pollinations.ts").then((m) => new m.PollinationsExecutor()),
|
||
pol: () => import("./pollinations.ts").then((m) => new m.PollinationsExecutor()), // Alias
|
||
"cloudflare-ai": () => import("./cloudflare-ai.ts").then((m) => new m.CloudflareAIExecutor()),
|
||
cf: () => import("./cloudflare-ai.ts").then((m) => new m.CloudflareAIExecutor()), // Alias
|
||
freebuff: () => import("./freebuff.ts").then((m) => new m.FreebuffExecutor()),
|
||
fb: () => import("./freebuff.ts").then((m) => new m.FreebuffExecutor()), // Alias
|
||
"opencode-zen": () =>
|
||
import("./opencode.ts").then((m) => new m.OpencodeExecutor("opencode-zen")),
|
||
"opencode-go": () =>
|
||
import("./opencode.ts").then((m) => new m.OpencodeExecutor("opencode-go")),
|
||
opencode: () =>
|
||
import("./opencode.ts").then((m) => new m.OpencodeExecutor("opencode-zen")), // Alias for opencode-zen
|
||
vertex: () => import("./vertex.ts").then((m) => new m.VertexExecutor()),
|
||
"vertex-partner": () => import("./vertex.ts").then((m) => new m.VertexExecutor()),
|
||
cliproxyapi: () => import("./cliproxyapi.ts").then((m) => new m.CliproxyapiExecutor()),
|
||
cpa: () => import("./cliproxyapi.ts").then((m) => new m.CliproxyapiExecutor()), // Alias
|
||
dario: () => import("./dario.ts").then((m) => new m.DarioExecutor()),
|
||
dr: () => import("./dario.ts").then((m) => new m.DarioExecutor()), // Alias
|
||
"9router": () => import("./ninerouter.ts").then((m) => new m.NineRouterExecutor()),
|
||
nr: () => import("./ninerouter.ts").then((m) => new m.NineRouterExecutor()), // Alias
|
||
"perplexity-web": () =>
|
||
import("./perplexity-web.ts").then((m) => new m.PerplexityWebExecutor()),
|
||
"pplx-web": () =>
|
||
import("./perplexity-web.ts").then((m) => new m.PerplexityWebExecutor()), // Alias
|
||
"grok-web": () => import("./grok-web.ts").then((m) => new m.GrokWebExecutor()),
|
||
"claude-web": () => import("./claude-web.ts").then((m) => new m.ClaudeWebExecutor()),
|
||
"cw-web": () => import("./claude-web.ts").then((m) => new m.ClaudeWebExecutor()), // Alias
|
||
"gemini-web": () => import("./gemini-web.ts").then((m) => new m.GeminiWebExecutor()),
|
||
gweb: () => import("./gemini-web.ts").then((m) => new m.GeminiWebExecutor()), // Alias
|
||
"gemini-business": () =>
|
||
import("./gemini-business.ts").then((m) => new m.GeminiBusinessExecutor()),
|
||
gembiz: () =>
|
||
import("./gemini-business.ts").then((m) => new m.GeminiBusinessExecutor()), // Alias
|
||
"blackbox-web": () => import("./blackbox-web.ts").then((m) => new m.BlackboxWebExecutor()),
|
||
"bb-web": () => import("./blackbox-web.ts").then((m) => new m.BlackboxWebExecutor()), // Alias
|
||
"muse-spark-web": () =>
|
||
import("./muse-spark-web.ts").then((m) => new m.MuseSparkWebExecutor()),
|
||
"ms-web": () => import("./muse-spark-web.ts").then((m) => new m.MuseSparkWebExecutor()), // Alias
|
||
"devin-desktop": () => import("./devin-desktop.ts").then((m) => new m.DevinDesktopExecutor()),
|
||
"zed-hosted": () => import("./zed-hosted.ts").then((m) => new m.ZedHostedExecutor()),
|
||
"devin-cli": () => import("./devin-cli.ts").then((m) => new m.DevinCliExecutor()),
|
||
zcode: () => import("./zcode.ts").then((m) => new m.ZcodeExecutor()),
|
||
zc: () => import("./zcode.ts").then((m) => new m.ZcodeExecutor()), // Alias
|
||
"devin-cli-agentic": () =>
|
||
import("./devin-cli-agentic.ts").then((m) => new m.DevinCliAgenticExecutor()),
|
||
devin: () => import("./devin-cli.ts").then((m) => new m.DevinCliExecutor()), // Alias
|
||
"deepseek-web": () =>
|
||
import("./deepseek-web-with-auto-refresh.ts").then(
|
||
(m) => new m.DeepSeekWebWithAutoRefreshExecutor()
|
||
),
|
||
"ds-web": () =>
|
||
import("./deepseek-web-with-auto-refresh.ts").then(
|
||
(m) => new m.DeepSeekWebWithAutoRefreshExecutor()
|
||
), // Alias
|
||
"adapta-web": () => import("./adapta-web.ts").then((m) => new m.AdaptaWebExecutor()),
|
||
"adp-web": () => import("./adapta-web.ts").then((m) => new m.AdaptaWebExecutor()), // Alias
|
||
"copilot-web": () => import("./copilot-web.ts").then((m) => new m.CopilotWebExecutor()),
|
||
"copilot-m365-web": () =>
|
||
import("./copilot-m365-web.ts").then((m) => new m.CopilotM365WebExecutor()),
|
||
copilot: () => import("./copilot-web.ts").then((m) => new m.CopilotWebExecutor()), // Alias
|
||
"adobe-firefly": () => import("./adobe-firefly.ts").then((m) => new m.AdobeFireflyExecutor()),
|
||
firefly: () => import("./adobe-firefly.ts").then((m) => new m.AdobeFireflyExecutor()), // Alias
|
||
"veoaifree-web": () => import("./veoaifree-web.ts").then((m) => new m.VeoAIFreeWebExecutor()),
|
||
"veo-free": () => import("./veoaifree-web.ts").then((m) => new m.VeoAIFreeWebExecutor()), // Alias
|
||
"duckduckgo-web": () =>
|
||
import("./duckduckgo-web.ts").then((m) => new m.DuckDuckGoWebExecutor()),
|
||
ddgw: () => import("./duckduckgo-web.ts").then((m) => new m.DuckDuckGoWebExecutor()), // Alias
|
||
"t3-web": () => import("./t3-chat-web.ts").then((m) => new m.T3ChatWebExecutor()),
|
||
t3chat: () => import("./t3-chat-web.ts").then((m) => new m.T3ChatWebExecutor()), // Alias
|
||
"inner-ai": () => import("./inner-ai.ts").then((m) => new m.InnerAiExecutor()),
|
||
"in-ai": () => import("./inner-ai.ts").then((m) => new m.InnerAiExecutor()), // Alias
|
||
huggingchat: () => import("./huggingchat.ts").then((m) => new m.HuggingChatExecutor()),
|
||
hc: () => import("./huggingchat.ts").then((m) => new m.HuggingChatExecutor()), // Alias
|
||
"yuanbao-web": () => import("./yuanbao-web.ts").then((m) => new m.YuanbaoWebExecutor()),
|
||
"tencent-aistudio-web": () =>
|
||
import("./tencent-aistudio-web.ts").then((m) => new m.TencentAIStudioWebExecutor()),
|
||
tasw: () =>
|
||
import("./tencent-aistudio-web.ts").then((m) => new m.TencentAIStudioWebExecutor()), // Alias
|
||
ybw: () => import("./yuanbao-web.ts").then((m) => new m.YuanbaoWebExecutor()), // Alias
|
||
"poe-web": () => import("./poe-web.ts").then((m) => new m.PoeWebExecutor()),
|
||
// #8969: do NOT alias canonical `poe` (API-key / api.poe.com) to PoeWebExecutor.
|
||
// Registry declares executor:"default"; the hard-coded map previously won and
|
||
// routed API-key traffic to GraphQL /api/gql_POST → HTTP 405.
|
||
"venice-web": () => import("./venice-web.ts").then((m) => new m.VeniceWebExecutor()),
|
||
ven: () => import("./venice-web.ts").then((m) => new m.VeniceWebExecutor()), // Alias
|
||
"notion-web": () => import("./notion-web.ts").then((m) => new m.NotionWebExecutor()),
|
||
nw: () => import("./notion-web.ts").then((m) => new m.NotionWebExecutor()), // Alias
|
||
promptql: () => import("./promptql.ts").then((m) => new m.PromptQlExecutor()),
|
||
pql: () => import("./promptql.ts").then((m) => new m.PromptQlExecutor()), // Alias
|
||
"v0-vercel-web": () => import("./v0-vercel-web.ts").then((m) => new m.V0VercelWebExecutor()),
|
||
v0: () => import("./v0-vercel-web.ts").then((m) => new m.V0VercelWebExecutor()), // Alias
|
||
"kimi-web": () => import("./kimi-web.ts").then((m) => new m.KimiWebExecutor()),
|
||
"kimi-coding-apikey": () =>
|
||
import("./kimi.ts").then((m) => new m.KimiExecutor("kimi-coding-apikey")), // Legacy alias
|
||
"kimi-coding": () => import("./kimi.ts").then((m) => new m.KimiExecutor()), // Alias
|
||
moonshot: () => import("./moonshot.ts").then((m) => new m.MoonshotExecutor()),
|
||
kimi: () => import("./moonshot.ts").then((m) => new m.MoonshotExecutor("kimi")), // Hidden legacy Moonshot provider id
|
||
cheaperinference: () =>
|
||
import("./cheaperinference.ts").then((m) => new m.CheaperInferenceExecutor()),
|
||
cinf: () =>
|
||
import("./cheaperinference.ts").then(
|
||
(m) => new m.CheaperInferenceExecutor("cheaperinference")
|
||
), // Alias
|
||
"doubao-web": () => import("./doubao-web.ts").then((m) => new m.DoubaoWebExecutor()),
|
||
db: () => import("./doubao-web.ts").then((m) => new m.DoubaoWebExecutor()), // Alias
|
||
"zai-web": () => import("./zai-web.ts").then((m) => new m.ZaiWebExecutor()),
|
||
zw: () => import("./zai-web.ts").then((m) => new m.ZaiWebExecutor()), // Alias
|
||
theoldllm: () => import("./theoldllm.ts").then((m) => new m.TheOldLlmExecutor()),
|
||
tllm: () => import("./theoldllm.ts").then((m) => new m.TheOldLlmExecutor()), // Alias
|
||
chipotle: () => import("./chipotle.ts").then((m) => new m.ChipotleExecutor()),
|
||
pepper: () => import("./chipotle.ts").then((m) => new m.ChipotleExecutor()), // Alias
|
||
lmarena: () => import("./lmarena.ts").then((m) => new m.LMArenaExecutor()),
|
||
lma: () => import("./lmarena.ts").then((m) => new m.LMArenaExecutor()), // Alias
|
||
"grok-cli": () => import("./grok-cli.ts").then((m) => new m.GrokCliExecutor()),
|
||
gc: () => import("./grok-cli.ts").then((m) => new m.GrokCliExecutor()), // Alias
|
||
"codebuddy-cn": () => import("./codebuddy-cn.ts").then((m) => new m.CodeBuddyCnExecutor()),
|
||
cbcn: () => import("./codebuddy-cn.ts").then((m) => new m.CodeBuddyCnExecutor()), // Alias for codebuddy-cn
|
||
"zenmux-free": () => import("./zenmux-free.ts").then((m) => new m.ZenmuxFreeExecutor()),
|
||
"cloudflare-playground": () =>
|
||
import("./cloudflare-playground.ts").then((m) => new m.CloudflarePlaygroundExecutor()),
|
||
cfp: () =>
|
||
import("./cloudflare-playground.ts").then((m) => new m.CloudflarePlaygroundExecutor()), // Alias for cloudflare-playground
|
||
"tinycms-web": () => import("./tinycms.ts").then((m) => new m.TinyCmsExecutor()),
|
||
tcw: () => import("./tinycms.ts").then((m) => new m.TinyCmsExecutor()), // Alias
|
||
hyperagent: () => import("./hyperagent.ts").then((m) => new m.HyperAgentExecutor()),
|
||
ha: () => import("./hyperagent.ts").then((m) => new m.HyperAgentExecutor()), // Alias
|
||
zmf: () => import("./zenmux-free.ts").then((m) => new m.ZenmuxFreeExecutor()), // Alias for zenmux-free
|
||
auggie: () => import("./auggie.ts").then((m) => new m.AuggieExecutor()),
|
||
xai: () => import("./xai.ts").then((m) => new m.XaiExecutor()),
|
||
"xai-oauth": () => import("./xai.ts").then((m) => new m.XaiExecutor("xai-oauth")),
|
||
xao: () => import("./xai.ts").then((m) => new m.XaiExecutor("xai-oauth")),
|
||
"conol-web": () => import("./conol-web.ts").then((m) => new m.ConolWebExecutor()),
|
||
cnl: () => import("./conol-web.ts").then((m) => new m.ConolWebExecutor()), // Alias
|
||
};
|
||
|
||
// Bootstrap: declare every built-in alias in the ExecutorRegistry. Duplicate
|
||
// aliases fail at module load, exactly as loudly as a duplicate object key
|
||
// would have failed at lint time. Instances materialize on first use (#11220).
|
||
for (const [alias, load] of Object.entries(lazyExecutors)) {
|
||
registerLazyExecutor(alias, load);
|
||
}
|
||
|
||
const defaultCache = new Map();
|
||
|
||
// #6699 — providers that exist ONLY as Cloud Agent task-API entries
|
||
// (CLOUD_AGENT_PROVIDERS / staticModels "Available Models" catalog) and have no
|
||
// chat-completions REGISTRY entry anywhere in open-sse/. Without this guard,
|
||
// getExecutor() silently falls through to DefaultExecutor's
|
||
// `PROVIDERS[provider] || PROVIDERS.openai` fallback, sending the user's real
|
||
// provider key to OpenAI's endpoint (mislabeled as coming from the provider the
|
||
// user actually selected). Starting with just "jules" (the reported case);
|
||
// "devin" and "codex-cloud" share the same structural gap and are left for a
|
||
// follow-up once their own chat-routing behavior is confirmed.
|
||
const CHAT_UNSUPPORTED_CLOUD_AGENT_PROVIDERS = new Set(["jules"]);
|
||
|
||
// #10274 — providers that exist ONLY as /v1/search endpoint entries
|
||
// (SEARCH_PROVIDERS in open-sse/config/searchRegistry.ts) and have no chat-completions
|
||
// REGISTRY entry anywhere in open-sse/. Without this guard, getExecutor() silently falls
|
||
// through to DefaultExecutor's `PROVIDERS[provider] || PROVIDERS.openai` fallback, sending
|
||
// the user's real search API key (e.g. a Tavily `tvly-...` key) to OpenAI's endpoint and
|
||
// surfacing OpenAI's own "Incorrect API key provided" error for a provider the user believes
|
||
// is the search provider. The set is DERIVED from SEARCH_PROVIDERS so adding a new search
|
||
// provider without updating this guard fails the regression test automatically. Search
|
||
// providers must be executed through /v1/search, never the chat-completions path.
|
||
const CHAT_UNSUPPORTED_SEARCH_PROVIDERS = new Set(Object.keys(SEARCH_PROVIDERS));
|
||
|
||
export async function getExecutor(provider: string): Promise<BaseExecutor> {
|
||
assertMicrosoftDesignerWebProviderAvailable(provider);
|
||
assertRuntimeProviderAvailable(provider);
|
||
assertCommonChatGptWebProviderAvailable(provider);
|
||
const loaded = await loadRegisteredExecutor(provider);
|
||
if (loaded) return loaded;
|
||
if (CHAT_UNSUPPORTED_CLOUD_AGENT_PROVIDERS.has(provider)) {
|
||
const err = new Error(
|
||
`Provider "${provider}" is a cloud-agent provider and does not support direct chat completions; use the Cloud Agents task API instead.`
|
||
);
|
||
(err as Error & { status?: number }).status = 400;
|
||
throw err;
|
||
}
|
||
if (CHAT_UNSUPPORTED_SEARCH_PROVIDERS.has(provider)) {
|
||
const err = new Error(
|
||
`Provider "${provider}" is a search provider and does not support chat completions; use the /v1/search endpoint instead.`
|
||
);
|
||
(err as Error & { status?: number }).status = 400;
|
||
throw err;
|
||
}
|
||
if (!defaultCache.has(provider)) defaultCache.set(provider, new DefaultExecutor(provider));
|
||
return defaultCache.get(provider)!;
|
||
}
|
||
|
||
export function hasSpecializedExecutor(provider: string): boolean {
|
||
return hasRegisteredExecutor(provider);
|
||
}
|
||
|
||
export {
|
||
registerExecutor,
|
||
registerLazyExecutor,
|
||
listExecutorAliases,
|
||
} from "./registry.ts";
|
||
// Value re-export: base.ts is already eager (DefaultExecutor extends it), and
|
||
// scripts/check/check-known-symbols.ts reads this export from the module.
|
||
export { BaseExecutor } from "./base.ts";
|
||
export { DefaultExecutor } from "./default.ts";
|