@sapiom/tools 0.16.0 → 0.17.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 (55) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +22 -0
  3. package/dist/cjs/_client/analytics.d.ts +66 -0
  4. package/dist/cjs/_client/analytics.d.ts.map +1 -0
  5. package/dist/cjs/_client/analytics.js +283 -0
  6. package/dist/cjs/_client/analytics.js.map +1 -0
  7. package/dist/cjs/_client/index.d.ts +28 -20
  8. package/dist/cjs/_client/index.d.ts.map +1 -1
  9. package/dist/cjs/_client/index.js +76 -13
  10. package/dist/cjs/_client/index.js.map +1 -1
  11. package/dist/cjs/_generated/version.d.ts +7 -0
  12. package/dist/cjs/_generated/version.d.ts.map +1 -0
  13. package/dist/cjs/_generated/version.js +10 -0
  14. package/dist/cjs/_generated/version.js.map +1 -0
  15. package/dist/cjs/agents/index.js +2 -2
  16. package/dist/cjs/agents/index.js.map +1 -1
  17. package/dist/cjs/client.d.ts +13 -0
  18. package/dist/cjs/client.d.ts.map +1 -1
  19. package/dist/cjs/client.js +1 -0
  20. package/dist/cjs/client.js.map +1 -1
  21. package/dist/cjs/schedules/index.d.ts +4 -2
  22. package/dist/cjs/schedules/index.d.ts.map +1 -1
  23. package/dist/cjs/schedules/index.js +11 -8
  24. package/dist/cjs/schedules/index.js.map +1 -1
  25. package/dist/cjs/stub/index.d.ts.map +1 -1
  26. package/dist/cjs/stub/index.js +3 -0
  27. package/dist/cjs/stub/index.js.map +1 -1
  28. package/dist/esm/_client/analytics.d.ts +66 -0
  29. package/dist/esm/_client/analytics.d.ts.map +1 -0
  30. package/dist/esm/_client/analytics.js +242 -0
  31. package/dist/esm/_client/analytics.js.map +1 -0
  32. package/dist/esm/_client/index.d.ts +28 -20
  33. package/dist/esm/_client/index.d.ts.map +1 -1
  34. package/dist/esm/_client/index.js +72 -9
  35. package/dist/esm/_client/index.js.map +1 -1
  36. package/dist/esm/_generated/version.d.ts +7 -0
  37. package/dist/esm/_generated/version.d.ts.map +1 -0
  38. package/dist/esm/_generated/version.js +7 -0
  39. package/dist/esm/_generated/version.js.map +1 -0
  40. package/dist/esm/agents/index.js +2 -2
  41. package/dist/esm/agents/index.js.map +1 -1
  42. package/dist/esm/client.d.ts +13 -0
  43. package/dist/esm/client.d.ts.map +1 -1
  44. package/dist/esm/client.js +1 -0
  45. package/dist/esm/client.js.map +1 -1
  46. package/dist/esm/schedules/index.d.ts +4 -2
  47. package/dist/esm/schedules/index.d.ts.map +1 -1
  48. package/dist/esm/schedules/index.js +11 -8
  49. package/dist/esm/schedules/index.js.map +1 -1
  50. package/dist/esm/stub/index.d.ts.map +1 -1
  51. package/dist/esm/stub/index.js +3 -0
  52. package/dist/esm/stub/index.js.map +1 -1
  53. package/dist/tsconfig.cjs.tsbuildinfo +1 -1
  54. package/dist/tsconfig.esm.tsbuildinfo +1 -1
  55. package/package.json +11 -7
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @sapiom/tools
2
2
 
3
+ ## 0.17.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 7fa17d1: Align agent run and schedule requests with the current API endpoints. This also fixes `@sapiom/tools` `schedules` operations (create/list/get/cancel), which were targeting an outdated endpoint. Public function signatures are unchanged.
8
+
9
+ ## 0.17.0
10
+
11
+ ### Minor Changes
12
+
13
+ - aee376a: Emit `capability.call` usage analytics from the capability transport via `@sapiom/analytics-core`.
14
+
15
+ Every capability HTTP call now enqueues one `capability.call` event at the transport choke point, carrying the capability path/name (the routed capability id, e.g. `web.scrape`, or the request path), the request URL path (query strings and fragments are stripped, never recorded), HTTP status, duration, request size, and the transport's attribution fields (agent, trace, metadata). Request and response bodies are never captured. The emitted `sdk_version` comes from a build-time constant generated from package.json, so it survives bundling.
16
+
17
+ Analytics ships dark: unless a collector endpoint is configured the emitter is a silent no-op — zero network calls, zero disk writes. Events are enqueued synchronously and delivered in background batches, so nothing is ever awaited, thrown, or slowed on the call path; capability behavior is byte-identical with telemetry on, off, or the collector unreachable. Opt out any time with `SAPIOM_TELEMETRY_DISABLED=1` or `DO_NOT_TRACK=1`.
18
+
19
+ Adds `Sapiom.shutdown(): Promise<void>` (additive): flushes buffered events and detaches the emitter's process exit hook. Call it once per client in hosts that construct many clients per process (e.g. an engine worker creating a per-execution client) so exit hooks don't accumulate; it's idempotent, never rejects, resolves immediately when there's nothing to release, and covers clients derived via `withAttribution` (the stub client implements it as an immediate resolve).
20
+
21
+ ### Patch Changes
22
+
23
+ - Updated dependencies [3f25008]
24
+ - Updated dependencies [55462b3]
25
+ - @sapiom/analytics-core@0.2.0
26
+
3
27
  ## 0.16.0
4
28
 
5
29
  ### Minor Changes
package/README.md CHANGED
@@ -105,6 +105,28 @@ const { downloadUrl } = await fileStorage.getDownloadUrl(
105
105
  );
106
106
  ```
107
107
 
108
+ ## Usage analytics
109
+
110
+ The SDK can emit anonymous usage analytics — one `capability.call` event per
111
+ capability request, carrying the capability name, the request URL path (query
112
+ strings are stripped, never recorded), HTTP status, duration, and request size
113
+ (never request or response bodies). Events describe calls to Sapiom's own API
114
+ only; nothing is captured about any third-party traffic.
115
+
116
+ By default nothing is sent anywhere: the emitter (see
117
+ [`@sapiom/analytics-core`](https://github.com/sapiom/sapiom-js/tree/main/packages/analytics-core))
118
+ is a no-op unless a collector endpoint is configured. Delivery is batched in the background and can
119
+ never throw, block, or slow a capability call. Opt out any time with
120
+ `SAPIOM_TELEMETRY_DISABLED=1` or `DO_NOT_TRACK=1`.
121
+
122
+ If your process constructs many clients over its lifetime (one per execution,
123
+ for example), call `await client.shutdown()` when you're done with each one: it
124
+ flushes any buffered events and detaches the emitter's process exit hook.
125
+ It's idempotent, never rejects, resolves immediately when there's nothing to
126
+ release, and covers every client derived via `withAttribution`. One-shot
127
+ scripts don't need it — events flush on process exit. Capability calls made
128
+ after shutdown still work; they just no longer emit analytics.
129
+
108
130
  ## License
109
131
 
110
132
  MIT
@@ -0,0 +1,66 @@
1
+ import { type SapiomAnalytics } from "@sapiom/analytics-core";
2
+ import type { Attribution } from "./index.js";
3
+ /** The one event this package emits: one per capability HTTP call. Dot-form is canonical. */
4
+ export declare const CAPABILITY_CALL_EVENT = "capability.call";
5
+ /**
6
+ * Lazily-created emitter shared by a Transport and every transport derived
7
+ * from it via `withAttribution` — one emitter (one batch queue, one exit
8
+ * hook) per client, no matter how many attributed views are derived.
9
+ */
10
+ export interface AnalyticsHolder {
11
+ instance?: SapiomAnalytics;
12
+ }
13
+ /** Get (creating on first use) the holder's emitter. Never throws — `createAnalytics` can't. */
14
+ export declare function analyticsFor(holder: AnalyticsHolder, apiKey: string | undefined): SapiomAnalytics;
15
+ /** Everything the Transport knows about one finished (or failed) capability HTTP call. */
16
+ export interface CapabilityCallInfo {
17
+ url: string;
18
+ /** `RequestInit.method`; absent means GET (fetch semantics). */
19
+ method: string | undefined;
20
+ /** `RequestInit.body` — only its SIZE is recorded, never its content. */
21
+ requestBody: unknown;
22
+ durationMs: number;
23
+ /** HTTP status; absent when the fetch itself threw (network-level failure). */
24
+ status?: number;
25
+ ok: boolean;
26
+ /** The thrown value when the fetch failed before producing a response. */
27
+ error?: unknown;
28
+ /** The transport's attribution — execution context, recorded as-is. */
29
+ attribution: Attribution;
30
+ }
31
+ /**
32
+ * Build the `capability.call` event `data`. All requests are Sapiom-bound, so
33
+ * request metadata (method, sizes, attribution) is captured; per-field size
34
+ * caps are enforced downstream by the emitter (~16 KB). The recorded `url` is
35
+ * the request URL PATH only — query strings and fragments are stripped, so a
36
+ * caller putting a secret in a query parameter can never leak it into an
37
+ * event. Request/response BODIES are never captured — only the request body size.
38
+ */
39
+ export declare function capabilityCallData(info: CapabilityCallInfo): Record<string, unknown>;
40
+ /**
41
+ * Derive the capability name from the request URL:
42
+ * - a routed call (`POST <core>/v1/capabilities/<id>`) → the capability id
43
+ * (`web.scrape`, `email.verify`, …);
44
+ * - anything else (service-gateway capabilities) → the URL path, which is the
45
+ * stable per-operation identifier on those hosts (`/v1/sandboxes`, …);
46
+ * - an unparseable URL → the raw string (capped downstream).
47
+ */
48
+ export declare function capabilityFromUrl(url: string): string;
49
+ /**
50
+ * The request URL with query string and fragment stripped (origin + pathname),
51
+ * consistent with {@link capabilityFromUrl}. Query parameters are deliberately
52
+ * never recorded: today's capabilities carry no secrets there, but a future or
53
+ * direct `transport.fetch(urlWithSecretQuery)` must not silently exfiltrate one.
54
+ */
55
+ export declare function urlWithoutQuery(url: string): string;
56
+ /**
57
+ * This package's own version. Primary source is the build-time constant
58
+ * generated from package.json (`src/_generated/version.ts`, regenerated by
59
+ * `gen:version` on every build/test/typecheck) — a constant survives bundlers,
60
+ * which inline it where no package.json exists on disk near the bundled
61
+ * module. The runtime package.json walk-up remains only as a fallback for the
62
+ * (should-be-impossible) case of an empty constant, and degrades to
63
+ * `"unknown"` rather than ever throwing.
64
+ */
65
+ export declare function resolveSdkVersion(): string;
66
+ //# sourceMappingURL=analytics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../../src/_client/analytics.ts"],"names":[],"mappings":"AAkBA,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,wBAAwB,CAAC;AAGhC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,6FAA6F;AAC7F,eAAO,MAAM,qBAAqB,oBAAoB,CAAC;AAEvD;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,eAAe,CAAC;CAC5B;AAED,gGAAgG;AAChG,wBAAgB,YAAY,CAC1B,MAAM,EAAE,eAAe,EACvB,MAAM,EAAE,MAAM,GAAG,SAAS,GACzB,eAAe,CASjB;AAMD,0FAA0F;AAC1F,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,gEAAgE;IAChE,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,yEAAyE;IACzE,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,OAAO,CAAC;IACZ,0EAA0E;IAC1E,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,uEAAuE;IACvE,WAAW,EAAE,WAAW,CAAC;CAC1B;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,kBAAkB,GACvB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAuBzB;AAKD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CASrD;AAUD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAQnD;AAmCD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAK1C"}
@@ -0,0 +1,283 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.CAPABILITY_CALL_EVENT = void 0;
37
+ exports.analyticsFor = analyticsFor;
38
+ exports.capabilityCallData = capabilityCallData;
39
+ exports.capabilityFromUrl = capabilityFromUrl;
40
+ exports.urlWithoutQuery = urlWithoutQuery;
41
+ exports.resolveSdkVersion = resolveSdkVersion;
42
+ /**
43
+ * Usage analytics for `@sapiom/tools` — the Transport (this package's single
44
+ * HTTP choke point) enqueues ONE `capability.call` event per capability HTTP
45
+ * call through `@sapiom/analytics-core`.
46
+ *
47
+ * Non-negotiables, guaranteed regardless of configuration:
48
+ * - Live by default: the emitter delivers to the hosted Sapiom collector unless
49
+ * opted out. Opt out with `SAPIOM_TELEMETRY_DISABLED=1`, `DO_NOT_TRACK=1`,
50
+ * or `disabled: true` in the config — any of these makes the emitter a
51
+ * complete no-op (zero network calls, zero disk writes, no notice).
52
+ * `SAPIOM_ANALYTICS_ENDPOINT` overrides the destination (useful in tests).
53
+ * - Enqueue-only on the call path: `track()` is synchronous, delivery is
54
+ * batched off the call path, and no failure here can surface to a caller.
55
+ */
56
+ const fs = __importStar(require("node:fs"));
57
+ const path = __importStar(require("node:path"));
58
+ const node_url_1 = require("node:url");
59
+ const analytics_core_1 = require("@sapiom/analytics-core");
60
+ const version_js_1 = require("../_generated/version.js");
61
+ /** The one event this package emits: one per capability HTTP call. Dot-form is canonical. */
62
+ exports.CAPABILITY_CALL_EVENT = "capability.call";
63
+ /** Get (creating on first use) the holder's emitter. Never throws — `createAnalytics` can't. */
64
+ function analyticsFor(holder, apiKey) {
65
+ return (holder.instance ?? (holder.instance = (0, analytics_core_1.createAnalytics)({
66
+ source: "tools",
67
+ sdkName: "@sapiom/tools",
68
+ sdkVersion: resolveSdkVersion(),
69
+ // Sent as `x-sapiom-api-key` on collector batches so the server can
70
+ // enrich events with the tenant — never placed in event payloads.
71
+ apiKey,
72
+ })));
73
+ }
74
+ /**
75
+ * Build the `capability.call` event `data`. All requests are Sapiom-bound, so
76
+ * request metadata (method, sizes, attribution) is captured; per-field size
77
+ * caps are enforced downstream by the emitter (~16 KB). The recorded `url` is
78
+ * the request URL PATH only — query strings and fragments are stripped, so a
79
+ * caller putting a secret in a query parameter can never leak it into an
80
+ * event. Request/response BODIES are never captured — only the request body size.
81
+ */
82
+ function capabilityCallData(info) {
83
+ const data = {
84
+ capability: capabilityFromUrl(info.url),
85
+ method: (info.method ?? "GET").toUpperCase(),
86
+ url: urlWithoutQuery(info.url),
87
+ ok: info.ok,
88
+ duration_ms: info.durationMs,
89
+ };
90
+ if (info.status !== undefined)
91
+ data.status = info.status;
92
+ const requestBytes = bodySize(info.requestBody);
93
+ if (requestBytes !== undefined)
94
+ data.request_bytes = requestBytes;
95
+ if (info.error !== undefined) {
96
+ data.error = errorLabel(info.error);
97
+ const message = errorMessage(info.error);
98
+ if (message !== undefined)
99
+ data.error_message = message;
100
+ }
101
+ const a = info.attribution;
102
+ if (a.agentId)
103
+ data.agent_id = a.agentId;
104
+ if (a.agentName)
105
+ data.agent_name = a.agentName;
106
+ if (a.traceId)
107
+ data.trace_id = a.traceId;
108
+ if (a.traceExternalId)
109
+ data.trace_external_id = a.traceExternalId;
110
+ if (a.metadata)
111
+ data.attribution_metadata = a.metadata;
112
+ return data;
113
+ }
114
+ /** Matches the routed-capability seam's path shape: `/v1/capabilities/<id>`. */
115
+ const ROUTED_CAPABILITY_PATH = /^\/v1\/capabilities\/([^/]+)\/?$/;
116
+ /**
117
+ * Derive the capability name from the request URL:
118
+ * - a routed call (`POST <core>/v1/capabilities/<id>`) → the capability id
119
+ * (`web.scrape`, `email.verify`, …);
120
+ * - anything else (service-gateway capabilities) → the URL path, which is the
121
+ * stable per-operation identifier on those hosts (`/v1/sandboxes`, …);
122
+ * - an unparseable URL → the raw string (capped downstream).
123
+ */
124
+ function capabilityFromUrl(url) {
125
+ try {
126
+ const parsed = new URL(url);
127
+ const routed = ROUTED_CAPABILITY_PATH.exec(parsed.pathname);
128
+ if (routed)
129
+ return safeDecode(routed[1]);
130
+ return parsed.pathname || url;
131
+ }
132
+ catch {
133
+ return url;
134
+ }
135
+ }
136
+ function safeDecode(segment) {
137
+ try {
138
+ return decodeURIComponent(segment);
139
+ }
140
+ catch {
141
+ return segment;
142
+ }
143
+ }
144
+ /**
145
+ * The request URL with query string and fragment stripped (origin + pathname),
146
+ * consistent with {@link capabilityFromUrl}. Query parameters are deliberately
147
+ * never recorded: today's capabilities carry no secrets there, but a future or
148
+ * direct `transport.fetch(urlWithSecretQuery)` must not silently exfiltrate one.
149
+ */
150
+ function urlWithoutQuery(url) {
151
+ try {
152
+ const parsed = new URL(url);
153
+ return `${parsed.origin}${parsed.pathname}`;
154
+ }
155
+ catch {
156
+ // Unparseable — strip conservatively at the first `?` or `#`.
157
+ return url.split(/[?#]/)[0] ?? url;
158
+ }
159
+ }
160
+ /** Size in bytes of a request body when cheaply knowable (string / Blob / buffer views). */
161
+ function bodySize(body) {
162
+ try {
163
+ if (typeof body === "string")
164
+ return Buffer.byteLength(body);
165
+ if (body !== null && typeof body === "object") {
166
+ const { size, byteLength } = body;
167
+ if (typeof size === "number")
168
+ return size;
169
+ if (typeof byteLength === "number")
170
+ return byteLength;
171
+ }
172
+ }
173
+ catch {
174
+ // Analytics never throws; an unknown body simply has no recorded size.
175
+ }
176
+ return undefined;
177
+ }
178
+ function errorLabel(error) {
179
+ if (error instanceof Error)
180
+ return error.name || "Error";
181
+ return typeof error;
182
+ }
183
+ function errorMessage(error) {
184
+ return error instanceof Error ? error.message : undefined;
185
+ }
186
+ // ---------------------------------------------------------------------------
187
+ // SDK version
188
+ // ---------------------------------------------------------------------------
189
+ let cachedSdkVersion = null;
190
+ /**
191
+ * This package's own version. Primary source is the build-time constant
192
+ * generated from package.json (`src/_generated/version.ts`, regenerated by
193
+ * `gen:version` on every build/test/typecheck) — a constant survives bundlers,
194
+ * which inline it where no package.json exists on disk near the bundled
195
+ * module. The runtime package.json walk-up remains only as a fallback for the
196
+ * (should-be-impossible) case of an empty constant, and degrades to
197
+ * `"unknown"` rather than ever throwing.
198
+ */
199
+ function resolveSdkVersion() {
200
+ return (cachedSdkVersion ?? (cachedSdkVersion = typeof version_js_1.VERSION === "string" && version_js_1.VERSION.length > 0
201
+ ? version_js_1.VERSION
202
+ : readOwnPackageVersion()));
203
+ }
204
+ function readOwnPackageVersion() {
205
+ try {
206
+ let dir = resolveModuleDir();
207
+ for (let hops = 0; dir !== ""; hops++) {
208
+ if (hops >= 8)
209
+ break;
210
+ const version = versionIfOwnPackage(path.join(dir, "package.json"));
211
+ if (version !== undefined)
212
+ return version;
213
+ const parent = path.dirname(dir);
214
+ if (parent === dir)
215
+ break;
216
+ dir = parent;
217
+ }
218
+ }
219
+ catch {
220
+ // Version resolution must never break a capability call.
221
+ }
222
+ return "unknown";
223
+ }
224
+ /** The `version` of the manifest at `manifestPath` iff it is this package's own. */
225
+ function versionIfOwnPackage(manifestPath) {
226
+ try {
227
+ const raw = fs.readFileSync(manifestPath, "utf8");
228
+ const manifest = JSON.parse(raw);
229
+ if (manifest.name === "@sapiom/tools" &&
230
+ typeof manifest.version === "string") {
231
+ return manifest.version;
232
+ }
233
+ }
234
+ catch {
235
+ // Missing or unparseable manifest at this level — keep walking up.
236
+ }
237
+ return undefined;
238
+ }
239
+ /**
240
+ * Directory of this compiled module, resolved for BOTH build formats:
241
+ * - CommonJS build (and ts-jest) → `__dirname` (a real global);
242
+ * - ESM build → recovered from a stack trace, where V8 reports this module
243
+ * as a `file://` URL.
244
+ *
245
+ * The stack route is used because a literal `import.meta` is a hard compile
246
+ * error (TS1343) in this single source file's CommonJS compilation, and
247
+ * `eval("import.meta.url")` throws at runtime — eval'd code is parsed as a
248
+ * script, where `import.meta` is invalid. A stack frame is the one anchor
249
+ * available to both formats; any failure degrades to `"unknown"`, never throws.
250
+ */
251
+ function resolveModuleDir() {
252
+ if (typeof __dirname !== "undefined")
253
+ return __dirname;
254
+ try {
255
+ const file = ownFileFromStack();
256
+ if (file !== undefined) {
257
+ return path.dirname(file.startsWith("file:") ? (0, node_url_1.fileURLToPath)(file) : file);
258
+ }
259
+ }
260
+ catch {
261
+ // No usable anchor — version resolution degrades to "unknown".
262
+ }
263
+ return "";
264
+ }
265
+ /**
266
+ * This module's own file (path or `file://` URL), read from the top-most
267
+ * user frame of a fresh stack — which is this function itself, i.e. this file.
268
+ */
269
+ function ownFileFromStack() {
270
+ const stack = new Error().stack;
271
+ if (typeof stack !== "string")
272
+ return undefined;
273
+ for (const line of stack.split("\n").slice(1)) {
274
+ // V8 frames: " at fn (file:///p/analytics.js:1:2)" / " at /p/analytics.js:1:2"
275
+ const match = /(?:\(|at )((?:file:\/\/)?[^\s()]+):\d+:\d+\)?\s*$/.exec(line);
276
+ const file = match?.[1];
277
+ if (!file || file.startsWith("node:") || file === "<anonymous>")
278
+ continue;
279
+ return file;
280
+ }
281
+ return undefined;
282
+ }
283
+ //# sourceMappingURL=analytics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analytics.js","sourceRoot":"","sources":["../../../src/_client/analytics.ts"],"names":[],"mappings":"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"}
@@ -1,23 +1,3 @@
1
- /**
2
- * Auth + transport core for `@sapiom/tools` — the private "runtime half" every
3
- * capability namespace wraps. Capability modules call THIS; they never import each
4
- * other's runtime, so the cross-capability mesh (`repo.pushFromSandbox(sandbox)`)
5
- * has no package/module cycles. See docs/plans/capability-authoring-sdk.md.
6
- *
7
- * The credential resolves from one of two sources, same surface either way:
8
- * - explicit: createClient({ apiKey }) — standalone / open-source use
9
- * - ambient: SAPIOM_API_KEY (env), which the workflow engine injects per
10
- * execution so step authors write zero auth plumbing.
11
- *
12
- * Attribution (which agent / trace a call belongs to) is execution context the
13
- * runtime owns, NOT a parameter of any operation — so it lives here on the
14
- * transport, set once and injected on every request, never threaded through
15
- * capability methods. The engine sets it per execution by constructing the
16
- * per-execution client; `withAttribution(...)` is a quiet escape hatch for the
17
- * router / standalone cases. Capability method signatures deliberately have no
18
- * attribution argument: that absence keeps LLM-authored step code from setting
19
- * (and getting wrong) context it doesn't own.
20
- */
21
1
  /**
22
2
  * Per-request attribution recorded with the gateway transaction. Every field is
23
3
  * optional and maps 1:1 to an `x-sapiom-*` header the collapsed flow understands.
@@ -85,6 +65,12 @@ export declare class Transport {
85
65
  * which injects `SAPIOM_CAPABILITY_RESUME_TOKEN` — keeps working unchanged.
86
66
  */
87
67
  readonly resumeToken: string | undefined;
68
+ /**
69
+ * Lazily-created usage-analytics emitter (see `./analytics.ts`). Not readonly:
70
+ * `withAttribution` re-points the derived transport at ITS holder so one client
71
+ * keeps one emitter no matter how many attributed views are derived from it.
72
+ */
73
+ private analyticsHolder;
88
74
  constructor(config?: TransportConfig);
89
75
  /**
90
76
  * A new transport sharing this one's credential, fetch, and resume token, with
@@ -93,6 +79,20 @@ export declare class Transport {
93
79
  * step-authoring code reaches for.
94
80
  */
95
81
  withAttribution(attribution: Attribution): Transport;
82
+ /**
83
+ * Flush and shut down this client's usage-analytics emitter: buffered events
84
+ * are delivered best-effort and the emitter's `beforeExit` hook is detached.
85
+ * Resolves immediately when no emitter was ever created (no calls made, or
86
+ * analytics disabled); idempotent; never rejects. One call covers every
87
+ * transport derived via {@link withAttribution} (they share the emitter).
88
+ *
89
+ * Call this once per client in hosts that construct MANY clients in one
90
+ * process — e.g. an engine worker building a per-execution client — so exit
91
+ * hooks don't accumulate across executions. One-shot processes don't need
92
+ * it: the emitter flushes on process exit by itself. Capability calls made
93
+ * after shutdown still work; they just no longer emit analytics.
94
+ */
95
+ shutdown(): Promise<void>;
96
96
  /**
97
97
  * Authenticated raw fetch — capabilities that need streaming or custom
98
98
  * response handling (filesystem, log streams) use this and inspect the
@@ -104,6 +104,14 @@ export declare class Transport {
104
104
  * instead (the value is identical — the Transport owns the key either way).
105
105
  */
106
106
  fetch(url: string, init?: RequestInit, options?: TransportRequestOptions): Promise<Response>;
107
+ /**
108
+ * Enqueue ONE `capability.call` usage event for a finished (or failed) HTTP
109
+ * call. Synchronous — nothing on the call path is awaited — and never throws;
110
+ * a silent no-op unless a collector endpoint is configured (see `./analytics.ts`).
111
+ * `request()` funnels through `fetch()`, so every capability call is counted
112
+ * exactly once.
113
+ */
114
+ private trackCapabilityCall;
107
115
  /** Authenticated JSON request — parses the body and throws on a non-2xx status. */
108
116
  request<T>(url: string, init?: RequestInit, options?: TransportRequestOptions): Promise<T>;
109
117
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/_client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,uFAAuF;IACvF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,eAAe;IAC9B,8FAA8F;IAC9F,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC,yFAAyF;IACzF,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG,kBAAkB,GAAG,WAAW,CAAC;AAI1D,0FAA0F;AAC1F,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAYD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,IAAI,WAAW,CAShD;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0B;IACpD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;gBAE7B,MAAM,GAAE,eAAoB;IAUxC;;;;;OAKG;IACH,eAAe,CAAC,WAAW,EAAE,WAAW,GAAG,SAAS;IASpD;;;;;;;;;OASG;IACG,KAAK,CACT,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,WAAgB,EACtB,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,QAAQ,CAAC;IAiBpB,mFAAmF;IAC7E,OAAO,CAAC,CAAC,EACb,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,WAAgB,EACtB,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,CAAC,CAAC;CAmBd;AAID,wBAAgB,gBAAgB,IAAI,SAAS,CAE5C;AAED,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,KAAK,qBAAqB,GAC3B,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/_client/index.ts"],"names":[],"mappings":"AA+BA;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,uFAAuF;IACvF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,eAAe;IAC9B,8FAA8F;IAC9F,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC,yFAAyF;IACzF,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG,kBAAkB,GAAG,WAAW,CAAC;AAI1D,0FAA0F;AAC1F,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAYD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,IAAI,WAAW,CAShD;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0B;IACpD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC;;;;OAIG;IACH,OAAO,CAAC,eAAe,CAAuB;gBAElC,MAAM,GAAE,eAAoB;IAUxC;;;;;OAKG;IACH,eAAe,CAAC,WAAW,EAAE,WAAW,GAAG,SAAS;IAWpD;;;;;;;;;;;;OAYG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAI/B;;;;;;;;;OASG;IACG,KAAK,CACT,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,WAAgB,EACtB,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,QAAQ,CAAC;IA0BpB;;;;;;OAMG;IACH,OAAO,CAAC,mBAAmB;IA0B3B,mFAAmF;IAC7E,OAAO,CAAC,CAAC,EACb,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,WAAgB,EACtB,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,CAAC,CAAC;CAmBd;AAID,wBAAgB,gBAAgB,IAAI,SAAS,CAE5C;AAED,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,KAAK,qBAAqB,GAC3B,MAAM,sBAAsB,CAAC"}
@@ -1,4 +1,8 @@
1
1
  "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveCoreBaseUrl = exports.capabilityCall = exports.Transport = void 0;
4
+ exports.attributionFromEnv = attributionFromEnv;
5
+ exports.defaultTransport = defaultTransport;
2
6
  /**
3
7
  * Auth + transport core for `@sapiom/tools` — the private "runtime half" every
4
8
  * capability namespace wraps. Capability modules call THIS; they never import each
@@ -18,11 +22,12 @@
18
22
  * router / standalone cases. Capability method signatures deliberately have no
19
23
  * attribution argument: that absence keeps LLM-authored step code from setting
20
24
  * (and getting wrong) context it doesn't own.
25
+ *
26
+ * Being the single HTTP choke point also makes this the (one) instrumentation
27
+ * seam: every call enqueues a `capability.call` usage event — synchronously,
28
+ * never awaited, live by default. See `./analytics.ts`.
21
29
  */
22
- Object.defineProperty(exports, "__esModule", { value: true });
23
- exports.resolveCoreBaseUrl = exports.capabilityCall = exports.Transport = void 0;
24
- exports.attributionFromEnv = attributionFromEnv;
25
- exports.defaultTransport = defaultTransport;
30
+ const analytics_js_1 = require("./analytics.js");
26
31
  const DEFAULT_AUTH_HEADER = "x-sapiom-api-key";
27
32
  function attributionToHeaders(a) {
28
33
  const h = {};
@@ -59,6 +64,12 @@ function attributionFromEnv() {
59
64
  }
60
65
  class Transport {
61
66
  constructor(config = {}) {
67
+ /**
68
+ * Lazily-created usage-analytics emitter (see `./analytics.ts`). Not readonly:
69
+ * `withAttribution` re-points the derived transport at ITS holder so one client
70
+ * keeps one emitter no matter how many attributed views are derived from it.
71
+ */
72
+ this.analyticsHolder = {};
62
73
  this.apiKey = config.apiKey ?? process.env.SAPIOM_API_KEY ?? undefined;
63
74
  this.fetchImpl = config.fetch ?? globalThis.fetch;
64
75
  this.attribution = config.attribution ?? {};
@@ -74,12 +85,30 @@ class Transport {
74
85
  * step-authoring code reaches for.
75
86
  */
76
87
  withAttribution(attribution) {
77
- return new Transport({
88
+ const derived = new Transport({
78
89
  apiKey: this.apiKey,
79
90
  fetch: this.fetchImpl,
80
91
  attribution: { ...this.attribution, ...attribution },
81
92
  resumeToken: this.resumeToken,
82
93
  });
94
+ derived.analyticsHolder = this.analyticsHolder;
95
+ return derived;
96
+ }
97
+ /**
98
+ * Flush and shut down this client's usage-analytics emitter: buffered events
99
+ * are delivered best-effort and the emitter's `beforeExit` hook is detached.
100
+ * Resolves immediately when no emitter was ever created (no calls made, or
101
+ * analytics disabled); idempotent; never rejects. One call covers every
102
+ * transport derived via {@link withAttribution} (they share the emitter).
103
+ *
104
+ * Call this once per client in hosts that construct MANY clients in one
105
+ * process — e.g. an engine worker building a per-execution client — so exit
106
+ * hooks don't accumulate across executions. One-shot processes don't need
107
+ * it: the emitter flushes on process exit by itself. Capability calls made
108
+ * after shutdown still work; they just no longer emit analytics.
109
+ */
110
+ async shutdown() {
111
+ await this.analyticsHolder.instance?.shutdown();
83
112
  }
84
113
  /**
85
114
  * Authenticated raw fetch — capabilities that need streaming or custom
@@ -96,14 +125,48 @@ class Transport {
96
125
  throw new Error("@sapiom/tools: no tenant credential. Pass createClient({ apiKey }) for standalone use, " +
97
126
  "or run inside a Sapiom workflow (the engine injects SAPIOM_API_KEY).");
98
127
  }
99
- return this.fetchImpl(url, {
100
- ...init,
101
- headers: {
102
- [options.authHeader ?? DEFAULT_AUTH_HEADER]: this.apiKey,
103
- ...attributionToHeaders(this.attribution),
104
- ...(init.headers ?? {}),
105
- },
106
- });
128
+ const startedAt = Date.now();
129
+ let response;
130
+ try {
131
+ response = await this.fetchImpl(url, {
132
+ ...init,
133
+ headers: {
134
+ [options.authHeader ?? DEFAULT_AUTH_HEADER]: this.apiKey,
135
+ ...attributionToHeaders(this.attribution),
136
+ ...(init.headers ?? {}),
137
+ },
138
+ });
139
+ }
140
+ catch (error) {
141
+ this.trackCapabilityCall(url, init, startedAt, undefined, error);
142
+ throw error;
143
+ }
144
+ this.trackCapabilityCall(url, init, startedAt, response);
145
+ return response;
146
+ }
147
+ /**
148
+ * Enqueue ONE `capability.call` usage event for a finished (or failed) HTTP
149
+ * call. Synchronous — nothing on the call path is awaited — and never throws;
150
+ * a silent no-op unless a collector endpoint is configured (see `./analytics.ts`).
151
+ * `request()` funnels through `fetch()`, so every capability call is counted
152
+ * exactly once.
153
+ */
154
+ trackCapabilityCall(url, init, startedAt, response, error) {
155
+ try {
156
+ (0, analytics_js_1.analyticsFor)(this.analyticsHolder, this.apiKey).track(analytics_js_1.CAPABILITY_CALL_EVENT, (0, analytics_js_1.capabilityCallData)({
157
+ url,
158
+ method: init.method,
159
+ requestBody: init.body,
160
+ durationMs: Date.now() - startedAt,
161
+ status: response?.status,
162
+ ok: response?.ok ?? false,
163
+ error,
164
+ attribution: this.attribution,
165
+ }));
166
+ }
167
+ catch {
168
+ // Usage analytics must never affect a capability call.
169
+ }
107
170
  }
108
171
  /** Authenticated JSON request — parses the body and throws on a non-2xx status. */
109
172
  async request(url, init = {}, options = {}) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/_client/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;GAmBG;;;AA0EH,gDASC;AAiGD,4CAEC;AAxID,MAAM,mBAAmB,GAAe,kBAAkB,CAAC;AAW3D,SAAS,oBAAoB,CAAC,CAAc;IAC1C,MAAM,CAAC,GAA2B,EAAE,CAAC;IACrC,IAAI,CAAC,CAAC,SAAS;QAAE,CAAC,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC;IACxD,IAAI,CAAC,CAAC,OAAO;QAAE,CAAC,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC;IAClD,IAAI,CAAC,CAAC,OAAO;QAAE,CAAC,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC;IAClD,IAAI,CAAC,CAAC,eAAe;QAAE,CAAC,CAAC,4BAA4B,CAAC,GAAG,CAAC,CAAC,eAAe,CAAC;IAC3E,IAAI,CAAC,CAAC,QAAQ;QAAE,CAAC,CAAC,mBAAmB,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;IACpE,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,kBAAkB;IAChC,MAAM,CAAC,GAAgB,EAAE,CAAC;IAC1B,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe;QAAE,CAAC,CAAC,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;IACzE,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB;QAC/B,CAAC,CAAC,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;IAC9C,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe;QAAE,CAAC,CAAC,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;IACzE,IAAI,OAAO,CAAC,GAAG,CAAC,wBAAwB;QACtC,CAAC,CAAC,eAAe,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC;IAC3D,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAa,SAAS;IAWpB,YAAY,SAA0B,EAAE;QACtC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,SAAS,CAAC;QACvE,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,KAAK,IAAI,UAAU,CAAC,KAAK,CAAC;QAClD,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC;QAC5C,IAAI,CAAC,WAAW;YACd,MAAM,CAAC,WAAW;gBAClB,OAAO,CAAC,GAAG,CAAC,8BAA8B;gBAC1C,SAAS,CAAC;IACd,CAAC;IAED;;;;;OAKG;IACH,eAAe,CAAC,WAAwB;QACtC,OAAO,IAAI,SAAS,CAAC;YACnB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,KAAK,EAAE,IAAI,CAAC,SAAS;YACrB,WAAW,EAAE,EAAE,GAAG,IAAI,CAAC,WAAW,EAAE,GAAG,WAAW,EAAE;YACpD,WAAW,EAAE,IAAI,CAAC,WAAW;SAC9B,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,KAAK,CACT,GAAW,EACX,OAAoB,EAAE,EACtB,UAAmC,EAAE;QAErC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CACb,yFAAyF;gBACvF,sEAAsE,CACzE,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE;YACzB,GAAG,IAAI;YACP,OAAO,EAAE;gBACP,CAAC,OAAO,CAAC,UAAU,IAAI,mBAAmB,CAAC,EAAE,IAAI,CAAC,MAAM;gBACxD,GAAG,oBAAoB,CAAC,IAAI,CAAC,WAAW,CAAC;gBACzC,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;aACxB;SACF,CAAC,CAAC;IACL,CAAC;IAED,mFAAmF;IACnF,KAAK,CAAC,OAAO,CACX,GAAW,EACX,OAAoB,EAAE,EACtB,UAAmC,EAAE;QAErC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAC1B,GAAG,EACH;YACE,GAAG,IAAI;YACP,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;aACxB;SACF,EACD,OAAO,CACR,CAAC;QACF,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,CAAC,MAAM,IAAI,KAAK,IAAI,GAAG,MAAM,GAAG,CAAC,MAAM,IAAI,MAAM,GAAG,CAAC,IAAI,EAAE,EAAE,CACrE,CAAC;QACJ,CAAC;QACD,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAM,CAAC;IACjC,CAAC;CACF;AA3FD,8BA2FC;AAED,qGAAqG;AACrG,IAAI,QAA+B,CAAC;AACpC,SAAgB,gBAAgB;IAC9B,OAAO,CAAC,QAAQ,KAAR,QAAQ,GAAK,IAAI,SAAS,CAAC,EAAE,WAAW,EAAE,kBAAkB,EAAE,EAAE,CAAC,EAAC,CAAC;AAC7E,CAAC;AAED,2DAI8B;AAH5B,oHAAA,cAAc,OAAA;AACd,wHAAA,kBAAkB,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/_client/index.ts"],"names":[],"mappings":"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"}