@nyte-ai/plugin 0.0.25-beta.1 → 0.0.25-nightly.20261025.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD-PARTY-NOTICES.md +13 -5
- package/dist/chunks/bash-description-0ah30s12.js +33 -0
- package/dist/chunks/bash-description-4cdbmar0.js +124 -0
- package/dist/chunks/bash-description-4h0hnsv4.js +68 -0
- package/dist/chunks/bash-description-5dpf5hpt.js +724 -0
- package/dist/chunks/bash-description-9gw9ets8.js +162 -0
- package/dist/chunks/bash-description-b78pre8c.js +147 -0
- package/dist/chunks/{bash-description-eqp7ap2w.js → bash-description-vg0xecwm.js} +16 -5
- package/dist/src/acp/connection.js +1 -1
- package/dist/src/acp/index.js +1 -1
- package/dist/src/claude-code-sdk/index.js +1 -0
- package/dist/src/codemode.js +148 -41
- package/dist/src/core.d.ts +1 -1
- package/dist/src/rename.d.ts +7 -22
- package/dist/src/rename.js +115 -48
- package/dist/src/subagents.d.ts +30 -0
- package/dist/src/subagents.js +323 -0
- package/dist/src/tool-search.d.ts +49 -0
- package/dist/src/tool-search.js +14 -0
- package/dist/src/web/body.d.ts +3 -0
- package/dist/src/web/body.js +6 -0
- package/dist/src/web/fetch.d.ts +16 -0
- package/dist/src/web/fetch.js +8 -0
- package/dist/src/web/html-markdown.d.ts +9 -0
- package/dist/src/web/html-markdown.js +10 -0
- package/dist/src/web/index.d.ts +24 -0
- package/dist/src/web/index.js +93 -0
- package/dist/src/web/providers.d.ts +60 -0
- package/dist/src/web/providers.js +11 -0
- package/dist/src/web/search.d.ts +21 -0
- package/dist/src/web/search.js +6 -0
- package/package.json +16 -7
- package/dist/src/web-search.d.ts +0 -78
- package/dist/src/web-search.js +0 -219
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export type Fetch = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
2
|
+
/** Reads a response body, throwing once its declared or streamed size passes `limit` bytes. */
|
|
3
|
+
export declare function readCapped(response: Response, limit: number): Promise<Uint8Array>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import type { Static } from "typebox";
|
|
3
|
+
import type { ToolDefinition } from "@nyte-ai/plugin/core";
|
|
4
|
+
import type { Fetch } from "./body.ts";
|
|
5
|
+
declare const fetchParameters: Type.TObject<{
|
|
6
|
+
url: Type.TString;
|
|
7
|
+
format: Type.TOptional<Type.TUnion<[Type.TLiteral<"markdown">, Type.TLiteral<"text">, Type.TLiteral<"html">]>>;
|
|
8
|
+
timeout: Type.TOptional<Type.TNumber>;
|
|
9
|
+
}>;
|
|
10
|
+
/**
|
|
11
|
+
* The `web_fetch` tool: GETs an http(s) URL through `fetch`, following at most ten
|
|
12
|
+
* redirects that stay on http(s), and returns HTML as markdown, text or itself, other
|
|
13
|
+
* text as is, and an image as a file. Bodies stop at 5 MiB; text shown inline at 50 KiB.
|
|
14
|
+
*/
|
|
15
|
+
export declare function webFetch(fetch: Fetch): ToolDefinition<Static<typeof fetchParameters>>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const MAX_MARKDOWN_BYTES: number;
|
|
2
|
+
/**
|
|
3
|
+
* Converts HTML to Markdown in one streaming pass: drops scripts, styles, the head and
|
|
4
|
+
* hidden elements; keeps headings, lists, GFM tables, fenced code, links and images;
|
|
5
|
+
* stops nesting past 10,000 levels and caps the result at `MAX_MARKDOWN_BYTES`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function htmlToMarkdown(html: string): string;
|
|
8
|
+
/** Returns an HTML document's text outside scripts, styles and embeds, trimmed. */
|
|
9
|
+
export declare function htmlToText(html: string): string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Plugin } from "@nyte-ai/plugin/core";
|
|
2
|
+
import type { Fetch } from "./body.ts";
|
|
3
|
+
export { adapters, PROVIDERS } from "./providers.ts";
|
|
4
|
+
export type { SearchInput, ShippedProvider } from "./providers.ts";
|
|
5
|
+
/**
|
|
6
|
+
* Returns the web plugin sending through `fetch`: `web_fetch`, and `web_search`
|
|
7
|
+
* routed by the host setting `web_search.provider` to any provider in the
|
|
8
|
+
* search registry. It adds Exa, Tavily, Firecrawl and Parallel there, each
|
|
9
|
+
* keyed from the plugin's secrets under its id (set with `/websearch-key`) or
|
|
10
|
+
* on its keyless route without one. The setting `off`, or an empty registry,
|
|
11
|
+
* drops `web_search`.
|
|
12
|
+
*
|
|
13
|
+
* @category plugins
|
|
14
|
+
* @since 0.1.0
|
|
15
|
+
*/
|
|
16
|
+
export declare function webPlugin(fetch: Fetch): Plugin;
|
|
17
|
+
/**
|
|
18
|
+
* The web plugin over the global `fetch`.
|
|
19
|
+
*
|
|
20
|
+
* @category plugins
|
|
21
|
+
* @since 0.1.0
|
|
22
|
+
*/
|
|
23
|
+
declare const _default: Plugin;
|
|
24
|
+
export default _default;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import"../../chunks/bash-description-0ah30s12.js";
|
|
2
|
+
import"../../chunks/bash-description-5dpf5hpt.js";
|
|
3
|
+
import {
|
|
4
|
+
webFetch2
|
|
5
|
+
} from "../../chunks/bash-description-4cdbmar0.js";
|
|
6
|
+
import {
|
|
7
|
+
PROVIDERS2,
|
|
8
|
+
adapters2,
|
|
9
|
+
shippedProviders2
|
|
10
|
+
} from "../../chunks/bash-description-9gw9ets8.js";
|
|
11
|
+
import {
|
|
12
|
+
webSearch2
|
|
13
|
+
} from "../../chunks/bash-description-4h0hnsv4.js";
|
|
14
|
+
|
|
15
|
+
// src/web/index.ts
|
|
16
|
+
import { standard } from "@nyte-ai/schema";
|
|
17
|
+
import { Type } from "typebox";
|
|
18
|
+
var OFF = { value: "off", label: "Off" };
|
|
19
|
+
var provider = {
|
|
20
|
+
id: "web_search.provider",
|
|
21
|
+
label: "Web search provider",
|
|
22
|
+
schema: standard(Type.String({ minLength: 1 })),
|
|
23
|
+
choices: [...PROVIDERS2.map((known) => ({ value: known.id, label: known.name })), OFF],
|
|
24
|
+
default: "exa",
|
|
25
|
+
scope: "host"
|
|
26
|
+
};
|
|
27
|
+
var keyCommand = {
|
|
28
|
+
name: "websearch-key",
|
|
29
|
+
title: "Set a web search key",
|
|
30
|
+
description: "Store a web search provider's API key: <provider> <key>; no key removes it.",
|
|
31
|
+
complete(prefix) {
|
|
32
|
+
const typed = prefix.trimStart();
|
|
33
|
+
if (/\s/u.test(typed))
|
|
34
|
+
return [];
|
|
35
|
+
return PROVIDERS2.filter((known) => known.id.startsWith(typed.toLowerCase())).map((known) => ({
|
|
36
|
+
value: `${known.id} `,
|
|
37
|
+
label: known.id
|
|
38
|
+
}));
|
|
39
|
+
},
|
|
40
|
+
async run(text, { api }) {
|
|
41
|
+
const [named = "", key = ""] = text.split(/\s+(.*)/su);
|
|
42
|
+
const chosen = PROVIDERS2.find((known) => known.id === named.toLowerCase());
|
|
43
|
+
if (chosen === undefined)
|
|
44
|
+
throw new Error(`Name a provider first: ${PROVIDERS2.map((known) => known.id).join(", ")}.`);
|
|
45
|
+
if (key === "")
|
|
46
|
+
await api.storage.secrets.delete(chosen.id);
|
|
47
|
+
else
|
|
48
|
+
await api.storage.secrets.set(chosen.id, key);
|
|
49
|
+
return `Key for ${chosen.id} ${key === "" ? "removed" : "saved"}.`;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
function webPlugin(fetch) {
|
|
53
|
+
return {
|
|
54
|
+
id: "web",
|
|
55
|
+
contribute: {
|
|
56
|
+
settings: [provider],
|
|
57
|
+
commands: [keyCommand],
|
|
58
|
+
tools: [webFetch2(fetch)]
|
|
59
|
+
},
|
|
60
|
+
async activate(api) {
|
|
61
|
+
for (const shipped of shippedProviders2(fetch, (id) => api.storage.secrets.get(id)))
|
|
62
|
+
api.contribute.searchProviders.add(shipped);
|
|
63
|
+
let selected = await api.contribute.settings.value(provider.id);
|
|
64
|
+
(async () => {
|
|
65
|
+
for await (const value of api.contribute.settings.watch(provider.id)) {
|
|
66
|
+
if (value === selected)
|
|
67
|
+
continue;
|
|
68
|
+
selected = value;
|
|
69
|
+
api.reload();
|
|
70
|
+
}
|
|
71
|
+
})();
|
|
72
|
+
api.contribute.settings.add((draft, siblings) => draft.wrap(provider.id, (inner) => ({
|
|
73
|
+
...inner,
|
|
74
|
+
choices: [
|
|
75
|
+
...siblings.searchProviders.list().map((known) => ({ value: known.id, label: known.name })),
|
|
76
|
+
OFF
|
|
77
|
+
]
|
|
78
|
+
})));
|
|
79
|
+
api.contribute.tools.add((draft, siblings) => {
|
|
80
|
+
if (selected === OFF.value || siblings.searchProviders.list().length === 0)
|
|
81
|
+
return;
|
|
82
|
+
draft.set("web_search", webSearch2(api, provider.id));
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
var web_default = webPlugin((input, init) => globalThis.fetch(input, init));
|
|
88
|
+
export {
|
|
89
|
+
PROVIDERS2 as PROVIDERS,
|
|
90
|
+
adapters2 as adapters,
|
|
91
|
+
web_default as default,
|
|
92
|
+
webPlugin
|
|
93
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { SearchProvider, SearchResult } from "@nyte-ai/plugin/core";
|
|
2
|
+
import type { Fetch } from "./body.ts";
|
|
3
|
+
/**
|
|
4
|
+
* The search providers the web plugin ships, by id and display name.
|
|
5
|
+
*
|
|
6
|
+
* @category plugins
|
|
7
|
+
* @since 0.1.0
|
|
8
|
+
*/
|
|
9
|
+
export declare const PROVIDERS: readonly [{
|
|
10
|
+
readonly id: "exa";
|
|
11
|
+
readonly name: "Exa";
|
|
12
|
+
}, {
|
|
13
|
+
readonly id: "tavily";
|
|
14
|
+
readonly name: "Tavily";
|
|
15
|
+
}, {
|
|
16
|
+
readonly id: "firecrawl";
|
|
17
|
+
readonly name: "Firecrawl";
|
|
18
|
+
}, {
|
|
19
|
+
readonly id: "parallel";
|
|
20
|
+
readonly name: "Parallel";
|
|
21
|
+
}];
|
|
22
|
+
/**
|
|
23
|
+
* Names a search provider the web plugin ships.
|
|
24
|
+
*
|
|
25
|
+
* @category plugins
|
|
26
|
+
* @since 0.1.0
|
|
27
|
+
*/
|
|
28
|
+
export type ShippedProvider = (typeof PROVIDERS)[number]["id"];
|
|
29
|
+
/**
|
|
30
|
+
* Returns what one adapter call needs: the query, the provider's key or
|
|
31
|
+
* undefined for its keyless route, the fetch to send with, and a signal.
|
|
32
|
+
*
|
|
33
|
+
* @category plugins
|
|
34
|
+
* @since 0.1.0
|
|
35
|
+
*/
|
|
36
|
+
export interface SearchInput {
|
|
37
|
+
readonly query: string;
|
|
38
|
+
readonly key: string | undefined;
|
|
39
|
+
readonly fetch: Fetch;
|
|
40
|
+
readonly signal: AbortSignal;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* One adapter per provider: Tavily over its REST endpoint, the others over
|
|
44
|
+
* one stateless MCP `tools/call` to their public route. Each sends through
|
|
45
|
+
* the given fetch only, reads at most 256 KiB, and returns the hits it recognised.
|
|
46
|
+
*
|
|
47
|
+
* @category plugins
|
|
48
|
+
* @since 0.1.0
|
|
49
|
+
*/
|
|
50
|
+
export declare const adapters: {
|
|
51
|
+
readonly [P in ShippedProvider]: (input: SearchInput) => Promise<readonly SearchResult[]>;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Returns the shipped providers for the search registry, each reading its key
|
|
55
|
+
* through `key` on every search and sending through `fetch`.
|
|
56
|
+
*
|
|
57
|
+
* @category plugins
|
|
58
|
+
* @since 0.1.0
|
|
59
|
+
*/
|
|
60
|
+
export declare function shippedProviders(fetch: Fetch, key: (provider: ShippedProvider) => Promise<string | undefined>): readonly SearchProvider[];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PROVIDERS2,
|
|
3
|
+
adapters2,
|
|
4
|
+
shippedProviders2
|
|
5
|
+
} from "../../chunks/bash-description-9gw9ets8.js";
|
|
6
|
+
import"../../chunks/bash-description-0ah30s12.js";
|
|
7
|
+
export {
|
|
8
|
+
PROVIDERS2 as PROVIDERS,
|
|
9
|
+
adapters2 as adapters,
|
|
10
|
+
shippedProviders2 as shippedProviders
|
|
11
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import type { Static } from "typebox";
|
|
3
|
+
import type { PluginApi, ToolDefinition } from "@nyte-ai/plugin/core";
|
|
4
|
+
declare const searchOutput: Type.TObject<{
|
|
5
|
+
provider: Type.TString;
|
|
6
|
+
results: Type.TArray<Type.TObject<{
|
|
7
|
+
url: Type.TString;
|
|
8
|
+
title: Type.TOptional<Type.TString>;
|
|
9
|
+
content: Type.TOptional<Type.TString>;
|
|
10
|
+
published: Type.TOptional<Type.TNumber>;
|
|
11
|
+
}>>;
|
|
12
|
+
}>;
|
|
13
|
+
type SearchOutput = Static<typeof searchOutput>;
|
|
14
|
+
/**
|
|
15
|
+
* The `web_search` tool: asks the registered provider the setting `setting` names,
|
|
16
|
+
* falling back to Exa and then to the first one registered.
|
|
17
|
+
*/
|
|
18
|
+
export declare function webSearch(api: PluginApi, setting: string): ToolDefinition<{
|
|
19
|
+
query: string;
|
|
20
|
+
}, SearchOutput>;
|
|
21
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nyte-ai/plugin",
|
|
3
|
-
"version": "0.0.25-
|
|
3
|
+
"version": "0.0.25-nightly.20261025.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -79,13 +79,21 @@
|
|
|
79
79
|
"types": "./dist/src/rename.d.ts",
|
|
80
80
|
"import": "./dist/src/rename.js"
|
|
81
81
|
},
|
|
82
|
+
"./core/subagents": {
|
|
83
|
+
"types": "./dist/src/subagents.d.ts",
|
|
84
|
+
"import": "./dist/src/subagents.js"
|
|
85
|
+
},
|
|
82
86
|
"./core/telemetry": {
|
|
83
87
|
"types": "./dist/src/telemetry.d.ts",
|
|
84
88
|
"import": "./dist/src/telemetry.js"
|
|
85
89
|
},
|
|
86
|
-
"./core/
|
|
87
|
-
"types": "./dist/src/
|
|
88
|
-
"import": "./dist/src/
|
|
90
|
+
"./core/tool-search": {
|
|
91
|
+
"types": "./dist/src/tool-search.d.ts",
|
|
92
|
+
"import": "./dist/src/tool-search.js"
|
|
93
|
+
},
|
|
94
|
+
"./core/web": {
|
|
95
|
+
"types": "./dist/src/web/index.d.ts",
|
|
96
|
+
"import": "./dist/src/web/index.js"
|
|
89
97
|
},
|
|
90
98
|
"./client": {
|
|
91
99
|
"types": "./dist/src/client.d.ts",
|
|
@@ -144,15 +152,16 @@
|
|
|
144
152
|
"@agentclientprotocol/sdk": "^2.0.0",
|
|
145
153
|
"@anthropic-ai/claude-agent-sdk": "^0.3.306",
|
|
146
154
|
"@earendil-works/pi-codemode": "^1.3.0",
|
|
147
|
-
"@nyte-ai/ai": "0.0.25-
|
|
148
|
-
"@nyte-ai/core": "0.0.25-
|
|
149
|
-
"@nyte-ai/schema": "0.0.25-
|
|
155
|
+
"@nyte-ai/ai": "0.0.25-nightly.20261025.2",
|
|
156
|
+
"@nyte-ai/core": "0.0.25-nightly.20261025.2",
|
|
157
|
+
"@nyte-ai/schema": "0.0.25-nightly.20261025.2",
|
|
150
158
|
"@opentelemetry/api": "^1.10.0",
|
|
151
159
|
"@opentelemetry/exporter-trace-otlp-http": "^0.300.0",
|
|
152
160
|
"@opentelemetry/resources": "^3.0.0",
|
|
153
161
|
"@opentelemetry/sdk-trace": "^3.0.0",
|
|
154
162
|
"@opentelemetry/semantic-conventions": "^1.43.0",
|
|
155
163
|
"es-module-lexer": "^3.0.4",
|
|
164
|
+
"htmlparser2": "^8.0.2",
|
|
156
165
|
"sucrase": "^3.35.1",
|
|
157
166
|
"typebox": "^1.4.2",
|
|
158
167
|
"zod": "^4.6.7"
|
package/dist/src/web-search.d.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import type { Command, SettingDefinition } from "@nyte-ai/core";
|
|
2
|
-
/**
|
|
3
|
-
* Names a search provider the `web_search` tool can route to.
|
|
4
|
-
*
|
|
5
|
-
* @category plugins
|
|
6
|
-
* @since 0.1.0
|
|
7
|
-
*/
|
|
8
|
-
export type SearchProvider = (typeof PROVIDERS)[number];
|
|
9
|
-
declare const PROVIDERS: readonly ["exa", "tavily", "firecrawl", "parallel"];
|
|
10
|
-
/**
|
|
11
|
-
* Returns one search hit as the tool reports it.
|
|
12
|
-
*
|
|
13
|
-
* @category plugins
|
|
14
|
-
* @since 0.1.0
|
|
15
|
-
*/
|
|
16
|
-
export interface SearchResult {
|
|
17
|
-
readonly url: string;
|
|
18
|
-
readonly title: string | null;
|
|
19
|
-
readonly content: string | null;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Returns what one adapter call needs: the query, the provider's key or
|
|
23
|
-
* undefined for its keyless route, the fetch to send with, and a signal.
|
|
24
|
-
*
|
|
25
|
-
* @category plugins
|
|
26
|
-
* @since 0.1.0
|
|
27
|
-
*/
|
|
28
|
-
export interface SearchInput {
|
|
29
|
-
readonly query: string;
|
|
30
|
-
readonly key: string | undefined;
|
|
31
|
-
readonly fetch: typeof globalThis.fetch;
|
|
32
|
-
readonly signal: AbortSignal;
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* One adapter per provider: Tavily over its REST endpoint, the others over
|
|
36
|
-
* one stateless MCP `tools/call` to their public route. Each sends through
|
|
37
|
-
* the given fetch only and returns the hits it recognised.
|
|
38
|
-
*
|
|
39
|
-
* @category plugins
|
|
40
|
-
* @since 0.1.0
|
|
41
|
-
*/
|
|
42
|
-
export declare const adapters: {
|
|
43
|
-
readonly [P in SearchProvider]: (input: SearchInput) => Promise<readonly SearchResult[]>;
|
|
44
|
-
};
|
|
45
|
-
/**
|
|
46
|
-
* Adds `web_search`, routed by the host setting `web_search.provider` to
|
|
47
|
-
* Tavily, Exa, Firecrawl or Parallel. A provider's key lives in the plugin's
|
|
48
|
-
* secrets under the provider's id, set with the `websearch-key` command; without one
|
|
49
|
-
* the provider's keyless route is used.
|
|
50
|
-
*
|
|
51
|
-
* @category plugins
|
|
52
|
-
* @since 0.1.0
|
|
53
|
-
*/
|
|
54
|
-
declare const _default: {
|
|
55
|
-
id: string;
|
|
56
|
-
contribute: {
|
|
57
|
-
settings: SettingDefinition<"exa" | "firecrawl" | "parallel" | "tavily">[];
|
|
58
|
-
commands: Command<keyof import("@nyte-ai/core").SideTable>[];
|
|
59
|
-
};
|
|
60
|
-
activate(api: {
|
|
61
|
-
readonly side: "core";
|
|
62
|
-
readonly sdk: import("@nyte-ai/core").Connection;
|
|
63
|
-
readonly contribute: import("@nyte-ai/core").Registries<"core">;
|
|
64
|
-
readonly storage: import("@nyte-ai/core").PluginStorage;
|
|
65
|
-
readonly signal: AbortSignal;
|
|
66
|
-
hook<N extends import("@nyte-ai/core").HookName<"core">>(name: N, entry: import("@nyte-ai/core").HookEntry<"core", N>): import("@nyte-ai/schema").Disposer;
|
|
67
|
-
report(issue: import("@nyte-ai/schema").ValidationIssue): void;
|
|
68
|
-
reload(): void;
|
|
69
|
-
} & {
|
|
70
|
-
readonly session: import("@nyte-ai/core").Session;
|
|
71
|
-
readonly env: import("@nyte-ai/core").ExecutionEnv;
|
|
72
|
-
wrapEnv(wrap: import("@nyte-ai/core").EnvironmentWrap): import("@nyte-ai/schema").Disposer;
|
|
73
|
-
readonly inputs: import("@nyte-ai/core").Resources;
|
|
74
|
-
request<T = string>(input: import("@nyte-ai/schema").ModelRequestInput<T>): Promise<import("@nyte-ai/schema").ModelRequestOutcome<T>>;
|
|
75
|
-
approve(call: import("@nyte-ai/core").RuntimeCall, signal: AbortSignal): Promise<import("@nyte-ai/core").RuntimeVerdict>;
|
|
76
|
-
}): void;
|
|
77
|
-
};
|
|
78
|
-
export default _default;
|
package/dist/src/web-search.js
DELETED
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
// src/web-search.ts
|
|
2
|
-
import { dev } from "@nyte-ai/core";
|
|
3
|
-
import { standard } from "@nyte-ai/schema";
|
|
4
|
-
import { Type } from "typebox";
|
|
5
|
-
import { Value } from "typebox/value";
|
|
6
|
-
var PROVIDERS = ["exa", "tavily", "firecrawl", "parallel"];
|
|
7
|
-
var providerSchema = Type.Union(PROVIDERS.map((name) => Type.Literal(name)));
|
|
8
|
-
var provider = {
|
|
9
|
-
id: "web_search.provider",
|
|
10
|
-
label: "Web search provider",
|
|
11
|
-
schema: standard(providerSchema),
|
|
12
|
-
choices: [
|
|
13
|
-
{ value: "exa", label: "Exa" },
|
|
14
|
-
{ value: "tavily", label: "Tavily" },
|
|
15
|
-
{ value: "firecrawl", label: "Firecrawl" },
|
|
16
|
-
{ value: "parallel", label: "Parallel" }
|
|
17
|
-
],
|
|
18
|
-
default: "exa",
|
|
19
|
-
scope: "host"
|
|
20
|
-
};
|
|
21
|
-
var textBlocks = Type.Object({
|
|
22
|
-
content: Type.Array(Type.Object({ type: Type.String(), text: Type.Optional(Type.String()) }))
|
|
23
|
-
});
|
|
24
|
-
var tavilyResponse = Type.Object({
|
|
25
|
-
results: Type.Array(Type.Object({ url: Type.String(), title: Type.String(), content: Type.String() }))
|
|
26
|
-
});
|
|
27
|
-
var firecrawlResponse = Type.Object({
|
|
28
|
-
data: Type.Object({
|
|
29
|
-
web: Type.Array(Type.Object({
|
|
30
|
-
url: Type.String(),
|
|
31
|
-
title: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
32
|
-
description: Type.Optional(Type.Union([Type.String(), Type.Null()]))
|
|
33
|
-
}))
|
|
34
|
-
})
|
|
35
|
-
});
|
|
36
|
-
var parallelResponse = Type.Object({
|
|
37
|
-
structuredContent: Type.Object({
|
|
38
|
-
results: Type.Array(Type.Object({
|
|
39
|
-
url: Type.String(),
|
|
40
|
-
title: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
41
|
-
excerpts: Type.Array(Type.String())
|
|
42
|
-
}))
|
|
43
|
-
})
|
|
44
|
-
});
|
|
45
|
-
var adapters = {
|
|
46
|
-
async tavily({ query, key, fetch, signal }) {
|
|
47
|
-
const response = await fetch("https://api.tavily.com/search", {
|
|
48
|
-
method: "POST",
|
|
49
|
-
headers: {
|
|
50
|
-
"Content-Type": "application/json",
|
|
51
|
-
...key === undefined ? { "X-Tavily-Access-Mode": "keyless" } : { Authorization: `Bearer ${key}` }
|
|
52
|
-
},
|
|
53
|
-
body: JSON.stringify({ query, search_depth: "basic", max_results: 8 }),
|
|
54
|
-
signal
|
|
55
|
-
});
|
|
56
|
-
const body = parse(await answered(response));
|
|
57
|
-
return Value.Check(tavilyResponse, body) ? body.results.map((hit) => ({ url: hit.url, title: hit.title, content: hit.content })) : [];
|
|
58
|
-
},
|
|
59
|
-
async exa({ query, key, fetch, signal }) {
|
|
60
|
-
const endpoint = new URL("https://mcp.exa.ai/mcp");
|
|
61
|
-
if (key !== undefined)
|
|
62
|
-
endpoint.searchParams.set("exaApiKey", key);
|
|
63
|
-
const result = await callTool(fetch, endpoint, undefined, "web_search_exa", { query, numResults: 8 }, signal);
|
|
64
|
-
const text = Value.Check(textBlocks, result) ? result.content.find((block) => block.text !== undefined)?.text ?? "" : "";
|
|
65
|
-
return text.split(/\n\n---\n\n/).flatMap((block) => {
|
|
66
|
-
const url = field(block, "URL");
|
|
67
|
-
return url === null ? [] : [
|
|
68
|
-
{
|
|
69
|
-
url,
|
|
70
|
-
title: field(block, "Title"),
|
|
71
|
-
content: block.match(/^(?:Highlights|Text):\s*\n?([\s\S]*)$/m)?.[1]?.trim() ?? null
|
|
72
|
-
}
|
|
73
|
-
];
|
|
74
|
-
});
|
|
75
|
-
},
|
|
76
|
-
async firecrawl({ query, key, fetch, signal }) {
|
|
77
|
-
const result = await callTool(fetch, new URL("https://mcp.firecrawl.dev/v2/mcp"), key, "firecrawl_search", { query, limit: 8 }, signal);
|
|
78
|
-
const text = Value.Check(textBlocks, result) ? result.content.find((block) => block.text !== undefined)?.text ?? "" : "";
|
|
79
|
-
const body = parse(text);
|
|
80
|
-
return Value.Check(firecrawlResponse, body) ? body.data.web.map((hit) => ({
|
|
81
|
-
url: hit.url,
|
|
82
|
-
title: hit.title ?? null,
|
|
83
|
-
content: hit.description ?? null
|
|
84
|
-
})) : [];
|
|
85
|
-
},
|
|
86
|
-
async parallel({ query, key, fetch, signal }) {
|
|
87
|
-
const result = await callTool(fetch, new URL("https://search.parallel.ai/mcp"), key, "web_search", { objective: query, search_queries: [query] }, signal);
|
|
88
|
-
return Value.Check(parallelResponse, result) ? result.structuredContent.results.map((hit) => ({
|
|
89
|
-
url: hit.url,
|
|
90
|
-
title: hit.title ?? null,
|
|
91
|
-
content: hit.excerpts.length === 0 ? null : hit.excerpts.join(`
|
|
92
|
-
|
|
93
|
-
`)
|
|
94
|
-
})) : [];
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
async function callTool(fetch, url, key, tool, args, signal) {
|
|
98
|
-
const response = await fetch(url, {
|
|
99
|
-
method: "POST",
|
|
100
|
-
headers: {
|
|
101
|
-
Accept: "application/json, text/event-stream",
|
|
102
|
-
"Content-Type": "application/json",
|
|
103
|
-
...key === undefined ? {} : { Authorization: `Bearer ${key}` }
|
|
104
|
-
},
|
|
105
|
-
body: JSON.stringify({
|
|
106
|
-
jsonrpc: "2.0",
|
|
107
|
-
id: 1,
|
|
108
|
-
method: "tools/call",
|
|
109
|
-
params: { name: tool, arguments: args }
|
|
110
|
-
}),
|
|
111
|
-
signal
|
|
112
|
-
});
|
|
113
|
-
const body = await answered(response);
|
|
114
|
-
const frames = [
|
|
115
|
-
body,
|
|
116
|
-
...body.split(`
|
|
117
|
-
`).flatMap((line) => line.startsWith("data: ") ? [line.slice(6)] : [])
|
|
118
|
-
];
|
|
119
|
-
for (const frame of frames) {
|
|
120
|
-
const message = parse(frame);
|
|
121
|
-
if (typeof message === "object" && message !== null && "result" in message)
|
|
122
|
-
return message.result;
|
|
123
|
-
}
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
async function answered(response) {
|
|
127
|
-
const body = await response.text();
|
|
128
|
-
if (!response.ok)
|
|
129
|
-
throw new Error(`HTTP ${response.status}: ${body.trim().slice(0, 200) || response.statusText}`);
|
|
130
|
-
return body;
|
|
131
|
-
}
|
|
132
|
-
function parse(text) {
|
|
133
|
-
try {
|
|
134
|
-
return JSON.parse(text);
|
|
135
|
-
} catch {
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
function field(block, name) {
|
|
140
|
-
const value = block.match(new RegExp(`^${name}:\\s*(.+)$`, "m"))?.[1]?.trim();
|
|
141
|
-
return value === undefined || value === "" || value === "N/A" ? null : value;
|
|
142
|
-
}
|
|
143
|
-
var parameters = standard(Type.Object({ query: Type.String({ minLength: 1, description: "What to search the web for." }) }));
|
|
144
|
-
function webSearch(api) {
|
|
145
|
-
return {
|
|
146
|
-
name: "web_search",
|
|
147
|
-
description: "Search the web for current information beyond your knowledge cutoff. Returns titles, URLs and excerpts.",
|
|
148
|
-
parameters,
|
|
149
|
-
class: (args) => ({ kind: "custom", label: `Searching ${args.query}` }),
|
|
150
|
-
effects: "none",
|
|
151
|
-
approval: "never",
|
|
152
|
-
replay: "safe",
|
|
153
|
-
async execute(args, invocation) {
|
|
154
|
-
const value = await api.contribute.settings.value(provider.id);
|
|
155
|
-
const name = PROVIDERS.find((known) => known === value) ?? provider.default;
|
|
156
|
-
const results = await invocation.memo("results", async () => {
|
|
157
|
-
const key = await api.storage.secrets.get(name);
|
|
158
|
-
dev("web-search", "search", {
|
|
159
|
-
call: invocation.call,
|
|
160
|
-
provider: name,
|
|
161
|
-
keyed: key !== undefined
|
|
162
|
-
});
|
|
163
|
-
const hits = await adapters[name]({
|
|
164
|
-
query: args.query,
|
|
165
|
-
key,
|
|
166
|
-
fetch: globalThis.fetch,
|
|
167
|
-
signal: AbortSignal.any([invocation.signal, AbortSignal.timeout(25000)])
|
|
168
|
-
});
|
|
169
|
-
return hits.map((hit) => ({ url: hit.url, title: hit.title, content: hit.content }));
|
|
170
|
-
});
|
|
171
|
-
const text = results.length === 0 ? "No results. Try a different query." : results.map((hit) => `## ${hit.title ?? hit.url}
|
|
172
|
-
${hit.url}${hit.content === null ? "" : `
|
|
173
|
-
|
|
174
|
-
${hit.content}`}`).join(`
|
|
175
|
-
|
|
176
|
-
`);
|
|
177
|
-
return { content: [{ kind: "text", text }] };
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
var keyCommand = {
|
|
182
|
-
name: "websearch-key",
|
|
183
|
-
title: "Set a web search key",
|
|
184
|
-
description: "Store a web search provider's API key: <provider> <key>; no key removes it.",
|
|
185
|
-
complete(prefix) {
|
|
186
|
-
const typed = prefix.trimStart();
|
|
187
|
-
if (/\s/u.test(typed))
|
|
188
|
-
return [];
|
|
189
|
-
return PROVIDERS.filter((known) => known.startsWith(typed.toLowerCase())).map((known) => ({
|
|
190
|
-
value: `${known} `,
|
|
191
|
-
label: known
|
|
192
|
-
}));
|
|
193
|
-
},
|
|
194
|
-
async run(text, { api }) {
|
|
195
|
-
const [named = "", key = ""] = text.split(/\s+(.*)/su);
|
|
196
|
-
const chosen = PROVIDERS.find((known) => known === named.toLowerCase());
|
|
197
|
-
if (chosen === undefined)
|
|
198
|
-
throw new Error(`Name a provider first: ${PROVIDERS.join(", ")}.`);
|
|
199
|
-
if (key === "")
|
|
200
|
-
await api.storage.secrets.delete(chosen);
|
|
201
|
-
else
|
|
202
|
-
await api.storage.secrets.set(chosen, key);
|
|
203
|
-
return `Key for ${chosen} ${key === "" ? "removed" : "saved"}.`;
|
|
204
|
-
}
|
|
205
|
-
};
|
|
206
|
-
var web_search_default = {
|
|
207
|
-
id: "web-search",
|
|
208
|
-
contribute: {
|
|
209
|
-
settings: [provider],
|
|
210
|
-
commands: [keyCommand]
|
|
211
|
-
},
|
|
212
|
-
activate(api) {
|
|
213
|
-
api.contribute.tools.add(webSearch(api));
|
|
214
|
-
}
|
|
215
|
-
};
|
|
216
|
-
export {
|
|
217
|
-
adapters,
|
|
218
|
-
web_search_default as default
|
|
219
|
-
};
|