@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 @@
1
+ {"version":3,"file":"errors.d.mts","names":[],"sources":["../../../../../../@warlock.js/ai-tools/src/errors.ts"],"mappings":";;;;;AAUA;;;;AAA6B;AAM7B;KANY,iBAAA;;;;;KAMA,sBAAA,GAAyB,cAAA;EAEZ,kDAAvB,IAAA,EAAM,iBAAiB;AAAA;;;;;;;;;;;;;;;;AAyB4C;AAiBrE;;cArBa,eAAA,SAAwB,OAAA;EAqBV;EAAA,SAnBT,IAAA,EAAM,iBAAA;cAEH,OAAA,UAAiB,OAAA,EAAS,sBAAA;AAAA;;;;;;;AA6BxB;AAmBvB;;KA/BY,eAAA;;;;;KAUA,oBAAA,GAAuB,cAAA;EAqBA,iDAnBjC,IAAA,EAAM,eAAe;AAAA;;;;;;AAuB4C;AAoBnE;;;;AAA+B;AAO/B;;;;;cA/Ba,aAAA,SAAsB,OAAA;EAiC3B;EAAA,SA/BU,IAAA,EAAM,eAAA;cAEH,OAAA,UAAiB,OAAA,EAAS,oBAAA;AAAA;AAqD/C;;;;;;;;;;;;AAAA,KAjCY,mBAAA;;;;;AAuC2D;KAhC3D,wBAAA,GAA2B,cAAA;EA0Db,iDAxDxB,IAAA,EAAM,mBAAmB,EAwDD;EAtDxB,MAAA;AAAA;;;;;;;;AAmEoB;AAsBtB;;;;;;;;;;;cAnEa,iBAAA,SAA0B,OAAA;EAuElB;EAAA,SArEH,IAAA,EAAM,mBAAA;EAqEc;EAAA,SAnEpB,MAAA;cAEG,OAAA,UAAiB,OAAA,EAAS,wBAAA;AAAA;;;;AAoFlB;AAM7B;;;;;;;;AAEyB;AAwBzB;;;;KA1FY,cAAA;;;;;KAWA,mBAAA,GAAsB,cAAA;EAiFV,gDA/EtB,IAAA,EAAM,cAAc;AAAA;;;;AAiF+C;;;;;;;;;;;;;;;;cA3DxD,YAAA,SAAqB,OAAA;;WAEhB,IAAA,EAAM,cAAA;cAEH,OAAA,UAAiB,OAAA,EAAS,mBAAA;AAAA;;;;;;;;;;;;KAmBnC,iBAAA;;;;;KAMA,sBAAA,GAAyB,cAAA;kDAEnC,IAAA,EAAM,iBAAiB;AAAA;;;;;;;;;;;;;;;;;;;;;;cAwBZ,eAAA,SAAwB,OAAA;;WAEnB,IAAA,EAAM,iBAAA;cAEH,OAAA,UAAiB,OAAA,EAAS,sBAAA;AAAA"}
package/esm/errors.mjs ADDED
@@ -0,0 +1,136 @@
1
+ import { AIError } from "@warlock.js/ai";
2
+
3
+ //#region ../@warlock.js/ai-tools/src/errors.ts
4
+ /**
5
+ * The `calculator` tool could not evaluate an expression — it was not
6
+ * valid arithmetic, divided by zero, or overflowed to a non-finite
7
+ * value.
8
+ *
9
+ * **Surface.** Thrown inside the tool handler, where `tool()` wraps it
10
+ * into a `ToolExecutionError` whose message is preserved verbatim and
11
+ * reaches the model as `{ error }` data, so the agent self-corrects
12
+ * rather than crashing. Extends the framework {@link AIError} (category
13
+ * `"tool"` via code `TOOL_EXEC_FAILED`) so it flows through the same
14
+ * typed error contract as every other AI error; branch on `error.type`
15
+ * for the specific failure.
16
+ *
17
+ * @example
18
+ * if (error instanceof CalculatorError && error.type === "divide-by-zero") {
19
+ * // the expression divided by zero — ask the model to revise it
20
+ * }
21
+ */
22
+ var CalculatorError = class extends AIError {
23
+ constructor(message, options) {
24
+ super("TOOL_EXEC_FAILED", message, options);
25
+ this.name = "CalculatorError";
26
+ this.type = options.type;
27
+ }
28
+ };
29
+ /**
30
+ * The `date_time` tool could not complete a call — a required field was
31
+ * missing or malformed, the unit/time zone was unrecognized, or the
32
+ * operation is unsupported.
33
+ *
34
+ * **Surface.** Thrown inside the tool handler, where `tool()` wraps it
35
+ * into a `ToolExecutionError` whose message reaches the model as
36
+ * `{ error }` data so the agent self-corrects. Extends the framework
37
+ * {@link AIError} (category `"tool"` via code `TOOL_EXEC_FAILED`); branch
38
+ * on `error.type` for the specific failure.
39
+ *
40
+ * @example
41
+ * if (error instanceof DateTimeError && error.type === "invalid-unit") {
42
+ * // the model passed an unknown unit — re-prompt with the allowed set
43
+ * }
44
+ */
45
+ var DateTimeError = class extends AIError {
46
+ constructor(message, options) {
47
+ super("TOOL_EXEC_FAILED", message, options);
48
+ this.name = "DateTimeError";
49
+ this.type = options.type;
50
+ }
51
+ };
52
+ /**
53
+ * The MCP client's transport layer failed — it could not connect, the
54
+ * peer spoke malformed JSON-RPC, a call timed out, or the transport was
55
+ * already closed.
56
+ *
57
+ * **Surface.** Connection / handshake failures surface at
58
+ * agent-construction time (the caller `await`s `client.tools()`). A
59
+ * `tools/call` failure raised mid-run is wrapped by `tool()` into a
60
+ * `ToolExecutionError` and reaches the model as `{ error }` data, so the
61
+ * agent self-corrects rather than crashing. Extends the framework
62
+ * {@link AIError} (category `"tool"`, code `TOOL_EXEC_FAILED`) so it
63
+ * flows through the same typed error contract as every other AI error;
64
+ * branch on `error.type` for the specific failure.
65
+ *
66
+ * @example
67
+ * if (error instanceof McpTransportError && error.type === "timeout") {
68
+ * // the remote call exceeded its deadline — retry or escalate
69
+ * }
70
+ */
71
+ var McpTransportError = class extends AIError {
72
+ constructor(message, options) {
73
+ super("TOOL_EXEC_FAILED", message, options);
74
+ this.name = "McpTransportError";
75
+ this.type = options.type;
76
+ this.method = options.method;
77
+ }
78
+ };
79
+ /**
80
+ * A web tool failed — a missing optional peer, an absent API key, a host
81
+ * rejected by the `allowHosts` guardrail, an unparseable URL, or a failed
82
+ * network call.
83
+ *
84
+ * **Surface.** Thrown from inside a tool's `execute`, so the framework's
85
+ * `tool()` wrapper catches it and surfaces it in the returned `{ error }`
86
+ * field (`invoke()` never throws) — the agent reads the failure as data
87
+ * and self-corrects rather than crashing. Extends the framework
88
+ * {@link AIError} (category `"tool"` via code `TOOL_EXEC_FAILED`) so it
89
+ * flows through the same typed error contract as every other AI error;
90
+ * branch on `error.type` for the specific failure.
91
+ *
92
+ * @example
93
+ * const { error } = await fetchTool.invoke({ url: "http://evil.test" });
94
+ * if (error instanceof WebToolError && error.type === "denied-host") {
95
+ * // the host was not in allowHosts — surfaced before any network call
96
+ * }
97
+ */
98
+ var WebToolError = class extends AIError {
99
+ constructor(message, options) {
100
+ super("TOOL_EXEC_FAILED", message, options);
101
+ this.name = "WebToolError";
102
+ this.type = options.type;
103
+ }
104
+ };
105
+ /**
106
+ * The `http_request` tool refused a call its construction-time policy
107
+ * does not permit — a disallowed method, a host outside the allowlist,
108
+ * or an unparseable URL. The rejection happens *before* any network
109
+ * request, so a guarded tool can never be coaxed into reaching an
110
+ * off-allowlist host (an SSRF guardrail).
111
+ *
112
+ * **Surface.** Thrown from inside the tool's `execute`, so the framework's
113
+ * `tool()` wrapper catches it and surfaces it in the returned `{ error }`
114
+ * field (`invoke()` never throws) — the agent reads the typed failure as
115
+ * data and self-corrects rather than crashing. Extends the framework
116
+ * {@link AIError} (category `"tool"` via code `TOOL_EXEC_FAILED`) so it
117
+ * flows through the same typed error contract as every other AI error;
118
+ * branch on `error.type` for the specific failure.
119
+ *
120
+ * @example
121
+ * const { error } = await httpTool.invoke({ url: "https://evil.test" });
122
+ * if (error instanceof HttpPolicyError && error.type === "host-not-allowed") {
123
+ * // the model tried to reach a host outside the configured allowlist
124
+ * }
125
+ */
126
+ var HttpPolicyError = class extends AIError {
127
+ constructor(message, options) {
128
+ super("TOOL_EXEC_FAILED", message, options);
129
+ this.name = "HttpPolicyError";
130
+ this.type = options.type;
131
+ }
132
+ };
133
+
134
+ //#endregion
135
+ export { CalculatorError, DateTimeError, HttpPolicyError, McpTransportError, WebToolError };
136
+ //# sourceMappingURL=errors.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.mjs","names":[],"sources":["../../../../../../@warlock.js/ai-tools/src/errors.ts"],"sourcesContent":["import { AIError, type AIErrorOptions } from \"@warlock.js/ai\";\n\n/**\n * Why the calculator rejected an expression.\n *\n * - `\"syntax\"` — the expression could not be tokenized or parsed\n * (an unknown character, a misplaced operator, unbalanced parens).\n * - `\"divide-by-zero\"` — evaluation divided (or took a modulo) by zero.\n * - `\"overflow\"` — the computed result was not a finite number.\n */\nexport type CalculatorFailure = \"syntax\" | \"divide-by-zero\" | \"overflow\";\n\n/**\n * Options for {@link CalculatorError} — the structured `type`\n * discriminator so a caller can branch without parsing the message.\n */\nexport type CalculatorErrorOptions = AIErrorOptions & {\n /** Which class of calculator failure occurred. */\n type: CalculatorFailure;\n};\n\n/**\n * The `calculator` tool could not evaluate an expression — it was not\n * valid arithmetic, divided by zero, or overflowed to a non-finite\n * value.\n *\n * **Surface.** Thrown inside the tool handler, where `tool()` wraps it\n * into a `ToolExecutionError` whose message is preserved verbatim and\n * reaches the model as `{ error }` data, so the agent self-corrects\n * rather than crashing. Extends the framework {@link AIError} (category\n * `\"tool\"` via code `TOOL_EXEC_FAILED`) so it flows through the same\n * typed error contract as every other AI error; branch on `error.type`\n * for the specific failure.\n *\n * @example\n * if (error instanceof CalculatorError && error.type === \"divide-by-zero\") {\n * // the expression divided by zero — ask the model to revise it\n * }\n */\nexport class CalculatorError extends AIError {\n /** Which class of calculator failure occurred. */\n public readonly type: CalculatorFailure;\n\n public constructor(message: string, options: CalculatorErrorOptions) {\n super(\"TOOL_EXEC_FAILED\", message, options);\n\n this.name = \"CalculatorError\";\n this.type = options.type;\n }\n}\n\n/**\n * Why the date-time tool rejected a call.\n *\n * - `\"invalid-input\"` — a required field for the chosen `op` was\n * missing or malformed (an unparsable ISO instant, a bad `amount`).\n * - `\"invalid-unit\"` — `unit` was not one of the supported units.\n * - `\"invalid-time-zone\"` — the IANA time zone was not recognized.\n * - `\"unsupported-op\"` — the `op` was not one this tool implements.\n */\nexport type DateTimeFailure =\n | \"invalid-input\"\n | \"invalid-unit\"\n | \"invalid-time-zone\"\n | \"unsupported-op\";\n\n/**\n * Options for {@link DateTimeError} — the structured `type`\n * discriminator so a caller can branch without parsing the message.\n */\nexport type DateTimeErrorOptions = AIErrorOptions & {\n /** Which class of date-time failure occurred. */\n type: DateTimeFailure;\n};\n\n/**\n * The `date_time` tool could not complete a call — a required field was\n * missing or malformed, the unit/time zone was unrecognized, or the\n * operation is unsupported.\n *\n * **Surface.** Thrown inside the tool handler, where `tool()` wraps it\n * into a `ToolExecutionError` whose message reaches the model as\n * `{ error }` data so the agent self-corrects. Extends the framework\n * {@link AIError} (category `\"tool\"` via code `TOOL_EXEC_FAILED`); branch\n * on `error.type` for the specific failure.\n *\n * @example\n * if (error instanceof DateTimeError && error.type === \"invalid-unit\") {\n * // the model passed an unknown unit — re-prompt with the allowed set\n * }\n */\nexport class DateTimeError extends AIError {\n /** Which class of date-time failure occurred. */\n public readonly type: DateTimeFailure;\n\n public constructor(message: string, options: DateTimeErrorOptions) {\n super(\"TOOL_EXEC_FAILED\", message, options);\n\n this.name = \"DateTimeError\";\n this.type = options.type;\n }\n}\n\n/**\n * Why an MCP transport operation failed.\n *\n * - `\"connect\"` — the transport could not be opened (child process\n * failed to spawn, HTTP endpoint unreachable) or the `initialize`\n * handshake failed.\n * - `\"protocol\"` — a malformed / unexpected JSON-RPC message, a\n * response that matched no in-flight request, or a missing field.\n * - `\"timeout\"` — a request exceeded its per-call deadline.\n * - `\"closed\"` — the transport was used after it was closed, or the\n * peer closed it mid-call.\n */\nexport type McpTransportFailure = \"connect\" | \"protocol\" | \"timeout\" | \"closed\";\n\n/**\n * Options for {@link McpTransportError} — the structured `type`\n * discriminator plus an optional JSON-RPC method name for branchable\n * diagnostics without parsing the message.\n */\nexport type McpTransportErrorOptions = AIErrorOptions & {\n /** Which class of transport failure occurred. */\n type: McpTransportFailure;\n /** The JSON-RPC method in flight when the failure occurred, if any. */\n method?: string;\n};\n\n/**\n * The MCP client's transport layer failed — it could not connect, the\n * peer spoke malformed JSON-RPC, a call timed out, or the transport was\n * already closed.\n *\n * **Surface.** Connection / handshake failures surface at\n * agent-construction time (the caller `await`s `client.tools()`). A\n * `tools/call` failure raised mid-run is wrapped by `tool()` into a\n * `ToolExecutionError` and reaches the model as `{ error }` data, so the\n * agent self-corrects rather than crashing. Extends the framework\n * {@link AIError} (category `\"tool\"`, code `TOOL_EXEC_FAILED`) so it\n * flows through the same typed error contract as every other AI error;\n * branch on `error.type` for the specific failure.\n *\n * @example\n * if (error instanceof McpTransportError && error.type === \"timeout\") {\n * // the remote call exceeded its deadline — retry or escalate\n * }\n */\nexport class McpTransportError extends AIError {\n /** Which class of transport failure occurred. */\n public readonly type: McpTransportFailure;\n /** The JSON-RPC method in flight when the failure occurred, if any. */\n public readonly method?: string;\n\n public constructor(message: string, options: McpTransportErrorOptions) {\n super(\"TOOL_EXEC_FAILED\", message, options);\n\n this.name = \"McpTransportError\";\n this.type = options.type;\n this.method = options.method;\n }\n}\n\n/**\n * Why a web tool (`ai.tools.webSearch` / `ai.tools.fetchUrl`) failed\n * before or during a network call.\n *\n * - `\"missing-peer\"` — an optional peer dependency the chosen mode needs\n * (`@mozilla/readability` + `jsdom` for text/markdown extraction, a\n * search provider SDK) is not installed. The message carries a curated\n * `npm install` string for the developer.\n * - `\"missing-key\"` — no API key was supplied via options or the\n * provider's environment variable.\n * - `\"denied-host\"` — the requested URL's host is not in the configured\n * `allowHosts` allowlist (an SSRF guardrail), rejected before any fetch.\n * - `\"invalid-url\"` — the supplied URL could not be parsed, or used a\n * non-`http(s)` scheme.\n * - `\"request-failed\"` — the network call itself failed (DNS, connection\n * reset, timeout) or the provider returned a non-OK status.\n */\nexport type WebToolFailure =\n | \"missing-peer\"\n | \"missing-key\"\n | \"denied-host\"\n | \"invalid-url\"\n | \"request-failed\";\n\n/**\n * Options for {@link WebToolError} — the structured `type` discriminator\n * so a caller can branch without parsing the message.\n */\nexport type WebToolErrorOptions = AIErrorOptions & {\n /** Which class of web-tool failure occurred. */\n type: WebToolFailure;\n};\n\n/**\n * A web tool failed — a missing optional peer, an absent API key, a host\n * rejected by the `allowHosts` guardrail, an unparseable URL, or a failed\n * network call.\n *\n * **Surface.** Thrown from inside a tool's `execute`, so the framework's\n * `tool()` wrapper catches it and surfaces it in the returned `{ error }`\n * field (`invoke()` never throws) — the agent reads the failure as data\n * and self-corrects rather than crashing. Extends the framework\n * {@link AIError} (category `\"tool\"` via code `TOOL_EXEC_FAILED`) so it\n * flows through the same typed error contract as every other AI error;\n * branch on `error.type` for the specific failure.\n *\n * @example\n * const { error } = await fetchTool.invoke({ url: \"http://evil.test\" });\n * if (error instanceof WebToolError && error.type === \"denied-host\") {\n * // the host was not in allowHosts — surfaced before any network call\n * }\n */\nexport class WebToolError extends AIError {\n /** Which class of web-tool failure occurred. */\n public readonly type: WebToolFailure;\n\n public constructor(message: string, options: WebToolErrorOptions) {\n super(\"TOOL_EXEC_FAILED\", message, options);\n\n this.name = \"WebToolError\";\n this.type = options.type;\n }\n}\n\n/**\n * Why an `http_request` call was rejected by its own guardrails, before\n * the network request was ever issued.\n *\n * - `\"method-not-allowed\"` — the model requested an HTTP method that is\n * not on the tool's `allowMethods` allowlist (defaults to `[\"GET\"]`).\n * - `\"host-not-allowed\"` — the resolved request host is not on the\n * tool's `allowHosts` allowlist (an SSRF guardrail).\n * - `\"invalid-url\"` — the supplied URL (or its join with `baseUrl`)\n * could not be parsed into an absolute `http(s)` URL.\n */\nexport type HttpPolicyFailure = \"method-not-allowed\" | \"host-not-allowed\" | \"invalid-url\";\n\n/**\n * Options for {@link HttpPolicyError} — the structured `type`\n * discriminator so a caller can branch without parsing the message.\n */\nexport type HttpPolicyErrorOptions = AIErrorOptions & {\n /** Which class of policy rejection occurred. */\n type: HttpPolicyFailure;\n};\n\n/**\n * The `http_request` tool refused a call its construction-time policy\n * does not permit — a disallowed method, a host outside the allowlist,\n * or an unparseable URL. The rejection happens *before* any network\n * request, so a guarded tool can never be coaxed into reaching an\n * off-allowlist host (an SSRF guardrail).\n *\n * **Surface.** Thrown from inside the tool's `execute`, so the framework's\n * `tool()` wrapper catches it and surfaces it in the returned `{ error }`\n * field (`invoke()` never throws) — the agent reads the typed failure as\n * data and self-corrects rather than crashing. Extends the framework\n * {@link AIError} (category `\"tool\"` via code `TOOL_EXEC_FAILED`) so it\n * flows through the same typed error contract as every other AI error;\n * branch on `error.type` for the specific failure.\n *\n * @example\n * const { error } = await httpTool.invoke({ url: \"https://evil.test\" });\n * if (error instanceof HttpPolicyError && error.type === \"host-not-allowed\") {\n * // the model tried to reach a host outside the configured allowlist\n * }\n */\nexport class HttpPolicyError extends AIError {\n /** Which class of policy rejection occurred. */\n public readonly type: HttpPolicyFailure;\n\n public constructor(message: string, options: HttpPolicyErrorOptions) {\n super(\"TOOL_EXEC_FAILED\", message, options);\n\n this.name = \"HttpPolicyError\";\n this.type = options.type;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAuCA,IAAa,kBAAb,cAAqC,QAAQ;CAI3C,AAAO,YAAY,SAAiB,SAAiC;EACnE,MAAM,oBAAoB,SAAS,OAAO;EAE1C,KAAK,OAAO;EACZ,KAAK,OAAO,QAAQ;CACtB;AACF;;;;;;;;;;;;;;;;;AA0CA,IAAa,gBAAb,cAAmC,QAAQ;CAIzC,AAAO,YAAY,SAAiB,SAA+B;EACjE,MAAM,oBAAoB,SAAS,OAAO;EAE1C,KAAK,OAAO;EACZ,KAAK,OAAO,QAAQ;CACtB;AACF;;;;;;;;;;;;;;;;;;;;AA+CA,IAAa,oBAAb,cAAuC,QAAQ;CAM7C,AAAO,YAAY,SAAiB,SAAmC;EACrE,MAAM,oBAAoB,SAAS,OAAO;EAE1C,KAAK,OAAO;EACZ,KAAK,OAAO,QAAQ;EACpB,KAAK,SAAS,QAAQ;CACxB;AACF;;;;;;;;;;;;;;;;;;;;AAsDA,IAAa,eAAb,cAAkC,QAAQ;CAIxC,AAAO,YAAY,SAAiB,SAA8B;EAChE,MAAM,oBAAoB,SAAS,OAAO;EAE1C,KAAK,OAAO;EACZ,KAAK,OAAO,QAAQ;CACtB;AACF;;;;;;;;;;;;;;;;;;;;;;AA6CA,IAAa,kBAAb,cAAqC,QAAQ;CAI3C,AAAO,YAAY,SAAiB,SAAiC;EACnE,MAAM,oBAAoB,SAAS,OAAO;EAE1C,KAAK,OAAO;EACZ,KAAK,OAAO,QAAQ;CACtB;AACF"}
@@ -0,0 +1,57 @@
1
+ import { HttpRequestInput, HttpRequestOptions, HttpRequestResult } from "../contracts/http.type.mjs";
2
+ import { ToolContract } from "@warlock.js/ai";
3
+
4
+ //#region ../@warlock.js/ai-tools/src/http/http-request.d.ts
5
+ /**
6
+ * Build the agent-facing `http_request` tool — a guarded HTTP/REST client
7
+ * over the global `fetch`. The `options` bound what the model may do; the
8
+ * model supplies the per-call URL / method / headers / body within those
9
+ * rails.
10
+ *
11
+ * **Guardrails (all enforced before the network call).**
12
+ * - **Method allowlist** — `allowMethods` (default `["GET"]`). A method
13
+ * outside the list is rejected with a typed
14
+ * {@link HttpPolicyError} (`type: "method-not-allowed"`).
15
+ * - **Host allowlist** — when `allowHosts` is set, any other host is
16
+ * rejected (`type: "host-not-allowed"`), an SSRF guardrail.
17
+ * - **`baseUrl` join** — when configured, the model passes a path that
18
+ * is resolved against `baseUrl`; otherwise it must pass an absolute
19
+ * `http(s)` URL. An unresolvable URL is rejected
20
+ * (`type: "invalid-url"`).
21
+ *
22
+ * **Request shaping.** Static `options.headers` are merged under the
23
+ * per-call `headers` (the per-call value wins). An object `body` is
24
+ * JSON-serialized with a `content-type: application/json` default; a
25
+ * string `body` is sent verbatim; `body` is dropped for bodyless methods
26
+ * (`GET`). The call is bounded by `timeoutMs` (default `15_000`) via an
27
+ * `AbortController`, also wired to `ctx.signal` for cooperative
28
+ * cancellation.
29
+ *
30
+ * **Response shaping.** Headers are returned with lower-cased keys. The
31
+ * body is read up to `maxBytes` (default `1_000_000`) and JSON-parsed
32
+ * when the response `content-type` is JSON, otherwise returned as text;
33
+ * `truncated` is `true` when the body was cut off at the cap (a truncated
34
+ * JSON body is returned as the raw partial string, since it can no longer
35
+ * be parsed).
36
+ *
37
+ * **Errors flow as data.** Every guardrail rejection and network failure
38
+ * is thrown inside `execute`; the framework's `tool()` wrapper catches it
39
+ * and surfaces it in the returned `{ error }` field, so the agent reads
40
+ * the failure and self-corrects rather than crashing.
41
+ *
42
+ * @param options - Construction-time policy bounding the tool.
43
+ * @returns A {@link ToolContract} the agent can call as `http_request`.
44
+ *
45
+ * @example
46
+ * const stripe = httpRequestTool({
47
+ * baseUrl: "https://api.stripe.com",
48
+ * allowHosts: ["api.stripe.com"],
49
+ * allowMethods: ["GET", "POST"],
50
+ * headers: { authorization: `Bearer ${process.env.STRIPE_KEY}` },
51
+ * });
52
+ * const { data } = await stripe.invoke({ method: "GET", url: "/v1/charges" });
53
+ */
54
+ declare function httpRequestTool(options?: HttpRequestOptions): ToolContract<HttpRequestInput, HttpRequestResult>;
55
+ //#endregion
56
+ export { httpRequestTool };
57
+ //# sourceMappingURL=http-request.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-request.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai-tools/src/http/http-request.ts"],"mappings":";;;;;;;AAwMA;;;;;;;;;;;;;;;AAEmD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAFnC,eAAA,CACd,OAAA,GAAS,kBAAA,GACR,YAAA,CAAa,gBAAA,EAAkB,iBAAA"}
@@ -0,0 +1,221 @@
1
+ import { HttpPolicyError } from "../errors.mjs";
2
+ import { objectSchema, optionalStringEnumField, optionalStringRecordField, passthroughField, stringField } from "../schema.mjs";
3
+ import { tool } from "@warlock.js/ai";
4
+
5
+ //#region ../@warlock.js/ai-tools/src/http/http-request.ts
6
+ /** Default tool name exposed to the LLM. */
7
+ const DEFAULT_NAME = "http_request";
8
+ /** Default per-request wall-clock timeout, in milliseconds. */
9
+ const DEFAULT_TIMEOUT_MS = 15e3;
10
+ /** Default hard cap on response-body bytes before truncation. */
11
+ const DEFAULT_MAX_BYTES = 1e6;
12
+ /** The full set of HTTP methods, in the order they appear in {@link HttpMethod}. */
13
+ const ALL_METHODS = [
14
+ "GET",
15
+ "POST",
16
+ "PUT",
17
+ "PATCH",
18
+ "DELETE"
19
+ ];
20
+ /** Methods that conventionally carry no request body — `body` is dropped for these. */
21
+ const BODYLESS_METHODS = new Set(["GET"]);
22
+ /**
23
+ * Standard Schema for {@link HttpRequestInput}. `url` is required;
24
+ * `method` is constrained to the canonical HTTP verb set (further
25
+ * narrowed to the tool's `allowMethods` at runtime); `headers` is an
26
+ * optional string-to-string record; `body` is an opaque passthrough the
27
+ * handler serializes based on its runtime type.
28
+ */
29
+ const httpRequestInputSchema = objectSchema({
30
+ method: optionalStringEnumField(ALL_METHODS),
31
+ url: stringField(),
32
+ headers: optionalStringRecordField(),
33
+ body: passthroughField()
34
+ });
35
+ /**
36
+ * Resolve the request target. With a `baseUrl` configured the model
37
+ * supplies a path joined against it; otherwise the model's `url` must be
38
+ * an absolute `http(s)` URL. Throws a typed {@link HttpPolicyError} of
39
+ * type `"invalid-url"` when the result cannot be parsed or is not an
40
+ * `http`/`https` URL — surfaced as `{ error }` data, never a crash.
41
+ */
42
+ function resolveUrl(rawUrl, baseUrl) {
43
+ let resolved;
44
+ try {
45
+ resolved = baseUrl !== void 0 ? new URL(rawUrl, baseUrl) : new URL(rawUrl);
46
+ } catch {
47
+ throw new HttpPolicyError(`http_request could not resolve a valid URL from "${rawUrl}"` + (baseUrl !== void 0 ? ` against base "${baseUrl}".` : "."), { type: "invalid-url" });
48
+ }
49
+ if (resolved.protocol !== "http:" && resolved.protocol !== "https:") throw new HttpPolicyError(`http_request only permits http(s) URLs; got "${resolved.protocol}".`, { type: "invalid-url" });
50
+ return resolved;
51
+ }
52
+ /**
53
+ * Read a `Response` body, capping at `maxBytes`. Returns the decoded text
54
+ * and whether it was cut off. Streams chunk-by-chunk so an oversized body
55
+ * is abandoned at the cap rather than fully buffered; falls back to
56
+ * `response.text()` (then a post-hoc byte slice) when the body is not a
57
+ * readable stream (e.g. a stubbed `Response` in tests).
58
+ */
59
+ async function readCappedBody(response, maxBytes) {
60
+ const body = response.body;
61
+ if (!body) return {
62
+ text: "",
63
+ truncated: false
64
+ };
65
+ const decoder = new TextDecoder();
66
+ const reader = body.getReader();
67
+ let received = 0;
68
+ let truncated = false;
69
+ let text = "";
70
+ try {
71
+ for (;;) {
72
+ const { done, value } = await reader.read();
73
+ if (done) break;
74
+ if (!value) continue;
75
+ const remaining = maxBytes - received;
76
+ if (value.byteLength > remaining) {
77
+ text += decoder.decode(value.subarray(0, remaining), { stream: true });
78
+ received = maxBytes;
79
+ truncated = true;
80
+ break;
81
+ }
82
+ text += decoder.decode(value, { stream: true });
83
+ received += value.byteLength;
84
+ }
85
+ } finally {
86
+ await reader.cancel().catch(() => void 0);
87
+ reader.releaseLock();
88
+ }
89
+ text += decoder.decode();
90
+ return {
91
+ text,
92
+ truncated
93
+ };
94
+ }
95
+ /**
96
+ * Decide whether a response's `content-type` indicates JSON. Matches
97
+ * `application/json` and the `+json` structured-suffix convention
98
+ * (e.g. `application/vnd.api+json`), case-insensitively.
99
+ */
100
+ function isJsonContentType(contentType) {
101
+ if (!contentType) return false;
102
+ const value = contentType.toLowerCase();
103
+ return value.includes("application/json") || value.includes("+json");
104
+ }
105
+ /**
106
+ * Build the agent-facing `http_request` tool — a guarded HTTP/REST client
107
+ * over the global `fetch`. The `options` bound what the model may do; the
108
+ * model supplies the per-call URL / method / headers / body within those
109
+ * rails.
110
+ *
111
+ * **Guardrails (all enforced before the network call).**
112
+ * - **Method allowlist** — `allowMethods` (default `["GET"]`). A method
113
+ * outside the list is rejected with a typed
114
+ * {@link HttpPolicyError} (`type: "method-not-allowed"`).
115
+ * - **Host allowlist** — when `allowHosts` is set, any other host is
116
+ * rejected (`type: "host-not-allowed"`), an SSRF guardrail.
117
+ * - **`baseUrl` join** — when configured, the model passes a path that
118
+ * is resolved against `baseUrl`; otherwise it must pass an absolute
119
+ * `http(s)` URL. An unresolvable URL is rejected
120
+ * (`type: "invalid-url"`).
121
+ *
122
+ * **Request shaping.** Static `options.headers` are merged under the
123
+ * per-call `headers` (the per-call value wins). An object `body` is
124
+ * JSON-serialized with a `content-type: application/json` default; a
125
+ * string `body` is sent verbatim; `body` is dropped for bodyless methods
126
+ * (`GET`). The call is bounded by `timeoutMs` (default `15_000`) via an
127
+ * `AbortController`, also wired to `ctx.signal` for cooperative
128
+ * cancellation.
129
+ *
130
+ * **Response shaping.** Headers are returned with lower-cased keys. The
131
+ * body is read up to `maxBytes` (default `1_000_000`) and JSON-parsed
132
+ * when the response `content-type` is JSON, otherwise returned as text;
133
+ * `truncated` is `true` when the body was cut off at the cap (a truncated
134
+ * JSON body is returned as the raw partial string, since it can no longer
135
+ * be parsed).
136
+ *
137
+ * **Errors flow as data.** Every guardrail rejection and network failure
138
+ * is thrown inside `execute`; the framework's `tool()` wrapper catches it
139
+ * and surfaces it in the returned `{ error }` field, so the agent reads
140
+ * the failure and self-corrects rather than crashing.
141
+ *
142
+ * @param options - Construction-time policy bounding the tool.
143
+ * @returns A {@link ToolContract} the agent can call as `http_request`.
144
+ *
145
+ * @example
146
+ * const stripe = httpRequestTool({
147
+ * baseUrl: "https://api.stripe.com",
148
+ * allowHosts: ["api.stripe.com"],
149
+ * allowMethods: ["GET", "POST"],
150
+ * headers: { authorization: `Bearer ${process.env.STRIPE_KEY}` },
151
+ * });
152
+ * const { data } = await stripe.invoke({ method: "GET", url: "/v1/charges" });
153
+ */
154
+ function httpRequestTool(options = {}) {
155
+ const allowMethods = options.allowMethods ?? ["GET"];
156
+ const allowedMethodSet = new Set(allowMethods);
157
+ const allowHostSet = options.allowHosts ? new Set(options.allowHosts) : void 0;
158
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
159
+ const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
160
+ const staticHeaders = options.headers;
161
+ return tool({
162
+ name: options.name ?? DEFAULT_NAME,
163
+ description: "Issue an HTTP request and return the status, response headers, and parsed body. Allowed methods and hosts are restricted by the tool's configuration; a request outside those rails is rejected before any network call. Pass an object body to send JSON, or a string to send it verbatim. The response body is JSON-parsed when the content-type is JSON, otherwise returned as text, and is capped — `truncated` is true when the body was cut off.",
164
+ action: (input) => `Requesting ${input.method ?? "GET"} ${input.url}`,
165
+ input: httpRequestInputSchema,
166
+ async execute(input, ctx) {
167
+ const method = input.method ?? "GET";
168
+ if (!allowedMethodSet.has(method)) throw new HttpPolicyError(`http_request method "${method}" is not allowed. Permitted methods: ${[...allowedMethodSet].join(", ")}.`, { type: "method-not-allowed" });
169
+ const url = resolveUrl(input.url, options.baseUrl);
170
+ if (allowHostSet && !allowHostSet.has(url.hostname)) throw new HttpPolicyError(`http_request host "${url.hostname}" is not in the allowlist. Permitted hosts: ${[...allowHostSet].join(", ")}.`, { type: "host-not-allowed" });
171
+ const headers = {
172
+ ...staticHeaders,
173
+ ...input.headers
174
+ };
175
+ let body;
176
+ if (!BODYLESS_METHODS.has(method) && input.body !== void 0) if (typeof input.body === "string") body = input.body;
177
+ else {
178
+ body = JSON.stringify(input.body);
179
+ if (!Object.keys(headers).some((key) => key.toLowerCase() === "content-type")) headers["content-type"] = "application/json";
180
+ }
181
+ const controller = new AbortController();
182
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
183
+ const onAbort = () => controller.abort();
184
+ if (ctx?.signal) if (ctx.signal.aborted) controller.abort();
185
+ else ctx.signal.addEventListener("abort", onAbort, { once: true });
186
+ let response;
187
+ try {
188
+ response = await fetch(url, {
189
+ method,
190
+ headers,
191
+ body,
192
+ signal: controller.signal
193
+ });
194
+ } finally {
195
+ clearTimeout(timer);
196
+ ctx?.signal?.removeEventListener("abort", onAbort);
197
+ }
198
+ const responseHeaders = {};
199
+ response.headers.forEach((value, key) => {
200
+ responseHeaders[key.toLowerCase()] = value;
201
+ });
202
+ const { text, truncated } = await readCappedBody(response, maxBytes);
203
+ let parsedBody = text;
204
+ if (!truncated && isJsonContentType(responseHeaders["content-type"]) && text.length > 0) try {
205
+ parsedBody = JSON.parse(text);
206
+ } catch {
207
+ parsedBody = text;
208
+ }
209
+ return {
210
+ status: response.status,
211
+ headers: responseHeaders,
212
+ body: parsedBody,
213
+ truncated
214
+ };
215
+ }
216
+ });
217
+ }
218
+
219
+ //#endregion
220
+ export { httpRequestTool };
221
+ //# sourceMappingURL=http-request.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-request.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai-tools/src/http/http-request.ts"],"sourcesContent":["import { type ToolContract, tool } from \"@warlock.js/ai\";\nimport type {\n HttpMethod,\n HttpRequestInput,\n HttpRequestOptions,\n HttpRequestResult,\n} from \"../contracts\";\nimport { HttpPolicyError } from \"../errors\";\nimport {\n objectSchema,\n optionalStringEnumField,\n optionalStringRecordField,\n passthroughField,\n stringField,\n} from \"../schema\";\n\n/** Default tool name exposed to the LLM. */\nconst DEFAULT_NAME = \"http_request\";\n\n/** Default per-request wall-clock timeout, in milliseconds. */\nconst DEFAULT_TIMEOUT_MS = 15_000;\n\n/** Default hard cap on response-body bytes before truncation. */\nconst DEFAULT_MAX_BYTES = 1_000_000;\n\n/** The full set of HTTP methods, in the order they appear in {@link HttpMethod}. */\nconst ALL_METHODS: readonly HttpMethod[] = [\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"];\n\n/** Methods that conventionally carry no request body — `body` is dropped for these. */\nconst BODYLESS_METHODS: ReadonlySet<HttpMethod> = new Set<HttpMethod>([\"GET\"]);\n\n/**\n * Standard Schema for {@link HttpRequestInput}. `url` is required;\n * `method` is constrained to the canonical HTTP verb set (further\n * narrowed to the tool's `allowMethods` at runtime); `headers` is an\n * optional string-to-string record; `body` is an opaque passthrough the\n * handler serializes based on its runtime type.\n */\nconst httpRequestInputSchema = objectSchema<HttpRequestInput>({\n method: optionalStringEnumField<HttpMethod>(ALL_METHODS),\n url: stringField(),\n headers: optionalStringRecordField(),\n body: passthroughField(),\n});\n\n/**\n * Resolve the request target. With a `baseUrl` configured the model\n * supplies a path joined against it; otherwise the model's `url` must be\n * an absolute `http(s)` URL. Throws a typed {@link HttpPolicyError} of\n * type `\"invalid-url\"` when the result cannot be parsed or is not an\n * `http`/`https` URL — surfaced as `{ error }` data, never a crash.\n */\nfunction resolveUrl(rawUrl: string, baseUrl: string | undefined): URL {\n let resolved: URL;\n\n try {\n // `new URL(input, base)` joins relative paths against `base` and\n // ignores `base` when `input` is already absolute, which is exactly\n // the \"path vs full URL\" behavior the design specifies.\n resolved = baseUrl !== undefined ? new URL(rawUrl, baseUrl) : new URL(rawUrl);\n } catch {\n throw new HttpPolicyError(\n `http_request could not resolve a valid URL from \"${rawUrl}\"` +\n (baseUrl !== undefined ? ` against base \"${baseUrl}\".` : \".\"),\n { type: \"invalid-url\" },\n );\n }\n\n if (resolved.protocol !== \"http:\" && resolved.protocol !== \"https:\") {\n throw new HttpPolicyError(\n `http_request only permits http(s) URLs; got \"${resolved.protocol}\".`,\n { type: \"invalid-url\" },\n );\n }\n\n return resolved;\n}\n\n/**\n * Read a `Response` body, capping at `maxBytes`. Returns the decoded text\n * and whether it was cut off. Streams chunk-by-chunk so an oversized body\n * is abandoned at the cap rather than fully buffered; falls back to\n * `response.text()` (then a post-hoc byte slice) when the body is not a\n * readable stream (e.g. a stubbed `Response` in tests).\n */\nasync function readCappedBody(\n response: Response,\n maxBytes: number,\n): Promise<{ text: string; truncated: boolean }> {\n const body = response.body;\n\n if (!body) {\n return { text: \"\", truncated: false };\n }\n\n const decoder = new TextDecoder();\n const reader = body.getReader();\n let received = 0;\n let truncated = false;\n let text = \"\";\n\n try {\n for (;;) {\n const { done, value } = await reader.read();\n\n if (done) {\n break;\n }\n\n if (!value) {\n continue;\n }\n\n const remaining = maxBytes - received;\n\n if (value.byteLength > remaining) {\n text += decoder.decode(value.subarray(0, remaining), { stream: true });\n received = maxBytes;\n truncated = true;\n break;\n }\n\n text += decoder.decode(value, { stream: true });\n received += value.byteLength;\n }\n } finally {\n // Release the lock and abandon any unread remainder.\n await reader.cancel().catch(() => undefined);\n reader.releaseLock();\n }\n\n text += decoder.decode();\n\n return { text, truncated };\n}\n\n/**\n * Decide whether a response's `content-type` indicates JSON. Matches\n * `application/json` and the `+json` structured-suffix convention\n * (e.g. `application/vnd.api+json`), case-insensitively.\n */\nfunction isJsonContentType(contentType: string | undefined): boolean {\n if (!contentType) {\n return false;\n }\n\n const value = contentType.toLowerCase();\n\n return value.includes(\"application/json\") || value.includes(\"+json\");\n}\n\n/**\n * Build the agent-facing `http_request` tool — a guarded HTTP/REST client\n * over the global `fetch`. The `options` bound what the model may do; the\n * model supplies the per-call URL / method / headers / body within those\n * rails.\n *\n * **Guardrails (all enforced before the network call).**\n * - **Method allowlist** — `allowMethods` (default `[\"GET\"]`). A method\n * outside the list is rejected with a typed\n * {@link HttpPolicyError} (`type: \"method-not-allowed\"`).\n * - **Host allowlist** — when `allowHosts` is set, any other host is\n * rejected (`type: \"host-not-allowed\"`), an SSRF guardrail.\n * - **`baseUrl` join** — when configured, the model passes a path that\n * is resolved against `baseUrl`; otherwise it must pass an absolute\n * `http(s)` URL. An unresolvable URL is rejected\n * (`type: \"invalid-url\"`).\n *\n * **Request shaping.** Static `options.headers` are merged under the\n * per-call `headers` (the per-call value wins). An object `body` is\n * JSON-serialized with a `content-type: application/json` default; a\n * string `body` is sent verbatim; `body` is dropped for bodyless methods\n * (`GET`). The call is bounded by `timeoutMs` (default `15_000`) via an\n * `AbortController`, also wired to `ctx.signal` for cooperative\n * cancellation.\n *\n * **Response shaping.** Headers are returned with lower-cased keys. The\n * body is read up to `maxBytes` (default `1_000_000`) and JSON-parsed\n * when the response `content-type` is JSON, otherwise returned as text;\n * `truncated` is `true` when the body was cut off at the cap (a truncated\n * JSON body is returned as the raw partial string, since it can no longer\n * be parsed).\n *\n * **Errors flow as data.** Every guardrail rejection and network failure\n * is thrown inside `execute`; the framework's `tool()` wrapper catches it\n * and surfaces it in the returned `{ error }` field, so the agent reads\n * the failure and self-corrects rather than crashing.\n *\n * @param options - Construction-time policy bounding the tool.\n * @returns A {@link ToolContract} the agent can call as `http_request`.\n *\n * @example\n * const stripe = httpRequestTool({\n * baseUrl: \"https://api.stripe.com\",\n * allowHosts: [\"api.stripe.com\"],\n * allowMethods: [\"GET\", \"POST\"],\n * headers: { authorization: `Bearer ${process.env.STRIPE_KEY}` },\n * });\n * const { data } = await stripe.invoke({ method: \"GET\", url: \"/v1/charges\" });\n */\nexport function httpRequestTool(\n options: HttpRequestOptions = {},\n): ToolContract<HttpRequestInput, HttpRequestResult> {\n const allowMethods = options.allowMethods ?? [\"GET\"];\n const allowedMethodSet = new Set<HttpMethod>(allowMethods);\n const allowHostSet = options.allowHosts ? new Set(options.allowHosts) : undefined;\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;\n const staticHeaders = options.headers;\n\n return tool<HttpRequestInput, HttpRequestResult>({\n name: options.name ?? DEFAULT_NAME,\n description:\n \"Issue an HTTP request and return the status, response headers, and \" +\n \"parsed body. Allowed methods and hosts are restricted by the tool's \" +\n \"configuration; a request outside those rails is rejected before any \" +\n \"network call. Pass an object body to send JSON, or a string to send \" +\n \"it verbatim. The response body is JSON-parsed when the content-type \" +\n \"is JSON, otherwise returned as text, and is capped — `truncated` is \" +\n \"true when the body was cut off.\",\n action: (input) => `Requesting ${input.method ?? \"GET\"} ${input.url}`,\n input: httpRequestInputSchema,\n async execute(input, ctx) {\n const method: HttpMethod = input.method ?? \"GET\";\n\n // 1. Method allowlist — rejected before anything else.\n if (!allowedMethodSet.has(method)) {\n throw new HttpPolicyError(\n `http_request method \"${method}\" is not allowed. ` +\n `Permitted methods: ${[...allowedMethodSet].join(\", \")}.`,\n { type: \"method-not-allowed\" },\n );\n }\n\n // 2. URL resolution (baseUrl join when configured).\n const url = resolveUrl(input.url, options.baseUrl);\n\n // 3. Host allowlist — SSRF guardrail, before the fetch.\n if (allowHostSet && !allowHostSet.has(url.hostname)) {\n throw new HttpPolicyError(\n `http_request host \"${url.hostname}\" is not in the allowlist. ` +\n `Permitted hosts: ${[...allowHostSet].join(\", \")}.`,\n { type: \"host-not-allowed\" },\n );\n }\n\n // 4. Merge headers — static option headers under the per-call ones,\n // so a per-call header overrides a static default of the same name.\n const headers: Record<string, string> = { ...staticHeaders, ...input.headers };\n\n // 5. Shape the body. Dropped for bodyless methods; objects become\n // JSON (with a default content-type); strings are sent verbatim.\n let body: string | undefined;\n\n if (!BODYLESS_METHODS.has(method) && input.body !== undefined) {\n if (typeof input.body === \"string\") {\n body = input.body;\n } else {\n body = JSON.stringify(input.body);\n\n const hasContentType = Object.keys(headers).some(\n (key) => key.toLowerCase() === \"content-type\",\n );\n\n if (!hasContentType) {\n headers[\"content-type\"] = \"application/json\";\n }\n }\n }\n\n // 6. Bound the call by timeout, chained to the caller's signal.\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n const onAbort = () => controller.abort();\n\n if (ctx?.signal) {\n if (ctx.signal.aborted) {\n controller.abort();\n } else {\n ctx.signal.addEventListener(\"abort\", onAbort, { once: true });\n }\n }\n\n let response: Response;\n\n try {\n response = await fetch(url, { method, headers, body, signal: controller.signal });\n } finally {\n clearTimeout(timer);\n ctx?.signal?.removeEventListener(\"abort\", onAbort);\n }\n\n // 7. Collect response headers with lower-cased keys.\n const responseHeaders: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n responseHeaders[key.toLowerCase()] = value;\n });\n\n // 8. Read the body up to the cap, then parse-or-pass.\n const { text, truncated } = await readCappedBody(response, maxBytes);\n\n let parsedBody: unknown = text;\n\n // A truncated body can no longer be valid JSON, so only attempt a\n // parse on a complete JSON response; otherwise hand back the raw text.\n if (!truncated && isJsonContentType(responseHeaders[\"content-type\"]) && text.length > 0) {\n try {\n parsedBody = JSON.parse(text);\n } catch {\n // Content-type claimed JSON but the body was not — fall back to\n // the raw text rather than failing the whole call.\n parsedBody = text;\n }\n }\n\n return {\n status: response.status,\n headers: responseHeaders,\n body: parsedBody,\n truncated,\n };\n },\n });\n}\n"],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { FetchUrlExtract, FetchUrlInput, FetchUrlOptions, FetchUrlResult, SearchProvider, WebSearchInput, WebSearchOptions, WebSearchResult, WebSearchResultItem } from "./contracts/web.type.mjs";
2
+ import { HttpMethod, HttpRequestInput, HttpRequestOptions, HttpRequestResult } from "./contracts/http.type.mjs";
3
+ import { CalculatorInput, CalculatorOptions, CalculatorResult, DateTimeInput, DateTimeOp, DateTimeOptions, DateTimeResult } from "./contracts/utility.type.mjs";
4
+ import { JsonRpcError, JsonRpcId, JsonRpcMessage, JsonRpcNotification, JsonRpcRequest, JsonRpcResponse, JsonRpcVersion, McpClient, McpClientOptions, McpContentBlock, McpServeOptions, McpServeSource, McpServeTransport, McpServer, McpToolCallResult, McpToolDescriptor, McpTransport } from "./contracts/mcp.type.mjs";
5
+ import { createServeHandler, serve } from "./mcp/serve.mjs";
6
+ import { jsonSchemaToStandard } from "./mcp/json-schema-to-standard.mjs";
7
+ import { McpTransportClient } from "./mcp/transport.type.mjs";
8
+ import { JsonRpcClientHandle, createJsonRpcClient, createTransport } from "./mcp/transport.mjs";
9
+ import { McpFactory, mcp } from "./mcp/index.mjs";
10
+ import { AiToolsNamespace } from "./register.mjs";
11
+ import { webSearchTool } from "./web/web-search.mjs";
12
+ import { fetchUrlTool } from "./web/fetch-url.mjs";
13
+ import { httpRequestTool } from "./http/http-request.mjs";
14
+ import { calculatorTool } from "./utility/calculator.mjs";
15
+ import { dateTimeTool } from "./utility/date-time.mjs";
16
+ import { CalculatorError, CalculatorErrorOptions, CalculatorFailure, DateTimeError, DateTimeErrorOptions, DateTimeFailure, HttpPolicyError, HttpPolicyErrorOptions, HttpPolicyFailure, McpTransportError, McpTransportErrorOptions, McpTransportFailure, WebToolError, WebToolErrorOptions, WebToolFailure } from "./errors.mjs";
17
+ export { type AiToolsNamespace, CalculatorError, type CalculatorErrorOptions, type CalculatorFailure, type CalculatorInput, type CalculatorOptions, type CalculatorResult, DateTimeError, type DateTimeErrorOptions, type DateTimeFailure, type DateTimeInput, type DateTimeOp, type DateTimeOptions, type DateTimeResult, type FetchUrlExtract, type FetchUrlInput, type FetchUrlOptions, type FetchUrlResult, type HttpMethod, HttpPolicyError, type HttpPolicyErrorOptions, type HttpPolicyFailure, type HttpRequestInput, type HttpRequestOptions, type HttpRequestResult, type JsonRpcClientHandle, type JsonRpcError, type JsonRpcId, type JsonRpcMessage, type JsonRpcNotification, type JsonRpcRequest, type JsonRpcResponse, type JsonRpcVersion, type McpClient, type McpClientOptions, type McpContentBlock, type McpFactory, type McpServeOptions, type McpServeSource, type McpServeTransport, type McpServer, type McpToolCallResult, type McpToolDescriptor, type McpTransport, type McpTransportClient, McpTransportError, type McpTransportErrorOptions, type McpTransportFailure, type SearchProvider, type WebSearchInput, type WebSearchOptions, type WebSearchResult, type WebSearchResultItem, WebToolError, type WebToolErrorOptions, type WebToolFailure, calculatorTool, createJsonRpcClient, createServeHandler, createTransport, dateTimeTool, fetchUrlTool, httpRequestTool, jsonSchemaToStandard, mcp, serve, webSearchTool };
18
+ import "./mcp/index.mjs";
19
+ import "./register.mjs";
package/esm/index.mjs ADDED
@@ -0,0 +1,15 @@
1
+ import { CalculatorError, DateTimeError, HttpPolicyError, McpTransportError, WebToolError } from "./errors.mjs";
2
+ import { httpRequestTool } from "./http/http-request.mjs";
3
+ import { jsonSchemaToStandard } from "./mcp/json-schema-to-standard.mjs";
4
+ import { createJsonRpcClient, createTransport } from "./mcp/transport.mjs";
5
+ import { createServeHandler, serve } from "./mcp/serve.mjs";
6
+ import { mcp } from "./mcp/index.mjs";
7
+ import { calculatorTool } from "./utility/calculator.mjs";
8
+ import { dateTimeTool } from "./utility/date-time.mjs";
9
+ import { fetchUrlTool } from "./web/fetch-url.mjs";
10
+ import { webSearchTool } from "./web/web-search.mjs";
11
+ import "./register.mjs";
12
+ import "./web/index.mjs";
13
+ import "./utility/index.mjs";
14
+
15
+ export { CalculatorError, DateTimeError, HttpPolicyError, McpTransportError, WebToolError, calculatorTool, createJsonRpcClient, createServeHandler, createTransport, dateTimeTool, fetchUrlTool, httpRequestTool, jsonSchemaToStandard, mcp, serve, webSearchTool };