feat(sdk): expose web search
This commit is contained in:
parent
56f44dbcaa
commit
66220071b5
17 changed files with 416 additions and 0 deletions
|
|
@ -887,6 +887,23 @@ export interface DebugApi<E = never> {
|
|||
readonly location: DebugLocationOperation<E>
|
||||
}
|
||||
|
||||
type Endpoint26_0Request = Parameters<RawClient["server.search"]["search.query"]>[0]
|
||||
export type Endpoint26_0Input = {
|
||||
readonly location?: Endpoint26_0Request["query"]["location"]
|
||||
readonly query: Endpoint26_0Request["payload"]["query"]
|
||||
readonly providerID?: Endpoint26_0Request["payload"]["providerID"]
|
||||
readonly numResults?: Endpoint26_0Request["payload"]["numResults"]
|
||||
readonly livecrawl?: Endpoint26_0Request["payload"]["livecrawl"]
|
||||
readonly type?: Endpoint26_0Request["payload"]["type"]
|
||||
readonly contextMaxCharacters?: Endpoint26_0Request["payload"]["contextMaxCharacters"]
|
||||
}
|
||||
export type Endpoint26_0Output = EffectValue<ReturnType<RawClient["server.search"]["search.query"]>>
|
||||
export type SearchQueryOperation<E = never> = (input: Endpoint26_0Input) => Effect.Effect<Endpoint26_0Output, E>
|
||||
|
||||
export interface SearchApi<E = never> {
|
||||
readonly query: SearchQueryOperation<E>
|
||||
}
|
||||
|
||||
export interface AppApi<E = never> {
|
||||
readonly health: HealthApi<E>
|
||||
readonly location: LocationApi<E>
|
||||
|
|
@ -914,4 +931,5 @@ export interface AppApi<E = never> {
|
|||
readonly projectCopy: ProjectCopyApi<E>
|
||||
readonly vcs: VcsApi<E>
|
||||
readonly debug: DebugApi<E>
|
||||
readonly search: SearchApi<E>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1066,6 +1066,31 @@ const Endpoint25_0 = (raw: RawClient["server.debug"]) => () =>
|
|||
|
||||
const adaptGroup25 = (raw: RawClient["server.debug"]) => ({ location: Endpoint25_0(raw) })
|
||||
|
||||
type Endpoint26_0Request = Parameters<RawClient["server.search"]["search.query"]>[0]
|
||||
type Endpoint26_0Input = {
|
||||
readonly location?: Endpoint26_0Request["query"]["location"]
|
||||
readonly query: Endpoint26_0Request["payload"]["query"]
|
||||
readonly providerID?: Endpoint26_0Request["payload"]["providerID"]
|
||||
readonly numResults?: Endpoint26_0Request["payload"]["numResults"]
|
||||
readonly livecrawl?: Endpoint26_0Request["payload"]["livecrawl"]
|
||||
readonly type?: Endpoint26_0Request["payload"]["type"]
|
||||
readonly contextMaxCharacters?: Endpoint26_0Request["payload"]["contextMaxCharacters"]
|
||||
}
|
||||
const Endpoint26_0 = (raw: RawClient["server.search"]) => (input: Endpoint26_0Input) =>
|
||||
raw["search.query"]({
|
||||
query: { location: input["location"] },
|
||||
payload: {
|
||||
query: input["query"],
|
||||
providerID: input["providerID"],
|
||||
numResults: input["numResults"],
|
||||
livecrawl: input["livecrawl"],
|
||||
type: input["type"],
|
||||
contextMaxCharacters: input["contextMaxCharacters"],
|
||||
},
|
||||
}).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
const adaptGroup26 = (raw: RawClient["server.search"]) => ({ query: Endpoint26_0(raw) })
|
||||
|
||||
const adaptClient = (raw: RawClient) => ({
|
||||
health: adaptGroup0(raw["server.health"]),
|
||||
location: adaptGroup1(raw["server.location"]),
|
||||
|
|
@ -1093,6 +1118,7 @@ const adaptClient = (raw: RawClient) => ({
|
|||
projectCopy: adaptGroup23(raw["server.projectCopy"]),
|
||||
vcs: adaptGroup24(raw["server.vcs"]),
|
||||
debug: adaptGroup25(raw["server.debug"]),
|
||||
search: adaptGroup26(raw["server.search"]),
|
||||
})
|
||||
|
||||
export const make = (options?: { readonly baseUrl?: URL | string }) =>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export type {
|
|||
PluginApi,
|
||||
ProviderApi,
|
||||
ReferenceApi,
|
||||
SearchApi,
|
||||
SessionApi,
|
||||
SkillApi,
|
||||
} from "./api.js"
|
||||
|
|
@ -36,6 +37,7 @@ export { Provider } from "@opencode-ai/schema/provider"
|
|||
export { Pty } from "@opencode-ai/schema/pty"
|
||||
export { Question } from "@opencode-ai/schema/question"
|
||||
export { Reference } from "@opencode-ai/schema/reference"
|
||||
export { Search } from "@opencode-ai/schema/search"
|
||||
export { AbsolutePath, RelativePath } from "@opencode-ai/schema/schema"
|
||||
export { Session } from "@opencode-ai/schema/session"
|
||||
export { SessionInput } from "@opencode-ai/schema/session-input"
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import type {
|
|||
PluginApi as EffectPluginApi,
|
||||
ProviderApi as EffectProviderApi,
|
||||
ReferenceApi as EffectReferenceApi,
|
||||
SearchApi as EffectSearchApi,
|
||||
SessionApi as EffectSessionApi,
|
||||
SkillApi as EffectSkillApi,
|
||||
} from "../effect/api/api.js"
|
||||
|
|
@ -32,6 +33,7 @@ export type ModelApi = PromisifyApi<EffectModelApi<unknown>>
|
|||
export type PluginApi = PromisifyApi<EffectPluginApi<unknown>>
|
||||
export type ProviderApi = PromisifyApi<EffectProviderApi<unknown>>
|
||||
export type ReferenceApi = PromisifyApi<EffectReferenceApi<unknown>>
|
||||
export type SearchApi = PromisifyApi<EffectSearchApi<unknown>>
|
||||
export type SessionApi = PromisifyApi<EffectSessionApi<unknown>>
|
||||
export type SkillApi = PromisifyApi<EffectSkillApi<unknown>>
|
||||
|
||||
|
|
|
|||
|
|
@ -177,6 +177,8 @@ import type {
|
|||
VcsDiffInput,
|
||||
VcsDiffOutput,
|
||||
DebugLocationOutput,
|
||||
SearchQueryInput,
|
||||
SearchQueryOutput,
|
||||
} from "./types"
|
||||
import { ClientError } from "./client-error"
|
||||
|
||||
|
|
@ -1483,6 +1485,28 @@ export function make(options: ClientOptions) {
|
|||
requestOptions,
|
||||
),
|
||||
},
|
||||
search: {
|
||||
query: (input: SearchQueryInput, requestOptions?: RequestOptions) =>
|
||||
request<SearchQueryOutput>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/search`,
|
||||
query: { location: input["location"] },
|
||||
body: {
|
||||
query: input["query"],
|
||||
providerID: input["providerID"],
|
||||
numResults: input["numResults"],
|
||||
livecrawl: input["livecrawl"],
|
||||
type: input["type"],
|
||||
contextMaxCharacters: input["contextMaxCharacters"],
|
||||
},
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 503, 401],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6084,3 +6084,66 @@ export type VcsDiffOutput = {
|
|||
}
|
||||
|
||||
export type DebugLocationOutput = ReadonlyArray<{ readonly directory: string; readonly workspaceID?: string }>
|
||||
|
||||
export type SearchQueryInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
readonly query: {
|
||||
readonly query: string
|
||||
readonly providerID?: string
|
||||
readonly numResults?: number
|
||||
readonly livecrawl?: "fallback" | "preferred"
|
||||
readonly type?: "auto" | "fast" | "deep"
|
||||
readonly contextMaxCharacters?: number
|
||||
}["query"]
|
||||
readonly providerID?: {
|
||||
readonly query: string
|
||||
readonly providerID?: string
|
||||
readonly numResults?: number
|
||||
readonly livecrawl?: "fallback" | "preferred"
|
||||
readonly type?: "auto" | "fast" | "deep"
|
||||
readonly contextMaxCharacters?: number
|
||||
}["providerID"]
|
||||
readonly numResults?: {
|
||||
readonly query: string
|
||||
readonly providerID?: string
|
||||
readonly numResults?: number
|
||||
readonly livecrawl?: "fallback" | "preferred"
|
||||
readonly type?: "auto" | "fast" | "deep"
|
||||
readonly contextMaxCharacters?: number
|
||||
}["numResults"]
|
||||
readonly livecrawl?: {
|
||||
readonly query: string
|
||||
readonly providerID?: string
|
||||
readonly numResults?: number
|
||||
readonly livecrawl?: "fallback" | "preferred"
|
||||
readonly type?: "auto" | "fast" | "deep"
|
||||
readonly contextMaxCharacters?: number
|
||||
}["livecrawl"]
|
||||
readonly type?: {
|
||||
readonly query: string
|
||||
readonly providerID?: string
|
||||
readonly numResults?: number
|
||||
readonly livecrawl?: "fallback" | "preferred"
|
||||
readonly type?: "auto" | "fast" | "deep"
|
||||
readonly contextMaxCharacters?: number
|
||||
}["type"]
|
||||
readonly contextMaxCharacters?: {
|
||||
readonly query: string
|
||||
readonly providerID?: string
|
||||
readonly numResults?: number
|
||||
readonly livecrawl?: "fallback" | "preferred"
|
||||
readonly type?: "auto" | "fast" | "deep"
|
||||
readonly contextMaxCharacters?: number
|
||||
}["contextMaxCharacters"]
|
||||
}
|
||||
|
||||
export type SearchQueryOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: { readonly providerID: string; readonly text: string; readonly metadata?: JsonValue }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ export type {
|
|||
PluginApi,
|
||||
ProviderApi,
|
||||
ReferenceApi,
|
||||
SearchApi,
|
||||
SessionApi,
|
||||
SkillApi,
|
||||
} from "./api.js"
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ test("exposes every standard HTTP API group", () => {
|
|||
"projectCopy",
|
||||
"vcs",
|
||||
"debug",
|
||||
"search",
|
||||
])
|
||||
expect(Object.keys(client.debug)).toEqual(["location"])
|
||||
expect(Object.keys(client.message)).toEqual(["list"])
|
||||
|
|
@ -44,6 +45,7 @@ test("exposes every standard HTTP API group", () => {
|
|||
"attemptComplete",
|
||||
"attemptCancel",
|
||||
])
|
||||
expect(Object.keys(client.search)).toEqual(["query"])
|
||||
expect(Object.keys(client.file)).toEqual(["read", "list", "find"])
|
||||
expect(Object.keys(client.vcs)).toEqual(["status", "diff"])
|
||||
expect(Object.keys(client.pty)).toEqual(["list", "create", "get", "update", "remove"])
|
||||
|
|
@ -51,6 +53,32 @@ test("exposes every standard HTTP API group", () => {
|
|||
expect(Object.keys(client.project)).toEqual(["list", "current", "directories"])
|
||||
})
|
||||
|
||||
test("search.query uses the public HTTP contract", async () => {
|
||||
let request: Request | undefined
|
||||
const client = OpenCode.make({
|
||||
baseUrl: "http://localhost:3000",
|
||||
fetch: async (input, init) => {
|
||||
request = input instanceof Request ? input : new Request(input, init)
|
||||
return Response.json({
|
||||
location: { directory: "/tmp/project", project: { id: "proj_test", directory: "/tmp/project" } },
|
||||
data: { providerID: "exa", text: "result", metadata: { requestID: "req_test" } },
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
const result = await client.search.query({
|
||||
query: "opencode",
|
||||
providerID: "exa",
|
||||
numResults: 5,
|
||||
location: { directory: "/tmp/project" },
|
||||
})
|
||||
|
||||
expect(result.data).toEqual({ providerID: "exa", text: "result", metadata: { requestID: "req_test" } })
|
||||
expect(request?.method).toBe("POST")
|
||||
expect(request?.url).toBe("http://localhost:3000/api/search?location%5Bdirectory%5D=%2Ftmp%2Fproject")
|
||||
expect(await request?.json()).toEqual({ query: "opencode", providerID: "exa", numResults: 5 })
|
||||
})
|
||||
|
||||
test("file.read returns binary content from the public HTTP contract", async () => {
|
||||
let request: Request | undefined
|
||||
const client = OpenCode.make({
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import { ReferenceGroup } from "./groups/reference.js"
|
|||
import { Authorization } from "./middleware/authorization.js"
|
||||
import { LocationGroup } from "./groups/location.js"
|
||||
import { IntegrationGroup } from "./groups/integration.js"
|
||||
import { SearchGroup } from "./groups/search.js"
|
||||
import { McpGroup } from "./groups/mcp.js"
|
||||
import { CredentialGroup } from "./groups/credential.js"
|
||||
import { ProjectGroup } from "./groups/project.js"
|
||||
|
|
@ -38,6 +39,7 @@ type LocationGroups<LocationId extends HttpApiMiddleware.AnyId> =
|
|||
| HttpApiGroup.AddMiddleware<typeof GenerateGroup, LocationId>
|
||||
| HttpApiGroup.AddMiddleware<typeof ProviderGroup, LocationId>
|
||||
| HttpApiGroup.AddMiddleware<typeof IntegrationGroup, LocationId>
|
||||
| HttpApiGroup.AddMiddleware<typeof SearchGroup, LocationId>
|
||||
| HttpApiGroup.AddMiddleware<typeof McpGroup, LocationId>
|
||||
| HttpApiGroup.AddMiddleware<typeof CredentialGroup, LocationId>
|
||||
| HttpApiGroup.AddMiddleware<typeof ProjectGroup, LocationId>
|
||||
|
|
@ -168,6 +170,7 @@ const makeApiFromGroup = <
|
|||
.add(ProjectCopyGroup.middleware(locationMiddleware))
|
||||
.add(VcsGroup.middleware(locationMiddleware))
|
||||
.add(DebugGroup)
|
||||
.add(SearchGroup.middleware(locationMiddleware))
|
||||
.annotateMerge(
|
||||
OpenApi.annotations({
|
||||
title: "opencode HttpApi",
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ export const groupNames = {
|
|||
"server.generate": "generate",
|
||||
"server.provider": "provider",
|
||||
"server.integration": "integration",
|
||||
"server.search": "search",
|
||||
"server.credential": "credential",
|
||||
"server.form": "form",
|
||||
"server.permission": "permission",
|
||||
|
|
|
|||
31
packages/protocol/src/groups/search.ts
Normal file
31
packages/protocol/src/groups/search.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { Location } from "@opencode-ai/schema/location"
|
||||
import { Search } from "@opencode-ai/schema/search"
|
||||
import { Schema } from "effect"
|
||||
import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { InvalidRequestError, ServiceUnavailableError } from "../errors.js"
|
||||
import { LocationQuery, locationQueryOpenApi } from "./location.js"
|
||||
|
||||
export const SearchGroup = HttpApiGroup.make("server.search")
|
||||
.add(
|
||||
HttpApiEndpoint.post("search.query", "/api/search", {
|
||||
query: LocationQuery,
|
||||
payload: Schema.Struct(Search.Input.fields),
|
||||
success: Location.response(Search.Result),
|
||||
error: [InvalidRequestError, ServiceUnavailableError],
|
||||
})
|
||||
.annotateMerge(locationQueryOpenApi)
|
||||
.annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "v2.search.query",
|
||||
summary: "Search the web",
|
||||
description:
|
||||
"Run one web search through the selected integration. Specify a provider to override the configured default.",
|
||||
}),
|
||||
),
|
||||
)
|
||||
.annotateMerge(
|
||||
OpenApi.annotations({
|
||||
title: "search",
|
||||
description: "Location-scoped web search routes.",
|
||||
}),
|
||||
)
|
||||
|
|
@ -5,11 +5,13 @@ export { ClientError } from "@opencode-ai/client/effect"
|
|||
export {
|
||||
AbsolutePath,
|
||||
Agent,
|
||||
Integration,
|
||||
Location,
|
||||
Model,
|
||||
Prompt,
|
||||
Provider,
|
||||
RelativePath,
|
||||
Search,
|
||||
Session,
|
||||
SessionInput,
|
||||
SessionMessage,
|
||||
|
|
|
|||
|
|
@ -141,6 +141,40 @@ it.live(
|
|||
10_000,
|
||||
)
|
||||
|
||||
it.live("embedded client exposes integration-backed search", () =>
|
||||
withEmbedded("opencode-embedded-search-", (fixture) =>
|
||||
Effect.gen(function* () {
|
||||
const opencode = yield* fixture.sdk.OpenCode.create()
|
||||
const providerID = fixture.sdk.Integration.ID.make("embedded-search")
|
||||
yield* opencode.plugin({
|
||||
id: `embedded-search-${crypto.randomUUID()}`,
|
||||
effect: (ctx) =>
|
||||
ctx.integration.transform((draft) => {
|
||||
draft.update(providerID, (integration) => (integration.name = "Embedded search"))
|
||||
draft.capability.search.update({
|
||||
integrationID: providerID,
|
||||
capability: { type: "search", connection: "optional" },
|
||||
execute: (input) =>
|
||||
Effect.succeed({ text: `Found ${input.query}`, metadata: { source: "embedded" } }),
|
||||
})
|
||||
}),
|
||||
})
|
||||
|
||||
const result = yield* opencode.search.query({
|
||||
query: "opencode",
|
||||
providerID,
|
||||
location: location(fixture),
|
||||
})
|
||||
|
||||
expect(result.data).toEqual({
|
||||
providerID,
|
||||
text: "Found opencode",
|
||||
metadata: { source: "embedded" },
|
||||
})
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
it.live(
|
||||
"Location-owned runner events reach the ready global client",
|
||||
() =>
|
||||
|
|
|
|||
|
|
@ -360,6 +360,8 @@ import type {
|
|||
V2QuestionRequestListResponses,
|
||||
V2ReferenceListErrors,
|
||||
V2ReferenceListResponses,
|
||||
V2SearchQueryErrors,
|
||||
V2SearchQueryResponses,
|
||||
V2SessionActiveErrors,
|
||||
V2SessionActiveResponses,
|
||||
V2SessionBackgroundErrors,
|
||||
|
|
@ -8119,6 +8121,56 @@ export class Debug extends HeyApiClient {
|
|||
}
|
||||
}
|
||||
|
||||
export class Search extends HeyApiClient {
|
||||
/**
|
||||
* Search the web
|
||||
*
|
||||
* Run one web search through the selected integration. Specify a provider to override the configured default.
|
||||
*/
|
||||
public query<ThrowOnError extends boolean = false>(
|
||||
parameters?: {
|
||||
location?: {
|
||||
directory?: string | null
|
||||
workspace?: string | null
|
||||
} | null
|
||||
query?: string
|
||||
providerID?: string
|
||||
numResults?: number
|
||||
livecrawl?: "fallback" | "preferred"
|
||||
type?: "auto" | "fast" | "deep"
|
||||
contextMaxCharacters?: number
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
const params = buildClientParams(
|
||||
[parameters],
|
||||
[
|
||||
{
|
||||
args: [
|
||||
{ in: "query", key: "location" },
|
||||
{ in: "body", key: "query" },
|
||||
{ in: "body", key: "providerID" },
|
||||
{ in: "body", key: "numResults" },
|
||||
{ in: "body", key: "livecrawl" },
|
||||
{ in: "body", key: "type" },
|
||||
{ in: "body", key: "contextMaxCharacters" },
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
return (options?.client ?? this.client).post<V2SearchQueryResponses, V2SearchQueryErrors, ThrowOnError>({
|
||||
url: "/api/search",
|
||||
...options,
|
||||
...params,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options?.headers,
|
||||
...params.headers,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class V2 extends HeyApiClient {
|
||||
private _health?: Health
|
||||
get health(): Health {
|
||||
|
|
@ -8244,6 +8296,11 @@ export class V2 extends HeyApiClient {
|
|||
get debug(): Debug {
|
||||
return (this._debug ??= new Debug({ client: this.client }))
|
||||
}
|
||||
|
||||
private _search?: Search
|
||||
get search(): Search {
|
||||
return (this._search ??= new Search({ client: this.client }))
|
||||
}
|
||||
}
|
||||
|
||||
export class OpencodeClient extends HeyApiClient {
|
||||
|
|
|
|||
|
|
@ -6693,6 +6693,12 @@ export type ProjectCopyCopy = {
|
|||
|
||||
export type VcsMode = "working" | "branch"
|
||||
|
||||
export type SearchResult = {
|
||||
providerID: string
|
||||
text: string
|
||||
metadata?: unknown
|
||||
}
|
||||
|
||||
export type EventModelsDevRefreshed = {
|
||||
id: string
|
||||
type: "models-dev.refreshed"
|
||||
|
|
@ -11460,6 +11466,12 @@ export type VcsFileStatusV2 = {
|
|||
|
||||
export type VcsMode2 = "working" | "branch"
|
||||
|
||||
export type SearchResult2 = {
|
||||
providerID: string
|
||||
text: string
|
||||
metadata?: unknown
|
||||
}
|
||||
|
||||
export type AuthRemoveData = {
|
||||
body?: never
|
||||
path: {
|
||||
|
|
@ -19273,6 +19285,54 @@ export type V2DebugLocationResponses = {
|
|||
|
||||
export type V2DebugLocationResponse = V2DebugLocationResponses[keyof V2DebugLocationResponses]
|
||||
|
||||
export type V2SearchQueryData = {
|
||||
body: {
|
||||
query: string
|
||||
providerID?: string
|
||||
numResults?: number
|
||||
livecrawl?: "fallback" | "preferred"
|
||||
type?: "auto" | "fast" | "deep"
|
||||
contextMaxCharacters?: number
|
||||
}
|
||||
path?: never
|
||||
query?: {
|
||||
location?: {
|
||||
directory?: string | null
|
||||
workspace?: string | null
|
||||
} | null
|
||||
}
|
||||
url: "/api/search"
|
||||
}
|
||||
|
||||
export type V2SearchQueryErrors = {
|
||||
/**
|
||||
* InvalidRequestError
|
||||
*/
|
||||
400: InvalidRequestError1 | InvalidRequestErrorV2
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
/**
|
||||
* ServiceUnavailableError
|
||||
*/
|
||||
503: ServiceUnavailableErrorV2
|
||||
}
|
||||
|
||||
export type V2SearchQueryError = V2SearchQueryErrors[keyof V2SearchQueryErrors]
|
||||
|
||||
export type V2SearchQueryResponses = {
|
||||
/**
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
data: SearchResult2
|
||||
}
|
||||
}
|
||||
|
||||
export type V2SearchQueryResponse = V2SearchQueryResponses[keyof V2SearchQueryResponses]
|
||||
|
||||
export type PtyConnectData = {
|
||||
body?: never
|
||||
path: {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import { QuestionHandler } from "./handlers/question"
|
|||
import { ReferenceHandler } from "./handlers/reference"
|
||||
import { LocationHandler } from "./handlers/location"
|
||||
import { IntegrationHandler } from "./handlers/integration"
|
||||
import { SearchHandler } from "./handlers/search"
|
||||
import { McpHandler } from "./handlers/mcp"
|
||||
import { CredentialHandler } from "./handlers/credential"
|
||||
import { ProjectHandler } from "./handlers/project"
|
||||
|
|
@ -38,6 +39,7 @@ export const handlers = Layer.mergeAll(
|
|||
GenerateHandler,
|
||||
ProviderHandler,
|
||||
IntegrationHandler,
|
||||
SearchHandler,
|
||||
McpHandler,
|
||||
CredentialHandler,
|
||||
ProjectHandler,
|
||||
|
|
|
|||
62
packages/server/src/handlers/search.ts
Normal file
62
packages/server/src/handlers/search.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import { PluginSupervisor } from "@opencode-ai/core/plugin/supervisor"
|
||||
import { Search } from "@opencode-ai/core/search"
|
||||
import { InvalidRequestError, ServiceUnavailableError } from "@opencode-ai/protocol/errors"
|
||||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
import { Api } from "../api"
|
||||
import { response } from "../location"
|
||||
|
||||
export const SearchHandler = HttpApiBuilder.group(Api, "server.search", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
return handlers.handle(
|
||||
"search.query",
|
||||
Effect.fn("server.search.query")(function* (request) {
|
||||
const plugins = yield* PluginSupervisor.Service
|
||||
yield* plugins.ready.pipe(
|
||||
Effect.timeoutOrElse({
|
||||
duration: "5 seconds",
|
||||
orElse: () =>
|
||||
Effect.fail(
|
||||
new ServiceUnavailableError({
|
||||
message: "Search integration initialization timed out",
|
||||
service: "search",
|
||||
}),
|
||||
),
|
||||
}),
|
||||
)
|
||||
const search = yield* Search.Service
|
||||
return yield* response(
|
||||
search.query(request.payload).pipe(
|
||||
Effect.catchTags({
|
||||
"Search.ProviderRequired": () =>
|
||||
new InvalidRequestError({
|
||||
message: "Search provider is required",
|
||||
kind: "search_provider_required",
|
||||
field: "providerID",
|
||||
}),
|
||||
"Search.ProviderNotFound": (error) =>
|
||||
new InvalidRequestError({
|
||||
message: `Search provider not found: ${error.providerID}`,
|
||||
kind: "search_provider_not_found",
|
||||
field: "providerID",
|
||||
}),
|
||||
"Search.ConnectionRequired": (error) =>
|
||||
new InvalidRequestError({
|
||||
message: `Search provider requires a connection: ${error.providerID}`,
|
||||
kind: "search_connection_required",
|
||||
field: "providerID",
|
||||
}),
|
||||
"Search.Cancelled": () =>
|
||||
new InvalidRequestError({ message: "Search cancelled", kind: "search_cancelled" }),
|
||||
"Search.Request": (error) =>
|
||||
new ServiceUnavailableError({
|
||||
message: `Search request failed: ${error.providerID}`,
|
||||
service: error.providerID,
|
||||
}),
|
||||
}),
|
||||
),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue