@theokit/sdk-tools 0.28.0 → 0.29.0

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.
@@ -0,0 +1,61 @@
1
+ import { CustomTool } from '@theokit/sdk';
2
+ import { z } from 'zod';
3
+
4
+ /**
5
+ * `structured_output` — hands the host the model's object only when it validates against the
6
+ * consumer's zod schema.
7
+ *
8
+ * The model sees the schema as the tool's JSON Schema and calls the tool with its answer. The
9
+ * handler runs the full zod contract (refinements and transforms included, which the JSON Schema
10
+ * only approximates) and never throws: a failed parse becomes a typed result the model can act on.
11
+ *
12
+ * Return shape (always a JSON string):
13
+ * - `{ ok: true, output }` with `output` the parsed value, defaults and transforms applied
14
+ * - `{ ok: false, error: "invalid_structured_output", issue_count, issues }` with at most
15
+ * `maxIssues` issues, each `{ path, message }`, and `issue_count` the full count
16
+ *
17
+ * The tool does not end the agent turn: `CustomTool` has no terminal flag, so the host stops on
18
+ * the first `ok: true` result.
19
+ *
20
+ * Limits, accepted and stated: "never throws" holds for synchronous schemas whose parsed value is
21
+ * JSON-serialisable. An async refinement makes `safeParse` throw, and a transform that yields a
22
+ * BigInt or a cycle makes the serialisation throw; both are the consumer's schema, and the error
23
+ * surfaces as the tool error the SDK reports for any throwing handler.
24
+ */
25
+
26
+ /** Options for {@link createStructuredOutputTool}. `schema` is required and must describe an object. */
27
+ interface CreateStructuredOutputToolOptions {
28
+ /** The zod schema the model's object must satisfy. Its JSON Schema must have `type: "object"`. */
29
+ schema: z.ZodType;
30
+ /** Name exposed to the model. Omitted ⇒ `structured_output`. */
31
+ name?: string;
32
+ /** Description exposed to the model. Omitted ⇒ a literal telling the model to retry on issues. */
33
+ description?: string;
34
+ /** Most issues returned in one failure. Omitted ⇒ 10. */
35
+ maxIssues?: number;
36
+ }
37
+ /** One validation issue: the dotted path to the field (empty for the root) and zod's message. */
38
+ interface StructuredOutputIssue {
39
+ path: string;
40
+ message: string;
41
+ }
42
+ /** The tool's result, before it is serialised to the JSON string the handler returns. */
43
+ type StructuredOutputResult = {
44
+ ok: true;
45
+ output: unknown;
46
+ } | {
47
+ ok: false;
48
+ error: "invalid_structured_output";
49
+ issue_count: number;
50
+ issues: StructuredOutputIssue[];
51
+ };
52
+ /**
53
+ * Build a `structured_output` tool over `schema`.
54
+ *
55
+ * @throws TypeError when the schema's JSON Schema is not `type: "object"` (provider tool inputs
56
+ * must be objects; a top-level union, intersection or recursive `$ref` is refused too);
57
+ * RangeError when `maxIssues` is not a positive integer.
58
+ */
59
+ declare function createStructuredOutputTool(opts: CreateStructuredOutputToolOptions): CustomTool;
60
+
61
+ export { type CreateStructuredOutputToolOptions, type StructuredOutputIssue, type StructuredOutputResult, createStructuredOutputTool };
@@ -0,0 +1,61 @@
1
+ import { CustomTool } from '@theokit/sdk';
2
+ import { z } from 'zod';
3
+
4
+ /**
5
+ * `structured_output` — hands the host the model's object only when it validates against the
6
+ * consumer's zod schema.
7
+ *
8
+ * The model sees the schema as the tool's JSON Schema and calls the tool with its answer. The
9
+ * handler runs the full zod contract (refinements and transforms included, which the JSON Schema
10
+ * only approximates) and never throws: a failed parse becomes a typed result the model can act on.
11
+ *
12
+ * Return shape (always a JSON string):
13
+ * - `{ ok: true, output }` with `output` the parsed value, defaults and transforms applied
14
+ * - `{ ok: false, error: "invalid_structured_output", issue_count, issues }` with at most
15
+ * `maxIssues` issues, each `{ path, message }`, and `issue_count` the full count
16
+ *
17
+ * The tool does not end the agent turn: `CustomTool` has no terminal flag, so the host stops on
18
+ * the first `ok: true` result.
19
+ *
20
+ * Limits, accepted and stated: "never throws" holds for synchronous schemas whose parsed value is
21
+ * JSON-serialisable. An async refinement makes `safeParse` throw, and a transform that yields a
22
+ * BigInt or a cycle makes the serialisation throw; both are the consumer's schema, and the error
23
+ * surfaces as the tool error the SDK reports for any throwing handler.
24
+ */
25
+
26
+ /** Options for {@link createStructuredOutputTool}. `schema` is required and must describe an object. */
27
+ interface CreateStructuredOutputToolOptions {
28
+ /** The zod schema the model's object must satisfy. Its JSON Schema must have `type: "object"`. */
29
+ schema: z.ZodType;
30
+ /** Name exposed to the model. Omitted ⇒ `structured_output`. */
31
+ name?: string;
32
+ /** Description exposed to the model. Omitted ⇒ a literal telling the model to retry on issues. */
33
+ description?: string;
34
+ /** Most issues returned in one failure. Omitted ⇒ 10. */
35
+ maxIssues?: number;
36
+ }
37
+ /** One validation issue: the dotted path to the field (empty for the root) and zod's message. */
38
+ interface StructuredOutputIssue {
39
+ path: string;
40
+ message: string;
41
+ }
42
+ /** The tool's result, before it is serialised to the JSON string the handler returns. */
43
+ type StructuredOutputResult = {
44
+ ok: true;
45
+ output: unknown;
46
+ } | {
47
+ ok: false;
48
+ error: "invalid_structured_output";
49
+ issue_count: number;
50
+ issues: StructuredOutputIssue[];
51
+ };
52
+ /**
53
+ * Build a `structured_output` tool over `schema`.
54
+ *
55
+ * @throws TypeError when the schema's JSON Schema is not `type: "object"` (provider tool inputs
56
+ * must be objects; a top-level union, intersection or recursive `$ref` is refused too);
57
+ * RangeError when `maxIssues` is not a positive integer.
58
+ */
59
+ declare function createStructuredOutputTool(opts: CreateStructuredOutputToolOptions): CustomTool;
60
+
61
+ export { type CreateStructuredOutputToolOptions, type StructuredOutputIssue, type StructuredOutputResult, createStructuredOutputTool };
@@ -0,0 +1,50 @@
1
+ import { Tool } from '@theokit/sdk';
2
+
3
+ // src/structured-output.ts
4
+ var DEFAULT_MAX_ISSUES = 10;
5
+ var DEFAULT_DESCRIPTION = "Return your final answer as an object matching this tool's input schema. If the result reports invalid_structured_output, fix the listed issues and call again.";
6
+ function requireMaxIssues(value) {
7
+ if (!Number.isInteger(value) || value <= 0) {
8
+ throw new RangeError(
9
+ `createStructuredOutputTool: maxIssues must be a positive integer, got ${value}`
10
+ );
11
+ }
12
+ return value;
13
+ }
14
+ function validate(schema, input, maxIssues) {
15
+ const parsed = schema.safeParse(input);
16
+ if (parsed.success) return { ok: true, output: parsed.data };
17
+ const all = parsed.error.issues;
18
+ return {
19
+ ok: false,
20
+ error: "invalid_structured_output",
21
+ issue_count: all.length,
22
+ issues: all.slice(0, maxIssues).map((issue) => ({
23
+ path: issue.path.map(String).join("."),
24
+ message: issue.message
25
+ }))
26
+ };
27
+ }
28
+ function createStructuredOutputTool(opts) {
29
+ const maxIssues = requireMaxIssues(opts.maxIssues ?? DEFAULT_MAX_ISSUES);
30
+ const base = Tool.create({
31
+ name: opts.name ?? "structured_output",
32
+ description: opts.description ?? DEFAULT_DESCRIPTION,
33
+ inputSchema: opts.schema,
34
+ handler: () => ""
35
+ });
36
+ const type = base.inputSchema.type;
37
+ if (type !== "object") {
38
+ throw new TypeError(
39
+ `createStructuredOutputTool: schema must be an object schema, got type ${String(type)}`
40
+ );
41
+ }
42
+ return {
43
+ ...base,
44
+ handler: (input) => JSON.stringify(validate(opts.schema, input, maxIssues))
45
+ };
46
+ }
47
+
48
+ export { createStructuredOutputTool };
49
+ //# sourceMappingURL=structured-output.js.map
50
+ //# sourceMappingURL=structured-output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/structured-output.ts"],"names":[],"mappings":";;;AA2BA,IAAM,kBAAA,GAAqB,EAAA;AAE3B,IAAM,mBAAA,GACJ,iKAAA;AA+BF,SAAS,iBAAiB,KAAA,EAAuB;AAC/C,EAAA,IAAI,CAAC,MAAA,CAAO,SAAA,CAAU,KAAK,CAAA,IAAK,SAAS,CAAA,EAAG;AAC1C,IAAA,MAAM,IAAI,UAAA;AAAA,MACR,yEAAyE,KAAK,CAAA;AAAA,KAChF;AAAA,EACF;AACA,EAAA,OAAO,KAAA;AACT;AAEA,SAAS,QAAA,CAAS,MAAA,EAAmB,KAAA,EAAgB,SAAA,EAA2C;AAC9F,EAAA,MAAM,MAAA,GAAS,MAAA,CAAO,SAAA,CAAU,KAAK,CAAA;AACrC,EAAA,IAAI,MAAA,CAAO,SAAS,OAAO,EAAE,IAAI,IAAA,EAAM,MAAA,EAAQ,OAAO,IAAA,EAAK;AAC3D,EAAA,MAAM,GAAA,GAAM,OAAO,KAAA,CAAM,MAAA;AACzB,EAAA,OAAO;AAAA,IACL,EAAA,EAAI,KAAA;AAAA,IACJ,KAAA,EAAO,2BAAA;AAAA,IACP,aAAa,GAAA,CAAI,MAAA;AAAA,IACjB,MAAA,EAAQ,IAAI,KAAA,CAAM,CAAA,EAAG,SAAS,CAAA,CAAE,GAAA,CAAI,CAAC,KAAA,MAAW;AAAA,MAC9C,MAAM,KAAA,CAAM,IAAA,CAAK,IAAI,MAAM,CAAA,CAAE,KAAK,GAAG,CAAA;AAAA,MACrC,SAAS,KAAA,CAAM;AAAA,KACjB,CAAE;AAAA,GACJ;AACF;AASO,SAAS,2BAA2B,IAAA,EAAqD;AAC9F,EAAA,MAAM,SAAA,GAAY,gBAAA,CAAiB,IAAA,CAAK,SAAA,IAAa,kBAAkB,CAAA;AACvE,EAAA,MAAM,IAAA,GAAO,KAAK,MAAA,CAAO;AAAA,IACvB,IAAA,EAAM,KAAK,IAAA,IAAQ,mBAAA;AAAA,IACnB,WAAA,EAAa,KAAK,WAAA,IAAe,mBAAA;AAAA,IACjC,aAAa,IAAA,CAAK,MAAA;AAAA,IAClB,SAAS,MAAM;AAAA,GAChB,CAAA;AACD,EAAA,MAAM,IAAA,GAAQ,KAAK,WAAA,CAAmC,IAAA;AACtD,EAAA,IAAI,SAAS,QAAA,EAAU;AACrB,IAAA,MAAM,IAAI,SAAA;AAAA,MACR,CAAA,sEAAA,EAAyE,MAAA,CAAO,IAAI,CAAC,CAAA;AAAA,KACvF;AAAA,EACF;AACA,EAAA,OAAO;AAAA,IACL,GAAG,IAAA;AAAA,IACH,OAAA,EAAS,CAAC,KAAA,KAAmB,IAAA,CAAK,SAAA,CAAU,SAAS,IAAA,CAAK,MAAA,EAAQ,KAAA,EAAO,SAAS,CAAC;AAAA,GACrF;AACF","file":"structured-output.js","sourcesContent":["/**\n * `structured_output` — hands the host the model's object only when it validates against the\n * consumer's zod schema.\n *\n * The model sees the schema as the tool's JSON Schema and calls the tool with its answer. The\n * handler runs the full zod contract (refinements and transforms included, which the JSON Schema\n * only approximates) and never throws: a failed parse becomes a typed result the model can act on.\n *\n * Return shape (always a JSON string):\n * - `{ ok: true, output }` with `output` the parsed value, defaults and transforms applied\n * - `{ ok: false, error: \"invalid_structured_output\", issue_count, issues }` with at most\n * `maxIssues` issues, each `{ path, message }`, and `issue_count` the full count\n *\n * The tool does not end the agent turn: `CustomTool` has no terminal flag, so the host stops on\n * the first `ok: true` result.\n *\n * Limits, accepted and stated: \"never throws\" holds for synchronous schemas whose parsed value is\n * JSON-serialisable. An async refinement makes `safeParse` throw, and a transform that yields a\n * BigInt or a cycle makes the serialisation throw; both are the consumer's schema, and the error\n * surfaces as the tool error the SDK reports for any throwing handler.\n */\n\nimport type { CustomTool } from \"@theokit/sdk\";\nimport { Tool } from \"@theokit/sdk\";\nimport type { z } from \"zod\";\n\n/** Default ceiling on the issues returned to the model. A default, not a measured limit. */\nconst DEFAULT_MAX_ISSUES = 10;\n\nconst DEFAULT_DESCRIPTION =\n \"Return your final answer as an object matching this tool's input schema. \" +\n \"If the result reports invalid_structured_output, fix the listed issues and call again.\";\n\n/** Options for {@link createStructuredOutputTool}. `schema` is required and must describe an object. */\nexport interface CreateStructuredOutputToolOptions {\n /** The zod schema the model's object must satisfy. Its JSON Schema must have `type: \"object\"`. */\n schema: z.ZodType;\n /** Name exposed to the model. Omitted ⇒ `structured_output`. */\n name?: string;\n /** Description exposed to the model. Omitted ⇒ a literal telling the model to retry on issues. */\n description?: string;\n /** Most issues returned in one failure. Omitted ⇒ 10. */\n maxIssues?: number;\n}\n\n/** One validation issue: the dotted path to the field (empty for the root) and zod's message. */\nexport interface StructuredOutputIssue {\n path: string;\n message: string;\n}\n\n/** The tool's result, before it is serialised to the JSON string the handler returns. */\nexport type StructuredOutputResult =\n | { ok: true; output: unknown }\n | {\n ok: false;\n error: \"invalid_structured_output\";\n issue_count: number;\n issues: StructuredOutputIssue[];\n };\n\nfunction requireMaxIssues(value: number): number {\n if (!Number.isInteger(value) || value <= 0) {\n throw new RangeError(\n `createStructuredOutputTool: maxIssues must be a positive integer, got ${value}`,\n );\n }\n return value;\n}\n\nfunction validate(schema: z.ZodType, input: unknown, maxIssues: number): StructuredOutputResult {\n const parsed = schema.safeParse(input);\n if (parsed.success) return { ok: true, output: parsed.data };\n const all = parsed.error.issues;\n return {\n ok: false,\n error: \"invalid_structured_output\",\n issue_count: all.length,\n issues: all.slice(0, maxIssues).map((issue) => ({\n path: issue.path.map(String).join(\".\"),\n message: issue.message,\n })),\n };\n}\n\n/**\n * Build a `structured_output` tool over `schema`.\n *\n * @throws TypeError when the schema's JSON Schema is not `type: \"object\"` (provider tool inputs\n * must be objects; a top-level union, intersection or recursive `$ref` is refused too);\n * RangeError when `maxIssues` is not a positive integer.\n */\nexport function createStructuredOutputTool(opts: CreateStructuredOutputToolOptions): CustomTool {\n const maxIssues = requireMaxIssues(opts.maxIssues ?? DEFAULT_MAX_ISSUES);\n const base = Tool.create({\n name: opts.name ?? \"structured_output\",\n description: opts.description ?? DEFAULT_DESCRIPTION,\n inputSchema: opts.schema,\n handler: () => \"\",\n });\n const type = (base.inputSchema as { type?: unknown }).type;\n if (type !== \"object\") {\n throw new TypeError(\n `createStructuredOutputTool: schema must be an object schema, got type ${String(type)}`,\n );\n }\n return {\n ...base,\n handler: (input: unknown) => JSON.stringify(validate(opts.schema, input, maxIssues)),\n };\n}\n"]}
@@ -0,0 +1,61 @@
1
+ import { CustomTool } from '@theokit/sdk';
2
+
3
+ /**
4
+ * `web_search` — built-in tool for coding agents.
5
+ *
6
+ * Accepts a search callback via DIP (consumer provides the search
7
+ * provider). The tool itself is provider-agnostic.
8
+ *
9
+ * Return shape (always a JSON string):
10
+ * - `{ ok: true, results: Array<{ title, url, snippet }> }`
11
+ * - `{ ok: false, error: 'search_failed' | 'no_provider' }`
12
+ */
13
+
14
+ interface WebSearchResult {
15
+ title: string;
16
+ url: string;
17
+ snippet: string;
18
+ }
19
+ /**
20
+ * The search provider {@link createWebSearchTool} calls.
21
+ *
22
+ * `maxResults` is what the caller asked for, not a limit you must enforce — the tool slices the array
23
+ * itself — but returning far more than asked wastes the round trip that produced them.
24
+ *
25
+ * Reject the promise to signal a failed search: the tool turns a rejection into
26
+ * `{ ok: false, error: "search_failed" }` and never lets it escape into the agent turn. Resolving
27
+ * with `[]` says something different — that the search ran and found nothing.
28
+ */
29
+ type WebSearchCallback = (query: string, maxResults: number) => Promise<WebSearchResult[]>;
30
+ /**
31
+ * Options for {@link createWebSearchTool}. `search` is required: the tool ships no provider of its
32
+ * own, and the two adapters in this package — {@link createBraveWebSearchAdapter} and
33
+ * {@link createGenericHttpSearchAdapter} — exist to fill it.
34
+ */
35
+ interface CreateWebSearchToolOptions {
36
+ /** M76 — name exposed to the model. Omitted => today's literal (additive). The name is a contract:
37
+ * the approval key, what the model sees and what telemetry records. */
38
+ name?: string;
39
+ /** M76 — description exposed to the model. Omitted => today's literal (additive). */
40
+ description?: string;
41
+ /** Search provider callback — consumer injects the implementation. */
42
+ search: WebSearchCallback;
43
+ /** Default max results if not specified by the LLM. */
44
+ defaultMaxResults?: number;
45
+ }
46
+ /**
47
+ * Build the `web_search` tool over a caller-supplied provider.
48
+ *
49
+ * The tool holds no API key and talks to no service; everything network-facing lives in the `search`
50
+ * callback, which is why one tool serves Brave, a self-hosted endpoint, or a fixture in a test. Use
51
+ * it to find pages and `web_fetch` to read one — results carry a snippet, not a body.
52
+ *
53
+ * The model's `max_results` (1..20) wins over `defaultMaxResults`, and the list is sliced to it even
54
+ * when the provider returns more. A rejecting provider yields `{ ok: false, error: "search_failed" }`
55
+ * with its message attached; a provider resolving `[]` yields `{ ok: true, results: [] }`. The two
56
+ * are different signals, so an adapter that swallows its own failures — as
57
+ * {@link createGenericHttpSearchAdapter} deliberately does — reports the first as the second.
58
+ */
59
+ declare function createWebSearchTool(opts: CreateWebSearchToolOptions): CustomTool;
60
+
61
+ export { type CreateWebSearchToolOptions as C, type WebSearchCallback as W, type WebSearchResult as a, createWebSearchTool as c };
@@ -0,0 +1,61 @@
1
+ import { CustomTool } from '@theokit/sdk';
2
+
3
+ /**
4
+ * `web_search` — built-in tool for coding agents.
5
+ *
6
+ * Accepts a search callback via DIP (consumer provides the search
7
+ * provider). The tool itself is provider-agnostic.
8
+ *
9
+ * Return shape (always a JSON string):
10
+ * - `{ ok: true, results: Array<{ title, url, snippet }> }`
11
+ * - `{ ok: false, error: 'search_failed' | 'no_provider' }`
12
+ */
13
+
14
+ interface WebSearchResult {
15
+ title: string;
16
+ url: string;
17
+ snippet: string;
18
+ }
19
+ /**
20
+ * The search provider {@link createWebSearchTool} calls.
21
+ *
22
+ * `maxResults` is what the caller asked for, not a limit you must enforce — the tool slices the array
23
+ * itself — but returning far more than asked wastes the round trip that produced them.
24
+ *
25
+ * Reject the promise to signal a failed search: the tool turns a rejection into
26
+ * `{ ok: false, error: "search_failed" }` and never lets it escape into the agent turn. Resolving
27
+ * with `[]` says something different — that the search ran and found nothing.
28
+ */
29
+ type WebSearchCallback = (query: string, maxResults: number) => Promise<WebSearchResult[]>;
30
+ /**
31
+ * Options for {@link createWebSearchTool}. `search` is required: the tool ships no provider of its
32
+ * own, and the two adapters in this package — {@link createBraveWebSearchAdapter} and
33
+ * {@link createGenericHttpSearchAdapter} — exist to fill it.
34
+ */
35
+ interface CreateWebSearchToolOptions {
36
+ /** M76 — name exposed to the model. Omitted => today's literal (additive). The name is a contract:
37
+ * the approval key, what the model sees and what telemetry records. */
38
+ name?: string;
39
+ /** M76 — description exposed to the model. Omitted => today's literal (additive). */
40
+ description?: string;
41
+ /** Search provider callback — consumer injects the implementation. */
42
+ search: WebSearchCallback;
43
+ /** Default max results if not specified by the LLM. */
44
+ defaultMaxResults?: number;
45
+ }
46
+ /**
47
+ * Build the `web_search` tool over a caller-supplied provider.
48
+ *
49
+ * The tool holds no API key and talks to no service; everything network-facing lives in the `search`
50
+ * callback, which is why one tool serves Brave, a self-hosted endpoint, or a fixture in a test. Use
51
+ * it to find pages and `web_fetch` to read one — results carry a snippet, not a body.
52
+ *
53
+ * The model's `max_results` (1..20) wins over `defaultMaxResults`, and the list is sliced to it even
54
+ * when the provider returns more. A rejecting provider yields `{ ok: false, error: "search_failed" }`
55
+ * with its message attached; a provider resolving `[]` yields `{ ok: true, results: [] }`. The two
56
+ * are different signals, so an adapter that swallows its own failures — as
57
+ * {@link createGenericHttpSearchAdapter} deliberately does — reports the first as the second.
58
+ */
59
+ declare function createWebSearchTool(opts: CreateWebSearchToolOptions): CustomTool;
60
+
61
+ export { type CreateWebSearchToolOptions as C, type WebSearchCallback as W, type WebSearchResult as a, createWebSearchTool as c };
@@ -0,0 +1,71 @@
1
+ 'use strict';
2
+
3
+ var sdk = require('@theokit/sdk');
4
+
5
+ // src/web-search-searxng.ts
6
+ function parseBase(raw) {
7
+ const fail = () => {
8
+ throw new sdk.ConfigurationError(
9
+ "SEARXNG_BASE_URL is not set or not a URL (pass { baseUrl } or set the env var).",
10
+ { code: "no_base_url" }
11
+ );
12
+ };
13
+ if (!raw) return fail();
14
+ try {
15
+ const url = new URL(raw);
16
+ if (!url.pathname.endsWith("/")) url.pathname += "/";
17
+ return url;
18
+ } catch {
19
+ return fail();
20
+ }
21
+ }
22
+ function createSearxngWebSearchAdapter(opts = {}) {
23
+ const base = parseBase(opts.baseUrl ?? process.env.SEARXNG_BASE_URL);
24
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
25
+ return async (query, maxResults) => {
26
+ const url = new URL("search", base);
27
+ url.searchParams.set("q", query);
28
+ url.searchParams.set("format", "json");
29
+ const res = await fetchImpl(url.toString(), { headers: { Accept: "application/json" } });
30
+ if (!res.ok) throw new Error(`searxng_search_failed: HTTP ${res.status}`);
31
+ const json = await res.json();
32
+ const results = Array.isArray(json?.results) ? json.results : [];
33
+ return results.slice(0, maxResults).map((r) => ({
34
+ title: String(r?.title ?? ""),
35
+ url: String(r?.url ?? ""),
36
+ snippet: String(r?.content ?? "")
37
+ }));
38
+ };
39
+ }
40
+ var TAVILY_ENDPOINT = "https://api.tavily.com/search";
41
+ function createTavilyWebSearchAdapter(opts = {}) {
42
+ const apiKey = opts.apiKey ?? process.env.TAVILY_API_KEY;
43
+ if (!apiKey) {
44
+ throw new sdk.ConfigurationError(
45
+ "TAVILY_API_KEY is not set (pass { apiKey } or set the env var).",
46
+ { code: "no_api_key" }
47
+ );
48
+ }
49
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
50
+ const endpoint = new URL(opts.endpoint ?? TAVILY_ENDPOINT).toString();
51
+ return async (query, maxResults) => {
52
+ const res = await fetchImpl(endpoint, {
53
+ method: "POST",
54
+ headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
55
+ body: JSON.stringify({ query, max_results: maxResults })
56
+ });
57
+ if (!res.ok) throw new Error(`tavily_search_failed: HTTP ${res.status}`);
58
+ const json = await res.json();
59
+ const results = Array.isArray(json?.results) ? json.results : [];
60
+ return results.slice(0, maxResults).map((r) => ({
61
+ title: String(r?.title ?? ""),
62
+ url: String(r?.url ?? ""),
63
+ snippet: String(r?.content ?? "")
64
+ }));
65
+ };
66
+ }
67
+
68
+ exports.createSearxngWebSearchAdapter = createSearxngWebSearchAdapter;
69
+ exports.createTavilyWebSearchAdapter = createTavilyWebSearchAdapter;
70
+ //# sourceMappingURL=web-search-adapters.cjs.map
71
+ //# sourceMappingURL=web-search-adapters.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/web-search-searxng.ts","../src/web-search-tavily.ts"],"names":["ConfigurationError"],"mappings":"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","file":"web-search-adapters.cjs","sourcesContent":["/**\n * SearXNG adapter for `createWebSearchTool`.\n *\n * `createSearxngWebSearchAdapter()` returns a `WebSearchCallback` that queries a self-hosted\n * SearXNG instance (`SEARXNG_BASE_URL`) with `format=json`, which the instance must enable. The\n * call is keyless. A failed request rejects (`searxng_search_failed`) so the tool reports\n * `search_failed`; a 200 body without `results` is an empty search. SearXNG has no count\n * parameter, so the list is sliced here. The base URL is chosen by the consumer, never by the\n * model, so a plain fetch is used (like the Brave adapter's fixed host); `fetchImpl` is injectable\n * for offline tests.\n */\n\nimport { ConfigurationError } from \"@theokit/sdk\";\n\nimport type { WebSearchCallback, WebSearchResult } from \"./web-search.js\";\n\ntype FetchLike = (url: string, init?: RequestInit) => Promise<Response>;\n\n/** Options for {@link createSearxngWebSearchAdapter}. */\nexport interface CreateSearxngWebSearchAdapterOptions {\n /** Instance base URL. Defaults to `process.env.SEARXNG_BASE_URL`. */\n baseUrl?: string;\n /** Injectable fetch (default `globalThis.fetch`) — set in tests for offline runs. */\n fetchImpl?: FetchLike;\n}\n\ninterface SearxngResult {\n title?: unknown;\n url?: unknown;\n content?: unknown;\n}\n\nfunction parseBase(raw: string | undefined): URL {\n const fail = (): never => {\n throw new ConfigurationError(\n \"SEARXNG_BASE_URL is not set or not a URL (pass { baseUrl } or set the env var).\",\n { code: \"no_base_url\" },\n );\n };\n if (!raw) return fail();\n try {\n const url = new URL(raw);\n if (!url.pathname.endsWith(\"/\")) url.pathname += \"/\";\n return url;\n } catch {\n return fail();\n }\n}\n\n/**\n * Build a `WebSearchCallback` backed by a SearXNG instance. Throws a typed\n * `ConfigurationError` (\"no_base_url\") at creation when no valid base URL is available.\n */\nexport function createSearxngWebSearchAdapter(\n opts: CreateSearxngWebSearchAdapterOptions = {},\n): WebSearchCallback {\n const base = parseBase(opts.baseUrl ?? process.env.SEARXNG_BASE_URL);\n const fetchImpl = opts.fetchImpl ?? globalThis.fetch;\n\n return async (query: string, maxResults: number): Promise<WebSearchResult[]> => {\n const url = new URL(\"search\", base);\n url.searchParams.set(\"q\", query);\n url.searchParams.set(\"format\", \"json\");\n const res = await fetchImpl(url.toString(), { headers: { Accept: \"application/json\" } });\n if (!res.ok) throw new Error(`searxng_search_failed: HTTP ${res.status}`);\n const json = (await res.json()) as { results?: unknown } | null;\n const results: SearxngResult[] = Array.isArray(json?.results) ? json.results : [];\n return results.slice(0, maxResults).map((r) => ({\n title: String(r?.title ?? \"\"),\n url: String(r?.url ?? \"\"),\n snippet: String(r?.content ?? \"\"),\n }));\n };\n}\n","/**\n * Tavily adapter for `createWebSearchTool`.\n *\n * `createTavilyWebSearchAdapter()` returns a `WebSearchCallback` that posts the query to the\n * Tavily Search API with a bearer key (`TAVILY_API_KEY`). The tool is not modified: the adapter\n * plugs into its callback seam like the Brave one. A failed request rejects (`tavily_search_failed`)\n * so the tool reports `search_failed`, and a 200 body without `results` is an empty search. The\n * `fetch` impl is injectable for offline tests; a plain fetch is used because the host is fixed and\n * `screenedFetch` cannot carry the auth header. The message never carries the key.\n */\n\nimport { ConfigurationError } from \"@theokit/sdk\";\n\nimport type { WebSearchCallback, WebSearchResult } from \"./web-search.js\";\n\nconst TAVILY_ENDPOINT = \"https://api.tavily.com/search\";\n\ntype FetchLike = (url: string, init?: RequestInit) => Promise<Response>;\n\n/** Options for {@link createTavilyWebSearchAdapter}. */\nexport interface CreateTavilyWebSearchAdapterOptions {\n /** Tavily API key. Defaults to `process.env.TAVILY_API_KEY`. */\n apiKey?: string;\n /** Injectable fetch (default `globalThis.fetch`) — set in tests for offline runs. */\n fetchImpl?: FetchLike;\n /** Override the Tavily endpoint (proxy / test). */\n endpoint?: string;\n}\n\ninterface TavilyResult {\n title?: unknown;\n url?: unknown;\n content?: unknown;\n}\n\n/**\n * Build a `WebSearchCallback` backed by the Tavily Search API. Throws a typed\n * `ConfigurationError` (\"no_api_key\") at creation when no key is available.\n */\nexport function createTavilyWebSearchAdapter(\n opts: CreateTavilyWebSearchAdapterOptions = {},\n): WebSearchCallback {\n const apiKey = opts.apiKey ?? process.env.TAVILY_API_KEY;\n if (!apiKey) {\n throw new ConfigurationError(\n \"TAVILY_API_KEY is not set (pass { apiKey } or set the env var).\",\n { code: \"no_api_key\" },\n );\n }\n const fetchImpl = opts.fetchImpl ?? globalThis.fetch;\n const endpoint = new URL(opts.endpoint ?? TAVILY_ENDPOINT).toString();\n\n return async (query: string, maxResults: number): Promise<WebSearchResult[]> => {\n const res = await fetchImpl(endpoint, {\n method: \"POST\",\n headers: { Authorization: `Bearer ${apiKey}`, \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ query, max_results: maxResults }),\n });\n if (!res.ok) throw new Error(`tavily_search_failed: HTTP ${res.status}`);\n const json = (await res.json()) as { results?: unknown } | null;\n const results: TavilyResult[] = Array.isArray(json?.results) ? json.results : [];\n return results.slice(0, maxResults).map((r) => ({\n title: String(r?.title ?? \"\"),\n url: String(r?.url ?? \"\"),\n snippet: String(r?.content ?? \"\"),\n }));\n };\n}\n"]}
@@ -0,0 +1,57 @@
1
+ import { W as WebSearchCallback } from './web-search-CE82zZVl.cjs';
2
+ import '@theokit/sdk';
3
+
4
+ /**
5
+ * SearXNG adapter for `createWebSearchTool`.
6
+ *
7
+ * `createSearxngWebSearchAdapter()` returns a `WebSearchCallback` that queries a self-hosted
8
+ * SearXNG instance (`SEARXNG_BASE_URL`) with `format=json`, which the instance must enable. The
9
+ * call is keyless. A failed request rejects (`searxng_search_failed`) so the tool reports
10
+ * `search_failed`; a 200 body without `results` is an empty search. SearXNG has no count
11
+ * parameter, so the list is sliced here. The base URL is chosen by the consumer, never by the
12
+ * model, so a plain fetch is used (like the Brave adapter's fixed host); `fetchImpl` is injectable
13
+ * for offline tests.
14
+ */
15
+
16
+ type FetchLike$1 = (url: string, init?: RequestInit) => Promise<Response>;
17
+ /** Options for {@link createSearxngWebSearchAdapter}. */
18
+ interface CreateSearxngWebSearchAdapterOptions {
19
+ /** Instance base URL. Defaults to `process.env.SEARXNG_BASE_URL`. */
20
+ baseUrl?: string;
21
+ /** Injectable fetch (default `globalThis.fetch`) — set in tests for offline runs. */
22
+ fetchImpl?: FetchLike$1;
23
+ }
24
+ /**
25
+ * Build a `WebSearchCallback` backed by a SearXNG instance. Throws a typed
26
+ * `ConfigurationError` ("no_base_url") at creation when no valid base URL is available.
27
+ */
28
+ declare function createSearxngWebSearchAdapter(opts?: CreateSearxngWebSearchAdapterOptions): WebSearchCallback;
29
+
30
+ /**
31
+ * Tavily adapter for `createWebSearchTool`.
32
+ *
33
+ * `createTavilyWebSearchAdapter()` returns a `WebSearchCallback` that posts the query to the
34
+ * Tavily Search API with a bearer key (`TAVILY_API_KEY`). The tool is not modified: the adapter
35
+ * plugs into its callback seam like the Brave one. A failed request rejects (`tavily_search_failed`)
36
+ * so the tool reports `search_failed`, and a 200 body without `results` is an empty search. The
37
+ * `fetch` impl is injectable for offline tests; a plain fetch is used because the host is fixed and
38
+ * `screenedFetch` cannot carry the auth header. The message never carries the key.
39
+ */
40
+
41
+ type FetchLike = (url: string, init?: RequestInit) => Promise<Response>;
42
+ /** Options for {@link createTavilyWebSearchAdapter}. */
43
+ interface CreateTavilyWebSearchAdapterOptions {
44
+ /** Tavily API key. Defaults to `process.env.TAVILY_API_KEY`. */
45
+ apiKey?: string;
46
+ /** Injectable fetch (default `globalThis.fetch`) — set in tests for offline runs. */
47
+ fetchImpl?: FetchLike;
48
+ /** Override the Tavily endpoint (proxy / test). */
49
+ endpoint?: string;
50
+ }
51
+ /**
52
+ * Build a `WebSearchCallback` backed by the Tavily Search API. Throws a typed
53
+ * `ConfigurationError` ("no_api_key") at creation when no key is available.
54
+ */
55
+ declare function createTavilyWebSearchAdapter(opts?: CreateTavilyWebSearchAdapterOptions): WebSearchCallback;
56
+
57
+ export { type CreateSearxngWebSearchAdapterOptions, type CreateTavilyWebSearchAdapterOptions, createSearxngWebSearchAdapter, createTavilyWebSearchAdapter };
@@ -0,0 +1,57 @@
1
+ import { W as WebSearchCallback } from './web-search-CE82zZVl.js';
2
+ import '@theokit/sdk';
3
+
4
+ /**
5
+ * SearXNG adapter for `createWebSearchTool`.
6
+ *
7
+ * `createSearxngWebSearchAdapter()` returns a `WebSearchCallback` that queries a self-hosted
8
+ * SearXNG instance (`SEARXNG_BASE_URL`) with `format=json`, which the instance must enable. The
9
+ * call is keyless. A failed request rejects (`searxng_search_failed`) so the tool reports
10
+ * `search_failed`; a 200 body without `results` is an empty search. SearXNG has no count
11
+ * parameter, so the list is sliced here. The base URL is chosen by the consumer, never by the
12
+ * model, so a plain fetch is used (like the Brave adapter's fixed host); `fetchImpl` is injectable
13
+ * for offline tests.
14
+ */
15
+
16
+ type FetchLike$1 = (url: string, init?: RequestInit) => Promise<Response>;
17
+ /** Options for {@link createSearxngWebSearchAdapter}. */
18
+ interface CreateSearxngWebSearchAdapterOptions {
19
+ /** Instance base URL. Defaults to `process.env.SEARXNG_BASE_URL`. */
20
+ baseUrl?: string;
21
+ /** Injectable fetch (default `globalThis.fetch`) — set in tests for offline runs. */
22
+ fetchImpl?: FetchLike$1;
23
+ }
24
+ /**
25
+ * Build a `WebSearchCallback` backed by a SearXNG instance. Throws a typed
26
+ * `ConfigurationError` ("no_base_url") at creation when no valid base URL is available.
27
+ */
28
+ declare function createSearxngWebSearchAdapter(opts?: CreateSearxngWebSearchAdapterOptions): WebSearchCallback;
29
+
30
+ /**
31
+ * Tavily adapter for `createWebSearchTool`.
32
+ *
33
+ * `createTavilyWebSearchAdapter()` returns a `WebSearchCallback` that posts the query to the
34
+ * Tavily Search API with a bearer key (`TAVILY_API_KEY`). The tool is not modified: the adapter
35
+ * plugs into its callback seam like the Brave one. A failed request rejects (`tavily_search_failed`)
36
+ * so the tool reports `search_failed`, and a 200 body without `results` is an empty search. The
37
+ * `fetch` impl is injectable for offline tests; a plain fetch is used because the host is fixed and
38
+ * `screenedFetch` cannot carry the auth header. The message never carries the key.
39
+ */
40
+
41
+ type FetchLike = (url: string, init?: RequestInit) => Promise<Response>;
42
+ /** Options for {@link createTavilyWebSearchAdapter}. */
43
+ interface CreateTavilyWebSearchAdapterOptions {
44
+ /** Tavily API key. Defaults to `process.env.TAVILY_API_KEY`. */
45
+ apiKey?: string;
46
+ /** Injectable fetch (default `globalThis.fetch`) — set in tests for offline runs. */
47
+ fetchImpl?: FetchLike;
48
+ /** Override the Tavily endpoint (proxy / test). */
49
+ endpoint?: string;
50
+ }
51
+ /**
52
+ * Build a `WebSearchCallback` backed by the Tavily Search API. Throws a typed
53
+ * `ConfigurationError` ("no_api_key") at creation when no key is available.
54
+ */
55
+ declare function createTavilyWebSearchAdapter(opts?: CreateTavilyWebSearchAdapterOptions): WebSearchCallback;
56
+
57
+ export { type CreateSearxngWebSearchAdapterOptions, type CreateTavilyWebSearchAdapterOptions, createSearxngWebSearchAdapter, createTavilyWebSearchAdapter };
@@ -0,0 +1,68 @@
1
+ import { ConfigurationError } from '@theokit/sdk';
2
+
3
+ // src/web-search-searxng.ts
4
+ function parseBase(raw) {
5
+ const fail = () => {
6
+ throw new ConfigurationError(
7
+ "SEARXNG_BASE_URL is not set or not a URL (pass { baseUrl } or set the env var).",
8
+ { code: "no_base_url" }
9
+ );
10
+ };
11
+ if (!raw) return fail();
12
+ try {
13
+ const url = new URL(raw);
14
+ if (!url.pathname.endsWith("/")) url.pathname += "/";
15
+ return url;
16
+ } catch {
17
+ return fail();
18
+ }
19
+ }
20
+ function createSearxngWebSearchAdapter(opts = {}) {
21
+ const base = parseBase(opts.baseUrl ?? process.env.SEARXNG_BASE_URL);
22
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
23
+ return async (query, maxResults) => {
24
+ const url = new URL("search", base);
25
+ url.searchParams.set("q", query);
26
+ url.searchParams.set("format", "json");
27
+ const res = await fetchImpl(url.toString(), { headers: { Accept: "application/json" } });
28
+ if (!res.ok) throw new Error(`searxng_search_failed: HTTP ${res.status}`);
29
+ const json = await res.json();
30
+ const results = Array.isArray(json?.results) ? json.results : [];
31
+ return results.slice(0, maxResults).map((r) => ({
32
+ title: String(r?.title ?? ""),
33
+ url: String(r?.url ?? ""),
34
+ snippet: String(r?.content ?? "")
35
+ }));
36
+ };
37
+ }
38
+ var TAVILY_ENDPOINT = "https://api.tavily.com/search";
39
+ function createTavilyWebSearchAdapter(opts = {}) {
40
+ const apiKey = opts.apiKey ?? process.env.TAVILY_API_KEY;
41
+ if (!apiKey) {
42
+ throw new ConfigurationError(
43
+ "TAVILY_API_KEY is not set (pass { apiKey } or set the env var).",
44
+ { code: "no_api_key" }
45
+ );
46
+ }
47
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
48
+ const endpoint = new URL(opts.endpoint ?? TAVILY_ENDPOINT).toString();
49
+ return async (query, maxResults) => {
50
+ const res = await fetchImpl(endpoint, {
51
+ method: "POST",
52
+ headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
53
+ body: JSON.stringify({ query, max_results: maxResults })
54
+ });
55
+ if (!res.ok) throw new Error(`tavily_search_failed: HTTP ${res.status}`);
56
+ const json = await res.json();
57
+ const results = Array.isArray(json?.results) ? json.results : [];
58
+ return results.slice(0, maxResults).map((r) => ({
59
+ title: String(r?.title ?? ""),
60
+ url: String(r?.url ?? ""),
61
+ snippet: String(r?.content ?? "")
62
+ }));
63
+ };
64
+ }
65
+
66
+ export { createSearxngWebSearchAdapter, createTavilyWebSearchAdapter };
67
+ //# sourceMappingURL=web-search-adapters.js.map
68
+ //# sourceMappingURL=web-search-adapters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/web-search-searxng.ts","../src/web-search-tavily.ts"],"names":["ConfigurationError"],"mappings":"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","file":"web-search-adapters.js","sourcesContent":["/**\n * SearXNG adapter for `createWebSearchTool`.\n *\n * `createSearxngWebSearchAdapter()` returns a `WebSearchCallback` that queries a self-hosted\n * SearXNG instance (`SEARXNG_BASE_URL`) with `format=json`, which the instance must enable. The\n * call is keyless. A failed request rejects (`searxng_search_failed`) so the tool reports\n * `search_failed`; a 200 body without `results` is an empty search. SearXNG has no count\n * parameter, so the list is sliced here. The base URL is chosen by the consumer, never by the\n * model, so a plain fetch is used (like the Brave adapter's fixed host); `fetchImpl` is injectable\n * for offline tests.\n */\n\nimport { ConfigurationError } from \"@theokit/sdk\";\n\nimport type { WebSearchCallback, WebSearchResult } from \"./web-search.js\";\n\ntype FetchLike = (url: string, init?: RequestInit) => Promise<Response>;\n\n/** Options for {@link createSearxngWebSearchAdapter}. */\nexport interface CreateSearxngWebSearchAdapterOptions {\n /** Instance base URL. Defaults to `process.env.SEARXNG_BASE_URL`. */\n baseUrl?: string;\n /** Injectable fetch (default `globalThis.fetch`) — set in tests for offline runs. */\n fetchImpl?: FetchLike;\n}\n\ninterface SearxngResult {\n title?: unknown;\n url?: unknown;\n content?: unknown;\n}\n\nfunction parseBase(raw: string | undefined): URL {\n const fail = (): never => {\n throw new ConfigurationError(\n \"SEARXNG_BASE_URL is not set or not a URL (pass { baseUrl } or set the env var).\",\n { code: \"no_base_url\" },\n );\n };\n if (!raw) return fail();\n try {\n const url = new URL(raw);\n if (!url.pathname.endsWith(\"/\")) url.pathname += \"/\";\n return url;\n } catch {\n return fail();\n }\n}\n\n/**\n * Build a `WebSearchCallback` backed by a SearXNG instance. Throws a typed\n * `ConfigurationError` (\"no_base_url\") at creation when no valid base URL is available.\n */\nexport function createSearxngWebSearchAdapter(\n opts: CreateSearxngWebSearchAdapterOptions = {},\n): WebSearchCallback {\n const base = parseBase(opts.baseUrl ?? process.env.SEARXNG_BASE_URL);\n const fetchImpl = opts.fetchImpl ?? globalThis.fetch;\n\n return async (query: string, maxResults: number): Promise<WebSearchResult[]> => {\n const url = new URL(\"search\", base);\n url.searchParams.set(\"q\", query);\n url.searchParams.set(\"format\", \"json\");\n const res = await fetchImpl(url.toString(), { headers: { Accept: \"application/json\" } });\n if (!res.ok) throw new Error(`searxng_search_failed: HTTP ${res.status}`);\n const json = (await res.json()) as { results?: unknown } | null;\n const results: SearxngResult[] = Array.isArray(json?.results) ? json.results : [];\n return results.slice(0, maxResults).map((r) => ({\n title: String(r?.title ?? \"\"),\n url: String(r?.url ?? \"\"),\n snippet: String(r?.content ?? \"\"),\n }));\n };\n}\n","/**\n * Tavily adapter for `createWebSearchTool`.\n *\n * `createTavilyWebSearchAdapter()` returns a `WebSearchCallback` that posts the query to the\n * Tavily Search API with a bearer key (`TAVILY_API_KEY`). The tool is not modified: the adapter\n * plugs into its callback seam like the Brave one. A failed request rejects (`tavily_search_failed`)\n * so the tool reports `search_failed`, and a 200 body without `results` is an empty search. The\n * `fetch` impl is injectable for offline tests; a plain fetch is used because the host is fixed and\n * `screenedFetch` cannot carry the auth header. The message never carries the key.\n */\n\nimport { ConfigurationError } from \"@theokit/sdk\";\n\nimport type { WebSearchCallback, WebSearchResult } from \"./web-search.js\";\n\nconst TAVILY_ENDPOINT = \"https://api.tavily.com/search\";\n\ntype FetchLike = (url: string, init?: RequestInit) => Promise<Response>;\n\n/** Options for {@link createTavilyWebSearchAdapter}. */\nexport interface CreateTavilyWebSearchAdapterOptions {\n /** Tavily API key. Defaults to `process.env.TAVILY_API_KEY`. */\n apiKey?: string;\n /** Injectable fetch (default `globalThis.fetch`) — set in tests for offline runs. */\n fetchImpl?: FetchLike;\n /** Override the Tavily endpoint (proxy / test). */\n endpoint?: string;\n}\n\ninterface TavilyResult {\n title?: unknown;\n url?: unknown;\n content?: unknown;\n}\n\n/**\n * Build a `WebSearchCallback` backed by the Tavily Search API. Throws a typed\n * `ConfigurationError` (\"no_api_key\") at creation when no key is available.\n */\nexport function createTavilyWebSearchAdapter(\n opts: CreateTavilyWebSearchAdapterOptions = {},\n): WebSearchCallback {\n const apiKey = opts.apiKey ?? process.env.TAVILY_API_KEY;\n if (!apiKey) {\n throw new ConfigurationError(\n \"TAVILY_API_KEY is not set (pass { apiKey } or set the env var).\",\n { code: \"no_api_key\" },\n );\n }\n const fetchImpl = opts.fetchImpl ?? globalThis.fetch;\n const endpoint = new URL(opts.endpoint ?? TAVILY_ENDPOINT).toString();\n\n return async (query: string, maxResults: number): Promise<WebSearchResult[]> => {\n const res = await fetchImpl(endpoint, {\n method: \"POST\",\n headers: { Authorization: `Bearer ${apiKey}`, \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ query, max_results: maxResults }),\n });\n if (!res.ok) throw new Error(`tavily_search_failed: HTTP ${res.status}`);\n const json = (await res.json()) as { results?: unknown } | null;\n const results: TavilyResult[] = Array.isArray(json?.results) ? json.results : [];\n return results.slice(0, maxResults).map((r) => ({\n title: String(r?.title ?? \"\"),\n url: String(r?.url ?? \"\"),\n snippet: String(r?.content ?? \"\"),\n }));\n };\n}\n"]}