@dvmkit/sdk 0.1.0-rc.2 → 0.1.0-rc.3

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 (42) hide show
  1. package/README.md +10 -2
  2. package/dist/{revenue-reporter-GB4WKLDC.js → chunk-2ABMGUDS.js} +1 -0
  3. package/dist/chunk-5SO7ZVOH.js +375 -0
  4. package/dist/chunk-66HGCPBU.js +25 -0
  5. package/dist/{chunk-KQAJVVZT.js → chunk-AZBXSXQT.js} +27 -287
  6. package/dist/chunk-C2DC4FKR.js +21424 -0
  7. package/dist/{chunk-H25M54MI.js → chunk-C3MTFLC6.js} +16 -0
  8. package/dist/{chunk-OJ5WFIB2.js → chunk-EXHBXA4U.js} +1 -1
  9. package/dist/chunk-FT7IM66W.js +1557 -0
  10. package/dist/{chunk-KXWROQGK.js → chunk-FUJ36YDV.js} +1 -24
  11. package/dist/{tempo-charge-store-6GJEMNUU.js → chunk-JZWELPFH.js} +1 -0
  12. package/dist/{chunk-365P52XQ.js → chunk-LWUR4CGG.js} +2 -1
  13. package/dist/chunk-RHP3BRTH.js +1090 -0
  14. package/dist/{tempo-session-store-FTEEGZXA.js → chunk-RU7SXHLO.js} +2 -1
  15. package/dist/{chunk-7IH5SG2A.js → chunk-TKA6ZP4M.js} +62 -41
  16. package/dist/chunk-TVI4V7GF.js +283 -0
  17. package/dist/{payout-reporter-4TNWRS5F.js → chunk-X3IKFWJA.js} +3 -2
  18. package/dist/{chunk-DCNT4PJS.js → chunk-XY5Y5REG.js} +6 -258
  19. package/dist/chunk-XYTSDAPH.js +232 -0
  20. package/dist/chunk-YD3TZNXV.js +1042 -0
  21. package/dist/{credit-ledger-RO4FGSHG.js → credit-ledger-ED6JXKVD.js} +2 -2
  22. package/dist/credit-menu-DONAtGVf.d.ts +5076 -0
  23. package/dist/{ssrf-DZi-xJyn.d.ts → fx-Bq4cvn16.d.ts} +37 -119
  24. package/dist/index.d.ts +8 -66
  25. package/dist/index.js +11 -219
  26. package/dist/internal/index.d.ts +5745 -0
  27. package/dist/internal/index.js +6379 -0
  28. package/dist/{job-store-6gR4pZRP.d.ts → job-store-m2pYmvbr.d.ts} +1493 -12
  29. package/dist/{memory-credit-ledger-I2G64DDK.js → memory-credit-ledger-XJ5VQEVP.js} +3 -3
  30. package/dist/payout-reporter-3UB5WRCV.js +13 -0
  31. package/dist/revenue-reporter-JIKUPXOK.js +7 -0
  32. package/dist/server/index.d.ts +14 -3590
  33. package/dist/server/index.js +309 -20796
  34. package/dist/ssrf-DbFkpDv0.d.ts +118 -0
  35. package/dist/tempo-charge-store-RIFTALZK.js +8 -0
  36. package/dist/tempo-session-store-DALMRIWN.js +11 -0
  37. package/dist/testing/index.d.ts +3 -2
  38. package/dist/testing/index.js +3 -2
  39. package/dist/usd-DjVAPMlf.d.ts +97 -0
  40. package/dist/x402-5EVIUSEP.js +81 -0
  41. package/package.json +6 -2
  42. package/dist/x402-35VLYFKZ.js +0 -1272
@@ -1,28 +1,13 @@
1
+ import {
2
+ DvmError
3
+ } from "./chunk-5SO7ZVOH.js";
1
4
  import {
2
5
  callerLoggers
3
- } from "./chunk-KXWROQGK.js";
6
+ } from "./chunk-66HGCPBU.js";
4
7
 
5
8
  // src/lib/x402/constants.ts
6
9
  var X402_DEFAULT_FACILITATOR = "https://x402.org/facilitator";
7
10
 
8
- // src/lib/errors.ts
9
- var DvmError = class extends Error {
10
- constructor(code, message, hint, data, display, proseSources) {
11
- super(message);
12
- this.code = code;
13
- this.hint = hint;
14
- this.data = data;
15
- this.display = display;
16
- this.proseSources = proseSources;
17
- this.name = "DvmError";
18
- }
19
- code;
20
- hint;
21
- data;
22
- display;
23
- proseSources;
24
- };
25
-
26
11
  // src/lib/x402/networks.ts
27
12
  var X402_NETWORKS = [
28
13
  {
@@ -459,231 +444,13 @@ function isOptionalRecord(value) {
459
444
  return value === void 0 || value === null || isRecord(value);
460
445
  }
461
446
 
462
- // src/lib/credit-implicit.ts
463
- var IMPLICIT_CREDIT_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
464
-
465
- // src/lib/currency.ts
466
- import { existsSync, readFileSync, writeFileSync } from "fs";
467
- import { dirname } from "path";
468
-
469
- // src/defaults.ts
470
- import { homedir } from "os";
471
- import { join } from "path";
472
- var CONFIG_DIR = process.env.DVM_CONFIG_DIR ?? join(homedir(), ".dvm");
473
- var CONFIG_FILE = join(CONFIG_DIR, "config.json");
474
- var CONFIG_BACKUP_FILE = join(CONFIG_DIR, "config.json.bak");
475
- var CONFIG_AUDIT_FILE = join(CONFIG_DIR, "config-audit.jsonl");
476
- var CONFIG_PINNED_FILE = join(CONFIG_DIR, "config.json.pinned");
477
- var JOBS_FILE = join(CONFIG_DIR, "jobs.json");
478
- var REQUEST_INTENTS_DIR = join(CONFIG_DIR, "request-intents");
479
- var FAVORITES_FILE = join(CONFIG_DIR, "favorites.json");
480
- var MESSAGES_DIR = join(CONFIG_DIR, "messages");
481
- var RECEIPTS_FILE = join(CONFIG_DIR, "receipts.jsonl");
482
- var CREDITS_FILE = join(CONFIG_DIR, "credits.json");
483
- var X402_CHANNELS_FILE = join(CONFIG_DIR, "x402-channels.json");
484
- var TEMPO_CHANNELS_FILE = join(CONFIG_DIR, "tempo-channels.json");
485
- var FEEDBACK_NUDGES_FILE = join(CONFIG_DIR, "feedback-nudges.json");
486
- var RECOVERY_MNEMONIC_FILE = join(CONFIG_DIR, "recovery.mnemonic");
487
- var WALLET_FILE = join(CONFIG_DIR, "wallet.json");
488
- var AGENT_MNEMONIC_FILE = join(CONFIG_DIR, "wallet.mnemonic");
489
- var IDENTITIES_FILE = join(CONFIG_DIR, "identities.json");
490
- var PAIRINGS_DIR = join(CONFIG_DIR, "pairings");
491
- var BUDGET_FILE = join(CONFIG_DIR, "budget.json");
492
- var BTC_RATE_FILE = join(CONFIG_DIR, "btc-rate.json");
493
- var ADMIN_DIR = join(CONFIG_DIR, "admin");
494
- var PROXY_STATE_FILE = join(ADMIN_DIR, "proxy.json");
495
- var BUILDER_CONFIG_DIR = process.env.DVMKIT_CONFIG_DIR ?? join(homedir(), ".dvmkit");
496
- var BUILDER_FILE = join(BUILDER_CONFIG_DIR, "builder.json");
497
- var BUILDER_MNEMONIC_FILE = join(BUILDER_CONFIG_DIR, "recovery.mnemonic");
498
- var BUILDER_KEY_FILE = join(BUILDER_CONFIG_DIR, "builder.key");
499
- var MELT_PENDING_LOG_FILE = join(BUILDER_CONFIG_DIR, "melt-pending.log");
500
- var DRAIN_PARK_LOG_FILE = join(BUILDER_CONFIG_DIR, "drain-park.log");
501
- var DRAIN_HOLD_STATE_FILE = join(BUILDER_CONFIG_DIR, "drain-hold.json");
502
- var MELT_SUCCESS_FILE = join(BUILDER_CONFIG_DIR, "melt-success.json");
503
-
504
- // src/lib/config-dir.ts
505
- import { chmodSync, mkdirSync, statSync } from "fs";
506
- var CONFIG_DIR_MODE = 448;
507
- function ensureConfigDir() {
508
- ensureConfigSubdir(CONFIG_DIR);
509
- }
510
- function ensureConfigSubdir(path) {
511
- mkdirSync(path, { recursive: true, mode: CONFIG_DIR_MODE });
512
- }
513
-
514
- // src/lib/fx-coingecko.ts
515
- var COINGECKO_BTC_USD_URL = "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd";
516
- var DEFAULT_TIMEOUT_MS = 5e3;
517
- var DEFAULT_USER_AGENT = "dvmkit-platform/1.0 (+https://dvmkit.com)";
518
- async function fetchCoinGeckoBtcUsd(opts) {
519
- const body = await fetchCoinGeckoBody(opts, COINGECKO_BTC_USD_URL);
520
- const btc = body.bitcoin?.usd;
521
- if (!isPositiveNumber(btc)) {
522
- throw new Error("coingecko response missing bitcoin.usd");
523
- }
524
- return { btc_usd: btc, as_of: (/* @__PURE__ */ new Date()).toISOString() };
525
- }
526
- async function fetchCoinGeckoBody(opts, defaultUrl) {
527
- const url = opts?.url ?? defaultUrl;
528
- const fetchImpl = opts?.fetchImpl ?? fetch;
529
- const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
530
- const userAgent = opts?.userAgent ?? DEFAULT_USER_AGENT;
531
- const res = await fetchImpl(url, {
532
- headers: { "User-Agent": userAgent, Accept: "application/json" },
533
- signal: AbortSignal.timeout(timeoutMs)
534
- });
535
- if (!res.ok) {
536
- throw new Error(`coingecko returned HTTP ${res.status.toString()}`);
537
- }
538
- return await res.json();
539
- }
540
- function isPositiveNumber(v) {
541
- return typeof v === "number" && Number.isFinite(v) && v > 0;
542
- }
543
-
544
- // src/lib/currency.ts
545
- function msatsToUsd(msats, rate) {
546
- return msats / 1e11 * rate;
547
- }
548
- function usdToMsats(usd, rate) {
549
- return Math.round(usd / rate * 1e11);
550
- }
551
- function msatsToUsdc(msats, btcUsdRate) {
552
- const usd = msatsToUsd(msats, btcUsdRate);
553
- return Math.ceil(usd * 1e6);
554
- }
555
- function usdcToMsats(usdc, btcUsdRate) {
556
- return usdToMsats(usdc, btcUsdRate);
557
- }
558
- var COST_ANCHOR_BANDS = {
559
- usd: [0.01, 0.1, 1, 10],
560
- eur: [0.01, 0.1, 1, 10],
561
- gbp: [0.01, 0.1, 1, 10],
562
- jpy: [1, 10, 100, 1500]
563
- };
564
- function costAnchor(amount, currency = "usd") {
565
- const bands = COST_ANCHOR_BANDS[currency] ?? COST_ANCHOR_BANDS.usd;
566
- if (amount < bands[0]) return "fraction of a cent";
567
- if (amount <= bands[1]) return "comparable to an API call";
568
- if (amount <= bands[2]) return "comparable to a ChatGPT query";
569
- if (amount <= bands[3]) return "comparable to a coffee";
570
- return "significant purchase \u2014 review carefully";
571
- }
572
- function formatUsd(usd) {
573
- if (usd === 0) return "0.00";
574
- if (usd >= 0.01) return usd.toFixed(2);
575
- const digits = Math.max(2, -Math.floor(Math.log10(usd)) + 1);
576
- return usd.toFixed(digits);
577
- }
578
- async function fetchBtcUsdRate() {
579
- if (memRate && Date.now() - memTime < CACHE_TTL) {
580
- return memRate;
581
- }
582
- const disk = readDiskCache();
583
- if (disk) {
584
- memRate = disk;
585
- memTime = Date.now();
586
- return disk;
587
- }
588
- const rate = await fetchFromApi();
589
- memRate = rate;
590
- memTime = Date.now();
591
- writeDiskCache(rate);
592
- return rate;
593
- }
594
- function getLastKnownBtcRate() {
595
- if (memRate && memTime > 0) return { rate: memRate, cachedAt: memTime };
596
- return readDiskCacheStale();
597
- }
598
- function formatStaleness(cachedAt, now = Date.now()) {
599
- const ageMs = Math.max(0, now - cachedAt);
600
- const seconds = Math.floor(ageMs / 1e3);
601
- if (seconds < 60) return `${seconds}s ago`;
602
- const minutes = Math.floor(seconds / 60);
603
- if (minutes < 60) return `${minutes} min ago`;
604
- const hours = Math.floor(minutes / 60);
605
- if (hours < 24) return `${hours} hour${hours === 1 ? "" : "s"} ago`;
606
- const days = Math.floor(hours / 24);
607
- return `${days} day${days === 1 ? "" : "s"} ago`;
608
- }
609
- var CACHE_TTL = 5 * 60 * 1e3;
610
- var CACHE_FILE = BTC_RATE_FILE;
611
- var memRate = null;
612
- var memTime = 0;
613
- function readDiskCache() {
614
- try {
615
- if (!existsSync(CACHE_FILE)) return null;
616
- const data = JSON.parse(readFileSync(CACHE_FILE, "utf-8"));
617
- if (Date.now() - data.timestamp >= CACHE_TTL) return null;
618
- if (!isPlausibleBtcUsdRate(data.rate)) return null;
619
- return data.rate;
620
- } catch {
621
- return null;
622
- }
623
- }
624
- function readDiskCacheStale() {
625
- try {
626
- if (!existsSync(CACHE_FILE)) return null;
627
- const data = JSON.parse(readFileSync(CACHE_FILE, "utf-8"));
628
- if (!isPlausibleBtcUsdRate(data.rate)) return null;
629
- return { rate: data.rate, cachedAt: data.timestamp };
630
- } catch {
631
- return null;
632
- }
633
- }
634
- function writeDiskCache(rate) {
635
- try {
636
- ensureConfigSubdir(dirname(CACHE_FILE));
637
- writeFileSync(CACHE_FILE, JSON.stringify({ rate, timestamp: Date.now() }));
638
- } catch {
639
- }
640
- }
641
- var MAX_PLAUSIBLE_BTC_USD = 1e7;
642
- function isPlausibleBtcUsdRate(rate) {
643
- return typeof rate === "number" && Number.isFinite(rate) && rate > 0 && rate < MAX_PLAUSIBLE_BTC_USD;
644
- }
645
- async function fetchFromApi() {
646
- const spot = await fetchCoinGeckoBtcUsd();
647
- const rate = spot.btc_usd;
648
- if (!isPlausibleBtcUsdRate(rate)) {
649
- throw new Error(`implausible BTC/USD rate from upstream: ${String(rate)}`);
650
- }
651
- return rate;
652
- }
653
-
654
- // src/lib/payment-rails/rpc.ts
655
- function resolveRpcOverride(envValue, bundled) {
656
- const override = envValue?.trim() ?? "";
657
- return override.length > 0 ? { url: override, source: "override" } : { url: bundled, source: "bundled" };
658
- }
659
- function resolveRpcHttpTransportOptions(source, envValue) {
660
- if (source !== "override" || !envValue?.trim()) return void 0;
661
- let parsed;
662
- try {
663
- parsed = JSON.parse(envValue);
664
- } catch {
665
- throw new Error("RPC headers must be a JSON object of non-empty string values.");
666
- }
667
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
668
- throw new Error("RPC headers must be a JSON object of non-empty string values.");
669
- const headers = {};
670
- for (const [name, value] of Object.entries(parsed)) {
671
- if (!/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(name) || typeof value !== "string" || value.length === 0 || /[\r\n]/.test(value))
672
- throw new Error("RPC headers must be a JSON object of non-empty string values.");
673
- headers[name] = value;
674
- }
675
- if (Object.keys(headers).length === 0)
676
- throw new Error("RPC headers must be a JSON object of non-empty string values.");
677
- return { fetchOptions: { headers } };
678
- }
679
-
680
447
  export {
681
448
  X402_DEFAULT_FACILITATOR,
682
449
  createX402FacilitatorAuthHeaders,
683
450
  createX402FacilitatorClient,
684
- DvmError,
685
451
  X402_NETWORKS,
686
452
  x402NetworkByCaip2,
453
+ x402NetworkBySlug,
687
454
  x402NetworkLabel,
688
455
  x402NetworkListForCopy,
689
456
  assertSupportedX402Network,
@@ -710,24 +477,5 @@ export {
710
477
  exactEvmAuthorization,
711
478
  encodeSettleResponseHeader,
712
479
  verifyWithFacilitator,
713
- settleWithFacilitator,
714
- IMPLICIT_CREDIT_TTL_MS,
715
- CONFIG_FILE,
716
- CONFIG_BACKUP_FILE,
717
- CONFIG_AUDIT_FILE,
718
- CONFIG_PINNED_FILE,
719
- X402_CHANNELS_FILE,
720
- WALLET_FILE,
721
- AGENT_MNEMONIC_FILE,
722
- ensureConfigDir,
723
- msatsToUsd,
724
- msatsToUsdc,
725
- usdcToMsats,
726
- costAnchor,
727
- formatUsd,
728
- fetchBtcUsdRate,
729
- getLastKnownBtcRate,
730
- formatStaleness,
731
- resolveRpcOverride,
732
- resolveRpcHttpTransportOptions
480
+ settleWithFacilitator
733
481
  };
@@ -0,0 +1,232 @@
1
+ import {
2
+ DEFAULT_CREDIT_MAX,
3
+ DEFAULT_CREDIT_MIN,
4
+ DEFAULT_CREDIT_TTL_SECONDS,
5
+ isZodSchema
6
+ } from "./chunk-AZBXSXQT.js";
7
+ import {
8
+ InvalidCurrencyError,
9
+ validateCurrency
10
+ } from "./chunk-27V2ILSR.js";
11
+ import {
12
+ parseUsdPrice
13
+ } from "./chunk-AT6V3SY7.js";
14
+
15
+ // src/lib/wire-schema.ts
16
+ var CAPABILITY_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
17
+
18
+ // src/sdk/configure.ts
19
+ function configureDVM(config) {
20
+ const tags = collectTags(config);
21
+ const currency = resolvePricingCurrency(config.currency);
22
+ const capabilities = buildCapabilityMap(config, currency);
23
+ const onlyCapability = singleCapabilityEntry(capabilities);
24
+ const descriptor = {
25
+ name: config.name,
26
+ description: config.description ?? "",
27
+ tags,
28
+ currency,
29
+ price: onlyCapability?.price,
30
+ inputSchema: onlyCapability?.input,
31
+ state: onlyCapability?.state ?? {},
32
+ idleTimeout: config.idleTimeout ?? 3600,
33
+ processingWatchdog: config.processingWatchdog,
34
+ config,
35
+ capabilities,
36
+ paymentMethods: config.paymentMethods,
37
+ x402: config.x402,
38
+ mpp: config.mpp,
39
+ credit: resolveCreditConfig(config.credit),
40
+ auth: config.auth
41
+ };
42
+ CONFIGURED_DVM_DESCRIPTORS.add(descriptor);
43
+ return Object.freeze(descriptor);
44
+ }
45
+ function isConfiguredDVMDescriptor(value) {
46
+ return typeof value === "object" && value !== null && CONFIGURED_DVM_DESCRIPTORS.has(value);
47
+ }
48
+ var CONFIGURED_DVM_DESCRIPTORS = /* @__PURE__ */ new WeakSet();
49
+ function resolvePricingCurrency(raw) {
50
+ if (raw === void 0) return "usd";
51
+ try {
52
+ return validateCurrency(raw);
53
+ } catch (err) {
54
+ if (err instanceof InvalidCurrencyError) {
55
+ throw new Error(
56
+ `configureDVM: currency ${JSON.stringify(raw)} is not an ISO 4217 lowercase code like "usd" or "eur". Omit the field to price in USD.`,
57
+ { cause: err }
58
+ );
59
+ }
60
+ throw err;
61
+ }
62
+ }
63
+ function resolveCreditConfig(credit) {
64
+ if (!credit) return void 0;
65
+ const min = credit.min ?? DEFAULT_CREDIT_MIN;
66
+ const max = credit.max ?? DEFAULT_CREDIT_MAX;
67
+ const ttlSeconds = credit.ttl ?? DEFAULT_CREDIT_TTL_SECONDS;
68
+ const minAmount = validatePriceLiteral(min, "credit.min");
69
+ const maxAmount = validatePriceLiteral(max, "credit.max");
70
+ if (minAmount > maxAmount) {
71
+ throw new Error(
72
+ `credit.min (${JSON.stringify(min)}) exceeds credit.max (${JSON.stringify(max)}) \u2014 no funding amount could satisfy both.`
73
+ );
74
+ }
75
+ if (!Number.isFinite(ttlSeconds) || ttlSeconds <= 0) {
76
+ throw new Error(`credit.ttl must be a positive number of seconds (got ${ttlSeconds}).`);
77
+ }
78
+ const allowOneShotStablecoin = credit.allowOneShotStablecoin === true;
79
+ return { min, max, ttlSeconds, allowOneShotStablecoin };
80
+ }
81
+ function validatePriceLiteral(value, field, key = field) {
82
+ if (typeof value === "number") {
83
+ throw new Error(
84
+ `${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.`
85
+ );
86
+ }
87
+ if (typeof value !== "string") {
88
+ throw new Error(`${field} must be a USD literal like "$0.05" (got ${JSON.stringify(value)}).`);
89
+ }
90
+ return parseUsdPrice(value, field);
91
+ }
92
+ function validateCapabilityPrice(price, capabilityName, currency) {
93
+ if (price === void 0) return;
94
+ if (currency !== "usd") {
95
+ throw new Error(
96
+ `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.`
97
+ );
98
+ }
99
+ validatePriceLiteral(price, `configureDVM: capability "${capabilityName}" price`, "price");
100
+ }
101
+ function collectTags(config) {
102
+ const tagSet = /* @__PURE__ */ new Set();
103
+ if (config.tag) tagSet.add(config.tag);
104
+ if (config.tags) {
105
+ for (const t of config.tags) tagSet.add(t);
106
+ }
107
+ return [...tagSet];
108
+ }
109
+ function buildCapabilityMap(config, currency) {
110
+ const hasExplicitBlock = config.capabilities !== void 0;
111
+ const hasFlatCapabilityName = typeof config.capability === "string";
112
+ const hasFlatHandler = typeof config.onJob === "function";
113
+ if (hasExplicitBlock && (hasFlatCapabilityName || hasFlatHandler || flatPerCapFieldsSet(config))) {
114
+ throw new Error(
115
+ "configureDVM: pass either the flat single-capability shape (capability + onJob + ...) or the explicit `capabilities` block, not both."
116
+ );
117
+ }
118
+ if (hasExplicitBlock) {
119
+ return buildExplicitCapabilities(config.capabilities ?? {}, currency);
120
+ }
121
+ if (!hasFlatHandler) {
122
+ throw new Error("configureDVM: onJob handler is required");
123
+ }
124
+ const name = typeof config.capability === "string" ? config.capability : slugifyCapabilityName(config.name);
125
+ validateCapabilityName(name);
126
+ validateCapabilityPrice(config.price, name, currency);
127
+ validateExampleAgainstInput(name, config.input, config.example);
128
+ const entry = {
129
+ name,
130
+ description: config.description ?? "",
131
+ input: config.input,
132
+ example: config.example,
133
+ state: config.state ?? {},
134
+ price: config.price,
135
+ onQuote: config.onQuote,
136
+ onJob: config.onJob,
137
+ onResponse: config.onResponse,
138
+ onPayment: config.onPayment,
139
+ onApproval: config.onApproval,
140
+ onCancel: config.onCancel,
141
+ onMessage: config.onMessage
142
+ };
143
+ return { [name]: entry };
144
+ }
145
+ function buildExplicitCapabilities(capabilities, currency) {
146
+ const keys = Object.keys(capabilities);
147
+ if (keys.length === 0) {
148
+ throw new Error("configureDVM: `capabilities` block must declare at least one capability");
149
+ }
150
+ const out = {};
151
+ for (const name of keys) {
152
+ validateCapabilityName(name);
153
+ const cap = capabilities[name];
154
+ if (typeof cap.onJob !== "function") {
155
+ throw new Error(`configureDVM: capability "${name}" is missing required onJob handler`);
156
+ }
157
+ validateCapabilityPrice(cap.price, name, currency);
158
+ validateExampleAgainstInput(name, cap.input, cap.example);
159
+ const entry = {
160
+ name,
161
+ description: cap.description ?? "",
162
+ // Per-capability `input` is the builder's Zod schema; the `any` widening
163
+ // through `Record<string, CapabilityConfig<any, any>>` is unavoidable
164
+ // here.
165
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
166
+ input: cap.input,
167
+ example: cap.example,
168
+ state: cap.state !== void 0 ? structuredClone(cap.state) : {},
169
+ price: cap.price,
170
+ onQuote: cap.onQuote,
171
+ onJob: cap.onJob,
172
+ onResponse: cap.onResponse,
173
+ onPayment: cap.onPayment,
174
+ onApproval: cap.onApproval,
175
+ onCancel: cap.onCancel,
176
+ onMessage: cap.onMessage
177
+ };
178
+ out[name] = entry;
179
+ }
180
+ return out;
181
+ }
182
+ function flatPerCapFieldsSet(config) {
183
+ 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";
184
+ }
185
+ function validateExampleAgainstInput(capName, input, example) {
186
+ if (example === void 0) return;
187
+ if (!isZodSchema(input)) return;
188
+ const result = input.safeParse(example);
189
+ if (result.success) return;
190
+ throw new Error(
191
+ `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)}`
192
+ );
193
+ }
194
+ function formatZodIssues(err) {
195
+ if (err && typeof err === "object" && "issues" in err && Array.isArray(err.issues)) {
196
+ const issues = err.issues;
197
+ if (issues.length === 0) return "(no issues reported)";
198
+ return issues.map((issue) => {
199
+ const path = issue.path.length > 0 ? issue.path.join(".") : "(root)";
200
+ return `${path}: ${issue.message}`;
201
+ }).join("; ");
202
+ }
203
+ if (err && typeof err === "object" && "message" in err && typeof err.message === "string") {
204
+ return err.message.replace(/\s+/g, " ").trim();
205
+ }
206
+ try {
207
+ return JSON.stringify(err);
208
+ } catch {
209
+ return String(err);
210
+ }
211
+ }
212
+ function validateCapabilityName(name) {
213
+ if (!CAPABILITY_NAME_RE.test(name)) {
214
+ throw new Error(
215
+ `configureDVM: invalid capability name "${name}" \u2014 must be lowercase letters/digits/hyphens, must start with a letter or digit, must not contain slashes.`
216
+ );
217
+ }
218
+ }
219
+ function slugifyCapabilityName(name) {
220
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
221
+ }
222
+ function singleCapabilityEntry(capabilities) {
223
+ const keys = Object.keys(capabilities);
224
+ if (keys.length !== 1) return void 0;
225
+ return capabilities[keys[0]];
226
+ }
227
+
228
+ export {
229
+ CAPABILITY_NAME_RE,
230
+ configureDVM,
231
+ isConfiguredDVMDescriptor
232
+ };