@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.
Files changed (34) hide show
  1. package/THIRD-PARTY-NOTICES.md +13 -5
  2. package/dist/chunks/bash-description-0ah30s12.js +33 -0
  3. package/dist/chunks/bash-description-4cdbmar0.js +124 -0
  4. package/dist/chunks/bash-description-4h0hnsv4.js +68 -0
  5. package/dist/chunks/bash-description-5dpf5hpt.js +724 -0
  6. package/dist/chunks/bash-description-9gw9ets8.js +162 -0
  7. package/dist/chunks/bash-description-b78pre8c.js +147 -0
  8. package/dist/chunks/{bash-description-eqp7ap2w.js → bash-description-vg0xecwm.js} +16 -5
  9. package/dist/src/acp/connection.js +1 -1
  10. package/dist/src/acp/index.js +1 -1
  11. package/dist/src/claude-code-sdk/index.js +1 -0
  12. package/dist/src/codemode.js +148 -41
  13. package/dist/src/core.d.ts +1 -1
  14. package/dist/src/rename.d.ts +7 -22
  15. package/dist/src/rename.js +115 -48
  16. package/dist/src/subagents.d.ts +30 -0
  17. package/dist/src/subagents.js +323 -0
  18. package/dist/src/tool-search.d.ts +49 -0
  19. package/dist/src/tool-search.js +14 -0
  20. package/dist/src/web/body.d.ts +3 -0
  21. package/dist/src/web/body.js +6 -0
  22. package/dist/src/web/fetch.d.ts +16 -0
  23. package/dist/src/web/fetch.js +8 -0
  24. package/dist/src/web/html-markdown.d.ts +9 -0
  25. package/dist/src/web/html-markdown.js +10 -0
  26. package/dist/src/web/index.d.ts +24 -0
  27. package/dist/src/web/index.js +93 -0
  28. package/dist/src/web/providers.d.ts +60 -0
  29. package/dist/src/web/providers.js +11 -0
  30. package/dist/src/web/search.d.ts +21 -0
  31. package/dist/src/web/search.js +6 -0
  32. package/package.json +16 -7
  33. package/dist/src/web-search.d.ts +0 -78
  34. 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,6 @@
1
+ import {
2
+ readCapped2
3
+ } from "../../chunks/bash-description-0ah30s12.js";
4
+ export {
5
+ readCapped2 as readCapped
6
+ };
@@ -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,8 @@
1
+ import {
2
+ webFetch2
3
+ } from "../../chunks/bash-description-4cdbmar0.js";
4
+ import"../../chunks/bash-description-5dpf5hpt.js";
5
+ import"../../chunks/bash-description-0ah30s12.js";
6
+ export {
7
+ webFetch2 as webFetch
8
+ };
@@ -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,10 @@
1
+ import {
2
+ MAX_MARKDOWN_BYTES2,
3
+ htmlToMarkdown2,
4
+ htmlToText2
5
+ } from "../../chunks/bash-description-5dpf5hpt.js";
6
+ export {
7
+ MAX_MARKDOWN_BYTES2 as MAX_MARKDOWN_BYTES,
8
+ htmlToMarkdown2 as htmlToMarkdown,
9
+ htmlToText2 as htmlToText
10
+ };
@@ -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 {};
@@ -0,0 +1,6 @@
1
+ import {
2
+ webSearch2
3
+ } from "../../chunks/bash-description-4h0hnsv4.js";
4
+ export {
5
+ webSearch2 as webSearch
6
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nyte-ai/plugin",
3
- "version": "0.0.25-beta.1",
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/web-search": {
87
- "types": "./dist/src/web-search.d.ts",
88
- "import": "./dist/src/web-search.js"
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-beta.1",
148
- "@nyte-ai/core": "0.0.25-beta.1",
149
- "@nyte-ai/schema": "0.0.25-beta.1",
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"
@@ -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;
@@ -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
- };