refactor(search): isolate provider schemas
This commit is contained in:
parent
228db3dd9a
commit
97aa6713b5
14 changed files with 151 additions and 227 deletions
|
|
@ -9,11 +9,11 @@ import { requests, resetSearchFixture, searchIntegrationTest } from "./search-fi
|
|||
|
||||
beforeEach(() => {
|
||||
resetSearchFixture(
|
||||
JSON.stringify({
|
||||
`event: message\ndata: ${JSON.stringify({
|
||||
jsonrpc: "2.0",
|
||||
id: 1,
|
||||
result: { content: [{ type: "text", text: "search results" }] },
|
||||
}),
|
||||
result: { content: [{ type: "text", text: "search results", _meta: { searchTime: 123 } }] },
|
||||
})}\n\n`,
|
||||
)
|
||||
})
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ describe("built-in search integrations", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
it.effect("registers Exa and maps search hints to its MCP tool", () =>
|
||||
it.effect("registers Exa with its MCP schema", () =>
|
||||
Effect.gen(function* () {
|
||||
const integrations = yield* Integration.Service
|
||||
yield* SearchExa.Plugin.effect(host({ integration: integrationHost(integrations) }))
|
||||
|
|
@ -59,16 +59,10 @@ describe("built-in search integrations", () => {
|
|||
if (!provider) return yield* Effect.die("Expected Exa search provider")
|
||||
expect(
|
||||
yield* provider.execute(
|
||||
{
|
||||
query: "effect typescript",
|
||||
numResults: 3,
|
||||
livecrawl: "preferred",
|
||||
type: "fast",
|
||||
contextMaxCharacters: 2500,
|
||||
},
|
||||
{ query: "effect typescript" },
|
||||
{ credential: Credential.Key.make({ type: "key", key: "exa secret" }) },
|
||||
),
|
||||
).toEqual({ text: "search results" })
|
||||
).toEqual({ text: "search results", metadata: { searchTime: 123 } })
|
||||
expect(requests).toEqual([
|
||||
{
|
||||
url: `${SearchExa.endpoint}?exaApiKey=exa+secret`,
|
||||
|
|
@ -79,13 +73,7 @@ describe("built-in search integrations", () => {
|
|||
method: "tools/call",
|
||||
params: {
|
||||
name: "web_search_exa",
|
||||
arguments: {
|
||||
query: "effect typescript",
|
||||
type: "fast",
|
||||
numResults: 3,
|
||||
livecrawl: "preferred",
|
||||
contextMaxCharacters: 2500,
|
||||
},
|
||||
arguments: { query: "effect typescript" },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -95,6 +83,29 @@ describe("built-in search integrations", () => {
|
|||
|
||||
it.effect("registers Parallel and keeps its credential in the authorization header", () =>
|
||||
Effect.gen(function* () {
|
||||
resetSearchFixture(
|
||||
JSON.stringify({
|
||||
jsonrpc: "2.0",
|
||||
id: 1,
|
||||
result: {
|
||||
content: [{ type: "text", text: "search results" }],
|
||||
structuredContent: {
|
||||
search_id: "search_1",
|
||||
results: [
|
||||
{
|
||||
url: "https://effect.website",
|
||||
title: "Effect",
|
||||
publish_date: null,
|
||||
excerpts: ["Effect documentation"],
|
||||
},
|
||||
],
|
||||
warnings: null,
|
||||
usage: [{ name: "sku_search", count: 1 }],
|
||||
session_id: "ses_parallel",
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
const integrations = yield* Integration.Service
|
||||
yield* SearchParallel.Plugin.effect(host({ integration: integrationHost(integrations) }))
|
||||
const provider = yield* integrations.search.get(Integration.ID.make("parallel"))
|
||||
|
|
@ -107,7 +118,23 @@ describe("built-in search integrations", () => {
|
|||
credential: Credential.Key.make({ type: "key", key: "parallel-secret" }),
|
||||
},
|
||||
)
|
||||
expect(output).toEqual({ text: "search results" })
|
||||
expect(output).toEqual({
|
||||
text: "search results",
|
||||
metadata: {
|
||||
search_id: "search_1",
|
||||
results: [
|
||||
{
|
||||
url: "https://effect.website",
|
||||
title: "Effect",
|
||||
publish_date: null,
|
||||
excerpts: ["Effect documentation"],
|
||||
},
|
||||
],
|
||||
warnings: null,
|
||||
usage: [{ name: "sku_search", count: 1 }],
|
||||
session_id: "ses_parallel",
|
||||
},
|
||||
})
|
||||
expect(requests[0]).toMatchObject({
|
||||
url: SearchParallel.endpoint,
|
||||
headers: { authorization: "Bearer parallel-secret" },
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { beforeEach, describe, expect, test } from "bun:test"
|
||||
import { Effect, Layer, Schema } from "effect"
|
||||
import { beforeEach, describe, expect } from "bun:test"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { PermissionV2 } from "@opencode-ai/core/permission"
|
||||
|
|
@ -20,36 +20,6 @@ const webSearchToolNode = makeLocationNode({
|
|||
})
|
||||
|
||||
const sessionID = SessionV2.ID.make("ses_websearch_test")
|
||||
const payload = (text: string) =>
|
||||
JSON.stringify({
|
||||
jsonrpc: "2.0",
|
||||
id: 1,
|
||||
result: { content: [{ type: "text", text }] },
|
||||
})
|
||||
|
||||
describe("WebSearchTool input", () => {
|
||||
test("rejects out-of-range numeric controls", () => {
|
||||
const decode = Schema.decodeUnknownSync(WebSearchTool.Input)
|
||||
expect(() => decode({ query: "x", numResults: 0 })).toThrow()
|
||||
expect(() => decode({ query: "x", numResults: WebSearchTool.MAX_NUM_RESULTS + 1 })).toThrow()
|
||||
expect(() => decode({ query: "x", contextMaxCharacters: WebSearchTool.MAX_CONTEXT_CHARACTERS + 1 })).toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
describe("WebSearchTool MCP response parser", () => {
|
||||
test("parses plain JSON-RPC responses", async () => {
|
||||
expect(await Effect.runPromise(WebSearchTool.parseResponse(payload("search results")))).toBe("search results")
|
||||
})
|
||||
|
||||
test("parses SSE JSON-RPC responses and ignores non-JSON frames", async () => {
|
||||
expect(
|
||||
await Effect.runPromise(
|
||||
WebSearchTool.parseResponse(`data: [DONE]\nevent: message\ndata: ${payload("search results")}\n\n`),
|
||||
),
|
||||
).toBe("search results")
|
||||
})
|
||||
})
|
||||
|
||||
const assertions: PermissionV2.AssertInput[] = []
|
||||
const queries: Search.QueryInput[] = []
|
||||
let result = new Search.Result({ providerID: Integration.ID.make("exa"), text: "search results" })
|
||||
|
|
@ -105,13 +75,7 @@ describe("WebSearchTool registration", () => {
|
|||
type: "tool-call",
|
||||
id: "call-search",
|
||||
name: "websearch",
|
||||
input: {
|
||||
query: "effect typescript",
|
||||
numResults: 3,
|
||||
livecrawl: "preferred",
|
||||
type: "fast",
|
||||
contextMaxCharacters: 2500,
|
||||
},
|
||||
input: { query: "effect typescript" },
|
||||
},
|
||||
}),
|
||||
).toEqual({ type: "text", value: "search results" })
|
||||
|
|
@ -121,23 +85,13 @@ describe("WebSearchTool registration", () => {
|
|||
action: "websearch",
|
||||
resources: ["effect typescript"],
|
||||
save: ["*"],
|
||||
metadata: {
|
||||
query: "effect typescript",
|
||||
numResults: 3,
|
||||
livecrawl: "preferred",
|
||||
type: "fast",
|
||||
contextMaxCharacters: 2500,
|
||||
},
|
||||
metadata: { query: "effect typescript" },
|
||||
},
|
||||
])
|
||||
expect(queries).toEqual([
|
||||
{
|
||||
sessionID,
|
||||
query: "effect typescript",
|
||||
numResults: 3,
|
||||
livecrawl: "preferred",
|
||||
type: "fast",
|
||||
contextMaxCharacters: 2500,
|
||||
},
|
||||
])
|
||||
}),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue