UNPKG

comfyui-mcp

Version:

Local-first, agent-native control plane for ComfyUI — MCP server + autonomous sidebar agent that drives your live graph in natural language on ANY LLM: Claude/ChatGPT/Gemini on your subscription (no API key), free local models via Ollama (fully offline),

107 lines • 7.45 kB
// Ready-banner text (#376) — extracted as a PURE builder so the connect handler // and the later "real model learned" correction render the SAME phrasing, and so // it is unit-testable. // // The bug (#376): the greeting is sent at HELLO time, before the SDK's init // message reports the actually-resolved model — so the Claude/default path's // label was the pre-init env default (COMFYUI_MCP_PANEL_MODEL ?? "claude-opus-5"), // not the model the agent actually runs. Threading the resolved model through // onSession lets us RE-SEND a corrected banner (bannerCorrection below) once the // real model is known. import { openAiKeyProvider } from "../services/openai-provider-registry.js"; import { processLocale, trFor } from "../i18n/index.js"; /** * The "🟢 comfyui-mcp agent ready — <label> …" greeting for a given backend, with * `label` (the model/agent name) substituted in. `customBaseUrl` is only used by * the custom-endpoint backend. Mirrors the connect-handler's provider phrasing * exactly so an initial banner and a later correction are identical but for the * model name. * * LOCALE. This text is pushed as a `say` frame, so it lands in a chat bubble inside a * specific panel tab and belongs in THAT reader's language — hence `trFor`, not `tr`. * The panel's hello frame carries no language field yet, so `locale` is an explicit * parameter the caller supplies: when it is omitted we fall back to the process locale * (`--lang` / `COMFYUI_MCP_LANG` / `LANG`), which on a loopback install is the same * person's own setting and is a far better guess than hard-coding English. Once the * panel reports its language, threading it here is a one-argument change and every * greeting below follows automatically. * * The 🟢/⚠️ markers are NOT translated. They are the status signal a reader scans for * before reading a word, they mean the same thing in every language, and a catalog * that drops or swaps one changes what the line asserts — so each fallback carries * its marker and every translation is expected to reproduce it verbatim. */ export function readyBannerText(backend, label, customBaseUrl = "", locale) { // `||`, not `??`: an unset panel language serializes as "" far more often than as // undefined, and an empty string is not a locale — it would resolve to English and // quietly skip the `--lang` the user actually set. const loc = locale || processLocale(); const t = (key, fallback) => trFor(loc, key, fallback, { label, url: customBaseUrl }); const reg = openAiKeyProvider(backend); if (reg) return reg.readyMessage(label, loc); switch (backend) { case "codex": return t("banner.ready.codex", "🟢 comfyui-mcp agent ready — {label} on your Codex (ChatGPT) account. Ask away."); case "chatgpt": return t("banner.ready.chatgpt", "🟢 comfyui-mcp agent ready — {label} on your ChatGPT subscription (direct OAuth). Ask away."); case "gemini": return t("banner.ready.gemini", "🟢 comfyui-mcp agent ready — {label} on your Google account (Gemini Code Assist). Ask away."); case "antigravity": return t("banner.ready.antigravity", "🟢 comfyui-mcp agent ready — {label} on your Google AI subscription via Antigravity CLI. Note: agy turns show final answers only (no live tool progress). Ask away."); case "pi": // Deliberately hedged: readiness for pi is a DISK check (a well-formed, // present credential), never a proven-working one — a revoked key or a // spent quota only shows up on the first real turn, so don't promise more // than "we found a credential" (#491). The hedge is the POINT of this // string, so it has to survive translation — keep it in every catalog. return t("banner.ready.pi", "🟢 comfyui-mcp agent ready — {label} via the pi CLI on your own provider credential (found on this machine; pi checks it on your first message). Note: pi has no ComfyUI tools (no MCP) — it works with its own coding tools on the local workspace, not the panel canvas. Ask away."); case "grok": return t("banner.ready.grok", "🟢 comfyui-mcp agent ready — {label} on your Grok (xAI) account. Ask away."); case "ollama": return t("banner.ready.ollama", "🟢 comfyui-mcp agent ready — {label} running locally via Ollama (no account, no API key). Small local models are slower and simpler than frontier ones — expect fewer frills. Ask away."); case "lmstudio": return t("banner.ready.lmstudio", "🟢 comfyui-mcp agent ready — {label} running locally via LM Studio (no account, no API key). Small local models are slower and simpler than frontier ones — expect fewer frills. Ask away."); case "llamacpp": return t("banner.ready.llamacpp", "🟢 comfyui-mcp agent ready — {label} running locally via llama.cpp (no account, no API key). Small local models are slower and simpler than frontier ones — expect fewer frills. Ask away."); case "custom": return t("banner.ready.custom", "🟢 comfyui-mcp agent ready — {label} via your custom endpoint ({url}). Ask away."); case "openrouter": return t("banner.ready.openrouter", "🟢 comfyui-mcp agent ready — {label} via OpenRouter (hosted API, your OPENROUTER_API_KEY). Ask away."); case "copilot": return t("banner.ready.copilot", "🟢 comfyui-mcp agent ready — {label} on your GitHub Copilot subscription (⚠️ experimental, ToS risk — you opted in). Ask away."); default: return t("banner.ready.claude", "🟢 comfyui-mcp agent ready — {label} on your Claude subscription. Ask away."); } } /** * The model name a fresh greeting advertises. A model the SDK already resolved * for this tab wins (#376: init can race ahead of the greeting), but only when it * was resolved by the backend the tab is greeting for NOW. The map outlives a * backend switch, so without that check a tab that ran Claude and then switched * to Ollama greeted with "claude-opus-5-5 running locally via Ollama". */ export function greetingLabel(resolved, backend, fallback) { return resolved && resolved.backend === backend ? resolved.model : fallback; } /** * Decide the CORRECTED ready banner to re-send once the SDK reports the real model * (#376), or `null` when no correction is warranted. Returns a banner ONLY when: * - a banner WAS actually advertised for this tab (`advertisedLabel` is a * non-empty string) — a resume/reconnect never greeted, so an EMPTY map must * NOT be read as a mismatch and produce a spurious banner; and * - the resolved model is a non-empty string that DIFFERS from that label. * So a correct initial banner never duplicates, and a resumed session with no * prior greeting is never corrected. */ export function bannerCorrection(opts) { const { backend, advertisedLabel, resolvedModel, customBaseUrl = "", locale } = opts; if (typeof advertisedLabel !== "string" || !advertisedLabel) return null; if (typeof resolvedModel !== "string" || !resolvedModel.trim()) return null; if (resolvedModel === advertisedLabel) return null; return readyBannerText(backend, resolvedModel, customBaseUrl, locale); } //# sourceMappingURL=ready-banner.js.map