@dvmkit/sdk 0.0.0 → 0.1.0-rc.2
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.
- package/NOTICE +2 -0
- package/README.md +38 -2
- package/dist/chunk-27V2ILSR.js +291 -0
- package/dist/chunk-365P52XQ.js +4121 -0
- package/dist/chunk-5GFED3GJ.js +955 -0
- package/dist/chunk-6JZIX5WW.js +1155 -0
- package/dist/chunk-7IH5SG2A.js +1038 -0
- package/dist/chunk-AT6V3SY7.js +102 -0
- package/dist/chunk-DCNT4PJS.js +733 -0
- package/dist/chunk-DMNLFNTW.js +135 -0
- package/dist/chunk-FROTD5XQ.js +70 -0
- package/dist/chunk-H25M54MI.js +149 -0
- package/dist/chunk-KQAJVVZT.js +712 -0
- package/dist/chunk-KXWROQGK.js +74 -0
- package/dist/chunk-L4OYF4DQ.js +67 -0
- package/dist/chunk-OJ5WFIB2.js +1266 -0
- package/dist/chunk-S3XAHZQY.js +63 -0
- package/dist/chunk-YG7G4DPZ.js +25 -0
- package/dist/credit-ledger-RO4FGSHG.js +28 -0
- package/dist/index.d.ts +144 -0
- package/dist/index.js +303 -0
- package/dist/job-store-6gR4pZRP.d.ts +5350 -0
- package/dist/memory-credit-ledger-I2G64DDK.js +9 -0
- package/dist/mpp-secret-state-WNAQQ6K4.js +127 -0
- package/dist/mpp-setup-MOBWGTWJ.js +30 -0
- package/dist/payout-reporter-4TNWRS5F.js +753 -0
- package/dist/postgres-consumed-credential-store-VHBT4KEA.js +72 -0
- package/dist/postgres-job-store-J5F4GUWU.js +7 -0
- package/dist/postgres-kv-store-JFBDP5IP.js +7 -0
- package/dist/postgres-replay-store-UJXRT6VO.js +7 -0
- package/dist/pricing-4CEB34RM.js +48 -0
- package/dist/processed-payment-store-HAA4SFNK.js +11 -0
- package/dist/revenue-reporter-GB4WKLDC.js +510 -0
- package/dist/server/index.d.ts +4168 -0
- package/dist/server/index.js +22716 -0
- package/dist/ssrf-DZi-xJyn.d.ts +325 -0
- package/dist/tempo-charge-store-6GJEMNUU.js +130 -0
- package/dist/tempo-session-store-FTEEGZXA.js +467 -0
- package/dist/testing/index.d.ts +135 -0
- package/dist/testing/index.js +151 -0
- package/dist/x402-35VLYFKZ.js +1272 -0
- 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-365P52XQ.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
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { C as Currency, Z as ZodLike, D as DVMConfig, a as DVMDescriptor } from './job-store-6gR4pZRP.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-6gR4pZRP.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-DZi-xJyn.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
|
+
};
|