@absol-labs/agent 0.9.1 → 0.11.0
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/README.md +184 -26
- package/dist/capability/invocation-capability.d.ts +18 -6
- package/dist/capability/invocation-capability.d.ts.map +1 -1
- package/dist/capability/invocation-capability.js.map +1 -1
- package/dist/cli/amounts.d.ts +26 -0
- package/dist/cli/amounts.d.ts.map +1 -0
- package/dist/cli/amounts.js +71 -0
- package/dist/cli/amounts.js.map +1 -0
- package/dist/cli/args.d.ts +32 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +119 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/attesters.d.ts +51 -0
- package/dist/cli/attesters.d.ts.map +1 -0
- package/dist/cli/attesters.js +58 -0
- package/dist/cli/attesters.js.map +1 -0
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +5 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/commands/claim.d.ts +13 -0
- package/dist/cli/commands/claim.d.ts.map +1 -0
- package/dist/cli/commands/claim.js +102 -0
- package/dist/cli/commands/claim.js.map +1 -0
- package/dist/cli/commands/close.d.ts +3 -0
- package/dist/cli/commands/close.d.ts.map +1 -0
- package/dist/cli/commands/close.js +55 -0
- package/dist/cli/commands/close.js.map +1 -0
- package/dist/cli/commands/discover.d.ts +3 -0
- package/dist/cli/commands/discover.d.ts.map +1 -0
- package/dist/cli/commands/discover.js +183 -0
- package/dist/cli/commands/discover.js.map +1 -0
- package/dist/cli/commands/invoke.d.ts +3 -0
- package/dist/cli/commands/invoke.d.ts.map +1 -0
- package/dist/cli/commands/invoke.js +142 -0
- package/dist/cli/commands/invoke.js.map +1 -0
- package/dist/cli/commands/mcp.d.ts +3 -0
- package/dist/cli/commands/mcp.d.ts.map +1 -0
- package/dist/cli/commands/mcp.js +113 -0
- package/dist/cli/commands/mcp.js.map +1 -0
- package/dist/cli/commands/open.d.ts +3 -0
- package/dist/cli/commands/open.d.ts.map +1 -0
- package/dist/cli/commands/open.js +331 -0
- package/dist/cli/commands/open.js.map +1 -0
- package/dist/cli/commands/reclaim-unverified.d.ts +3 -0
- package/dist/cli/commands/reclaim-unverified.d.ts.map +1 -0
- package/dist/cli/commands/reclaim-unverified.js +172 -0
- package/dist/cli/commands/reclaim-unverified.js.map +1 -0
- package/dist/cli/commands/reclaim.d.ts +33 -0
- package/dist/cli/commands/reclaim.d.ts.map +1 -0
- package/dist/cli/commands/reclaim.js +308 -0
- package/dist/cli/commands/reclaim.js.map +1 -0
- package/dist/cli/commands/status.d.ts +3 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +43 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/streams.d.ts +3 -0
- package/dist/cli/commands/streams.d.ts.map +1 -0
- package/dist/cli/commands/streams.js +257 -0
- package/dist/cli/commands/streams.js.map +1 -0
- package/dist/cli/commands/watch.d.ts +3 -0
- package/dist/cli/commands/watch.d.ts.map +1 -0
- package/dist/cli/commands/watch.js +55 -0
- package/dist/cli/commands/watch.js.map +1 -0
- package/dist/cli/config.d.ts +61 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +140 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/context.d.ts +29 -0
- package/dist/cli/context.d.ts.map +1 -0
- package/dist/cli/context.js +2 -0
- package/dist/cli/context.js.map +1 -0
- package/dist/cli/deps.d.ts +191 -0
- package/dist/cli/deps.d.ts.map +1 -0
- package/dist/cli/deps.js +277 -0
- package/dist/cli/deps.js.map +1 -0
- package/dist/cli/descriptor.d.ts +61 -0
- package/dist/cli/descriptor.d.ts.map +1 -0
- package/dist/cli/descriptor.js +78 -0
- package/dist/cli/descriptor.js.map +1 -0
- package/dist/cli/errors.d.ts +48 -0
- package/dist/cli/errors.d.ts.map +1 -0
- package/dist/cli/errors.js +129 -0
- package/dist/cli/errors.js.map +1 -0
- package/dist/cli/escape-hatch.d.ts +50 -0
- package/dist/cli/escape-hatch.d.ts.map +1 -0
- package/dist/cli/escape-hatch.js +45 -0
- package/dist/cli/escape-hatch.js.map +1 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +7 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/listings.d.ts +15 -0
- package/dist/cli/listings.d.ts.map +1 -0
- package/dist/cli/listings.js +56 -0
- package/dist/cli/listings.js.map +1 -0
- package/dist/cli/mandate.d.ts +66 -0
- package/dist/cli/mandate.d.ts.map +1 -0
- package/dist/cli/mandate.js +61 -0
- package/dist/cli/mandate.js.map +1 -0
- package/dist/cli/output.d.ts +33 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +34 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/preflight.d.ts +38 -0
- package/dist/cli/preflight.d.ts.map +1 -0
- package/dist/cli/preflight.js +72 -0
- package/dist/cli/preflight.js.map +1 -0
- package/dist/cli/privy.d.ts +83 -0
- package/dist/cli/privy.d.ts.map +1 -0
- package/dist/cli/privy.js +230 -0
- package/dist/cli/privy.js.map +1 -0
- package/dist/cli/progress.d.ts +36 -0
- package/dist/cli/progress.d.ts.map +1 -0
- package/dist/cli/progress.js +40 -0
- package/dist/cli/progress.js.map +1 -0
- package/dist/cli/run.d.ts +25 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +173 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/scan-range.d.ts +55 -0
- package/dist/cli/scan-range.d.ts.map +1 -0
- package/dist/cli/scan-range.js +57 -0
- package/dist/cli/scan-range.js.map +1 -0
- package/dist/cli/spend-policy.d.ts +60 -0
- package/dist/cli/spend-policy.d.ts.map +1 -0
- package/dist/cli/spend-policy.js +65 -0
- package/dist/cli/spend-policy.js.map +1 -0
- package/dist/cli/status.d.ts +90 -0
- package/dist/cli/status.d.ts.map +1 -0
- package/dist/cli/status.js +252 -0
- package/dist/cli/status.js.map +1 -0
- package/dist/cli/timeout.d.ts +30 -0
- package/dist/cli/timeout.d.ts.map +1 -0
- package/dist/cli/timeout.js +55 -0
- package/dist/cli/timeout.js.map +1 -0
- package/dist/cli/version.d.ts +12 -0
- package/dist/cli/version.d.ts.map +1 -0
- package/dist/cli/version.js +22 -0
- package/dist/cli/version.js.map +1 -0
- package/dist/cli/wallet.d.ts +164 -0
- package/dist/cli/wallet.d.ts.map +1 -0
- package/dist/cli/wallet.js +234 -0
- package/dist/cli/wallet.js.map +1 -0
- package/dist/frameworks/crewai.d.ts +10 -0
- package/dist/frameworks/crewai.d.ts.map +1 -1
- package/dist/frameworks/crewai.js +12 -2
- package/dist/frameworks/crewai.js.map +1 -1
- package/dist/gateway/caller-auth-gateway.d.ts +38 -3
- package/dist/gateway/caller-auth-gateway.d.ts.map +1 -1
- package/dist/gateway/caller-auth-gateway.js +25 -1
- package/dist/gateway/caller-auth-gateway.js.map +1 -1
- package/dist/gateway/http-server.d.ts +53 -0
- package/dist/gateway/http-server.d.ts.map +1 -1
- package/dist/gateway/http-server.js +189 -4
- package/dist/gateway/http-server.js.map +1 -1
- package/dist/gateway/server-entry.d.ts +1 -0
- package/dist/gateway/server-entry.js +1 -0
- package/dist/gateway/server-entry.js.map +1 -1
- package/dist/gateway/x402-mode.d.ts +123 -0
- package/dist/gateway/x402-mode.d.ts.map +1 -0
- package/dist/gateway/x402-mode.js +212 -0
- package/dist/gateway/x402-mode.js.map +1 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -3
- package/dist/index.js.map +1 -1
- package/dist/mandates/env.d.ts +3 -2
- package/dist/mandates/env.d.ts.map +1 -1
- package/dist/mandates/env.js +3 -2
- package/dist/mandates/env.js.map +1 -1
- package/dist/mcp/http-server.d.ts +1 -0
- package/dist/mcp/http-server.js +1 -0
- package/dist/mcp/http-server.js.map +1 -1
- package/dist/mcp/server.d.ts +49 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +533 -25
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/service-invoker.d.ts +97 -0
- package/dist/mcp/service-invoker.d.ts.map +1 -0
- package/dist/mcp/service-invoker.js +167 -0
- package/dist/mcp/service-invoker.js.map +1 -0
- package/dist/mcp/stdio.d.ts +1 -0
- package/dist/mcp/stdio.js +1 -0
- package/dist/mcp/stdio.js.map +1 -1
- package/dist/sdk/client.d.ts +25 -1
- package/dist/sdk/client.d.ts.map +1 -1
- package/dist/sdk/client.js +20 -1
- package/dist/sdk/client.js.map +1 -1
- package/dist/sdk/invoke.d.ts +19 -4
- package/dist/sdk/invoke.d.ts.map +1 -1
- package/dist/sdk/invoke.js +69 -5
- package/dist/sdk/invoke.js.map +1 -1
- package/dist/x402/delivery-oracle.d.ts +108 -0
- package/dist/x402/delivery-oracle.d.ts.map +1 -0
- package/dist/x402/delivery-oracle.js +174 -0
- package/dist/x402/delivery-oracle.js.map +1 -0
- package/dist/x402/facilitator-server-entry.d.ts +3 -0
- package/dist/x402/facilitator-server-entry.d.ts.map +1 -0
- package/dist/x402/facilitator-server-entry.js +27 -0
- package/dist/x402/facilitator-server-entry.js.map +1 -0
- package/dist/x402/facilitator-server.d.ts +73 -0
- package/dist/x402/facilitator-server.d.ts.map +1 -0
- package/dist/x402/facilitator-server.js +305 -0
- package/dist/x402/facilitator-server.js.map +1 -0
- package/dist/x402/facilitator.d.ts +69 -17
- package/dist/x402/facilitator.d.ts.map +1 -1
- package/dist/x402/facilitator.js +76 -33
- package/dist/x402/facilitator.js.map +1 -1
- package/dist/x402/payment-requirements.d.ts +106 -0
- package/dist/x402/payment-requirements.d.ts.map +1 -0
- package/dist/x402/payment-requirements.js +138 -0
- package/dist/x402/payment-requirements.js.map +1 -0
- package/dist/x402/relayer.d.ts +41 -0
- package/dist/x402/relayer.d.ts.map +1 -0
- package/dist/x402/relayer.js +64 -0
- package/dist/x402/relayer.js.map +1 -0
- package/dist/x402/resource-index.d.ts +75 -0
- package/dist/x402/resource-index.d.ts.map +1 -0
- package/dist/x402/resource-index.js +126 -0
- package/dist/x402/resource-index.js.map +1 -0
- package/dist/x402/verified-delivery-facilitator.d.ts +285 -0
- package/dist/x402/verified-delivery-facilitator.d.ts.map +1 -0
- package/dist/x402/verified-delivery-facilitator.js +423 -0
- package/dist/x402/verified-delivery-facilitator.js.map +1 -0
- package/dist/zktls/t2-delivery-proof.d.ts +6 -6
- package/docs/agent-layer.md +14 -9
- package/docs/cli.md +705 -0
- package/docs/crewai.md +6 -2
- package/docs/eliza.md +6 -1
- package/docs/mcp-hosted.md +7 -5
- package/docs/mcp-reference.md +175 -0
- package/docs/quickstart.md +36 -9
- package/docs/threat-model.md +4 -3
- package/docs/x402.md +648 -0
- package/package.json +33 -10
- package/src/capability/invocation-capability.ts +17 -1
- package/src/cli/amounts.ts +102 -0
- package/src/cli/args.ts +150 -0
- package/src/cli/attesters.ts +86 -0
- package/src/cli/bin.ts +5 -0
- package/src/cli/commands/claim.ts +126 -0
- package/src/cli/commands/close.ts +69 -0
- package/src/cli/commands/discover.ts +228 -0
- package/src/cli/commands/invoke.ts +178 -0
- package/src/cli/commands/mcp.ts +135 -0
- package/src/cli/commands/open.ts +397 -0
- package/src/cli/commands/reclaim-unverified.ts +218 -0
- package/src/cli/commands/reclaim.ts +377 -0
- package/src/cli/commands/status.ts +56 -0
- package/src/cli/commands/streams.ts +344 -0
- package/src/cli/commands/watch.ts +70 -0
- package/src/cli/config.ts +208 -0
- package/src/cli/context.ts +30 -0
- package/src/cli/deps.ts +528 -0
- package/src/cli/descriptor.ts +133 -0
- package/src/cli/errors.ts +162 -0
- package/src/cli/escape-hatch.ts +92 -0
- package/src/cli/index.ts +27 -0
- package/src/cli/listings.ts +77 -0
- package/src/cli/mandate.ts +126 -0
- package/src/cli/output.ts +63 -0
- package/src/cli/preflight.ts +88 -0
- package/src/cli/privy.ts +396 -0
- package/src/cli/progress.ts +75 -0
- package/src/cli/run.ts +209 -0
- package/src/cli/scan-range.ts +76 -0
- package/src/cli/spend-policy.ts +139 -0
- package/src/cli/status.ts +381 -0
- package/src/cli/timeout.ts +64 -0
- package/src/cli/version.ts +21 -0
- package/src/cli/wallet.ts +372 -0
- package/src/frameworks/crewai.ts +13 -2
- package/src/gateway/caller-auth-gateway.ts +77 -6
- package/src/gateway/http-server.ts +280 -6
- package/src/gateway/server-entry.ts +1 -0
- package/src/gateway/x402-mode.ts +350 -0
- package/src/index.ts +40 -5
- package/src/mandates/env.ts +3 -2
- package/src/mcp/http-server.ts +1 -0
- package/src/mcp/server.ts +706 -30
- package/src/mcp/service-invoker.ts +285 -0
- package/src/mcp/stdio.ts +1 -0
- package/src/sdk/client.ts +33 -1
- package/src/sdk/invoke.ts +90 -12
- package/src/x402/delivery-oracle.ts +285 -0
- package/src/x402/facilitator-server-entry.ts +35 -0
- package/src/x402/facilitator-server.ts +445 -0
- package/src/x402/facilitator.ts +125 -46
- package/src/x402/payment-requirements.ts +231 -0
- package/src/x402/relayer.ts +122 -0
- package/src/x402/resource-index.ts +228 -0
- package/src/x402/verified-delivery-facilitator.ts +744 -0
package/src/cli/deps.ts
ADDED
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
import { streamEscrowV2Abi } from "@absol-labs/shared";
|
|
2
|
+
import {
|
|
3
|
+
MetrikClient,
|
|
4
|
+
type CheckpointProof,
|
|
5
|
+
type HireComputeInput,
|
|
6
|
+
type HireComputeResult,
|
|
7
|
+
type HireTxPreview,
|
|
8
|
+
type StreamV2,
|
|
9
|
+
type StreamProofTransactionResult,
|
|
10
|
+
} from "@absol-labs/sdk";
|
|
11
|
+
import { erc20Abi, getAbiItem, http, type Transport } from "viem";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
discoverServicesDetailed,
|
|
15
|
+
type DiscoverResult,
|
|
16
|
+
type DiscoverServicesOptions,
|
|
17
|
+
type ServiceListing,
|
|
18
|
+
} from "../discovery/registry.js";
|
|
19
|
+
import {
|
|
20
|
+
invoke as sdkInvoke,
|
|
21
|
+
type InvokeOptions,
|
|
22
|
+
type InvokeResult,
|
|
23
|
+
type InvokeServiceInput,
|
|
24
|
+
type InvokeStreamReader,
|
|
25
|
+
} from "../sdk/invoke.js";
|
|
26
|
+
import {
|
|
27
|
+
VerifiedStreamAgentClient,
|
|
28
|
+
type MandateAuthorizedStreamActionInput,
|
|
29
|
+
type OpenVerifiedStreamInput,
|
|
30
|
+
type ReclaimAuthorizedStreamInput,
|
|
31
|
+
type ReclaimVerifiedStreamResult,
|
|
32
|
+
} from "../sdk/client.js";
|
|
33
|
+
import type { SignedSpendMandate } from "../mandates/mandate.js";
|
|
34
|
+
|
|
35
|
+
import type { CliConfig } from "./config.js";
|
|
36
|
+
import type { CliSignerAccount } from "./wallet.js";
|
|
37
|
+
import { RPC_RETRY_COUNT, RPC_TIMEOUT_MS, withReadTimeout } from "./timeout.js";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Everything the CLI touches that is not pure computation, behind one
|
|
41
|
+
* injectable surface. Commands are written against these interfaces so the test
|
|
42
|
+
* suite exercises the real argument parsing, config resolution, testnet guard,
|
|
43
|
+
* retry logic and rendering against doubles — without a key, an RPC, or a
|
|
44
|
+
* single transaction.
|
|
45
|
+
*/
|
|
46
|
+
export interface CliReadClient {
|
|
47
|
+
/** The chain id the RPC endpoint actually serves (half of the testnet guard). */
|
|
48
|
+
getChainId(): Promise<number>;
|
|
49
|
+
getStreamV2(streamId: `0x${string}`): Promise<StreamV2>;
|
|
50
|
+
/**
|
|
51
|
+
* The ONLY live reading of accrual. `StreamEscrowV2.getStream` reports
|
|
52
|
+
* `claimedCumulative = 0` and `settledCumulative = 0` for a stream's entire
|
|
53
|
+
* active life — `settledCumulative` is written by `reclaim`, not by accrual —
|
|
54
|
+
* so a status command that reads a stream field would always print zero. The
|
|
55
|
+
* oracle's checkpoint proof carries the real earned-so-far figure.
|
|
56
|
+
*/
|
|
57
|
+
checkpointProof(streamId: `0x${string}`): Promise<CheckpointProof>;
|
|
58
|
+
claimableForV2(
|
|
59
|
+
streamId: `0x${string}`,
|
|
60
|
+
cumulativeAmount: bigint,
|
|
61
|
+
): Promise<bigint>;
|
|
62
|
+
reclaimableForV2(
|
|
63
|
+
streamId: `0x${string}`,
|
|
64
|
+
cumulativeAmount: bigint,
|
|
65
|
+
): Promise<bigint>;
|
|
66
|
+
/** Live protocol fee, read from the escrow rather than hardcoded. */
|
|
67
|
+
protocolFeeBps(): Promise<number>;
|
|
68
|
+
/**
|
|
69
|
+
* `StreamEscrowV2.escapeGraceSeconds` (public immutable). The buyer's no-proof
|
|
70
|
+
* escape hatch unlocks at `expiresAt + escapeGraceSeconds`, so this is half of
|
|
71
|
+
* every readiness answer the CLI gives about it.
|
|
72
|
+
*/
|
|
73
|
+
escapeGraceSeconds(): Promise<number>;
|
|
74
|
+
/**
|
|
75
|
+
* `StreamEscrowV2.latestCheckpointCheckedAt` — how far the oracle has
|
|
76
|
+
* published. A buyer reading "the last checkpoint is two hours old" is reading
|
|
77
|
+
* the reason their proof-based reclaim is blocked.
|
|
78
|
+
*/
|
|
79
|
+
latestCheckpointCheckedAt(): Promise<number>;
|
|
80
|
+
/**
|
|
81
|
+
* `StreamEscrowV2.getOracleSigners` — every address whose signature can carry
|
|
82
|
+
* a checkpoint into the escrow.
|
|
83
|
+
*
|
|
84
|
+
* Read so the CLI can say who attests. A buyer told a listing is
|
|
85
|
+
* "cryptographically verified" is entitled to know whether the operator they
|
|
86
|
+
* are about to pay is ALSO one of the parties attesting that the delivery
|
|
87
|
+
* happened; on the live deployment that overlap exists. Disclosure only — the
|
|
88
|
+
* CLI does not refuse such a listing, it names the fact.
|
|
89
|
+
*/
|
|
90
|
+
oracleSigners(): Promise<readonly `0x${string}`[]>;
|
|
91
|
+
/**
|
|
92
|
+
* The timestamp of the latest block. The escape hatch is gated on
|
|
93
|
+
* `block.timestamp`, not on the caller's clock, so readiness is answered
|
|
94
|
+
* against the chain rather than against a laptop that may be skewed.
|
|
95
|
+
*/
|
|
96
|
+
latestBlockTimestamp(): Promise<number>;
|
|
97
|
+
/**
|
|
98
|
+
* Native balance of an address, in wei. Read before a fund-moving command so
|
|
99
|
+
* an unfunded wallet is reported as an unfunded wallet, rather than as the
|
|
100
|
+
* raw node error `gas required exceeds allowance (0)`.
|
|
101
|
+
*/
|
|
102
|
+
nativeBalance(address: `0x${string}`): Promise<bigint>;
|
|
103
|
+
/**
|
|
104
|
+
* USDC balance of an address, in base units. Gas and deposit are SEPARATE
|
|
105
|
+
* balances, so `assertCanPayGas` passing says nothing about whether the hire
|
|
106
|
+
* can be funded — see `assertCanFundStream`.
|
|
107
|
+
*/
|
|
108
|
+
usdcBalance(address: `0x${string}`): Promise<bigint>;
|
|
109
|
+
/** Current chain head, the upper bound of every log scan. */
|
|
110
|
+
blockNumber(): Promise<bigint>;
|
|
111
|
+
/**
|
|
112
|
+
* `StreamOpened` logs for one indexed party over one inclusive block window.
|
|
113
|
+
*
|
|
114
|
+
* `buyer` and `operator` are INDEXED on the event, so `role` becomes an
|
|
115
|
+
* `eth_getLogs` topic filter the NODE evaluates: the response carries only
|
|
116
|
+
* that wallet's streams regardless of how wide the window is. What bounds a
|
|
117
|
+
* window is the endpoint's per-call BLOCK RANGE cap, not the response size —
|
|
118
|
+
* see `./scan-range.ts`.
|
|
119
|
+
*/
|
|
120
|
+
streamOpenedLogs(query: StreamOpenedQuery): Promise<readonly OpenedStream[]>;
|
|
121
|
+
/**
|
|
122
|
+
* Count of ANY escrow log in a single block. Used as a known-answer retention
|
|
123
|
+
* canary: a node that has pruned its log index answers a range it no longer
|
|
124
|
+
* retains with a SUCCESSFUL EMPTY result, so an empty scan is only honest
|
|
125
|
+
* from an endpoint that demonstrably still serves the oldest block in range.
|
|
126
|
+
*/
|
|
127
|
+
escrowLogCountAt(block: bigint): Promise<number>;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** One indexed party filter over one inclusive block window. */
|
|
131
|
+
export interface StreamOpenedQuery {
|
|
132
|
+
readonly role: "buyer" | "operator";
|
|
133
|
+
readonly address: `0x${string}`;
|
|
134
|
+
readonly fromBlock: bigint;
|
|
135
|
+
readonly toBlock: bigint;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** The immutable facts `StreamOpened` carries, which later state cannot restate. */
|
|
139
|
+
export interface OpenedStream {
|
|
140
|
+
readonly streamId: `0x${string}`;
|
|
141
|
+
readonly buyer: `0x${string}`;
|
|
142
|
+
readonly operator: `0x${string}`;
|
|
143
|
+
readonly serviceRef: `0x${string}`;
|
|
144
|
+
/**
|
|
145
|
+
* The amount originally escrowed. V2 MUTATES `Stream.deposit` on reclaim, so
|
|
146
|
+
* the event is the only place the opening figure survives.
|
|
147
|
+
*/
|
|
148
|
+
readonly deposit: bigint;
|
|
149
|
+
readonly ratePerSecond: bigint;
|
|
150
|
+
readonly expiresAt: number;
|
|
151
|
+
readonly blockNumber: bigint;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* A fund-moving call, with the mandate made EXPLICITLY optional.
|
|
156
|
+
*
|
|
157
|
+
* `null` is not "no authorization" — it is "this wallet's ceiling is enforced
|
|
158
|
+
* by a broker policy the client cannot reach or raise, so a locally self-signed
|
|
159
|
+
* mandate would be the agent attesting to its own limits on top of a stronger
|
|
160
|
+
* limit". The CLI substitutes the real ceiling instead; see
|
|
161
|
+
* `./spend-policy.ts`, which also explains why re-adding the mandate here would
|
|
162
|
+
* be a regression rather than a tidy-up.
|
|
163
|
+
*
|
|
164
|
+
* A key-holding wallet (private key, CDP) always passes a real mandate, and its
|
|
165
|
+
* path through `VerifiedStreamAgentClient` is unchanged.
|
|
166
|
+
*/
|
|
167
|
+
type MandateOptional<T extends { readonly signedMandate: SignedSpendMandate }> =
|
|
168
|
+
Omit<T, "signedMandate"> & {
|
|
169
|
+
readonly signedMandate: SignedSpendMandate | null;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export type CliOpenStreamInput = MandateOptional<OpenVerifiedStreamInput>;
|
|
173
|
+
export type CliStreamActionInput =
|
|
174
|
+
MandateOptional<MandateAuthorizedStreamActionInput>;
|
|
175
|
+
export type CliReclaimStreamInput =
|
|
176
|
+
MandateOptional<ReclaimAuthorizedStreamInput>;
|
|
177
|
+
|
|
178
|
+
export interface CliWriteClient {
|
|
179
|
+
/** Build + best-effort simulate the hire WITHOUT broadcasting (`--dry-run`). */
|
|
180
|
+
buildHireTx(input: HireComputeInput): Promise<HireTxPreview>;
|
|
181
|
+
openVerifiedStream(input: CliOpenStreamInput): Promise<HireComputeResult>;
|
|
182
|
+
closeStream(
|
|
183
|
+
input: CliStreamActionInput,
|
|
184
|
+
): Promise<StreamProofTransactionResult>;
|
|
185
|
+
reclaimStream(
|
|
186
|
+
input: CliReclaimStreamInput,
|
|
187
|
+
): Promise<ReclaimVerifiedStreamResult>;
|
|
188
|
+
/**
|
|
189
|
+
* The no-proof escape hatch. Separate from {@link reclaimStream} because it is
|
|
190
|
+
* a different transaction with a different outcome, not a mode of the same one
|
|
191
|
+
* — see `VerifiedStreamAgentClient.reclaimUnverifiedStream`.
|
|
192
|
+
*/
|
|
193
|
+
reclaimUnverifiedStream(
|
|
194
|
+
input: CliStreamActionInput,
|
|
195
|
+
): Promise<StreamProofTransactionResult>;
|
|
196
|
+
/** Operator-only: redeem the verified-earned balance for a stream. */
|
|
197
|
+
claimStream(
|
|
198
|
+
input: CliStreamActionInput,
|
|
199
|
+
): Promise<StreamProofTransactionResult>;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Per-wallet write-path overrides.
|
|
204
|
+
*
|
|
205
|
+
* `transport` exists for exactly one reason: a Privy wallet has no local
|
|
206
|
+
* `signTransaction`. Its EIP-1193 provider performs `eth_sendTransaction`
|
|
207
|
+
* itself, so the provider must BE the wallet client's transport rather than the
|
|
208
|
+
* plain `http(--rpc-url)` every local signer uses. Left unset — which is every
|
|
209
|
+
* private-key and CDP run — the transport is unchanged, byte for byte.
|
|
210
|
+
*/
|
|
211
|
+
export interface CreateWriteClientOptions {
|
|
212
|
+
readonly transport?: Transport;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export interface CliDependencies {
|
|
216
|
+
readonly createReadClient: (config: CliConfig) => CliReadClient;
|
|
217
|
+
/**
|
|
218
|
+
* `account` is nullable because `open --dry-run` broadcasts nothing and must
|
|
219
|
+
* work for someone who has no key yet. Without one the SDK still builds the
|
|
220
|
+
* calldata and simply reports that the allowance read and simulation were
|
|
221
|
+
* skipped; every fund-moving method throws rather than inventing a signer.
|
|
222
|
+
*/
|
|
223
|
+
readonly createWriteClient: (
|
|
224
|
+
config: CliConfig,
|
|
225
|
+
account: CliSignerAccount | null,
|
|
226
|
+
options?: CreateWriteClientOptions,
|
|
227
|
+
) => CliWriteClient;
|
|
228
|
+
readonly discover: (
|
|
229
|
+
options: DiscoverServicesOptions,
|
|
230
|
+
) => Promise<DiscoverResult>;
|
|
231
|
+
readonly invoke: (
|
|
232
|
+
streamId: `0x${string}`,
|
|
233
|
+
request: InvokeServiceInput,
|
|
234
|
+
options: InvokeOptions,
|
|
235
|
+
) => Promise<InvokeResult>;
|
|
236
|
+
readonly createInvokeStreamReader: (config: CliConfig) => InvokeStreamReader;
|
|
237
|
+
readonly nowSeconds: () => number;
|
|
238
|
+
readonly sleep: (ms: number) => Promise<void>;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* The `StreamOpened` ABI entry, resolved once. Taken from the shared V2 ABI
|
|
243
|
+
* rather than re-declared: a hand-written copy that drifted by one indexed
|
|
244
|
+
* argument would silently match nothing.
|
|
245
|
+
*/
|
|
246
|
+
const STREAM_OPENED_EVENT = getAbiItem({
|
|
247
|
+
abi: streamEscrowV2Abi,
|
|
248
|
+
name: "StreamOpened",
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* The RPC transport, with an explicit per-call budget.
|
|
253
|
+
*
|
|
254
|
+
* viem's defaults leave a single `eth_call` able to hang for a long time behind
|
|
255
|
+
* three backing-off retries, which is how a `status` run reached two minutes
|
|
256
|
+
* with nothing on screen. Bounding it here bounds every read and every
|
|
257
|
+
* simulation the SDK performs, in one place.
|
|
258
|
+
*/
|
|
259
|
+
function rpcTransport(config: CliConfig) {
|
|
260
|
+
return http(config.rpcUrl, {
|
|
261
|
+
timeout: RPC_TIMEOUT_MS,
|
|
262
|
+
retryCount: RPC_RETRY_COUNT,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Shared `MetrikClient` construction: one escrow, declared as V2 on both keys. */
|
|
267
|
+
function createMetrikClient(
|
|
268
|
+
config: CliConfig,
|
|
269
|
+
account?: CliSignerAccount,
|
|
270
|
+
transport?: Transport,
|
|
271
|
+
): MetrikClient {
|
|
272
|
+
return new MetrikClient({
|
|
273
|
+
chain: config.chain,
|
|
274
|
+
transport: transport ?? rpcTransport(config),
|
|
275
|
+
escrow: config.escrow,
|
|
276
|
+
// Declared on BOTH keys: without `escrowV2` the SDK routes reads and
|
|
277
|
+
// settlement through the legacy V1 ABI, which reverts against
|
|
278
|
+
// StreamEscrowV2 (its one-argument views were replaced by proof-carrying
|
|
279
|
+
// ones, and `reclaim` becomes `reclaimWithProof`).
|
|
280
|
+
escrowV2: config.escrow,
|
|
281
|
+
usdc: config.usdc,
|
|
282
|
+
oracleBaseUrl: config.oracleBaseUrl,
|
|
283
|
+
settlementTarget: { chainId: config.chainId, escrow: config.escrow },
|
|
284
|
+
...(account === undefined ? {} : { account }),
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export const defaultCliDependencies: CliDependencies = {
|
|
289
|
+
createReadClient(config) {
|
|
290
|
+
const metrik = createMetrikClient(config);
|
|
291
|
+
return {
|
|
292
|
+
async getChainId() {
|
|
293
|
+
return await metrik.publicClient.getChainId();
|
|
294
|
+
},
|
|
295
|
+
async getStreamV2(streamId) {
|
|
296
|
+
return await metrik.getStreamV2(streamId);
|
|
297
|
+
},
|
|
298
|
+
async checkpointProof(streamId) {
|
|
299
|
+
// Not an RPC call: this is an HTTP fetch against the oracle, so the
|
|
300
|
+
// transport budget above does not cover it.
|
|
301
|
+
return await withReadTimeout(
|
|
302
|
+
async () => await metrik.checkpointProof(streamId),
|
|
303
|
+
`the oracle at ${config.oracleBaseUrl}`,
|
|
304
|
+
);
|
|
305
|
+
},
|
|
306
|
+
async claimableForV2(streamId, cumulativeAmount) {
|
|
307
|
+
return await metrik.claimableForV2(streamId, cumulativeAmount);
|
|
308
|
+
},
|
|
309
|
+
async reclaimableForV2(streamId, cumulativeAmount) {
|
|
310
|
+
return await metrik.reclaimableForV2(streamId, cumulativeAmount);
|
|
311
|
+
},
|
|
312
|
+
async nativeBalance(address) {
|
|
313
|
+
return await metrik.publicClient.getBalance({ address });
|
|
314
|
+
},
|
|
315
|
+
async usdcBalance(address) {
|
|
316
|
+
return await metrik.publicClient.readContract({
|
|
317
|
+
address: config.usdc,
|
|
318
|
+
abi: erc20Abi,
|
|
319
|
+
functionName: "balanceOf",
|
|
320
|
+
args: [address],
|
|
321
|
+
});
|
|
322
|
+
},
|
|
323
|
+
async blockNumber() {
|
|
324
|
+
return await metrik.publicClient.getBlockNumber();
|
|
325
|
+
},
|
|
326
|
+
async streamOpenedLogs({ role, address, fromBlock, toBlock }) {
|
|
327
|
+
const logs = await metrik.publicClient.getLogs({
|
|
328
|
+
address: config.escrow,
|
|
329
|
+
event: STREAM_OPENED_EVENT,
|
|
330
|
+
args: { [role]: address },
|
|
331
|
+
fromBlock,
|
|
332
|
+
toBlock,
|
|
333
|
+
});
|
|
334
|
+
// Returned as-is: the topic filter is evaluated by the node, so
|
|
335
|
+
// re-checking `log.args.buyer === address` here would imply the filter
|
|
336
|
+
// is decorative and would hide a mis-built filter behind a second pass.
|
|
337
|
+
// A wrong filter should produce a visibly wrong result, not a quietly
|
|
338
|
+
// correct one.
|
|
339
|
+
return logs.flatMap((log) =>
|
|
340
|
+
log.args.streamId === undefined ||
|
|
341
|
+
log.args.buyer === undefined ||
|
|
342
|
+
log.args.operator === undefined ||
|
|
343
|
+
log.args.serviceRef === undefined ||
|
|
344
|
+
log.args.deposit === undefined ||
|
|
345
|
+
log.args.ratePerSecond === undefined ||
|
|
346
|
+
log.args.expiresAt === undefined
|
|
347
|
+
? []
|
|
348
|
+
: [
|
|
349
|
+
{
|
|
350
|
+
streamId: log.args.streamId,
|
|
351
|
+
buyer: log.args.buyer,
|
|
352
|
+
operator: log.args.operator,
|
|
353
|
+
serviceRef: log.args.serviceRef,
|
|
354
|
+
deposit: log.args.deposit,
|
|
355
|
+
ratePerSecond: log.args.ratePerSecond,
|
|
356
|
+
expiresAt: Number(log.args.expiresAt),
|
|
357
|
+
blockNumber: log.blockNumber ?? 0n,
|
|
358
|
+
},
|
|
359
|
+
],
|
|
360
|
+
);
|
|
361
|
+
},
|
|
362
|
+
async escrowLogCountAt(block) {
|
|
363
|
+
const logs = await metrik.publicClient.getLogs({
|
|
364
|
+
address: config.escrow,
|
|
365
|
+
fromBlock: block,
|
|
366
|
+
toBlock: block,
|
|
367
|
+
});
|
|
368
|
+
return logs.length;
|
|
369
|
+
},
|
|
370
|
+
async protocolFeeBps() {
|
|
371
|
+
const bps = await metrik.publicClient.readContract({
|
|
372
|
+
address: config.escrow,
|
|
373
|
+
abi: streamEscrowV2Abi,
|
|
374
|
+
functionName: "protocolFeeBps",
|
|
375
|
+
});
|
|
376
|
+
return Number(bps);
|
|
377
|
+
},
|
|
378
|
+
async escapeGraceSeconds() {
|
|
379
|
+
const seconds = await metrik.publicClient.readContract({
|
|
380
|
+
address: config.escrow,
|
|
381
|
+
abi: streamEscrowV2Abi,
|
|
382
|
+
functionName: "escapeGraceSeconds",
|
|
383
|
+
});
|
|
384
|
+
return Number(seconds);
|
|
385
|
+
},
|
|
386
|
+
async latestCheckpointCheckedAt() {
|
|
387
|
+
const checkedAt = await metrik.publicClient.readContract({
|
|
388
|
+
address: config.escrow,
|
|
389
|
+
abi: streamEscrowV2Abi,
|
|
390
|
+
functionName: "latestCheckpointCheckedAt",
|
|
391
|
+
});
|
|
392
|
+
return Number(checkedAt);
|
|
393
|
+
},
|
|
394
|
+
async latestBlockTimestamp() {
|
|
395
|
+
const block = await metrik.publicClient.getBlock();
|
|
396
|
+
return Number(block.timestamp);
|
|
397
|
+
},
|
|
398
|
+
async oracleSigners() {
|
|
399
|
+
return await metrik.publicClient.readContract({
|
|
400
|
+
address: config.escrow,
|
|
401
|
+
abi: streamEscrowV2Abi,
|
|
402
|
+
functionName: "getOracleSigners",
|
|
403
|
+
});
|
|
404
|
+
},
|
|
405
|
+
};
|
|
406
|
+
},
|
|
407
|
+
createWriteClient(config, account, options) {
|
|
408
|
+
// A Privy wallet's provider IS the transport (see CreateWriteClientOptions);
|
|
409
|
+
// every other wallet keeps the plain bounded HTTP transport.
|
|
410
|
+
const transport = options?.transport ?? rpcTransport(config);
|
|
411
|
+
const clientConfig = {
|
|
412
|
+
chain: config.chain,
|
|
413
|
+
transport,
|
|
414
|
+
escrow: config.escrow,
|
|
415
|
+
escrowV2: config.escrow,
|
|
416
|
+
usdc: config.usdc,
|
|
417
|
+
oracleBaseUrl: config.oracleBaseUrl,
|
|
418
|
+
settlementTarget: { chainId: config.chainId, escrow: config.escrow },
|
|
419
|
+
...(account === null ? {} : { account }),
|
|
420
|
+
};
|
|
421
|
+
// The mandate-gated agent client owns every fund-moving call; the raw SDK
|
|
422
|
+
// client is used only for the build-only `--dry-run` preview, which
|
|
423
|
+
// broadcasts nothing.
|
|
424
|
+
const agentClient = new VerifiedStreamAgentClient(clientConfig);
|
|
425
|
+
const metrik = createMetrikClient(
|
|
426
|
+
config,
|
|
427
|
+
account === null ? undefined : account,
|
|
428
|
+
transport,
|
|
429
|
+
);
|
|
430
|
+
return {
|
|
431
|
+
async buildHireTx(input) {
|
|
432
|
+
return await metrik.buildHireTx(input);
|
|
433
|
+
},
|
|
434
|
+
async openVerifiedStream({ signedMandate, ...input }) {
|
|
435
|
+
// No mandate means a policy-enforced wallet: the ceiling was already
|
|
436
|
+
// checked against the broker's own `/v1/config` before we got here, and
|
|
437
|
+
// the broker checks it AGAIN server-side on the transaction it signs.
|
|
438
|
+
// Going straight at the SDK client is the same path the SDK-driven
|
|
439
|
+
// (non-CLI) flow has always used.
|
|
440
|
+
if (signedMandate === null) {
|
|
441
|
+
return await metrik.hireVerifiedService({
|
|
442
|
+
operator: input.operator,
|
|
443
|
+
serviceRef: input.serviceRef,
|
|
444
|
+
budgetUsdc: input.budgetUsdc,
|
|
445
|
+
ratePerSecond: input.ratePerSecondUsdc,
|
|
446
|
+
maxDurationSeconds: input.maxDurationSeconds,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
return await agentClient.openVerifiedStream({
|
|
450
|
+
...input,
|
|
451
|
+
signedMandate,
|
|
452
|
+
});
|
|
453
|
+
},
|
|
454
|
+
async closeStream({ signedMandate, ...input }) {
|
|
455
|
+
if (signedMandate === null) return await metrik.close(input.streamId);
|
|
456
|
+
return await agentClient.closeStream({ ...input, signedMandate });
|
|
457
|
+
},
|
|
458
|
+
async reclaimStream({ signedMandate, ...input }) {
|
|
459
|
+
if (signedMandate === null) {
|
|
460
|
+
const closeResult = input.closeFirst
|
|
461
|
+
? await metrik.close(input.streamId)
|
|
462
|
+
: null;
|
|
463
|
+
return {
|
|
464
|
+
closeResult,
|
|
465
|
+
reclaimResult: await metrik.reclaim(input.streamId),
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
return await agentClient.reclaimStream({ ...input, signedMandate });
|
|
469
|
+
},
|
|
470
|
+
async reclaimUnverifiedStream({ signedMandate, ...input }) {
|
|
471
|
+
if (signedMandate === null) {
|
|
472
|
+
return await metrik.reclaimUnverified(input.streamId);
|
|
473
|
+
}
|
|
474
|
+
return await agentClient.reclaimUnverifiedStream({
|
|
475
|
+
...input,
|
|
476
|
+
signedMandate,
|
|
477
|
+
});
|
|
478
|
+
},
|
|
479
|
+
async claimStream({ signedMandate, ...input }) {
|
|
480
|
+
if (signedMandate === null) return await metrik.claim(input.streamId);
|
|
481
|
+
return await agentClient.claimStream({ ...input, signedMandate });
|
|
482
|
+
},
|
|
483
|
+
};
|
|
484
|
+
},
|
|
485
|
+
async discover(options) {
|
|
486
|
+
return await discoverServicesDetailed(options);
|
|
487
|
+
},
|
|
488
|
+
async invoke(streamId, request, options) {
|
|
489
|
+
return await sdkInvoke(streamId, request, options);
|
|
490
|
+
},
|
|
491
|
+
createInvokeStreamReader(config) {
|
|
492
|
+
const metrik = createMetrikClient(config);
|
|
493
|
+
return {
|
|
494
|
+
async getStreamV2(streamId) {
|
|
495
|
+
const stream = await metrik.getStreamV2(streamId);
|
|
496
|
+
return {
|
|
497
|
+
buyer: stream.buyer,
|
|
498
|
+
operator: stream.operator,
|
|
499
|
+
serviceRef: stream.serviceRef,
|
|
500
|
+
status: stream.status,
|
|
501
|
+
expiresAt: stream.expiresAt,
|
|
502
|
+
};
|
|
503
|
+
},
|
|
504
|
+
};
|
|
505
|
+
},
|
|
506
|
+
nowSeconds() {
|
|
507
|
+
return Math.floor(Date.now() / 1000);
|
|
508
|
+
},
|
|
509
|
+
async sleep(ms) {
|
|
510
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
511
|
+
},
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* The write-client options a resolved wallet implies.
|
|
516
|
+
*
|
|
517
|
+
* Kept next to {@link CreateWriteClientOptions} so every fund-moving command
|
|
518
|
+
* spells the transport decision the same way, and so a wallet that needs a
|
|
519
|
+
* provider transport cannot be broadcast through a plain RPC by a command that
|
|
520
|
+
* simply forgot to pass it.
|
|
521
|
+
*/
|
|
522
|
+
export function writeClientOptions(
|
|
523
|
+
wallet: { readonly transport?: Transport } | null,
|
|
524
|
+
): CreateWriteClientOptions {
|
|
525
|
+
return wallet?.transport === undefined ? {} : { transport: wallet.transport };
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
export type { DiscoverResult, ServiceListing };
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SignedServiceBinding,
|
|
3
|
+
SignedServiceDescriptor,
|
|
4
|
+
} from "@absol-labs/shared";
|
|
5
|
+
|
|
6
|
+
import type { DiscoveredService } from "../discovery/registry.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* What a listing's SIGNED descriptor already tells a newcomer — surfaced rather
|
|
10
|
+
* than discarded.
|
|
11
|
+
*
|
|
12
|
+
* The registry's signed records carry the callable endpoints, one worked example
|
|
13
|
+
* call, and the operator's accepted rate band. `metrik discover` used to project
|
|
14
|
+
* them away, so the CLI could show a `serviceRef` but never what the service
|
|
15
|
+
* DOES, what path to call, or what `--rate` the operator will accept. Everything
|
|
16
|
+
* here is read from the record whose signature was already recovered and
|
|
17
|
+
* matched, so nothing unsigned is ever presented as fact.
|
|
18
|
+
*/
|
|
19
|
+
export interface ListingEndpoint {
|
|
20
|
+
readonly name: string;
|
|
21
|
+
readonly path: string;
|
|
22
|
+
readonly method: string;
|
|
23
|
+
readonly description: string | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ListingExample {
|
|
27
|
+
readonly endpoint: string;
|
|
28
|
+
readonly description: string | null;
|
|
29
|
+
readonly request: unknown;
|
|
30
|
+
readonly response: unknown;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ListingRates {
|
|
34
|
+
/** USDC base units per second, as signed (decimal strings, never floats). */
|
|
35
|
+
readonly minRatePerSecond: string | null;
|
|
36
|
+
readonly maxRatePerSecond: string | null;
|
|
37
|
+
readonly currency: string | null;
|
|
38
|
+
readonly slaDescription: string | null;
|
|
39
|
+
readonly refundPolicy: string | null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ListingDetail {
|
|
43
|
+
readonly endpoints: readonly ListingEndpoint[];
|
|
44
|
+
readonly example: ListingExample | null;
|
|
45
|
+
readonly rates: ListingRates | null;
|
|
46
|
+
readonly authModel: string | null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const EMPTY_DETAIL: ListingDetail = {
|
|
50
|
+
endpoints: [],
|
|
51
|
+
example: null,
|
|
52
|
+
rates: null,
|
|
53
|
+
authModel: null,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Project the signed record onto the presentable detail. A legacy binding (which
|
|
58
|
+
* carries no interface at all) yields empty detail rather than an invented one.
|
|
59
|
+
*/
|
|
60
|
+
export function listingDetail(
|
|
61
|
+
signed: SignedServiceBinding | SignedServiceDescriptor | undefined,
|
|
62
|
+
): ListingDetail {
|
|
63
|
+
const descriptor = (signed as SignedServiceDescriptor | undefined)
|
|
64
|
+
?.descriptor;
|
|
65
|
+
if (descriptor === undefined) return EMPTY_DETAIL;
|
|
66
|
+
|
|
67
|
+
const iface = descriptor.interface;
|
|
68
|
+
const commercial = descriptor.commercial;
|
|
69
|
+
return {
|
|
70
|
+
endpoints: (iface?.endpoints ?? []).map((endpoint) => ({
|
|
71
|
+
name: endpoint.name,
|
|
72
|
+
path: endpoint.path,
|
|
73
|
+
method: endpoint.method,
|
|
74
|
+
description: endpoint.description ?? null,
|
|
75
|
+
})),
|
|
76
|
+
example:
|
|
77
|
+
iface?.example === undefined
|
|
78
|
+
? null
|
|
79
|
+
: {
|
|
80
|
+
endpoint: iface.example.endpoint,
|
|
81
|
+
description: iface.example.description ?? null,
|
|
82
|
+
request: iface.example.request ?? null,
|
|
83
|
+
response: iface.example.response ?? null,
|
|
84
|
+
},
|
|
85
|
+
rates:
|
|
86
|
+
commercial === undefined
|
|
87
|
+
? null
|
|
88
|
+
: {
|
|
89
|
+
minRatePerSecond: commercial.minRatePerSecond ?? null,
|
|
90
|
+
maxRatePerSecond: commercial.maxRatePerSecond ?? null,
|
|
91
|
+
currency: commercial.currency ?? null,
|
|
92
|
+
slaDescription: commercial.slaDescription ?? null,
|
|
93
|
+
refundPolicy: commercial.refundPolicy ?? null,
|
|
94
|
+
},
|
|
95
|
+
authModel: iface?.authModel ?? null,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* How much verification a listing actually declares. Used to order `discover`
|
|
101
|
+
* strongest-first so `--limit 1` returns the best-attested listing rather than
|
|
102
|
+
* whatever the registry happened to serve first — which is how a newcomer's very
|
|
103
|
+
* first result ended up being a deliberately-failing test fixture.
|
|
104
|
+
*
|
|
105
|
+
* This ranks the DECLARED verification spec, which is the only thing the signed
|
|
106
|
+
* record can tell us. It is a presentation order, not a trust score.
|
|
107
|
+
*/
|
|
108
|
+
export function verificationStrength(service: DiscoveredService): number {
|
|
109
|
+
const summary = service.verification;
|
|
110
|
+
if (summary === null) return 0;
|
|
111
|
+
return (
|
|
112
|
+
(summary.hasReexec ? 8 : 0) +
|
|
113
|
+
(summary.hasSchema ? 4 : 0) +
|
|
114
|
+
(summary.hasSla ? 2 : 0) +
|
|
115
|
+
Math.min(summary.canaries, 1)
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Deterministic, strongest-verification-first order. Ties break on `serviceRef`
|
|
121
|
+
* so the same registry always renders in the same order and `--limit N` is
|
|
122
|
+
* reproducible between runs.
|
|
123
|
+
*/
|
|
124
|
+
export function orderListings<T extends DiscoveredService>(
|
|
125
|
+
services: readonly T[],
|
|
126
|
+
): T[] {
|
|
127
|
+
return [...services].sort((left, right) => {
|
|
128
|
+
if (left.tier !== right.tier) return right.tier - left.tier;
|
|
129
|
+
const strength = verificationStrength(right) - verificationStrength(left);
|
|
130
|
+
if (strength !== 0) return strength;
|
|
131
|
+
return left.serviceRef.localeCompare(right.serviceRef);
|
|
132
|
+
});
|
|
133
|
+
}
|