@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":"register.mjs","names":[],"sources":["../../../../../../@warlock.js/ai-tools/src/register.ts"],"sourcesContent":["import { ai, type ToolContract } from \"@warlock.js/ai\";\nimport type {\n CalculatorInput,\n CalculatorOptions,\n CalculatorResult,\n DateTimeInput,\n DateTimeOptions,\n DateTimeResult,\n FetchUrlInput,\n FetchUrlOptions,\n FetchUrlResult,\n HttpRequestInput,\n HttpRequestOptions,\n HttpRequestResult,\n WebSearchInput,\n WebSearchOptions,\n WebSearchResult,\n} from \"./contracts\";\nimport { httpRequestTool } from \"./http/http-request\";\nimport { type McpFactory, mcp } from \"./mcp\";\nimport { calculatorTool } from \"./utility/calculator\";\nimport { dateTimeTool } from \"./utility/date-time\";\nimport { fetchUrlTool } from \"./web/fetch-url\";\nimport { webSearchTool } from \"./web/web-search\";\n\n/**\n * The `ai.tools.*` namespace — the five ready-made agent tools this\n * package vends. Each member is a factory returning a {@link ToolContract}\n * that drops straight into `ai.agent({ tools: [...] })`. Declared inline on\n * the `Ai` interface below (one block, one shape) rather than accreted\n * across the tool sub-barrels, mirroring the `ai.workspace` augmentation in\n * `@warlock.js/ai-workspace`.\n */\nexport interface AiToolsNamespace {\n /** Search the web via a chosen provider; returns ranked LLM-ready hits. */\n webSearch(options: WebSearchOptions): ToolContract<WebSearchInput, WebSearchResult>;\n /** Fetch a URL (host-allowlisted, byte-capped) and return its content. */\n fetchUrl(options?: FetchUrlOptions): ToolContract<FetchUrlInput, FetchUrlResult>;\n /** A guarded HTTP/REST client — method + host allowlists, byte/timeout caps. */\n http(options?: HttpRequestOptions): ToolContract<HttpRequestInput, HttpRequestResult>;\n /** A SAFE arithmetic-expression evaluator (no `eval`/`Function`). */\n calculator(options?: CalculatorOptions): ToolContract<CalculatorInput, CalculatorResult>;\n /** Clock/calendar ops — now / add / diff / format over ISO instants. */\n dateTime(options?: DateTimeOptions): ToolContract<DateTimeInput, DateTimeResult>;\n}\n\n/**\n * Attach the `tools` namespace and the `mcp` factory to the `ai` namespace\n * via module augmentation, per the `ai.`-namespace convention.\n * `@warlock.js/ai` now exposes a named `Ai` interface for exactly this, so\n * after a bare `import \"@warlock.js/ai-tools\"`, `ai.tools.webSearch(...)`,\n * `ai.tools.http(...)`, `ai.mcp(server)`, and `ai.mcp.serve(source)` are all\n * globally typed — no view/cast needed.\n */\ndeclare module \"@warlock.js/ai\" {\n interface Ai {\n /**\n * Ready-made agent tools from `@warlock.js/ai-tools` — web search,\n * fetch/scrape, HTTP/REST, calculator, and date-time. Each returns a\n * `ToolContract` that slots into `ai.agent({ tools: [...] })`.\n */\n tools: AiToolsNamespace;\n /**\n * The Model Context Protocol surface — `ai.mcp(server)` connects to an\n * external MCP server and adapts its tools as agent tools (Direction A),\n * while `ai.mcp.serve(source, options)` exposes a local primitive AS an\n * MCP server other clients can consume (Direction B).\n */\n mcp: McpFactory;\n }\n}\n\n// Runtime registration: attach `tools` and `mcp` onto the shared `ai`\n// object the moment this module is imported (the augmentation above types\n// them). The `dateTime` factory's optional injectable-clock `deps` is a\n// test/advanced seam, omitted from the agent-facing signature — call\n// `dateTimeTool` directly to use it.\nai.tools = {\n webSearch: webSearchTool,\n fetchUrl: fetchUrlTool,\n http: httpRequestTool,\n calculator: calculatorTool,\n dateTime: (options?: DateTimeOptions) => dateTimeTool(options),\n};\n\nai.mcp = mcp;\n"],"mappings":";;;;;;;;;AA6EA,GAAG,QAAQ;CACT,WAAW;CACX,UAAU;CACV,MAAM;CACN,YAAY;CACZ,WAAW,YAA8B,aAAa,OAAO;AAC/D;AAEA,GAAG,MAAM"}
package/esm/schema.mjs ADDED
@@ -0,0 +1,127 @@
1
+ //#region ../@warlock.js/ai-tools/src/schema.ts
2
+ /**
3
+ * Tiny, dependency-free [Standard Schema](https://standardschema.dev)
4
+ * builders for the tools' input validation. The package pins only
5
+ * `@warlock.js/ai` as a required runtime peer and keeps every heavy
6
+ * dependency optional, so rather than pull in a schema library we
7
+ * hand-roll the few shapes the tools need — exactly the pattern
8
+ * `@warlock.js/ai`'s own `tool()` tests use. Each builder returns a
9
+ * `StandardSchemaV1`, which is what `tool({ input })` validates against
10
+ * before calling `execute`.
11
+ *
12
+ * These intentionally cover only the primitive cases the bundled tools
13
+ * require (`string`, an optional string drawn from a fixed set, an
14
+ * optional string-to-string record, and an opaque passthrough value),
15
+ * plus an `object` of fields. They are not a general-purpose validator.
16
+ */
17
+ /** The vendor tag stamped on every issue these builders produce. */
18
+ const VENDOR = "ai-tools";
19
+ /** Required string field — rejects anything that is not a string. */
20
+ function stringField() {
21
+ return (value, key) => {
22
+ if (typeof value === "string") return { value };
23
+ return { issues: [{
24
+ message: `"${key}" must be a string`,
25
+ path: [key]
26
+ }] };
27
+ };
28
+ }
29
+ /**
30
+ * Optional string field constrained to a fixed set of allowed values.
31
+ * Accepts `undefined` (the property absent or explicitly undefined) or
32
+ * one of `allowed`; rejects any other string and every non-string type.
33
+ * Used for `method` so an out-of-vocabulary verb is a clean validation
34
+ * error rather than reaching the handler.
35
+ *
36
+ * @param allowed - The exhaustive set of permitted string literals.
37
+ */
38
+ function optionalStringEnumField(allowed) {
39
+ return (value, key) => {
40
+ if (value === void 0) return { value: void 0 };
41
+ if (typeof value === "string" && allowed.includes(value)) return { value };
42
+ return { issues: [{
43
+ message: `"${key}" must be one of: ${allowed.join(", ")}`,
44
+ path: [key]
45
+ }] };
46
+ };
47
+ }
48
+ /**
49
+ * Optional string-to-string record field — accepts `undefined` or a flat
50
+ * object whose every value is a string, and rejects arrays, non-objects,
51
+ * and any record with a non-string value. Used for per-call `headers`.
52
+ */
53
+ function optionalStringRecordField() {
54
+ return (value, key) => {
55
+ if (value === void 0) return { value: void 0 };
56
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return { issues: [{
57
+ message: `"${key}" must be an object of string values when provided`,
58
+ path: [key]
59
+ }] };
60
+ const source = value;
61
+ const result = {};
62
+ for (const recordKey of Object.keys(source)) {
63
+ const entry = source[recordKey];
64
+ if (typeof entry !== "string") return { issues: [{
65
+ message: `"${key}.${recordKey}" must be a string`,
66
+ path: [key, recordKey]
67
+ }] };
68
+ result[recordKey] = entry;
69
+ }
70
+ return { value: result };
71
+ };
72
+ }
73
+ /**
74
+ * Opaque passthrough field — accepts any value (including `undefined`)
75
+ * verbatim, performing no validation. Used for a request `body` whose
76
+ * shape the tool deliberately does not constrain: an object is later
77
+ * JSON-serialized, a string is sent as-is.
78
+ */
79
+ function passthroughField() {
80
+ return (value) => ({ value });
81
+ }
82
+ /**
83
+ * Build a {@link StandardSchemaV1} for a flat object whose every property
84
+ * is validated by a {@link FieldValidator}. The input must be a non-null
85
+ * object; each declared field is validated and the (possibly coerced)
86
+ * values are collected into the typed result. All field issues are merged
87
+ * so the caller sees every problem at once.
88
+ *
89
+ * `T` is constrained to `object` rather than `Record<string, unknown>` so
90
+ * the tool IO `interface`s (which carry no implicit string index
91
+ * signature) satisfy it directly — only the declared keys in `shape` are
92
+ * ever read, so a string index signature is never required.
93
+ *
94
+ * @example
95
+ * const schema = objectSchema<{ url: string; method?: HttpMethod }>({
96
+ * url: stringField(),
97
+ * method: optionalStringEnumField(["GET", "POST"]),
98
+ * });
99
+ */
100
+ function objectSchema(shape) {
101
+ return { "~standard": {
102
+ version: 1,
103
+ vendor: VENDOR,
104
+ validate(input) {
105
+ if (typeof input !== "object" || input === null || Array.isArray(input)) return { issues: [{ message: "input must be an object" }] };
106
+ const source = input;
107
+ const issues = [];
108
+ const result = {};
109
+ for (const key of Object.keys(shape)) {
110
+ const field = shape[key];
111
+ const present = key in source;
112
+ const outcome = field(source[key], key);
113
+ if ("issues" in outcome) {
114
+ issues.push(...outcome.issues);
115
+ continue;
116
+ }
117
+ if (outcome.value !== void 0 || present) result[key] = outcome.value;
118
+ }
119
+ if (issues.length > 0) return { issues };
120
+ return { value: result };
121
+ }
122
+ } };
123
+ }
124
+
125
+ //#endregion
126
+ export { objectSchema, optionalStringEnumField, optionalStringRecordField, passthroughField, stringField };
127
+ //# sourceMappingURL=schema.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.mjs","names":[],"sources":["../../../../../../@warlock.js/ai-tools/src/schema.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\n/**\n * Tiny, dependency-free [Standard Schema](https://standardschema.dev)\n * builders for the tools' input validation. The package pins only\n * `@warlock.js/ai` as a required runtime peer and keeps every heavy\n * dependency optional, so rather than pull in a schema library we\n * hand-roll the few shapes the tools need — exactly the pattern\n * `@warlock.js/ai`'s own `tool()` tests use. Each builder returns a\n * `StandardSchemaV1`, which is what `tool({ input })` validates against\n * before calling `execute`.\n *\n * These intentionally cover only the primitive cases the bundled tools\n * require (`string`, an optional string drawn from a fixed set, an\n * optional string-to-string record, and an opaque passthrough value),\n * plus an `object` of fields. They are not a general-purpose validator.\n */\n\n/** The vendor tag stamped on every issue these builders produce. */\nconst VENDOR = \"ai-tools\";\n\n/**\n * A single field validator inside {@link objectSchema}: given a value,\n * return either the coerced value or a list of issues. Field validators\n * receive the raw property and the property name (for issue messages).\n */\nexport type FieldValidator<T> = (\n value: unknown,\n key: string,\n) => { value: T } | { issues: StandardSchemaV1.Issue[] };\n\n/** Required string field — rejects anything that is not a string. */\nexport function stringField(): FieldValidator<string> {\n return (value, key) => {\n if (typeof value === \"string\") {\n return { value };\n }\n\n return { issues: [{ message: `\"${key}\" must be a string`, path: [key] }] };\n };\n}\n\n/**\n * Optional string field constrained to a fixed set of allowed values.\n * Accepts `undefined` (the property absent or explicitly undefined) or\n * one of `allowed`; rejects any other string and every non-string type.\n * Used for `method` so an out-of-vocabulary verb is a clean validation\n * error rather than reaching the handler.\n *\n * @param allowed - The exhaustive set of permitted string literals.\n */\nexport function optionalStringEnumField<T extends string>(\n allowed: readonly T[],\n): FieldValidator<T | undefined> {\n return (value, key) => {\n if (value === undefined) {\n return { value: undefined };\n }\n\n if (typeof value === \"string\" && (allowed as readonly string[]).includes(value)) {\n return { value: value as T };\n }\n\n return {\n issues: [\n {\n message: `\"${key}\" must be one of: ${allowed.join(\", \")}`,\n path: [key],\n },\n ],\n };\n };\n}\n\n/**\n * Optional string-to-string record field — accepts `undefined` or a flat\n * object whose every value is a string, and rejects arrays, non-objects,\n * and any record with a non-string value. Used for per-call `headers`.\n */\nexport function optionalStringRecordField(): FieldValidator<\n Record<string, string> | undefined\n> {\n return (value, key) => {\n if (value === undefined) {\n return { value: undefined };\n }\n\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return {\n issues: [\n { message: `\"${key}\" must be an object of string values when provided`, path: [key] },\n ],\n };\n }\n\n const source = value as Record<string, unknown>;\n const result: Record<string, string> = {};\n\n for (const recordKey of Object.keys(source)) {\n const entry = source[recordKey];\n\n if (typeof entry !== \"string\") {\n return {\n issues: [\n {\n message: `\"${key}.${recordKey}\" must be a string`,\n path: [key, recordKey],\n },\n ],\n };\n }\n\n result[recordKey] = entry;\n }\n\n return { value: result };\n };\n}\n\n/**\n * Opaque passthrough field — accepts any value (including `undefined`)\n * verbatim, performing no validation. Used for a request `body` whose\n * shape the tool deliberately does not constrain: an object is later\n * JSON-serialized, a string is sent as-is.\n */\nexport function passthroughField(): FieldValidator<unknown> {\n return (value) => ({ value });\n}\n\n/** The per-key field validator map describing an object schema's shape. */\ntype ObjectShape<T> = {\n [K in keyof T]-?: FieldValidator<T[K]>;\n};\n\n/**\n * Build a {@link StandardSchemaV1} for a flat object whose every property\n * is validated by a {@link FieldValidator}. The input must be a non-null\n * object; each declared field is validated and the (possibly coerced)\n * values are collected into the typed result. All field issues are merged\n * so the caller sees every problem at once.\n *\n * `T` is constrained to `object` rather than `Record<string, unknown>` so\n * the tool IO `interface`s (which carry no implicit string index\n * signature) satisfy it directly — only the declared keys in `shape` are\n * ever read, so a string index signature is never required.\n *\n * @example\n * const schema = objectSchema<{ url: string; method?: HttpMethod }>({\n * url: stringField(),\n * method: optionalStringEnumField([\"GET\", \"POST\"]),\n * });\n */\nexport function objectSchema<T extends object>(\n shape: ObjectShape<T>,\n): StandardSchemaV1<T> {\n return {\n \"~standard\": {\n version: 1,\n vendor: VENDOR,\n validate(input) {\n if (typeof input !== \"object\" || input === null || Array.isArray(input)) {\n return { issues: [{ message: \"input must be an object\" }] };\n }\n\n const source = input as Record<string, unknown>;\n const issues: StandardSchemaV1.Issue[] = [];\n const result: Record<string, unknown> = {};\n\n for (const key of Object.keys(shape) as (keyof T)[]) {\n const field = shape[key];\n const present = (key as string) in source;\n const outcome = field(source[key as string], key as string);\n\n if (\"issues\" in outcome) {\n issues.push(...outcome.issues);\n\n continue;\n }\n\n // Carry through any key that resolved to a defined value. A\n // passthrough field can legitimately resolve to `undefined`\n // even when the property was present (e.g. an explicit\n // `body: undefined`); only suppress keys that were absent so\n // optional-absent fields stay absent.\n if (outcome.value !== undefined || present) {\n result[key as string] = outcome.value;\n }\n }\n\n if (issues.length > 0) {\n return { issues };\n }\n\n return { value: result as T };\n },\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAmBA,MAAM,SAAS;;AAaf,SAAgB,cAAsC;CACpD,QAAQ,OAAO,QAAQ;EACrB,IAAI,OAAO,UAAU,UACnB,OAAO,EAAE,MAAM;EAGjB,OAAO,EAAE,QAAQ,CAAC;GAAE,SAAS,IAAI,IAAI;GAAqB,MAAM,CAAC,GAAG;EAAE,CAAC,EAAE;CAC3E;AACF;;;;;;;;;;AAWA,SAAgB,wBACd,SAC+B;CAC/B,QAAQ,OAAO,QAAQ;EACrB,IAAI,UAAU,QACZ,OAAO,EAAE,OAAO,OAAU;EAG5B,IAAI,OAAO,UAAU,YAAa,QAA8B,SAAS,KAAK,GAC5E,OAAO,EAAS,MAAW;EAG7B,OAAO,EACL,QAAQ,CACN;GACE,SAAS,IAAI,IAAI,oBAAoB,QAAQ,KAAK,IAAI;GACtD,MAAM,CAAC,GAAG;EACZ,CACF,EACF;CACF;AACF;;;;;;AAOA,SAAgB,4BAEd;CACA,QAAQ,OAAO,QAAQ;EACrB,IAAI,UAAU,QACZ,OAAO,EAAE,OAAO,OAAU;EAG5B,IAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,KAAK,GACpE,OAAO,EACL,QAAQ,CACN;GAAE,SAAS,IAAI,IAAI;GAAqD,MAAM,CAAC,GAAG;EAAE,CACtF,EACF;EAGF,MAAM,SAAS;EACf,MAAM,SAAiC,CAAC;EAExC,KAAK,MAAM,aAAa,OAAO,KAAK,MAAM,GAAG;GAC3C,MAAM,QAAQ,OAAO;GAErB,IAAI,OAAO,UAAU,UACnB,OAAO,EACL,QAAQ,CACN;IACE,SAAS,IAAI,IAAI,GAAG,UAAU;IAC9B,MAAM,CAAC,KAAK,SAAS;GACvB,CACF,EACF;GAGF,OAAO,aAAa;EACtB;EAEA,OAAO,EAAE,OAAO,OAAO;CACzB;AACF;;;;;;;AAQA,SAAgB,mBAA4C;CAC1D,QAAQ,WAAW,EAAE,MAAM;AAC7B;;;;;;;;;;;;;;;;;;;AAyBA,SAAgB,aACd,OACqB;CACrB,OAAO,EACL,aAAa;EACX,SAAS;EACT,QAAQ;EACR,SAAS,OAAO;GACd,IAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,KAAK,GACpE,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,0BAA0B,CAAC,EAAE;GAG5D,MAAM,SAAS;GACf,MAAM,SAAmC,CAAC;GAC1C,MAAM,SAAkC,CAAC;GAEzC,KAAK,MAAM,OAAO,OAAO,KAAK,KAAK,GAAkB;IACnD,MAAM,QAAQ,MAAM;IACpB,MAAM,UAAW,OAAkB;IACnC,MAAM,UAAU,MAAM,OAAO,MAAgB,GAAa;IAE1D,IAAI,YAAY,SAAS;KACvB,OAAO,KAAK,GAAG,QAAQ,MAAM;KAE7B;IACF;IAOA,IAAI,QAAQ,UAAU,UAAa,SACjC,OAAO,OAAiB,QAAQ;GAEpC;GAEA,IAAI,OAAO,SAAS,GAClB,OAAO,EAAE,OAAO;GAGlB,OAAO,EAAE,OAAO,OAAY;EAC9B;CACF,EACF;AACF"}
@@ -0,0 +1,35 @@
1
+ import { CalculatorInput, CalculatorOptions, CalculatorResult } from "../contracts/utility.type.mjs";
2
+ import { ToolContract } from "@warlock.js/ai";
3
+
4
+ //#region ../@warlock.js/ai-tools/src/utility/calculator.d.ts
5
+ /**
6
+ * Build the `calculator` tool — a SAFE arithmetic evaluator the agent can
7
+ * call to compute a numeric expression. It supports `+ - * / % ^`, unary
8
+ * signs, parentheses, and decimal/scientific-notation literals, with the
9
+ * usual precedence (`^` highest and right-associative, then `* / %`, then
10
+ * `+ -`).
11
+ *
12
+ * **Safety.** The expression is tokenized and evaluated with a
13
+ * shunting-yard pass — it NEVER calls `eval` or `new Function`. The
14
+ * lexer only recognizes numbers, parentheses, and the fixed operator set,
15
+ * so there is no path to an identifier, function call, or property
16
+ * access; any other character is a syntax error returned as data.
17
+ *
18
+ * **Errors flow as data.** A malformed expression, division/modulo by
19
+ * zero, or a non-finite result throws a {@link CalculatorError} inside
20
+ * the handler; `tool()` catches it and surfaces it in the returned
21
+ * `{ error }` field (the LLM-visible message is preserved), so the agent
22
+ * reads the failure and self-corrects instead of crashing.
23
+ *
24
+ * @param options - Optional overrides; `name` renames the LLM-visible tool.
25
+ * @returns A `ToolContract<{ expression }, { result }>` ready to drop into `tools: []`.
26
+ *
27
+ * @example
28
+ * const calc = calculatorTool();
29
+ * const { data } = await calc.invoke({ expression: "(3 + 4) * 2" });
30
+ * console.log(data?.result); // 14
31
+ */
32
+ declare function calculatorTool(options?: CalculatorOptions): ToolContract<CalculatorInput, CalculatorResult>;
33
+ //#endregion
34
+ export { calculatorTool };
35
+ //# sourceMappingURL=calculator.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calculator.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai-tools/src/utility/calculator.ts"],"mappings":";;;;;;AAiZA;;;;;;;;;;;;;;;AAEiD;;;;;;;;;;iBAFjC,cAAA,CACd,OAAA,GAAU,iBAAA,GACT,YAAA,CAAa,eAAA,EAAiB,gBAAA"}
@@ -0,0 +1,272 @@
1
+ import { CalculatorError } from "../errors.mjs";
2
+ import { objectSchema, stringField } from "./schema.mjs";
3
+ import { tool } from "@warlock.js/ai";
4
+
5
+ //#region ../@warlock.js/ai-tools/src/utility/calculator.ts
6
+ /** Default tool name exposed to the LLM. */
7
+ const DEFAULT_NAME = "calculator";
8
+ /** Input schema for the `calculator` tool: a single `expression` string. */
9
+ const inputSchema = objectSchema({ expression: stringField() });
10
+ /** The closed set of supported binary operators. */
11
+ const OPERATORS = {
12
+ "+": {
13
+ precedence: 1,
14
+ associativity: "left",
15
+ apply: (a, b) => a + b
16
+ },
17
+ "-": {
18
+ precedence: 1,
19
+ associativity: "left",
20
+ apply: (a, b) => a - b
21
+ },
22
+ "*": {
23
+ precedence: 2,
24
+ associativity: "left",
25
+ apply: (a, b) => a * b
26
+ },
27
+ "/": {
28
+ precedence: 2,
29
+ associativity: "left",
30
+ apply: (a, b) => {
31
+ if (b === 0) throw new CalculatorError("Division by zero.", { type: "divide-by-zero" });
32
+ return a / b;
33
+ }
34
+ },
35
+ "%": {
36
+ precedence: 2,
37
+ associativity: "left",
38
+ apply: (a, b) => {
39
+ if (b === 0) throw new CalculatorError("Modulo by zero.", { type: "divide-by-zero" });
40
+ return a % b;
41
+ }
42
+ },
43
+ "^": {
44
+ precedence: 3,
45
+ associativity: "right",
46
+ apply: (a, b) => a ** b
47
+ }
48
+ };
49
+ /**
50
+ * Tokenize an arithmetic expression into {@link Token}s. Recognizes
51
+ * decimal and scientific-notation numbers (`3`, `4.5`, `1e3`, `2.5E-2`),
52
+ * the operators in {@link OPERATORS}, and parentheses; whitespace is
53
+ * skipped. Any other character is a syntax error — there is no path to
54
+ * an identifier, function call, or property access, so nothing
55
+ * code-like can be smuggled in.
56
+ *
57
+ * @throws CalculatorError `type: "syntax"` on an unrecognized character
58
+ * or a malformed number.
59
+ */
60
+ function tokenize(expression) {
61
+ const tokens = [];
62
+ let index = 0;
63
+ while (index < expression.length) {
64
+ const char = expression[index];
65
+ if (char === " " || char === " " || char === "\n" || char === "\r") {
66
+ index += 1;
67
+ continue;
68
+ }
69
+ if (char === "(" || char === ")") {
70
+ tokens.push({
71
+ type: "paren",
72
+ value: char
73
+ });
74
+ index += 1;
75
+ continue;
76
+ }
77
+ if (char in OPERATORS) {
78
+ tokens.push({
79
+ type: "operator",
80
+ value: char
81
+ });
82
+ index += 1;
83
+ continue;
84
+ }
85
+ if (isDigit(char) || char === ".") {
86
+ const { value, nextIndex } = readNumber(expression, index);
87
+ tokens.push({
88
+ type: "number",
89
+ value
90
+ });
91
+ index = nextIndex;
92
+ continue;
93
+ }
94
+ throw new CalculatorError(`Unexpected character "${char}" at position ${index}. Only numbers, parentheses, and the operators + - * / % ^ are allowed.`, { type: "syntax" });
95
+ }
96
+ return tokens;
97
+ }
98
+ /** True for an ASCII digit `0`–`9`. */
99
+ function isDigit(char) {
100
+ return char >= "0" && char <= "9";
101
+ }
102
+ /**
103
+ * Read a single number literal starting at `start`. Consumes an optional
104
+ * integer part, optional fraction, and optional exponent
105
+ * (`e`/`E` with an optional sign). Returns the parsed value and the index
106
+ * just past the literal.
107
+ *
108
+ * @throws CalculatorError `type: "syntax"` if the consumed run is not a
109
+ * valid finite number (e.g. a lone `.` or `1e` with no exponent).
110
+ */
111
+ function readNumber(expression, start) {
112
+ let index = start;
113
+ while (index < expression.length && isDigit(expression[index])) index += 1;
114
+ if (expression[index] === ".") {
115
+ index += 1;
116
+ while (index < expression.length && isDigit(expression[index])) index += 1;
117
+ }
118
+ if (expression[index] === "e" || expression[index] === "E") {
119
+ index += 1;
120
+ if (expression[index] === "+" || expression[index] === "-") index += 1;
121
+ while (index < expression.length && isDigit(expression[index])) index += 1;
122
+ }
123
+ const literal = expression.slice(start, index);
124
+ const value = Number(literal);
125
+ if (!Number.isFinite(value)) throw new CalculatorError(`Invalid number literal "${literal}".`, { type: "syntax" });
126
+ return {
127
+ value,
128
+ nextIndex: index
129
+ };
130
+ }
131
+ /**
132
+ * Evaluate a token stream with a single left-to-right pass that resolves
133
+ * unary signs, then a shunting-yard conversion that interleaves operator
134
+ * application — so the result is produced without ever building an AST or
135
+ * calling `eval`/`Function`.
136
+ *
137
+ * Unary `+`/`-` are detected positionally: a `+`/`-` is unary when it
138
+ * starts the expression or directly follows another operator or an
139
+ * opening paren. A unary `-` folds into the following number literal
140
+ * (and a unary `+` is a no-op), which keeps the operator stack purely
141
+ * binary.
142
+ *
143
+ * @throws CalculatorError on malformed structure (`type: "syntax"`),
144
+ * division/modulo by zero (`type: "divide-by-zero"`), or a non-finite
145
+ * result (`type: "overflow"`).
146
+ */
147
+ function evaluate(tokens) {
148
+ const values = [];
149
+ const operators = [];
150
+ /** Pop the top operator and apply it to the top two values. */
151
+ const applyTop = () => {
152
+ const operator = operators.pop();
153
+ if (operator === void 0) throw new CalculatorError("Malformed expression.", { type: "syntax" });
154
+ const right = values.pop();
155
+ const left = values.pop();
156
+ if (left === void 0 || right === void 0) throw new CalculatorError(`Operator "${operator}" is missing an operand.`, { type: "syntax" });
157
+ values.push(OPERATORS[operator].apply(left, right));
158
+ };
159
+ let expectOperand = true;
160
+ let pendingSign = 1;
161
+ for (let i = 0; i < tokens.length; i += 1) {
162
+ const token = tokens[i];
163
+ if (token.type === "number") {
164
+ if (!expectOperand) throw new CalculatorError("Unexpected number — a number cannot directly follow another value.", { type: "syntax" });
165
+ values.push(pendingSign * token.value);
166
+ pendingSign = 1;
167
+ expectOperand = false;
168
+ continue;
169
+ }
170
+ if (token.type === "paren") {
171
+ if (token.value === "(") {
172
+ if (!expectOperand) throw new CalculatorError("Unexpected \"(\" — it cannot directly follow a value.", { type: "syntax" });
173
+ if (pendingSign === -1) {
174
+ values.push(-1);
175
+ operators.push("*");
176
+ pendingSign = 1;
177
+ }
178
+ operators.push("(");
179
+ expectOperand = true;
180
+ continue;
181
+ }
182
+ if (expectOperand) throw new CalculatorError("Unexpected \")\" — an operand was expected.", { type: "syntax" });
183
+ let foundOpen = false;
184
+ while (operators.length > 0) {
185
+ if (operators[operators.length - 1] === "(") {
186
+ operators.pop();
187
+ foundOpen = true;
188
+ break;
189
+ }
190
+ applyTop();
191
+ }
192
+ if (!foundOpen) throw new CalculatorError("Unbalanced parentheses.", { type: "syntax" });
193
+ expectOperand = false;
194
+ continue;
195
+ }
196
+ if (expectOperand) {
197
+ if (token.value === "-") {
198
+ pendingSign = -pendingSign;
199
+ continue;
200
+ }
201
+ if (token.value === "+") continue;
202
+ throw new CalculatorError(`Operator "${token.value}" has no left-hand operand.`, { type: "syntax" });
203
+ }
204
+ const incoming = OPERATORS[token.value];
205
+ while (operators.length > 0) {
206
+ const top = operators[operators.length - 1];
207
+ if (top === "(") break;
208
+ const topSpec = OPERATORS[top];
209
+ const higher = topSpec.precedence > incoming.precedence;
210
+ const equalLeft = topSpec.precedence === incoming.precedence && incoming.associativity === "left";
211
+ if (higher || equalLeft) {
212
+ applyTop();
213
+ continue;
214
+ }
215
+ break;
216
+ }
217
+ operators.push(token.value);
218
+ expectOperand = true;
219
+ }
220
+ if (expectOperand) throw new CalculatorError("Expression ends with an operator or is empty.", { type: "syntax" });
221
+ while (operators.length > 0) {
222
+ if (operators[operators.length - 1] === "(") throw new CalculatorError("Unbalanced parentheses.", { type: "syntax" });
223
+ applyTop();
224
+ }
225
+ const result = values.pop();
226
+ if (result === void 0 || values.length > 0) throw new CalculatorError("Malformed expression.", { type: "syntax" });
227
+ if (!Number.isFinite(result)) throw new CalculatorError("Result is not a finite number.", { type: "overflow" });
228
+ return result;
229
+ }
230
+ /**
231
+ * Build the `calculator` tool — a SAFE arithmetic evaluator the agent can
232
+ * call to compute a numeric expression. It supports `+ - * / % ^`, unary
233
+ * signs, parentheses, and decimal/scientific-notation literals, with the
234
+ * usual precedence (`^` highest and right-associative, then `* / %`, then
235
+ * `+ -`).
236
+ *
237
+ * **Safety.** The expression is tokenized and evaluated with a
238
+ * shunting-yard pass — it NEVER calls `eval` or `new Function`. The
239
+ * lexer only recognizes numbers, parentheses, and the fixed operator set,
240
+ * so there is no path to an identifier, function call, or property
241
+ * access; any other character is a syntax error returned as data.
242
+ *
243
+ * **Errors flow as data.** A malformed expression, division/modulo by
244
+ * zero, or a non-finite result throws a {@link CalculatorError} inside
245
+ * the handler; `tool()` catches it and surfaces it in the returned
246
+ * `{ error }` field (the LLM-visible message is preserved), so the agent
247
+ * reads the failure and self-corrects instead of crashing.
248
+ *
249
+ * @param options - Optional overrides; `name` renames the LLM-visible tool.
250
+ * @returns A `ToolContract<{ expression }, { result }>` ready to drop into `tools: []`.
251
+ *
252
+ * @example
253
+ * const calc = calculatorTool();
254
+ * const { data } = await calc.invoke({ expression: "(3 + 4) * 2" });
255
+ * console.log(data?.result); // 14
256
+ */
257
+ function calculatorTool(options) {
258
+ return tool({
259
+ name: options?.name ?? DEFAULT_NAME,
260
+ description: "Evaluate an arithmetic expression and return the numeric result. Supports + - * / % ^, parentheses, unary +/-, and decimal or scientific-notation numbers. Does not support variables or functions — pass a literal expression like \"(3 + 4) * 2\".",
261
+ input: inputSchema,
262
+ async execute(input) {
263
+ const tokens = tokenize(input.expression);
264
+ if (tokens.length === 0) throw new CalculatorError("Expression is empty.", { type: "syntax" });
265
+ return { result: evaluate(tokens) };
266
+ }
267
+ });
268
+ }
269
+
270
+ //#endregion
271
+ export { calculatorTool };
272
+ //# sourceMappingURL=calculator.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calculator.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai-tools/src/utility/calculator.ts"],"sourcesContent":["import { tool, type ToolContract } from \"@warlock.js/ai\";\nimport { CalculatorError } from \"../errors\";\nimport { objectSchema, stringField } from \"./schema\";\nimport type {\n CalculatorInput,\n CalculatorOptions,\n CalculatorResult,\n} from \"../contracts/utility.type\";\n\n/** Default tool name exposed to the LLM. */\nconst DEFAULT_NAME = \"calculator\";\n\n/** Input schema for the `calculator` tool: a single `expression` string. */\nconst inputSchema = objectSchema<CalculatorInput>({\n expression: stringField(),\n});\n\n/**\n * A binary operator the evaluator understands, with its precedence and\n * associativity. Higher `precedence` binds tighter; `^` is the only\n * right-associative operator (so `2 ^ 3 ^ 2` is `2 ^ (3 ^ 2)`).\n */\ninterface OperatorSpec {\n precedence: number;\n associativity: \"left\" | \"right\";\n /** Apply the operator to its two operands. May throw {@link CalculatorError}. */\n apply(left: number, right: number): number;\n}\n\n/** The closed set of supported binary operators. */\nconst OPERATORS: Record<string, OperatorSpec> = {\n \"+\": { precedence: 1, associativity: \"left\", apply: (a, b) => a + b },\n \"-\": { precedence: 1, associativity: \"left\", apply: (a, b) => a - b },\n \"*\": { precedence: 2, associativity: \"left\", apply: (a, b) => a * b },\n \"/\": {\n precedence: 2,\n associativity: \"left\",\n apply: (a, b) => {\n if (b === 0) {\n throw new CalculatorError(\"Division by zero.\", { type: \"divide-by-zero\" });\n }\n\n return a / b;\n },\n },\n \"%\": {\n precedence: 2,\n associativity: \"left\",\n apply: (a, b) => {\n if (b === 0) {\n throw new CalculatorError(\"Modulo by zero.\", { type: \"divide-by-zero\" });\n }\n\n return a % b;\n },\n },\n \"^\": { precedence: 3, associativity: \"right\", apply: (a, b) => a ** b },\n};\n\n/** A lexed token: a number literal, an operator, or a parenthesis. */\ntype Token =\n | { type: \"number\"; value: number }\n | { type: \"operator\"; value: string }\n | { type: \"paren\"; value: \"(\" | \")\" };\n\n/**\n * Tokenize an arithmetic expression into {@link Token}s. Recognizes\n * decimal and scientific-notation numbers (`3`, `4.5`, `1e3`, `2.5E-2`),\n * the operators in {@link OPERATORS}, and parentheses; whitespace is\n * skipped. Any other character is a syntax error — there is no path to\n * an identifier, function call, or property access, so nothing\n * code-like can be smuggled in.\n *\n * @throws CalculatorError `type: \"syntax\"` on an unrecognized character\n * or a malformed number.\n */\nfunction tokenize(expression: string): Token[] {\n const tokens: Token[] = [];\n let index = 0;\n\n while (index < expression.length) {\n const char = expression[index];\n\n if (char === \" \" || char === \"\\t\" || char === \"\\n\" || char === \"\\r\") {\n index += 1;\n\n continue;\n }\n\n if (char === \"(\" || char === \")\") {\n tokens.push({ type: \"paren\", value: char });\n index += 1;\n\n continue;\n }\n\n if (char in OPERATORS) {\n tokens.push({ type: \"operator\", value: char });\n index += 1;\n\n continue;\n }\n\n if (isDigit(char) || char === \".\") {\n const { value, nextIndex } = readNumber(expression, index);\n tokens.push({ type: \"number\", value });\n index = nextIndex;\n\n continue;\n }\n\n throw new CalculatorError(\n `Unexpected character \"${char}\" at position ${index}. Only numbers, parentheses, and the operators + - * / % ^ are allowed.`,\n { type: \"syntax\" },\n );\n }\n\n return tokens;\n}\n\n/** True for an ASCII digit `0`–`9`. */\nfunction isDigit(char: string): boolean {\n return char >= \"0\" && char <= \"9\";\n}\n\n/**\n * Read a single number literal starting at `start`. Consumes an optional\n * integer part, optional fraction, and optional exponent\n * (`e`/`E` with an optional sign). Returns the parsed value and the index\n * just past the literal.\n *\n * @throws CalculatorError `type: \"syntax\"` if the consumed run is not a\n * valid finite number (e.g. a lone `.` or `1e` with no exponent).\n */\nfunction readNumber(\n expression: string,\n start: number,\n): { value: number; nextIndex: number } {\n let index = start;\n\n while (index < expression.length && isDigit(expression[index])) {\n index += 1;\n }\n\n if (expression[index] === \".\") {\n index += 1;\n\n while (index < expression.length && isDigit(expression[index])) {\n index += 1;\n }\n }\n\n if (expression[index] === \"e\" || expression[index] === \"E\") {\n index += 1;\n\n if (expression[index] === \"+\" || expression[index] === \"-\") {\n index += 1;\n }\n\n while (index < expression.length && isDigit(expression[index])) {\n index += 1;\n }\n }\n\n const literal = expression.slice(start, index);\n const value = Number(literal);\n\n if (!Number.isFinite(value)) {\n throw new CalculatorError(`Invalid number literal \"${literal}\".`, {\n type: \"syntax\",\n });\n }\n\n return { value, nextIndex: index };\n}\n\n/**\n * Evaluate a token stream with a single left-to-right pass that resolves\n * unary signs, then a shunting-yard conversion that interleaves operator\n * application — so the result is produced without ever building an AST or\n * calling `eval`/`Function`.\n *\n * Unary `+`/`-` are detected positionally: a `+`/`-` is unary when it\n * starts the expression or directly follows another operator or an\n * opening paren. A unary `-` folds into the following number literal\n * (and a unary `+` is a no-op), which keeps the operator stack purely\n * binary.\n *\n * @throws CalculatorError on malformed structure (`type: \"syntax\"`),\n * division/modulo by zero (`type: \"divide-by-zero\"`), or a non-finite\n * result (`type: \"overflow\"`).\n */\nfunction evaluate(tokens: Token[]): number {\n const values: number[] = [];\n const operators: string[] = [];\n\n /** Pop the top operator and apply it to the top two values. */\n const applyTop = (): void => {\n const operator = operators.pop();\n\n if (operator === undefined) {\n throw new CalculatorError(\"Malformed expression.\", { type: \"syntax\" });\n }\n\n const right = values.pop();\n const left = values.pop();\n\n if (left === undefined || right === undefined) {\n throw new CalculatorError(\n `Operator \"${operator}\" is missing an operand.`,\n { type: \"syntax\" },\n );\n }\n\n values.push(OPERATORS[operator].apply(left, right));\n };\n\n // `expectOperand` tracks the positional state machine: at the start, and\n // immediately after an operator or \"(\", we expect an operand (a number,\n // a \"(\", or a unary sign). After a number or \")\", we expect a binary\n // operator or \")\".\n let expectOperand = true;\n // Pending unary sign multiplier folded into the next numeric operand.\n let pendingSign = 1;\n\n for (let i = 0; i < tokens.length; i += 1) {\n const token = tokens[i];\n\n if (token.type === \"number\") {\n if (!expectOperand) {\n throw new CalculatorError(\n \"Unexpected number — a number cannot directly follow another value.\",\n { type: \"syntax\" },\n );\n }\n\n values.push(pendingSign * token.value);\n pendingSign = 1;\n expectOperand = false;\n\n continue;\n }\n\n if (token.type === \"paren\") {\n if (token.value === \"(\") {\n if (!expectOperand) {\n throw new CalculatorError(\n 'Unexpected \"(\" — it cannot directly follow a value.',\n { type: \"syntax\" },\n );\n }\n\n // A unary sign in front of a parenthesized group is modeled by\n // pushing the sign as a multiplication: `-(…)` becomes `-1 * (…)`.\n if (pendingSign === -1) {\n values.push(-1);\n operators.push(\"*\");\n pendingSign = 1;\n }\n\n operators.push(\"(\");\n expectOperand = true;\n\n continue;\n }\n\n // token.value === \")\"\n if (expectOperand) {\n throw new CalculatorError(\n 'Unexpected \")\" — an operand was expected.',\n { type: \"syntax\" },\n );\n }\n\n let foundOpen = false;\n\n while (operators.length > 0) {\n if (operators[operators.length - 1] === \"(\") {\n operators.pop();\n foundOpen = true;\n\n break;\n }\n\n applyTop();\n }\n\n if (!foundOpen) {\n throw new CalculatorError(\"Unbalanced parentheses.\", { type: \"syntax\" });\n }\n\n expectOperand = false;\n\n continue;\n }\n\n // token.type === \"operator\"\n if (expectOperand) {\n // A `+`/`-` in operand position is a unary sign; anything else is a\n // misplaced binary operator.\n if (token.value === \"-\") {\n pendingSign = -pendingSign;\n\n continue;\n }\n\n if (token.value === \"+\") {\n continue;\n }\n\n throw new CalculatorError(\n `Operator \"${token.value}\" has no left-hand operand.`,\n { type: \"syntax\" },\n );\n }\n\n const incoming = OPERATORS[token.value];\n\n while (operators.length > 0) {\n const top = operators[operators.length - 1];\n\n if (top === \"(\") {\n break;\n }\n\n const topSpec = OPERATORS[top];\n const higher = topSpec.precedence > incoming.precedence;\n const equalLeft =\n topSpec.precedence === incoming.precedence &&\n incoming.associativity === \"left\";\n\n if (higher || equalLeft) {\n applyTop();\n\n continue;\n }\n\n break;\n }\n\n operators.push(token.value);\n expectOperand = true;\n }\n\n if (expectOperand) {\n throw new CalculatorError(\n \"Expression ends with an operator or is empty.\",\n { type: \"syntax\" },\n );\n }\n\n while (operators.length > 0) {\n if (operators[operators.length - 1] === \"(\") {\n throw new CalculatorError(\"Unbalanced parentheses.\", { type: \"syntax\" });\n }\n\n applyTop();\n }\n\n const result = values.pop();\n\n if (result === undefined || values.length > 0) {\n throw new CalculatorError(\"Malformed expression.\", { type: \"syntax\" });\n }\n\n if (!Number.isFinite(result)) {\n throw new CalculatorError(\"Result is not a finite number.\", {\n type: \"overflow\",\n });\n }\n\n return result;\n}\n\n/**\n * Build the `calculator` tool — a SAFE arithmetic evaluator the agent can\n * call to compute a numeric expression. It supports `+ - * / % ^`, unary\n * signs, parentheses, and decimal/scientific-notation literals, with the\n * usual precedence (`^` highest and right-associative, then `* / %`, then\n * `+ -`).\n *\n * **Safety.** The expression is tokenized and evaluated with a\n * shunting-yard pass — it NEVER calls `eval` or `new Function`. The\n * lexer only recognizes numbers, parentheses, and the fixed operator set,\n * so there is no path to an identifier, function call, or property\n * access; any other character is a syntax error returned as data.\n *\n * **Errors flow as data.** A malformed expression, division/modulo by\n * zero, or a non-finite result throws a {@link CalculatorError} inside\n * the handler; `tool()` catches it and surfaces it in the returned\n * `{ error }` field (the LLM-visible message is preserved), so the agent\n * reads the failure and self-corrects instead of crashing.\n *\n * @param options - Optional overrides; `name` renames the LLM-visible tool.\n * @returns A `ToolContract<{ expression }, { result }>` ready to drop into `tools: []`.\n *\n * @example\n * const calc = calculatorTool();\n * const { data } = await calc.invoke({ expression: \"(3 + 4) * 2\" });\n * console.log(data?.result); // 14\n */\nexport function calculatorTool(\n options?: CalculatorOptions,\n): ToolContract<CalculatorInput, CalculatorResult> {\n return tool<CalculatorInput, CalculatorResult>({\n name: options?.name ?? DEFAULT_NAME,\n description:\n \"Evaluate an arithmetic expression and return the numeric result. \" +\n \"Supports + - * / % ^, parentheses, unary +/-, and decimal or \" +\n \"scientific-notation numbers. Does not support variables or \" +\n \"functions — pass a literal expression like \\\"(3 + 4) * 2\\\".\",\n input: inputSchema,\n async execute(input) {\n const tokens = tokenize(input.expression);\n\n if (tokens.length === 0) {\n throw new CalculatorError(\"Expression is empty.\", { type: \"syntax\" });\n }\n\n return { result: evaluate(tokens) };\n },\n });\n}\n"],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import { DateTimeInput, DateTimeOptions, DateTimeResult } from "../contracts/utility.type.mjs";
2
+ import { ToolContract } from "@warlock.js/ai";
3
+
4
+ //#region ../@warlock.js/ai-tools/src/utility/date-time.d.ts
5
+ /**
6
+ * A clock seam so tests are deterministic. Defaults to the real
7
+ * {@link Date}; a test injects a fixed instant. Returns milliseconds
8
+ * since the Unix epoch — the same contract as `Date.now()`.
9
+ */
10
+ type Clock = () => number;
11
+ /** Construction dependencies for {@link dateTimeTool}, all optional. */
12
+ interface DateTimeToolDeps {
13
+ /**
14
+ * The clock used by the `now` op. Defaults to `Date.now`. Injected in
15
+ * tests for deterministic output.
16
+ */
17
+ clock?: Clock;
18
+ }
19
+ /**
20
+ * Build the `date_time` tool — a small clock/calendar utility the agent
21
+ * can call to read the current instant, shift an instant, measure the
22
+ * gap between two instants, or render one. The `op` discriminator selects
23
+ * the operation:
24
+ *
25
+ * - **`now`** — the current instant, rendered per `format` / `timeZone`
26
+ * (defaults to a UTC ISO string). Reads the injectable {@link Clock}.
27
+ * - **`add`** — `iso` shifted by `amount` of `unit` (e.g.
28
+ * `+3 days`); a negative `amount` shifts backward.
29
+ * - **`diff`** — the signed difference `to − iso`, expressed in `unit`.
30
+ * - **`format`** — `iso` rendered per `format` / `timeZone`.
31
+ *
32
+ * Units are millisecond-based (`milliseconds`…`weeks`, plus common
33
+ * aliases); calendar-relative `month`/`year` are intentionally
34
+ * unsupported because they have no fixed length.
35
+ *
36
+ * **Deterministic in tests.** The clock backing `now` is injectable via
37
+ * `deps.clock`; production defaults to `Date.now`.
38
+ *
39
+ * **Errors flow as data.** A missing/invalid field, an unknown unit, or
40
+ * an unrecognized time zone throws a {@link DateTimeError} inside the
41
+ * handler; `tool()` surfaces it in `{ error }` so the agent self-corrects.
42
+ *
43
+ * @param options - Optional overrides; `name` renames the tool,
44
+ * `defaultTimeZone` applies when a call omits `timeZone`.
45
+ * @param deps - Injectable dependencies (the {@link Clock}); defaults to real time.
46
+ * @returns A `ToolContract<DateTimeInput, { value }>` ready for `tools: []`.
47
+ *
48
+ * @example
49
+ * const clock = () => Date.parse("2026-06-22T00:00:00Z");
50
+ * const dt = dateTimeTool({}, { clock });
51
+ * const { data } = await dt.invoke({ op: "now" });
52
+ * console.log(data?.value); // "2026-06-22T00:00:00.000Z"
53
+ */
54
+ declare function dateTimeTool(options?: DateTimeOptions, deps?: DateTimeToolDeps): ToolContract<DateTimeInput, DateTimeResult>;
55
+ //#endregion
56
+ export { Clock, DateTimeToolDeps, dateTimeTool };
57
+ //# sourceMappingURL=date-time.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"date-time.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai-tools/src/utility/date-time.ts"],"mappings":";;;;;;AAqBA;;;KAAY,KAAA;AAAK;AAAA,UAGA,gBAAA;EAAgB;;;AAKlB;EAAb,KAAA,GAAQ,KAAK;AAAA;;;;;;;;;;;;;;;;;AAqL8B;;;;;;;;;;;;;;;;;;;iBAH7B,YAAA,CACd,OAAA,GAAU,eAAA,EACV,IAAA,GAAO,gBAAA,GACN,YAAA,CAAa,aAAA,EAAe,cAAA"}