@ziggs-ai/api-client 0.34.1 → 0.36.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/dist/capabilities/agreementVerbs.js +9 -18
- package/dist/capabilities/agreements.js +20 -3
- package/dist/capabilities/artifacts.js +3 -51
- package/dist/capabilities/chat.js +0 -1
- package/dist/capabilities/connections.js +2 -10
- package/dist/capabilities/context.js +9 -12
- package/dist/capabilities/discovery.js +0 -2
- package/dist/capabilities/everyday.d.ts +4 -7
- package/dist/capabilities/everyday.js +6 -11
- package/dist/capabilities/grants.js +0 -1
- package/dist/capabilities/index.d.ts +2 -1
- package/dist/capabilities/index.js +2 -1
- package/dist/capabilities/introductions.js +2 -6
- package/dist/capabilities/links.js +1 -3
- package/dist/capabilities/marketplace.js +0 -1
- package/dist/capabilities/mcpBriefs.d.ts +45 -0
- package/dist/capabilities/mcpBriefs.js +335 -0
- package/dist/capabilities/payments.js +1 -2
- package/dist/capabilities/tasks.js +1 -3
- package/dist/capabilities/types.d.ts +3 -4
- package/dist/http/AgreementClient.d.ts +6 -0
- package/dist/http/AgreementClient.js +1 -1
- package/dist/http/AssistantConnectionClient.d.ts +2 -2
- package/dist/http/AssistantConnectionClient.js +3 -3
- package/dist/http/InboxClient.d.ts +4 -0
- package/dist/http/InboxClient.js +10 -0
- package/dist/http/PaymentsClient.d.ts +4 -125
- package/dist/http/PaymentsClient.js +4 -205
- package/dist/http/index.d.ts +1 -1
- package/package.json +1 -1
- package/dist/http/paymentGrantSelection.d.ts +0 -58
- package/dist/http/paymentGrantSelection.js +0 -121
|
@@ -10,10 +10,6 @@ export interface WalletBalance {
|
|
|
10
10
|
balance: number;
|
|
11
11
|
availableBalance: number;
|
|
12
12
|
}
|
|
13
|
-
export interface PaymentTransactionView {
|
|
14
|
-
transactionId?: string;
|
|
15
|
-
[key: string]: unknown;
|
|
16
|
-
}
|
|
17
13
|
/**
|
|
18
14
|
* A resolved wallet reference (GET /payments/wallets/resolve).
|
|
19
15
|
*
|
|
@@ -24,27 +20,6 @@ export interface PaymentTransactionView {
|
|
|
24
20
|
*/
|
|
25
21
|
import type { WalletRef } from '@ziggs-ai/contracts';
|
|
26
22
|
export type { WalletRef };
|
|
27
|
-
export type TransferResult = {
|
|
28
|
-
status: 'approval_required';
|
|
29
|
-
approvalId: string | null;
|
|
30
|
-
expiresAt: string | null;
|
|
31
|
-
reason: string | null;
|
|
32
|
-
toWalletId: string;
|
|
33
|
-
amount: number;
|
|
34
|
-
} | {
|
|
35
|
-
status: 'transferred';
|
|
36
|
-
transactionId: string | null;
|
|
37
|
-
toWalletId: string;
|
|
38
|
-
amount: number;
|
|
39
|
-
};
|
|
40
|
-
export interface HoldResult {
|
|
41
|
-
transaction?: PaymentTransactionView;
|
|
42
|
-
[key: string]: unknown;
|
|
43
|
-
}
|
|
44
|
-
export interface ReleaseResult {
|
|
45
|
-
transaction?: PaymentTransactionView;
|
|
46
|
-
[key: string]: unknown;
|
|
47
|
-
}
|
|
48
23
|
/**
|
|
49
24
|
* A payment grant as this rail answers it.
|
|
50
25
|
*
|
|
@@ -69,40 +44,14 @@ export interface RevokeGrantResult {
|
|
|
69
44
|
revoked?: unknown;
|
|
70
45
|
[key: string]: unknown;
|
|
71
46
|
}
|
|
72
|
-
export interface PaymentApproval {
|
|
73
|
-
approvalId?: string;
|
|
74
|
-
status?: string;
|
|
75
|
-
executedTransactionId?: string | null;
|
|
76
|
-
[key: string]: unknown;
|
|
77
|
-
}
|
|
78
|
-
export type WaitForApprovalResult = {
|
|
79
|
-
status: 'gone';
|
|
80
|
-
} | {
|
|
81
|
-
status: 'executed';
|
|
82
|
-
approval: PaymentApproval;
|
|
83
|
-
transactionId: string | null;
|
|
84
|
-
} | {
|
|
85
|
-
status: 'rejected' | 'expired' | 'timeout';
|
|
86
|
-
approval: PaymentApproval;
|
|
87
|
-
};
|
|
88
|
-
/**
|
|
89
|
-
* Who to pay on a transfer. Kind is declared by the caller — never inferred
|
|
90
|
-
* from id shape. A bare string is only a wallet id (`wal_…`).
|
|
91
|
-
*/
|
|
92
|
-
export type TransferPayee = string | {
|
|
93
|
-
walletId: string;
|
|
94
|
-
} | {
|
|
95
|
-
agentId: string;
|
|
96
|
-
} | {
|
|
97
|
-
userId: string;
|
|
98
|
-
};
|
|
99
47
|
/**
|
|
100
48
|
* the one payments client for every surface (agent-sdk, ziggs-mcp,
|
|
101
49
|
* scripts). Consolidates the former agent-sdk private ZiggsPayClient. Wallet
|
|
102
50
|
* ops ride the operator key; agent-impersonated calls send X-Agent-Id and are
|
|
103
|
-
* policy-gated server-side
|
|
104
|
-
*
|
|
105
|
-
*
|
|
51
|
+
* policy-gated server-side. Points move only when an agreement or a task
|
|
52
|
+
* settles, and the server does that; there is no standalone movement verb on
|
|
53
|
+
* this client. Wallet-rail grant mutations (issue / attenuate / revoke) live
|
|
54
|
+
* here; grant *listing* is the unified GET /grants.
|
|
106
55
|
*/
|
|
107
56
|
export declare class PaymentsClient {
|
|
108
57
|
private readonly operatorKey;
|
|
@@ -128,60 +77,6 @@ export declare class PaymentsClient {
|
|
|
128
77
|
userId?: string;
|
|
129
78
|
agentId?: string;
|
|
130
79
|
}): Promise<WalletRef | null>;
|
|
131
|
-
/**
|
|
132
|
-
* Resolve `transfer.to` to a wallet id. String form is wallet-only; agent /
|
|
133
|
-
* user payees must name their kind.
|
|
134
|
-
*/
|
|
135
|
-
private resolvePayeeWalletId;
|
|
136
|
-
transfer({ to, amount, idempotencyKey, description, paymentGrantId, agreementId, }: {
|
|
137
|
-
to: TransferPayee;
|
|
138
|
-
amount: number;
|
|
139
|
-
idempotencyKey?: string;
|
|
140
|
-
description?: string;
|
|
141
|
-
paymentGrantId?: string;
|
|
142
|
-
/**
|
|
143
|
-
* The hire this spend belongs to. Used for attribution, and to prefer that
|
|
144
|
-
* hire's own grant over a standing one — a preference about which budget is
|
|
145
|
-
* billed, never about which grants may be spent. That answer is the
|
|
146
|
-
* server's, and it turns on who gave the grant.
|
|
147
|
-
*/
|
|
148
|
-
agreementId?: string;
|
|
149
|
-
}): Promise<TransferResult>;
|
|
150
|
-
hold({ amount, idempotencyKey, description, paymentGrantId, agreementId, }: {
|
|
151
|
-
amount: number;
|
|
152
|
-
idempotencyKey?: string;
|
|
153
|
-
description?: string;
|
|
154
|
-
paymentGrantId?: string;
|
|
155
|
-
/**
|
|
156
|
-
* The hire this spend belongs to. Used for attribution, and to prefer that
|
|
157
|
-
* hire's own grant over a standing one — a preference about which budget is
|
|
158
|
-
* billed, never about which grants may be spent. That answer is the
|
|
159
|
-
* server's, and it turns on who gave the grant.
|
|
160
|
-
*/
|
|
161
|
-
agreementId?: string;
|
|
162
|
-
}): Promise<HoldResult>;
|
|
163
|
-
/**
|
|
164
|
-
* Choose the grant that actually covers this spend.
|
|
165
|
-
*
|
|
166
|
-
* This used to be `grants[0]` — the first active grant, matched against
|
|
167
|
-
* nothing. An agent holding both its standing grant and an
|
|
168
|
-
* agreement-conferred one presented whichever came back first, the backend
|
|
169
|
-
* correctly refused it against wallet or amount, and the agent was refused a
|
|
170
|
-
* spend it was genuinely authorized to make.
|
|
171
|
-
*
|
|
172
|
-
* The source wallet is read rather than assumed: a grant on some other
|
|
173
|
-
* wallet cannot authorize money leaving this one, and the agent may hold
|
|
174
|
-
* grants on a counterparty's wallet from an engagement. A failure to read it
|
|
175
|
-
* is not fatal — selection then skips the wallet test and the server still
|
|
176
|
-
* validates, which is the same position we were in before.
|
|
177
|
-
*/
|
|
178
|
-
private selectPaymentGrant;
|
|
179
|
-
release({ holdId, action, toWalletId, idempotencyKey, }: {
|
|
180
|
-
holdId: string;
|
|
181
|
-
action?: string;
|
|
182
|
-
toWalletId?: string;
|
|
183
|
-
idempotencyKey?: string;
|
|
184
|
-
}): Promise<ReleaseResult>;
|
|
185
80
|
history(params?: {
|
|
186
81
|
limit?: number;
|
|
187
82
|
offset?: number;
|
|
@@ -202,27 +97,11 @@ export declare class PaymentsClient {
|
|
|
202
97
|
* (retired GET /payments/grants), following the cursor to completion.
|
|
203
98
|
*/
|
|
204
99
|
listGrants(): Promise<GrantView[]>;
|
|
205
|
-
createTopUpIntent({ amount, description, currency, }?: {
|
|
206
|
-
amount?: number;
|
|
207
|
-
description?: string;
|
|
208
|
-
currency?: string;
|
|
209
|
-
}): Promise<unknown>;
|
|
210
|
-
confirmMockIntent(intentId: string): Promise<unknown>;
|
|
211
100
|
faucet({ amount, description, idempotencyKey, }?: {
|
|
212
101
|
amount?: number;
|
|
213
102
|
description?: string;
|
|
214
103
|
idempotencyKey?: string;
|
|
215
104
|
}): Promise<unknown>;
|
|
216
|
-
approvals({ status }?: {
|
|
217
|
-
status?: string;
|
|
218
|
-
}): Promise<PaymentApproval[]>;
|
|
219
|
-
getApproval(approvalId: string): Promise<PaymentApproval | null>;
|
|
220
|
-
decide(approvalId: string, decision: string, note?: string): Promise<unknown>;
|
|
221
|
-
waitForApproval(approvalId: string, opts?: {
|
|
222
|
-
pollMs?: number;
|
|
223
|
-
timeoutMs?: number;
|
|
224
|
-
signal?: AbortSignal;
|
|
225
|
-
}): Promise<WaitForApprovalResult>;
|
|
226
105
|
private _request;
|
|
227
106
|
private _get;
|
|
228
107
|
private _post;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { getBackendUrl } from '../utils/urlUtils.js';
|
|
2
2
|
import { buildOperatorHeaders } from './operatorHeaders.js';
|
|
3
3
|
import { GrantsClient } from './GrantsClient.js';
|
|
4
|
-
import { describeNoGrant, selectPaymentGrant, } from './paymentGrantSelection.js';
|
|
5
4
|
import { throwApiError } from '../shared/apiError.js';
|
|
6
5
|
function randomIdempotencyKey(prefix = 'op') {
|
|
7
6
|
return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
@@ -10,9 +9,10 @@ function randomIdempotencyKey(prefix = 'op') {
|
|
|
10
9
|
* the one payments client for every surface (agent-sdk, ziggs-mcp,
|
|
11
10
|
* scripts). Consolidates the former agent-sdk private ZiggsPayClient. Wallet
|
|
12
11
|
* ops ride the operator key; agent-impersonated calls send X-Agent-Id and are
|
|
13
|
-
* policy-gated server-side
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* policy-gated server-side. Points move only when an agreement or a task
|
|
13
|
+
* settles, and the server does that; there is no standalone movement verb on
|
|
14
|
+
* this client. Wallet-rail grant mutations (issue / attenuate / revoke) live
|
|
15
|
+
* here; grant *listing* is the unified GET /grants.
|
|
16
16
|
*/
|
|
17
17
|
export class PaymentsClient {
|
|
18
18
|
operatorKey;
|
|
@@ -61,139 +61,6 @@ export class PaymentsClient {
|
|
|
61
61
|
const res = (await this._get(`/payments/wallets/resolve?${params}`));
|
|
62
62
|
return res['wallet'] || null;
|
|
63
63
|
}
|
|
64
|
-
/**
|
|
65
|
-
* Resolve `transfer.to` to a wallet id. String form is wallet-only; agent /
|
|
66
|
-
* user payees must name their kind.
|
|
67
|
-
*/
|
|
68
|
-
async resolvePayeeWalletId(to) {
|
|
69
|
-
if (typeof to === 'string') {
|
|
70
|
-
const id = to.trim();
|
|
71
|
-
if (!id)
|
|
72
|
-
throw new Error('transfer: `to` is required');
|
|
73
|
-
if (id.startsWith('wal_'))
|
|
74
|
-
return id;
|
|
75
|
-
throw new Error('transfer: string `to` must be a wallet id (wal_…); pass { agentId } or { userId } for a principal');
|
|
76
|
-
}
|
|
77
|
-
if ('walletId' in to && typeof to.walletId === 'string' && to.walletId.trim()) {
|
|
78
|
-
return to.walletId.trim();
|
|
79
|
-
}
|
|
80
|
-
if ('agentId' in to && typeof to.agentId === 'string' && to.agentId.trim()) {
|
|
81
|
-
const w = await this.resolve({ agentId: to.agentId.trim() });
|
|
82
|
-
if (!w?.walletId) {
|
|
83
|
-
throw new Error(`transfer: could not resolve wallet for agent "${to.agentId}"`);
|
|
84
|
-
}
|
|
85
|
-
return w.walletId;
|
|
86
|
-
}
|
|
87
|
-
if ('userId' in to && typeof to.userId === 'string' && to.userId.trim()) {
|
|
88
|
-
const w = await this.resolve({ userId: to.userId.trim() });
|
|
89
|
-
if (!w?.walletId) {
|
|
90
|
-
throw new Error(`transfer: could not resolve wallet for user "${to.userId}"`);
|
|
91
|
-
}
|
|
92
|
-
return w.walletId;
|
|
93
|
-
}
|
|
94
|
-
throw new Error('transfer: `to` must be a wallet id (wal_…) or { walletId | agentId | userId }');
|
|
95
|
-
}
|
|
96
|
-
async transfer({ to, amount, idempotencyKey, description, paymentGrantId, agreementId, }) {
|
|
97
|
-
if (to == null || to === '')
|
|
98
|
-
throw new Error('transfer: `to` is required');
|
|
99
|
-
if (!(Number.isInteger(amount) && amount > 0))
|
|
100
|
-
throw new Error('transfer: `amount` must be a positive integer (cents)');
|
|
101
|
-
// The recipient is resolved BEFORE choosing a grant: an `allowed_recipients`
|
|
102
|
-
// caveat cannot be checked against a name the client has not resolved yet.
|
|
103
|
-
const toWalletId = await this.resolvePayeeWalletId(to);
|
|
104
|
-
// Choose the grant that covers this spend when the caller omitted one.
|
|
105
|
-
// An explicit id still wins — a caller who names a grant means it.
|
|
106
|
-
if (this.agentId && !paymentGrantId) {
|
|
107
|
-
const picked = await this.selectPaymentGrant({ amount, toWalletId, agreementId });
|
|
108
|
-
if (!picked.grant) {
|
|
109
|
-
throw new Error(describeNoGrant('transfer', { amount, toWalletId, agreementId }, picked.rejected));
|
|
110
|
-
}
|
|
111
|
-
paymentGrantId = picked.grant.grantId;
|
|
112
|
-
}
|
|
113
|
-
const result = (await this._post('/payments/transfer', {
|
|
114
|
-
toWalletId,
|
|
115
|
-
amount,
|
|
116
|
-
idempotencyKey: idempotencyKey || randomIdempotencyKey('xfer'),
|
|
117
|
-
description,
|
|
118
|
-
paymentGrantId,
|
|
119
|
-
}));
|
|
120
|
-
if (result?.['status'] === 'approval_required') {
|
|
121
|
-
const approval = result['approval'] || {};
|
|
122
|
-
return {
|
|
123
|
-
status: 'approval_required',
|
|
124
|
-
approvalId: approval['approvalId'] || null,
|
|
125
|
-
expiresAt: approval['expiresAt'] || null,
|
|
126
|
-
reason: approval['reason'] || 'Amount exceeds auto-approve threshold',
|
|
127
|
-
toWalletId,
|
|
128
|
-
amount,
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
const tx = result?.['transaction'];
|
|
132
|
-
return {
|
|
133
|
-
status: 'transferred',
|
|
134
|
-
transactionId: tx?.['transactionId'] || null,
|
|
135
|
-
toWalletId,
|
|
136
|
-
amount,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
async hold({ amount, idempotencyKey, description, paymentGrantId, agreementId, }) {
|
|
140
|
-
if (!(Number.isInteger(amount) && amount > 0))
|
|
141
|
-
throw new Error('hold: `amount` must be a positive integer (cents)');
|
|
142
|
-
// A hold reserves the owner's money, so it needs a grant like a transfer.
|
|
143
|
-
// It has no counterparty, so no recipient is offered to the caveats.
|
|
144
|
-
if (this.agentId && !paymentGrantId) {
|
|
145
|
-
const picked = await this.selectPaymentGrant({ amount, agreementId });
|
|
146
|
-
if (!picked.grant) {
|
|
147
|
-
throw new Error(describeNoGrant('hold', { amount, agreementId }, picked.rejected));
|
|
148
|
-
}
|
|
149
|
-
paymentGrantId = picked.grant.grantId;
|
|
150
|
-
}
|
|
151
|
-
return (await this._post('/payments/hold', {
|
|
152
|
-
amount,
|
|
153
|
-
idempotencyKey: idempotencyKey || randomIdempotencyKey('hold'),
|
|
154
|
-
description,
|
|
155
|
-
paymentGrantId,
|
|
156
|
-
}));
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Choose the grant that actually covers this spend.
|
|
160
|
-
*
|
|
161
|
-
* This used to be `grants[0]` — the first active grant, matched against
|
|
162
|
-
* nothing. An agent holding both its standing grant and an
|
|
163
|
-
* agreement-conferred one presented whichever came back first, the backend
|
|
164
|
-
* correctly refused it against wallet or amount, and the agent was refused a
|
|
165
|
-
* spend it was genuinely authorized to make.
|
|
166
|
-
*
|
|
167
|
-
* The source wallet is read rather than assumed: a grant on some other
|
|
168
|
-
* wallet cannot authorize money leaving this one, and the agent may hold
|
|
169
|
-
* grants on a counterparty's wallet from an engagement. A failure to read it
|
|
170
|
-
* is not fatal — selection then skips the wallet test and the server still
|
|
171
|
-
* validates, which is the same position we were in before.
|
|
172
|
-
*/
|
|
173
|
-
async selectPaymentGrant(spend) {
|
|
174
|
-
let grants = [];
|
|
175
|
-
try {
|
|
176
|
-
grants = await this.listGrants();
|
|
177
|
-
}
|
|
178
|
-
catch {
|
|
179
|
-
return { grant: null, rejected: [] };
|
|
180
|
-
}
|
|
181
|
-
let fromWalletId = null;
|
|
182
|
-
try {
|
|
183
|
-
fromWalletId = (await this.balance()).walletId;
|
|
184
|
-
}
|
|
185
|
-
catch {
|
|
186
|
-
fromWalletId = null;
|
|
187
|
-
}
|
|
188
|
-
return selectPaymentGrant(grants, { ...spend, fromWalletId });
|
|
189
|
-
}
|
|
190
|
-
async release({ holdId, action = 'complete', toWalletId, idempotencyKey, }) {
|
|
191
|
-
return (await this._post(`/payments/release/${holdId}`, {
|
|
192
|
-
idempotencyKey: idempotencyKey || randomIdempotencyKey('rel'),
|
|
193
|
-
action,
|
|
194
|
-
toWalletId,
|
|
195
|
-
}));
|
|
196
|
-
}
|
|
197
64
|
async history(params = {}) {
|
|
198
65
|
const q = new URLSearchParams();
|
|
199
66
|
if (params.limit != null)
|
|
@@ -224,12 +91,6 @@ export class PaymentsClient {
|
|
|
224
91
|
const grants = new GrantsClient(this.operatorKey, this.agentId, this.baseUrl, this.laneId);
|
|
225
92
|
return grants.listAllGrants({ scopeKind: 'wallet', health: 'active' });
|
|
226
93
|
}
|
|
227
|
-
async createTopUpIntent({ amount, description, currency, } = {}) {
|
|
228
|
-
return this._post('/payments/onramp/intent', { amount, description, currency });
|
|
229
|
-
}
|
|
230
|
-
async confirmMockIntent(intentId) {
|
|
231
|
-
return this._post(`/payments/onramp/mock-confirm/${intentId}`, {});
|
|
232
|
-
}
|
|
233
94
|
async faucet({ amount, description = 'Faucet', idempotencyKey, } = {}) {
|
|
234
95
|
return this._post('/payments/wallet/fund', {
|
|
235
96
|
amount,
|
|
@@ -237,68 +98,6 @@ export class PaymentsClient {
|
|
|
237
98
|
description,
|
|
238
99
|
});
|
|
239
100
|
}
|
|
240
|
-
async approvals({ status = 'pending' } = {}) {
|
|
241
|
-
const res = (await this._get(`/payments/approvals?status=${encodeURIComponent(status)}`));
|
|
242
|
-
return res['approvals'] || [];
|
|
243
|
-
}
|
|
244
|
-
async getApproval(approvalId) {
|
|
245
|
-
if (!approvalId)
|
|
246
|
-
throw new Error('getApproval: approvalId is required');
|
|
247
|
-
try {
|
|
248
|
-
const res = (await this._get(`/payments/approvals/${approvalId}`));
|
|
249
|
-
return res?.['approval'] || null;
|
|
250
|
-
}
|
|
251
|
-
catch (err) {
|
|
252
|
-
if (err.status === 404)
|
|
253
|
-
return null;
|
|
254
|
-
throw err;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
async decide(approvalId, decision, note = '') {
|
|
258
|
-
return this._post(`/payments/approvals/${approvalId}/decide`, { decision, note });
|
|
259
|
-
}
|
|
260
|
-
async waitForApproval(approvalId, opts = {}) {
|
|
261
|
-
const pollMs = Math.max(500, opts.pollMs ?? 3_000);
|
|
262
|
-
const timeoutMs = Math.max(pollMs, opts.timeoutMs ?? 120_000);
|
|
263
|
-
const signal = opts.signal;
|
|
264
|
-
const deadline = Date.now() + timeoutMs;
|
|
265
|
-
const sleep = (ms) => new Promise((resolve, reject) => {
|
|
266
|
-
const timer = setTimeout(resolve, ms);
|
|
267
|
-
if (signal) {
|
|
268
|
-
if (signal.aborted) {
|
|
269
|
-
clearTimeout(timer);
|
|
270
|
-
reject(new Error('aborted'));
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
signal.addEventListener('abort', () => {
|
|
274
|
-
clearTimeout(timer);
|
|
275
|
-
reject(new Error('aborted'));
|
|
276
|
-
}, { once: true });
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
|
-
while (true) {
|
|
280
|
-
if (signal?.aborted)
|
|
281
|
-
throw new Error('aborted');
|
|
282
|
-
const approval = await this.getApproval(approvalId);
|
|
283
|
-
if (!approval)
|
|
284
|
-
return { status: 'gone' };
|
|
285
|
-
const s = approval.status;
|
|
286
|
-
if (s === 'executed')
|
|
287
|
-
return {
|
|
288
|
-
status: 'executed',
|
|
289
|
-
approval,
|
|
290
|
-
transactionId: approval.executedTransactionId || null,
|
|
291
|
-
};
|
|
292
|
-
if (s === 'rejected')
|
|
293
|
-
return { status: 'rejected', approval };
|
|
294
|
-
if (s === 'expired')
|
|
295
|
-
return { status: 'expired', approval };
|
|
296
|
-
const remaining = deadline - Date.now();
|
|
297
|
-
if (remaining <= 0)
|
|
298
|
-
return { status: 'timeout', approval };
|
|
299
|
-
await sleep(Math.min(pollMs, remaining));
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
101
|
async _request(method, path, body) {
|
|
303
102
|
const init = {
|
|
304
103
|
method,
|
package/dist/http/index.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export type { ContextGrantRecord, ContextGrantScope, ContextGrantScopeKind, Cont
|
|
|
22
22
|
export { grantCaveat, CONTEXT_GRANT_SCOPE_KINDS, GRANT_SCOPE_KINDS, } from './grants.js';
|
|
23
23
|
export type { GrantView, GrantScopeKind, GrantScopeView, GrantCaveatView, GrantHealth, GrantHolderKind, GrantAccessKind, GrantBasisKind, } from './grants.js';
|
|
24
24
|
export { PaymentsClient } from './PaymentsClient.js';
|
|
25
|
-
export type { PaymentsError, WalletBalance, WalletRef,
|
|
25
|
+
export type { PaymentsError, WalletBalance, WalletRef, PaymentGrantView, PaymentGrantEnvelope, RevokeGrantResult, } from './PaymentsClient.js';
|
|
26
26
|
export { ConnectionsClient, assertNoLeakedConnectionSecret, } from './ConnectionsClient.js';
|
|
27
27
|
export type { ConnectionsError, ConnectionProxyParams, ConnectionGrant, ConnectionWithGrants, McpConnectionRequestParams, } from './ConnectionsClient.js';
|
|
28
28
|
export { fetchMyOrgs, resolveOrgSelector, fetchDelegateAccess, fetchHostedAgentAccess, fetchSessionAccess, } from './OrgsClient.js';
|
package/package.json
CHANGED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { type GrantView } from './grants.js';
|
|
2
|
-
/**
|
|
3
|
-
* Which grant to present for a spend, and — when none fits — why each candidate
|
|
4
|
-
* did not.
|
|
5
|
-
*
|
|
6
|
-
* The client used to take `grants[0]` from the active list. It matched nothing
|
|
7
|
-
* else: not the source wallet, not the amount, not the agreement the spend
|
|
8
|
-
* belongs to. An agent holding both its standing grant and an agreement-conferred
|
|
9
|
-
* one presented whichever came back first, the backend correctly refused it
|
|
10
|
-
* against wallet or amount, and the agent was refused a spend it was genuinely
|
|
11
|
-
* authorized to make.
|
|
12
|
-
*
|
|
13
|
-
* Selection is a pure function of the grants and the spend so it can be tested
|
|
14
|
-
* without a server, and so the refusal can name what it considered.
|
|
15
|
-
*/
|
|
16
|
-
export interface SpendShape {
|
|
17
|
-
/** The wallet the money leaves. A grant on another wallet cannot authorize it. */
|
|
18
|
-
fromWalletId?: string | null;
|
|
19
|
-
/** Cents. Checked against `max_amount`. */
|
|
20
|
-
amount: number;
|
|
21
|
-
/** The wallet receiving it, when known. Checked against `allowed_recipients`. */
|
|
22
|
-
toWalletId?: string | null;
|
|
23
|
-
/** The engagement this spend belongs to, when the caller names one. */
|
|
24
|
-
agreementId?: string | null;
|
|
25
|
-
}
|
|
26
|
-
export interface RejectedGrant {
|
|
27
|
-
grantId: string;
|
|
28
|
-
reason: string;
|
|
29
|
-
}
|
|
30
|
-
export interface GrantSelection {
|
|
31
|
-
grant: GrantView | null;
|
|
32
|
-
/** Every grant that was looked at and did not fit, with the reason. */
|
|
33
|
-
rejected: RejectedGrant[];
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Pick the grant to present, preferring the one that belongs to the engagement.
|
|
37
|
-
*
|
|
38
|
-
* Order among grants that all fit: the agreement's own grant first when the
|
|
39
|
-
* spend names one, then the standing grant. A standing grant is the buyer
|
|
40
|
-
* bootstrap and the right default; reaching for it while an agreement grant
|
|
41
|
-
* covers the same spend would bill the wrong budget.
|
|
42
|
-
*
|
|
43
|
-
* This is a preference, never a fence. Every grant here is already one the
|
|
44
|
-
* server said this wake may spend, so picking a different one cannot reach
|
|
45
|
-
* another party's authority — it would only bill the wrong budget of the same
|
|
46
|
-
* party. That is why the mismatch is no longer a rejection above.
|
|
47
|
-
*/
|
|
48
|
-
export declare function selectPaymentGrant(grants: readonly GrantView[], spend: SpendShape): GrantSelection;
|
|
49
|
-
/**
|
|
50
|
-
* The sentence an agent gets when nothing fits.
|
|
51
|
-
*
|
|
52
|
-
* It names every grant considered and why each was not it, because "no payment
|
|
53
|
-
* grant" is a condition and an agent cannot act on a condition — it retries,
|
|
54
|
-
* and every retry is a full model call. Which ceiling was in the way is the
|
|
55
|
-
* thing that tells the agent whether to ask for a bigger grant, name the
|
|
56
|
-
* agreement, or stop.
|
|
57
|
-
*/
|
|
58
|
-
export declare function describeNoGrant(verb: string, spend: SpendShape, rejected: readonly RejectedGrant[]): string;
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { grantCaveat } from './grants.js';
|
|
2
|
-
/**
|
|
3
|
-
* A caveat off a grant that may not carry the array at all.
|
|
4
|
-
*
|
|
5
|
-
* `grantCaveat` assumes `caveats` is populated, which the unified list read
|
|
6
|
-
* does. A caller handing us a partial view should get "no such caveat", not a
|
|
7
|
-
* crash — this runs on the spend path, where throwing would turn a missing
|
|
8
|
-
* field into a refused payment.
|
|
9
|
-
*/
|
|
10
|
-
function caveatValue(grant, type) {
|
|
11
|
-
const caveats = grant.caveats;
|
|
12
|
-
if (!Array.isArray(caveats))
|
|
13
|
-
return undefined;
|
|
14
|
-
return grantCaveat(grant, type);
|
|
15
|
-
}
|
|
16
|
-
function numericCaveat(grant, type) {
|
|
17
|
-
const raw = caveatValue(grant, type);
|
|
18
|
-
return typeof raw === 'number' && Number.isFinite(raw) ? raw : null;
|
|
19
|
-
}
|
|
20
|
-
function recipientCaveat(grant) {
|
|
21
|
-
const raw = caveatValue(grant, 'allowed_recipients');
|
|
22
|
-
if (!Array.isArray(raw))
|
|
23
|
-
return null;
|
|
24
|
-
const ids = raw.filter((r) => typeof r === 'string');
|
|
25
|
-
return ids.length ? ids : null;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Why this grant cannot authorize this spend, or null if it can.
|
|
29
|
-
*
|
|
30
|
-
* `daily_budget` is deliberately not checked. It is a ceiling on spend already
|
|
31
|
-
* made, which only the server can see — guessing here would refuse a grant that
|
|
32
|
-
* would have worked. The server validates it inside the same transaction as the
|
|
33
|
-
* ledger write, which is the only place the answer is not already stale.
|
|
34
|
-
*/
|
|
35
|
-
function whyNot(grant, spend) {
|
|
36
|
-
// A stated kind that is not `wallet` is disqualifying. An ABSENT one is not:
|
|
37
|
-
// `listGrants` asks the server for `scopeKind: 'wallet'`, so the rail has
|
|
38
|
-
// already answered, and treating a field the response did not populate as a
|
|
39
|
-
// wrong answer would refuse every grant. Same reading as an unread source
|
|
40
|
-
// wallet below — absence means unknown, never no.
|
|
41
|
-
if (grant.scope?.kind && grant.scope.kind !== 'wallet') {
|
|
42
|
-
return `is a ${grant.scope.kind} grant, not a wallet grant`;
|
|
43
|
-
}
|
|
44
|
-
if (spend.fromWalletId && grant.scope?.id && grant.scope.id !== spend.fromWalletId) {
|
|
45
|
-
return `is on wallet ${grant.scope.id}, and this spend leaves ${spend.fromWalletId}`;
|
|
46
|
-
}
|
|
47
|
-
const maxAmount = numericCaveat(grant, 'max_amount');
|
|
48
|
-
if (maxAmount !== null && spend.amount > maxAmount) {
|
|
49
|
-
return `caps a single spend at ${maxAmount}, and this one is ${spend.amount}`;
|
|
50
|
-
}
|
|
51
|
-
const allowed = recipientCaveat(grant);
|
|
52
|
-
if (allowed && spend.toWalletId && !allowed.includes(spend.toWalletId)) {
|
|
53
|
-
return `may only pay ${allowed.join(', ')}, and this one pays ${spend.toWalletId}`;
|
|
54
|
-
}
|
|
55
|
-
// A grant's agreement is NOT checked here, deliberately.
|
|
56
|
-
//
|
|
57
|
-
// It used to be refused when it did not match the spend's agreement, which
|
|
58
|
-
// made a customer's own grant unusable inside their second hire of the same
|
|
59
|
-
// agent, and made a standing grant unusable on any spend that named a deal.
|
|
60
|
-
// Whether a grant may be spent at all is the server's answer, and it turns on
|
|
61
|
-
// who GAVE the grant against who the agent is acting for — not on which of
|
|
62
|
-
// that party's deals is running. A list the server answered for this wake
|
|
63
|
-
// holds only grants it may spend, so there is nothing left to refuse here.
|
|
64
|
-
//
|
|
65
|
-
// The agreement still decides the ORDER below, which is a budget preference
|
|
66
|
-
// rather than a question of authority.
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Pick the grant to present, preferring the one that belongs to the engagement.
|
|
71
|
-
*
|
|
72
|
-
* Order among grants that all fit: the agreement's own grant first when the
|
|
73
|
-
* spend names one, then the standing grant. A standing grant is the buyer
|
|
74
|
-
* bootstrap and the right default; reaching for it while an agreement grant
|
|
75
|
-
* covers the same spend would bill the wrong budget.
|
|
76
|
-
*
|
|
77
|
-
* This is a preference, never a fence. Every grant here is already one the
|
|
78
|
-
* server said this wake may spend, so picking a different one cannot reach
|
|
79
|
-
* another party's authority — it would only bill the wrong budget of the same
|
|
80
|
-
* party. That is why the mismatch is no longer a rejection above.
|
|
81
|
-
*/
|
|
82
|
-
export function selectPaymentGrant(grants, spend) {
|
|
83
|
-
const rejected = [];
|
|
84
|
-
const fitting = [];
|
|
85
|
-
for (const grant of grants) {
|
|
86
|
-
const reason = whyNot(grant, spend);
|
|
87
|
-
if (reason)
|
|
88
|
-
rejected.push({ grantId: grant.grantId, reason });
|
|
89
|
-
else
|
|
90
|
-
fitting.push(grant);
|
|
91
|
-
}
|
|
92
|
-
if (fitting.length === 0)
|
|
93
|
-
return { grant: null, rejected };
|
|
94
|
-
// Explicit both ways, because the deal is no longer a fence: until it was
|
|
95
|
-
// removed, a deal-bound grant simply never reached this list on a spend that
|
|
96
|
-
// named nothing, so "prefer the standing grant" happened by accident of the
|
|
97
|
-
// rejection. Now it has to be said.
|
|
98
|
-
const standing = fitting.find((g) => !g.agreementId);
|
|
99
|
-
const preferred = spend.agreementId
|
|
100
|
-
? (fitting.find((g) => g.agreementId === spend.agreementId) ?? standing)
|
|
101
|
-
: standing;
|
|
102
|
-
return { grant: preferred ?? fitting[0], rejected };
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* The sentence an agent gets when nothing fits.
|
|
106
|
-
*
|
|
107
|
-
* It names every grant considered and why each was not it, because "no payment
|
|
108
|
-
* grant" is a condition and an agent cannot act on a condition — it retries,
|
|
109
|
-
* and every retry is a full model call. Which ceiling was in the way is the
|
|
110
|
-
* thing that tells the agent whether to ask for a bigger grant, name the
|
|
111
|
-
* agreement, or stop.
|
|
112
|
-
*/
|
|
113
|
-
export function describeNoGrant(verb, spend, rejected) {
|
|
114
|
-
if (rejected.length === 0) {
|
|
115
|
-
return (`${verb}: you hold no active payment grant, so this spend cannot be ` +
|
|
116
|
-
`authorized. The wallet owner issues one; nothing you can do from here.`);
|
|
117
|
-
}
|
|
118
|
-
const lines = rejected.map((r) => ` - ${r.grantId} ${r.reason}`).join('\n');
|
|
119
|
-
return (`${verb}: none of your ${rejected.length} active payment grant(s) covers ` +
|
|
120
|
-
`this spend of ${spend.amount}.\n${lines}`);
|
|
121
|
-
}
|