feat(core): add pluggable web search (#35558)
Co-authored-by: Dax Raad <d@ironbay.co>
This commit is contained in:
parent
2ddc91a0e8
commit
efb629a33a
57 changed files with 1700 additions and 587 deletions
|
|
@ -59,29 +59,43 @@ export function connectionSummary(integration: IntegrationInfo) {
|
|||
.join(", ")
|
||||
}
|
||||
|
||||
export function DialogIntegration(props: { onConnected?: OnIntegrationConnected } = {}) {
|
||||
export function DialogIntegration(
|
||||
props: { onConnected?: OnIntegrationConnected; integrationID?: string; connectionOnly?: boolean } = {},
|
||||
) {
|
||||
const data = useData()
|
||||
const dialog = useDialog()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const options = createMemo(() =>
|
||||
integrationOptions(data.location.integration.list() ?? []).map((integration) => {
|
||||
const options = createMemo(() => {
|
||||
const providers = data.location.websearch.list() ?? []
|
||||
const providersByID = new Map(providers.map((provider) => [provider.id, provider]))
|
||||
const integrations = integrationOptions(data.location.integration.list() ?? []).filter(
|
||||
(integration) => props.integrationID === undefined || integration.id === props.integrationID,
|
||||
)
|
||||
return integrations.map((integration) => {
|
||||
const methods = connectMethods(integration)
|
||||
const connected = integration.connections.length > 0
|
||||
const provider = providersByID.get(integration.id)
|
||||
const credentials = credentialConnections(integration)
|
||||
let category = "Services"
|
||||
if (integration.id in INTEGRATION_PRIORITY) category = "Popular"
|
||||
if (provider) category = "Web search"
|
||||
return {
|
||||
title: integration.name,
|
||||
value: integration.id,
|
||||
description: methods.length ? undefined : "Environment only",
|
||||
description: methods.length === 0 ? "Environment only" : undefined,
|
||||
footer: connectionSummary(integration) || undefined,
|
||||
category: integration.id in INTEGRATION_PRIORITY ? "Popular" : "Services",
|
||||
disabled: methods.length === 0,
|
||||
gutter: connected ? () => <text fg={themeV2.text.feedback.success.default}>✓</text> : undefined,
|
||||
onSelect: () =>
|
||||
credentialConnections(integration).length
|
||||
? manageConnections(integration, methods, dialog, props.onConnected)
|
||||
: selectMethod(integration, methods, dialog, props.onConnected),
|
||||
category,
|
||||
disabled: methods.length === 0 && credentials.length === 0,
|
||||
gutter:
|
||||
integration.connections.length > 0
|
||||
? () => <text fg={themeV2.text.feedback.success.default}>✓</text>
|
||||
: undefined,
|
||||
onSelect: () => {
|
||||
if (credentials.length) return manageConnections(integration, methods, dialog, props.onConnected)
|
||||
return selectMethod(integration, methods, dialog, props.onConnected)
|
||||
},
|
||||
}
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<DialogSelect
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import type {
|
|||
ShellInfo,
|
||||
SkillInfo,
|
||||
OpenCodeEvent,
|
||||
WebSearchProvider,
|
||||
} from "@opencode-ai/client"
|
||||
import type { Plugin } from "@opencode-ai/plugin/v2/tui"
|
||||
import { createStore, produce, reconcile } from "solid-js/store"
|
||||
|
|
@ -56,6 +57,7 @@ type LocationData = {
|
|||
model?: ModelInfo[]
|
||||
provider?: ProviderV2Info[]
|
||||
reference?: ReferenceInfo[]
|
||||
websearch?: WebSearchProvider[]
|
||||
// Currently running shell commands for this location, keyed by shell id. Entries are removed
|
||||
// once the command exits or is deleted, so this only ever holds in-flight shells.
|
||||
shell?: Record<string, ShellInfo>
|
||||
|
|
@ -875,6 +877,10 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
result.location.provider.sync(event.location),
|
||||
])
|
||||
break
|
||||
case "config.updated":
|
||||
case "websearch.updated":
|
||||
void result.location.websearch.refresh(event.location)
|
||||
break
|
||||
// Authenticating an MCP integration reconnects its server, which emits mcp.status.changed,
|
||||
// so the mcp list syncs here rather than off integration.updated.
|
||||
case "mcp.status.changed":
|
||||
|
|
@ -1251,6 +1257,20 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
sync.invalidate(`location.reference:${locationKey(ref ?? defaultLocation())}`)
|
||||
},
|
||||
},
|
||||
websearch: {
|
||||
list(location?: LocationRef) {
|
||||
return store.location[locationKey(location ?? defaultLocation())]?.websearch
|
||||
},
|
||||
async refresh(ref?: LocationRef) {
|
||||
const input = { location: locationQuery(ref ?? defaultLocation()) }
|
||||
const providers = await client.api.websearch.providers(input)
|
||||
const key = locationKey(providers.location)
|
||||
setStore("location", key, {
|
||||
...store.location[key],
|
||||
websearch: providers.data,
|
||||
})
|
||||
},
|
||||
},
|
||||
skill: {
|
||||
list(location?: LocationRef) {
|
||||
return store.location[locationKey(location ?? defaultLocation())]?.skill
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue