@dvmkit/sdk 0.0.0 → 0.1.0-rc.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 (41) hide show
  1. package/NOTICE +2 -0
  2. package/README.md +38 -2
  3. package/dist/chunk-27V2ILSR.js +291 -0
  4. package/dist/chunk-5GFED3GJ.js +955 -0
  5. package/dist/chunk-6JZIX5WW.js +1155 -0
  6. package/dist/chunk-7IH5SG2A.js +1038 -0
  7. package/dist/chunk-AT6V3SY7.js +102 -0
  8. package/dist/chunk-DCNT4PJS.js +733 -0
  9. package/dist/chunk-DMNLFNTW.js +135 -0
  10. package/dist/chunk-FROTD5XQ.js +70 -0
  11. package/dist/chunk-H25M54MI.js +149 -0
  12. package/dist/chunk-KQAJVVZT.js +712 -0
  13. package/dist/chunk-KXWROQGK.js +74 -0
  14. package/dist/chunk-L4OYF4DQ.js +67 -0
  15. package/dist/chunk-NTK5DJ6R.js +1256 -0
  16. package/dist/chunk-RPXHKMYE.js +3808 -0
  17. package/dist/chunk-S3XAHZQY.js +63 -0
  18. package/dist/chunk-YG7G4DPZ.js +25 -0
  19. package/dist/credit-ledger-EDMEZSA2.js +28 -0
  20. package/dist/index.d.ts +144 -0
  21. package/dist/index.js +303 -0
  22. package/dist/job-store-C5n6bhap.d.ts +5090 -0
  23. package/dist/memory-credit-ledger-7TTZDSRS.js +9 -0
  24. package/dist/mpp-secret-state-WNAQQ6K4.js +127 -0
  25. package/dist/mpp-setup-MOBWGTWJ.js +30 -0
  26. package/dist/postgres-consumed-credential-store-VHBT4KEA.js +72 -0
  27. package/dist/postgres-job-store-J5F4GUWU.js +7 -0
  28. package/dist/postgres-kv-store-JFBDP5IP.js +7 -0
  29. package/dist/postgres-replay-store-UJXRT6VO.js +7 -0
  30. package/dist/pricing-4CEB34RM.js +48 -0
  31. package/dist/processed-payment-store-HAA4SFNK.js +11 -0
  32. package/dist/revenue-reporter-M35KP6V7.js +435 -0
  33. package/dist/server/index.d.ts +4108 -0
  34. package/dist/server/index.js +22538 -0
  35. package/dist/ssrf-BdHsrrIb.d.ts +325 -0
  36. package/dist/tempo-charge-store-6GJEMNUU.js +130 -0
  37. package/dist/tempo-session-store-FTEEGZXA.js +467 -0
  38. package/dist/testing/index.d.ts +135 -0
  39. package/dist/testing/index.js +151 -0
  40. package/dist/x402-35VLYFKZ.js +1272 -0
  41. package/package.json +89 -6
@@ -0,0 +1,63 @@
1
+ // src/sdk/server/init-lock.ts
2
+ var SDK_INIT_ADVISORY_LOCK = 17320927;
3
+ async function withSdkInitLock(db, fn) {
4
+ assertPoolFitsLock(db);
5
+ let holder = holders.get(db);
6
+ if (holder) {
7
+ holder.refs += 1;
8
+ } else {
9
+ holder = { refs: 1, acquired: acquire(db) };
10
+ holders.set(db, holder);
11
+ }
12
+ let guardedFailed = false;
13
+ try {
14
+ await holder.acquired;
15
+ return await fn();
16
+ } catch (err) {
17
+ guardedFailed = true;
18
+ throw err;
19
+ } finally {
20
+ await releaseHolder(db, holder, guardedFailed);
21
+ }
22
+ }
23
+ var holders = /* @__PURE__ */ new WeakMap();
24
+ function assertPoolFitsLock(db) {
25
+ const max = db.options?.max;
26
+ if (max === void 0 || max >= 2) return;
27
+ throw new Error(
28
+ `withSdkInitLock: the SDK boot lock needs a pool that allows at least 2 connections (got max=${max}). The lock is held on one pinned client while the guarded DDL runs on another, so boot would hang forever.`
29
+ );
30
+ }
31
+ async function acquire(db) {
32
+ const client = await db.connect();
33
+ try {
34
+ await client.query(`SELECT pg_advisory_lock(${SDK_INIT_ADVISORY_LOCK})`);
35
+ } catch (err) {
36
+ client.release();
37
+ throw err;
38
+ }
39
+ return client;
40
+ }
41
+ async function releaseHolder(db, holder, guardedFailed) {
42
+ holder.refs -= 1;
43
+ if (holder.refs > 0) return;
44
+ holders.delete(db);
45
+ let client;
46
+ try {
47
+ client = await holder.acquired;
48
+ } catch {
49
+ return;
50
+ }
51
+ try {
52
+ await client.query(`SELECT pg_advisory_unlock(${SDK_INIT_ADVISORY_LOCK})`);
53
+ client.release();
54
+ } catch (err) {
55
+ client.release(err instanceof Error ? err : new Error(String(err)));
56
+ if (!guardedFailed) throw err;
57
+ }
58
+ }
59
+
60
+ export {
61
+ SDK_INIT_ADVISORY_LOCK,
62
+ withSdkInitLock
63
+ };
@@ -0,0 +1,25 @@
1
+ // src/sdk/server/store-json.ts
2
+ var BIGINT_TAG = "#__bigint";
3
+ var TAGGED_BIGINT = /^-?\d+#__bigint$/;
4
+ function stringifyStoreValue(value, what = "Store value") {
5
+ const encoded = JSON.stringify(
6
+ value,
7
+ (_key, item) => typeof item === "bigint" ? `${item.toString()}${BIGINT_TAG}` : item
8
+ );
9
+ if (typeof encoded !== "string") throw new Error(`${what} is not serializable`);
10
+ return encoded;
11
+ }
12
+ function parseStoreValue(value) {
13
+ const parsed = JSON.parse(value, (_key, item) => {
14
+ if (typeof item === "string" && TAGGED_BIGINT.test(item)) {
15
+ return BigInt(item.slice(0, -BIGINT_TAG.length));
16
+ }
17
+ return item;
18
+ });
19
+ return parsed;
20
+ }
21
+
22
+ export {
23
+ stringifyStoreValue,
24
+ parseStoreValue
25
+ };
@@ -0,0 +1,28 @@
1
+ import {
2
+ CreditLedger,
3
+ CreditLedgerError,
4
+ DRAIN_METHODS,
5
+ FUNDING_RAILS,
6
+ allocateDrawValue,
7
+ assertFundingBasis,
8
+ clipEarmark,
9
+ growthRailValue,
10
+ isDrainMethod,
11
+ isFundingRail,
12
+ x402SettlementPending
13
+ } from "./chunk-RPXHKMYE.js";
14
+ import "./chunk-H25M54MI.js";
15
+ import "./chunk-S3XAHZQY.js";
16
+ export {
17
+ CreditLedger,
18
+ CreditLedgerError,
19
+ DRAIN_METHODS,
20
+ FUNDING_RAILS,
21
+ allocateDrawValue,
22
+ assertFundingBasis,
23
+ clipEarmark,
24
+ growthRailValue,
25
+ isDrainMethod,
26
+ isFundingRail,
27
+ x402SettlementPending
28
+ };
@@ -0,0 +1,144 @@
1
+ import { C as Currency, Z as ZodLike, D as DVMConfig, a as DVMDescriptor } from './job-store-C5n6bhap.js';
2
+ export { A as ApprovalContent, b as ArtifactContent, c as CancelContent, d as CanonicalEnvelope, e as CreateSignedRequestVerifierOpts, f as CreditConfig, g as CreditView, h as DEFAULT_CREDIT_MAX, i as DEFAULT_CREDIT_MIN, j as DEFAULT_CREDIT_TTL_SECONDS, k as DVMRouteContext, I as IncomingMessage, l as InputType, m as InvalidCurrencyError, J as JobRecord, n as JobStatus, o as JobStore, K as KVStore, L as Logger, P as PaymentContent, p as PaymentMethod, q as PriceValue, r as ProgressContent, s as PromptOpts, Q as QuoteConfig, t as QuoteContext, u as QuoteResult, R as ResolvedCreditConfig, v as ResponseContent, S as SDKJobContext, w as SDKPaymentRequestOpts, x as SIGNED_REQUEST_AUTH_ID, y as SIGNED_REQUEST_STATEMENT_VERSION, z as SignedRequestAudience, B as SignedRequestDomain, E as SignedRequestError, F as SignedRequestFailure, G as SignedRequestReplayStore, H as SignedRequestSignOpts, M as SignedRequestStatementHeader, N as SignedRequestVerifier, U as UnsupportedCurrencyError, O as createSignedRequestVerifier, T as isZodSchema, V as signedRequestStatementHeader, W as validateCurrency } from './job-store-C5n6bhap.js';
3
+ export { C as CreateFxFetcherOpts, D as DEFAULT_FX_CURRENCIES, a as DEFAULT_FX_RATE_SOURCE, F as FxFetcher, b as FxRateSnapshot, c as FxRateUnavailableError, P as PinnedFetch, d as PlatformFxSource, S as SSRFError, e as SSRFGuardOpts, f as SSRFReason, g as SSRFResolver, h as assertSafeUrl, i as createFxFetcher, j as createPinnedFetch, k as fxRateFor, r as resolveFxSourceFromEnv } from './ssrf-BdHsrrIb.js';
4
+ export { z } from 'zod';
5
+ import '@cashu/cashu-ts';
6
+ import 'mppx';
7
+ import 'hono';
8
+ import '@x402/core/server';
9
+ import '@x402/evm/batch-settlement/server';
10
+ import 'pg';
11
+ import '@x402/core/types';
12
+
13
+ /**
14
+ * Protocol-enforced upfront fiat commitment. The hard invariant of the new
15
+ * `QuoteResult` shape — every DVM's quote handler returns one. The SDK
16
+ * derives sats from `amount × fx` at settlement time; for fixed-price DVMs
17
+ * this is the total cost, for two-phase DVMs (scribe) more may be requested
18
+ * mid-job via `ctx.requestPayment`.
19
+ */
20
+ interface Upfront {
21
+ amount: number;
22
+ currency: Currency;
23
+ }
24
+ /**
25
+ * Build a validated `Upfront` literal. Throws `InvalidCurrencyError` when
26
+ * `currency` doesn't match `/^[a-z]{3}$/`, and a plain `Error` when `amount`
27
+ * is non-finite or negative. Operators should call this at the boundary of
28
+ * their `onQuote` handler so a malformed envelope can't escape to the wire.
29
+ */
30
+ declare function buildUpfront(input: {
31
+ amount: number;
32
+ currency: string;
33
+ }): Upfront;
34
+
35
+ /**
36
+ * Thrown by `fiatToSatsCeil` / `satsToFiat` when the supplied `ratePerBtc` is
37
+ * non-finite or non-positive. Surfaces a misconfigured fx fetcher loudly
38
+ * instead of silently producing a 0-sats charge or a 0-fiat display string —
39
+ * a silent 0 lost real money in internal-review slice-04. Callers obtain a valid rate
40
+ * from the SDK's fx fetcher (which raises `FxRateUnavailableError` on
41
+ * upstream failure); this error is the defence-in-depth backstop for any
42
+ * caller that constructs a rate by other means.
43
+ */
44
+ declare class InvalidFxRateError extends Error {
45
+ readonly code = "invalid_fx_rate";
46
+ readonly ratePerBtc: unknown;
47
+ constructor(ratePerBtc: unknown);
48
+ }
49
+ /**
50
+ * Convert a fiat amount to integer sats at the supplied fiat-per-BTC rate.
51
+ * Rounds **up** so the operator never overcharges the caller (caller-friendly
52
+ * rounding). Negative or non-finite amounts collapse to 0 (a $0 charge is
53
+ * well-defined). A non-finite or non-positive `ratePerBtc` raises
54
+ * `InvalidFxRateError` — a silent 0 would mask a misconfigured fetcher into
55
+ * shipping a free charge. The math is currency-agnostic — `ratePerBtc` is the
56
+ * number of units of *some* fiat per 1 BTC; the caller looks the right rate
57
+ * out of an `FxRateSnapshot`.
58
+ */
59
+ declare function fiatToSatsCeil(amount: number, ratePerBtc: number): number;
60
+ /**
61
+ * Convert integer sats back to a fiat amount at the supplied fiat-per-BTC
62
+ * rate. A non-finite or non-positive `ratePerBtc` raises
63
+ * `InvalidFxRateError` — the silent `0` would land in display strings and
64
+ * mislead callers about the value at hand.
65
+ */
66
+ declare function satsToFiat(sats: number, ratePerBtc: number): number;
67
+ /**
68
+ * Round a USD value to 4 decimal places (matches scribe's stored display
69
+ * precision). This is the **storage / accounting** precision — preserves
70
+ * sub-cent amounts so a `$0.0150` per-minute rate doesn't collapse to
71
+ * `$0.02` before it ever reaches a multiplier. See {@link formatUsd} for the
72
+ * human-render precision, which deliberately truncates at the cent boundary
73
+ * for amounts ≥ $0.01.
74
+ */
75
+ declare function roundUsd(usd: number): number;
76
+ /**
77
+ * Format a USD amount with adaptive precision so sub-cent amounts don't
78
+ * render as `$0.00`. The 2dp/4dp boundary at $0.01 is **human-render**
79
+ * precision and is intentionally coarser than {@link roundUsd}'s storage
80
+ * precision: a value like `0.015` keeps four decimals through `roundUsd`
81
+ * (storage / multiplier inputs) but renders as `$0.02` here (human display).
82
+ * Don't try to unify the two — they serve different surfaces.
83
+ */
84
+ declare function formatUsd(usd: number): string;
85
+ /**
86
+ * Format a fiat amount with currency-aware symbol, precision, and ISO code.
87
+ * Used by the CLI to render payment-request `fiat_amount` envelopes ("$0.05
88
+ * USD", "€0.05 EUR", "¥5 JPY"). Precision is adaptive: JPY has no sub-yen
89
+ * denomination so renders as 0 decimals, every other currency uses 2 decimals
90
+ * at or above 0.01 and 4 below so sub-cent figures don't collapse to 0.00.
91
+ * Unknown currencies (anything not in {@link FIAT_SYMBOLS}) render with the
92
+ * uppercased ISO code and no leading symbol.
93
+ */
94
+ declare function formatFiat(amount: number, currency: Currency): string;
95
+
96
+ /** Create a typed DVM descriptor from a configuration object. */
97
+ declare function configureDVM<State = Record<string, unknown>, InputSchema extends ZodLike | undefined = undefined>(config: DVMConfig<State, InputSchema>): DVMDescriptor<State, InputSchema>;
98
+ /** Check whether this SDK module instance created a value through `configureDVM`. */
99
+ declare function isConfiguredDVMDescriptor(value: unknown): value is DVMDescriptor<unknown, ZodLike | undefined>;
100
+
101
+ /**
102
+ * Strict env-flag parser. Returns true only for the literal strings `"1"` or
103
+ * `"true"` — any other value (including `"false"`, `"0"`, `""`, or `undefined`)
104
+ * is false.
105
+ *
106
+ * Use this everywhere a boolean is read from `process.env` / `ctx.env`. Plain
107
+ * truthy checks like `!ctx.env.X` mistreat the string `"false"` as truthy and
108
+ * led to the upfront SDK gate disagreeing with handler-level `requestPayment`
109
+ * skips on the same env value.
110
+ */
111
+ declare function envFlag(value: string | undefined): boolean;
112
+
113
+ /** Options for `withProgressHeartbeat`. */
114
+ interface ProgressHeartbeatOpts {
115
+ /**
116
+ * Progress emitter — typically `ctx.progress.bind(ctx)` in a handler, or a
117
+ * plumbed-through callback in DVM internals. When omitted, the helper just
118
+ * runs `fn` with no overhead.
119
+ */
120
+ onProgress?: (percentComplete: number, phase: string) => void;
121
+ /** Phase label emitted with each heartbeat. */
122
+ phase: string;
123
+ /** Percent (0-100) emitted with each heartbeat. Stays constant across the run. */
124
+ percent: number;
125
+ /**
126
+ * Heartbeat cadence in milliseconds. Default 30s — twice the AC's
127
+ * "every minute" floor, so a single missed tick still satisfies the contract.
128
+ */
129
+ intervalMs?: number;
130
+ }
131
+ /**
132
+ * Wrap a long-running async op with a periodic progress heartbeat at fixed
133
+ * `phase` and `percent`. Emits one heartbeat synchronously on entry, then
134
+ * repeats every `intervalMs` until `fn` settles. Use to keep agents informed
135
+ * during slow single-call operations (e.g. Whisper transcribe, diariser
136
+ * polling) where the percent doesn't naturally advance.
137
+ *
138
+ * Phase transitions should still be emitted directly via `ctx.progress(...)`
139
+ * before/after the wrapped call — this helper only covers the "I'm alive,
140
+ * still in this phase" case.
141
+ */
142
+ declare function withProgressHeartbeat<T>(opts: ProgressHeartbeatOpts, fn: () => Promise<T>): Promise<T>;
143
+
144
+ export { Currency, DVMConfig, DVMDescriptor, InvalidFxRateError, type ProgressHeartbeatOpts, type Upfront, ZodLike, buildUpfront, configureDVM, envFlag, fiatToSatsCeil, formatFiat, formatUsd, isConfiguredDVMDescriptor, roundUsd, satsToFiat, withProgressHeartbeat };
package/dist/index.js ADDED
@@ -0,0 +1,303 @@
1
+ import {
2
+ DEFAULT_CREDIT_MAX,
3
+ DEFAULT_CREDIT_MIN,
4
+ DEFAULT_CREDIT_TTL_SECONDS,
5
+ SIGNED_REQUEST_AUTH_ID,
6
+ SIGNED_REQUEST_STATEMENT_VERSION,
7
+ SSRFError,
8
+ SignedRequestError,
9
+ assertSafeUrl,
10
+ createPinnedFetch,
11
+ createSignedRequestVerifier,
12
+ envFlag,
13
+ isZodSchema,
14
+ signedRequestStatementHeader
15
+ } from "./chunk-KQAJVVZT.js";
16
+ import {
17
+ DEFAULT_FX_CURRENCIES,
18
+ DEFAULT_FX_RATE_SOURCE,
19
+ FxRateUnavailableError,
20
+ InvalidCurrencyError,
21
+ UnsupportedCurrencyError,
22
+ buildUpfront,
23
+ createFxFetcher,
24
+ fxRateFor,
25
+ resolveFxSourceFromEnv,
26
+ validateCurrency
27
+ } from "./chunk-27V2ILSR.js";
28
+ import {
29
+ InvalidFxRateError,
30
+ fiatToSatsCeil,
31
+ formatFiat,
32
+ formatUsd,
33
+ parseUsdPrice,
34
+ roundUsd,
35
+ satsToFiat
36
+ } from "./chunk-AT6V3SY7.js";
37
+
38
+ // src/lib/wire-schema.ts
39
+ var CAPABILITY_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
40
+
41
+ // src/sdk/configure.ts
42
+ function configureDVM(config) {
43
+ const tags = collectTags(config);
44
+ const currency = resolvePricingCurrency(config.currency);
45
+ const capabilities = buildCapabilityMap(config, currency);
46
+ const onlyCapability = singleCapabilityEntry(capabilities);
47
+ const descriptor = {
48
+ name: config.name,
49
+ description: config.description ?? "",
50
+ tags,
51
+ currency,
52
+ price: onlyCapability?.price,
53
+ inputSchema: onlyCapability?.input,
54
+ state: onlyCapability?.state ?? {},
55
+ idleTimeout: config.idleTimeout ?? 3600,
56
+ processingWatchdog: config.processingWatchdog,
57
+ config,
58
+ capabilities,
59
+ paymentMethods: config.paymentMethods,
60
+ x402: config.x402,
61
+ mpp: config.mpp,
62
+ credit: resolveCreditConfig(config.credit),
63
+ auth: config.auth
64
+ };
65
+ CONFIGURED_DVM_DESCRIPTORS.add(descriptor);
66
+ return Object.freeze(descriptor);
67
+ }
68
+ function isConfiguredDVMDescriptor(value) {
69
+ return typeof value === "object" && value !== null && CONFIGURED_DVM_DESCRIPTORS.has(value);
70
+ }
71
+ var CONFIGURED_DVM_DESCRIPTORS = /* @__PURE__ */ new WeakSet();
72
+ function resolvePricingCurrency(raw) {
73
+ if (raw === void 0) return "usd";
74
+ try {
75
+ return validateCurrency(raw);
76
+ } catch (err) {
77
+ if (err instanceof InvalidCurrencyError) {
78
+ throw new Error(
79
+ `configureDVM: currency ${JSON.stringify(raw)} is not an ISO 4217 lowercase code like "usd" or "eur". Omit the field to price in USD.`,
80
+ { cause: err }
81
+ );
82
+ }
83
+ throw err;
84
+ }
85
+ }
86
+ function resolveCreditConfig(credit) {
87
+ if (!credit) return void 0;
88
+ const min = credit.min ?? DEFAULT_CREDIT_MIN;
89
+ const max = credit.max ?? DEFAULT_CREDIT_MAX;
90
+ const ttlSeconds = credit.ttl ?? DEFAULT_CREDIT_TTL_SECONDS;
91
+ const minAmount = validatePriceLiteral(min, "credit.min");
92
+ const maxAmount = validatePriceLiteral(max, "credit.max");
93
+ if (minAmount > maxAmount) {
94
+ throw new Error(
95
+ `credit.min (${JSON.stringify(min)}) exceeds credit.max (${JSON.stringify(max)}) \u2014 no funding amount could satisfy both.`
96
+ );
97
+ }
98
+ if (!Number.isFinite(ttlSeconds) || ttlSeconds <= 0) {
99
+ throw new Error(`credit.ttl must be a positive number of seconds (got ${ttlSeconds}).`);
100
+ }
101
+ const allowOneShotStablecoin = credit.allowOneShotStablecoin === true;
102
+ return { min, max, ttlSeconds, allowOneShotStablecoin };
103
+ }
104
+ function validatePriceLiteral(value, field, key = field) {
105
+ if (typeof value === "number") {
106
+ throw new Error(
107
+ `${field} is a number (${value}). Prices are USD literals \u2014 write ${key}: "$0.01" for one cent. Callers still pay in sats; the SDK converts at request time. Omit the field for a free capability, or use \`onQuote\` for dynamic pricing.`
108
+ );
109
+ }
110
+ if (typeof value !== "string") {
111
+ throw new Error(`${field} must be a USD literal like "$0.05" (got ${JSON.stringify(value)}).`);
112
+ }
113
+ return parseUsdPrice(value, field);
114
+ }
115
+ function validateCapabilityPrice(price, capabilityName, currency) {
116
+ if (price === void 0) return;
117
+ if (currency !== "usd") {
118
+ throw new Error(
119
+ `configureDVM: capability "${capabilityName}" declares a static price (${JSON.stringify(price)}) but this DVM prices in ${currency}. Static prices are USD literals, so they cannot express ${currency} \u2014 price this capability with \`onQuote\` returning { amount, currency: ${JSON.stringify(currency)} }, or drop the DVM-level \`currency\` to price in USD.`
120
+ );
121
+ }
122
+ validatePriceLiteral(price, `configureDVM: capability "${capabilityName}" price`, "price");
123
+ }
124
+ function collectTags(config) {
125
+ const tagSet = /* @__PURE__ */ new Set();
126
+ if (config.tag) tagSet.add(config.tag);
127
+ if (config.tags) {
128
+ for (const t of config.tags) tagSet.add(t);
129
+ }
130
+ return [...tagSet];
131
+ }
132
+ function buildCapabilityMap(config, currency) {
133
+ const hasExplicitBlock = config.capabilities !== void 0;
134
+ const hasFlatCapabilityName = typeof config.capability === "string";
135
+ const hasFlatHandler = typeof config.onJob === "function";
136
+ if (hasExplicitBlock && (hasFlatCapabilityName || hasFlatHandler || flatPerCapFieldsSet(config))) {
137
+ throw new Error(
138
+ "configureDVM: pass either the flat single-capability shape (capability + onJob + ...) or the explicit `capabilities` block, not both."
139
+ );
140
+ }
141
+ if (hasExplicitBlock) {
142
+ return buildExplicitCapabilities(config.capabilities ?? {}, currency);
143
+ }
144
+ if (!hasFlatHandler) {
145
+ throw new Error("configureDVM: onJob handler is required");
146
+ }
147
+ const name = typeof config.capability === "string" ? config.capability : slugifyCapabilityName(config.name);
148
+ validateCapabilityName(name);
149
+ validateCapabilityPrice(config.price, name, currency);
150
+ validateExampleAgainstInput(name, config.input, config.example);
151
+ const entry = {
152
+ name,
153
+ description: config.description ?? "",
154
+ input: config.input,
155
+ example: config.example,
156
+ state: config.state ?? {},
157
+ price: config.price,
158
+ onQuote: config.onQuote,
159
+ onJob: config.onJob,
160
+ onResponse: config.onResponse,
161
+ onPayment: config.onPayment,
162
+ onApproval: config.onApproval,
163
+ onCancel: config.onCancel,
164
+ onMessage: config.onMessage
165
+ };
166
+ return { [name]: entry };
167
+ }
168
+ function buildExplicitCapabilities(capabilities, currency) {
169
+ const keys = Object.keys(capabilities);
170
+ if (keys.length === 0) {
171
+ throw new Error("configureDVM: `capabilities` block must declare at least one capability");
172
+ }
173
+ const out = {};
174
+ for (const name of keys) {
175
+ validateCapabilityName(name);
176
+ const cap = capabilities[name];
177
+ if (typeof cap.onJob !== "function") {
178
+ throw new Error(`configureDVM: capability "${name}" is missing required onJob handler`);
179
+ }
180
+ validateCapabilityPrice(cap.price, name, currency);
181
+ validateExampleAgainstInput(name, cap.input, cap.example);
182
+ const entry = {
183
+ name,
184
+ description: cap.description ?? "",
185
+ // Per-capability `input` is the builder's Zod schema; the `any` widening
186
+ // through `Record<string, CapabilityConfig<any, any>>` is unavoidable
187
+ // here.
188
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
189
+ input: cap.input,
190
+ example: cap.example,
191
+ state: cap.state !== void 0 ? structuredClone(cap.state) : {},
192
+ price: cap.price,
193
+ onQuote: cap.onQuote,
194
+ onJob: cap.onJob,
195
+ onResponse: cap.onResponse,
196
+ onPayment: cap.onPayment,
197
+ onApproval: cap.onApproval,
198
+ onCancel: cap.onCancel,
199
+ onMessage: cap.onMessage
200
+ };
201
+ out[name] = entry;
202
+ }
203
+ return out;
204
+ }
205
+ function flatPerCapFieldsSet(config) {
206
+ return config.input !== void 0 || config.example !== void 0 || config.state !== void 0 || config.price !== void 0 || config.onQuote !== void 0 || typeof config.onResponse === "function" || typeof config.onPayment === "function" || typeof config.onApproval === "function" || typeof config.onCancel === "function" || typeof config.onMessage === "function";
207
+ }
208
+ function validateExampleAgainstInput(capName, input, example) {
209
+ if (example === void 0) return;
210
+ if (!isZodSchema(input)) return;
211
+ const result = input.safeParse(example);
212
+ if (result.success) return;
213
+ throw new Error(
214
+ `configureDVM: capability "${capName}" example does not satisfy its input schema. Update the example to match the schema (including any \`.refine()\` checks). Issues: ${formatZodIssues(result.error)}`
215
+ );
216
+ }
217
+ function formatZodIssues(err) {
218
+ if (err && typeof err === "object" && "issues" in err && Array.isArray(err.issues)) {
219
+ const issues = err.issues;
220
+ if (issues.length === 0) return "(no issues reported)";
221
+ return issues.map((issue) => {
222
+ const path = issue.path.length > 0 ? issue.path.join(".") : "(root)";
223
+ return `${path}: ${issue.message}`;
224
+ }).join("; ");
225
+ }
226
+ if (err && typeof err === "object" && "message" in err && typeof err.message === "string") {
227
+ return err.message.replace(/\s+/g, " ").trim();
228
+ }
229
+ try {
230
+ return JSON.stringify(err);
231
+ } catch {
232
+ return String(err);
233
+ }
234
+ }
235
+ function validateCapabilityName(name) {
236
+ if (!CAPABILITY_NAME_RE.test(name)) {
237
+ throw new Error(
238
+ `configureDVM: invalid capability name "${name}" \u2014 must be lowercase letters/digits/hyphens, must start with a letter or digit, must not contain slashes.`
239
+ );
240
+ }
241
+ }
242
+ function slugifyCapabilityName(name) {
243
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
244
+ }
245
+ function singleCapabilityEntry(capabilities) {
246
+ const keys = Object.keys(capabilities);
247
+ if (keys.length !== 1) return void 0;
248
+ return capabilities[keys[0]];
249
+ }
250
+
251
+ // src/sdk/heartbeat.ts
252
+ async function withProgressHeartbeat(opts, fn) {
253
+ const onProgress = opts.onProgress;
254
+ if (!onProgress) return fn();
255
+ const intervalMs = opts.intervalMs ?? 3e4;
256
+ onProgress(opts.percent, opts.phase);
257
+ const timer = setInterval(() => {
258
+ onProgress(opts.percent, opts.phase);
259
+ }, intervalMs);
260
+ try {
261
+ return await fn();
262
+ } finally {
263
+ clearInterval(timer);
264
+ }
265
+ }
266
+
267
+ // src/sdk/index.ts
268
+ import { z } from "zod";
269
+ export {
270
+ DEFAULT_CREDIT_MAX,
271
+ DEFAULT_CREDIT_MIN,
272
+ DEFAULT_CREDIT_TTL_SECONDS,
273
+ DEFAULT_FX_CURRENCIES,
274
+ DEFAULT_FX_RATE_SOURCE,
275
+ FxRateUnavailableError,
276
+ InvalidCurrencyError,
277
+ InvalidFxRateError,
278
+ SIGNED_REQUEST_AUTH_ID,
279
+ SIGNED_REQUEST_STATEMENT_VERSION,
280
+ SSRFError,
281
+ SignedRequestError,
282
+ UnsupportedCurrencyError,
283
+ assertSafeUrl,
284
+ buildUpfront,
285
+ configureDVM,
286
+ createFxFetcher,
287
+ createPinnedFetch,
288
+ createSignedRequestVerifier,
289
+ envFlag,
290
+ fiatToSatsCeil,
291
+ formatFiat,
292
+ formatUsd,
293
+ fxRateFor,
294
+ isConfiguredDVMDescriptor,
295
+ isZodSchema,
296
+ resolveFxSourceFromEnv,
297
+ roundUsd,
298
+ satsToFiat,
299
+ signedRequestStatementHeader,
300
+ validateCurrency,
301
+ withProgressHeartbeat,
302
+ z
303
+ };