@warlock.js/ai-tools 4.5.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.
Files changed (83) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/LICENSE +21 -0
  3. package/README.md +164 -0
  4. package/cjs/index.cjs +2519 -0
  5. package/cjs/index.cjs.map +1 -0
  6. package/esm/contracts/http.type.d.mts +96 -0
  7. package/esm/contracts/http.type.d.mts.map +1 -0
  8. package/esm/contracts/index.d.mts +4 -0
  9. package/esm/contracts/mcp.type.d.mts +216 -0
  10. package/esm/contracts/mcp.type.d.mts.map +1 -0
  11. package/esm/contracts/utility.type.d.mts +96 -0
  12. package/esm/contracts/utility.type.d.mts.map +1 -0
  13. package/esm/contracts/web.type.d.mts +136 -0
  14. package/esm/contracts/web.type.d.mts.map +1 -0
  15. package/esm/errors.d.mts +225 -0
  16. package/esm/errors.d.mts.map +1 -0
  17. package/esm/errors.mjs +136 -0
  18. package/esm/errors.mjs.map +1 -0
  19. package/esm/http/http-request.d.mts +57 -0
  20. package/esm/http/http-request.d.mts.map +1 -0
  21. package/esm/http/http-request.mjs +221 -0
  22. package/esm/http/http-request.mjs.map +1 -0
  23. package/esm/index.d.mts +19 -0
  24. package/esm/index.mjs +15 -0
  25. package/esm/mcp/client.mjs +199 -0
  26. package/esm/mcp/client.mjs.map +1 -0
  27. package/esm/mcp/index.d.mts +43 -0
  28. package/esm/mcp/index.d.mts.map +1 -0
  29. package/esm/mcp/index.mjs +19 -0
  30. package/esm/mcp/index.mjs.map +1 -0
  31. package/esm/mcp/json-schema-to-standard.d.mts +34 -0
  32. package/esm/mcp/json-schema-to-standard.d.mts.map +1 -0
  33. package/esm/mcp/json-schema-to-standard.mjs +147 -0
  34. package/esm/mcp/json-schema-to-standard.mjs.map +1 -0
  35. package/esm/mcp/serve.d.mts +46 -0
  36. package/esm/mcp/serve.d.mts.map +1 -0
  37. package/esm/mcp/serve.mjs +264 -0
  38. package/esm/mcp/serve.mjs.map +1 -0
  39. package/esm/mcp/transport.d.mts +48 -0
  40. package/esm/mcp/transport.d.mts.map +1 -0
  41. package/esm/mcp/transport.mjs +381 -0
  42. package/esm/mcp/transport.mjs.map +1 -0
  43. package/esm/mcp/transport.type.d.mts +51 -0
  44. package/esm/mcp/transport.type.d.mts.map +1 -0
  45. package/esm/node_modules/@standard-schema/spec/dist/index.d.mts +80 -0
  46. package/esm/node_modules/@standard-schema/spec/dist/index.d.mts.map +1 -0
  47. package/esm/register.d.mts +55 -0
  48. package/esm/register.d.mts.map +1 -0
  49. package/esm/register.mjs +21 -0
  50. package/esm/register.mjs.map +1 -0
  51. package/esm/schema.mjs +127 -0
  52. package/esm/schema.mjs.map +1 -0
  53. package/esm/utility/calculator.d.mts +35 -0
  54. package/esm/utility/calculator.d.mts.map +1 -0
  55. package/esm/utility/calculator.mjs +272 -0
  56. package/esm/utility/calculator.mjs.map +1 -0
  57. package/esm/utility/date-time.d.mts +57 -0
  58. package/esm/utility/date-time.d.mts.map +1 -0
  59. package/esm/utility/date-time.mjs +193 -0
  60. package/esm/utility/date-time.mjs.map +1 -0
  61. package/esm/utility/index.d.mts +2 -0
  62. package/esm/utility/index.mjs +4 -0
  63. package/esm/utility/schema.mjs +114 -0
  64. package/esm/utility/schema.mjs.map +1 -0
  65. package/esm/web/fetch-url.d.mts +39 -0
  66. package/esm/web/fetch-url.d.mts.map +1 -0
  67. package/esm/web/fetch-url.mjs +228 -0
  68. package/esm/web/fetch-url.mjs.map +1 -0
  69. package/esm/web/index.d.mts +2 -0
  70. package/esm/web/index.mjs +4 -0
  71. package/esm/web/schema.mjs +86 -0
  72. package/esm/web/schema.mjs.map +1 -0
  73. package/esm/web/web-search.d.mts +38 -0
  74. package/esm/web/web-search.d.mts.map +1 -0
  75. package/esm/web/web-search.mjs +167 -0
  76. package/esm/web/web-search.mjs.map +1 -0
  77. package/llms-full.txt +326 -0
  78. package/llms.txt +11 -0
  79. package/package.json +45 -0
  80. package/skills/README.md +17 -0
  81. package/skills/connect-mcp-server/SKILL.md +98 -0
  82. package/skills/expose-as-mcp-server/SKILL.md +85 -0
  83. package/skills/use-web-and-http-tools/SKILL.md +125 -0
@@ -0,0 +1,125 @@
1
+ ---
2
+ name: use-web-and-http-tools
3
+ description: 'Wire the @warlock.js/ai-tools belt — web search, fetch/scrape, HTTP/REST, calculator, date-time — into an agent under their guardrails. Triggers: `ai.tools.webSearch`, `ai.tools.fetchUrl`, `ai.tools.http`, `ai.tools.calculator`, `ai.tools.dateTime`, `webSearchTool`, `fetchUrlTool`, `httpRequestTool`, `calculatorTool`, `dateTimeTool`, `web_search`, `fetch_url`, `http_request`, `calculator`, `date_time`, `WebToolError`, `HttpPolicyError`, `CalculatorError`, `DateTimeError`, `allowHosts`, `allowMethods`, `baseUrl`, `maxBytes`, `SearchProvider`, `TAVILY_API_KEY`; ''give an agent web search'', ''let an agent fetch/scrape a page'', ''let an agent call a REST API'', ''restrict which hosts/methods an agent can hit'', ''add a calculator/date tool to an agent'', ''SSRF guardrail for an agent tool''; typical import `import "@warlock.js/ai-tools"; import { ai } from "@warlock.js/ai"`. Skip: connecting to an external MCP server — `@warlock.js/ai-tools/connect-mcp-server/SKILL.md`; exposing tools AS an MCP server — `@warlock.js/ai-tools/expose-as-mcp-server/SKILL.md`; filesystem/shell tools — `@warlock.js/ai-workspace/use-a-workspace/SKILL.md`; defining your own custom tool — `@warlock.js/ai/define-ai-tool/SKILL.md`.'
4
+ ---
5
+
6
+ # Use the web + HTTP tool belt
7
+
8
+ `@warlock.js/ai-tools` ships five ready-made tools. Import the package once for its side effect and they register on the shared `ai` object under `ai.tools.*` — each is a factory returning a `ToolContract` you spread into `ai.agent({ tools: [...] })`.
9
+
10
+ ```ts
11
+ import "@warlock.js/ai-tools";
12
+ import { ai } from "@warlock.js/ai";
13
+ import { OpenAISDK } from "@warlock.js/ai-openai";
14
+
15
+ const openai = new OpenAISDK({ apiKey: process.env.OPENAI_API_KEY! });
16
+
17
+ const agent = ai.agent({
18
+ model: openai.model({ name: "gpt-4o" }),
19
+ systemPrompt: "Research, then call the API. Read tool errors and adjust.",
20
+ tools: [
21
+ ai.tools.webSearch({ provider: "tavily" }),
22
+ ai.tools.fetchUrl({ extract: "text", allowHosts: ["docs.stripe.com"] }),
23
+ ai.tools.http({ baseUrl: "https://api.stripe.com", allowHosts: ["api.stripe.com"] }),
24
+ ai.tools.calculator(),
25
+ ai.tools.dateTime({ defaultTimeZone: "UTC" }),
26
+ ],
27
+ maxTrips: 15,
28
+ });
29
+ ```
30
+
31
+ The construction `options` are the **rails** — they bound what the model may do; the model supplies the per-call inputs *within* those rails. Least-privilege is just array composition: hand an agent only the tools (and only the methods/hosts) it needs.
32
+
33
+ ## The five tools
34
+
35
+ | Factory | Name | Model input | Returns |
36
+ |---|---|---|---|
37
+ | `ai.tools.webSearch(options)` | `web_search` | `{ query, maxResults? }` | `{ results: { title, url, snippet, score? }[] }` |
38
+ | `ai.tools.fetchUrl(options?)` | `fetch_url` | `{ url }` | `{ url, status, content, truncated }` |
39
+ | `ai.tools.http(options?)` | `http_request` | `{ method?, url, headers?, body? }` | `{ status, headers, body, truncated }` |
40
+ | `ai.tools.calculator(options?)` | `calculator` | `{ expression }` | `{ result }` |
41
+ | `ai.tools.dateTime(options?)` | `date_time` | `{ op, iso?, from?, to?, amount?, unit?, timeZone?, format? }` | `{ value }` |
42
+
43
+ ## `web_search` — pick a provider, supply a key
44
+
45
+ ```ts
46
+ ai.tools.webSearch({ provider: "tavily", apiKey: process.env.TAVILY_API_KEY, maxResults: 8 });
47
+ ```
48
+
49
+ - `provider`: `"tavily"` (LLM-ready snippets + a relevance `score`), `"brave"`, or `"serpapi"`. Each is called over the global `fetch` — **no provider SDK is required**.
50
+ - `apiKey` falls back to a per-provider env var: `TAVILY_API_KEY` / `BRAVE_API_KEY` / `SERPAPI_API_KEY`. A missing key is a `WebToolError` of type `"missing-key"`.
51
+ - The model's `maxResults` is clamped into `[1, options.maxResults]` (default `5`).
52
+
53
+ ## `fetch_url` — read a page you have the URL for
54
+
55
+ ```ts
56
+ ai.tools.fetchUrl({ extract: "text", allowHosts: ["docs.stripe.com"], maxBytes: 500_000, timeoutMs: 10_000 });
57
+ ```
58
+
59
+ - `extract`: `"text"` (default — readability-extracted main text), `"html"` (raw body), or `"markdown"`. **`"text"`/`"markdown"` lazily import the `@mozilla/readability` + `jsdom` optional peers** — a missing peer is a `WebToolError` of type `"missing-peer"` carrying the `npm install @mozilla/readability jsdom` string. `"html"` needs nothing.
60
+ - `allowHosts` (when set) rejects any other host **before the fetch** (an SSRF guardrail) → `"denied-host"`.
61
+ - `maxBytes` caps the body and flags `truncated`; `timeoutMs` aborts via `AbortSignal.timeout`.
62
+
63
+ ## `http_request` — a guarded REST client
64
+
65
+ ```ts
66
+ ai.tools.http({
67
+ baseUrl: "https://api.stripe.com",
68
+ allowHosts: ["api.stripe.com"],
69
+ allowMethods: ["GET", "POST"], // default ["GET"] — mutating methods are opt-in
70
+ headers: { authorization: `Bearer ${process.env.STRIPE_KEY}` },
71
+ timeoutMs: 15_000,
72
+ maxBytes: 1_000_000,
73
+ });
74
+ ```
75
+
76
+ All guardrails are enforced **before the network call** and surface as a typed `HttpPolicyError`:
77
+ - a method outside `allowMethods` → `"method-not-allowed"`;
78
+ - a host outside `allowHosts` → `"host-not-allowed"` (SSRF guardrail);
79
+ - an unresolvable URL → `"invalid-url"`.
80
+
81
+ When `baseUrl` is set the model passes a **path** joined against it; otherwise it must pass an absolute `http(s)` URL. Static `headers` merge **under** the per-call headers (per-call wins). An object `body` is JSON-serialized (with a default `content-type: application/json`); a string `body` is sent verbatim; `body` is dropped for `GET`. The response body is JSON-parsed when the `content-type` is JSON, else returned as text.
82
+
83
+ ## `calculator` — SAFE arithmetic (never `eval`)
84
+
85
+ ```ts
86
+ const { data } = await ai.tools.calculator().invoke({ expression: "(3 + 4) * 2 ^ 3" });
87
+ data?.result; // 56
88
+ ```
89
+
90
+ Supports `+ - * / % ^`, unary signs, parentheses, and decimal/scientific literals, with the usual precedence (`^` highest and right-associative). The expression is tokenized and evaluated with a shunting-yard pass — there is no path to an identifier, function call, or property access, so nothing code-like can be smuggled in. Failures are a `CalculatorError`: `"syntax"`, `"divide-by-zero"`, or `"overflow"`.
91
+
92
+ ## `date_time` — clock + calendar
93
+
94
+ ```ts
95
+ await ai.tools.dateTime().invoke({ op: "now", format: "iso" });
96
+ await ai.tools.dateTime().invoke({ op: "add", iso: "2026-06-22T00:00:00Z", amount: 3, unit: "days" });
97
+ await ai.tools.dateTime().invoke({ op: "diff", iso: "2026-06-22T00:00:00Z", to: "2026-06-25T00:00:00Z", unit: "days" }); // "3"
98
+ await ai.tools.dateTime({ defaultTimeZone: "Africa/Cairo" }).invoke({ op: "format", iso: "2026-06-22T12:00:00Z", format: "datetime" });
99
+ ```
100
+
101
+ - `op`: `"now"` / `"add"` / `"diff"` / `"format"`. `format`: `"iso"` (default), `"date"`, `"time"`, `"datetime"`.
102
+ - For `diff`, pass the start instant as `iso` **or** `from` (an alias models reach for naturally with `from`/`to`); `iso` wins when both are set. The end instant is always `to`.
103
+ - Units are millisecond-based: `milliseconds`, `seconds`, `minutes`, `hours`, `days`, `weeks` (plus singular/short aliases like `day`/`d`). Calendar-relative `month`/`year` are intentionally **unsupported** — they have no fixed length. An unknown unit → `DateTimeError` `"invalid-unit"`; an unknown IANA zone → `"invalid-time-zone"`.
104
+ - For deterministic tests, inject a clock by calling the factory directly: `import { dateTimeTool } from "@warlock.js/ai-tools"; dateTimeTool({}, { clock: () => Date.parse("2026-06-22T00:00:00Z") })`.
105
+
106
+ ## Errors flow as data, not throws
107
+
108
+ Every failure above is thrown inside the tool handler, caught by the framework's `tool()` wrapper, and surfaced in the returned `{ error }` field — `invoke()` never throws, so the agent reads the failure as a `role: "tool"` message and self-corrects on the next trip. Each error class extends the `@warlock.js/ai` `AIError` base; branch on `.type`:
109
+
110
+ ```ts
111
+ import { WebToolError } from "@warlock.js/ai-tools";
112
+
113
+ const { error } = await ai.tools.fetchUrl({ allowHosts: ["docs.stripe.com"] }).invoke({ url: "http://evil.test" });
114
+ if (error instanceof WebToolError && error.type === "denied-host") {
115
+ // rejected before any network call
116
+ }
117
+ ```
118
+
119
+ ## See also
120
+
121
+ - [`@warlock.js/ai-tools/connect-mcp-server/SKILL.md`](@warlock.js/ai-tools/connect-mcp-server/SKILL.md) — adapt an external MCP server's tools as agent tools.
122
+ - [`@warlock.js/ai-tools/expose-as-mcp-server/SKILL.md`](@warlock.js/ai-tools/expose-as-mcp-server/SKILL.md) — publish these tools AS an MCP server.
123
+ - [`@warlock.js/ai-workspace/use-a-workspace/SKILL.md`](@warlock.js/ai-workspace/use-a-workspace/SKILL.md) — the filesystem + shell tool belt (jailed `read_file` / `edit_file` / `run_shell` / …).
124
+ - [`@warlock.js/ai/define-ai-tool/SKILL.md`](@warlock.js/ai/define-ai-tool/SKILL.md) — the `tool()` / `ToolContract` seam every tool here is built on.
125
+ - [`@warlock.js/ai/run-ai-agent/SKILL.md`](@warlock.js/ai/run-ai-agent/SKILL.md) — running the agent that consumes these tools.