@ziggs-ai/api-client 0.3.1 → 0.4.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/artifacts.d.ts +3 -0
- package/dist/capabilities/artifacts.js +86 -0
- package/dist/capabilities/chat.d.ts +11 -0
- package/dist/capabilities/chat.js +38 -0
- package/dist/capabilities/connections.d.ts +4 -0
- package/dist/capabilities/connections.js +112 -0
- package/dist/capabilities/context.d.ts +23 -0
- package/dist/capabilities/context.js +220 -0
- package/dist/capabilities/discovery.d.ts +4 -0
- package/dist/capabilities/discovery.js +77 -0
- package/dist/capabilities/grants.d.ts +9 -0
- package/dist/capabilities/grants.js +77 -0
- package/dist/capabilities/index.d.ts +9 -0
- package/dist/capabilities/index.js +9 -0
- package/dist/capabilities/links.d.ts +17 -0
- package/dist/capabilities/links.js +193 -0
- package/dist/capabilities/payments.d.ts +11 -0
- package/dist/capabilities/payments.js +404 -0
- package/dist/capabilities/types.d.ts +88 -0
- package/dist/capabilities/types.js +24 -0
- package/dist/http/ConnectionsClient.d.ts +27 -1
- package/dist/http/ConnectionsClient.js +29 -0
- package/dist/http/GrantsClient.d.ts +16 -0
- package/dist/http/GrantsClient.js +3 -0
- package/dist/http/OrgsClient.d.ts +36 -0
- package/dist/http/OrgsClient.js +61 -0
- package/dist/http/PaymentsClient.d.ts +75 -10
- package/dist/http/PaymentsClient.js +26 -14
- package/dist/http/index.d.ts +5 -5
- package/dist/http/index.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +1 -1
- package/dist/http/grantRails.d.ts +0 -20
- package/dist/http/grantRails.js +0 -50
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import { PaymentsClient } from '../http/PaymentsClient.js';
|
|
2
|
+
import { rethrowWithContext, } from './types.js';
|
|
3
|
+
function client(env) {
|
|
4
|
+
const { operatorKey, agentId } = env.creds;
|
|
5
|
+
if (!operatorKey)
|
|
6
|
+
throw new Error('operatorKey missing from tool context');
|
|
7
|
+
return new PaymentsClient(operatorKey, agentId, env.baseUrl);
|
|
8
|
+
}
|
|
9
|
+
/** Lower the optional caveat args to the wire's caveat list (was written twice). */
|
|
10
|
+
function buildCaveats(args) {
|
|
11
|
+
const caveats = [];
|
|
12
|
+
if (args['maxAmount'] != null)
|
|
13
|
+
caveats.push({ type: 'max_amount', value: args['maxAmount'] });
|
|
14
|
+
if (args['dailyBudget'] != null)
|
|
15
|
+
caveats.push({ type: 'daily_budget', value: args['dailyBudget'] });
|
|
16
|
+
if (args['allowedRecipients'] != null)
|
|
17
|
+
caveats.push({ type: 'allowed_recipients', value: args['allowedRecipients'] });
|
|
18
|
+
if (args['expiresInSeconds'] != null)
|
|
19
|
+
caveats.push({ type: 'expires_at', value: Date.now() + args['expiresInSeconds'] * 1000 });
|
|
20
|
+
return caveats;
|
|
21
|
+
}
|
|
22
|
+
const grantCaveatParams = {
|
|
23
|
+
maxAmount: { type: 'number', description: 'Per-transfer ceiling in cents' },
|
|
24
|
+
dailyBudget: { type: 'number', description: 'Rolling daily budget in cents' },
|
|
25
|
+
allowedRecipients: {
|
|
26
|
+
type: 'array',
|
|
27
|
+
items: { type: 'string' },
|
|
28
|
+
description: 'Wallet ids the holder may pay',
|
|
29
|
+
},
|
|
30
|
+
expiresInSeconds: { type: 'number', description: 'Grant lifetime from now' },
|
|
31
|
+
};
|
|
32
|
+
export const paymentBalanceCapability = {
|
|
33
|
+
key: 'payment_balance',
|
|
34
|
+
names: { sdk: 'payment_balance', mcp: 'ziggs_payment_balance' },
|
|
35
|
+
descriptions: {
|
|
36
|
+
sdk: "Check the caller's current wallet balance and available balance (total minus active holds). Use before a transfer to confirm sufficient funds.",
|
|
37
|
+
mcp: "Check the caller's current wallet balance and available balance (total minus active holds). Use before a transfer to confirm sufficient funds.",
|
|
38
|
+
},
|
|
39
|
+
annotation: 'read-only',
|
|
40
|
+
params: {},
|
|
41
|
+
handler: async (_args, env) => {
|
|
42
|
+
try {
|
|
43
|
+
return await client(env).balance();
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
rethrowWithContext(e, 'Failed to get balance');
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
export const paymentResolveWalletCapability = {
|
|
51
|
+
key: 'payment_resolve_wallet',
|
|
52
|
+
names: { sdk: 'payment_resolve_wallet', mcp: 'ziggs_payment_resolve_wallet' },
|
|
53
|
+
descriptions: {
|
|
54
|
+
sdk: 'Look up a walletId by userId or agentId. Use before a transfer when you only know the recipient by their platform ID.',
|
|
55
|
+
mcp: 'Look up a walletId by userId or agentId. Use before a transfer when you only know the recipient by their platform ID.',
|
|
56
|
+
},
|
|
57
|
+
annotation: 'read-only',
|
|
58
|
+
params: {
|
|
59
|
+
userId: { type: 'string', description: 'User to resolve' },
|
|
60
|
+
agentId: { type: 'string', description: 'Agent to resolve' },
|
|
61
|
+
},
|
|
62
|
+
handler: async (args, env) => {
|
|
63
|
+
if (!args['userId'] && !args['agentId'])
|
|
64
|
+
throw new Error('Provide userId or agentId to resolve a wallet');
|
|
65
|
+
try {
|
|
66
|
+
const wallet = await client(env).resolve({
|
|
67
|
+
userId: args['userId'],
|
|
68
|
+
agentId: args['agentId'],
|
|
69
|
+
});
|
|
70
|
+
return {
|
|
71
|
+
walletId: wallet?.walletId || null,
|
|
72
|
+
ownerId: wallet?.ownerId || null,
|
|
73
|
+
currency: wallet?.currency || 'pez',
|
|
74
|
+
status: wallet?.status || null,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
rethrowWithContext(e, 'Failed to resolve wallet');
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
export const paymentTransferCapability = {
|
|
83
|
+
key: 'payment_transfer',
|
|
84
|
+
names: { sdk: 'payment_transfer', mcp: 'ziggs_payment_transfer' },
|
|
85
|
+
descriptions: {
|
|
86
|
+
sdk: 'Transfer funds to another wallet. Amounts are integer cents. Transfers above the wallet owner\'s policy pause with status "approval_required" — wait inline with payment_wait_for_approval when you expect a quick decision.',
|
|
87
|
+
mcp: 'Transfer funds to another wallet. Amounts are integer cents. As a delegate you spend under a payment grant the wallet owner issued (paymentGrantId — find yours via ziggs_list_grants scopeKind=wallet). Transfers above the owner\'s policy pause with status "approval_required": the human approves on the wallet page (it also shows in ziggs_pending_decisions) — you can wait inline with ziggs_payment_wait_for_approval, and you must NEVER approve your own transfer.',
|
|
88
|
+
},
|
|
89
|
+
annotation: 'write',
|
|
90
|
+
params: {
|
|
91
|
+
toWalletId: {
|
|
92
|
+
type: 'string',
|
|
93
|
+
required: true,
|
|
94
|
+
description: 'Destination wal_... id — or a userId/agentId to auto-resolve',
|
|
95
|
+
},
|
|
96
|
+
amount: { type: 'number', required: true, description: 'Amount in integer cents, > 0' },
|
|
97
|
+
description: { type: 'string', description: 'Human-readable transfer memo' },
|
|
98
|
+
idempotencyKey: {
|
|
99
|
+
type: 'string',
|
|
100
|
+
description: 'Client-supplied key to make retries safe (auto-generated when omitted)',
|
|
101
|
+
},
|
|
102
|
+
paymentGrantId: {
|
|
103
|
+
type: 'string',
|
|
104
|
+
description: 'Payment grant to spend under (required for agent-impersonated transfers)',
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
handler: async (args, env) => {
|
|
108
|
+
if (!args['toWalletId'])
|
|
109
|
+
throw new Error('toWalletId is required');
|
|
110
|
+
const amount = args['amount'];
|
|
111
|
+
if (!amount || amount <= 0)
|
|
112
|
+
throw new Error('amount must be positive');
|
|
113
|
+
let result;
|
|
114
|
+
try {
|
|
115
|
+
result = await client(env).transfer({
|
|
116
|
+
to: args['toWalletId'],
|
|
117
|
+
amount: Math.round(amount),
|
|
118
|
+
description: args['description'] || 'Agent-initiated transfer',
|
|
119
|
+
idempotencyKey: args['idempotencyKey'],
|
|
120
|
+
paymentGrantId: args['paymentGrantId'],
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
rethrowWithContext(e, 'Transfer failed');
|
|
125
|
+
}
|
|
126
|
+
if (result.status === 'approval_required') {
|
|
127
|
+
const base = {
|
|
128
|
+
status: 'approval_required',
|
|
129
|
+
approvalId: result.approvalId,
|
|
130
|
+
expiresAt: result.expiresAt,
|
|
131
|
+
reason: result.reason,
|
|
132
|
+
amount,
|
|
133
|
+
toWalletId: result.toWalletId,
|
|
134
|
+
};
|
|
135
|
+
// Same pause, surface-local guidance: the MCP delegate must surface the
|
|
136
|
+
// pending approval to the human (pull-only MCP has no push); the SDK
|
|
137
|
+
// runtime routes via structured next_actions.
|
|
138
|
+
if (env.surface === 'mcp') {
|
|
139
|
+
return {
|
|
140
|
+
...base,
|
|
141
|
+
note: 'Transfer paused: the wallet owner must approve this amount on the wallet page (also listed by ziggs_pending_decisions). Tell the human now (pull-only MCP has no push). ' +
|
|
142
|
+
'Wait inline with ziggs_payment_wait_for_approval when you expect a quick decision (≤2 min).',
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
...base,
|
|
147
|
+
message: 'Transfer paused: the wallet owner must approve this amount.',
|
|
148
|
+
next_actions: [
|
|
149
|
+
{
|
|
150
|
+
tool: 'payment_wait_for_approval',
|
|
151
|
+
when: 'You expect a quick decision (≤2 min) and can wait inline.',
|
|
152
|
+
args: { approvalId: result.approvalId, timeoutMs: 120000 },
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
tool: 'task_update_plan_step',
|
|
156
|
+
when: 'You want to abandon the transfer and route around it.',
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
status: 'transferred',
|
|
163
|
+
transactionId: result.transactionId,
|
|
164
|
+
amount,
|
|
165
|
+
toWalletId: result.toWalletId,
|
|
166
|
+
};
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
export const paymentWaitForApprovalCapability = {
|
|
170
|
+
key: 'payment_wait_for_approval',
|
|
171
|
+
names: { sdk: 'payment_wait_for_approval', mcp: 'ziggs_payment_wait_for_approval' },
|
|
172
|
+
descriptions: {
|
|
173
|
+
sdk: 'Poll a paused transfer (status "approval_required") until the human decides or the timeout passes. Returns executed | rejected | expired | timeout | gone.',
|
|
174
|
+
mcp: 'Poll a paused transfer (status "approval_required") until the human decides or the timeout passes. Returns executed | rejected | expired | timeout | gone. Use for quick decisions (≤2 min); for longer waits, stop and check again next session.',
|
|
175
|
+
},
|
|
176
|
+
annotation: 'read-only',
|
|
177
|
+
params: {
|
|
178
|
+
approvalId: {
|
|
179
|
+
type: 'string',
|
|
180
|
+
required: true,
|
|
181
|
+
description: 'Approval to wait on (from the paused transfer)',
|
|
182
|
+
},
|
|
183
|
+
timeoutMs: { type: 'number', description: 'Max wait, default 120000' },
|
|
184
|
+
pollMs: { type: 'number', description: 'Poll interval, default 3000 (min 500)' },
|
|
185
|
+
},
|
|
186
|
+
handler: async (args, env) => {
|
|
187
|
+
if (!args['approvalId'])
|
|
188
|
+
throw new Error('approvalId is required');
|
|
189
|
+
try {
|
|
190
|
+
const result = await client(env).waitForApproval(args['approvalId'], {
|
|
191
|
+
timeoutMs: args['timeoutMs'],
|
|
192
|
+
pollMs: args['pollMs'],
|
|
193
|
+
});
|
|
194
|
+
const loose = result;
|
|
195
|
+
return {
|
|
196
|
+
status: result.status,
|
|
197
|
+
approvalId: args['approvalId'],
|
|
198
|
+
transactionId: loose['transactionId'] || null,
|
|
199
|
+
approval: loose['approval'] || null,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
catch (e) {
|
|
203
|
+
rethrowWithContext(e, 'wait_for_approval failed');
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
export const paymentHoldCapability = {
|
|
208
|
+
key: 'payment_hold',
|
|
209
|
+
names: { sdk: 'payment_hold', mcp: 'ziggs_payment_hold' },
|
|
210
|
+
descriptions: {
|
|
211
|
+
sdk: 'Pre-authorize (escrow) funds without moving them. Use to reserve payment at agreement formation; release with payment_release once work is complete or to refund if work is cancelled.',
|
|
212
|
+
mcp: "Pre-authorize (escrow) funds without moving them. Use to reserve payment at agreement formation; release with ziggs_payment_release once work is complete, or refund if it's cancelled.",
|
|
213
|
+
},
|
|
214
|
+
annotation: 'write',
|
|
215
|
+
params: {
|
|
216
|
+
amount: { type: 'number', required: true, description: 'Amount in integer cents, > 0' },
|
|
217
|
+
description: { type: 'string', description: 'Human-readable hold memo' },
|
|
218
|
+
idempotencyKey: { type: 'string', description: 'Client-supplied retry-safety key' },
|
|
219
|
+
},
|
|
220
|
+
handler: async (args, env) => {
|
|
221
|
+
const amount = args['amount'];
|
|
222
|
+
if (!amount || amount <= 0)
|
|
223
|
+
throw new Error('amount must be positive');
|
|
224
|
+
try {
|
|
225
|
+
const result = await client(env).hold({
|
|
226
|
+
amount: Math.round(amount),
|
|
227
|
+
description: args['description'] || 'Agent escrow hold',
|
|
228
|
+
idempotencyKey: args['idempotencyKey'],
|
|
229
|
+
});
|
|
230
|
+
return {
|
|
231
|
+
status: 'held',
|
|
232
|
+
transactionId: result.transaction?.transactionId || null,
|
|
233
|
+
amount,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
catch (e) {
|
|
237
|
+
rethrowWithContext(e, 'Hold failed');
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
export const paymentReleaseCapability = {
|
|
242
|
+
key: 'payment_release',
|
|
243
|
+
names: { sdk: 'payment_release', mcp: 'ziggs_payment_release' },
|
|
244
|
+
descriptions: {
|
|
245
|
+
sdk: "Settle or refund an escrow hold. Use action='complete' to transfer held funds to toWalletId (work done), or action='refund' to return funds to the sender (work cancelled).",
|
|
246
|
+
mcp: "Settle or refund an escrow hold. action='complete' transfers held funds to toWalletId (work done); action='refund' returns funds to the sender (work cancelled).",
|
|
247
|
+
},
|
|
248
|
+
annotation: 'write',
|
|
249
|
+
params: {
|
|
250
|
+
holdId: {
|
|
251
|
+
type: 'string',
|
|
252
|
+
required: true,
|
|
253
|
+
description: 'Hold to settle (transactionId from the hold)',
|
|
254
|
+
},
|
|
255
|
+
action: {
|
|
256
|
+
type: 'string',
|
|
257
|
+
required: true,
|
|
258
|
+
enum: ['complete', 'refund'],
|
|
259
|
+
description: 'complete = pay out, refund = return',
|
|
260
|
+
},
|
|
261
|
+
toWalletId: { type: 'string', description: 'Destination wallet — required when action=complete' },
|
|
262
|
+
idempotencyKey: { type: 'string', description: 'Client-supplied retry-safety key' },
|
|
263
|
+
},
|
|
264
|
+
handler: async (args, env) => {
|
|
265
|
+
if (!args['holdId'])
|
|
266
|
+
throw new Error('holdId is required');
|
|
267
|
+
const action = args['action'];
|
|
268
|
+
if (action !== 'complete' && action !== 'refund')
|
|
269
|
+
throw new Error("action must be 'complete' or 'refund'");
|
|
270
|
+
if (action === 'complete' && !args['toWalletId'])
|
|
271
|
+
throw new Error('toWalletId is required when action=complete');
|
|
272
|
+
try {
|
|
273
|
+
const result = await client(env).release({
|
|
274
|
+
holdId: args['holdId'],
|
|
275
|
+
action,
|
|
276
|
+
toWalletId: args['toWalletId'],
|
|
277
|
+
idempotencyKey: args['idempotencyKey'],
|
|
278
|
+
});
|
|
279
|
+
return {
|
|
280
|
+
status: action === 'complete' ? 'settled' : 'refunded',
|
|
281
|
+
transactionId: result.transaction?.transactionId || null,
|
|
282
|
+
holdId: args['holdId'],
|
|
283
|
+
action,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
catch (e) {
|
|
287
|
+
rethrowWithContext(e, 'Release failed');
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
export const paymentIssueGrantCapability = {
|
|
292
|
+
key: 'payment_issue_grant',
|
|
293
|
+
names: { sdk: 'payment_issue_grant', mcp: 'ziggs_payment_issue_grant' },
|
|
294
|
+
descriptions: {
|
|
295
|
+
sdk: "Issue a payment grant delegating bounded spend from the operator's wallet to an agent holder. Caveats bound what the holder can do (max_amount, daily_budget, allowed_recipients, expiry). The holder spends by passing the grantId as paymentGrantId on transfers.",
|
|
296
|
+
mcp: "Issue a payment grant delegating bounded spend from the operator's wallet to an agent holder. Caveats bound what the holder can do (max_amount, daily_budget, allowed_recipients, expiry). The holder spends by passing the grantId as paymentGrantId on transfers.",
|
|
297
|
+
},
|
|
298
|
+
annotation: 'write',
|
|
299
|
+
params: {
|
|
300
|
+
holderId: { type: 'string', required: true, description: 'Agent that will hold the grant' },
|
|
301
|
+
...grantCaveatParams,
|
|
302
|
+
},
|
|
303
|
+
handler: async (args, env) => {
|
|
304
|
+
if (!args['holderId'])
|
|
305
|
+
throw new Error('holderId is required');
|
|
306
|
+
try {
|
|
307
|
+
const caveats = buildCaveats(args);
|
|
308
|
+
const { grant } = await client(env).issueGrant({
|
|
309
|
+
holderId: args['holderId'],
|
|
310
|
+
caveats,
|
|
311
|
+
});
|
|
312
|
+
return {
|
|
313
|
+
grantId: grant?.grantId || null,
|
|
314
|
+
holderId: grant?.holderId || args['holderId'],
|
|
315
|
+
caveats: grant?.caveats || caveats,
|
|
316
|
+
expiresAt: grant?.expiresAt || null,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
catch (e) {
|
|
320
|
+
rethrowWithContext(e, 'Failed to issue payment grant');
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
export const paymentAttenuateGrantCapability = {
|
|
325
|
+
key: 'payment_attenuate_grant',
|
|
326
|
+
names: { sdk: 'payment_attenuate_grant', mcp: 'ziggs_payment_attenuate_grant' },
|
|
327
|
+
descriptions: {
|
|
328
|
+
sdk: 'Re-delegate a payment grant you hold to another agent with TIGHTER caveats (narrowing only — the child can never exceed the parent). Use to pass a bounded spend slice to a sub-agent.',
|
|
329
|
+
mcp: 'Re-delegate a payment grant you hold to another agent with TIGHTER caveats (narrowing only — the child can never exceed the parent). Use to pass a bounded spend slice to a sub-agent.',
|
|
330
|
+
},
|
|
331
|
+
annotation: 'write',
|
|
332
|
+
params: {
|
|
333
|
+
grantId: { type: 'string', required: true, description: 'Parent grant to attenuate' },
|
|
334
|
+
holderId: {
|
|
335
|
+
type: 'string',
|
|
336
|
+
required: true,
|
|
337
|
+
description: 'Agent that will hold the narrowed grant',
|
|
338
|
+
},
|
|
339
|
+
...grantCaveatParams,
|
|
340
|
+
},
|
|
341
|
+
handler: async (args, env) => {
|
|
342
|
+
const grantId = args['grantId'];
|
|
343
|
+
if (!grantId)
|
|
344
|
+
throw new Error('grantId is required');
|
|
345
|
+
if (!args['holderId'])
|
|
346
|
+
throw new Error('holderId is required');
|
|
347
|
+
try {
|
|
348
|
+
const caveats = buildCaveats(args);
|
|
349
|
+
const { grant } = await client(env).attenuateGrant({
|
|
350
|
+
grantId,
|
|
351
|
+
holderId: args['holderId'],
|
|
352
|
+
caveats,
|
|
353
|
+
});
|
|
354
|
+
return {
|
|
355
|
+
grantId: grant?.grantId || null,
|
|
356
|
+
parentGrantId: grant?.parentGrantId || grantId,
|
|
357
|
+
holderId: grant?.holderId || args['holderId'],
|
|
358
|
+
caveats: grant?.caveats || caveats,
|
|
359
|
+
expiresAt: grant?.expiresAt || null,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
catch (e) {
|
|
363
|
+
rethrowWithContext(e, 'Failed to attenuate payment grant');
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
};
|
|
367
|
+
export const paymentRevokeGrantCapability = {
|
|
368
|
+
key: 'payment_revoke_grant',
|
|
369
|
+
names: { sdk: 'payment_revoke_grant', mcp: 'ziggs_payment_revoke_grant' },
|
|
370
|
+
descriptions: {
|
|
371
|
+
sdk: 'Revoke a payment grant (and its attenuated children). The holder can no longer spend under it.',
|
|
372
|
+
mcp: 'Revoke a payment grant (and its attenuated children). The holder can no longer spend under it.',
|
|
373
|
+
},
|
|
374
|
+
annotation: 'destructive',
|
|
375
|
+
params: {
|
|
376
|
+
grantId: { type: 'string', required: true, description: 'Grant to revoke' },
|
|
377
|
+
},
|
|
378
|
+
handler: async (args, env) => {
|
|
379
|
+
const grantId = args['grantId'];
|
|
380
|
+
if (!grantId)
|
|
381
|
+
throw new Error('grantId is required');
|
|
382
|
+
try {
|
|
383
|
+
const result = await client(env).revokeGrant(grantId);
|
|
384
|
+
return { status: 'revoked', grantId, revoked: result?.revoked ?? null };
|
|
385
|
+
}
|
|
386
|
+
catch (e) {
|
|
387
|
+
rethrowWithContext(e, 'Failed to revoke payment grant');
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
};
|
|
391
|
+
// ZIG-893 — payment_list_grants stays retired; the wallet rail is part of the
|
|
392
|
+
// unified list_grants capability (scopeKind: 'wallet'). Grant *mutations* stay
|
|
393
|
+
// on the payment rail above.
|
|
394
|
+
export const PAYMENT_CAPABILITIES = [
|
|
395
|
+
paymentBalanceCapability,
|
|
396
|
+
paymentTransferCapability,
|
|
397
|
+
paymentWaitForApprovalCapability,
|
|
398
|
+
paymentHoldCapability,
|
|
399
|
+
paymentReleaseCapability,
|
|
400
|
+
paymentResolveWalletCapability,
|
|
401
|
+
paymentIssueGrantCapability,
|
|
402
|
+
paymentAttenuateGrantCapability,
|
|
403
|
+
paymentRevokeGrantCapability,
|
|
404
|
+
];
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZIG-956 — one shared definition per SDK/MCP tool capability.
|
|
3
|
+
*
|
|
4
|
+
* The tool-surface-parity epic (ZIG-893→903) unified the HTTP layer here in
|
|
5
|
+
* api-client but left ~25 shared capabilities hand-written twice: once in the
|
|
6
|
+
* agent-sdk's defineTool DSL, once in ziggs-mcp zod. Each capability below is
|
|
7
|
+
* the single source for the tool's schema, validation, response shaping, and
|
|
8
|
+
* guidance; the two surfaces register it through thin adapters
|
|
9
|
+
* (agent-sdk `toolFromCapability`, ziggs-mcp `registerCapability`).
|
|
10
|
+
*
|
|
11
|
+
* Params are a neutral JSON-schema-flavoured DSL rather than zod because the
|
|
12
|
+
* two surfaces cannot share one zod: agent-sdk is on zod v4, ziggs-mcp on
|
|
13
|
+
* zod v3 (pinned by the MCP SDK's ZodRawShape), and api-client deliberately
|
|
14
|
+
* has no zod dependency. The SDK adapter feeds params straight into
|
|
15
|
+
* defineTool's converter; the MCP adapter lowers them to zod v3.
|
|
16
|
+
*/
|
|
17
|
+
import type { Creds } from '../types.js';
|
|
18
|
+
export type CapabilitySurface = 'sdk' | 'mcp';
|
|
19
|
+
/** read-only / write / destructive — lowered to MCP tool annotations. */
|
|
20
|
+
export type CapabilityAnnotation = 'read-only' | 'write' | 'destructive';
|
|
21
|
+
export interface CapabilityParam {
|
|
22
|
+
type: 'string' | 'number' | 'boolean' | 'object' | 'array';
|
|
23
|
+
required?: boolean;
|
|
24
|
+
description?: string;
|
|
25
|
+
/** Only for type 'string'. */
|
|
26
|
+
enum?: readonly string[];
|
|
27
|
+
/** Only for type 'array'. */
|
|
28
|
+
items?: {
|
|
29
|
+
type: 'string';
|
|
30
|
+
enum?: readonly string[];
|
|
31
|
+
} | {
|
|
32
|
+
type: 'object';
|
|
33
|
+
properties?: Record<string, unknown>;
|
|
34
|
+
required?: string[];
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Runtime context a surface adapter hands the shared handler. `creds.agentId`
|
|
39
|
+
* may be absent for agent-scoped operator keys (payments rail); capabilities
|
|
40
|
+
* that impersonate set `needsAgentId` so the adapter fails early instead.
|
|
41
|
+
*/
|
|
42
|
+
export interface CapabilityEnv {
|
|
43
|
+
creds: {
|
|
44
|
+
operatorKey: string;
|
|
45
|
+
agentId?: string;
|
|
46
|
+
};
|
|
47
|
+
/** SDK runner base-URL override (BACKEND_URL / ZIGGS_BACKEND_URL). */
|
|
48
|
+
baseUrl?: string;
|
|
49
|
+
/** Web-app origin for human-facing URLs (claim links etc.). */
|
|
50
|
+
webUrl?: string;
|
|
51
|
+
surface: CapabilitySurface;
|
|
52
|
+
}
|
|
53
|
+
export interface CapabilityDefinition {
|
|
54
|
+
/** Stable capability key (the parity-table base name), e.g. 'payment_transfer'. */
|
|
55
|
+
key: string;
|
|
56
|
+
/** Registered tool name per surface — the ZIG-903 parity table is the authority. */
|
|
57
|
+
names: {
|
|
58
|
+
sdk: string;
|
|
59
|
+
mcp: string;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Tool description per surface. Kept side by side deliberately: wording may
|
|
63
|
+
* reference surface-local tool names and MCP delegate-protocol guidance, but
|
|
64
|
+
* schema + handler can no longer drift.
|
|
65
|
+
*/
|
|
66
|
+
descriptions: {
|
|
67
|
+
sdk: string;
|
|
68
|
+
mcp: string;
|
|
69
|
+
};
|
|
70
|
+
annotation: CapabilityAnnotation;
|
|
71
|
+
params: Record<string, CapabilityParam>;
|
|
72
|
+
/** True when the handler impersonates an agent (X-Agent-Id required). */
|
|
73
|
+
needsAgentId?: boolean;
|
|
74
|
+
handler: (args: Record<string, unknown>, env: CapabilityEnv) => Promise<unknown>;
|
|
75
|
+
/** Pass-through flags for the SDK's defineTool options. */
|
|
76
|
+
sdkOptions?: {
|
|
77
|
+
isAgreementCreation?: boolean;
|
|
78
|
+
isGenericFallback?: boolean;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/** Creds for impersonated calls; adapters guarantee agentId when needsAgentId. */
|
|
82
|
+
export declare function fullCreds(env: CapabilityEnv): Creds;
|
|
83
|
+
/**
|
|
84
|
+
* Re-throw a client error with a capability-level prefix, preserving the HTTP
|
|
85
|
+
* status/body the clients attach (the SDK runtime and toolError classification
|
|
86
|
+
* both read them).
|
|
87
|
+
*/
|
|
88
|
+
export declare function rethrowWithContext(error: unknown, prefix: string): never;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Creds for impersonated calls; adapters guarantee agentId when needsAgentId. */
|
|
2
|
+
export function fullCreds(env) {
|
|
3
|
+
const { operatorKey, agentId } = env.creds;
|
|
4
|
+
if (!operatorKey)
|
|
5
|
+
throw new Error('operatorKey missing from tool context');
|
|
6
|
+
if (!agentId)
|
|
7
|
+
throw new Error('agentId missing from tool context');
|
|
8
|
+
return { operatorKey, agentId };
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Re-throw a client error with a capability-level prefix, preserving the HTTP
|
|
12
|
+
* status/body the clients attach (the SDK runtime and toolError classification
|
|
13
|
+
* both read them).
|
|
14
|
+
*/
|
|
15
|
+
export function rethrowWithContext(error, prefix) {
|
|
16
|
+
const e = error;
|
|
17
|
+
const wrapped = new Error(`${prefix}: ${e.message}`);
|
|
18
|
+
if (e.status !== undefined)
|
|
19
|
+
wrapped.status = e.status;
|
|
20
|
+
if (e.body !== undefined)
|
|
21
|
+
wrapped.body = e.body;
|
|
22
|
+
wrapped['cause'] = error;
|
|
23
|
+
throw wrapped;
|
|
24
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import 'dotenv/config';
|
|
2
|
+
import type { GrantView } from './grants.js';
|
|
2
3
|
export declare function assertNoLeakedConnectionSecret(serialized: string): void;
|
|
3
4
|
/** Thrown by ConnectionsClient with the HTTP status and raw body attached. */
|
|
4
5
|
export interface ConnectionsError extends Error {
|
|
@@ -11,6 +12,23 @@ export interface ConnectionProxyParams {
|
|
|
11
12
|
action: string;
|
|
12
13
|
payload?: unknown;
|
|
13
14
|
}
|
|
15
|
+
/** A grant row from the per-connection lister (GET /connections/:id/grants). */
|
|
16
|
+
export interface ConnectionGrant {
|
|
17
|
+
grantId?: string;
|
|
18
|
+
holderId?: string;
|
|
19
|
+
caveats?: unknown[];
|
|
20
|
+
[key: string]: unknown;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* ZIG-641 / ZIG-648 — every connection grant the acting agent holds, grouped by
|
|
24
|
+
* connection, via the unified GET /grants. `provider` comes from the grant's
|
|
25
|
+
* resolved scope label.
|
|
26
|
+
*/
|
|
27
|
+
export interface ConnectionWithGrants {
|
|
28
|
+
connectionId: string;
|
|
29
|
+
provider: string | null;
|
|
30
|
+
grants: GrantView[];
|
|
31
|
+
}
|
|
14
32
|
export interface McpConnectionRequestParams {
|
|
15
33
|
serverUrl: string;
|
|
16
34
|
tools: string[];
|
|
@@ -53,7 +71,15 @@ export declare class ConnectionsClient {
|
|
|
53
71
|
*/
|
|
54
72
|
listGrants({ connectionId }: {
|
|
55
73
|
connectionId: string;
|
|
56
|
-
}): Promise<
|
|
74
|
+
}): Promise<ConnectionGrant[]>;
|
|
75
|
+
/**
|
|
76
|
+
* ZIG-641 / ZIG-956 — cross-connection discovery over the unified GET /grants:
|
|
77
|
+
* every live connection grant this agent holds, grouped by connection, so a
|
|
78
|
+
* proxy caller's connectionId/grantId no longer has to arrive out of band.
|
|
79
|
+
* Moved here from ziggs-mcp's inline helper. The response is scanned
|
|
80
|
+
* defensively for leaked secrets, as `proxy` does.
|
|
81
|
+
*/
|
|
82
|
+
listForHolder(): Promise<ConnectionWithGrants[]>;
|
|
57
83
|
/** Issue a connection grant to an agent holder (connection owner side). */
|
|
58
84
|
issueGrant({ connectionId, holderId, caveats, }: {
|
|
59
85
|
connectionId: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import 'dotenv/config';
|
|
2
2
|
import { getBackendUrl } from '../utils/urlUtils.js';
|
|
3
3
|
import { buildOperatorHeaders } from './operatorHeaders.js';
|
|
4
|
+
import { GrantsClient } from './GrantsClient.js';
|
|
4
5
|
// ZIG-569 — defense-in-depth mirror of the backend leak-guard
|
|
5
6
|
// (assertProxyResponseDoesNotLeakTokens). The backend strips the *specific*
|
|
6
7
|
// vault token from the response; clients never see that token, so this layer
|
|
@@ -82,6 +83,34 @@ export class ConnectionsClient {
|
|
|
82
83
|
const grants = res['grants'] || [];
|
|
83
84
|
return grants.filter((g) => g['holderId'] === this.agentId);
|
|
84
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* ZIG-641 / ZIG-956 — cross-connection discovery over the unified GET /grants:
|
|
88
|
+
* every live connection grant this agent holds, grouped by connection, so a
|
|
89
|
+
* proxy caller's connectionId/grantId no longer has to arrive out of band.
|
|
90
|
+
* Moved here from ziggs-mcp's inline helper. The response is scanned
|
|
91
|
+
* defensively for leaked secrets, as `proxy` does.
|
|
92
|
+
*/
|
|
93
|
+
async listForHolder() {
|
|
94
|
+
const grantsClient = new GrantsClient(this.operatorKey, this.agentId, this.baseUrl);
|
|
95
|
+
// All pages of the agent's live connection grants (not just the first page).
|
|
96
|
+
const items = await grantsClient.listAllGrants({
|
|
97
|
+
scopeKind: 'connection',
|
|
98
|
+
health: 'active',
|
|
99
|
+
});
|
|
100
|
+
const byConnection = new Map();
|
|
101
|
+
for (const g of items) {
|
|
102
|
+
const connectionId = g.scope.id;
|
|
103
|
+
let group = byConnection.get(connectionId);
|
|
104
|
+
if (!group) {
|
|
105
|
+
group = { connectionId, provider: g.scope.label ?? null, grants: [] };
|
|
106
|
+
byConnection.set(connectionId, group);
|
|
107
|
+
}
|
|
108
|
+
group.grants.push(g);
|
|
109
|
+
}
|
|
110
|
+
const result = [...byConnection.values()];
|
|
111
|
+
assertNoLeakedConnectionSecret(JSON.stringify(result));
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
85
114
|
/** Issue a connection grant to an agent holder (connection owner side). */
|
|
86
115
|
async issueGrant({ connectionId, holderId, caveats, }) {
|
|
87
116
|
if (!connectionId)
|
|
@@ -16,10 +16,26 @@ export interface ListGrantsQuery {
|
|
|
16
16
|
cursor?: string;
|
|
17
17
|
limit?: number;
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* ZIG-893 / ZIG-956 — a grant rail the caller's operator key cannot read, named
|
|
21
|
+
* by the backend on `GET /grants` (it silently drops those rails from `items`,
|
|
22
|
+
* so the unified list tool names what it isn't entitled to instead of
|
|
23
|
+
* presenting a short list as if it were complete). Replaces the client-side
|
|
24
|
+
* mirror of the backend scope table + JWT decode (the retired grantRails.ts).
|
|
25
|
+
*/
|
|
26
|
+
export interface UnreadableRail {
|
|
27
|
+
rail: 'context' | 'connection' | 'wallet';
|
|
28
|
+
requiredScope: string;
|
|
29
|
+
}
|
|
19
30
|
export interface ListGrantsResult {
|
|
20
31
|
items: GrantView[];
|
|
21
32
|
nextCursor: string | null;
|
|
22
33
|
hasMore: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Rails the caller can't read, per the backend. Absent when the backend
|
|
36
|
+
* predates ZIG-956 or every requested rail was readable.
|
|
37
|
+
*/
|
|
38
|
+
unreadableRails?: UnreadableRail[];
|
|
23
39
|
}
|
|
24
40
|
/**
|
|
25
41
|
* ZIG-648 — unified grant listing across every rail. `GET /grants` returns the
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
import type { Creds } from '../types.js';
|
|
3
|
+
export interface MyOrg {
|
|
4
|
+
orgId: string;
|
|
5
|
+
name: string;
|
|
6
|
+
kind: string;
|
|
7
|
+
role?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* ZIG-739 / ZIG-956 — the operator's full org membership (not just granted
|
|
11
|
+
* scopes, which is all the grant listers see). Lets a delegate resolve an org
|
|
12
|
+
* name to an id and offer a pick-list instead of demanding a pasted org_... id.
|
|
13
|
+
* Moved here from ziggs-mcp so every surface rides the one client (ZIG-894).
|
|
14
|
+
*/
|
|
15
|
+
export declare function fetchMyOrgs(creds: Creds, baseUrl?: string): Promise<MyOrg[]>;
|
|
16
|
+
export type OrgResolution = {
|
|
17
|
+
status: 'ok';
|
|
18
|
+
orgId: string;
|
|
19
|
+
} | {
|
|
20
|
+
status: 'ambiguous';
|
|
21
|
+
matches: MyOrg[];
|
|
22
|
+
} | {
|
|
23
|
+
status: 'not-found';
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* ZIG-739 — resolve an org selector (exact org_... id OR a name/handle) against
|
|
27
|
+
* the operator's memberships. Exact id wins; otherwise case-insensitive name
|
|
28
|
+
* match. Ambiguous names return the candidates rather than guessing.
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolveOrgSelector(orgs: MyOrg[], selector: string): OrgResolution;
|
|
31
|
+
/**
|
|
32
|
+
* ZIG-640 / ZIG-956 — runtime acting org from the server (self-hire / agent
|
|
33
|
+
* row): GET /agents/claude-delegate/access. Moved here from ziggs-mcp's inline
|
|
34
|
+
* fetch (ZIG-894 "one client for every surface").
|
|
35
|
+
*/
|
|
36
|
+
export declare function fetchDelegateAccess(creds: Creds, baseUrl?: string): Promise<Record<string, unknown>>;
|