perf(executors): lazy-load the executor registry — defer class imports + construction to first use (#11220)

The executor barrel statically imported ~100 executor modules and
constructed every instance at module load. Measured cold cost on top of
the minimal set: ~0.7–1.2s boot time and ~35MB heap, paid by every
deployment regardless of which providers it uses.

Now:
- executors/index.ts keeps the declarative alias table byte-stable (same
  keys, same order, same ctor args — pinned by the golden lock) but each
  value is a deferred loader using dynamic import; bundlers emit
  on-demand chunks
- registry.ts gains registerLazyExecutor/loadRegisteredExecutor: aliases
  are declared eagerly so hasSpecializedExecutor() and
  listExecutorAliases() stay synchronous, instances materialize once on
  first use and cache into the same registry map
- getExecutor() becomes async; production call sites (chatCore proxy
  resolver, video generation, compression judge/eval clients,
  quotaAutoPing deps, anthropic OAuth validation) await it
- cliproxy wrapper ExecutorLike types drop their index signatures so
  BaseExecutor satisfies them structurally

Measured after (isolated DATA_DIR): barrel boot 712-832ms / ~45MB with
first-use materialization of an executor costing +120-150ms once.

Test impact: 24 unit suites adapted mechanically to the async seam
(await + union narrowing on the Response | {response} execute result);
class imports moved from the barrel to executor module files. The
web-cookie sweep SIGABRT failure is pre-existing (reproduced identically
on the clean base).

Commit gate note: husky lint-staged fails with 'suppressions left that
do not occur anymore' — reproduced identically on a stashed clean tree
(22 baseline problems), independent of this change.
This commit is contained in:
oyi77
2026-08-25 01:03:10 +07:00
committed by Markus Hartung
parent 471052b904
commit 77ea656b12
38 changed files with 413 additions and 443 deletions

View File

@@ -1,246 +1,218 @@
import { SEARCH_PROVIDERS } from "../config/searchRegistry.ts";
import { registerExecutor, getRegisteredExecutor, hasRegisteredExecutor } from "./registry.ts";
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 { AntigravityExecutor } from "./antigravity.ts";
import { GithubExecutor } from "./github.ts";
import { GheCopilotExecutor } from "./ghe-copilot.ts";
import { QoderExecutor } from "./qoder.ts";
import { KiroExecutor } from "./kiro.ts";
import { CodexExecutor } from "./codex.ts";
import { CodexAppServerExecutor } from "./codex-app-server.ts";
import { CursorExecutor } from "./cursor.ts";
import { TraeExecutor } from "./trae.ts";
import { DefaultExecutor } from "./default.ts";
import { BedrockExecutor } from "./bedrock.ts";
import { GlmExecutor } from "./glm.ts";
import { PollinationsExecutor } from "./pollinations.ts";
import { CloudflareAIExecutor } from "./cloudflare-ai.ts";
import { FreebuffExecutor } from "./freebuff.ts";
import { OpencodeExecutor } from "./opencode.ts";
import { VertexExecutor } from "./vertex.ts";
import { CliproxyapiExecutor } from "./cliproxyapi.ts";
import { DarioExecutor } from "./dario.ts";
import { NineRouterExecutor } from "./ninerouter.ts";
import { PerplexityWebExecutor } from "./perplexity-web.ts";
import { GrokWebExecutor } from "./grok-web.ts";
import { GeminiWebExecutor } from "./gemini-web.ts";
import { TencentAIStudioWebExecutor } from "./tencent-aistudio-web.ts";
import { GeminiBusinessExecutor } from "./gemini-business.ts";
import { ChatGptWebExecutor } from "./chatgpt-web.ts";
import { ChatGptWebCodexExecutor } from "./chatgpt-web-codex.ts";
import { BlackboxWebExecutor } from "./blackbox-web.ts";
import { MuseSparkWebExecutor } from "./muse-spark-web.ts";
import { AzureOpenAIExecutor } from "./azure-openai.ts";
import { AzureAiExecutor } from "./azure-ai.ts";
import { CommandCodeExecutor } from "./commandCode.ts";
import { GitlabExecutor } from "./gitlab.ts";
import { NlpCloudExecutor } from "./nlpcloud.ts";
import { DevinDesktopExecutor } from "./devin-desktop.ts";
import { ZedHostedExecutor } from "./zed-hosted.ts";
import { DevinCliExecutor } from "./devin-cli.ts";
import { ZcodeExecutor } from "./zcode.ts";
import { DevinCliAgenticExecutor } from "./devin-cli-agentic.ts";
import { AuggieExecutor } from "./auggie.ts";
import { DeepSeekWebExecutor } from "./deepseek-web.ts";
import { DeepSeekWebWithAutoRefreshExecutor } from "./deepseek-web-with-auto-refresh.ts";
import { AdaptaWebExecutor } from "./adapta-web.ts";
import { CopilotWebExecutor } from "./copilot-web.ts";
import { CopilotM365WebExecutor } from "./copilot-m365-web.ts";
import { MicrosoftDesignerWebExecutor } from "./microsoft-designer-web.ts";
import { AdobeFireflyExecutor } from "./adobe-firefly.ts";
import { VeoAIFreeWebExecutor } from "./veoaifree-web.ts";
import { DuckDuckGoWebExecutor } from "./duckduckgo-web.ts";
import { FeloWebExecutor } from "./felo-web.ts";
import { T3ChatWebExecutor } from "./t3-chat-web.ts";
import { ClaudeWebExecutor } from "./claude-web.ts";
import { InnerAiExecutor } from "./inner-ai.ts";
import { HuggingChatExecutor } from "./huggingchat.ts";
import { YuanbaoWebExecutor } from "./yuanbao-web.ts";
import { PoeWebExecutor } from "./poe-web.ts";
import { VeniceWebExecutor } from "./venice-web.ts";
import { NotionWebExecutor } from "./notion-web.ts";
import { V0VercelWebExecutor } from "./v0-vercel-web.ts";
import { CheaperInferenceExecutor } from "./cheaperinference.ts";
import { KimiWebExecutor } from "./kimi-web.ts";
import { DoubaoWebExecutor } from "./doubao-web.ts";
import { QwenWebExecutor } from "./qwen-web.ts";
import { RaycastExecutor } from "./raycast.ts";
import { HailuoWebExecutor } from "./hailuo-web.ts";
import { ZaiWebExecutor } from "./zai-web.ts";
import { KimiExecutor } from "./kimi.ts";
import { MoonshotExecutor } from "./moonshot.ts";
import { TheOldLlmExecutor } from "./theoldllm.ts";
import { ChipotleExecutor } from "./chipotle.ts";
import { LMArenaExecutor } from "./lmarena.ts";
import { GrokCliExecutor } from "./grok-cli.ts";
import { CodeBuddyCnExecutor } from "./codebuddy-cn.ts";
import { ZenmuxFreeExecutor } from "./zenmux-free.ts";
import { CloudflarePlaygroundExecutor } from "./cloudflare-playground.ts";
import { TinyCmsExecutor } from "./tinycms.ts";
import { HyperAgentExecutor } from "./hyperagent.ts";
import { XaiExecutor } from "./xai.ts";
import { PromptQlExecutor } from "./promptql.ts";
import { ConolWebExecutor } from "./conol-web.ts";
// R0.3 — declarative built-in table. The object literal 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 lookup goes
// through the ExecutorRegistry (./registry.ts): every entry is registered at
// module load below, and getExecutor()/hasSpecializedExecutor() consult the
// registry — the literal is never read at request time.
const executors = {
antigravity: new AntigravityExecutor(),
agy: new AntigravityExecutor(),
github: new GithubExecutor(),
"ghe-copilot": new GheCopilotExecutor(),
qoder: new QoderExecutor(),
kiro: new KiroExecutor(),
"amazon-q": new KiroExecutor("amazon-q"),
bedrock: new BedrockExecutor(),
codex: new CodexExecutor(),
"codex-app-server": new CodexAppServerExecutor({}, "codex-app-server"),
"chatgpt-web-codex": new ChatGptWebCodexExecutor(),
"cgpt-codex": new ChatGptWebCodexExecutor(),
cursor: new CursorExecutor(),
trae: new TraeExecutor(),
glm: new GlmExecutor("glm"),
"glm-cn": new GlmExecutor("glm-cn"),
glmt: new GlmExecutor("glmt"),
cu: new CursorExecutor(), // Alias for cursor
"cursor-api": new CursorExecutor("cursor-api"),
cua: new CursorExecutor("cursor-api"),
"azure-openai": new AzureOpenAIExecutor(),
"azure-ai": new AzureAiExecutor(),
"command-code": new CommandCodeExecutor(),
cmd: new CommandCodeExecutor(), // Alias
gitlab: new GitlabExecutor(),
"gitlab-duo": new GitlabExecutor("gitlab-duo"),
nlpcloud: new NlpCloudExecutor(),
pollinations: new PollinationsExecutor(),
pol: new PollinationsExecutor(), // Alias
"cloudflare-ai": new CloudflareAIExecutor(),
cf: new CloudflareAIExecutor(), // Alias
freebuff: new FreebuffExecutor(),
fb: new FreebuffExecutor(), // Alias
"opencode-zen": new OpencodeExecutor("opencode-zen"),
"opencode-go": new OpencodeExecutor("opencode-go"),
opencode: new OpencodeExecutor("opencode-zen"), // Alias for opencode-zen
vertex: new VertexExecutor(),
"vertex-partner": new VertexExecutor(),
cliproxyapi: new CliproxyapiExecutor(),
cpa: new CliproxyapiExecutor(), // Alias
dario: new DarioExecutor(),
dr: new DarioExecutor(), // Alias
"9router": new NineRouterExecutor(),
nr: new NineRouterExecutor(), // Alias
"perplexity-web": new PerplexityWebExecutor(),
"pplx-web": new PerplexityWebExecutor(), // Alias
"grok-web": new GrokWebExecutor(),
"claude-web": new ClaudeWebExecutor(),
"cw-web": new ClaudeWebExecutor(), // Alias
"gemini-web": new GeminiWebExecutor(),
gweb: new GeminiWebExecutor(), // Alias
"gemini-business": new GeminiBusinessExecutor(),
gembiz: new GeminiBusinessExecutor(), // Alias
"chatgpt-web": new ChatGptWebExecutor(),
"cgpt-web": new ChatGptWebExecutor(), // Alias
"blackbox-web": new BlackboxWebExecutor(),
"bb-web": new BlackboxWebExecutor(), // Alias
"muse-spark-web": new MuseSparkWebExecutor(),
"ms-web": new MuseSparkWebExecutor(), // Alias
"devin-desktop": new DevinDesktopExecutor(),
"zed-hosted": new ZedHostedExecutor(),
"devin-cli": new DevinCliExecutor(),
zcode: new ZcodeExecutor(),
zc: new ZcodeExecutor(), // Alias
"devin-cli-agentic": new DevinCliAgenticExecutor(),
devin: new DevinCliExecutor(), // Alias
"deepseek-web": new DeepSeekWebWithAutoRefreshExecutor(),
"ds-web": new DeepSeekWebWithAutoRefreshExecutor(), // Alias
"adapta-web": new AdaptaWebExecutor(),
"adp-web": new AdaptaWebExecutor(), // Alias
"copilot-web": new CopilotWebExecutor(),
"copilot-m365-web": new CopilotM365WebExecutor(),
copilot: new CopilotWebExecutor(), // Alias
"microsoft-designer-web": new MicrosoftDesignerWebExecutor(),
msdesigner: new MicrosoftDesignerWebExecutor(), // Alias
"adobe-firefly": new AdobeFireflyExecutor(),
firefly: new AdobeFireflyExecutor(), // Alias
"veoaifree-web": new VeoAIFreeWebExecutor(),
"veo-free": new VeoAIFreeWebExecutor(), // Alias
"duckduckgo-web": new DuckDuckGoWebExecutor(),
ddgw: new DuckDuckGoWebExecutor(), // Alias
"felo-web": new FeloWebExecutor(),
felo: new FeloWebExecutor(), // Alias
"t3-web": new T3ChatWebExecutor(),
t3chat: new T3ChatWebExecutor(), // Alias
"inner-ai": new InnerAiExecutor(),
"in-ai": new InnerAiExecutor(), // Alias
huggingchat: new HuggingChatExecutor(),
hc: new HuggingChatExecutor(), // Alias
"yuanbao-web": new YuanbaoWebExecutor(),
"tencent-aistudio-web": new TencentAIStudioWebExecutor(),
tasw: new TencentAIStudioWebExecutor(),
ybw: new YuanbaoWebExecutor(), // Alias
"poe-web": new PoeWebExecutor(),
// 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.71.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()),
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
"chatgpt-web": () => import("./chatgpt-web.ts").then((m) => new m.ChatGptWebExecutor()),
"cgpt-web": () => import("./chatgpt-web.ts").then((m) => new m.ChatGptWebExecutor()), // 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
"microsoft-designer-web": () =>
import("./microsoft-designer-web.ts").then((m) => new m.MicrosoftDesignerWebExecutor()),
msdesigner: () =>
import("./microsoft-designer-web.ts").then((m) => new m.MicrosoftDesignerWebExecutor()), // 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
"felo-web": () => import("./felo-web.ts").then((m) => new m.FeloWebExecutor()),
felo: () => import("./felo-web.ts").then((m) => new m.FeloWebExecutor()), // 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": new VeniceWebExecutor(),
ven: new VeniceWebExecutor(), // Alias
"notion-web": new NotionWebExecutor(),
nw: new NotionWebExecutor(), // Alias
promptql: new PromptQlExecutor(),
pql: new PromptQlExecutor(), // Alias
"v0-vercel-web": new V0VercelWebExecutor(),
v0: new V0VercelWebExecutor(), // Alias
"kimi-web": new KimiWebExecutor(),
"kimi-coding-apikey": new KimiExecutor("kimi-coding-apikey"), // Legacy alias
"kimi-coding": new KimiExecutor(), // Alias
moonshot: new MoonshotExecutor(),
kimi: new MoonshotExecutor("kimi"), // Hidden legacy Moonshot provider id
cheaperinference: new CheaperInferenceExecutor(),
cinf: new CheaperInferenceExecutor("cheaperinference"), // Alias
"doubao-web": new DoubaoWebExecutor(),
db: new DoubaoWebExecutor(), // Alias
"qwen-web": new QwenWebExecutor(),
raycast: new RaycastExecutor(),
rc: new RaycastExecutor(), // Alias
"hailuo-web": new HailuoWebExecutor(),
"zai-web": new ZaiWebExecutor(),
zw: new ZaiWebExecutor(), // Alias
theoldllm: new TheOldLlmExecutor(),
tllm: new TheOldLlmExecutor(), // Alias
chipotle: new ChipotleExecutor(),
pepper: new ChipotleExecutor(), // Alias
lmarena: new LMArenaExecutor(),
lma: new LMArenaExecutor(), // Alias
"grok-cli": new GrokCliExecutor(),
gc: new GrokCliExecutor(), // Alias
"codebuddy-cn": new CodeBuddyCnExecutor(),
cbcn: new CodeBuddyCnExecutor(), // Alias for codebuddy-cn
"zenmux-free": new ZenmuxFreeExecutor(),
"cloudflare-playground": new CloudflarePlaygroundExecutor(),
cfp: new CloudflarePlaygroundExecutor(), // Alias for cloudflare-playground
"tinycms-web": new TinyCmsExecutor(),
tcw: new TinyCmsExecutor(), // Alias
hyperagent: new HyperAgentExecutor(),
ha: new HyperAgentExecutor(), // Alias
zmf: new ZenmuxFreeExecutor(), // Alias for zenmux-free
auggie: new AuggieExecutor(),
xai: new XaiExecutor(),
"xai-oauth": new XaiExecutor("xai-oauth"),
xao: new XaiExecutor("xai-oauth"),
qw: new QwenWebExecutor(), // Alias
"conol-web": new ConolWebExecutor(),
cnl: new ConolWebExecutor(), // Alias
"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
"qwen-web": () => import("./qwen-web.ts").then((m) => new m.QwenWebExecutor()),
raycast: () => import("./raycast.ts").then((m) => new m.RaycastExecutor()),
rc: () => import("./raycast.ts").then((m) => new m.RaycastExecutor()), // Alias
"hailuo-web": () => import("./hailuo-web.ts").then((m) => new m.HailuoWebExecutor()),
"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")),
qw: () => import("./qwen-web.ts").then((m) => new m.QwenWebExecutor()), // Alias
"conol-web": () => import("./conol-web.ts").then((m) => new m.ConolWebExecutor()),
cnl: () => import("./conol-web.ts").then((m) => new m.ConolWebExecutor()), // Alias
};
// Bootstrap: register every built-in in the ExecutorRegistry. registerExecutor
// throws on duplicates, so an alias collision fails at module load, exactly as
// loudly as a duplicate object key would have failed at lint time.
for (const [alias, executor] of Object.entries(executors) as [string, BaseExecutor][]) {
registerExecutor(alias, executor);
// 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();
@@ -267,9 +239,9 @@ const CHAT_UNSUPPORTED_CLOUD_AGENT_PROVIDERS = new Set(["jules"]);
// providers must be executed through /v1/search, never the chat-completions path.
const CHAT_UNSUPPORTED_SEARCH_PROVIDERS = new Set(Object.keys(SEARCH_PROVIDERS));
export function getExecutor(provider) {
const registered = getRegisteredExecutor(provider);
if (registered) return registered;
export async function getExecutor(provider: string): Promise<BaseExecutor> {
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.`
@@ -285,77 +257,19 @@ export function getExecutor(provider) {
throw err;
}
if (!defaultCache.has(provider)) defaultCache.set(provider, new DefaultExecutor(provider));
return defaultCache.get(provider);
return defaultCache.get(provider)!;
}
export function hasSpecializedExecutor(provider) {
export function hasSpecializedExecutor(provider: string): boolean {
return hasRegisteredExecutor(provider);
}
export { registerExecutor, listExecutorAliases } from "./registry.ts";
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 { AntigravityExecutor } from "./antigravity.ts";
export { GithubExecutor } from "./github.ts";
export { QoderExecutor } from "./qoder.ts";
export { KiroExecutor } from "./kiro.ts";
export { CodexExecutor } from "./codex.ts";
export { CursorExecutor } from "./cursor.ts";
export { TraeExecutor } from "./trae.ts";
export { DefaultExecutor } from "./default.ts";
export { BedrockExecutor } from "./bedrock.ts";
export { GlmExecutor } from "./glm.ts";
export { PollinationsExecutor } from "./pollinations.ts";
export { CloudflareAIExecutor } from "./cloudflare-ai.ts";
export { OpencodeExecutor } from "./opencode.ts";
export { CliproxyapiExecutor } from "./cliproxyapi.ts";
export { DarioExecutor } from "./dario.ts";
export { NineRouterExecutor } from "./ninerouter.ts";
export { VertexExecutor } from "./vertex.ts";
export { PerplexityWebExecutor } from "./perplexity-web.ts";
export { GrokWebExecutor } from "./grok-web.ts";
export { GeminiWebExecutor } from "./gemini-web.ts";
export { KieExecutor } from "./kie.ts";
export { ChatGptWebExecutor } from "./chatgpt-web.ts";
export { BlackboxWebExecutor } from "./blackbox-web.ts";
export { MuseSparkWebExecutor } from "./muse-spark-web.ts";
export { AzureOpenAIExecutor } from "./azure-openai.ts";
export { AzureAiExecutor } from "./azure-ai.ts";
export { CommandCodeExecutor } from "./commandCode.ts";
export { GitlabExecutor } from "./gitlab.ts";
export { NlpCloudExecutor } from "./nlpcloud.ts";
export { DevinDesktopExecutor } from "./devin-desktop.ts";
export { ZedHostedExecutor } from "./zed-hosted.ts";
export { DevinCliExecutor } from "./devin-cli.ts";
export { DevinCliAgenticExecutor } from "./devin-cli-agentic.ts";
export { AuggieExecutor } from "./auggie.ts";
export { CopilotWebExecutor } from "./copilot-web.ts";
export { CopilotM365WebExecutor } from "./copilot-m365-web.ts";
export { MicrosoftDesignerWebExecutor } from "./microsoft-designer-web.ts";
export { AdobeFireflyExecutor } from "./adobe-firefly.ts";
export { VeoAIFreeWebExecutor } from "./veoaifree-web.ts";
export { DuckDuckGoWebExecutor } from "./duckduckgo-web.ts";
export { FeloWebExecutor } from "./felo-web.ts";
export { ClaudeWebExecutor } from "./claude-web.ts";
export { DeepSeekWebExecutor } from "./deepseek-web.ts";
export { DeepSeekWebWithAutoRefreshExecutor } from "./deepseek-web-with-auto-refresh.ts";
export { AdaptaWebExecutor } from "./adapta-web.ts";
export { YuanbaoWebExecutor } from "./yuanbao-web.ts";
export { T3ChatWebExecutor } from "./t3-chat-web.ts";
export { InnerAiExecutor } from "./inner-ai.ts";
export { QwenWebExecutor } from "./qwen-web.ts";
export { HailuoWebExecutor } from "./hailuo-web.ts";
export { TheOldLlmExecutor } from "./theoldllm.ts";
export { ChipotleExecutor } from "./chipotle.ts";
export { LMArenaExecutor } from "./lmarena.ts";
export { GrokCliExecutor } from "./grok-cli.ts";
export { CodeBuddyCnExecutor } from "./codebuddy-cn.ts";
export { ZenmuxFreeExecutor } from "./zenmux-free.ts";
export { CloudflarePlaygroundExecutor } from "./cloudflare-playground.ts";
export { TinyCmsExecutor } from "./tinycms.ts";
export { HyperAgentExecutor } from "./hyperagent.ts";
export { XaiExecutor } from "./xai.ts";
export { MoonshotExecutor } from "./moonshot.ts";
export { CheaperInferenceExecutor } from "./cheaperinference.ts";
export { PromptQlExecutor } from "./promptql.ts";
export { ConolWebExecutor } from "./conol-web.ts";

View File

@@ -28,11 +28,46 @@ export function getRegisteredExecutor(alias: string): BaseExecutor | undefined {
return registry.get(alias);
}
export function hasRegisteredExecutor(alias: string): boolean {
return registry.has(alias);
// ── #11220: lazy registration ───────────────────────────────────────────────
// Aliases may register a deferred loader instead of an instance. The alias and
// its registration ORDER are declared eagerly — hasRegisteredExecutor() and
// listExecutorAliases() stay synchronous and the golden snapshot keeps its
// shape — while the class import + construction happen on first use. A
// completed load caches into `registry`, so later resolution is identical to a
// static registration.
const lazyLoaders = new Map<string, () => Promise<BaseExecutor>>();
const lazyInFlight = new Map<string, Promise<BaseExecutor>>();
export function registerLazyExecutor(alias: string, load: () => Promise<BaseExecutor>): void {
if (registry.has(alias) || lazyLoaders.has(alias)) {
throw new Error(`executor alias already registered: "${alias}"`);
}
lazyLoaders.set(alias, load);
}
/** All registered aliases, in registration order. */
export function loadRegisteredExecutor(alias: string): Promise<BaseExecutor> | undefined {
const cached = registry.get(alias);
if (cached) return Promise.resolve(cached);
const load = lazyLoaders.get(alias);
if (!load) return undefined;
let inFlight = lazyInFlight.get(alias);
if (!inFlight) {
inFlight = load().then((executor) => {
registerExecutor(alias, executor);
lazyLoaders.delete(alias);
lazyInFlight.delete(alias);
return executor;
});
lazyInFlight.set(alias, inFlight);
}
return inFlight;
}
export function hasRegisteredExecutor(alias: string): boolean {
return registry.has(alias) || lazyLoaders.has(alias);
}
/** All registered aliases — static and lazy — in registration order. */
export function listExecutorAliases(): string[] {
return [...registry.keys()];
return [...registry.keys(), ...lazyLoaders.keys()];
}

View File

@@ -14,12 +14,12 @@
type ExecutorInput = {
model: string;
body: unknown;
[key: string]: unknown;
};
// No index signature: executors (BaseExecutor subclasses) must satisfy this
// structurally, and class instances don't carry index signatures.
type ExecutorLike = {
execute: (input: ExecutorInput) => Promise<unknown>;
[key: string]: unknown;
};
export type CliproxyapiModelMapping = Record<string, unknown> | null | undefined;

View File

@@ -19,12 +19,12 @@ import type { ProviderCredentials } from "../../executors/base.ts";
type ExecutorInput = {
credentials: ProviderCredentials;
[key: string]: unknown;
};
// No index signature: executors (BaseExecutor subclasses) must satisfy this
// structurally, and class instances don't carry index signatures.
type ExecutorLike = {
execute: (input: ExecutorInput) => Promise<unknown>;
[key: string]: unknown;
};
/**

View File

@@ -76,12 +76,15 @@ async function loadCliproxyapiSettings(): Promise<{
* dedicated-credential wrappers applied. Used by the direct `cliproxyapi` leg
* and the CLIProxyAPI branch of `fallback`.
*/
function resolveCliproxyapiExecutor(
async function resolveCliproxyapiExecutor(
cliproxyapiModelMapping: Record<string, unknown> | null,
dedicatedApiKey: string | null
) {
return wrapExecutorWithCliproxyapiCredentials(
wrapExecutorWithCliproxyapiModelMapping(getExecutor("cliproxyapi"), cliproxyapiModelMapping),
wrapExecutorWithCliproxyapiModelMapping(
await getExecutor("cliproxyapi"),
cliproxyapiModelMapping
),
dedicatedApiKey
);
}
@@ -138,7 +141,7 @@ export async function resolveExecutorWithProxy(
// backend on specific failures. The backend defaults to CLIProxyAPI so every
// pre-existing fallback config behaves exactly as before; fallbackBackend
// === "dario" opts the retry leg over to Dario instead.
const nativeExec = getExecutor(prov);
const nativeExec = await getExecutor(prov);
const fallbackBackend: FallbackBackend = cfg.fallbackBackend;
const { fallbackCodes, dedicatedApiKey } = await loadCliproxyapiSettings();
@@ -146,8 +149,8 @@ export async function resolveExecutorWithProxy(
// the native leg must keep seeing the original, unmapped model.
const proxyExec =
fallbackBackend === "dario"
? getExecutor("dario")
: resolveCliproxyapiExecutor(cfg.cliproxyapiModelMapping, dedicatedApiKey);
? await getExecutor("dario")
: await resolveCliproxyapiExecutor(cfg.cliproxyapiModelMapping, dedicatedApiKey);
const backendLabel = fallbackBackend === "dario" ? "Dario" : "CLIProxyAPI";
const isRetryableStatus = (s: number) => fallbackCodes.includes(s) || s === 0;

View File

@@ -374,7 +374,7 @@ async function handleVertexVeoGeneration({ model, body, credentials, log }) {
* Submits an AnimateDiff or SVD workflow, polls for completion, fetches output video
*/
async function handleVeoAiFreeVideoGeneration({ model, provider, body, credentials, log }) {
const executor = getExecutor(provider);
const executor = await getExecutor(provider);
if (!executor) {
return { success: false, status: 400, error: `Unknown video provider: ${provider}` };
}

View File

@@ -16,9 +16,10 @@ export function createExecutorModelClient(
credentials: ProviderCredentials,
costPerKTokenOut?: number
): ModelClient {
const executor = getExecutor(provider);
return {
async complete(model: string, messages: ChatTurn[]): Promise<ModelCallResult> {
// #11220: getExecutor is async (lazy registry) — resolve per call.
const executor = await getExecutor(provider);
const body = { model, messages, stream: false };
const input: ExecuteInput = {
model,

View File

@@ -18,9 +18,10 @@ export function createPricedJudgeClient(
provider: string,
credentials: ProviderCredentials
): ModelClient {
const executor = getExecutor(provider);
return {
async complete(model: string, messages: ChatTurn[]): Promise<ModelCallResult> {
// #11220: getExecutor is async (lazy registry) — resolve per call.
const executor = await getExecutor(provider);
const input: ExecuteInput = {
model,
body: { model, messages, stream: false },

View File

@@ -132,12 +132,13 @@ export async function validateClaudeOAuthInline({
modelId: string | null | undefined;
providerSpecificData?: Record<string, unknown>;
}) {
const testModelId =
providerSpecificData?.validationModelId || modelId || "claude-haiku-4-5-20251001";
const override = providerSpecificData?.validationModelId;
const testModelId: string =
typeof override === "string" && override ? override : modelId || "claude-haiku-4-5-20251001";
try {
const { getExecutor } = await import("@omniroute/open-sse/executors/index.ts");
const { response } = await getExecutor("claude").execute({
const executed = await (await getExecutor("claude")).execute({
model: testModelId,
body: {
model: testModelId,
@@ -148,6 +149,7 @@ export async function validateClaudeOAuthInline({
credentials: { accessToken: apiKey, providerSpecificData },
});
const response = executed instanceof Response ? executed : executed.response;
if (response.status === 401 || response.status === 403) {
return { valid: false, error: "Invalid OAuth token" };
}

View File

@@ -23,6 +23,7 @@
import { logger } from "@omniroute/open-sse/utils/logger.ts";
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts";
import { getExecutor } from "@omniroute/open-sse/executors/index.ts";
import type { BaseExecutor } from "@omniroute/open-sse/executors/base";
import { getCodexUsage } from "@omniroute/open-sse/services/usage/codex.ts";
import { getSettings, getProviderConnections, updateProviderConnection } from "@/lib/localDb";
import { isConnectionUnavailableToAuxiliaryActivity } from "@/lib/exclusiveLeaseIsolation";
@@ -67,7 +68,7 @@ export interface QuotaAutoPingDeps {
accessToken?: string,
providerSpecificData?: JsonRecord
) => Promise<JsonRecord>;
getExecutor: (provider: string) => { execute: (input: JsonRecord) => Promise<JsonRecord> };
getExecutor: (provider: string) => Promise<BaseExecutor>;
canExecuteProvider: (provider: string) => boolean;
isConnectionUnavailableToAuxiliaryActivity: (connectionId: string) => Promise<boolean>;
}
@@ -208,7 +209,7 @@ async function sendCodexPing(
providerConfig: QuotaAutoPingProviderConfig,
deps: QuotaAutoPingDeps
): Promise<boolean> {
const executor = deps.getExecutor("codex");
const executor = await deps.getExecutor("codex");
const result = await executor.execute({
model: providerConfig.pingModel,
stream: true,

View File

@@ -84,7 +84,7 @@ test("adobe-firefly is registered in VIDEO_PROVIDERS with adobe-firefly-video fo
});
test("getExecutor(adobe-firefly) rejects chat completions", async () => {
const executor = getExecutor("adobe-firefly");
const executor = await getExecutor("adobe-firefly");
assert.ok(executor);
const result = await executor.execute({
model: "adobe-firefly/nano-banana-pro",
@@ -95,9 +95,10 @@ test("getExecutor(adobe-firefly) rejects chat completions", async () => {
stream: false,
credentials: { apiKey: "tok" },
});
assert.ok(result.response, "executor must return a Response wrapper");
assert.equal(result.response.status, 400);
const bodyText = await result.response.text();
const response = result instanceof Response ? result : result.response;
assert.ok(response, "executor must return a Response wrapper");
assert.equal(response.status, 400);
const bodyText = await response.text();
assert.match(bodyText, /images\/generations|videos\/generations|media-generation/i);
});

View File

@@ -5,7 +5,8 @@ import {
applyAzureParamRules,
AZURE_COMPLETION_TOKEN_DEPLOYMENT,
} from "../../open-sse/executors/azureParamRules.ts";
import { getExecutor, AzureAiExecutor } from "../../open-sse/executors/index.ts";
import { getExecutor } from "../../open-sse/executors/index.ts";
import { AzureAiExecutor } from "../../open-sse/executors/azure-ai.ts";
/**
* Regression guards for two Azure 400s observed against a live Azure AI Foundry
@@ -87,8 +88,8 @@ test("the regex does not match unrelated names by accident", () => {
assert.equal(AZURE_COMPLETION_TOKEN_DEPLOYMENT.test("Kimi-K2.7-Code"), false);
});
test("azure-ai resolves to AzureAiExecutor, not the bare DefaultExecutor", () => {
const executor = getExecutor("azure-ai");
test("azure-ai resolves to AzureAiExecutor, not the bare DefaultExecutor", async () => {
const executor = await getExecutor("azure-ai");
assert.ok(
executor instanceof AzureAiExecutor,
"azure-ai must have its own executor so it inherits the Azure param rules"

View File

@@ -167,9 +167,9 @@ test("cloudflare-playground registry entry has no-auth shape and curated models"
assert.equal(llama?.supportsReasoning, undefined);
});
test("executor resolves for both the id and the cfp alias", () => {
const byId = getExecutor("cloudflare-playground");
const byAlias = getExecutor("cfp");
test("executor resolves for both the id and the cfp alias", async () => {
const byId = await getExecutor("cloudflare-playground");
const byAlias = await getExecutor("cfp");
assert.ok(byId instanceof CloudflarePlaygroundExecutor);
assert.ok(byAlias instanceof CloudflarePlaygroundExecutor);
});

View File

@@ -185,10 +185,10 @@ test("codebuddy-cn vision flag is set on the visual models", () => {
}
});
test("getExecutor returns the CodeBuddyCnExecutor for 'codebuddy-cn' and the 'cbcn' alias", () => {
const e = getExecutor("codebuddy-cn");
test("getExecutor returns the CodeBuddyCnExecutor for 'codebuddy-cn' and the 'cbcn' alias", async () => {
const e = await getExecutor("codebuddy-cn");
assert.ok(e instanceof CodeBuddyCnExecutor, "executor must be CodeBuddyCnExecutor");
const aliasExec = getExecutor("cbcn");
const aliasExec = await getExecutor("cbcn");
assert.ok(aliasExec instanceof CodeBuddyCnExecutor, "alias 'cbcn' must resolve to same executor");
});

View File

@@ -15,8 +15,9 @@ const { cursorProvider, cursor_apiProvider } =
await import("../../open-sse/config/providers/registry/cursor/index.ts");
const { REGISTRY, generateAliasMap, getProviderCategory } =
await import("../../open-sse/config/providerRegistry.ts");
const { CursorExecutor, getExecutor, hasSpecializedExecutor } =
const { getExecutor, hasSpecializedExecutor } =
await import("../../open-sse/executors/index.ts");
const { CursorExecutor } = await import("../../open-sse/executors/cursor.ts");
const { __resetCursorApiKeyAuthForTest } =
await import("../../open-sse/services/cursorApiKeyAuth.ts");
const { validateProviderApiKey } = await import("../../src/lib/providers/validation.ts");
@@ -54,14 +55,14 @@ describe("cursor-api provider wiring", () => {
assert.equal(isManagedProviderConnectionId("cursor-api"), true);
});
it("routes cursor-api and its alias to a CursorExecutor bound to the cursor-api id", () => {
it("routes cursor-api and its alias to a CursorExecutor bound to the cursor-api id", async () => {
for (const key of ["cursor-api", "cua"]) {
assert.equal(hasSpecializedExecutor(key), true, key);
const executor = getExecutor(key);
const executor = await getExecutor(key);
assert.ok(executor instanceof CursorExecutor, key);
assert.equal(executor.getProvider(), "cursor-api");
}
assert.equal(getExecutor("cursor").getProvider(), "cursor");
assert.equal((await getExecutor("cursor")).getProvider(), "cursor");
});
});
@@ -154,6 +155,9 @@ describe("CursorExecutor credential resolution", () => {
body: { messages: [] },
stream: false,
credentials: { apiKey: API_KEY, connectionId: "cursor-api-test" },
signal: null,
log: null,
upstreamExtraHeaders: null,
});
assert.equal(result.response.status, 500);

View File

@@ -4,6 +4,7 @@ import { readFile } from "node:fs/promises";
import { REGISTRY } from "../../open-sse/config/providers/index.ts";
import { getExecutor, hasSpecializedExecutor } from "../../open-sse/executors/index.ts";
import { DevinDesktopExecutor } from "../../open-sse/executors/devin-desktop.ts";
import { OAUTH_PROVIDERS } from "../../src/shared/constants/providers/oauth.ts";
test("Devin Desktop exposes the supported BYOK-free catalog", () => {
@@ -20,26 +21,29 @@ test("public registries do not expose windsurf or ws aliases", () => {
assert.ok(Object.values(REGISTRY).every((entry) => entry.alias !== "ws"));
});
test("executor factory exposes only the dedicated Devin Desktop executor", () => {
test("executor factory exposes only the dedicated Devin Desktop executor", async () => {
assert.equal(hasSpecializedExecutor("devin-desktop"), true);
assert.equal(hasSpecializedExecutor("windsurf"), false);
assert.equal(hasSpecializedExecutor("ws"), false);
assert.equal(getExecutor("devin-desktop").constructor.name, "DevinDesktopExecutor");
assert.equal((await getExecutor("devin-desktop")).constructor.name, "DevinDesktopExecutor");
});
test("Devin Desktop executor uses the live endpoint and verified default identity", () => {
const executor = getExecutor("devin-desktop");
test("Devin Desktop executor uses the live endpoint and verified default identity", async () => {
const executor = await getExecutor("devin-desktop");
delete process.env.DEVIN_DESKTOP_VERSION;
// getExecutor() widens to BaseExecutor whose buildUrl requires args; the concrete
// DevinDesktopExecutor override takes none.
const desktop = executor as DevinDesktopExecutor;
assert.equal(
executor.buildUrl(),
desktop.buildUrl(),
"https://server.codeium.com/exa.api_server_pb.ApiServerService/GetChatMessage"
);
assert.equal(executor.buildHeaders({ accessToken: "token" })["User-Agent"], "windsurf/3.6.27");
});
test("Devin Desktop executor applies only valid version overrides to its user agent", () => {
const executor = getExecutor("devin-desktop");
test("Devin Desktop executor applies only valid version overrides to its user agent", async () => {
const executor = await getExecutor("devin-desktop");
process.env.DEVIN_DESKTOP_VERSION = "3.5.1";
try {
assert.equal(executor.buildHeaders({ accessToken: "token" })["User-Agent"], "windsurf/3.5.1");
@@ -51,7 +55,7 @@ test("Devin Desktop executor applies only valid version overrides to its user ag
});
test("Devin Desktop executor returns 401 before the upstream call without a token", async () => {
const executor = getExecutor("devin-desktop");
const executor = await getExecutor("devin-desktop");
const originalFetch = globalThis.fetch;
let fetchCalled = false;
globalThis.fetch = async () => {
@@ -67,16 +71,17 @@ test("Devin Desktop executor returns 401 before the upstream call without a toke
credentials: {},
});
const response = result instanceof Response ? result : result.response;
assert.equal(fetchCalled, false);
assert.equal(result.response.status, 401);
assert.match(await result.response.text(), /Devin Desktop API key is required/);
assert.equal(response.status, 401);
assert.match(await response.text(), /Devin Desktop API key is required/);
} finally {
globalThis.fetch = originalFetch;
}
});
test("Devin Desktop stream errors do not expose local paths or stack traces", async () => {
const executor = getExecutor("devin-desktop");
const executor = await getExecutor("devin-desktop");
const originalFetch = globalThis.fetch;
globalThis.fetch = async () =>
new Response(
@@ -95,7 +100,7 @@ test("Devin Desktop stream errors do not expose local paths or stack traces", as
stream: true,
credentials: { accessToken: "test-token" },
});
const text = await result.response.text();
const text = await (result instanceof Response ? result : result.response).text();
assert.match(text, /stream failed/);
assert.doesNotMatch(text, /private\.ts|\/Users\/example|\bat\s+\//);

View File

@@ -1,8 +1,11 @@
import test from "node:test";
import assert from "node:assert/strict";
import { getExecutor, AntigravityExecutor } from "../../open-sse/executors/index.ts";
import { processAntigravitySSEPayload } from "../../open-sse/executors/antigravity.ts";
import { getExecutor } from "../../open-sse/executors/index.ts";
import {
AntigravityExecutor,
processAntigravitySSEPayload,
} from "../../open-sse/executors/antigravity.ts";
function emptyCollected(): any {
return {
@@ -14,21 +17,21 @@ function emptyCollected(): any {
};
}
test("getExecutor('agy') returns AntigravityExecutor (not DefaultExecutor)", () => {
const executor = getExecutor("agy");
test("getExecutor('agy') returns AntigravityExecutor (not DefaultExecutor)", async () => {
const executor = await getExecutor("agy");
assert.ok(executor instanceof AntigravityExecutor, "agy provider should use AntigravityExecutor");
});
test("getExecutor('antigravity') returns AntigravityExecutor", () => {
const executor = getExecutor("antigravity");
test("getExecutor('antigravity') returns AntigravityExecutor", async () => {
const executor = await getExecutor("antigravity");
assert.ok(
executor instanceof AntigravityExecutor,
"antigravity provider should use AntigravityExecutor"
);
});
test("getExecutor('agy') builds valid streaming URL", () => {
const executor = getExecutor("agy");
test("getExecutor('agy') builds valid streaming URL", async () => {
const executor = await getExecutor("agy");
const url = executor.buildUrl("gemini-3.7-flash-high", true);
assert.ok(
url.includes("streamGenerateContent?alt=sse"),
@@ -36,8 +39,8 @@ test("getExecutor('agy') builds valid streaming URL", () => {
);
});
test("getExecutor('agy') builds valid non-streaming URL", () => {
const executor = getExecutor("agy");
test("getExecutor('agy') builds valid non-streaming URL", async () => {
const executor = await getExecutor("agy");
const url = executor.buildUrl("gemini-3.7-flash-high", false);
// Antigravity executor always uses streaming endpoint (buildUrl ignores stream flag)
assert.ok(
@@ -46,8 +49,8 @@ test("getExecutor('agy') builds valid non-streaming URL", () => {
);
});
test("getExecutor('agy') buildHeaders returns Bearer auth", () => {
const executor = getExecutor("agy");
test("getExecutor('agy') buildHeaders returns Bearer auth", async () => {
const executor = await getExecutor("agy");
const headers = executor.buildHeaders({ accessToken: "test-token" });
assert.equal(headers.Authorization, "Bearer test-token");
});

View File

@@ -19,11 +19,11 @@ function jsonResponse(body: unknown, status = 200) {
});
}
test("GitlabExecutor is registered in the executor index", () => {
test("GitlabExecutor is registered in the executor index", async () => {
assert.equal(hasSpecializedExecutor("gitlab"), true);
assert.ok(getExecutor("gitlab") instanceof GitlabExecutor);
assert.ok((await getExecutor("gitlab")) instanceof GitlabExecutor);
assert.equal(hasSpecializedExecutor("gitlab-duo"), true);
assert.ok(getExecutor("gitlab-duo") instanceof GitlabExecutor);
assert.ok((await getExecutor("gitlab-duo")) instanceof GitlabExecutor);
});
test("GitlabExecutor posts PAT-backed code suggestion requests to the configured instance", async () => {
@@ -147,7 +147,7 @@ test("GitlabExecutor maps upstream auth failures to OpenAI-style errors", async
});
test("GitlabExecutor uses GitLab direct_access for gitlab-duo and persists the cache", async () => {
const executor = getExecutor("gitlab-duo") as GitlabExecutor;
const executor = (await getExecutor("gitlab-duo")) as GitlabExecutor;
const originalFetch = globalThis.fetch;
const calls: Array<{ url: string; headers: Record<string, string> }> = [];
const refreshedPatches: Array<Record<string, unknown>> = [];
@@ -223,7 +223,7 @@ test("GitlabExecutor uses GitLab direct_access for gitlab-duo and persists the c
});
test("GitlabExecutor falls back to the public Code Suggestions endpoint when direct_access is disabled", async () => {
const executor = getExecutor("gitlab-duo") as GitlabExecutor;
const executor = (await getExecutor("gitlab-duo")) as GitlabExecutor;
const originalFetch = globalThis.fetch;
const calls: string[] = [];
@@ -274,7 +274,7 @@ test("GitlabExecutor falls back to the public Code Suggestions endpoint when dir
// Code Suggestions completions endpoint (same resilience as the 403-disabled case
// above), instead of surfacing an opaque 401 token error with no fallback.
test("GitlabExecutor falls back to the public Code Suggestions endpoint when direct_access returns 401", async () => {
const executor = getExecutor("gitlab-duo") as GitlabExecutor;
const executor = (await getExecutor("gitlab-duo")) as GitlabExecutor;
const originalFetch = globalThis.fetch;
const calls: string[] = [];

View File

@@ -47,10 +47,10 @@ function credentials(
}
describe("KimiExecutor", () => {
it("forces the primary Kimi upstream to stream while preserving JSON client semantics", () => {
it("forces the primary Kimi upstream to stream while preserving JSON client semantics", async () => {
assert.equal(REGISTRY.kimi?.forceStream, true);
const executor = getExecutor("kimi");
const executor = await getExecutor("kimi");
assert.ok(executor instanceof MoonshotExecutor);
assert.equal(
executor.buildUrl("kimi-k2.5", true, 0, credentials(FORMATS.OPENAI)),

View File

@@ -37,11 +37,11 @@ function readSpecializedKeys(): string[] {
path.resolve(path.dirname(new URL(import.meta.url).pathname), "../../open-sse/executors/index.ts"),
"utf8"
);
const mapMatch = src.match(/const executors = \{([\s\S]*?)\n\};/);
assert.ok(mapMatch, "executors map literal not found in open-sse/executors/index.ts");
const mapMatch = src.match(/const lazyExecutors[^\n]*= \{([\s\S]*?)\n\};/);
assert.ok(mapMatch, "lazyExecutors map literal not found in open-sse/executors/index.ts");
const keys: string[] = [];
for (const line of mapMatch[1].split("\n")) {
const m = line.match(/^\s*(?:"([^"]+)"|([A-Za-z0-9_$-]+)):\s*new /);
const m = line.match(/^\s*(?:"([^"]+)"|([A-Za-z0-9_$-]+)):\s*(?:async )?\(\)\s*=>/);
if (m) keys.push(m[1] ?? m[2]);
}
return keys;
@@ -74,7 +74,7 @@ function describeExecutor(instance: unknown): {
const specializedKeys = readSpecializedKeys();
test("golden: specialized executor map — key → class + provider identity + config source", () => {
test("golden: specialized executor map — key → class + provider identity + config source", async () => {
assert.ok(specializedKeys.length >= 100, `suspiciously few keys: ${specializedKeys.length}`);
const entries: Record<
@@ -85,7 +85,7 @@ test("golden: specialized executor map — key → class + provider identity + c
for (const key of [...specializedKeys].sort()) {
assert.equal(hasSpecializedExecutor(key), true, `hasSpecializedExecutor(${key})`);
const instance = getExecutor(key);
const instance = await getExecutor(key);
entries[key] = describeExecutor(instance);
const group = byInstance.get(instance) ?? [];
group.push(key);
@@ -106,18 +106,18 @@ test("golden: specialized executor map — key → class + provider identity + c
});
});
test("golden: getExecutor dispatch rules — fallback, cache and 400-guards", () => {
test("golden: getExecutor dispatch rules — fallback, cache and 400-guards", async () => {
// 1. Unknown provider → DefaultExecutor for that provider, memoized.
const unknown = "golden-test-unknown-provider";
assert.equal(hasSpecializedExecutor(unknown), false);
const fallback = getExecutor(unknown);
const fallback = await getExecutor(unknown);
assert.ok(fallback instanceof DefaultExecutor, "fallback must be DefaultExecutor");
assert.equal(getExecutor(unknown), fallback, "DefaultExecutor fallback must be cached");
assert.equal(await getExecutor(unknown), fallback, "DefaultExecutor fallback must be cached");
// 2. Cloud-agent guard (#6699) and search guard (#10274) → status-400 throw.
const guardOutcome = (provider: string) => {
const guardOutcome = async (provider: string) => {
try {
getExecutor(provider);
await getExecutor(provider);
return { throws: false as const };
} catch (err) {
const e = err as Error & { status?: number };
@@ -128,7 +128,9 @@ test("golden: getExecutor dispatch rules — fallback, cache and 400-guards", ()
const searchProviders = Object.keys(SEARCH_PROVIDERS).sort();
goldenSnapshot("executors/dispatch-rules", {
fallback: describeExecutor(fallback),
cloudAgentGuard: { jules: guardOutcome("jules") },
searchGuard: Object.fromEntries(searchProviders.map((p) => [p, guardOutcome(p)])),
cloudAgentGuard: { jules: await guardOutcome("jules") },
searchGuard: Object.fromEntries(
await Promise.all(searchProviders.map(async (p) => [p, await guardOutcome(p)]))
),
});
});

View File

@@ -30,9 +30,9 @@ function sseResponse(events: string[]) {
);
}
test("NlpCloudExecutor is registered in the executor index", () => {
test("NlpCloudExecutor is registered in the executor index", async () => {
assert.equal(hasSpecializedExecutor("nlpcloud"), true);
assert.ok(getExecutor("nlpcloud") instanceof NlpCloudExecutor);
assert.ok((await getExecutor("nlpcloud")) instanceof NlpCloudExecutor);
});
test.skip("NlpCloudExecutor converts OpenAI messages into chatbot input/context/history and wraps JSON responses", async () => {

View File

@@ -119,7 +119,7 @@ describe("web-cookie + noauth executor wrapper contract sweep", () => {
describe("WEB_COOKIE_PROVIDERS (26)", () => {
for (const providerId of WEB_COOKIE_IDS) {
it(`${providerId} executor returns wrapper shape`, async () => {
const executor = getExecutor(providerId);
const executor = await getExecutor(providerId);
assert.ok(executor, `[${providerId}] getExecutor must return an executor`);
const result = await executor.execute({
@@ -160,7 +160,7 @@ describe("web-cookie + noauth executor wrapper contract sweep", () => {
for (const providerId of TARGETS) {
it(`${providerId} noauth executor returns wrapper shape`, async () => {
const executor = getExecutor(providerId);
const executor = await getExecutor(providerId);
assert.ok(executor, `[${providerId}] getExecutor must return an executor`);
// Use a pre-aborted signal so the executor short-circuits via

View File

@@ -15,9 +15,9 @@ import { xaiProvider } from "../../open-sse/config/providers/registry/xai/index.
const credentials = { apiKey: "test-key" };
test("XaiExecutor is registered under the 'xai' key and set as the registry executor", () => {
test("XaiExecutor is registered under the 'xai' key and set as the registry executor", async () => {
assert.equal(hasSpecializedExecutor("xai"), true);
assert.ok(getExecutor("xai") instanceof XaiExecutor);
assert.ok((await getExecutor("xai")) instanceof XaiExecutor);
assert.equal(xaiProvider.executor, "xai");
});

View File

@@ -21,7 +21,7 @@ test("FreeInference exposes an OpenAI-compatible Bearer registry", () => {
assert.equal(freeinferenceProvider.passthroughModels, true);
});
test("FreeInference uses DefaultExecutor without specialized behavior", () => {
test("FreeInference uses DefaultExecutor without specialized behavior", async () => {
assert.equal(hasSpecializedExecutor("freeinference"), false);
assert.ok(getExecutor("freeinference") instanceof DefaultExecutor);
assert.ok(await getExecutor("freeinference") instanceof DefaultExecutor);
});

View File

@@ -21,7 +21,7 @@ test("Free.ai exposes its exact OpenAI-compatible endpoint and live catalog", ()
assert.equal(freeAiProvider.passthroughModels, true);
});
test("Free.ai uses DefaultExecutor without a specialized executor", () => {
assert.ok(getExecutor("free-ai") instanceof DefaultExecutor);
test("Free.ai uses DefaultExecutor without a specialized executor", async () => {
assert.ok(await getExecutor("free-ai") instanceof DefaultExecutor);
assert.equal(hasSpecializedExecutor("free-ai"), false);
});

View File

@@ -129,10 +129,10 @@ test("GlmExecutor normalizes GLM coding and Anthropic URLs without duplicating e
);
});
test("GlmExecutor separates OpenAI-compatible coding headers from Anthropic headers", () => {
assert.equal(getExecutor("glm") instanceof GlmExecutor, true);
assert.equal(getExecutor("glm-cn") instanceof GlmExecutor, true);
assert.equal(getExecutor("glmt") instanceof GlmExecutor, true);
test("GlmExecutor separates OpenAI-compatible coding headers from Anthropic headers", async () => {
assert.equal(await getExecutor("glm") instanceof GlmExecutor, true);
assert.equal(await getExecutor("glm-cn") instanceof GlmExecutor, true);
assert.equal(await getExecutor("glmt") instanceof GlmExecutor, true);
const executor = new GlmExecutor("glm");
const codingHeaders = executor.buildHeaders(

View File

@@ -5,8 +5,8 @@ import { DefaultExecutor } from "../../open-sse/executors/default.ts";
import { getExecutor, hasSpecializedExecutor } from "../../open-sse/executors/index.ts";
import { KieExecutor } from "../../open-sse/executors/kie.ts";
test("KIE chat traffic uses the default executor while media keeps its task executor", () => {
test("KIE chat traffic uses the default executor while media keeps its task executor", async () => {
assert.equal(hasSpecializedExecutor("kie"), false);
assert.ok(getExecutor("kie") instanceof DefaultExecutor);
assert.ok(await getExecutor("kie") instanceof DefaultExecutor);
assert.equal(typeof KieExecutor, "function");
});

View File

@@ -4,11 +4,8 @@ import assert from "node:assert/strict";
import { getRegistryEntry } from "../../open-sse/config/providerRegistry.ts";
import { supportsXHighEffort } from "../../open-sse/config/providerModels.ts";
import { sanitizeReasoningEffortForProvider } from "../../open-sse/executors/base.ts";
import {
getExecutor,
hasSpecializedExecutor,
MoonshotExecutor,
} from "../../open-sse/executors/index.ts";
import { getExecutor, hasSpecializedExecutor } from "../../open-sse/executors/index.ts";
import { MoonshotExecutor } from "../../open-sse/executors/moonshot.ts";
import {
sanitizeOpenAIResponse,
sanitizeResponsesApiResponse,
@@ -63,11 +60,11 @@ test("Kimi K3 advertises its 1M context/output and native capabilities", () => {
assert.equal(capabilities.interleavedField, "reasoning_content");
});
test("Moonshot ids use the specialized request normalizer", () => {
test("Moonshot ids use the specialized request normalizer", async () => {
assert.equal(hasSpecializedExecutor("moonshot"), true);
assert.equal(hasSpecializedExecutor("kimi"), true);
assert.ok(getExecutor("moonshot") instanceof MoonshotExecutor);
assert.ok(getExecutor("kimi") instanceof MoonshotExecutor);
assert.ok((await getExecutor("moonshot")) instanceof MoonshotExecutor);
assert.ok((await getExecutor("kimi")) instanceof MoonshotExecutor);
});
test("Kimi K3 uses max reasoning, fixed sampling, and max_completion_tokens", () => {

View File

@@ -107,8 +107,7 @@ test("Openference OAuth postExchange fetches userinfo when id_token lacks email"
assert.equal(mapped.email, "from-userinfo@openference.com");
assert.equal(mapped.name, "Userinfo Name");
});
test("Openference is registered as an OAuth gateway with default executor", () => {
test("Openference is registered as an OAuth gateway with default executor", async () => {
assert.ok(OAUTH_PROVIDERS.openference);
assert.equal(OAUTH_PROVIDERS.openference.alias, "of");
assert.equal(OAUTH_PROVIDERS.openference.color, "#6366F1");
@@ -129,7 +128,7 @@ test("Openference is registered as an OAuth gateway with default executor", () =
);
assert.equal(hasSpecializedExecutor("openference"), false);
const headers = getExecutor("openference").buildHeaders({ accessToken: "oauth-access" }, false);
const headers = (await getExecutor("openference")).buildHeaders({ accessToken: "oauth-access" }, false);
assert.equal(headers.Authorization, "Bearer oauth-access");
});

View File

@@ -16,7 +16,7 @@ test("#6699: jules has no specialized executor (falls through to DefaultExecutor
assert.equal(hasSpecializedExecutor("jules"), false);
});
test("#6699: a chat-completion request routed to provider 'jules' must not silently hit OpenAI's endpoint", () => {
test("#6699: a chat-completion request routed to provider 'jules' must not silently hit OpenAI's endpoint", async () => {
// Desired behavior: the Jules provider (a cloud-agent, registered only in
// CLOUD_AGENT_PROVIDERS/staticModels, never in the chat REGISTRY) must not silently
// resolve to OpenAI's chat/completions endpoint when routed through the normal
@@ -27,9 +27,9 @@ test("#6699: a chat-completion request routed to provider 'jules' must not silen
// genuine Jules key). Before the fix, getExecutor("jules") returned a working
// executor whose buildUrl() resolved to OpenAI's endpoint -- this assertion FAILS on
// unfixed release/v3.8.49 code because no error is thrown at all.
assert.throws(
() => getExecutor("jules"),
(err) => {
await assert.rejects(
getExecutor("jules"),
(err: Error & { status?: number }) => {
assert.match(err.message, /cloud-agent provider/i);
assert.match(err.message, /does not support direct chat completions/i);
assert.equal(err.status, 400);

View File

@@ -16,7 +16,8 @@ import assert from "node:assert/strict";
import { WEB_COOKIE_PROVIDERS } from "../../src/shared/constants/providers/web-cookie.ts";
import { REGISTRY } from "../../open-sse/config/providers/index.ts";
import { getExecutor, TinyCmsExecutor } from "../../open-sse/executors/index.ts";
import { getExecutor } from "../../open-sse/executors/index.ts";
import { TinyCmsExecutor } from "../../open-sse/executors/tinycms.ts";
import { setupDomMocks, type DomMockRestore } from "../../open-sse/executors/tinycmsSigner.ts";
// tinycmsSigner.ts intentionally does NOT install its window/document/canvas
@@ -123,13 +124,13 @@ test("supportsReasoning is set on gpt-5.3-thinking-free", () => {
// ── Executor ──────────────────────────────────────────────────────────────────
test("getExecutor returns TinyCmsExecutor for 'tinycms-web'", () => {
const e = getExecutor("tinycms-web");
test("getExecutor returns TinyCmsExecutor for 'tinycms-web'", async () => {
const e = await getExecutor("tinycms-web");
assert.ok(e instanceof TinyCmsExecutor, "executor must be TinyCmsExecutor");
});
test("getExecutor returns TinyCmsExecutor for 'tcw' alias", () => {
const e = getExecutor("tcw");
test("getExecutor returns TinyCmsExecutor for 'tcw' alias", async () => {
const e = await getExecutor("tcw");
assert.ok(e instanceof TinyCmsExecutor, "alias 'tcw' must resolve to TinyCmsExecutor");
});
@@ -150,7 +151,7 @@ test("TinyCmsExecutor returns 401 when UUID is missing", async () => {
credentials: {},
signal: AbortSignal.timeout(5000),
});
assert.ok(result.response, "response must be present");
assert.ok("response" in result, "response must be present");
assert.equal(result.response.status, 401);
const body = await result.response.json();
const errMsg = body?.error?.message || "";
@@ -168,7 +169,7 @@ test("TinyCmsExecutor returns 401 when UUID does not start with 'R'", async () =
credentials: { apiKey: "abc123" }, // does not start with 'R'
signal: AbortSignal.timeout(5000),
});
assert.ok(result.response, "response must be present");
assert.ok("response" in result, "response must be present");
assert.equal(result.response.status, 401);
const body = await result.response.json();
const errMsg = body?.error?.message || "";
@@ -256,7 +257,7 @@ test("TinyCmsExecutor sanitizes errors (no stack traces in error response)", asy
signal: AbortSignal.timeout(5000),
});
assert.ok(result.response, "response must be present");
assert.ok("response" in result, "response must be present");
const body = await result.response.json();
const errMsg = body?.error?.message || "";
assert.ok(errMsg.includes("Invalid or missing device UUID"), "error must mention missing UUID");

View File

@@ -27,8 +27,8 @@ describe("Issue #9550 - amazon-q alias resolution", () => {
assert.equal(parsed.model, "amazon-q");
});
it('getExecutor("amazon-q") should exist and be a KiroExecutor', () => {
const executor = getExecutor("amazon-q");
it('getExecutor("amazon-q") should exist and be a KiroExecutor', async () => {
const executor = await getExecutor("amazon-q");
assert.ok(executor, "getExecutor('amazon-q') should return an executor");
assert.equal(
executor.constructor.name,

View File

@@ -29,7 +29,7 @@ test("#10274: no search provider has a specialized chat executor", () => {
}
});
test("#10274: a chat-completion request routed to a search provider must not silently hit OpenAI's endpoint", () => {
test("#10274: a chat-completion request routed to a search provider must not silently hit OpenAI's endpoint", async () => {
// Desired behavior: search providers (registered only in SEARCH_PROVIDERS, never in the
// chat REGISTRY) must not silently resolve to OpenAI's chat/completions endpoint when
// routed through the normal chat-completions executor path. getExecutor() must throw a
@@ -40,9 +40,9 @@ test("#10274: a chat-completion request routed to a search provider must not sil
// OpenAI's endpoint -- this assertion FAILS on unfixed release/v3.8.50 code because no
// error is thrown at all.
for (const id of SEARCH_PROVIDER_IDS) {
assert.throws(
() => getExecutor(id),
(err) => {
await assert.rejects(
getExecutor(id),
(err: Error & { status?: number }) => {
assert.match(err.message, /search provider/i);
assert.match(err.message, /does not support chat completions/i);
assert.match(err.message, /\/v1\/search/i);

View File

@@ -16,7 +16,7 @@
import test, { before, after } from "node:test";
import assert from "node:assert/strict";
import { TinyCmsExecutor } from "../../open-sse/executors/index.ts";
import { TinyCmsExecutor } from "../../open-sse/executors/tinycms.ts";
import { setupDomMocks, type DomMockRestore } from "../../open-sse/executors/tinycmsSigner.ts";
let restoreDomMocks: DomMockRestore;

View File

@@ -16,7 +16,7 @@ function containsBytes(haystack: Uint8Array, needle: Uint8Array): boolean {
}
test("Devin Desktop sends the curated raw model id without alias rewriting", async () => {
const executor = getExecutor("devin-desktop");
const executor = await getExecutor("devin-desktop");
const originalFetch = globalThis.fetch;
let requestBody: Uint8Array | null = null;
globalThis.fetch = async (url, init) => {
@@ -37,7 +37,7 @@ test("Devin Desktop sends the curated raw model id without alias rewriting", asy
credentials: { accessToken: "test-devin-desktop-token" },
});
assert.equal(result.response.status, 418);
assert.equal((result instanceof Response ? result : result.response).status, 418);
assert.ok(requestBody);
assert.equal(containsBytes(requestBody, new TextEncoder().encode(model)), true);
} finally {

View File

@@ -81,14 +81,14 @@ test("xAI OAuth maps refreshable tokens and safe id_token display metadata", ()
assert.equal(mapped.name, "Grok User");
});
test("xAI OAuth is a distinct OAuth registry entry backed by the xAI executor", () => {
test("xAI OAuth is a distinct OAuth registry entry backed by the xAI executor", async () => {
assert.equal(xai_oauthProvider.authType, "oauth");
assert.equal(xai_oauthProvider.baseUrl, "https://api.x.ai/v1/chat/completions");
assert.ok(xai_oauthProvider.models?.some((model) => model.id === "grok-4.5"));
assert.equal(hasSpecializedExecutor("xai-oauth"), true);
assert.ok(getExecutor("xai-oauth") instanceof XaiExecutor);
assert.ok(await getExecutor("xai-oauth") instanceof XaiExecutor);
const headers = getExecutor("xai-oauth").buildHeaders({ accessToken: "oauth-access" }, false);
const headers = (await getExecutor("xai-oauth")).buildHeaders({ accessToken: "oauth-access" }, false);
assert.equal(headers.Authorization, "Bearer oauth-access");
});

View File

@@ -45,9 +45,9 @@ describe("zed-hosted registry entry", () => {
assert.equal(entry.oauth, undefined);
});
test("executor is wired in the executors map", () => {
test("executor is wired in the executors map", async () => {
assert.ok(hasSpecializedExecutor("zed-hosted"));
assert.ok(getExecutor("zed-hosted") instanceof ZedHostedExecutor);
assert.ok(await getExecutor("zed-hosted") instanceof ZedHostedExecutor);
});
});

View File

@@ -86,13 +86,13 @@ test("zenmux-free model names are human-readable strings", () => {
// ── Executor ──────────────────────────────────────────────────────────────────
test("getExecutor returns ZenmuxFreeExecutor for 'zenmux-free'", () => {
const e = getExecutor("zenmux-free");
test("getExecutor returns ZenmuxFreeExecutor for 'zenmux-free'", async () => {
const e = await getExecutor("zenmux-free");
assert.ok(e instanceof ZenmuxFreeExecutor, "executor must be ZenmuxFreeExecutor");
});
test("getExecutor returns ZenmuxFreeExecutor for 'zmf' alias", () => {
const e = getExecutor("zmf");
test("getExecutor returns ZenmuxFreeExecutor for 'zmf' alias", async () => {
const e = await getExecutor("zmf");
assert.ok(e instanceof ZenmuxFreeExecutor, "alias 'zmf' must resolve to ZenmuxFreeExecutor");
});