Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@ identity facts when merging or refactoring:
single bookmark, so channel inference can be ambiguous or unavailable. The
build fails closed in that case; the explicit channel pins the intended
database and service identity.
- The upstream Claude Agent SDK provider stays on disk
(`packages/core/src/plugin/provider/claude-agent/**` and its tests, plus the
optional `@anthropic-ai/claude-agent-sdk` peer and `#claude-agent-sdk`
alias) so later syncs do not resurrect a deleted tree. It is not a Shuvcode
provider. The guarantee is three layers, not the bundled registry alone:
`ProviderPlugins` does not load it, `ConfigProviderPlugin` skips provider
ids `claude-agent` and `opencode.provider.claude-agent`, and every client
list of integrations, providers, or models calls
`isHiddenProvider` from `@opencode/util/hidden-provider`. Do not register
`ClaudeAgentPlugin` or `make(...)`, do not let config recreate the id, and
do not add a list surface that omits the predicate. Anthropic in the
product is `@shuvcode/claude-plugin` (`ClaudePlugin`). When merging
`upstream/dev`, drop any reintroduced registration, config path, or
user-facing surface for that provider.

## Live V2 TUI Testing

Expand Down
6 changes: 5 additions & 1 deletion PLAN-fresh-v2-rewrite.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@ still `2.0.3`). Re-fetch again at kickoff and re-run the watchlist checks.
3. **Plugin rule:** if it can be a plugin, it is a plugin, **bundled and
enabled** (Core `ProviderPlugins` / TUI `builtins`). Disable by config id
(`-<id>` in `plugins`). Do not require a user `plugins` array for the
shipped set.
shipped set. Exception: the upstream Claude Agent SDK provider
(`packages/core/src/plugin/provider/claude-agent/**`) stays on disk for
sync and stays unregistered. Config must not recreate it, and client
lists must keep filtering it. Do not re-enable it. Anthropic remains
`@shuvcode/claude-plugin`. See `AGENTS.md` Shuvcode Fork.
4. **Core may change only for:** identity, default port, XDG app id, one-shot
DB convert script, OAuth refresh single-flight, host seams plugins cannot
exist without, and watchlist items whose check **fails** at kickoff.
Expand Down
3 changes: 2 additions & 1 deletion packages/app/src/composer/selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createStore } from "solid-js/store"
import type { ComposerControls } from "./adapter"
import { useLocal, type ModelKey, type ModelSelection } from "@/providers/models/selection"
import { useWorkspaceLocation } from "@/workspaces/location"
import { omitHiddenProviderCatalog } from "@/providers/catalog/order"
import { useProviders } from "@/providers/catalog/providers"
import { useData } from "@/runtime/server/current"
import { normalizeAgentList, normalizeProviderList } from "@/runtime/server/global-sync/utils"
Expand Down Expand Up @@ -55,7 +56,7 @@ export function createComposerControls(input: { model?: ModelSelection } = {}) {
const catalog = data.location.model.list(location)

if (!provider || !catalog) return
const normalized = normalizeProviderList(provider, catalog)
const normalized = omitHiddenProviderCatalog(normalizeProviderList(provider, catalog))

return { all: normalized.all, connected: normalized.connected }
})
Expand Down
3 changes: 2 additions & 1 deletion packages/app/src/providers/catalog/integrations.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useServerSDK } from "@/runtime/server/client"
import { useData } from "@/runtime/server/current"
import { createEffect, type Accessor } from "solid-js"
import { withoutHiddenProviders } from "./order"

export function useIntegrations(directory: Accessor<string | undefined>) {
const serverSDK = useServerSDK()
Expand All @@ -25,6 +26,6 @@ export function useIntegrations(directory: Accessor<string | undefined>) {

return {
ready: () => data.location.integration.list(location()) !== undefined,
list: () => data.location.integration.list(location()) ?? [],
list: () => withoutHiddenProviders(data.location.integration.list(location()) ?? []),
}
}
26 changes: 26 additions & 0 deletions packages/app/src/providers/catalog/order.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { expect, test } from "bun:test"
import { connectPopular, omitHiddenProviderCatalog, popularProviders, withoutHiddenProviders } from "./order"

test("keeps Anthropic visible and drops Claude Agent from provider menus", () => {
expect(popularProviders).toContain("anthropic")
expect(connectPopular).toContain("anthropic")
expect(popularProviders).not.toContain("claude-agent")
expect(connectPopular).not.toContain("claude-agent")

expect(withoutHiddenProviders([{ id: "claude-agent" }, { id: "anthropic" }]).map((item) => item.id)).toEqual([
"anthropic",
])

const catalog = omitHiddenProviderCatalog({
all: new Map<string, { name: string }>([
["claude-agent", { name: "Claude Agent" }],
["anthropic", { name: "Anthropic" }],
]),
connected: ["claude-agent", "anthropic"],
default: { "claude-agent": "sonnet", anthropic: "claude-sonnet" },
})

expect([...catalog.all.keys()]).toEqual(["anthropic"])
expect(catalog.connected).toEqual(["anthropic"])
expect(catalog.default).toEqual({ anthropic: "claude-sonnet" })
})
29 changes: 28 additions & 1 deletion packages/app/src/providers/catalog/order.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,38 @@
import { isHiddenProvider } from "@opencode/util/hidden-provider"

export { isHiddenProvider }

export const popularProviders = [
"opencode-go",
"opencode",
"anthropic",
"claude-agent",
"github-copilot",
"openai",
"google",
"openrouter",
"vercel",
]

// Connect dialog group order. Matches the TUI priority list.
export const connectPopular = ["opencode", "opencode-go", "openai", "github-copilot", "anthropic", "google"]

// App catalogs that call these helpers omit the upstream Claude Agent id.
// Other list surfaces call isHiddenProvider themselves.
export function withoutHiddenProviders<T extends { id: string }>(items: readonly T[]) {
return items.filter((item) => !isHiddenProvider(item.id))
}

export function omitHiddenProviderCatalog<T>(catalog: {
all: Map<string, T>
connected: readonly string[]
default: Record<string, string>
}) {
const all = new Map([...catalog.all].filter(([id]) => !isHiddenProvider(id)))
const connected = catalog.connected.filter((id) => !isHiddenProvider(id))

return {
all,
connected,
default: Object.fromEntries(Object.entries(catalog.default).filter(([id]) => !isHiddenProvider(id))),
}
}
4 changes: 2 additions & 2 deletions packages/app/src/providers/catalog/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Iterable, pipe } from "effect"
import { createEffect, createMemo, type Accessor } from "solid-js"
import type { ProviderListResponse } from "@/runtime/server/types"
import { useIntegrations } from "./integrations"
import { popularProviders } from "./order"
import { omitHiddenProviderCatalog, popularProviders } from "./order"

export { popularProviders } from "./order"

Expand Down Expand Up @@ -49,7 +49,7 @@ export function useProviders(directory: Accessor<string | undefined>) {

if (!provider || !model) return emptyProviderCatalog

return normalizeProviderList(provider, model)
return omitHiddenProviderCatalog(normalizeProviderList(provider, model))
})

const ready = () => {
Expand Down
2 changes: 0 additions & 2 deletions packages/app/src/providers/connect/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ type Polling = {
// and connects with its own methods, the same as the TUI.
export const CONSOLE_INTEGRATION = "opencode"

export const CLAUDE_AGENT_INTEGRATION = "claude-agent"

// Same order as the TUI: browser and command sign-ins first, API keys last.
export function connectMethods(integration: IntegrationInfo) {
return integration.methods
Expand Down
Loading
Loading