mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-10 01:39:36 +00:00
refactor(websearch): update provider consumers
This commit is contained in:
parent
820b1a1a46
commit
86ad303695
7 changed files with 217 additions and 135 deletions
|
|
@ -461,8 +461,10 @@ import type {
|
|||
V2VcsDiffResponses,
|
||||
V2VcsStatusErrors,
|
||||
V2VcsStatusResponses,
|
||||
V2WebsearchProviderGetErrors,
|
||||
V2WebsearchProviderGetResponses,
|
||||
V2WebsearchProviderListErrors,
|
||||
V2WebsearchProviderListResponses,
|
||||
V2WebsearchProviderSelectedErrors,
|
||||
V2WebsearchProviderSelectedResponses,
|
||||
V2WebsearchProviderSelectErrors,
|
||||
V2WebsearchProviderSelectResponses,
|
||||
V2WebsearchQueryErrors,
|
||||
|
|
@ -8254,11 +8256,11 @@ export class Debug extends HeyApiClient {
|
|||
|
||||
export class Provider3 extends HeyApiClient {
|
||||
/**
|
||||
* Get default web search provider
|
||||
* List web search providers
|
||||
*
|
||||
* Return the globally selected web search provider.
|
||||
* Return the registered web search providers.
|
||||
*/
|
||||
public get<ThrowOnError extends boolean = false>(
|
||||
public list<ThrowOnError extends boolean = false>(
|
||||
parameters?: {
|
||||
location?: {
|
||||
directory?: string | null
|
||||
|
|
@ -8269,8 +8271,8 @@ export class Provider3 extends HeyApiClient {
|
|||
) {
|
||||
const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }])
|
||||
return (options?.client ?? this.client).get<
|
||||
V2WebsearchProviderGetResponses,
|
||||
V2WebsearchProviderGetErrors,
|
||||
V2WebsearchProviderListResponses,
|
||||
V2WebsearchProviderListErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
url: "/api/websearch/provider",
|
||||
|
|
@ -8279,6 +8281,32 @@ export class Provider3 extends HeyApiClient {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get selected web search provider
|
||||
*
|
||||
* Return the globally selected web search provider.
|
||||
*/
|
||||
public selected<ThrowOnError extends boolean = false>(
|
||||
parameters?: {
|
||||
location?: {
|
||||
directory?: string | null
|
||||
workspace?: string | null
|
||||
} | null
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }])
|
||||
return (options?.client ?? this.client).get<
|
||||
V2WebsearchProviderSelectedResponses,
|
||||
V2WebsearchProviderSelectedErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
url: "/api/websearch/provider/selected",
|
||||
...options,
|
||||
...params,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Select default web search provider
|
||||
*
|
||||
|
|
@ -8310,7 +8338,7 @@ export class Provider3 extends HeyApiClient {
|
|||
V2WebsearchProviderSelectErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
url: "/api/websearch/provider",
|
||||
url: "/api/websearch/provider/selected",
|
||||
...options,
|
||||
...params,
|
||||
headers: {
|
||||
|
|
@ -8326,7 +8354,7 @@ export class Websearch extends HeyApiClient {
|
|||
/**
|
||||
* Search the web
|
||||
*
|
||||
* Run one web search through the selected integration. Specify a provider to override the configured default.
|
||||
* Run one web search through the selected provider. Specify a provider to override the configured default.
|
||||
*/
|
||||
public query<ThrowOnError extends boolean = false>(
|
||||
parameters?: {
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ export type Event =
|
|||
| EventFormCreated
|
||||
| EventFormReplied
|
||||
| EventFormCancelled
|
||||
| EventWebsearchUpdated
|
||||
| EventLspUpdated
|
||||
| EventPermissionAsked
|
||||
| EventPermissionReplied
|
||||
|
|
@ -1408,7 +1409,7 @@ export type GlobalEvent = {
|
|||
id: string
|
||||
type: "form.created"
|
||||
properties: {
|
||||
form: FormFormInfo | FormUrlInfo | FormIntegrationInfo
|
||||
form: FormFormInfo | FormUrlInfo
|
||||
}
|
||||
}
|
||||
| {
|
||||
|
|
@ -1428,6 +1429,13 @@ export type GlobalEvent = {
|
|||
sessionID: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
id: string
|
||||
type: "websearch.updated"
|
||||
properties: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
| {
|
||||
id: string
|
||||
type: "lsp.updated"
|
||||
|
|
@ -3084,6 +3092,7 @@ export type V2Event =
|
|||
| FormCreated
|
||||
| FormReplied
|
||||
| FormCancelled
|
||||
| WebsearchUpdated
|
||||
| LspUpdated
|
||||
| PermissionAsked
|
||||
| PermissionReplied
|
||||
|
|
@ -3563,15 +3572,6 @@ export type FormUrlInfo = {
|
|||
url: string
|
||||
}
|
||||
|
||||
export type FormIntegrationInfo = {
|
||||
id: string
|
||||
sessionID: string
|
||||
title: string
|
||||
metadata?: FormMetadata
|
||||
mode: "integration"
|
||||
integrationID: string
|
||||
}
|
||||
|
||||
export type FormValue =
|
||||
| string
|
||||
| number
|
||||
|
|
@ -5723,10 +5723,6 @@ export type IntegrationEnvMethod = {
|
|||
names: Array<string>
|
||||
}
|
||||
|
||||
export type IntegrationWebSearch = {
|
||||
connection: "optional" | "required"
|
||||
}
|
||||
|
||||
export type ConnectionCredentialInfo = {
|
||||
type: "credential"
|
||||
id: string
|
||||
|
|
@ -5744,7 +5740,6 @@ export type IntegrationInfo = {
|
|||
id: string
|
||||
name: string
|
||||
methods: Array<IntegrationMethod>
|
||||
websearch?: IntegrationWebSearch
|
||||
connections: Array<ConnectionInfo>
|
||||
}
|
||||
|
||||
|
|
@ -6573,7 +6568,7 @@ export type FormCreated = {
|
|||
type: "form.created"
|
||||
location?: LocationRef
|
||||
data: {
|
||||
form: FormFormInfo | FormUrlInfo | FormIntegrationInfo
|
||||
form: FormFormInfo | FormUrlInfo
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -6608,6 +6603,19 @@ export type FormCancelled = {
|
|||
}
|
||||
}
|
||||
|
||||
export type WebsearchUpdated = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "websearch.updated"
|
||||
location?: LocationRef
|
||||
data: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
|
||||
export type LspUpdated = {
|
||||
id: string
|
||||
created: number
|
||||
|
|
@ -7023,6 +7031,11 @@ export type ProjectCopyCopy = {
|
|||
|
||||
export type VcsMode = "working" | "branch"
|
||||
|
||||
export type WebSearchProvider = {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export type WebSearchResult = {
|
||||
providerID: string
|
||||
text: string
|
||||
|
|
@ -7851,7 +7864,7 @@ export type EventFormCreated = {
|
|||
id: string
|
||||
type: "form.created"
|
||||
properties: {
|
||||
form: FormFormInfo | FormUrlInfo | FormIntegrationInfo
|
||||
form: FormFormInfo | FormUrlInfo
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -7874,6 +7887,14 @@ export type EventFormCancelled = {
|
|||
}
|
||||
}
|
||||
|
||||
export type EventWebsearchUpdated = {
|
||||
id: string
|
||||
type: "websearch.updated"
|
||||
properties: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
|
||||
export type EventLspUpdated = {
|
||||
id: string
|
||||
type: "lsp.updated"
|
||||
|
|
@ -8701,6 +8722,7 @@ export type V2EventV2 =
|
|||
| FormCreatedV2
|
||||
| FormRepliedV2
|
||||
| FormCancelledV2
|
||||
| WebsearchUpdatedV2
|
||||
| SessionStatusV22
|
||||
| SessionIdleV2
|
||||
| TuiPromptAppendV2
|
||||
|
|
@ -9930,15 +9952,6 @@ export type FormUrlInfoV2 = {
|
|||
url: string
|
||||
}
|
||||
|
||||
export type FormIntegrationInfoV2 = {
|
||||
id: string
|
||||
sessionID: string
|
||||
title: string
|
||||
metadata?: FormMetadata
|
||||
mode: "integration"
|
||||
integrationID: string
|
||||
}
|
||||
|
||||
export type FormCreatePayloadV2 = {
|
||||
id?: string | null
|
||||
title: string
|
||||
|
|
@ -10674,15 +10687,6 @@ export type FormUrlInfo1 = {
|
|||
url: string
|
||||
}
|
||||
|
||||
export type FormIntegrationInfo1 = {
|
||||
id: string
|
||||
sessionID: string
|
||||
title: string
|
||||
metadata?: FormMetadata1
|
||||
mode: "integration"
|
||||
integrationID: string
|
||||
}
|
||||
|
||||
export type FormCreatedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
|
|
@ -10692,7 +10696,7 @@ export type FormCreatedV2 = {
|
|||
type: "form.created"
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
form: FormFormInfo1 | FormUrlInfo1 | FormIntegrationInfo1
|
||||
form: FormFormInfo1 | FormUrlInfo1
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -10729,6 +10733,21 @@ export type FormCancelledV2 = {
|
|||
}
|
||||
}
|
||||
|
||||
export type WebsearchUpdatedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "websearch.updated"
|
||||
location?: LocationRefV2
|
||||
data:
|
||||
| {
|
||||
[key: string]: unknown
|
||||
}
|
||||
| Array<unknown>
|
||||
}
|
||||
|
||||
export type SessionIdleV2 = {
|
||||
id: string
|
||||
created: number
|
||||
|
|
@ -17245,7 +17264,7 @@ export type V2FormRequestListResponses = {
|
|||
*/
|
||||
200: {
|
||||
location: LocationInfoV2
|
||||
data: Array<FormFormInfoV2 | FormUrlInfoV2 | FormIntegrationInfoV2>
|
||||
data: Array<FormFormInfoV2 | FormUrlInfoV2>
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -17282,7 +17301,7 @@ export type V2SessionFormListResponses = {
|
|||
* Success
|
||||
*/
|
||||
200: {
|
||||
data: Array<FormFormInfoV2 | FormUrlInfoV2 | FormIntegrationInfoV2>
|
||||
data: Array<FormFormInfoV2 | FormUrlInfoV2>
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -17323,7 +17342,7 @@ export type V2SessionFormCreateResponses = {
|
|||
* Success
|
||||
*/
|
||||
200: {
|
||||
data: FormFormInfoV2 | FormUrlInfoV2 | FormIntegrationInfoV2
|
||||
data: FormFormInfoV2 | FormUrlInfoV2
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -17361,7 +17380,7 @@ export type V2SessionFormGetResponses = {
|
|||
* Success
|
||||
*/
|
||||
200: {
|
||||
data: FormFormInfoV2 | FormUrlInfoV2 | FormIntegrationInfoV2
|
||||
data: FormFormInfoV2 | FormUrlInfoV2
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -18968,7 +18987,7 @@ export type V2DebugLocationListResponses = {
|
|||
|
||||
export type V2DebugLocationListResponse = V2DebugLocationListResponses[keyof V2DebugLocationListResponses]
|
||||
|
||||
export type V2WebsearchProviderGetData = {
|
||||
export type V2WebsearchProviderListData = {
|
||||
body?: never
|
||||
path?: never
|
||||
query?: {
|
||||
|
|
@ -18980,7 +18999,48 @@ export type V2WebsearchProviderGetData = {
|
|||
url: "/api/websearch/provider"
|
||||
}
|
||||
|
||||
export type V2WebsearchProviderGetErrors = {
|
||||
export type V2WebsearchProviderListErrors = {
|
||||
/**
|
||||
* InvalidRequestError
|
||||
*/
|
||||
400: InvalidRequestErrorV2
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* ServiceUnavailableError
|
||||
*/
|
||||
503: ServiceUnavailableErrorV2
|
||||
}
|
||||
|
||||
export type V2WebsearchProviderListError = V2WebsearchProviderListErrors[keyof V2WebsearchProviderListErrors]
|
||||
|
||||
export type V2WebsearchProviderListResponses = {
|
||||
/**
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfoV2
|
||||
data: Array<WebSearchProvider>
|
||||
}
|
||||
}
|
||||
|
||||
export type V2WebsearchProviderListResponse = V2WebsearchProviderListResponses[keyof V2WebsearchProviderListResponses]
|
||||
|
||||
export type V2WebsearchProviderSelectedData = {
|
||||
body?: never
|
||||
path?: never
|
||||
query?: {
|
||||
location?: {
|
||||
directory?: string | null
|
||||
workspace?: string | null
|
||||
} | null
|
||||
}
|
||||
url: "/api/websearch/provider/selected"
|
||||
}
|
||||
|
||||
export type V2WebsearchProviderSelectedErrors = {
|
||||
/**
|
||||
* InvalidRequestError
|
||||
*/
|
||||
|
|
@ -18991,9 +19051,10 @@ export type V2WebsearchProviderGetErrors = {
|
|||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2WebsearchProviderGetError = V2WebsearchProviderGetErrors[keyof V2WebsearchProviderGetErrors]
|
||||
export type V2WebsearchProviderSelectedError =
|
||||
V2WebsearchProviderSelectedErrors[keyof V2WebsearchProviderSelectedErrors]
|
||||
|
||||
export type V2WebsearchProviderGetResponses = {
|
||||
export type V2WebsearchProviderSelectedResponses = {
|
||||
/**
|
||||
* Success
|
||||
*/
|
||||
|
|
@ -19003,7 +19064,8 @@ export type V2WebsearchProviderGetResponses = {
|
|||
}
|
||||
}
|
||||
|
||||
export type V2WebsearchProviderGetResponse = V2WebsearchProviderGetResponses[keyof V2WebsearchProviderGetResponses]
|
||||
export type V2WebsearchProviderSelectedResponse =
|
||||
V2WebsearchProviderSelectedResponses[keyof V2WebsearchProviderSelectedResponses]
|
||||
|
||||
export type V2WebsearchProviderSelectData = {
|
||||
body: {
|
||||
|
|
@ -19016,7 +19078,7 @@ export type V2WebsearchProviderSelectData = {
|
|||
workspace?: string | null
|
||||
} | null
|
||||
}
|
||||
url: "/api/websearch/provider"
|
||||
url: "/api/websearch/provider/selected"
|
||||
}
|
||||
|
||||
export type V2WebsearchProviderSelectErrors = {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ const INTEGRATION_PRIORITY: Record<string, number> = {
|
|||
type ConnectMethod = Exclude<IntegrationInfo["methods"][number], { type: "env" }>
|
||||
type IntegrationAttempt = IntegrationConnectOauthOutput["data"]
|
||||
type OnIntegrationConnected = (providerID?: string) => void
|
||||
type WebSearchProvider = { id: string; name: string }
|
||||
|
||||
export function integrationOptions(list: IntegrationInfo[]) {
|
||||
return list.toSorted(
|
||||
|
|
@ -59,27 +60,30 @@ export function DialogIntegration(
|
|||
const data = useData()
|
||||
const dialog = useDialog()
|
||||
const { theme } = useTheme()
|
||||
const options = createMemo(() =>
|
||||
integrationOptions(data.location.integration.list() ?? [])
|
||||
.filter((integration) => props.integrationID === undefined || integration.id === props.integrationID)
|
||||
.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,
|
||||
)
|
||||
const integrationIDs = new Set(integrations.map((integration) => integration.id))
|
||||
return [
|
||||
...integrations.map((integration) => {
|
||||
const methods = connectMethods(integration)
|
||||
const connected = integration.connections.length > 0
|
||||
const websearch = integration.websearch
|
||||
const selected = data.location.websearch.provider() === integration.id
|
||||
const provider = providersByID.get(integration.id)
|
||||
const selected = data.location.websearch.provider() === provider?.id
|
||||
const credentials = credentialConnections(integration)
|
||||
const description = websearch?.connection === "optional" ? "API key optional" : undefined
|
||||
const category = websearch ? "Web search" : undefined
|
||||
return {
|
||||
title: integration.name,
|
||||
value: integration.id,
|
||||
description: description ?? (methods.length === 0 ? "Environment only" : undefined),
|
||||
description: methods.length === 0 ? "Environment only" : undefined,
|
||||
footer:
|
||||
[connectionSummary(integration), selected ? "Web search default" : undefined]
|
||||
.filter((value) => value !== undefined && value.length > 0)
|
||||
.join(" · ") || undefined,
|
||||
category: category ?? (integration.id in INTEGRATION_PRIORITY ? "Popular" : "Services"),
|
||||
disabled: methods.length === 0 && !websearch,
|
||||
category: provider ? "Web search" : integration.id in INTEGRATION_PRIORITY ? "Popular" : "Services",
|
||||
disabled: methods.length === 0 && !provider,
|
||||
gutter: connected ? () => <text fg={theme.success}>✓</text> : undefined,
|
||||
onSelect: () => {
|
||||
if (props.connectionOnly) {
|
||||
|
|
@ -87,14 +91,24 @@ export function DialogIntegration(
|
|||
? manageConnections(integration, methods, dialog, props.onConnected)
|
||||
: selectMethod(integration, methods, dialog, props.onConnected)
|
||||
}
|
||||
if (websearch) return manageIntegration(integration, methods, websearch, dialog)
|
||||
if (provider) return manageWebSearch(provider, dialog, integration, methods)
|
||||
return credentials.length
|
||||
? manageConnections(integration, methods, dialog, props.onConnected)
|
||||
: selectMethod(integration, methods, dialog, props.onConnected)
|
||||
},
|
||||
}
|
||||
}),
|
||||
)
|
||||
...providers
|
||||
.filter((provider) => props.integrationID === undefined && !integrationIDs.has(provider.id))
|
||||
.map((provider) => ({
|
||||
title: provider.name,
|
||||
value: provider.id,
|
||||
footer: data.location.websearch.provider() === provider.id ? "Web search default" : undefined,
|
||||
category: "Web search",
|
||||
onSelect: () => manageWebSearch(provider, dialog),
|
||||
})),
|
||||
]
|
||||
})
|
||||
|
||||
return (
|
||||
<DialogSelect
|
||||
|
|
@ -109,28 +123,30 @@ export function DialogIntegration(
|
|||
)
|
||||
}
|
||||
|
||||
function manageIntegration(
|
||||
integration: IntegrationInfo,
|
||||
methods: ConnectMethod[],
|
||||
websearch: NonNullable<IntegrationInfo["websearch"]>,
|
||||
function manageWebSearch(
|
||||
provider: WebSearchProvider,
|
||||
dialog: ReturnType<typeof useDialog>,
|
||||
integration?: IntegrationInfo,
|
||||
methods: ConnectMethod[] = [],
|
||||
) {
|
||||
const connected = integration.connections.length > 0
|
||||
dialog.replace(() => {
|
||||
const data = useData()
|
||||
const sdk = useSDK()
|
||||
const toast = useToast()
|
||||
const credentials = credentialConnections(integration)
|
||||
const selected = createMemo(() => data.location.websearch.provider() === integration.id)
|
||||
const credentials = integration ? credentialConnections(integration) : []
|
||||
const selected = createMemo(() => data.location.websearch.provider() === provider.id)
|
||||
const selectWebSearch = () => {
|
||||
void sdk.api.websearch.provider
|
||||
.select({
|
||||
providerID: integration.id,
|
||||
providerID: provider.id,
|
||||
location: location(data),
|
||||
})
|
||||
.then(async () => {
|
||||
await Promise.all([data.location.integration.refresh(), data.location.websearch.refresh()])
|
||||
toast.show({ variant: "success", message: `${integration.name} is now the web search default` })
|
||||
await Promise.all([
|
||||
...(integration ? [data.location.integration.refresh()] : []),
|
||||
data.location.websearch.refresh(),
|
||||
])
|
||||
toast.show({ variant: "success", message: `${provider.name} is now the web search default` })
|
||||
dialog.clear()
|
||||
})
|
||||
.catch(toast.error)
|
||||
|
|
@ -138,18 +154,15 @@ function manageIntegration(
|
|||
|
||||
return (
|
||||
<DialogSelect
|
||||
title={integration.name}
|
||||
title={provider.name}
|
||||
options={[
|
||||
{
|
||||
title: selected() ? "Web search default" : "Use for web search",
|
||||
value: "websearch",
|
||||
disabled: selected(),
|
||||
onSelect:
|
||||
websearch.connection === "required" && !connected
|
||||
? () => selectMethod(integration, methods, dialog, selectWebSearch)
|
||||
: selectWebSearch,
|
||||
onSelect: selectWebSearch,
|
||||
},
|
||||
...(methods.length
|
||||
...(integration && methods.length
|
||||
? [
|
||||
{
|
||||
title: credentials.length ? "Manage connections" : "Connect",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import type {
|
|||
AgentInfo,
|
||||
CommandInfo,
|
||||
FormFormInfo,
|
||||
FormIntegrationInfo,
|
||||
FormUrlInfo,
|
||||
IntegrationInfo,
|
||||
LocationRef,
|
||||
|
|
@ -39,7 +38,7 @@ const messageIDFromEvent = (eventID: string) => eventID.replace(/^evt_/, "msg_")
|
|||
// Global MCP elicitations temporarily use "global" instead of a real session ID, so the
|
||||
// server cannot recover their Location when settling them. Preserve the event Location
|
||||
// until MCP elicitations carry session ownership.
|
||||
export type FormInfo = (FormFormInfo | FormUrlInfo | FormIntegrationInfo) & { readonly location?: LocationRef }
|
||||
export type FormInfo = (FormFormInfo | FormUrlInfo) & { readonly location?: LocationRef }
|
||||
|
||||
type LocationData = {
|
||||
agent?: AgentInfo[]
|
||||
|
|
@ -49,7 +48,8 @@ type LocationData = {
|
|||
model?: ModelInfo[]
|
||||
provider?: ProviderV2Info[]
|
||||
reference?: ReferenceInfo[]
|
||||
websearchProvider?: string | null
|
||||
websearch?: { id: string; name: string }[]
|
||||
websearchSelected?: string | null
|
||||
// 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, Shell>
|
||||
|
|
@ -797,6 +797,9 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
case "config.updated":
|
||||
void result.location.websearch.refresh(event.location)
|
||||
break
|
||||
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 refreshes here rather than off integration.updated.
|
||||
case "mcp.status.changed":
|
||||
|
|
@ -1006,13 +1009,24 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
},
|
||||
},
|
||||
websearch: {
|
||||
list(location?: LocationRef) {
|
||||
return store.location[locationKey(location ?? defaultLocation())]?.websearch
|
||||
},
|
||||
provider(location?: LocationRef) {
|
||||
return store.location[locationKey(location ?? defaultLocation())]?.websearchProvider ?? undefined
|
||||
return store.location[locationKey(location ?? defaultLocation())]?.websearchSelected ?? undefined
|
||||
},
|
||||
async refresh(ref?: LocationRef) {
|
||||
const result = await sdk.api.websearch.provider.get({ location: locationQuery(ref ?? defaultLocation()) })
|
||||
const key = locationKey(result.location)
|
||||
setStore("location", key, { ...store.location[key], websearchProvider: result.data ?? null })
|
||||
const location = { location: locationQuery(ref ?? defaultLocation()) }
|
||||
const [providers, selected] = await Promise.all([
|
||||
sdk.api.websearch.provider.list(location),
|
||||
sdk.api.websearch.provider.selected(location),
|
||||
])
|
||||
const key = locationKey(providers.location)
|
||||
setStore("location", key, {
|
||||
...store.location[key],
|
||||
websearch: mutable(providers.data),
|
||||
websearchSelected: selected.data ?? null,
|
||||
})
|
||||
},
|
||||
},
|
||||
skill: {
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@ import { useSDK } from "../../context/sdk"
|
|||
import { SplitBorder } from "../../ui/border"
|
||||
import { useTuiConfig } from "../../config"
|
||||
import { useBindings, useOpencodeModeStack } from "../../keymap"
|
||||
import { DialogIntegration } from "../../component/dialog-integration"
|
||||
import { useDialog } from "../../ui/dialog"
|
||||
|
||||
const FORM_MODE = "form"
|
||||
|
||||
|
|
@ -144,51 +142,9 @@ function requestOptions(form: FormInfo) {
|
|||
|
||||
export function FormPrompt(props: { form: FormInfo }) {
|
||||
if (props.form.mode === "url") return <UrlPrompt form={props.form} />
|
||||
if (props.form.mode === "integration") return <IntegrationPrompt form={props.form} />
|
||||
return <FieldsPrompt form={props.form} />
|
||||
}
|
||||
|
||||
function IntegrationPrompt(props: { form: FormInfo & { mode: "integration" } }) {
|
||||
const sdk = useSDK()
|
||||
const dialog = useDialog()
|
||||
const { theme } = useTheme()
|
||||
let settled = false
|
||||
|
||||
onMount(() => {
|
||||
dialog.replace(
|
||||
() => (
|
||||
<DialogIntegration
|
||||
integrationID={props.form.integrationID}
|
||||
connectionOnly
|
||||
onConnected={() => {
|
||||
settled = true
|
||||
dialog.clear()
|
||||
void sdk.api.form.reply({ sessionID: props.form.sessionID, formID: props.form.id, answer: {} })
|
||||
}}
|
||||
/>
|
||||
),
|
||||
() => {
|
||||
if (settled) return
|
||||
void sdk.api.form.cancel({ sessionID: props.form.sessionID, formID: props.form.id })
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<box
|
||||
backgroundColor={theme.backgroundPanel}
|
||||
border={["left"]}
|
||||
borderColor={theme.accent}
|
||||
customBorderChars={SplitBorder.customBorderChars}
|
||||
>
|
||||
<box gap={1} paddingLeft={2} paddingRight={3} paddingTop={1} paddingBottom={1}>
|
||||
<text fg={theme.text}>{props.form.title ?? "Connect integration"}</text>
|
||||
<text fg={theme.textMuted}>Complete the connection dialog to continue.</text>
|
||||
</box>
|
||||
</box>
|
||||
)
|
||||
}
|
||||
|
||||
function UrlPrompt(props: { form: FormInfo & { mode: "url" } }) {
|
||||
const sdk = useSDK()
|
||||
const { theme } = useTheme()
|
||||
|
|
|
|||
|
|
@ -66,6 +66,9 @@ test("refreshes resources into reactive getters", async () => {
|
|||
location,
|
||||
data: [{ id: "build", request: { headers: {}, body: {} }, mode: "primary", hidden: false, permissions: [] }],
|
||||
})
|
||||
if (url.pathname === "/api/websearch/provider")
|
||||
return json({ location, data: [{ id: "standalone", name: "Standalone" }] })
|
||||
if (url.pathname === "/api/websearch/provider/selected") return json({ location, data: "standalone" })
|
||||
return undefined
|
||||
}, events)
|
||||
let data!: ReturnType<typeof useData>
|
||||
|
|
@ -101,6 +104,7 @@ test("refreshes resources into reactive getters", async () => {
|
|||
await data.session.refresh("ses_test")
|
||||
await data.session.message.refresh("ses_test")
|
||||
await data.location.agent.refresh()
|
||||
await data.location.websearch.refresh()
|
||||
|
||||
expect(data.session.get("ses_test")?.title).toBe("Test session")
|
||||
expect(data.session.message.ids("ses_test")).toEqual(["msg_first", "msg_second"])
|
||||
|
|
@ -109,6 +113,8 @@ test("refreshes resources into reactive getters", async () => {
|
|||
expect(app.captureCharFrame()).toContain("msg_second")
|
||||
expect(data.location.default()).toEqual({ directory, workspaceID: undefined })
|
||||
expect(data.location.agent.list(location)?.map((agent) => agent.id)).toEqual(["build"])
|
||||
expect(data.location.websearch.list(location)).toEqual([{ id: "standalone", name: "Standalone" }])
|
||||
expect(data.location.websearch.provider(location)).toBe("standalone")
|
||||
} finally {
|
||||
app.renderer.destroy()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,6 +121,9 @@ export function createFetch(override?: FetchHandler, events?: ReturnType<typeof
|
|||
if (url.pathname === "/api/reference")
|
||||
return json({ location: { directory, project: { id: "proj_test", directory } }, data: [] })
|
||||
if (url.pathname === "/api/websearch/provider") {
|
||||
return json({ location: { directory, project: { id: "proj_test", directory } }, data: [] })
|
||||
}
|
||||
if (url.pathname === "/api/websearch/provider/selected") {
|
||||
if (request.method === "POST") return new Response(null, { status: 204 })
|
||||
return json({ location: { directory, project: { id: "proj_test", directory } }, data: null })
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue