Skip to content

app: non-inference providers (e.g. Firecrawl) show "models available" toast and are lumped into generic "Other" category in Connect modal #59141

Open

Description

@franjorub

Summary

  1. Misleading connection toast for non-inference providers: When connecting a non-inference provider such as Firecrawl (a web search/scraping provider), the confirmation toast treats it as an LLM inference provider and states "Firecrawl models are now available to use."
  2. Lack of provider segmentation in Connect modal (Other category): In the Connect provider modal, providers of completely different types and billing models (subscriptions, pay-as-you-go inference APIs, and web search tools like Firecrawl) are all lumped together under a single generic "Other" category instead of being properly segmented.

Environment

  • opencode version: 2.1.4
  • OS: Windows_NT 10.0.26300 (win32 x64)
  • Terminal: OpenCode Desktop App (Unavailable: terminal environment variables are not set)
  • Shell: C:\WINDOWS\system32\cmd.exe
  • Install/channel: latest (Desktop)
  • Active plugins: None found in config

Reproduction

  1. Open OpenCode Desktop and open the Connect provider modal.
  2. Scroll to the "Other" category in the provider list and observe that LLM inference providers, subscription services, and non-inference web search providers (like Firecrawl) are all mixed together in the same bucket.
  3. Select Firecrawl, enter the API key, and complete the connection.
  4. Observe the confirmation toast notification.

Expected Behavior

  1. Better category segmentation in the Connect modal: Instead of dumping disparate provider types into a catch-all "Other" category, segment the list by connection and capability type:
    • Subscription providers (e.g., OpenCode Go, GitHub Copilot, ChatGPT Plus/Pro)
    • Pay-as-you-go (API key) inference providers (e.g., Anthropic, OpenAI, Google, OpenRouter, etc.)
    • Web Search / Tool providers (e.g., Firecrawl, Exa, etc.)
  2. Accurate connection feedback: Connecting a non-inference provider like Firecrawl should display a confirmation message matching its actual capability (e.g., "Firecrawl is now connected and available for web search" instead of referring to "Firecrawl models").

Actual Behavior

  • In the Connect modal, all non-featured providers—including web search integrations like Firecrawl, subscription providers, and API-key model providers—are mixed together inside a generic "Other" category.
  • After connecting Firecrawl, a toast notification appears with the message:

    Firecrawl connected
    Firecrawl models are now available to use.

Additional Context

  • Reproducible consistently on OpenCode Desktop v2.1.4.

Activity

  1. franjorub commented on Oct 26, 2026

    @franjorub
    Author
    Image
  2. opencode-agent commented on Oct 26, 2026

    @opencode-agent
    Contributor

    Thanks for the report. I reproduced the misleading toast on 2.1.4, using the web UI served by opencode serve (the same app as the desktop build): Settings → Providers → Show more providers, search "Firecrawl", enter an API key and connect. The toast says "Firecrawl connected — Firecrawl models are now available to use." even though Firecrawl is a web search integration with no models. It still happens on the latest dev (5b41e1f). The screenshots below show the Connect dialog and the toast on both versions, and the script below automates the steps.

    About the grouping: the Connect dialog currently has two groups by design, a fixed "Popular" list and an alphabetical "Other" list. Splitting it into subscription, API-key and web search sections would be a new feature rather than a bug fix, so maintainers will treat that part as a suggestion.

    Screenshot

    Screenshot

    Screenshot

    Reproduction script (connect-firecrawl.mjs)
    // Reproduces #59141: connecting Firecrawl (web search) in the web/desktop app's
    // Connect dialog shows "Firecrawl models are now available to use."
    // Prereq: `OPENCODE_SERVER_PASSWORD=pw opencode serve --hostname 127.0.0.1 --port 4096`
    // (from a project dir, with an isolated HOME), then: node connect-firecrawl.mjs
    import { chromium } from "playwright"
    
    const url = process.env.OC_URL ?? "http://127.0.0.1:4096/"
    const shots = process.env.SHOTS ?? "/workspace/repro"
    const browser = await chromium.launch()
    const context = await browser.newContext({
      httpCredentials: { username: "opencode", password: process.env.OC_PASSWORD ?? "pw" },
      viewport: { width: 1400, height: 900 },
    })
    const page = await context.newPage()
    await page.goto(url)
    await page.waitForTimeout(4000)
    // Server login screen: "Use password"
    if (await page.getByText("Use password").isVisible().catch(() => false)) {
      await page.getByText("Use password").click()
      await page.waitForTimeout(500)
      const inputs = page.locator("input")
      const n = await inputs.count()
      for (let i = 0; i < n; i++) {
        const el = inputs.nth(i)
        const type = await el.getAttribute("type")
        const ph = ((await el.getAttribute("placeholder")) ?? "").toLowerCase()
        if (type === "password") await el.fill(process.env.OC_PASSWORD ?? "pw")
        else if (ph.includes("http") || ph.includes("url") || ph.includes("server")) await el.fill(url)
      }
      await page.screenshot({ path: `${shots}/0-login.png` })
      await page.getByRole("button", { name: /connect/i }).first().click()
      await page.waitForTimeout(5000)
    }
    await page.screenshot({ path: `${shots}/0-home.png` })
    
    // Open the Connect provider dialog
    await page.locator("text=Settings").locator("visible=true").first().click()
    await page.waitForTimeout(1000)
    await page.locator("text=Providers").locator("visible=true").first().click()
    await page.waitForTimeout(1500)
    await page.screenshot({ path: `${shots}/0-providers.png` })
    const connect = page.locator(".settings-providers-view-all")
    await connect.scrollIntoViewIfNeeded()
    await connect.click()
    await page.waitForTimeout(1500)
    await page.screenshot({ path: `${shots}/1-connect-dialog.png` })
    
    await page.getByPlaceholder(/search/i).last().fill("firecrawl")
    await page.waitForTimeout(800)
    await page.locator('[data-provider-id="firecrawl"]').click()
    await page.waitForTimeout(1500)
    await page.screenshot({ path: `${shots}/2-firecrawl.png` })
    
    const input = page.locator('input[type="password"], input[type="text"]').last()
    await input.fill("fc-test-dummy-key")
    await input.press("Enter")
    await page.waitForTimeout(3000)
    await page.screenshot({ path: `${shots}/3-toast.png` })
    console.log("BODY TEXT CONTAINS:", (await page.textContent("body")).match(/Firecrawl[^.]{0,80}\./g))
    await browser.close()

    Run it: bun connect-firecrawl.mjs

  3. Dante-dan commented on Oct 26, 2026

    @Dante-dan
    Contributor

    I traced the toast to the connection callback, which always adds the model-availability sentence even when the refreshed model list is empty. This focused patch keeps the existing “Firecrawl connected” title and only includes the models sentence when models are available. Provider grouping is unchanged.

    The app typechecks pass. The patch includes settings regression cases for Firecrawl without models and OpenRouter with models, but I could not execute their UI assertions or capture an after screenshot: Chromium fails at startup with a host permission error. The UI regression remains unverified.

  4. SU-AN-coder commented on Oct 26, 2026

    @SU-AN-coder

    Part 1 is in #59243: when a connection brings no models the toast no longer says "models are now available to use", so Firecrawl reads just "Firecrawl connected".

    I left the sentence out instead of adding a new one: every locale dict is typed keyof typeof en, so a new key means editing ~30 locale files, and I did not want to put English in the other languages. Say the word and I will add a proper string with the locales.

    Part 2 I did not touch. Splitting "Other" into subscription / API-key inference / web-search providers changes the modal's structure, and it needs your naming — if the provider metadata already carries that split, point me at the field and I will do it as its own PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions