@vinaystwt/xmpp-http-interceptor 0.1.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/LICENSE +21 -0
- package/dist/config/src/index.d.ts +49 -0
- package/dist/config/src/index.js +117 -0
- package/dist/contract-runtime/src/index.d.ts +44 -0
- package/dist/contract-runtime/src/index.js +364 -0
- package/dist/http-interceptor/src/agents.d.ts +5 -0
- package/dist/http-interceptor/src/agents.js +88 -0
- package/dist/http-interceptor/src/agents.test.d.ts +1 -0
- package/dist/http-interceptor/src/agents.test.js +35 -0
- package/dist/http-interceptor/src/idempotency.d.ts +68 -0
- package/dist/http-interceptor/src/idempotency.js +149 -0
- package/dist/http-interceptor/src/idempotency.test.d.ts +1 -0
- package/dist/http-interceptor/src/idempotency.test.js +75 -0
- package/dist/http-interceptor/src/index.d.ts +10 -0
- package/dist/http-interceptor/src/index.js +870 -0
- package/dist/http-interceptor/src/index.test.d.ts +1 -0
- package/dist/http-interceptor/src/index.test.js +131 -0
- package/dist/http-interceptor/src/state.d.ts +17 -0
- package/dist/http-interceptor/src/state.js +188 -0
- package/dist/logger/src/index.d.ts +2 -0
- package/dist/logger/src/index.js +18 -0
- package/dist/payment-adapters/src/index.d.ts +27 -0
- package/dist/payment-adapters/src/index.js +512 -0
- package/dist/policy-engine/src/index.d.ts +8 -0
- package/dist/policy-engine/src/index.js +90 -0
- package/dist/router/src/index.d.ts +23 -0
- package/dist/router/src/index.js +432 -0
- package/dist/types/src/index.d.ts +343 -0
- package/dist/types/src/index.js +1 -0
- package/dist/wallet/src/index.d.ts +14 -0
- package/dist/wallet/src/index.js +250 -0
- package/package.json +56 -0
|
@@ -0,0 +1,870 @@
|
|
|
1
|
+
import { createRouter, estimateRouteCost } from '@xmpp/router';
|
|
2
|
+
import { getAgentTreasuryState, getAgentPolicySnapshot, getTreasurySnapshot, listAgentPolicySnapshots, getPolicyRuntimeSnapshot, recordTreasurySpend, recordSessionRouteEvent, } from '@xmpp/contract-runtime';
|
|
3
|
+
import { executePaymentRoute, preparePaymentExecution } from '@xmpp/payment-adapters';
|
|
4
|
+
import { config } from '@xmpp/config';
|
|
5
|
+
import { evaluatePolicyForRequest } from '@xmpp/policy-engine';
|
|
6
|
+
import { signXmppReceipt } from '@xmpp/wallet';
|
|
7
|
+
import { buildIdempotentReplay, clearIdempotentReservation, inspectIdempotency, storeIdempotentResponse, } from './idempotency.js';
|
|
8
|
+
import { buildBudgetSnapshot, getXmppOperatorState, listLocalSessions, recordXmppEvent, resetXmppOperatorState, upsertLocalSession, } from './state.js';
|
|
9
|
+
import { applyAgentPolicy, getXmppAgentProfile, listXmppAgentProfiles, mergeAgentPolicies, } from './agents.js';
|
|
10
|
+
const router = createRouter();
|
|
11
|
+
const sessionRegistry = new Map();
|
|
12
|
+
function resolveUrl(input) {
|
|
13
|
+
return typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;
|
|
14
|
+
}
|
|
15
|
+
async function parseChallenge(resp) {
|
|
16
|
+
const contentType = resp.headers.get('content-type') ?? '';
|
|
17
|
+
if (!contentType.includes('application/json')) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const body = (await resp.clone().json());
|
|
21
|
+
if (!body.kind || !body.service || !body.retryHeaderName || !body.retryHeaderValue) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
kind: body.kind,
|
|
26
|
+
service: body.service,
|
|
27
|
+
amountUsd: body.amountUsd ?? 0,
|
|
28
|
+
asset: 'USDC_TESTNET',
|
|
29
|
+
retryHeaderName: body.retryHeaderName,
|
|
30
|
+
retryHeaderValue: body.retryHeaderValue,
|
|
31
|
+
sessionId: body.sessionId,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function setMetadata(resp, metadata) {
|
|
35
|
+
Object.defineProperty(resp, '__xmpp', {
|
|
36
|
+
configurable: true,
|
|
37
|
+
enumerable: false,
|
|
38
|
+
value: metadata,
|
|
39
|
+
});
|
|
40
|
+
return resp;
|
|
41
|
+
}
|
|
42
|
+
export function getXmppMetadata(resp) {
|
|
43
|
+
return resp.__xmpp;
|
|
44
|
+
}
|
|
45
|
+
export { getXmppOperatorState, listLocalSessions, listXmppAgentProfiles };
|
|
46
|
+
export function resetXmppRuntimeState() {
|
|
47
|
+
sessionRegistry.clear();
|
|
48
|
+
resetXmppOperatorState();
|
|
49
|
+
}
|
|
50
|
+
export { resetXmppRuntimeState as resetXmppOperatorState };
|
|
51
|
+
function withLocalAgentDefaults(agent) {
|
|
52
|
+
return {
|
|
53
|
+
...agent,
|
|
54
|
+
enabled: agent.enabled ?? true,
|
|
55
|
+
policySource: agent.policySource ?? 'local',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function createSyntheticAgentProfile(policy) {
|
|
59
|
+
return {
|
|
60
|
+
agentId: policy.agentId,
|
|
61
|
+
displayName: policy.agentId,
|
|
62
|
+
role: 'shared',
|
|
63
|
+
description: 'Contract-defined treasury agent surfaced through xMPP runtime policy.',
|
|
64
|
+
dailyBudgetUsd: policy.dailyBudgetUsd,
|
|
65
|
+
allowedServices: [...policy.allowedServices],
|
|
66
|
+
preferredRoutes: [...policy.preferredRoutes],
|
|
67
|
+
autopayMethods: [...policy.autopayMethods],
|
|
68
|
+
enabled: policy.enabled,
|
|
69
|
+
policySource: 'contract',
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export async function getEffectiveXmppAgentProfile(agentId) {
|
|
73
|
+
const local = withLocalAgentDefaults(getXmppAgentProfile(agentId));
|
|
74
|
+
const runtime = await getAgentPolicySnapshot(local.agentId);
|
|
75
|
+
if (runtime.source === 'contract' && runtime.agentPolicy) {
|
|
76
|
+
return applyAgentPolicy(local, runtime.agentPolicy);
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
...local,
|
|
80
|
+
policySource: runtime.source === 'fallback' ? 'fallback' : local.policySource,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export async function listEffectiveXmppAgentProfiles() {
|
|
84
|
+
const localProfiles = listXmppAgentProfiles().map(withLocalAgentDefaults);
|
|
85
|
+
const contractPolicies = await listAgentPolicySnapshots();
|
|
86
|
+
if (contractPolicies.length === 0) {
|
|
87
|
+
return localProfiles;
|
|
88
|
+
}
|
|
89
|
+
const mergedProfiles = mergeAgentPolicies(localProfiles, contractPolicies);
|
|
90
|
+
const knownAgentIds = new Set(mergedProfiles.map((profile) => profile.agentId));
|
|
91
|
+
for (const policy of contractPolicies) {
|
|
92
|
+
if (!knownAgentIds.has(policy.agentId)) {
|
|
93
|
+
mergedProfiles.push(createSyntheticAgentProfile(policy));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return mergedProfiles;
|
|
97
|
+
}
|
|
98
|
+
function decodeEvidenceHeader(value) {
|
|
99
|
+
if (!value) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
return JSON.parse(Buffer.from(value, 'base64').toString('utf8'));
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function extractExplorerDetails(evidenceHeaders) {
|
|
110
|
+
if (!evidenceHeaders) {
|
|
111
|
+
return {};
|
|
112
|
+
}
|
|
113
|
+
const directTxHash = evidenceHeaders['x-payment-response-tx-hash'] ??
|
|
114
|
+
evidenceHeaders['x-mpp-transaction-hash'] ??
|
|
115
|
+
evidenceHeaders['x-payment-transaction'] ??
|
|
116
|
+
evidenceHeaders['x-mpp-receipt-tx'];
|
|
117
|
+
if (directTxHash) {
|
|
118
|
+
return {
|
|
119
|
+
txHash: directTxHash,
|
|
120
|
+
explorerUrl: `https://stellar.expert/explorer/testnet/tx/${directTxHash}`,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const paymentResponse = decodeEvidenceHeader(evidenceHeaders['payment-response']);
|
|
124
|
+
const paymentReceipt = decodeEvidenceHeader(evidenceHeaders['payment-receipt']);
|
|
125
|
+
const responseHash = (typeof paymentResponse?.transaction === 'string' && paymentResponse.transaction) ||
|
|
126
|
+
(typeof paymentReceipt?.reference === 'string' &&
|
|
127
|
+
/^[a-f0-9]{64}$/i.test(paymentReceipt.reference)
|
|
128
|
+
? paymentReceipt.reference
|
|
129
|
+
: undefined);
|
|
130
|
+
if (!responseHash) {
|
|
131
|
+
return {};
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
txHash: responseHash,
|
|
135
|
+
explorerUrl: `https://stellar.expert/explorer/testnet/tx/${responseHash}`,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function extractPaymentReference(evidenceHeaders) {
|
|
139
|
+
if (!evidenceHeaders) {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
const paymentResponse = decodeEvidenceHeader(evidenceHeaders['payment-response']);
|
|
143
|
+
const paymentReceipt = decodeEvidenceHeader(evidenceHeaders['payment-receipt']);
|
|
144
|
+
if (typeof paymentResponse?.transaction === 'string') {
|
|
145
|
+
return paymentResponse.transaction;
|
|
146
|
+
}
|
|
147
|
+
if (typeof paymentReceipt?.reference === 'string') {
|
|
148
|
+
return paymentReceipt.reference;
|
|
149
|
+
}
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
function getBooleanHeader(headers, name) {
|
|
153
|
+
const value = headers.get(name);
|
|
154
|
+
if (value == null) {
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
return value.toLowerCase() === 'true';
|
|
158
|
+
}
|
|
159
|
+
function extractEvidenceHeaders(headers) {
|
|
160
|
+
const evidenceEntries = [];
|
|
161
|
+
headers.forEach((value, key) => {
|
|
162
|
+
const normalized = key.toLowerCase();
|
|
163
|
+
if (normalized.startsWith('x-payment') ||
|
|
164
|
+
normalized.startsWith('x-mpp') ||
|
|
165
|
+
normalized.startsWith('x-xmpp-') ||
|
|
166
|
+
normalized.includes('receipt') ||
|
|
167
|
+
normalized.includes('transaction') ||
|
|
168
|
+
normalized === 'payment-response') {
|
|
169
|
+
evidenceEntries.push([key, value]);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
if (evidenceEntries.length === 0) {
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
return Object.fromEntries(evidenceEntries);
|
|
176
|
+
}
|
|
177
|
+
function attachSignedReceipt(input) {
|
|
178
|
+
if (!input.metadata) {
|
|
179
|
+
return input.metadata;
|
|
180
|
+
}
|
|
181
|
+
const explorer = extractExplorerDetails(input.metadata.evidenceHeaders);
|
|
182
|
+
const signedReceipt = signXmppReceipt({
|
|
183
|
+
receiptId: input.metadata.receiptId,
|
|
184
|
+
serviceId: input.serviceId,
|
|
185
|
+
url: input.url,
|
|
186
|
+
method: input.method,
|
|
187
|
+
route: input.route,
|
|
188
|
+
amountUsd: input.amountUsd,
|
|
189
|
+
txHash: explorer.txHash,
|
|
190
|
+
explorerUrl: explorer.explorerUrl,
|
|
191
|
+
paymentReference: extractPaymentReference(input.metadata.evidenceHeaders),
|
|
192
|
+
});
|
|
193
|
+
return signedReceipt
|
|
194
|
+
? {
|
|
195
|
+
...input.metadata,
|
|
196
|
+
signedReceipt,
|
|
197
|
+
}
|
|
198
|
+
: input.metadata;
|
|
199
|
+
}
|
|
200
|
+
async function evaluateSpendPolicy(input) {
|
|
201
|
+
const runtime = await getPolicyRuntimeSnapshot(input.serviceId);
|
|
202
|
+
const state = getXmppOperatorState();
|
|
203
|
+
const amountUsdCents = Math.round(input.amountUsd * 100);
|
|
204
|
+
const agentPolicySource = input.agent.policySource === 'merged' ? 'contract' : 'local';
|
|
205
|
+
const [contractTreasury, contractAgentTreasury] = await Promise.all([
|
|
206
|
+
getTreasurySnapshot(),
|
|
207
|
+
getAgentTreasuryState(input.agent.agentId),
|
|
208
|
+
]);
|
|
209
|
+
const effectiveSharedTreasuryUsd = Math.max(config.dailyBudgetUsd, contractTreasury?.sharedTreasuryUsd ?? 0);
|
|
210
|
+
const effectiveOperatorSpentUsd = Math.max(state.spentThisSessionUsd, contractTreasury?.totalSpentUsd ?? 0);
|
|
211
|
+
if (effectiveOperatorSpentUsd + input.amountUsd > effectiveSharedTreasuryUsd + 1e-9) {
|
|
212
|
+
return {
|
|
213
|
+
allowed: false,
|
|
214
|
+
reason: 'xMPP blocked automatic payment because it would exceed the operator daily budget.',
|
|
215
|
+
code: 'blocked-budget',
|
|
216
|
+
source: contractTreasury ? 'contract' : runtime.source,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
const agentState = state.agentStates.find((entry) => entry.agentId === input.agent.agentId);
|
|
220
|
+
const effectiveAgentSpentUsd = Math.max(agentState?.spentThisSessionUsd ?? 0, contractAgentTreasury?.spentUsd ?? 0);
|
|
221
|
+
if (effectiveAgentSpentUsd + input.amountUsd > input.agent.dailyBudgetUsd + 1e-9) {
|
|
222
|
+
return {
|
|
223
|
+
allowed: false,
|
|
224
|
+
reason: `${input.agent.displayName} would exceed its daily treasury allocation.`,
|
|
225
|
+
code: 'blocked-budget',
|
|
226
|
+
source: contractAgentTreasury ? 'contract' : agentPolicySource,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
if (runtime.globalPolicy &&
|
|
230
|
+
runtime.globalPolicy.maxSpendUsdCents > 0 &&
|
|
231
|
+
amountUsdCents > runtime.globalPolicy.maxSpendUsdCents) {
|
|
232
|
+
return {
|
|
233
|
+
allowed: false,
|
|
234
|
+
reason: 'xMPP policy blocked this payment because it exceeds the global spend ceiling.',
|
|
235
|
+
code: 'blocked-budget',
|
|
236
|
+
source: runtime.source,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
if (runtime.servicePolicy &&
|
|
240
|
+
runtime.servicePolicy.maxSpendUsdCents > 0 &&
|
|
241
|
+
amountUsdCents > runtime.servicePolicy.maxSpendUsdCents) {
|
|
242
|
+
return {
|
|
243
|
+
allowed: false,
|
|
244
|
+
reason: 'xMPP policy blocked this payment because it exceeds the service spend ceiling.',
|
|
245
|
+
code: 'blocked-budget',
|
|
246
|
+
source: runtime.source,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
if (input.route === 'mpp-session-reuse' &&
|
|
250
|
+
runtime.servicePolicy &&
|
|
251
|
+
!runtime.servicePolicy.allowSessionReuse) {
|
|
252
|
+
return {
|
|
253
|
+
allowed: false,
|
|
254
|
+
reason: 'xMPP policy disabled session reuse for this service.',
|
|
255
|
+
code: 'blocked-service',
|
|
256
|
+
source: runtime.source,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
function evaluateAgentAccess(input) {
|
|
262
|
+
const upperMethod = input.method.toUpperCase();
|
|
263
|
+
const source = input.agent.policySource === 'merged' ? 'contract' : 'local';
|
|
264
|
+
if (input.agent.enabled === false) {
|
|
265
|
+
return {
|
|
266
|
+
allowed: false,
|
|
267
|
+
reason: `${input.agent.displayName} is disabled by xMPP treasury policy.`,
|
|
268
|
+
code: 'blocked-agent',
|
|
269
|
+
source,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
if (!input.agent.autopayMethods.includes(upperMethod)) {
|
|
273
|
+
return {
|
|
274
|
+
allowed: false,
|
|
275
|
+
reason: `${input.agent.displayName} cannot auto-pay ${upperMethod} requests.`,
|
|
276
|
+
code: 'blocked-agent',
|
|
277
|
+
source,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
if (input.serviceId && !input.agent.allowedServices.includes(input.serviceId)) {
|
|
281
|
+
return {
|
|
282
|
+
allowed: false,
|
|
283
|
+
reason: `${input.agent.displayName} is not allowed to spend against ${input.serviceId}.`,
|
|
284
|
+
code: 'blocked-agent',
|
|
285
|
+
source,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
if (input.route && !input.agent.preferredRoutes.includes(input.route)) {
|
|
289
|
+
return {
|
|
290
|
+
allowed: false,
|
|
291
|
+
reason: `${input.agent.displayName} is not configured to use ${input.route}.`,
|
|
292
|
+
code: 'blocked-agent',
|
|
293
|
+
source,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
export async function xmppFetch(input, init, options) {
|
|
299
|
+
const url = resolveUrl(input);
|
|
300
|
+
const method = init?.method ?? 'GET';
|
|
301
|
+
const agent = await getEffectiveXmppAgentProfile(options?.agentId);
|
|
302
|
+
const policy = await evaluatePolicyForRequest({
|
|
303
|
+
url,
|
|
304
|
+
method,
|
|
305
|
+
serviceId: options?.serviceId,
|
|
306
|
+
});
|
|
307
|
+
if (!policy.allowed) {
|
|
308
|
+
recordXmppEvent({
|
|
309
|
+
agentId: agent.agentId,
|
|
310
|
+
url,
|
|
311
|
+
method,
|
|
312
|
+
serviceId: options?.serviceId ?? 'unknown-service',
|
|
313
|
+
route: 'x402',
|
|
314
|
+
status: 'denied',
|
|
315
|
+
amountUsd: 0,
|
|
316
|
+
projectedRequests: options?.projectedRequests ?? 1,
|
|
317
|
+
policyCode: policy.code,
|
|
318
|
+
});
|
|
319
|
+
const denied = new Response(JSON.stringify({
|
|
320
|
+
error: 'xMPP policy denied automatic payment for this request.',
|
|
321
|
+
policy,
|
|
322
|
+
}), {
|
|
323
|
+
status: 403,
|
|
324
|
+
headers: { 'content-type': 'application/json' },
|
|
325
|
+
});
|
|
326
|
+
return setMetadata(denied, {
|
|
327
|
+
route: 'x402',
|
|
328
|
+
retried: false,
|
|
329
|
+
policy,
|
|
330
|
+
budget: buildBudgetSnapshot({
|
|
331
|
+
agentId: agent.agentId,
|
|
332
|
+
agentProfile: agent,
|
|
333
|
+
url,
|
|
334
|
+
method,
|
|
335
|
+
serviceId: options?.serviceId,
|
|
336
|
+
route: 'x402',
|
|
337
|
+
projectedRequests: options?.projectedRequests,
|
|
338
|
+
}),
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
const agentAccess = evaluateAgentAccess({ agent, serviceId: options?.serviceId, method });
|
|
342
|
+
if (agentAccess) {
|
|
343
|
+
recordXmppEvent({
|
|
344
|
+
agentId: agent.agentId,
|
|
345
|
+
url,
|
|
346
|
+
method,
|
|
347
|
+
serviceId: options?.serviceId ?? 'unknown-service',
|
|
348
|
+
route: 'x402',
|
|
349
|
+
status: 'denied',
|
|
350
|
+
amountUsd: 0,
|
|
351
|
+
projectedRequests: options?.projectedRequests ?? 1,
|
|
352
|
+
policyCode: agentAccess.code,
|
|
353
|
+
});
|
|
354
|
+
const denied = new Response(JSON.stringify({
|
|
355
|
+
error: 'xMPP agent policy denied automatic payment for this request.',
|
|
356
|
+
policy: agentAccess,
|
|
357
|
+
}), {
|
|
358
|
+
status: 403,
|
|
359
|
+
headers: { 'content-type': 'application/json' },
|
|
360
|
+
});
|
|
361
|
+
return setMetadata(denied, {
|
|
362
|
+
route: 'x402',
|
|
363
|
+
retried: false,
|
|
364
|
+
policy: agentAccess,
|
|
365
|
+
budget: buildBudgetSnapshot({
|
|
366
|
+
agentId: agent.agentId,
|
|
367
|
+
agentProfile: agent,
|
|
368
|
+
url,
|
|
369
|
+
method,
|
|
370
|
+
serviceId: options?.serviceId,
|
|
371
|
+
route: 'x402',
|
|
372
|
+
projectedRequests: options?.projectedRequests,
|
|
373
|
+
}),
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
const idempotency = inspectIdempotency(url, method, init, options);
|
|
377
|
+
if (!idempotency.allowed) {
|
|
378
|
+
const idempotencyPolicy = {
|
|
379
|
+
allowed: false,
|
|
380
|
+
reason: idempotency.reason,
|
|
381
|
+
code: 'blocked-idempotency',
|
|
382
|
+
source: 'local',
|
|
383
|
+
};
|
|
384
|
+
recordXmppEvent({
|
|
385
|
+
agentId: agent.agentId,
|
|
386
|
+
url,
|
|
387
|
+
method,
|
|
388
|
+
serviceId: options?.serviceId ?? 'unknown-service',
|
|
389
|
+
route: 'x402',
|
|
390
|
+
status: 'denied',
|
|
391
|
+
amountUsd: 0,
|
|
392
|
+
projectedRequests: options?.projectedRequests ?? 1,
|
|
393
|
+
policyCode: idempotencyPolicy.code,
|
|
394
|
+
});
|
|
395
|
+
const status = idempotency.code === 'missing-key' ? 428 : 409;
|
|
396
|
+
const denied = new Response(JSON.stringify({
|
|
397
|
+
error: 'xMPP idempotency policy denied automatic payment for this request.',
|
|
398
|
+
policy: idempotencyPolicy,
|
|
399
|
+
}), {
|
|
400
|
+
status,
|
|
401
|
+
headers: { 'content-type': 'application/json' },
|
|
402
|
+
});
|
|
403
|
+
return setMetadata(denied, {
|
|
404
|
+
route: 'x402',
|
|
405
|
+
retried: false,
|
|
406
|
+
policy: idempotencyPolicy,
|
|
407
|
+
budget: buildBudgetSnapshot({
|
|
408
|
+
agentId: agent.agentId,
|
|
409
|
+
agentProfile: agent,
|
|
410
|
+
url,
|
|
411
|
+
method,
|
|
412
|
+
serviceId: options?.serviceId,
|
|
413
|
+
route: 'x402',
|
|
414
|
+
projectedRequests: options?.projectedRequests,
|
|
415
|
+
}),
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
if (idempotency.replay) {
|
|
419
|
+
const replay = buildIdempotentReplay(idempotency.response);
|
|
420
|
+
if (replay) {
|
|
421
|
+
return setMetadata(replay.response, replay.metadata);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
const preview = await router.preview({
|
|
425
|
+
url,
|
|
426
|
+
method,
|
|
427
|
+
serviceId: options?.serviceId,
|
|
428
|
+
projectedRequests: options?.projectedRequests,
|
|
429
|
+
streaming: options?.streaming,
|
|
430
|
+
});
|
|
431
|
+
const previewAccess = evaluateAgentAccess({
|
|
432
|
+
agent,
|
|
433
|
+
serviceId: options?.serviceId ?? preview.service?.serviceId,
|
|
434
|
+
method,
|
|
435
|
+
route: preview.route,
|
|
436
|
+
});
|
|
437
|
+
if (previewAccess) {
|
|
438
|
+
if (idempotency.idempotencyKey && idempotency.fingerprint) {
|
|
439
|
+
clearIdempotentReservation(idempotency.idempotencyKey, idempotency.fingerprint);
|
|
440
|
+
}
|
|
441
|
+
recordXmppEvent({
|
|
442
|
+
agentId: agent.agentId,
|
|
443
|
+
url,
|
|
444
|
+
method,
|
|
445
|
+
serviceId: options?.serviceId ?? preview.service?.serviceId ?? 'unknown-service',
|
|
446
|
+
route: preview.route,
|
|
447
|
+
status: 'denied',
|
|
448
|
+
amountUsd: 0,
|
|
449
|
+
projectedRequests: options?.projectedRequests ?? 1,
|
|
450
|
+
policyCode: previewAccess.code,
|
|
451
|
+
});
|
|
452
|
+
const denied = new Response(JSON.stringify({
|
|
453
|
+
error: 'xMPP agent policy denied automatic payment for this request.',
|
|
454
|
+
policy: previewAccess,
|
|
455
|
+
}), {
|
|
456
|
+
status: 403,
|
|
457
|
+
headers: { 'content-type': 'application/json' },
|
|
458
|
+
});
|
|
459
|
+
return setMetadata(denied, {
|
|
460
|
+
route: preview.route,
|
|
461
|
+
retried: false,
|
|
462
|
+
policy: previewAccess,
|
|
463
|
+
budget: buildBudgetSnapshot({
|
|
464
|
+
agentId: agent.agentId,
|
|
465
|
+
agentProfile: agent,
|
|
466
|
+
url,
|
|
467
|
+
method,
|
|
468
|
+
serviceId: options?.serviceId ?? preview.service?.serviceId,
|
|
469
|
+
route: preview.route,
|
|
470
|
+
projectedRequests: options?.projectedRequests,
|
|
471
|
+
}),
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
if ((process.env.XMPP_PAYMENT_EXECUTION_MODE ?? config.paymentExecutionMode) === 'testnet') {
|
|
475
|
+
const sessionKey = `${options?.serviceId ?? url}:${url}`;
|
|
476
|
+
const existingSession = sessionRegistry.get(sessionKey);
|
|
477
|
+
const hasReusableSession = Boolean(existingSession?.callCount);
|
|
478
|
+
const liveRoute = preview.route === 'mpp-session-open' && hasReusableSession
|
|
479
|
+
? 'mpp-session-reuse'
|
|
480
|
+
: preview.route;
|
|
481
|
+
const liveAmountUsd = estimateRouteCost({
|
|
482
|
+
route: liveRoute,
|
|
483
|
+
url,
|
|
484
|
+
method,
|
|
485
|
+
serviceId: options?.serviceId ?? preview.service?.serviceId,
|
|
486
|
+
projectedRequests: 1,
|
|
487
|
+
hasReusableSession,
|
|
488
|
+
});
|
|
489
|
+
const spendPolicy = await evaluateSpendPolicy({
|
|
490
|
+
agent,
|
|
491
|
+
serviceId: options?.serviceId ?? preview.service?.serviceId,
|
|
492
|
+
amountUsd: liveAmountUsd,
|
|
493
|
+
route: liveRoute,
|
|
494
|
+
});
|
|
495
|
+
if (spendPolicy) {
|
|
496
|
+
if (idempotency.idempotencyKey && idempotency.fingerprint) {
|
|
497
|
+
clearIdempotentReservation(idempotency.idempotencyKey, idempotency.fingerprint);
|
|
498
|
+
}
|
|
499
|
+
recordXmppEvent({
|
|
500
|
+
agentId: agent.agentId,
|
|
501
|
+
url,
|
|
502
|
+
method,
|
|
503
|
+
serviceId: options?.serviceId ?? preview.service?.serviceId ?? 'unknown-service',
|
|
504
|
+
route: liveRoute,
|
|
505
|
+
status: 'denied',
|
|
506
|
+
amountUsd: 0,
|
|
507
|
+
projectedRequests: options?.projectedRequests ?? 1,
|
|
508
|
+
policyCode: spendPolicy.code,
|
|
509
|
+
});
|
|
510
|
+
const denied = new Response(JSON.stringify({
|
|
511
|
+
error: 'xMPP spend policy denied automatic payment for this request.',
|
|
512
|
+
policy: spendPolicy,
|
|
513
|
+
}), {
|
|
514
|
+
status: 403,
|
|
515
|
+
headers: { 'content-type': 'application/json' },
|
|
516
|
+
});
|
|
517
|
+
return setMetadata(denied, {
|
|
518
|
+
route: liveRoute,
|
|
519
|
+
retried: false,
|
|
520
|
+
policy: spendPolicy,
|
|
521
|
+
budget: buildBudgetSnapshot({
|
|
522
|
+
agentId: agent.agentId,
|
|
523
|
+
agentProfile: agent,
|
|
524
|
+
url,
|
|
525
|
+
method,
|
|
526
|
+
serviceId: options?.serviceId ?? preview.service?.serviceId,
|
|
527
|
+
route: liveRoute,
|
|
528
|
+
projectedRequests: options?.projectedRequests,
|
|
529
|
+
hasReusableSession,
|
|
530
|
+
}),
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
const result = await executePaymentRoute(liveRoute, input, init);
|
|
534
|
+
const settled = result.metadata.status !== 'missing-config' && result.response.status !== 402;
|
|
535
|
+
if (settled && (liveRoute === 'mpp-session-open' || liveRoute === 'mpp-session-reuse')) {
|
|
536
|
+
const nextSession = {
|
|
537
|
+
sessionId: existingSession?.sessionId ?? sessionKey,
|
|
538
|
+
callCount: liveRoute === 'mpp-session-open' ? 1 : (existingSession?.callCount ?? 0) + 1,
|
|
539
|
+
};
|
|
540
|
+
sessionRegistry.set(sessionKey, nextSession);
|
|
541
|
+
upsertLocalSession(nextSession.sessionId, options?.serviceId ?? 'unknown-service', nextSession.callCount);
|
|
542
|
+
await recordSessionRouteEvent({
|
|
543
|
+
sessionId: nextSession.sessionId,
|
|
544
|
+
serviceId: options?.serviceId ?? 'unknown-service',
|
|
545
|
+
route: liveRoute,
|
|
546
|
+
status: liveRoute === 'mpp-session-open' ? 'open' : 'reused',
|
|
547
|
+
callCount: nextSession.callCount,
|
|
548
|
+
receiptId: result.metadata.receiptId,
|
|
549
|
+
totalAmountUsdCents: Math.round(liveAmountUsd * 100),
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
if (settled) {
|
|
553
|
+
const explorer = extractExplorerDetails(result.metadata.evidenceHeaders);
|
|
554
|
+
const execution = attachSignedReceipt({
|
|
555
|
+
metadata: result.metadata,
|
|
556
|
+
url,
|
|
557
|
+
method,
|
|
558
|
+
serviceId: options?.serviceId ?? preview.service?.serviceId ?? 'unknown-service',
|
|
559
|
+
route: liveRoute,
|
|
560
|
+
amountUsd: liveAmountUsd,
|
|
561
|
+
});
|
|
562
|
+
await recordTreasurySpend({
|
|
563
|
+
agentId: agent.agentId,
|
|
564
|
+
serviceId: options?.serviceId ?? preview.service?.serviceId ?? 'unknown-service',
|
|
565
|
+
route: liveRoute,
|
|
566
|
+
amountUsdCents: Math.round(liveAmountUsd * 100),
|
|
567
|
+
});
|
|
568
|
+
recordXmppEvent({
|
|
569
|
+
agentId: agent.agentId,
|
|
570
|
+
url,
|
|
571
|
+
method,
|
|
572
|
+
serviceId: options?.serviceId ?? preview.service?.serviceId ?? 'unknown-service',
|
|
573
|
+
route: liveRoute,
|
|
574
|
+
status: 'settled',
|
|
575
|
+
amountUsd: liveAmountUsd,
|
|
576
|
+
projectedRequests: options?.projectedRequests ?? 1,
|
|
577
|
+
receiptId: execution?.receiptId ?? result.metadata.receiptId,
|
|
578
|
+
txHash: explorer.txHash,
|
|
579
|
+
explorerUrl: explorer.explorerUrl,
|
|
580
|
+
sessionId: sessionRegistry.get(sessionKey)?.sessionId,
|
|
581
|
+
signedReceipt: execution?.signedReceipt,
|
|
582
|
+
feeSponsored: execution?.feeSponsored,
|
|
583
|
+
feeSponsorPublicKey: execution?.feeSponsorPublicKey,
|
|
584
|
+
settlementStrategy: execution?.settlementStrategy,
|
|
585
|
+
executionNote: execution?.executionNote,
|
|
586
|
+
});
|
|
587
|
+
const metadata = {
|
|
588
|
+
route: liveRoute,
|
|
589
|
+
retried: settled,
|
|
590
|
+
execution,
|
|
591
|
+
policy,
|
|
592
|
+
budget: buildBudgetSnapshot({
|
|
593
|
+
agentId: agent.agentId,
|
|
594
|
+
agentProfile: agent,
|
|
595
|
+
url,
|
|
596
|
+
method,
|
|
597
|
+
serviceId: options?.serviceId ?? preview.service?.serviceId,
|
|
598
|
+
route: liveRoute,
|
|
599
|
+
projectedRequests: options?.projectedRequests,
|
|
600
|
+
hasReusableSession,
|
|
601
|
+
}),
|
|
602
|
+
};
|
|
603
|
+
if (idempotency.idempotencyKey && idempotency.fingerprint) {
|
|
604
|
+
await storeIdempotentResponse(idempotency.idempotencyKey, idempotency.fingerprint, result.response, metadata);
|
|
605
|
+
}
|
|
606
|
+
return setMetadata(result.response, metadata);
|
|
607
|
+
}
|
|
608
|
+
if (idempotency.idempotencyKey && idempotency.fingerprint) {
|
|
609
|
+
clearIdempotentReservation(idempotency.idempotencyKey, idempotency.fingerprint);
|
|
610
|
+
}
|
|
611
|
+
return setMetadata(result.response, {
|
|
612
|
+
route: liveRoute,
|
|
613
|
+
retried: settled,
|
|
614
|
+
execution: result.metadata,
|
|
615
|
+
policy,
|
|
616
|
+
budget: buildBudgetSnapshot({
|
|
617
|
+
agentId: agent.agentId,
|
|
618
|
+
agentProfile: agent,
|
|
619
|
+
url,
|
|
620
|
+
method,
|
|
621
|
+
serviceId: options?.serviceId ?? preview.service?.serviceId,
|
|
622
|
+
route: liveRoute,
|
|
623
|
+
projectedRequests: options?.projectedRequests,
|
|
624
|
+
hasReusableSession,
|
|
625
|
+
}),
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
const headers = new Headers(init?.headers);
|
|
629
|
+
const firstResponse = await fetch(url, { ...init, headers });
|
|
630
|
+
if (firstResponse.status !== 402) {
|
|
631
|
+
recordXmppEvent({
|
|
632
|
+
agentId: agent.agentId,
|
|
633
|
+
url,
|
|
634
|
+
method,
|
|
635
|
+
serviceId: options?.serviceId ?? preview.service?.serviceId ?? 'unknown-service',
|
|
636
|
+
route: 'x402',
|
|
637
|
+
status: 'preview',
|
|
638
|
+
amountUsd: 0,
|
|
639
|
+
projectedRequests: options?.projectedRequests ?? 1,
|
|
640
|
+
});
|
|
641
|
+
const metadata = {
|
|
642
|
+
route: 'x402',
|
|
643
|
+
retried: false,
|
|
644
|
+
policy,
|
|
645
|
+
budget: buildBudgetSnapshot({
|
|
646
|
+
agentId: agent.agentId,
|
|
647
|
+
agentProfile: agent,
|
|
648
|
+
url,
|
|
649
|
+
method,
|
|
650
|
+
serviceId: options?.serviceId ?? preview.service?.serviceId,
|
|
651
|
+
route: 'x402',
|
|
652
|
+
projectedRequests: options?.projectedRequests,
|
|
653
|
+
}),
|
|
654
|
+
};
|
|
655
|
+
if (idempotency.idempotencyKey && idempotency.fingerprint) {
|
|
656
|
+
await storeIdempotentResponse(idempotency.idempotencyKey, idempotency.fingerprint, firstResponse, metadata);
|
|
657
|
+
}
|
|
658
|
+
return setMetadata(firstResponse, metadata);
|
|
659
|
+
}
|
|
660
|
+
const challenge = await parseChallenge(firstResponse);
|
|
661
|
+
if (!challenge) {
|
|
662
|
+
if (idempotency.idempotencyKey && idempotency.fingerprint) {
|
|
663
|
+
clearIdempotentReservation(idempotency.idempotencyKey, idempotency.fingerprint);
|
|
664
|
+
}
|
|
665
|
+
return setMetadata(firstResponse, {
|
|
666
|
+
route: 'x402',
|
|
667
|
+
retried: false,
|
|
668
|
+
policy,
|
|
669
|
+
budget: buildBudgetSnapshot({
|
|
670
|
+
agentId: agent.agentId,
|
|
671
|
+
agentProfile: agent,
|
|
672
|
+
url,
|
|
673
|
+
method,
|
|
674
|
+
serviceId: options?.serviceId ?? preview.service?.serviceId,
|
|
675
|
+
route: 'x402',
|
|
676
|
+
projectedRequests: options?.projectedRequests,
|
|
677
|
+
}),
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
if ((options?.maxAutoPayUsd ?? 1) < challenge.amountUsd) {
|
|
681
|
+
if (idempotency.idempotencyKey && idempotency.fingerprint) {
|
|
682
|
+
clearIdempotentReservation(idempotency.idempotencyKey, idempotency.fingerprint);
|
|
683
|
+
}
|
|
684
|
+
return setMetadata(firstResponse, {
|
|
685
|
+
route: 'x402',
|
|
686
|
+
challenge,
|
|
687
|
+
retried: false,
|
|
688
|
+
policy,
|
|
689
|
+
budget: buildBudgetSnapshot({
|
|
690
|
+
agentId: agent.agentId,
|
|
691
|
+
agentProfile: agent,
|
|
692
|
+
url,
|
|
693
|
+
method,
|
|
694
|
+
serviceId: options?.serviceId ?? preview.service?.serviceId,
|
|
695
|
+
route: 'x402',
|
|
696
|
+
projectedRequests: options?.projectedRequests,
|
|
697
|
+
}),
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
const sessionKey = `${challenge.service}:${url}`;
|
|
701
|
+
const existingSession = sessionRegistry.get(sessionKey);
|
|
702
|
+
const hasReusableSession = Boolean(existingSession?.callCount);
|
|
703
|
+
const decision = await router.chooseFromChallenge({
|
|
704
|
+
url,
|
|
705
|
+
method,
|
|
706
|
+
serviceId: options?.serviceId,
|
|
707
|
+
projectedRequests: options?.projectedRequests,
|
|
708
|
+
streaming: options?.streaming,
|
|
709
|
+
challenge,
|
|
710
|
+
hasReusableSession,
|
|
711
|
+
});
|
|
712
|
+
const payment = preparePaymentExecution(challenge, decision.route);
|
|
713
|
+
if (payment.metadata.status === 'missing-config') {
|
|
714
|
+
if (idempotency.idempotencyKey && idempotency.fingerprint) {
|
|
715
|
+
clearIdempotentReservation(idempotency.idempotencyKey, idempotency.fingerprint);
|
|
716
|
+
}
|
|
717
|
+
return setMetadata(firstResponse, {
|
|
718
|
+
route: decision.route,
|
|
719
|
+
challenge,
|
|
720
|
+
retried: false,
|
|
721
|
+
execution: payment.metadata,
|
|
722
|
+
policy,
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
const spendPolicy = await evaluateSpendPolicy({
|
|
726
|
+
agent,
|
|
727
|
+
serviceId: challenge.service,
|
|
728
|
+
amountUsd: challenge.amountUsd,
|
|
729
|
+
route: decision.route,
|
|
730
|
+
});
|
|
731
|
+
if (spendPolicy) {
|
|
732
|
+
if (idempotency.idempotencyKey && idempotency.fingerprint) {
|
|
733
|
+
clearIdempotentReservation(idempotency.idempotencyKey, idempotency.fingerprint);
|
|
734
|
+
}
|
|
735
|
+
recordXmppEvent({
|
|
736
|
+
agentId: agent.agentId,
|
|
737
|
+
url,
|
|
738
|
+
method,
|
|
739
|
+
serviceId: challenge.service,
|
|
740
|
+
route: decision.route,
|
|
741
|
+
status: 'denied',
|
|
742
|
+
amountUsd: 0,
|
|
743
|
+
projectedRequests: options?.projectedRequests ?? 1,
|
|
744
|
+
policyCode: spendPolicy.code,
|
|
745
|
+
});
|
|
746
|
+
const denied = new Response(JSON.stringify({
|
|
747
|
+
error: 'xMPP spend policy denied automatic payment for this request.',
|
|
748
|
+
policy: spendPolicy,
|
|
749
|
+
}), {
|
|
750
|
+
status: 403,
|
|
751
|
+
headers: { 'content-type': 'application/json' },
|
|
752
|
+
});
|
|
753
|
+
return setMetadata(denied, {
|
|
754
|
+
route: decision.route,
|
|
755
|
+
retried: false,
|
|
756
|
+
policy: spendPolicy,
|
|
757
|
+
budget: buildBudgetSnapshot({
|
|
758
|
+
agentId: agent.agentId,
|
|
759
|
+
agentProfile: agent,
|
|
760
|
+
url,
|
|
761
|
+
method,
|
|
762
|
+
serviceId: challenge.service,
|
|
763
|
+
route: decision.route,
|
|
764
|
+
projectedRequests: options?.projectedRequests,
|
|
765
|
+
hasReusableSession: hasReusableSession || decision.route === 'mpp-session-reuse',
|
|
766
|
+
}),
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
const retryHeaders = new Headers(init?.headers);
|
|
770
|
+
for (const [key, value] of Object.entries(payment.headers)) {
|
|
771
|
+
retryHeaders.set(key, value);
|
|
772
|
+
}
|
|
773
|
+
const retried = await fetch(url, {
|
|
774
|
+
...init,
|
|
775
|
+
headers: retryHeaders,
|
|
776
|
+
});
|
|
777
|
+
if (retried.status < 400) {
|
|
778
|
+
let currentSession = existingSession;
|
|
779
|
+
if (decision.route === 'mpp-session-open' || decision.route === 'mpp-session-reuse') {
|
|
780
|
+
const nextSession = {
|
|
781
|
+
sessionId: challenge.sessionId ?? existingSession?.sessionId ?? sessionKey,
|
|
782
|
+
callCount: decision.route === 'mpp-session-open' ? 1 : (existingSession?.callCount ?? 0) + 1,
|
|
783
|
+
};
|
|
784
|
+
sessionRegistry.set(sessionKey, nextSession);
|
|
785
|
+
upsertLocalSession(nextSession.sessionId, challenge.service, nextSession.callCount);
|
|
786
|
+
currentSession = nextSession;
|
|
787
|
+
}
|
|
788
|
+
const evidenceHeaders = extractEvidenceHeaders(retried.headers);
|
|
789
|
+
const execution = attachSignedReceipt({
|
|
790
|
+
metadata: {
|
|
791
|
+
...payment.metadata,
|
|
792
|
+
status: 'settled-testnet',
|
|
793
|
+
evidenceHeaders,
|
|
794
|
+
feeSponsored: getBooleanHeader(retried.headers, 'x-xmpp-fee-sponsored'),
|
|
795
|
+
feeSponsorPublicKey: retried.headers.get('x-xmpp-fee-sponsor') ?? undefined,
|
|
796
|
+
feeBumpPublicKey: retried.headers.get('x-xmpp-fee-bump-sponsor') ?? undefined,
|
|
797
|
+
},
|
|
798
|
+
url,
|
|
799
|
+
method,
|
|
800
|
+
serviceId: challenge.service,
|
|
801
|
+
route: decision.route,
|
|
802
|
+
amountUsd: challenge.amountUsd,
|
|
803
|
+
});
|
|
804
|
+
recordXmppEvent({
|
|
805
|
+
agentId: agent.agentId,
|
|
806
|
+
url,
|
|
807
|
+
method,
|
|
808
|
+
serviceId: challenge.service,
|
|
809
|
+
route: decision.route,
|
|
810
|
+
status: 'settled',
|
|
811
|
+
amountUsd: estimateRouteCost({
|
|
812
|
+
route: decision.route,
|
|
813
|
+
url,
|
|
814
|
+
method,
|
|
815
|
+
serviceId: challenge.service,
|
|
816
|
+
projectedRequests: 1,
|
|
817
|
+
hasReusableSession: hasReusableSession || decision.route === 'mpp-session-reuse',
|
|
818
|
+
}),
|
|
819
|
+
projectedRequests: options?.projectedRequests ?? 1,
|
|
820
|
+
receiptId: execution?.receiptId ?? payment.metadata.receiptId,
|
|
821
|
+
sessionId: currentSession?.sessionId,
|
|
822
|
+
signedReceipt: execution?.signedReceipt,
|
|
823
|
+
feeSponsored: execution?.feeSponsored,
|
|
824
|
+
feeSponsorPublicKey: execution?.feeSponsorPublicKey,
|
|
825
|
+
settlementStrategy: execution?.settlementStrategy,
|
|
826
|
+
executionNote: execution?.executionNote,
|
|
827
|
+
});
|
|
828
|
+
const metadata = {
|
|
829
|
+
route: decision.route,
|
|
830
|
+
challenge,
|
|
831
|
+
retried: true,
|
|
832
|
+
execution,
|
|
833
|
+
policy,
|
|
834
|
+
budget: buildBudgetSnapshot({
|
|
835
|
+
agentId: agent.agentId,
|
|
836
|
+
agentProfile: agent,
|
|
837
|
+
url,
|
|
838
|
+
method,
|
|
839
|
+
serviceId: challenge.service,
|
|
840
|
+
route: decision.route,
|
|
841
|
+
projectedRequests: options?.projectedRequests,
|
|
842
|
+
hasReusableSession: hasReusableSession || decision.route === 'mpp-session-reuse',
|
|
843
|
+
}),
|
|
844
|
+
};
|
|
845
|
+
if (idempotency.idempotencyKey && idempotency.fingerprint) {
|
|
846
|
+
await storeIdempotentResponse(idempotency.idempotencyKey, idempotency.fingerprint, retried, metadata);
|
|
847
|
+
}
|
|
848
|
+
return setMetadata(retried, metadata);
|
|
849
|
+
}
|
|
850
|
+
if (idempotency.idempotencyKey && idempotency.fingerprint) {
|
|
851
|
+
clearIdempotentReservation(idempotency.idempotencyKey, idempotency.fingerprint);
|
|
852
|
+
}
|
|
853
|
+
return setMetadata(retried, {
|
|
854
|
+
route: decision.route,
|
|
855
|
+
challenge,
|
|
856
|
+
retried: true,
|
|
857
|
+
execution: payment.metadata,
|
|
858
|
+
policy,
|
|
859
|
+
budget: buildBudgetSnapshot({
|
|
860
|
+
agentId: agent.agentId,
|
|
861
|
+
agentProfile: agent,
|
|
862
|
+
url,
|
|
863
|
+
method,
|
|
864
|
+
serviceId: challenge.service,
|
|
865
|
+
route: decision.route,
|
|
866
|
+
projectedRequests: options?.projectedRequests,
|
|
867
|
+
hasReusableSession: hasReusableSession || decision.route === 'mpp-session-reuse',
|
|
868
|
+
}),
|
|
869
|
+
});
|
|
870
|
+
}
|