@warlock.js/ai-tools 4.8.2 → 4.9.1

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 (67) hide show
  1. package/cjs/index.cjs +15 -15
  2. package/cjs/index.cjs.map +1 -1
  3. package/esm/contracts/http.type.d.mts +1 -1
  4. package/esm/contracts/http.type.d.mts.map +1 -1
  5. package/esm/contracts/mcp.type.d.mts +1 -1
  6. package/esm/contracts/mcp.type.d.mts.map +1 -1
  7. package/esm/contracts/utility.type.d.mts +1 -1
  8. package/esm/contracts/utility.type.d.mts.map +1 -1
  9. package/esm/contracts/web.type.d.mts +1 -1
  10. package/esm/contracts/web.type.d.mts.map +1 -1
  11. package/esm/errors.d.mts +1 -1
  12. package/esm/errors.d.mts.map +1 -1
  13. package/esm/errors.mjs +1 -1
  14. package/esm/errors.mjs.map +1 -1
  15. package/esm/http/http-request.d.mts +1 -1
  16. package/esm/http/http-request.d.mts.map +1 -1
  17. package/esm/http/http-request.mjs +1 -1
  18. package/esm/http/http-request.mjs.map +1 -1
  19. package/esm/mcp/client.mjs +1 -1
  20. package/esm/mcp/client.mjs.map +1 -1
  21. package/esm/mcp/index.d.mts +1 -1
  22. package/esm/mcp/index.d.mts.map +1 -1
  23. package/esm/mcp/index.mjs +1 -1
  24. package/esm/mcp/index.mjs.map +1 -1
  25. package/esm/mcp/json-schema-to-standard.d.mts +1 -1
  26. package/esm/mcp/json-schema-to-standard.d.mts.map +1 -1
  27. package/esm/mcp/json-schema-to-standard.mjs +1 -1
  28. package/esm/mcp/json-schema-to-standard.mjs.map +1 -1
  29. package/esm/mcp/serve.d.mts +1 -1
  30. package/esm/mcp/serve.d.mts.map +1 -1
  31. package/esm/mcp/serve.mjs +1 -1
  32. package/esm/mcp/serve.mjs.map +1 -1
  33. package/esm/mcp/transport.d.mts +1 -1
  34. package/esm/mcp/transport.d.mts.map +1 -1
  35. package/esm/mcp/transport.mjs +1 -1
  36. package/esm/mcp/transport.mjs.map +1 -1
  37. package/esm/mcp/transport.type.d.mts +1 -1
  38. package/esm/mcp/transport.type.d.mts.map +1 -1
  39. package/esm/node_modules/@standard-schema/spec/dist/index.d.mts +1 -1
  40. package/esm/node_modules/@standard-schema/spec/dist/index.d.mts.map +1 -1
  41. package/esm/register.d.mts +1 -1
  42. package/esm/register.d.mts.map +1 -1
  43. package/esm/register.mjs +1 -1
  44. package/esm/register.mjs.map +1 -1
  45. package/esm/schema.mjs +1 -1
  46. package/esm/schema.mjs.map +1 -1
  47. package/esm/utility/calculator.d.mts +1 -1
  48. package/esm/utility/calculator.d.mts.map +1 -1
  49. package/esm/utility/calculator.mjs +1 -1
  50. package/esm/utility/calculator.mjs.map +1 -1
  51. package/esm/utility/date-time.d.mts +1 -1
  52. package/esm/utility/date-time.d.mts.map +1 -1
  53. package/esm/utility/date-time.mjs +1 -1
  54. package/esm/utility/date-time.mjs.map +1 -1
  55. package/esm/utility/schema.mjs +1 -1
  56. package/esm/utility/schema.mjs.map +1 -1
  57. package/esm/web/fetch-url.d.mts +1 -1
  58. package/esm/web/fetch-url.d.mts.map +1 -1
  59. package/esm/web/fetch-url.mjs +1 -1
  60. package/esm/web/fetch-url.mjs.map +1 -1
  61. package/esm/web/schema.mjs +1 -1
  62. package/esm/web/schema.mjs.map +1 -1
  63. package/esm/web/web-search.d.mts +1 -1
  64. package/esm/web/web-search.d.mts.map +1 -1
  65. package/esm/web/web-search.mjs +1 -1
  66. package/esm/web/web-search.mjs.map +1 -1
  67. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"transport.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai-tools/src/mcp/transport.ts"],"sourcesContent":["import { spawn, type ChildProcessWithoutNullStreams } from \"node:child_process\";\nimport { createInterface, type Interface } from \"node:readline\";\nimport type {\n JsonRpcId,\n JsonRpcRequest,\n JsonRpcResponse,\n McpTransport,\n} from \"../contracts\";\nimport { McpTransportError } from \"../errors\";\nimport type { McpTransportClient } from \"./transport.type\";\n\n/** Default per-request wait before a transport call is abandoned. */\nconst DEFAULT_REQUEST_TIMEOUT_MS = 30_000;\n\n/** The JSON-RPC version literal every outbound message carries. */\nconst JSONRPC_VERSION = \"2.0\";\n\n/**\n * A pending in-flight request awaiting its correlated response, keyed by\n * the JSON-RPC `id`. The stdio transport multiplexes many requests over\n * one line-framed pipe, so each resolve/reject is parked here until the\n * line whose `id` matches arrives.\n */\ninterface PendingCall {\n resolve(response: JsonRpcResponse): void;\n reject(error: McpTransportError): void;\n /** Clears the per-call timeout + abort wiring when the call settles. */\n cleanup(): void;\n}\n\n/**\n * Wire a per-call timeout and an optional caller `AbortSignal` onto a\n * pending request, returning a `cleanup()` that tears both down. The\n * `onSettle` callback removes the pending entry from whatever registry the\n * transport keeps so a late response can't double-settle.\n */\nfunction armCall(\n reject: (error: McpTransportError) => void,\n method: string,\n options: { signal?: AbortSignal; timeoutMs?: number } | undefined,\n onSettle: () => void,\n): () => void {\n const timeoutMs = options?.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n\n const timer = setTimeout(() => {\n onSettle();\n reject(\n new McpTransportError(\n `MCP request \"${method}\" timed out after ${timeoutMs}ms.`,\n { type: \"timeout\", method },\n ),\n );\n }, timeoutMs);\n\n const onAbort = () => {\n cleanup();\n reject(\n new McpTransportError(`MCP request \"${method}\" was aborted.`, {\n type: \"closed\",\n method,\n }),\n );\n };\n\n const signal = options?.signal;\n\n if (signal) {\n if (signal.aborted) {\n // Defer so the caller has the rejection wired before it fires.\n queueMicrotask(onAbort);\n } else {\n signal.addEventListener(\"abort\", onAbort, { once: true });\n }\n }\n\n function cleanup(): void {\n clearTimeout(timer);\n\n if (signal) {\n signal.removeEventListener(\"abort\", onAbort);\n }\n }\n\n return cleanup;\n}\n\n/**\n * The stdio transport — spawns a child process and speaks JSON-RPC over\n * its stdin/stdout, one JSON object per line (newline-delimited framing).\n * Uses only Node built-ins (`node:child_process` + `node:readline`); no\n * dependency.\n *\n * Constructed via {@link createStdioTransport}; the class itself is\n * internal.\n */\nclass StdioTransport implements McpTransportClient {\n /** The spawned server process. */\n private readonly child: ChildProcessWithoutNullStreams;\n\n /** Line reader over the child's stdout — one JSON-RPC message per line. */\n private readonly reader: Interface;\n\n /** In-flight requests awaiting a correlated response, keyed by id. */\n private readonly pending = new Map<JsonRpcId, PendingCall>();\n\n /** Monotonic id source for outbound requests. */\n private nextId = 1;\n\n /** Flipped once {@link close} runs (or the child exits) so reuse rejects. */\n private closed = false;\n\n public constructor(transport: Extract<McpTransport, { type: \"stdio\" }>) {\n let child: ChildProcessWithoutNullStreams;\n\n try {\n child = spawn(transport.command, transport.args ?? [], {\n // process.env is NOT inherited unless the caller opts in — pass\n // what the server needs explicitly, mirroring the workspace shell\n // policy. `undefined` lets Node default to an empty-ish env.\n env: transport.env,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n }) as ChildProcessWithoutNullStreams;\n } catch (cause) {\n const message = cause instanceof Error ? cause.message : String(cause);\n\n throw new McpTransportError(\n `MCP stdio transport could not spawn \"${transport.command}\": ${message}`,\n { type: \"connect\", cause },\n );\n }\n\n this.child = child;\n this.reader = createInterface({ input: child.stdout });\n\n this.reader.on(\"line\", (line) => this.onLine(line));\n\n // A child that dies takes every in-flight (and future) call with it.\n child.on(\"exit\", (code) => this.failAll(\"connect\", `child exited with code ${code ?? \"null\"}`));\n child.on(\"error\", (error) => this.failAll(\"connect\", error.message));\n }\n\n /**\n * Parse one stdout line and route it to its pending request. Non-JSON\n * lines (a server logging to stdout) and messages with no matching `id`\n * (notifications, stray responses) are ignored — robustness over strictness.\n */\n private onLine(line: string): void {\n const trimmed = line.trim();\n\n if (!trimmed) {\n return;\n }\n\n let message: JsonRpcResponse;\n\n try {\n message = JSON.parse(trimmed) as JsonRpcResponse;\n } catch {\n // Not a JSON-RPC line (server diagnostics on stdout) — ignore.\n return;\n }\n\n if (message.id === undefined || message.id === null) {\n // A notification or a malformed response — nothing to correlate.\n return;\n }\n\n const call = this.pending.get(message.id);\n\n if (!call) {\n return;\n }\n\n this.pending.delete(message.id);\n call.cleanup();\n call.resolve(message);\n }\n\n /**\n * Reject every pending call (and mark the transport unusable) when the\n * child dies or errors — so a hung server can never leave a caller\n * waiting forever.\n */\n private failAll(type: \"connect\" | \"closed\", reason: string): void {\n this.closed = true;\n\n for (const [id, call] of this.pending) {\n this.pending.delete(id);\n call.cleanup();\n call.reject(\n new McpTransportError(`MCP stdio transport failed: ${reason}.`, { type }),\n );\n }\n }\n\n public request<TResult = unknown>(\n request: JsonRpcRequest,\n options?: { signal?: AbortSignal; timeoutMs?: number },\n ): Promise<JsonRpcResponse<TResult>> {\n if (this.closed) {\n return Promise.reject(\n new McpTransportError(\"MCP stdio transport is closed.\", {\n type: \"closed\",\n method: request.method,\n }),\n );\n }\n\n const id = request.id;\n\n return new Promise<JsonRpcResponse<TResult>>((resolve, reject) => {\n const cleanup = armCall(reject, request.method, options, () =>\n this.pending.delete(id),\n );\n\n this.pending.set(id, {\n resolve: (response) => resolve(response as JsonRpcResponse<TResult>),\n reject,\n cleanup,\n });\n\n try {\n this.child.stdin.write(`${JSON.stringify(request)}\\n`);\n } catch (cause) {\n this.pending.delete(id);\n cleanup();\n\n const message = cause instanceof Error ? cause.message : String(cause);\n\n reject(\n new McpTransportError(\n `MCP stdio transport failed to write request \"${request.method}\": ${message}`,\n { type: \"closed\", method: request.method, cause },\n ),\n );\n }\n });\n }\n\n public async notify(method: string, params?: unknown): Promise<void> {\n if (this.closed) {\n throw new McpTransportError(\"MCP stdio transport is closed.\", {\n type: \"closed\",\n method,\n });\n }\n\n const notification = { jsonrpc: JSONRPC_VERSION, method, params };\n this.child.stdin.write(`${JSON.stringify(notification)}\\n`);\n }\n\n /** Allocate the next outbound request id. */\n public allocateId(): number {\n return this.nextId++;\n }\n\n public async close(): Promise<void> {\n if (this.closed) {\n return;\n }\n\n this.closed = true;\n this.reader.close();\n this.failAll(\"closed\", \"transport closed by caller\");\n this.child.kill();\n }\n}\n\n/**\n * The Streamable HTTP transport — POSTs each JSON-RPC request to the\n * server endpoint over the global `fetch` (Node 18+) and reads the single\n * JSON response. No SSE-legacy, no WebSocket. Static `headers` (e.g. auth)\n * are sent with every request.\n *\n * Constructed via {@link createHttpTransport}; the class itself is internal.\n */\nclass HttpTransport implements McpTransportClient {\n /** The server endpoint POST target. */\n private readonly url: string;\n\n /** Static headers merged into every request (auth, etc.). */\n private readonly headers: Record<string, string>;\n\n /** Monotonic id source for outbound requests. */\n private nextId = 1;\n\n public constructor(transport: Extract<McpTransport, { type: \"http\" }>) {\n this.url = transport.url;\n this.headers = {\n \"content-type\": \"application/json\",\n accept: \"application/json, text/event-stream\",\n ...transport.headers,\n };\n }\n\n public async request<TResult = unknown>(\n request: JsonRpcRequest,\n options?: { signal?: AbortSignal; timeoutMs?: number },\n ): Promise<JsonRpcResponse<TResult>> {\n const timeoutMs = options?.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n const onAbort = () => controller.abort();\n const signal = options?.signal;\n\n if (signal) {\n if (signal.aborted) {\n controller.abort();\n } else {\n signal.addEventListener(\"abort\", onAbort, { once: true });\n }\n }\n\n let response: Response;\n\n try {\n response = await fetch(this.url, {\n method: \"POST\",\n headers: this.headers,\n body: JSON.stringify(request),\n signal: controller.signal,\n });\n } catch (cause) {\n const aborted = controller.signal.aborted;\n const message = cause instanceof Error ? cause.message : String(cause);\n\n throw new McpTransportError(\n aborted\n ? `MCP request \"${request.method}\" timed out or was aborted after ${timeoutMs}ms.`\n : `MCP http transport request \"${request.method}\" failed: ${message}`,\n { type: aborted ? \"timeout\" : \"connect\", method: request.method, cause },\n );\n } finally {\n clearTimeout(timer);\n\n if (signal) {\n signal.removeEventListener(\"abort\", onAbort);\n }\n }\n\n if (!response.ok) {\n throw new McpTransportError(\n `MCP http transport request \"${request.method}\" returned HTTP ${response.status}.`,\n { type: \"connect\", method: request.method, context: { status: response.status } },\n );\n }\n\n return this.parseBody<TResult>(response, request.method);\n }\n\n /**\n * Parse the HTTP response body into a JSON-RPC response. Streamable HTTP\n * may answer with either `application/json` (a single response object)\n * or `text/event-stream` (SSE frames); we read the body as text and\n * extract the first JSON object, supporting the common `data: {...}`\n * SSE line shape without a streaming parser.\n */\n private async parseBody<TResult>(\n response: Response,\n method: string,\n ): Promise<JsonRpcResponse<TResult>> {\n const raw = await response.text();\n const contentType = response.headers.get(\"content-type\")?.toLowerCase() ?? \"\";\n\n const jsonText = contentType.includes(\"text/event-stream\")\n ? extractSseData(raw)\n : raw;\n\n if (!jsonText) {\n throw new McpTransportError(\n `MCP http transport got an empty response for \"${method}\".`,\n { type: \"protocol\", method },\n );\n }\n\n try {\n return JSON.parse(jsonText) as JsonRpcResponse<TResult>;\n } catch (cause) {\n throw new McpTransportError(\n `MCP http transport got a non-JSON response for \"${method}\".`,\n { type: \"protocol\", method, cause },\n );\n }\n }\n\n public async notify(method: string, params?: unknown): Promise<void> {\n const notification = { jsonrpc: JSONRPC_VERSION, method, params };\n\n // A notification expects no response; fire-and-forget but surface a\n // connect failure so a dead endpoint is not silently ignored.\n try {\n await fetch(this.url, {\n method: \"POST\",\n headers: this.headers,\n body: JSON.stringify(notification),\n });\n } catch (cause) {\n const message = cause instanceof Error ? cause.message : String(cause);\n\n throw new McpTransportError(\n `MCP http transport notification \"${method}\" failed: ${message}`,\n { type: \"connect\", method, cause },\n );\n }\n }\n\n /** Allocate the next outbound request id. */\n public allocateId(): number {\n return this.nextId++;\n }\n\n public async close(): Promise<void> {\n // Streamable HTTP is stateless per request — nothing persistent to\n // release.\n }\n}\n\n/**\n * Pull the first `data:` JSON payload out of an SSE response body. MCP's\n * Streamable HTTP transport answers a single request with one SSE frame\n * carrying the JSON-RPC response; we take the first non-empty `data:`\n * line. Returns an empty string when none is found.\n */\nfunction extractSseData(body: string): string {\n for (const line of body.split(/\\r?\\n/)) {\n const trimmed = line.trim();\n\n if (trimmed.startsWith(\"data:\")) {\n const payload = trimmed.slice(\"data:\".length).trim();\n\n if (payload && payload !== \"[DONE]\") {\n return payload;\n }\n }\n }\n\n return \"\";\n}\n\n/**\n * Build the concrete {@link McpTransportClient} for an {@link McpTransport}\n * config — a {@link StdioTransport} for `type: \"stdio\"`, an\n * {@link HttpTransport} for `type: \"http\"`. The returned client also\n * carries an `allocateId()` for the JSON-RPC client to mint request ids.\n *\n * @param transport - The transport config (discriminated by `type`).\n * @returns A transport client paired with its id allocator.\n */\nexport function createTransport(\n transport: McpTransport,\n): McpTransportClient & { allocateId(): number } {\n if (transport.type === \"stdio\") {\n return new StdioTransport(transport);\n }\n\n return new HttpTransport(transport);\n}\n\n/**\n * A minimal JSON-RPC 2.0 request/response client over any\n * {@link McpTransportClient}. Mints monotonic ids, frames the\n * `{ jsonrpc, id, method, params }` envelope, and unwraps the response —\n * translating a JSON-RPC `error` member into a typed\n * {@link McpTransportError} so callers branch on `error.type` rather than\n * parsing the wire.\n *\n * Constructed via {@link createJsonRpcClient}; the class itself is internal.\n */\nclass JsonRpcClient {\n /** The underlying framing transport. */\n private readonly transport: McpTransportClient & { allocateId(): number };\n\n public constructor(transport: McpTransportClient & { allocateId(): number }) {\n this.transport = transport;\n }\n\n /**\n * Issue a JSON-RPC `method` call and resolve with its `result`,\n * throwing a typed {@link McpTransportError} on a JSON-RPC error member\n * or a malformed response (neither `result` nor `error`).\n */\n public async call<TResult = unknown>(\n method: string,\n params?: unknown,\n options?: { signal?: AbortSignal; timeoutMs?: number },\n ): Promise<TResult> {\n const request: JsonRpcRequest = {\n jsonrpc: JSONRPC_VERSION,\n id: this.transport.allocateId(),\n method,\n params,\n };\n\n const response = await this.transport.request<TResult>(request, options);\n\n if (response.error) {\n throw new McpTransportError(\n `MCP \"${method}\" failed: ${response.error.message} (code ${response.error.code}).`,\n { type: \"protocol\", method, context: { code: response.error.code }, cause: response.error.data },\n );\n }\n\n if (response.result === undefined) {\n throw new McpTransportError(\n `MCP \"${method}\" returned a response with neither result nor error.`,\n { type: \"protocol\", method },\n );\n }\n\n return response.result;\n }\n\n /** Send a one-way JSON-RPC notification (no response awaited). */\n public notify(method: string, params?: unknown): Promise<void> {\n return this.transport.notify(method, params);\n }\n\n /** Close the underlying transport. */\n public close(): Promise<void> {\n return this.transport.close();\n }\n}\n\n/**\n * A JSON-RPC client over an MCP transport. Either pass an already-built\n * transport client (tests inject a fake) or an {@link McpTransport} config\n * to spawn/connect a real one.\n */\nexport interface JsonRpcClientHandle {\n /** Issue a request and resolve with its `result` (throws on error). */\n call<TResult = unknown>(\n method: string,\n params?: unknown,\n options?: { signal?: AbortSignal; timeoutMs?: number },\n ): Promise<TResult>;\n /** Send a one-way notification. */\n notify(method: string, params?: unknown): Promise<void>;\n /** Close the underlying transport. */\n close(): Promise<void>;\n}\n\n/**\n * Build a {@link JsonRpcClientHandle} over a transport. Accepts either a\n * pre-built {@link McpTransportClient} (the test seam — inject a scripted\n * fake) or an {@link McpTransport} config, in which case the concrete\n * transport is constructed via {@link createTransport}.\n *\n * When a bare {@link McpTransportClient} (without an `allocateId`) is\n * injected, the client supplies its own monotonic id source.\n *\n * @param source - A transport client or an `McpTransport` config.\n * @returns A JSON-RPC client handle.\n */\nexport function createJsonRpcClient(\n source: McpTransport | McpTransportClient,\n): JsonRpcClientHandle {\n const transport: McpTransportClient & { allocateId(): number } = isTransportConfig(source)\n ? createTransport(source)\n : withIdAllocator(source);\n\n return new JsonRpcClient(transport);\n}\n\n/**\n * Distinguish an {@link McpTransport} config (a plain object with a `type`\n * discriminator and no `request` method) from a built\n * {@link McpTransportClient} (which exposes `request`).\n */\nfunction isTransportConfig(\n source: McpTransport | McpTransportClient,\n): source is McpTransport {\n return typeof (source as McpTransportClient).request !== \"function\";\n}\n\n/**\n * Wrap an injected {@link McpTransportClient} that lacks its own\n * `allocateId` with a monotonic id source, so the JSON-RPC client can mint\n * request ids uniformly regardless of whether the transport was built here\n * or supplied by a test.\n */\nfunction withIdAllocator(\n client: McpTransportClient,\n): McpTransportClient & { allocateId(): number } {\n const candidate = client as McpTransportClient & { allocateId?(): number };\n\n if (typeof candidate.allocateId === \"function\") {\n return candidate as McpTransportClient & { allocateId(): number };\n }\n\n let nextId = 1;\n\n return Object.assign(client, { allocateId: () => nextId++ });\n}\n"],"mappings":";;;;;;AAYA,MAAM,6BAA6B;;AAGnC,MAAM,kBAAkB;;;;;;;AAqBxB,SAAS,QACP,QACA,QACA,SACA,UACY;CACZ,MAAM,YAAY,SAAS,aAAa;CAExC,MAAM,QAAQ,iBAAiB;EAC7B,SAAS;EACT,OACE,IAAI,kBACF,gBAAgB,OAAO,oBAAoB,UAAU,MACrD;GAAE,MAAM;GAAW;EAAO,CAC5B,CACF;CACF,GAAG,SAAS;CAEZ,MAAM,gBAAgB;EACpB,QAAQ;EACR,OACE,IAAI,kBAAkB,gBAAgB,OAAO,iBAAiB;GAC5D,MAAM;GACN;EACF,CAAC,CACH;CACF;CAEA,MAAM,SAAS,SAAS;CAExB,IAAI,QACF,IAAI,OAAO,SAET,eAAe,OAAO;MAEtB,OAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;CAI5D,SAAS,UAAgB;EACvB,aAAa,KAAK;EAElB,IAAI,QACF,OAAO,oBAAoB,SAAS,OAAO;CAE/C;CAEA,OAAO;AACT;;;;;;;;;;AAWA,IAAM,iBAAN,MAAmD;CAgBjD,AAAO,YAAY,WAAqD;iCAR7C,IAAI,IAA4B;gBAG1C;gBAGA;EAGf,IAAI;EAEJ,IAAI;GACF,QAAQ,MAAM,UAAU,SAAS,UAAU,QAAQ,CAAC,GAAG;IAIrD,KAAK,UAAU;IACf,OAAO;KAAC;KAAQ;KAAQ;IAAM;GAChC,CAAC;EACH,SAAS,OAAO;GACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GAErE,MAAM,IAAI,kBACR,wCAAwC,UAAU,QAAQ,KAAK,WAC/D;IAAE,MAAM;IAAW;GAAM,CAC3B;EACF;EAEA,KAAK,QAAQ;EACb,KAAK,SAAS,gBAAgB,EAAE,OAAO,MAAM,OAAO,CAAC;EAErD,KAAK,OAAO,GAAG,SAAS,SAAS,KAAK,OAAO,IAAI,CAAC;EAGlD,MAAM,GAAG,SAAS,SAAS,KAAK,QAAQ,WAAW,0BAA0B,QAAQ,QAAQ,CAAC;EAC9F,MAAM,GAAG,UAAU,UAAU,KAAK,QAAQ,WAAW,MAAM,OAAO,CAAC;CACrE;;;;;;CAOA,AAAQ,OAAO,MAAoB;EACjC,MAAM,UAAU,KAAK,KAAK;EAE1B,IAAI,CAAC,SACH;EAGF,IAAI;EAEJ,IAAI;GACF,UAAU,KAAK,MAAM,OAAO;EAC9B,QAAQ;GAEN;EACF;EAEA,IAAI,QAAQ,OAAO,UAAa,QAAQ,OAAO,MAE7C;EAGF,MAAM,OAAO,KAAK,QAAQ,IAAI,QAAQ,EAAE;EAExC,IAAI,CAAC,MACH;EAGF,KAAK,QAAQ,OAAO,QAAQ,EAAE;EAC9B,KAAK,QAAQ;EACb,KAAK,QAAQ,OAAO;CACtB;;;;;;CAOA,AAAQ,QAAQ,MAA4B,QAAsB;EAChE,KAAK,SAAS;EAEd,KAAK,MAAM,CAAC,IAAI,SAAS,KAAK,SAAS;GACrC,KAAK,QAAQ,OAAO,EAAE;GACtB,KAAK,QAAQ;GACb,KAAK,OACH,IAAI,kBAAkB,+BAA+B,OAAO,IAAI,EAAE,KAAK,CAAC,CAC1E;EACF;CACF;CAEA,AAAO,QACL,SACA,SACmC;EACnC,IAAI,KAAK,QACP,OAAO,QAAQ,OACb,IAAI,kBAAkB,kCAAkC;GACtD,MAAM;GACN,QAAQ,QAAQ;EAClB,CAAC,CACH;EAGF,MAAM,KAAK,QAAQ;EAEnB,OAAO,IAAI,SAAmC,SAAS,WAAW;GAChE,MAAM,UAAU,QAAQ,QAAQ,QAAQ,QAAQ,eAC9C,KAAK,QAAQ,OAAO,EAAE,CACxB;GAEA,KAAK,QAAQ,IAAI,IAAI;IACnB,UAAU,aAAa,QAAQ,QAAoC;IACnE;IACA;GACF,CAAC;GAED,IAAI;IACF,KAAK,MAAM,MAAM,MAAM,GAAG,KAAK,UAAU,OAAO,EAAE,GAAG;GACvD,SAAS,OAAO;IACd,KAAK,QAAQ,OAAO,EAAE;IACtB,QAAQ;IAER,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;IAErE,OACE,IAAI,kBACF,gDAAgD,QAAQ,OAAO,KAAK,WACpE;KAAE,MAAM;KAAU,QAAQ,QAAQ;KAAQ;IAAM,CAClD,CACF;GACF;EACF,CAAC;CACH;CAEA,MAAa,OAAO,QAAgB,QAAiC;EACnE,IAAI,KAAK,QACP,MAAM,IAAI,kBAAkB,kCAAkC;GAC5D,MAAM;GACN;EACF,CAAC;EAGH,MAAM,eAAe;GAAE,SAAS;GAAiB;GAAQ;EAAO;EAChE,KAAK,MAAM,MAAM,MAAM,GAAG,KAAK,UAAU,YAAY,EAAE,GAAG;CAC5D;;CAGA,AAAO,aAAqB;EAC1B,OAAO,KAAK;CACd;CAEA,MAAa,QAAuB;EAClC,IAAI,KAAK,QACP;EAGF,KAAK,SAAS;EACd,KAAK,OAAO,MAAM;EAClB,KAAK,QAAQ,UAAU,4BAA4B;EACnD,KAAK,MAAM,KAAK;CAClB;AACF;;;;;;;;;AAUA,IAAM,gBAAN,MAAkD;CAUhD,AAAO,YAAY,WAAoD;gBAFtD;EAGf,KAAK,MAAM,UAAU;EACrB,KAAK,UAAU;GACb,gBAAgB;GAChB,QAAQ;GACR,GAAG,UAAU;EACf;CACF;CAEA,MAAa,QACX,SACA,SACmC;EACnC,MAAM,YAAY,SAAS,aAAa;EACxC,MAAM,aAAa,IAAI,gBAAgB;EACvC,MAAM,QAAQ,iBAAiB,WAAW,MAAM,GAAG,SAAS;EAE5D,MAAM,gBAAgB,WAAW,MAAM;EACvC,MAAM,SAAS,SAAS;EAExB,IAAI,QACF,IAAI,OAAO,SACT,WAAW,MAAM;OAEjB,OAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;EAI5D,IAAI;EAEJ,IAAI;GACF,WAAW,MAAM,MAAM,KAAK,KAAK;IAC/B,QAAQ;IACR,SAAS,KAAK;IACd,MAAM,KAAK,UAAU,OAAO;IAC5B,QAAQ,WAAW;GACrB,CAAC;EACH,SAAS,OAAO;GACd,MAAM,UAAU,WAAW,OAAO;GAClC,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GAErE,MAAM,IAAI,kBACR,UACI,gBAAgB,QAAQ,OAAO,mCAAmC,UAAU,OAC5E,+BAA+B,QAAQ,OAAO,YAAY,WAC9D;IAAE,MAAM,UAAU,YAAY;IAAW,QAAQ,QAAQ;IAAQ;GAAM,CACzE;EACF,UAAU;GACR,aAAa,KAAK;GAElB,IAAI,QACF,OAAO,oBAAoB,SAAS,OAAO;EAE/C;EAEA,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,kBACR,+BAA+B,QAAQ,OAAO,kBAAkB,SAAS,OAAO,IAChF;GAAE,MAAM;GAAW,QAAQ,QAAQ;GAAQ,SAAS,EAAE,QAAQ,SAAS,OAAO;EAAE,CAClF;EAGF,OAAO,KAAK,UAAmB,UAAU,QAAQ,MAAM;CACzD;;;;;;;;CASA,MAAc,UACZ,UACA,QACmC;EACnC,MAAM,MAAM,MAAM,SAAS,KAAK;EAGhC,MAAM,YAFc,SAAS,QAAQ,IAAI,cAAc,CAAC,EAAE,YAAY,KAAK,GAE/C,CAAC,SAAS,mBAAmB,IACrD,eAAe,GAAG,IAClB;EAEJ,IAAI,CAAC,UACH,MAAM,IAAI,kBACR,iDAAiD,OAAO,KACxD;GAAE,MAAM;GAAY;EAAO,CAC7B;EAGF,IAAI;GACF,OAAO,KAAK,MAAM,QAAQ;EAC5B,SAAS,OAAO;GACd,MAAM,IAAI,kBACR,mDAAmD,OAAO,KAC1D;IAAE,MAAM;IAAY;IAAQ;GAAM,CACpC;EACF;CACF;CAEA,MAAa,OAAO,QAAgB,QAAiC;EACnE,MAAM,eAAe;GAAE,SAAS;GAAiB;GAAQ;EAAO;EAIhE,IAAI;GACF,MAAM,MAAM,KAAK,KAAK;IACpB,QAAQ;IACR,SAAS,KAAK;IACd,MAAM,KAAK,UAAU,YAAY;GACnC,CAAC;EACH,SAAS,OAAO;GAGd,MAAM,IAAI,kBACR,oCAAoC,OAAO,YAH7B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,KAInE;IAAE,MAAM;IAAW;IAAQ;GAAM,CACnC;EACF;CACF;;CAGA,AAAO,aAAqB;EAC1B,OAAO,KAAK;CACd;CAEA,MAAa,QAAuB,CAGpC;AACF;;;;;;;AAQA,SAAS,eAAe,MAAsB;CAC5C,KAAK,MAAM,QAAQ,KAAK,MAAM,OAAO,GAAG;EACtC,MAAM,UAAU,KAAK,KAAK;EAE1B,IAAI,QAAQ,WAAW,OAAO,GAAG;GAC/B,MAAM,UAAU,QAAQ,MAAM,CAAc,CAAC,CAAC,KAAK;GAEnD,IAAI,WAAW,YAAY,UACzB,OAAO;EAEX;CACF;CAEA,OAAO;AACT;;;;;;;;;;AAWA,SAAgB,gBACd,WAC+C;CAC/C,IAAI,UAAU,SAAS,SACrB,OAAO,IAAI,eAAe,SAAS;CAGrC,OAAO,IAAI,cAAc,SAAS;AACpC;;;;;;;;;;;AAYA,IAAM,gBAAN,MAAoB;CAIlB,AAAO,YAAY,WAA0D;EAC3E,KAAK,YAAY;CACnB;;;;;;CAOA,MAAa,KACX,QACA,QACA,SACkB;EAClB,MAAM,UAA0B;GAC9B,SAAS;GACT,IAAI,KAAK,UAAU,WAAW;GAC9B;GACA;EACF;EAEA,MAAM,WAAW,MAAM,KAAK,UAAU,QAAiB,SAAS,OAAO;EAEvE,IAAI,SAAS,OACX,MAAM,IAAI,kBACR,QAAQ,OAAO,YAAY,SAAS,MAAM,QAAQ,SAAS,SAAS,MAAM,KAAK,KAC/E;GAAE,MAAM;GAAY;GAAQ,SAAS,EAAE,MAAM,SAAS,MAAM,KAAK;GAAG,OAAO,SAAS,MAAM;EAAK,CACjG;EAGF,IAAI,SAAS,WAAW,QACtB,MAAM,IAAI,kBACR,QAAQ,OAAO,uDACf;GAAE,MAAM;GAAY;EAAO,CAC7B;EAGF,OAAO,SAAS;CAClB;;CAGA,AAAO,OAAO,QAAgB,QAAiC;EAC7D,OAAO,KAAK,UAAU,OAAO,QAAQ,MAAM;CAC7C;;CAGA,AAAO,QAAuB;EAC5B,OAAO,KAAK,UAAU,MAAM;CAC9B;AACF;;;;;;;;;;;;;AAgCA,SAAgB,oBACd,QACqB;CAKrB,OAAO,IAAI,cAJsD,kBAAkB,MAAM,IACrF,gBAAgB,MAAM,IACtB,gBAAgB,MAAM,CAEQ;AACpC;;;;;;AAOA,SAAS,kBACP,QACwB;CACxB,OAAO,OAAQ,OAA8B,YAAY;AAC3D;;;;;;;AAQA,SAAS,gBACP,QAC+C;CAC/C,MAAM,YAAY;CAElB,IAAI,OAAO,UAAU,eAAe,YAClC,OAAO;CAGT,IAAI,SAAS;CAEb,OAAO,OAAO,OAAO,QAAQ,EAAE,kBAAkB,SAAS,CAAC;AAC7D"}
1
+ {"version":3,"file":"transport.mjs","names":[],"sources":["../../../../../../../ai-tools/src/mcp/transport.ts"],"sourcesContent":["import { spawn, type ChildProcessWithoutNullStreams } from \"node:child_process\";\nimport { createInterface, type Interface } from \"node:readline\";\nimport type {\n JsonRpcId,\n JsonRpcRequest,\n JsonRpcResponse,\n McpTransport,\n} from \"../contracts\";\nimport { McpTransportError } from \"../errors\";\nimport type { McpTransportClient } from \"./transport.type\";\n\n/** Default per-request wait before a transport call is abandoned. */\nconst DEFAULT_REQUEST_TIMEOUT_MS = 30_000;\n\n/** The JSON-RPC version literal every outbound message carries. */\nconst JSONRPC_VERSION = \"2.0\";\n\n/**\n * A pending in-flight request awaiting its correlated response, keyed by\n * the JSON-RPC `id`. The stdio transport multiplexes many requests over\n * one line-framed pipe, so each resolve/reject is parked here until the\n * line whose `id` matches arrives.\n */\ninterface PendingCall {\n resolve(response: JsonRpcResponse): void;\n reject(error: McpTransportError): void;\n /** Clears the per-call timeout + abort wiring when the call settles. */\n cleanup(): void;\n}\n\n/**\n * Wire a per-call timeout and an optional caller `AbortSignal` onto a\n * pending request, returning a `cleanup()` that tears both down. The\n * `onSettle` callback removes the pending entry from whatever registry the\n * transport keeps so a late response can't double-settle.\n */\nfunction armCall(\n reject: (error: McpTransportError) => void,\n method: string,\n options: { signal?: AbortSignal; timeoutMs?: number } | undefined,\n onSettle: () => void,\n): () => void {\n const timeoutMs = options?.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n\n const timer = setTimeout(() => {\n onSettle();\n reject(\n new McpTransportError(\n `MCP request \"${method}\" timed out after ${timeoutMs}ms.`,\n { type: \"timeout\", method },\n ),\n );\n }, timeoutMs);\n\n const onAbort = () => {\n cleanup();\n reject(\n new McpTransportError(`MCP request \"${method}\" was aborted.`, {\n type: \"closed\",\n method,\n }),\n );\n };\n\n const signal = options?.signal;\n\n if (signal) {\n if (signal.aborted) {\n // Defer so the caller has the rejection wired before it fires.\n queueMicrotask(onAbort);\n } else {\n signal.addEventListener(\"abort\", onAbort, { once: true });\n }\n }\n\n function cleanup(): void {\n clearTimeout(timer);\n\n if (signal) {\n signal.removeEventListener(\"abort\", onAbort);\n }\n }\n\n return cleanup;\n}\n\n/**\n * The stdio transport — spawns a child process and speaks JSON-RPC over\n * its stdin/stdout, one JSON object per line (newline-delimited framing).\n * Uses only Node built-ins (`node:child_process` + `node:readline`); no\n * dependency.\n *\n * Constructed via {@link createStdioTransport}; the class itself is\n * internal.\n */\nclass StdioTransport implements McpTransportClient {\n /** The spawned server process. */\n private readonly child: ChildProcessWithoutNullStreams;\n\n /** Line reader over the child's stdout — one JSON-RPC message per line. */\n private readonly reader: Interface;\n\n /** In-flight requests awaiting a correlated response, keyed by id. */\n private readonly pending = new Map<JsonRpcId, PendingCall>();\n\n /** Monotonic id source for outbound requests. */\n private nextId = 1;\n\n /** Flipped once {@link close} runs (or the child exits) so reuse rejects. */\n private closed = false;\n\n public constructor(transport: Extract<McpTransport, { type: \"stdio\" }>) {\n let child: ChildProcessWithoutNullStreams;\n\n try {\n child = spawn(transport.command, transport.args ?? [], {\n // process.env is NOT inherited unless the caller opts in — pass\n // what the server needs explicitly, mirroring the workspace shell\n // policy. `undefined` lets Node default to an empty-ish env.\n env: transport.env,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n }) as ChildProcessWithoutNullStreams;\n } catch (cause) {\n const message = cause instanceof Error ? cause.message : String(cause);\n\n throw new McpTransportError(\n `MCP stdio transport could not spawn \"${transport.command}\": ${message}`,\n { type: \"connect\", cause },\n );\n }\n\n this.child = child;\n this.reader = createInterface({ input: child.stdout });\n\n this.reader.on(\"line\", (line) => this.onLine(line));\n\n // A child that dies takes every in-flight (and future) call with it.\n child.on(\"exit\", (code) => this.failAll(\"connect\", `child exited with code ${code ?? \"null\"}`));\n child.on(\"error\", (error) => this.failAll(\"connect\", error.message));\n }\n\n /**\n * Parse one stdout line and route it to its pending request. Non-JSON\n * lines (a server logging to stdout) and messages with no matching `id`\n * (notifications, stray responses) are ignored — robustness over strictness.\n */\n private onLine(line: string): void {\n const trimmed = line.trim();\n\n if (!trimmed) {\n return;\n }\n\n let message: JsonRpcResponse;\n\n try {\n message = JSON.parse(trimmed) as JsonRpcResponse;\n } catch {\n // Not a JSON-RPC line (server diagnostics on stdout) — ignore.\n return;\n }\n\n if (message.id === undefined || message.id === null) {\n // A notification or a malformed response — nothing to correlate.\n return;\n }\n\n const call = this.pending.get(message.id);\n\n if (!call) {\n return;\n }\n\n this.pending.delete(message.id);\n call.cleanup();\n call.resolve(message);\n }\n\n /**\n * Reject every pending call (and mark the transport unusable) when the\n * child dies or errors — so a hung server can never leave a caller\n * waiting forever.\n */\n private failAll(type: \"connect\" | \"closed\", reason: string): void {\n this.closed = true;\n\n for (const [id, call] of this.pending) {\n this.pending.delete(id);\n call.cleanup();\n call.reject(\n new McpTransportError(`MCP stdio transport failed: ${reason}.`, { type }),\n );\n }\n }\n\n public request<TResult = unknown>(\n request: JsonRpcRequest,\n options?: { signal?: AbortSignal; timeoutMs?: number },\n ): Promise<JsonRpcResponse<TResult>> {\n if (this.closed) {\n return Promise.reject(\n new McpTransportError(\"MCP stdio transport is closed.\", {\n type: \"closed\",\n method: request.method,\n }),\n );\n }\n\n const id = request.id;\n\n return new Promise<JsonRpcResponse<TResult>>((resolve, reject) => {\n const cleanup = armCall(reject, request.method, options, () =>\n this.pending.delete(id),\n );\n\n this.pending.set(id, {\n resolve: (response) => resolve(response as JsonRpcResponse<TResult>),\n reject,\n cleanup,\n });\n\n try {\n this.child.stdin.write(`${JSON.stringify(request)}\\n`);\n } catch (cause) {\n this.pending.delete(id);\n cleanup();\n\n const message = cause instanceof Error ? cause.message : String(cause);\n\n reject(\n new McpTransportError(\n `MCP stdio transport failed to write request \"${request.method}\": ${message}`,\n { type: \"closed\", method: request.method, cause },\n ),\n );\n }\n });\n }\n\n public async notify(method: string, params?: unknown): Promise<void> {\n if (this.closed) {\n throw new McpTransportError(\"MCP stdio transport is closed.\", {\n type: \"closed\",\n method,\n });\n }\n\n const notification = { jsonrpc: JSONRPC_VERSION, method, params };\n this.child.stdin.write(`${JSON.stringify(notification)}\\n`);\n }\n\n /** Allocate the next outbound request id. */\n public allocateId(): number {\n return this.nextId++;\n }\n\n public async close(): Promise<void> {\n if (this.closed) {\n return;\n }\n\n this.closed = true;\n this.reader.close();\n this.failAll(\"closed\", \"transport closed by caller\");\n this.child.kill();\n }\n}\n\n/**\n * The Streamable HTTP transport — POSTs each JSON-RPC request to the\n * server endpoint over the global `fetch` (Node 18+) and reads the single\n * JSON response. No SSE-legacy, no WebSocket. Static `headers` (e.g. auth)\n * are sent with every request.\n *\n * Constructed via {@link createHttpTransport}; the class itself is internal.\n */\nclass HttpTransport implements McpTransportClient {\n /** The server endpoint POST target. */\n private readonly url: string;\n\n /** Static headers merged into every request (auth, etc.). */\n private readonly headers: Record<string, string>;\n\n /** Monotonic id source for outbound requests. */\n private nextId = 1;\n\n public constructor(transport: Extract<McpTransport, { type: \"http\" }>) {\n this.url = transport.url;\n this.headers = {\n \"content-type\": \"application/json\",\n accept: \"application/json, text/event-stream\",\n ...transport.headers,\n };\n }\n\n public async request<TResult = unknown>(\n request: JsonRpcRequest,\n options?: { signal?: AbortSignal; timeoutMs?: number },\n ): Promise<JsonRpcResponse<TResult>> {\n const timeoutMs = options?.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n const onAbort = () => controller.abort();\n const signal = options?.signal;\n\n if (signal) {\n if (signal.aborted) {\n controller.abort();\n } else {\n signal.addEventListener(\"abort\", onAbort, { once: true });\n }\n }\n\n let response: Response;\n\n try {\n response = await fetch(this.url, {\n method: \"POST\",\n headers: this.headers,\n body: JSON.stringify(request),\n signal: controller.signal,\n });\n } catch (cause) {\n const aborted = controller.signal.aborted;\n const message = cause instanceof Error ? cause.message : String(cause);\n\n throw new McpTransportError(\n aborted\n ? `MCP request \"${request.method}\" timed out or was aborted after ${timeoutMs}ms.`\n : `MCP http transport request \"${request.method}\" failed: ${message}`,\n { type: aborted ? \"timeout\" : \"connect\", method: request.method, cause },\n );\n } finally {\n clearTimeout(timer);\n\n if (signal) {\n signal.removeEventListener(\"abort\", onAbort);\n }\n }\n\n if (!response.ok) {\n throw new McpTransportError(\n `MCP http transport request \"${request.method}\" returned HTTP ${response.status}.`,\n { type: \"connect\", method: request.method, context: { status: response.status } },\n );\n }\n\n return this.parseBody<TResult>(response, request.method);\n }\n\n /**\n * Parse the HTTP response body into a JSON-RPC response. Streamable HTTP\n * may answer with either `application/json` (a single response object)\n * or `text/event-stream` (SSE frames); we read the body as text and\n * extract the first JSON object, supporting the common `data: {...}`\n * SSE line shape without a streaming parser.\n */\n private async parseBody<TResult>(\n response: Response,\n method: string,\n ): Promise<JsonRpcResponse<TResult>> {\n const raw = await response.text();\n const contentType = response.headers.get(\"content-type\")?.toLowerCase() ?? \"\";\n\n const jsonText = contentType.includes(\"text/event-stream\")\n ? extractSseData(raw)\n : raw;\n\n if (!jsonText) {\n throw new McpTransportError(\n `MCP http transport got an empty response for \"${method}\".`,\n { type: \"protocol\", method },\n );\n }\n\n try {\n return JSON.parse(jsonText) as JsonRpcResponse<TResult>;\n } catch (cause) {\n throw new McpTransportError(\n `MCP http transport got a non-JSON response for \"${method}\".`,\n { type: \"protocol\", method, cause },\n );\n }\n }\n\n public async notify(method: string, params?: unknown): Promise<void> {\n const notification = { jsonrpc: JSONRPC_VERSION, method, params };\n\n // A notification expects no response; fire-and-forget but surface a\n // connect failure so a dead endpoint is not silently ignored.\n try {\n await fetch(this.url, {\n method: \"POST\",\n headers: this.headers,\n body: JSON.stringify(notification),\n });\n } catch (cause) {\n const message = cause instanceof Error ? cause.message : String(cause);\n\n throw new McpTransportError(\n `MCP http transport notification \"${method}\" failed: ${message}`,\n { type: \"connect\", method, cause },\n );\n }\n }\n\n /** Allocate the next outbound request id. */\n public allocateId(): number {\n return this.nextId++;\n }\n\n public async close(): Promise<void> {\n // Streamable HTTP is stateless per request — nothing persistent to\n // release.\n }\n}\n\n/**\n * Pull the first `data:` JSON payload out of an SSE response body. MCP's\n * Streamable HTTP transport answers a single request with one SSE frame\n * carrying the JSON-RPC response; we take the first non-empty `data:`\n * line. Returns an empty string when none is found.\n */\nfunction extractSseData(body: string): string {\n for (const line of body.split(/\\r?\\n/)) {\n const trimmed = line.trim();\n\n if (trimmed.startsWith(\"data:\")) {\n const payload = trimmed.slice(\"data:\".length).trim();\n\n if (payload && payload !== \"[DONE]\") {\n return payload;\n }\n }\n }\n\n return \"\";\n}\n\n/**\n * Build the concrete {@link McpTransportClient} for an {@link McpTransport}\n * config — a {@link StdioTransport} for `type: \"stdio\"`, an\n * {@link HttpTransport} for `type: \"http\"`. The returned client also\n * carries an `allocateId()` for the JSON-RPC client to mint request ids.\n *\n * @param transport - The transport config (discriminated by `type`).\n * @returns A transport client paired with its id allocator.\n */\nexport function createTransport(\n transport: McpTransport,\n): McpTransportClient & { allocateId(): number } {\n if (transport.type === \"stdio\") {\n return new StdioTransport(transport);\n }\n\n return new HttpTransport(transport);\n}\n\n/**\n * A minimal JSON-RPC 2.0 request/response client over any\n * {@link McpTransportClient}. Mints monotonic ids, frames the\n * `{ jsonrpc, id, method, params }` envelope, and unwraps the response —\n * translating a JSON-RPC `error` member into a typed\n * {@link McpTransportError} so callers branch on `error.type` rather than\n * parsing the wire.\n *\n * Constructed via {@link createJsonRpcClient}; the class itself is internal.\n */\nclass JsonRpcClient {\n /** The underlying framing transport. */\n private readonly transport: McpTransportClient & { allocateId(): number };\n\n public constructor(transport: McpTransportClient & { allocateId(): number }) {\n this.transport = transport;\n }\n\n /**\n * Issue a JSON-RPC `method` call and resolve with its `result`,\n * throwing a typed {@link McpTransportError} on a JSON-RPC error member\n * or a malformed response (neither `result` nor `error`).\n */\n public async call<TResult = unknown>(\n method: string,\n params?: unknown,\n options?: { signal?: AbortSignal; timeoutMs?: number },\n ): Promise<TResult> {\n const request: JsonRpcRequest = {\n jsonrpc: JSONRPC_VERSION,\n id: this.transport.allocateId(),\n method,\n params,\n };\n\n const response = await this.transport.request<TResult>(request, options);\n\n if (response.error) {\n throw new McpTransportError(\n `MCP \"${method}\" failed: ${response.error.message} (code ${response.error.code}).`,\n { type: \"protocol\", method, context: { code: response.error.code }, cause: response.error.data },\n );\n }\n\n if (response.result === undefined) {\n throw new McpTransportError(\n `MCP \"${method}\" returned a response with neither result nor error.`,\n { type: \"protocol\", method },\n );\n }\n\n return response.result;\n }\n\n /** Send a one-way JSON-RPC notification (no response awaited). */\n public notify(method: string, params?: unknown): Promise<void> {\n return this.transport.notify(method, params);\n }\n\n /** Close the underlying transport. */\n public close(): Promise<void> {\n return this.transport.close();\n }\n}\n\n/**\n * A JSON-RPC client over an MCP transport. Either pass an already-built\n * transport client (tests inject a fake) or an {@link McpTransport} config\n * to spawn/connect a real one.\n */\nexport interface JsonRpcClientHandle {\n /** Issue a request and resolve with its `result` (throws on error). */\n call<TResult = unknown>(\n method: string,\n params?: unknown,\n options?: { signal?: AbortSignal; timeoutMs?: number },\n ): Promise<TResult>;\n /** Send a one-way notification. */\n notify(method: string, params?: unknown): Promise<void>;\n /** Close the underlying transport. */\n close(): Promise<void>;\n}\n\n/**\n * Build a {@link JsonRpcClientHandle} over a transport. Accepts either a\n * pre-built {@link McpTransportClient} (the test seam — inject a scripted\n * fake) or an {@link McpTransport} config, in which case the concrete\n * transport is constructed via {@link createTransport}.\n *\n * When a bare {@link McpTransportClient} (without an `allocateId`) is\n * injected, the client supplies its own monotonic id source.\n *\n * @param source - A transport client or an `McpTransport` config.\n * @returns A JSON-RPC client handle.\n */\nexport function createJsonRpcClient(\n source: McpTransport | McpTransportClient,\n): JsonRpcClientHandle {\n const transport: McpTransportClient & { allocateId(): number } = isTransportConfig(source)\n ? createTransport(source)\n : withIdAllocator(source);\n\n return new JsonRpcClient(transport);\n}\n\n/**\n * Distinguish an {@link McpTransport} config (a plain object with a `type`\n * discriminator and no `request` method) from a built\n * {@link McpTransportClient} (which exposes `request`).\n */\nfunction isTransportConfig(\n source: McpTransport | McpTransportClient,\n): source is McpTransport {\n return typeof (source as McpTransportClient).request !== \"function\";\n}\n\n/**\n * Wrap an injected {@link McpTransportClient} that lacks its own\n * `allocateId` with a monotonic id source, so the JSON-RPC client can mint\n * request ids uniformly regardless of whether the transport was built here\n * or supplied by a test.\n */\nfunction withIdAllocator(\n client: McpTransportClient,\n): McpTransportClient & { allocateId(): number } {\n const candidate = client as McpTransportClient & { allocateId?(): number };\n\n if (typeof candidate.allocateId === \"function\") {\n return candidate as McpTransportClient & { allocateId(): number };\n }\n\n let nextId = 1;\n\n return Object.assign(client, { allocateId: () => nextId++ });\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { JsonRpcRequest, JsonRpcResponse } from "../contracts/mcp.type.mjs";
2
- //#region ../@warlock.js/ai-tools/src/mcp/transport.type.d.ts
2
+ //#region ../ai-tools/src/mcp/transport.type.d.ts
3
3
  /**
4
4
  * Internal contract every concrete MCP transport (stdio / Streamable HTTP)
5
5
  * satisfies. Pure declaration — the factories that build transports live
@@ -1 +1 @@
1
- {"version":3,"file":"transport.type.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai-tools/src/mcp/transport.type.ts"],"mappings":";;;;;AAgBA;;;;;;;;;;;UAAiB,kBAAA;EAaf;;;;;;;;;;;;EAAA,OAAA,oBACE,OAAA,EAAS,cAAA,EACT,OAAA;IAAY,MAAA,GAAS,WAAA;IAAa,SAAA;EAAA,IACjC,OAAA,CAAQ,eAAA,CAAgB,OAAA;EAgBlB;;AAAO;;;;;EAPhB,MAAA,CAAO,MAAA,UAAgB,MAAA,aAAmB,OAAA;;;;;;EAO1C,KAAA,IAAS,OAAA;AAAA"}
1
+ {"version":3,"file":"transport.type.d.mts","names":[],"sources":["../../../../../../../ai-tools/src/mcp/transport.type.ts"],"mappings":";;;;;AAgBA;;;;;;;;;;;UAAiB,kBAAA;EAaf;;;;;;;;;;;;EAAA,OAAA,oBACE,OAAA,EAAS,cAAA,EACT,OAAA;IAAY,MAAA,GAAS,WAAA;IAAa,SAAA;EAAA,IACjC,OAAA,CAAQ,eAAA,CAAgB,OAAA;EAgBlB;;AAAO;;;;;EAPhB,MAAA,CAAO,MAAA,UAAgB,MAAA,aAAmB,OAAA;;;;;;EAO1C,KAAA,IAAS,OAAA;AAAA"}
@@ -1,4 +1,4 @@
1
- //#region ../@warlock.js/node_modules/@standard-schema/spec/dist/index.d.ts
1
+ //#region ../node_modules/@standard-schema/spec/dist/index.d.ts
2
2
  /** The Standard Typed interface. This is a base type extended by other specs. */
3
3
  interface StandardTypedV1<Input = unknown, Output = Input> {
4
4
  /** The Standard properties. */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":["Input","Output","StandardTypedV1","Props","version","vendor","types","Types","input","output","InferInput","Schema","NonNullable","InferOutput","StandardSchemaV1","validate","value","Options","options","Result","Promise","SuccessResult","FailureResult","issues","libraryOptions","Record","ReadonlyArray","Issue","message","path","PropertyKey","PathSegment","key","StandardJSONSchemaV1","jsonSchema","Converter","Target","target"],"sources":["../../../../../../../../../../@warlock.js/node_modules/@standard-schema/spec/dist/index.d.ts"],"x_google_ignoreList":[0],"mappings":";;UACU,eAAA,2BAA0C,KAAA;EAA3B;EAAA,SAEZ,WAAA,EAAa,eAAA,CAAgB,KAAA,CAAM,KAAA,EAAO,MAAA;AAAA;AAAA,kBAErC,eAAA;EAFqC;EAAA,UAIzCG,KAAAA,2BAAgC,KAAA;IAJC;IAAA,SAM9BC,OAAAA;IARSJ;IAAAA,SAUTK,MAAAA;IAVmCL;IAAAA,SAYnCM,KAAAA,GAAQ,KAAA,CAAM,KAAA,EAAO,MAAA;EAAA;EAVIH;EAAAA,UAa5BI,KAAAA,2BAAgC,KAAA;IAbSN;IAAAA,SAetCO,KAAAA,EAAO,KAAA;IAfqC;IAAA,SAiB5CC,MAAAA,EAAQ,MAAA;EAAA;EAfQ;EAAA,KAkBxBC,UAAAA,gBAA0B,eAAA,IAAmB,WAAA,CAAY,MAAA;EAVnC;EAAA,KAYtBG,WAAAA,gBAA2B,eAAA,IAAmB,WAAA,CAAY,MAAA;AAAA;;UAGzD,gBAAA,2BAA2C,KAAA;EAR5B;EAAA,SAUZ,WAAA,EAAa,gBAAA,CAAiB,KAAA,CAAM,KAAA,EAAO,MAAA;AAAA;AAAA,kBAEtC,gBAAA;EAPkB;EAAA,UAStBV,KAAAA,2BAAgC,KAAA,UAAe,eAAA,CAAgB,KAAA,CAAM,KAAA,EAAO,MAAA;IATnC;IAAA,SAWtCY,QAAAA,GAAWC,KAAAA,WAAgBE,OAAAA,GAAU,gBAAA,CAAiB,OAAA,iBAAwB,MAAA,CAAO,MAAA,IAAU,OAAA,CAAQ,MAAA,CAAO,MAAA;EAAA;EA7BjHf;EAAAA,KAgCLgB,MAAAA,WAAiB,aAAA,CAAc,MAAA,IAAU,aAAA;EAhCblB;EAAAA,UAkCvBoB,aAAAA;IAhCGjB;IAAAA,SAkCAY,KAAAA,EAAO,MAAA;IA9BPV;IAAAA,SAgCAiB,MAAAA;EAAAA;EAAAA,UAEHN,OAAAA;IA/BAV;IAAAA,SAiCGiB,cAAAA,GAAiB,MAAA;EAAA;EAjCYxB;EAAAA,UAoChCsB,aAAAA;IAlCUtB;IAAAA,SAoCPuB,MAAAA,EAAQ,aAAA,CAAc,KAAA;EAAA;EA/B9Bb;EAAAA,UAkCKiB,KAAAA;IAlCqBzB;IAAAA,SAoClB0B,OAAAA;IApCiDjB;IAAAA,SAsCjDkB,IAAAA,GAAO,aAAA,CAAc,WAAA,GAAc,WAAA;EAAA;EApChB3B;EAAAA,UAuCtB6B,WAAAA;IAvCqDpB;IAAAA,SAyClDqB,GAAAA,EAAK,WAAA;EAAA;EAtCZ;EAAA,UAyCIzB,KAAAA,2BAAgC,KAAA,UAAe,eAAA,CAAgB,KAAA,CAAM,KAAA,EAAO,MAAA;EAzCrC;EAAA,KA4C5CG,UAAAA,gBAA0B,eAAA,IAAmB,eAAA,CAAgB,UAAA,CAAW,MAAA;EA1CzB;EAAA,KA4C/CG,WAAAA,gBAA2B,eAAA,IAAmB,eAAA,CAAgB,WAAA,CAAY,MAAA;AAAA"}
1
+ {"version":3,"file":"index.d.mts","names":["Input","Output","StandardTypedV1","Props","version","vendor","types","Types","input","output","InferInput","Schema","NonNullable","InferOutput","StandardSchemaV1","validate","value","Options","options","Result","Promise","SuccessResult","FailureResult","issues","libraryOptions","Record","ReadonlyArray","Issue","message","path","PropertyKey","PathSegment","key","StandardJSONSchemaV1","jsonSchema","Converter","Target","target"],"sources":["../../../../../../../../../../node_modules/@standard-schema/spec/dist/index.d.ts"],"x_google_ignoreList":[0],"mappings":";;UACU,eAAA,2BAA0C,KAAA;EAA3B;EAAA,SAEZ,WAAA,EAAa,eAAA,CAAgB,KAAA,CAAM,KAAA,EAAO,MAAA;AAAA;AAAA,kBAErC,eAAA;EAFqC;EAAA,UAIzCG,KAAAA,2BAAgC,KAAA;IAJC;IAAA,SAM9BC,OAAAA;IARSJ;IAAAA,SAUTK,MAAAA;IAVmCL;IAAAA,SAYnCM,KAAAA,GAAQ,KAAA,CAAM,KAAA,EAAO,MAAA;EAAA;EAVIH;EAAAA,UAa5BI,KAAAA,2BAAgC,KAAA;IAbSN;IAAAA,SAetCO,KAAAA,EAAO,KAAA;IAfqC;IAAA,SAiB5CC,MAAAA,EAAQ,MAAA;EAAA;EAfQ;EAAA,KAkBxBC,UAAAA,gBAA0B,eAAA,IAAmB,WAAA,CAAY,MAAA;EAVnC;EAAA,KAYtBG,WAAAA,gBAA2B,eAAA,IAAmB,WAAA,CAAY,MAAA;AAAA;;UAGzD,gBAAA,2BAA2C,KAAA;EAR5B;EAAA,SAUZ,WAAA,EAAa,gBAAA,CAAiB,KAAA,CAAM,KAAA,EAAO,MAAA;AAAA;AAAA,kBAEtC,gBAAA;EAPkB;EAAA,UAStBV,KAAAA,2BAAgC,KAAA,UAAe,eAAA,CAAgB,KAAA,CAAM,KAAA,EAAO,MAAA;IATnC;IAAA,SAWtCY,QAAAA,GAAWC,KAAAA,WAAgBE,OAAAA,GAAU,gBAAA,CAAiB,OAAA,iBAAwB,MAAA,CAAO,MAAA,IAAU,OAAA,CAAQ,MAAA,CAAO,MAAA;EAAA;EA7BjHf;EAAAA,KAgCLgB,MAAAA,WAAiB,aAAA,CAAc,MAAA,IAAU,aAAA;EAhCblB;EAAAA,UAkCvBoB,aAAAA;IAhCGjB;IAAAA,SAkCAY,KAAAA,EAAO,MAAA;IA9BPV;IAAAA,SAgCAiB,MAAAA;EAAAA;EAAAA,UAEHN,OAAAA;IA/BAV;IAAAA,SAiCGiB,cAAAA,GAAiB,MAAA;EAAA;EAjCYxB;EAAAA,UAoChCsB,aAAAA;IAlCUtB;IAAAA,SAoCPuB,MAAAA,EAAQ,aAAA,CAAc,KAAA;EAAA;EA/B9Bb;EAAAA,UAkCKiB,KAAAA;IAlCqBzB;IAAAA,SAoClB0B,OAAAA;IApCiDjB;IAAAA,SAsCjDkB,IAAAA,GAAO,aAAA,CAAc,WAAA,GAAc,WAAA;EAAA;EApChB3B;EAAAA,UAuCtB6B,WAAAA;IAvCqDpB;IAAAA,SAyClDqB,GAAAA,EAAK,WAAA;EAAA;EAtCZ;EAAA,UAyCIzB,KAAAA,2BAAgC,KAAA,UAAe,eAAA,CAAgB,KAAA,CAAM,KAAA,EAAO,MAAA;EAzCrC;EAAA,KA4C5CG,UAAAA,gBAA0B,eAAA,IAAmB,eAAA,CAAgB,UAAA,CAAW,MAAA;EA1CzB;EAAA,KA4C/CG,WAAAA,gBAA2B,eAAA,IAAmB,eAAA,CAAgB,WAAA,CAAY,MAAA;AAAA"}
@@ -4,7 +4,7 @@ import { CalculatorInput, CalculatorOptions, CalculatorResult, DateTimeInput, Da
4
4
  import { McpFactory } from "./mcp/index.mjs";
5
5
  import { ToolContract } from "@warlock.js/ai";
6
6
 
7
- //#region ../@warlock.js/ai-tools/src/register.d.ts
7
+ //#region ../ai-tools/src/register.d.ts
8
8
  /**
9
9
  * The `ai.tools.*` namespace — the five ready-made agent tools this
10
10
  * package vends. Each member is a factory returning a {@link ToolContract}
@@ -1 +1 @@
1
- {"version":3,"file":"register.d.mts","names":[],"sources":["../../../../../../@warlock.js/ai-tools/src/register.ts"],"mappings":";;;;;;;;;;;;AAiCA;;;UAAiB,gBAAA;EAEoC;EAAnD,SAAA,CAAU,OAAA,EAAS,gBAAA,GAAmB,YAAA,CAAa,cAAA,EAAgB,eAAA;EAA7B;EAEtC,QAAA,CAAS,OAAA,GAAU,eAAA,GAAkB,YAAA,CAAa,aAAA,EAAe,cAAA;EAAf;EAElD,IAAA,CAAK,OAAA,GAAU,kBAAA,GAAqB,YAAA,CAAa,gBAAA,EAAkB,iBAAA;EAF9B;EAIrC,UAAA,CAAW,OAAA,GAAU,iBAAA,GAAoB,YAAA,CAAa,eAAA,EAAiB,gBAAA;EAFtB;EAIjD,QAAA,CAAS,OAAA,GAAU,eAAA,GAAkB,YAAA,CAAa,aAAA,EAAe,cAAA;AAAA;;;;;;;;;;YAYvD,EAAA;IApBV;;;;;IA0BE,KAAA,EAAO,gBAAA;IAxBT;;;;;;IA+BE,GAAA,EAAK,UAAU;EAAA;AAAA"}
1
+ {"version":3,"file":"register.d.mts","names":[],"sources":["../../../../../../ai-tools/src/register.ts"],"mappings":";;;;;;;;;;;;AAiCA;;;UAAiB,gBAAA;EAEoC;EAAnD,SAAA,CAAU,OAAA,EAAS,gBAAA,GAAmB,YAAA,CAAa,cAAA,EAAgB,eAAA;EAA7B;EAEtC,QAAA,CAAS,OAAA,GAAU,eAAA,GAAkB,YAAA,CAAa,aAAA,EAAe,cAAA;EAAf;EAElD,IAAA,CAAK,OAAA,GAAU,kBAAA,GAAqB,YAAA,CAAa,gBAAA,EAAkB,iBAAA;EAF9B;EAIrC,UAAA,CAAW,OAAA,GAAU,iBAAA,GAAoB,YAAA,CAAa,eAAA,EAAiB,gBAAA;EAFtB;EAIjD,QAAA,CAAS,OAAA,GAAU,eAAA,GAAkB,YAAA,CAAa,aAAA,EAAe,cAAA;AAAA;;;;;;;;;;YAYvD,EAAA;IApBV;;;;;IA0BE,KAAA,EAAO,gBAAA;IAxBT;;;;;;IA+BE,GAAA,EAAK,UAAU;EAAA;AAAA"}
package/esm/register.mjs CHANGED
@@ -6,7 +6,7 @@ import { fetchUrlTool } from "./web/fetch-url.mjs";
6
6
  import { webSearchTool } from "./web/web-search.mjs";
7
7
  import { ai } from "@warlock.js/ai";
8
8
 
9
- //#region ../@warlock.js/ai-tools/src/register.ts
9
+ //#region ../ai-tools/src/register.ts
10
10
  ai.tools = {
11
11
  webSearch: webSearchTool,
12
12
  fetchUrl: fetchUrlTool,
@@ -1 +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"}
1
+ {"version":3,"file":"register.mjs","names":[],"sources":["../../../../../../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 CHANGED
@@ -1,4 +1,4 @@
1
- //#region ../@warlock.js/ai-tools/src/schema.ts
1
+ //#region ../ai-tools/src/schema.ts
2
2
  /**
3
3
  * Tiny, dependency-free [Standard Schema](https://standardschema.dev)
4
4
  * builders for the tools' input validation. The package pins only
@@ -1 +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"}
1
+ {"version":3,"file":"schema.mjs","names":[],"sources":["../../../../../../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"}
@@ -1,7 +1,7 @@
1
1
  import { CalculatorInput, CalculatorOptions, CalculatorResult } from "../contracts/utility.type.mjs";
2
2
  import { ToolContract } from "@warlock.js/ai";
3
3
 
4
- //#region ../@warlock.js/ai-tools/src/utility/calculator.d.ts
4
+ //#region ../ai-tools/src/utility/calculator.d.ts
5
5
  /**
6
6
  * Build the `calculator` tool — a SAFE arithmetic evaluator the agent can
7
7
  * call to compute a numeric expression. It supports `+ - * / % ^`, unary
@@ -1 +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"}
1
+ {"version":3,"file":"calculator.d.mts","names":[],"sources":["../../../../../../../ai-tools/src/utility/calculator.ts"],"mappings":";;;;;;AAiZA;;;;;;;;;;;;;;;AAEiD;;;;;;;;;;iBAFjC,cAAA,CACd,OAAA,GAAU,iBAAA,GACT,YAAA,CAAa,eAAA,EAAiB,gBAAA"}
@@ -2,7 +2,7 @@ import { CalculatorError } from "../errors.mjs";
2
2
  import { objectSchema, stringField } from "./schema.mjs";
3
3
  import { tool } from "@warlock.js/ai";
4
4
 
5
- //#region ../@warlock.js/ai-tools/src/utility/calculator.ts
5
+ //#region ../ai-tools/src/utility/calculator.ts
6
6
  /** Default tool name exposed to the LLM. */
7
7
  const DEFAULT_NAME = "calculator";
8
8
  /** Input schema for the `calculator` tool: a single `expression` string. */
@@ -1 +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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"}
1
+ {"version":3,"file":"calculator.mjs","names":[],"sources":["../../../../../../../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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"}
@@ -1,7 +1,7 @@
1
1
  import { DateTimeInput, DateTimeOptions, DateTimeResult } from "../contracts/utility.type.mjs";
2
2
  import { ToolContract } from "@warlock.js/ai";
3
3
 
4
- //#region ../@warlock.js/ai-tools/src/utility/date-time.d.ts
4
+ //#region ../ai-tools/src/utility/date-time.d.ts
5
5
  /**
6
6
  * A clock seam so tests are deterministic. Defaults to the real
7
7
  * {@link Date}; a test injects a fixed instant. Returns milliseconds
@@ -1 +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"}
1
+ {"version":3,"file":"date-time.d.mts","names":[],"sources":["../../../../../../../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"}
@@ -2,7 +2,7 @@ import { DateTimeError } from "../errors.mjs";
2
2
  import { literalField, objectSchema, optionalNumberField, optionalStringField } from "./schema.mjs";
3
3
  import { tool } from "@warlock.js/ai";
4
4
 
5
- //#region ../@warlock.js/ai-tools/src/utility/date-time.ts
5
+ //#region ../ai-tools/src/utility/date-time.ts
6
6
  /** Default tool name exposed to the LLM. */
7
7
  const DEFAULT_NAME = "date_time";
8
8
  /** Input schema for the `date_time` tool. */
@@ -1 +1 @@
1
- {"version":3,"file":"date-time.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai-tools/src/utility/date-time.ts"],"sourcesContent":["import { tool, type ToolContract } from \"@warlock.js/ai\";\nimport { DateTimeError } from \"../errors\";\nimport { literalField, objectSchema, optionalNumberField, optionalStringField } from \"./schema\";\nimport type {\n DateTimeInput,\n DateTimeOp,\n DateTimeOptions,\n DateTimeResult,\n} from \"../contracts/utility.type\";\n\n/** Default tool name exposed to the LLM. */\nconst DEFAULT_NAME = \"date_time\";\n\n/** The closed set of operations the tool implements. */\nconst OPS: readonly DateTimeOp[] = [\"now\", \"add\", \"diff\", \"format\"];\n\n/**\n * A clock seam so tests are deterministic. Defaults to the real\n * {@link Date}; a test injects a fixed instant. Returns milliseconds\n * since the Unix epoch — the same contract as `Date.now()`.\n */\nexport type Clock = () => number;\n\n/** Construction dependencies for {@link dateTimeTool}, all optional. */\nexport interface DateTimeToolDeps {\n /**\n * The clock used by the `now` op. Defaults to `Date.now`. Injected in\n * tests for deterministic output.\n */\n clock?: Clock;\n}\n\n/** Input schema for the `date_time` tool. */\nconst inputSchema = objectSchema<DateTimeInput>({\n op: literalField<DateTimeOp>(OPS),\n iso: optionalStringField(),\n from: optionalStringField(),\n to: optionalStringField(),\n amount: optionalNumberField(),\n unit: optionalStringField(),\n timeZone: optionalStringField(),\n format: optionalStringField(),\n});\n\n/**\n * The time units `add` and `diff` understand, each as its length in\n * milliseconds. Calendar-unaware on purpose: a \"month\" / \"year\" has no\n * fixed millisecond length, so they are deliberately excluded — adding\n * `30 days` is unambiguous in a way adding `1 month` is not.\n */\nconst UNIT_MS: Record<string, number> = {\n milliseconds: 1,\n seconds: 1_000,\n minutes: 60_000,\n hours: 3_600_000,\n days: 86_400_000,\n weeks: 604_800_000,\n};\n\n/** Singular aliases mapped to their canonical plural unit key. */\nconst UNIT_ALIASES: Record<string, string> = {\n millisecond: \"milliseconds\",\n ms: \"milliseconds\",\n second: \"seconds\",\n sec: \"seconds\",\n s: \"seconds\",\n minute: \"minutes\",\n min: \"minutes\",\n m: \"minutes\",\n hour: \"hours\",\n hr: \"hours\",\n h: \"hours\",\n day: \"days\",\n d: \"days\",\n week: \"weeks\",\n w: \"weeks\",\n};\n\n/** Resolve a (possibly aliased / singular) unit token to its ms length. */\nfunction unitToMs(unit: string): number {\n const canonical = UNIT_ALIASES[unit] ?? unit;\n const ms = UNIT_MS[canonical];\n\n if (ms === undefined) {\n throw new DateTimeError(\n `Unknown unit \"${unit}\". Supported units: ${Object.keys(UNIT_MS).join(\", \")}.`,\n { type: \"invalid-unit\" },\n );\n }\n\n return ms;\n}\n\n/**\n * Parse an ISO-8601 instant into a {@link Date}, or throw a typed\n * {@link DateTimeError} when the string is missing or unparseable.\n *\n * @param iso - The ISO string from the model (may be undefined).\n * @param field - The input field name, for the error message.\n */\nfunction parseIso(iso: string | undefined, field: string): Date {\n if (iso === undefined) {\n throw new DateTimeError(`\"${field}\" is required for this operation.`, {\n type: \"invalid-input\",\n });\n }\n\n const date = new Date(iso);\n\n if (Number.isNaN(date.getTime())) {\n throw new DateTimeError(`\"${field}\" is not a valid ISO-8601 instant: \"${iso}\".`, {\n type: \"invalid-input\",\n });\n }\n\n return date;\n}\n\n/**\n * Render a {@link Date} in a target time zone using `Intl`. The\n * `\"iso\"` format (the default) returns the instant's UTC ISO string;\n * any other `format` value is treated as an `Intl.DateTimeFormat`\n * locale-style rendering in the given `timeZone`.\n *\n * @throws DateTimeError `type: \"invalid-time-zone\"` when `timeZone` is\n * not a recognized IANA zone.\n */\nfunction render(date: Date, format: string | undefined, timeZone: string | undefined): string {\n if (format === undefined || format === \"iso\") {\n // `timeZone` is irrelevant to a UTC ISO string, but validate it when\n // supplied so a bad zone is reported rather than silently ignored.\n if (timeZone !== undefined) {\n assertTimeZone(timeZone);\n }\n\n return date.toISOString();\n }\n\n try {\n return new Intl.DateTimeFormat(\"en-US\", {\n timeZone,\n dateStyle: format === \"date\" ? \"medium\" : undefined,\n timeStyle: format === \"time\" ? \"medium\" : undefined,\n ...(format === \"datetime\" ? { dateStyle: \"medium\", timeStyle: \"medium\" } : {}),\n }).format(date);\n } catch (error) {\n throw new DateTimeError(\n `Could not render with format \"${format}\"${\n timeZone ? ` in time zone \"${timeZone}\"` : \"\"\n }.`,\n { type: \"invalid-time-zone\", cause: error },\n );\n }\n}\n\n/**\n * Validate an IANA time zone by attempting to construct a formatter for\n * it; an unrecognized zone makes `Intl` throw a `RangeError`.\n *\n * @throws DateTimeError `type: \"invalid-time-zone\"` for an unknown zone.\n */\nfunction assertTimeZone(timeZone: string): void {\n try {\n new Intl.DateTimeFormat(\"en-US\", { timeZone });\n } catch (error) {\n throw new DateTimeError(`Unknown time zone \"${timeZone}\".`, {\n type: \"invalid-time-zone\",\n cause: error,\n });\n }\n}\n\n/**\n * Build the `date_time` tool — a small clock/calendar utility the agent\n * can call to read the current instant, shift an instant, measure the\n * gap between two instants, or render one. The `op` discriminator selects\n * the operation:\n *\n * - **`now`** — the current instant, rendered per `format` / `timeZone`\n * (defaults to a UTC ISO string). Reads the injectable {@link Clock}.\n * - **`add`** — `iso` shifted by `amount` of `unit` (e.g.\n * `+3 days`); a negative `amount` shifts backward.\n * - **`diff`** — the signed difference `to − iso`, expressed in `unit`.\n * - **`format`** — `iso` rendered per `format` / `timeZone`.\n *\n * Units are millisecond-based (`milliseconds`…`weeks`, plus common\n * aliases); calendar-relative `month`/`year` are intentionally\n * unsupported because they have no fixed length.\n *\n * **Deterministic in tests.** The clock backing `now` is injectable via\n * `deps.clock`; production defaults to `Date.now`.\n *\n * **Errors flow as data.** A missing/invalid field, an unknown unit, or\n * an unrecognized time zone throws a {@link DateTimeError} inside the\n * handler; `tool()` surfaces it in `{ error }` so the agent self-corrects.\n *\n * @param options - Optional overrides; `name` renames the tool,\n * `defaultTimeZone` applies when a call omits `timeZone`.\n * @param deps - Injectable dependencies (the {@link Clock}); defaults to real time.\n * @returns A `ToolContract<DateTimeInput, { value }>` ready for `tools: []`.\n *\n * @example\n * const clock = () => Date.parse(\"2026-06-22T00:00:00Z\");\n * const dt = dateTimeTool({}, { clock });\n * const { data } = await dt.invoke({ op: \"now\" });\n * console.log(data?.value); // \"2026-06-22T00:00:00.000Z\"\n */\nexport function dateTimeTool(\n options?: DateTimeOptions,\n deps?: DateTimeToolDeps,\n): ToolContract<DateTimeInput, DateTimeResult> {\n const clock: Clock = deps?.clock ?? Date.now;\n const defaultTimeZone = options?.defaultTimeZone;\n\n return tool<DateTimeInput, DateTimeResult>({\n name: options?.name ?? DEFAULT_NAME,\n description:\n \"Work with dates and times. Set op to: \\\"now\\\" (current instant), \" +\n '\"add\" (shift iso by amount of unit), \"diff\" (signed to − from in unit; ' +\n \"pass the start instant as iso or from), \" +\n 'or \"format\" (render iso). Units are milliseconds, seconds, minutes, ' +\n \"hours, days, or weeks (month/year are not supported). Pass instants as \" +\n 'ISO-8601 strings; set format to \"iso\" (default), \"date\", \"time\", or ' +\n '\"datetime\", and timeZone as an IANA zone like \"Africa/Cairo\".',\n input: inputSchema,\n async execute(input) {\n const timeZone = input.timeZone ?? defaultTimeZone;\n\n switch (input.op) {\n case \"now\": {\n const date = new Date(clock());\n\n return { value: render(date, input.format, timeZone) };\n }\n\n case \"format\": {\n const date = parseIso(input.iso, \"iso\");\n\n return { value: render(date, input.format, timeZone) };\n }\n\n case \"add\": {\n const date = parseIso(input.iso, \"iso\");\n\n if (input.amount === undefined) {\n throw new DateTimeError('\"amount\" is required for the \"add\" operation.', {\n type: \"invalid-input\",\n });\n }\n\n if (input.unit === undefined) {\n throw new DateTimeError('\"unit\" is required for the \"add\" operation.', {\n type: \"invalid-input\",\n });\n }\n\n const shifted = new Date(date.getTime() + input.amount * unitToMs(input.unit));\n\n return { value: render(shifted, input.format, timeZone) };\n }\n\n case \"diff\": {\n // Accept `from` as an alias for `iso` — models naturally pass\n // `from` / `to` for a difference. `iso` wins when both are set.\n const from = parseIso(input.iso ?? input.from, \"iso (or from)\");\n const to = parseIso(input.to, \"to\");\n\n if (input.unit === undefined) {\n throw new DateTimeError('\"unit\" is required for the \"diff\" operation.', {\n type: \"invalid-input\",\n });\n }\n\n const diff = (to.getTime() - from.getTime()) / unitToMs(input.unit);\n\n return { value: String(diff) };\n }\n\n default: {\n // Exhaustiveness guard — the schema's literal union should make\n // this unreachable, but a future op added to the union without a\n // case here surfaces as typed data rather than silent fallthrough.\n const unreachable: never = input.op;\n\n throw new DateTimeError(`Unsupported operation \"${String(unreachable)}\".`, {\n type: \"unsupported-op\",\n });\n }\n }\n },\n });\n}\n"],"mappings":";;;;;;AAWA,MAAM,eAAe;;AAsBrB,MAAM,cAAc,aAA4B;CAC9C,IAAI,aAAyB;EApBK;EAAO;EAAO;EAAQ;CAoBzB,CAAC;CAChC,KAAK,oBAAoB;CACzB,MAAM,oBAAoB;CAC1B,IAAI,oBAAoB;CACxB,QAAQ,oBAAoB;CAC5B,MAAM,oBAAoB;CAC1B,UAAU,oBAAoB;CAC9B,QAAQ,oBAAoB;AAC9B,CAAC;;;;;;;AAQD,MAAM,UAAkC;CACtC,cAAc;CACd,SAAS;CACT,SAAS;CACT,OAAO;CACP,MAAM;CACN,OAAO;AACT;;AAGA,MAAM,eAAuC;CAC3C,aAAa;CACb,IAAI;CACJ,QAAQ;CACR,KAAK;CACL,GAAG;CACH,QAAQ;CACR,KAAK;CACL,GAAG;CACH,MAAM;CACN,IAAI;CACJ,GAAG;CACH,KAAK;CACL,GAAG;CACH,MAAM;CACN,GAAG;AACL;;AAGA,SAAS,SAAS,MAAsB;CAEtC,MAAM,KAAK,QADO,aAAa,SAAS;CAGxC,IAAI,OAAO,QACT,MAAM,IAAI,cACR,iBAAiB,KAAK,sBAAsB,OAAO,KAAK,OAAO,CAAC,CAAC,KAAK,IAAI,EAAE,IAC5E,EAAE,MAAM,eAAe,CACzB;CAGF,OAAO;AACT;;;;;;;;AASA,SAAS,SAAS,KAAyB,OAAqB;CAC9D,IAAI,QAAQ,QACV,MAAM,IAAI,cAAc,IAAI,MAAM,oCAAoC,EACpE,MAAM,gBACR,CAAC;CAGH,MAAM,OAAO,IAAI,KAAK,GAAG;CAEzB,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,GAC7B,MAAM,IAAI,cAAc,IAAI,MAAM,sCAAsC,IAAI,KAAK,EAC/E,MAAM,gBACR,CAAC;CAGH,OAAO;AACT;;;;;;;;;;AAWA,SAAS,OAAO,MAAY,QAA4B,UAAsC;CAC5F,IAAI,WAAW,UAAa,WAAW,OAAO;EAG5C,IAAI,aAAa,QACf,eAAe,QAAQ;EAGzB,OAAO,KAAK,YAAY;CAC1B;CAEA,IAAI;EACF,OAAO,IAAI,KAAK,eAAe,SAAS;GACtC;GACA,WAAW,WAAW,SAAS,WAAW;GAC1C,WAAW,WAAW,SAAS,WAAW;GAC1C,GAAI,WAAW,aAAa;IAAE,WAAW;IAAU,WAAW;GAAS,IAAI,CAAC;EAC9E,CAAC,CAAC,CAAC,OAAO,IAAI;CAChB,SAAS,OAAO;EACd,MAAM,IAAI,cACR,iCAAiC,OAAO,GACtC,WAAW,kBAAkB,SAAS,KAAK,GAC5C,IACD;GAAE,MAAM;GAAqB,OAAO;EAAM,CAC5C;CACF;AACF;;;;;;;AAQA,SAAS,eAAe,UAAwB;CAC9C,IAAI;EACF,IAAI,KAAK,eAAe,SAAS,EAAE,SAAS,CAAC;CAC/C,SAAS,OAAO;EACd,MAAM,IAAI,cAAc,sBAAsB,SAAS,KAAK;GAC1D,MAAM;GACN,OAAO;EACT,CAAC;CACH;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,SAAgB,aACd,SACA,MAC6C;CAC7C,MAAM,QAAe,MAAM,SAAS,KAAK;CACzC,MAAM,kBAAkB,SAAS;CAEjC,OAAO,KAAoC;EACzC,MAAM,SAAS,QAAQ;EACvB,aACE;EAOF,OAAO;EACP,MAAM,QAAQ,OAAO;GACnB,MAAM,WAAW,MAAM,YAAY;GAEnC,QAAQ,MAAM,IAAd;IACE,KAAK,OAGH,OAAO,EAAE,OAAO,OAAO,IAFN,KAAK,MAAM,CAEF,GAAG,MAAM,QAAQ,QAAQ,EAAE;IAGvD,KAAK,UAGH,OAAO,EAAE,OAAO,OAFH,SAAS,MAAM,KAAK,KAEP,GAAG,MAAM,QAAQ,QAAQ,EAAE;IAGvD,KAAK,OAAO;KACV,MAAM,OAAO,SAAS,MAAM,KAAK,KAAK;KAEtC,IAAI,MAAM,WAAW,QACnB,MAAM,IAAI,cAAc,qDAAiD,EACvE,MAAM,gBACR,CAAC;KAGH,IAAI,MAAM,SAAS,QACjB,MAAM,IAAI,cAAc,mDAA+C,EACrE,MAAM,gBACR,CAAC;KAKH,OAAO,EAAE,OAAO,OAAO,IAFH,KAAK,KAAK,QAAQ,IAAI,MAAM,SAAS,SAAS,MAAM,IAAI,CAE/C,GAAG,MAAM,QAAQ,QAAQ,EAAE;IAC1D;IAEA,KAAK,QAAQ;KAGX,MAAM,OAAO,SAAS,MAAM,OAAO,MAAM,MAAM,eAAe;KAC9D,MAAM,KAAK,SAAS,MAAM,IAAI,IAAI;KAElC,IAAI,MAAM,SAAS,QACjB,MAAM,IAAI,cAAc,oDAAgD,EACtE,MAAM,gBACR,CAAC;KAGH,MAAM,QAAQ,GAAG,QAAQ,IAAI,KAAK,QAAQ,KAAK,SAAS,MAAM,IAAI;KAElE,OAAO,EAAE,OAAO,OAAO,IAAI,EAAE;IAC/B;IAEA,SAAS;KAIP,MAAM,cAAqB,MAAM;KAEjC,MAAM,IAAI,cAAc,0BAA0B,OAAO,WAAW,EAAE,KAAK,EACzE,MAAM,iBACR,CAAC;IACH;GACF;EACF;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"date-time.mjs","names":[],"sources":["../../../../../../../ai-tools/src/utility/date-time.ts"],"sourcesContent":["import { tool, type ToolContract } from \"@warlock.js/ai\";\nimport { DateTimeError } from \"../errors\";\nimport { literalField, objectSchema, optionalNumberField, optionalStringField } from \"./schema\";\nimport type {\n DateTimeInput,\n DateTimeOp,\n DateTimeOptions,\n DateTimeResult,\n} from \"../contracts/utility.type\";\n\n/** Default tool name exposed to the LLM. */\nconst DEFAULT_NAME = \"date_time\";\n\n/** The closed set of operations the tool implements. */\nconst OPS: readonly DateTimeOp[] = [\"now\", \"add\", \"diff\", \"format\"];\n\n/**\n * A clock seam so tests are deterministic. Defaults to the real\n * {@link Date}; a test injects a fixed instant. Returns milliseconds\n * since the Unix epoch — the same contract as `Date.now()`.\n */\nexport type Clock = () => number;\n\n/** Construction dependencies for {@link dateTimeTool}, all optional. */\nexport interface DateTimeToolDeps {\n /**\n * The clock used by the `now` op. Defaults to `Date.now`. Injected in\n * tests for deterministic output.\n */\n clock?: Clock;\n}\n\n/** Input schema for the `date_time` tool. */\nconst inputSchema = objectSchema<DateTimeInput>({\n op: literalField<DateTimeOp>(OPS),\n iso: optionalStringField(),\n from: optionalStringField(),\n to: optionalStringField(),\n amount: optionalNumberField(),\n unit: optionalStringField(),\n timeZone: optionalStringField(),\n format: optionalStringField(),\n});\n\n/**\n * The time units `add` and `diff` understand, each as its length in\n * milliseconds. Calendar-unaware on purpose: a \"month\" / \"year\" has no\n * fixed millisecond length, so they are deliberately excluded — adding\n * `30 days` is unambiguous in a way adding `1 month` is not.\n */\nconst UNIT_MS: Record<string, number> = {\n milliseconds: 1,\n seconds: 1_000,\n minutes: 60_000,\n hours: 3_600_000,\n days: 86_400_000,\n weeks: 604_800_000,\n};\n\n/** Singular aliases mapped to their canonical plural unit key. */\nconst UNIT_ALIASES: Record<string, string> = {\n millisecond: \"milliseconds\",\n ms: \"milliseconds\",\n second: \"seconds\",\n sec: \"seconds\",\n s: \"seconds\",\n minute: \"minutes\",\n min: \"minutes\",\n m: \"minutes\",\n hour: \"hours\",\n hr: \"hours\",\n h: \"hours\",\n day: \"days\",\n d: \"days\",\n week: \"weeks\",\n w: \"weeks\",\n};\n\n/** Resolve a (possibly aliased / singular) unit token to its ms length. */\nfunction unitToMs(unit: string): number {\n const canonical = UNIT_ALIASES[unit] ?? unit;\n const ms = UNIT_MS[canonical];\n\n if (ms === undefined) {\n throw new DateTimeError(\n `Unknown unit \"${unit}\". Supported units: ${Object.keys(UNIT_MS).join(\", \")}.`,\n { type: \"invalid-unit\" },\n );\n }\n\n return ms;\n}\n\n/**\n * Parse an ISO-8601 instant into a {@link Date}, or throw a typed\n * {@link DateTimeError} when the string is missing or unparseable.\n *\n * @param iso - The ISO string from the model (may be undefined).\n * @param field - The input field name, for the error message.\n */\nfunction parseIso(iso: string | undefined, field: string): Date {\n if (iso === undefined) {\n throw new DateTimeError(`\"${field}\" is required for this operation.`, {\n type: \"invalid-input\",\n });\n }\n\n const date = new Date(iso);\n\n if (Number.isNaN(date.getTime())) {\n throw new DateTimeError(`\"${field}\" is not a valid ISO-8601 instant: \"${iso}\".`, {\n type: \"invalid-input\",\n });\n }\n\n return date;\n}\n\n/**\n * Render a {@link Date} in a target time zone using `Intl`. The\n * `\"iso\"` format (the default) returns the instant's UTC ISO string;\n * any other `format` value is treated as an `Intl.DateTimeFormat`\n * locale-style rendering in the given `timeZone`.\n *\n * @throws DateTimeError `type: \"invalid-time-zone\"` when `timeZone` is\n * not a recognized IANA zone.\n */\nfunction render(date: Date, format: string | undefined, timeZone: string | undefined): string {\n if (format === undefined || format === \"iso\") {\n // `timeZone` is irrelevant to a UTC ISO string, but validate it when\n // supplied so a bad zone is reported rather than silently ignored.\n if (timeZone !== undefined) {\n assertTimeZone(timeZone);\n }\n\n return date.toISOString();\n }\n\n try {\n return new Intl.DateTimeFormat(\"en-US\", {\n timeZone,\n dateStyle: format === \"date\" ? \"medium\" : undefined,\n timeStyle: format === \"time\" ? \"medium\" : undefined,\n ...(format === \"datetime\" ? { dateStyle: \"medium\", timeStyle: \"medium\" } : {}),\n }).format(date);\n } catch (error) {\n throw new DateTimeError(\n `Could not render with format \"${format}\"${\n timeZone ? ` in time zone \"${timeZone}\"` : \"\"\n }.`,\n { type: \"invalid-time-zone\", cause: error },\n );\n }\n}\n\n/**\n * Validate an IANA time zone by attempting to construct a formatter for\n * it; an unrecognized zone makes `Intl` throw a `RangeError`.\n *\n * @throws DateTimeError `type: \"invalid-time-zone\"` for an unknown zone.\n */\nfunction assertTimeZone(timeZone: string): void {\n try {\n new Intl.DateTimeFormat(\"en-US\", { timeZone });\n } catch (error) {\n throw new DateTimeError(`Unknown time zone \"${timeZone}\".`, {\n type: \"invalid-time-zone\",\n cause: error,\n });\n }\n}\n\n/**\n * Build the `date_time` tool — a small clock/calendar utility the agent\n * can call to read the current instant, shift an instant, measure the\n * gap between two instants, or render one. The `op` discriminator selects\n * the operation:\n *\n * - **`now`** — the current instant, rendered per `format` / `timeZone`\n * (defaults to a UTC ISO string). Reads the injectable {@link Clock}.\n * - **`add`** — `iso` shifted by `amount` of `unit` (e.g.\n * `+3 days`); a negative `amount` shifts backward.\n * - **`diff`** — the signed difference `to − iso`, expressed in `unit`.\n * - **`format`** — `iso` rendered per `format` / `timeZone`.\n *\n * Units are millisecond-based (`milliseconds`…`weeks`, plus common\n * aliases); calendar-relative `month`/`year` are intentionally\n * unsupported because they have no fixed length.\n *\n * **Deterministic in tests.** The clock backing `now` is injectable via\n * `deps.clock`; production defaults to `Date.now`.\n *\n * **Errors flow as data.** A missing/invalid field, an unknown unit, or\n * an unrecognized time zone throws a {@link DateTimeError} inside the\n * handler; `tool()` surfaces it in `{ error }` so the agent self-corrects.\n *\n * @param options - Optional overrides; `name` renames the tool,\n * `defaultTimeZone` applies when a call omits `timeZone`.\n * @param deps - Injectable dependencies (the {@link Clock}); defaults to real time.\n * @returns A `ToolContract<DateTimeInput, { value }>` ready for `tools: []`.\n *\n * @example\n * const clock = () => Date.parse(\"2026-06-22T00:00:00Z\");\n * const dt = dateTimeTool({}, { clock });\n * const { data } = await dt.invoke({ op: \"now\" });\n * console.log(data?.value); // \"2026-06-22T00:00:00.000Z\"\n */\nexport function dateTimeTool(\n options?: DateTimeOptions,\n deps?: DateTimeToolDeps,\n): ToolContract<DateTimeInput, DateTimeResult> {\n const clock: Clock = deps?.clock ?? Date.now;\n const defaultTimeZone = options?.defaultTimeZone;\n\n return tool<DateTimeInput, DateTimeResult>({\n name: options?.name ?? DEFAULT_NAME,\n description:\n \"Work with dates and times. Set op to: \\\"now\\\" (current instant), \" +\n '\"add\" (shift iso by amount of unit), \"diff\" (signed to − from in unit; ' +\n \"pass the start instant as iso or from), \" +\n 'or \"format\" (render iso). Units are milliseconds, seconds, minutes, ' +\n \"hours, days, or weeks (month/year are not supported). Pass instants as \" +\n 'ISO-8601 strings; set format to \"iso\" (default), \"date\", \"time\", or ' +\n '\"datetime\", and timeZone as an IANA zone like \"Africa/Cairo\".',\n input: inputSchema,\n async execute(input) {\n const timeZone = input.timeZone ?? defaultTimeZone;\n\n switch (input.op) {\n case \"now\": {\n const date = new Date(clock());\n\n return { value: render(date, input.format, timeZone) };\n }\n\n case \"format\": {\n const date = parseIso(input.iso, \"iso\");\n\n return { value: render(date, input.format, timeZone) };\n }\n\n case \"add\": {\n const date = parseIso(input.iso, \"iso\");\n\n if (input.amount === undefined) {\n throw new DateTimeError('\"amount\" is required for the \"add\" operation.', {\n type: \"invalid-input\",\n });\n }\n\n if (input.unit === undefined) {\n throw new DateTimeError('\"unit\" is required for the \"add\" operation.', {\n type: \"invalid-input\",\n });\n }\n\n const shifted = new Date(date.getTime() + input.amount * unitToMs(input.unit));\n\n return { value: render(shifted, input.format, timeZone) };\n }\n\n case \"diff\": {\n // Accept `from` as an alias for `iso` — models naturally pass\n // `from` / `to` for a difference. `iso` wins when both are set.\n const from = parseIso(input.iso ?? input.from, \"iso (or from)\");\n const to = parseIso(input.to, \"to\");\n\n if (input.unit === undefined) {\n throw new DateTimeError('\"unit\" is required for the \"diff\" operation.', {\n type: \"invalid-input\",\n });\n }\n\n const diff = (to.getTime() - from.getTime()) / unitToMs(input.unit);\n\n return { value: String(diff) };\n }\n\n default: {\n // Exhaustiveness guard — the schema's literal union should make\n // this unreachable, but a future op added to the union without a\n // case here surfaces as typed data rather than silent fallthrough.\n const unreachable: never = input.op;\n\n throw new DateTimeError(`Unsupported operation \"${String(unreachable)}\".`, {\n type: \"unsupported-op\",\n });\n }\n }\n },\n });\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- //#region ../@warlock.js/ai-tools/src/utility/schema.ts
1
+ //#region ../ai-tools/src/utility/schema.ts
2
2
  /**
3
3
  * Tiny, dependency-free [Standard Schema](https://standardschema.dev)
4
4
  * builders for the utility tools' input validation. This package pins
@@ -1 +1 @@
1
- {"version":3,"file":"schema.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai-tools/src/utility/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 utility tools' input validation. This package pins\n * only `@warlock.js/ai` as a runtime peer, so rather than pull in a\n * schema library we hand-roll the few field shapes the calculator and\n * date-time tools need — the same approach `@warlock.js/ai`'s own\n * `tool()` tests use. Each builder returns a {@link StandardSchemaV1},\n * which is what `tool({ input })` validates against before calling\n * `execute`.\n *\n * These intentionally cover only the primitive cases the UTILITY tools\n * require (`string`, `optional string`, `optional number`, and a\n * `literal union` enum). 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 */\ntype 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 — accepts `undefined` (the property absent or\n * explicitly undefined) or a string, and rejects every other type.\n */\nexport function optionalStringField(): FieldValidator<string | undefined> {\n return (value, key) => {\n if (value === undefined) {\n return { value: undefined };\n }\n\n if (typeof value === \"string\") {\n return { value };\n }\n\n return {\n issues: [{ message: `\"${key}\" must be a string when provided`, path: [key] }],\n };\n };\n}\n\n/**\n * Optional finite-number field — accepts `undefined` or a finite number,\n * rejecting `NaN`/`Infinity` and non-number types.\n */\nexport function optionalNumberField(): FieldValidator<number | undefined> {\n return (value, key) => {\n if (value === undefined) {\n return { value: undefined };\n }\n\n if (typeof value === \"number\" && Number.isFinite(value)) {\n return { value };\n }\n\n return {\n issues: [\n { message: `\"${key}\" must be a finite number when provided`, path: [key] },\n ],\n };\n };\n}\n\n/**\n * Required literal-union field — accepts only one of `allowed`, rejecting\n * everything else. Used for discriminators like the date-time `op`.\n *\n * @param allowed - The closed set of permitted string literals.\n */\nexport function literalField<T extends string>(\n allowed: readonly T[],\n): FieldValidator<T> {\n return (value, key) => {\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/** 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<{ expression: string }>({\n * expression: stringField(),\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 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 // Only carry through keys that resolved to a defined value, so\n // optional-absent fields stay absent rather than becoming\n // explicit `undefined` properties.\n if (outcome.value !== undefined) {\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":";;;;;;;;;;;;;;;;AAkBA,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;;;;;AAMA,SAAgB,sBAA0D;CACxE,QAAQ,OAAO,QAAQ;EACrB,IAAI,UAAU,QACZ,OAAO,EAAE,OAAO,OAAU;EAG5B,IAAI,OAAO,UAAU,UACnB,OAAO,EAAE,MAAM;EAGjB,OAAO,EACL,QAAQ,CAAC;GAAE,SAAS,IAAI,IAAI;GAAmC,MAAM,CAAC,GAAG;EAAE,CAAC,EAC9E;CACF;AACF;;;;;AAMA,SAAgB,sBAA0D;CACxE,QAAQ,OAAO,QAAQ;EACrB,IAAI,UAAU,QACZ,OAAO,EAAE,OAAO,OAAU;EAG5B,IAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,GACpD,OAAO,EAAE,MAAM;EAGjB,OAAO,EACL,QAAQ,CACN;GAAE,SAAS,IAAI,IAAI;GAA0C,MAAM,CAAC,GAAG;EAAE,CAC3E,EACF;CACF;AACF;;;;;;;AAQA,SAAgB,aACd,SACmB;CACnB,QAAQ,OAAO,QAAQ;EACrB,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;;;;;;;;;;;;;;;;;;AAwBA,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,UAAU,MAAM,OAAO,MAAgB,GAAa;IAE1D,IAAI,YAAY,SAAS;KACvB,OAAO,KAAK,GAAG,QAAQ,MAAM;KAE7B;IACF;IAKA,IAAI,QAAQ,UAAU,QACpB,OAAO,OAAiB,QAAQ;GAEpC;GAEA,IAAI,OAAO,SAAS,GAClB,OAAO,EAAE,OAAO;GAGlB,OAAO,EAAE,OAAO,OAAY;EAC9B;CACF,EACF;AACF"}
1
+ {"version":3,"file":"schema.mjs","names":[],"sources":["../../../../../../../ai-tools/src/utility/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 utility tools' input validation. This package pins\n * only `@warlock.js/ai` as a runtime peer, so rather than pull in a\n * schema library we hand-roll the few field shapes the calculator and\n * date-time tools need — the same approach `@warlock.js/ai`'s own\n * `tool()` tests use. Each builder returns a {@link StandardSchemaV1},\n * which is what `tool({ input })` validates against before calling\n * `execute`.\n *\n * These intentionally cover only the primitive cases the UTILITY tools\n * require (`string`, `optional string`, `optional number`, and a\n * `literal union` enum). 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 */\ntype 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 — accepts `undefined` (the property absent or\n * explicitly undefined) or a string, and rejects every other type.\n */\nexport function optionalStringField(): FieldValidator<string | undefined> {\n return (value, key) => {\n if (value === undefined) {\n return { value: undefined };\n }\n\n if (typeof value === \"string\") {\n return { value };\n }\n\n return {\n issues: [{ message: `\"${key}\" must be a string when provided`, path: [key] }],\n };\n };\n}\n\n/**\n * Optional finite-number field — accepts `undefined` or a finite number,\n * rejecting `NaN`/`Infinity` and non-number types.\n */\nexport function optionalNumberField(): FieldValidator<number | undefined> {\n return (value, key) => {\n if (value === undefined) {\n return { value: undefined };\n }\n\n if (typeof value === \"number\" && Number.isFinite(value)) {\n return { value };\n }\n\n return {\n issues: [\n { message: `\"${key}\" must be a finite number when provided`, path: [key] },\n ],\n };\n };\n}\n\n/**\n * Required literal-union field — accepts only one of `allowed`, rejecting\n * everything else. Used for discriminators like the date-time `op`.\n *\n * @param allowed - The closed set of permitted string literals.\n */\nexport function literalField<T extends string>(\n allowed: readonly T[],\n): FieldValidator<T> {\n return (value, key) => {\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/** 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<{ expression: string }>({\n * expression: stringField(),\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 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 // Only carry through keys that resolved to a defined value, so\n // optional-absent fields stay absent rather than becoming\n // explicit `undefined` properties.\n if (outcome.value !== undefined) {\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":";;;;;;;;;;;;;;;;AAkBA,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;;;;;AAMA,SAAgB,sBAA0D;CACxE,QAAQ,OAAO,QAAQ;EACrB,IAAI,UAAU,QACZ,OAAO,EAAE,OAAO,OAAU;EAG5B,IAAI,OAAO,UAAU,UACnB,OAAO,EAAE,MAAM;EAGjB,OAAO,EACL,QAAQ,CAAC;GAAE,SAAS,IAAI,IAAI;GAAmC,MAAM,CAAC,GAAG;EAAE,CAAC,EAC9E;CACF;AACF;;;;;AAMA,SAAgB,sBAA0D;CACxE,QAAQ,OAAO,QAAQ;EACrB,IAAI,UAAU,QACZ,OAAO,EAAE,OAAO,OAAU;EAG5B,IAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,GACpD,OAAO,EAAE,MAAM;EAGjB,OAAO,EACL,QAAQ,CACN;GAAE,SAAS,IAAI,IAAI;GAA0C,MAAM,CAAC,GAAG;EAAE,CAC3E,EACF;CACF;AACF;;;;;;;AAQA,SAAgB,aACd,SACmB;CACnB,QAAQ,OAAO,QAAQ;EACrB,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;;;;;;;;;;;;;;;;;;AAwBA,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,UAAU,MAAM,OAAO,MAAgB,GAAa;IAE1D,IAAI,YAAY,SAAS;KACvB,OAAO,KAAK,GAAG,QAAQ,MAAM;KAE7B;IACF;IAKA,IAAI,QAAQ,UAAU,QACpB,OAAO,OAAiB,QAAQ;GAEpC;GAEA,IAAI,OAAO,SAAS,GAClB,OAAO,EAAE,OAAO;GAGlB,OAAO,EAAE,OAAO,OAAY;EAC9B;CACF,EACF;AACF"}
@@ -1,7 +1,7 @@
1
1
  import { FetchUrlInput, FetchUrlOptions, FetchUrlResult } from "../contracts/web.type.mjs";
2
2
  import { ToolContract } from "@warlock.js/ai";
3
3
 
4
- //#region ../@warlock.js/ai-tools/src/web/fetch-url.d.ts
4
+ //#region ../ai-tools/src/web/fetch-url.d.ts
5
5
  /**
6
6
  * Build the agent-facing `fetch_url` tool — fetch a URL over the global
7
7
  * `fetch` (Node 18+) and hand the model back rendered `content`.
@@ -1 +1 @@
1
- {"version":3,"file":"fetch-url.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai-tools/src/web/fetch-url.ts"],"mappings":";;;;;;;AA6SA;;;;;;;;;;;;;;;AAE6C;;;;;;;;;;;;;iBAF7B,YAAA,CACd,OAAA,GAAU,eAAA,GACT,YAAA,CAAa,aAAA,EAAe,cAAA"}
1
+ {"version":3,"file":"fetch-url.d.mts","names":[],"sources":["../../../../../../../ai-tools/src/web/fetch-url.ts"],"mappings":";;;;;;;AA6SA;;;;;;;;;;;;;;;AAE6C;;;;;;;;;;;;;iBAF7B,YAAA,CACd,OAAA,GAAU,eAAA,GACT,YAAA,CAAa,aAAA,EAAe,cAAA"}
@@ -2,7 +2,7 @@ import { WebToolError } from "../errors.mjs";
2
2
  import { objectSchema, stringField } from "./schema.mjs";
3
3
  import { tool } from "@warlock.js/ai";
4
4
 
5
- //#region ../@warlock.js/ai-tools/src/web/fetch-url.ts
5
+ //#region ../ai-tools/src/web/fetch-url.ts
6
6
  /** Default tool name exposed to the LLM. */
7
7
  const DEFAULT_NAME = "fetch_url";
8
8
  /** Default hard cap on response bytes read before truncation. */