@tangle-network/agent-gateway 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -2
- package/dist/chunk-GITV7CPT.js +84 -0
- package/dist/chunk-GITV7CPT.js.map +1 -0
- package/dist/chunk-J5SDVHOL.js +104 -0
- package/dist/chunk-J5SDVHOL.js.map +1 -0
- package/dist/chunk-MP6IIAIA.js +5651 -0
- package/dist/chunk-MP6IIAIA.js.map +1 -0
- package/dist/index.d.ts +70 -10
- package/dist/index.js +303 -21
- package/dist/index.js.map +1 -1
- package/dist/middleware.d.ts +7 -2
- package/dist/middleware.js +3 -2
- package/dist/nonce-store.d.ts +47 -11
- package/dist/nonce-store.js +9 -3
- package/dist/observer-types-A0RtA8uL.d.ts +95 -0
- package/dist/observer.d.ts +79 -0
- package/dist/observer.js +11 -0
- package/dist/observer.js.map +1 -0
- package/dist/{types-DEsMmS-X.d.ts → types-BHISsm7D.d.ts} +414 -170
- package/dist/types.d.ts +2 -1
- package/package.json +1 -1
- package/src/a2a/execution-fence.ts +162 -0
- package/src/a2a/handler.ts +506 -560
- package/src/a2a/message-send-execution.ts +241 -0
- package/src/a2a/message-stream-execution.ts +392 -0
- package/src/a2a/payment-recovery.ts +431 -0
- package/src/a2a/push-config-methods.ts +158 -0
- package/src/a2a/push-notifications.ts +172 -22
- package/src/a2a/task-cancellation.ts +50 -0
- package/src/a2a/task-finalization.ts +451 -0
- package/src/a2a/task-lifecycle.ts +54 -0
- package/src/a2a/task-methods.ts +163 -0
- package/src/a2a/task-push-delivery.ts +119 -0
- package/src/a2a/task-recovery.ts +11 -0
- package/src/a2a/task-state.ts +99 -0
- package/src/a2a/task-store-sql.ts +222 -24
- package/src/a2a/task-store.ts +58 -1
- package/src/a2a/task-submission-recovery.ts +178 -0
- package/src/a2a/types.ts +1 -0
- package/src/dispatch-authorization.ts +437 -0
- package/src/dispatch-payment-recovery.ts +248 -0
- package/src/dispatch-payment.ts +425 -0
- package/src/dispatch-pricing.ts +108 -0
- package/src/dispatch-sandbox.ts +422 -0
- package/src/dispatch-settlement.ts +139 -0
- package/src/dispatch-types.ts +81 -0
- package/src/dispatch.ts +35 -483
- package/src/index.ts +57 -1
- package/src/middleware.ts +307 -26
- package/src/mpp-payment.ts +117 -0
- package/src/nonce-store.ts +122 -20
- package/src/observer-types.ts +63 -0
- package/src/observer.ts +3 -63
- package/src/payment-operations.ts +485 -0
- package/src/payment-recovery-sql.ts +108 -0
- package/src/payment-recovery-worker.ts +488 -0
- package/src/payment-recovery.ts +331 -0
- package/src/payment-types.ts +48 -0
- package/src/types.ts +144 -46
- package/src/verify.ts +233 -71
- package/dist/chunk-M7ZJAK4K.js +0 -53
- package/dist/chunk-M7ZJAK4K.js.map +0 -1
- package/dist/chunk-Q4YAIEZY.js +0 -1763
- package/dist/chunk-Q4YAIEZY.js.map +0 -1
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import type { MppChargeOperation } from './mpp-payment'
|
|
2
|
+
import type { PaymentOperation } from './payment-operations'
|
|
3
|
+
import type {
|
|
4
|
+
PaymentMethod,
|
|
5
|
+
PaymentSettlementBasis,
|
|
6
|
+
SandboxExecutionBudget,
|
|
7
|
+
SandboxUsageReceipt,
|
|
8
|
+
} from './payment-types'
|
|
9
|
+
|
|
10
|
+
export type { PaymentSettlementBasis } from './payment-types'
|
|
11
|
+
|
|
12
|
+
export const PAYMENT_RECOVERY_VERSION = 1 as const
|
|
13
|
+
|
|
14
|
+
export type PaymentRecoveryState =
|
|
15
|
+
| 'claiming'
|
|
16
|
+
| 'claimed'
|
|
17
|
+
| 'executing'
|
|
18
|
+
| 'retained'
|
|
19
|
+
| 'settling'
|
|
20
|
+
| 'releasing'
|
|
21
|
+
| 'reconciled'
|
|
22
|
+
|
|
23
|
+
export interface SerializedPaymentOperation {
|
|
24
|
+
protocolVersion: 2
|
|
25
|
+
operationId: string
|
|
26
|
+
acquiredByRequestId: string
|
|
27
|
+
executionStartedAt?: number
|
|
28
|
+
retentionReason?: string
|
|
29
|
+
nonceKey: string
|
|
30
|
+
authorizationId: string
|
|
31
|
+
reservedAmount: string
|
|
32
|
+
settledAmount: string
|
|
33
|
+
refundAmount: string
|
|
34
|
+
expiresAt: number
|
|
35
|
+
state: PaymentOperation['state']
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface PaymentRecoveryAttribution {
|
|
39
|
+
requestId: string
|
|
40
|
+
agentId: string
|
|
41
|
+
agentSlug: string
|
|
42
|
+
consumerId: string
|
|
43
|
+
paymentMethod: PaymentMethod
|
|
44
|
+
startMs: number
|
|
45
|
+
pricePerTokenUsd: number
|
|
46
|
+
platformFeePercent: number
|
|
47
|
+
requiredAmount: string
|
|
48
|
+
currencyDecimals: number
|
|
49
|
+
maxOutputTokens: number
|
|
50
|
+
executionBudget: SandboxExecutionBudget
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type PaymentRecoveryTarget =
|
|
54
|
+
| {
|
|
55
|
+
kind: 'x402'
|
|
56
|
+
operationId: string
|
|
57
|
+
operation?: SerializedPaymentOperation
|
|
58
|
+
}
|
|
59
|
+
| {
|
|
60
|
+
kind: 'mpp-charge'
|
|
61
|
+
method: string
|
|
62
|
+
operationId: string
|
|
63
|
+
operation?: MppChargeOperation
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Durable outbox row for one payment identity. Rows are never deleted here. */
|
|
67
|
+
export interface PaymentRecoveryRecord {
|
|
68
|
+
version: typeof PAYMENT_RECOVERY_VERSION
|
|
69
|
+
id: string
|
|
70
|
+
revision: number
|
|
71
|
+
state: PaymentRecoveryState
|
|
72
|
+
payment: PaymentRecoveryTarget
|
|
73
|
+
attribution: PaymentRecoveryAttribution
|
|
74
|
+
workStarted: boolean
|
|
75
|
+
/** Earliest time a missing receipt may settle at the quoted ceiling. */
|
|
76
|
+
fallbackAt?: number
|
|
77
|
+
usage?: SandboxUsageReceipt
|
|
78
|
+
usageRecorded: boolean
|
|
79
|
+
settlementBasis?: PaymentSettlementBasis
|
|
80
|
+
reason?: string
|
|
81
|
+
attempts: number
|
|
82
|
+
lastError?: string
|
|
83
|
+
nextAttemptAt: number
|
|
84
|
+
lease?: { id: string; expiresAt: number }
|
|
85
|
+
createdAt: number
|
|
86
|
+
updatedAt: number
|
|
87
|
+
reconciledAt?: number
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface PaymentRecoveryStore {
|
|
91
|
+
createIfAbsent(record: PaymentRecoveryRecord): Promise<boolean>
|
|
92
|
+
get(id: string): Promise<PaymentRecoveryRecord | undefined>
|
|
93
|
+
compareAndSet(
|
|
94
|
+
expected: PaymentRecoveryRecord,
|
|
95
|
+
next: PaymentRecoveryRecord,
|
|
96
|
+
): Promise<boolean>
|
|
97
|
+
listDue(now: number, limit: number): Promise<PaymentRecoveryRecord[]>
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface PaymentRecoveryConfig {
|
|
101
|
+
store: PaymentRecoveryStore
|
|
102
|
+
/** Claimed payment with no execution becomes recoverable after this delay. */
|
|
103
|
+
staleRequestMs?: number
|
|
104
|
+
/** Work without a final receipt settles at the quoted ceiling after this delay. */
|
|
105
|
+
receiptTimeoutMs?: number
|
|
106
|
+
/** Failed provider recovery waits this long before its next attempt. */
|
|
107
|
+
retryDelayMs?: number
|
|
108
|
+
/** One recovery worker owns a row for this duration. */
|
|
109
|
+
leaseMs?: number
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export const DEFAULT_STALE_REQUEST_MS = 30_000
|
|
113
|
+
export const DEFAULT_RECEIPT_TIMEOUT_MS = 5 * 60_000
|
|
114
|
+
export const DEFAULT_RECOVERY_RETRY_MS = 30_000
|
|
115
|
+
export const DEFAULT_RECOVERY_LEASE_MS = 30_000
|
|
116
|
+
|
|
117
|
+
/** Atomic single-process store for tests and explicit local demo mode. */
|
|
118
|
+
export class MemoryPaymentRecoveryStore implements PaymentRecoveryStore {
|
|
119
|
+
private readonly records = new Map<string, PaymentRecoveryRecord>()
|
|
120
|
+
|
|
121
|
+
async createIfAbsent(record: PaymentRecoveryRecord): Promise<boolean> {
|
|
122
|
+
if (this.records.has(record.id)) return false
|
|
123
|
+
this.records.set(record.id, clone(record))
|
|
124
|
+
return true
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async get(id: string): Promise<PaymentRecoveryRecord | undefined> {
|
|
128
|
+
const record = this.records.get(id)
|
|
129
|
+
return record ? clone(record) : undefined
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async compareAndSet(
|
|
133
|
+
expected: PaymentRecoveryRecord,
|
|
134
|
+
next: PaymentRecoveryRecord,
|
|
135
|
+
): Promise<boolean> {
|
|
136
|
+
const current = this.records.get(expected.id)
|
|
137
|
+
if (!current || current.revision !== expected.revision) return false
|
|
138
|
+
this.records.set(expected.id, clone(next))
|
|
139
|
+
return true
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async listDue(now: number, limit: number): Promise<PaymentRecoveryRecord[]> {
|
|
143
|
+
return [...this.records.values()]
|
|
144
|
+
.filter((record) => record.state !== 'reconciled' && record.nextAttemptAt <= now)
|
|
145
|
+
.sort((left, right) => left.nextAttemptAt - right.nextAttemptAt)
|
|
146
|
+
.slice(0, limit)
|
|
147
|
+
.map(clone)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export class PaymentRecoveryFenceError extends Error {
|
|
152
|
+
constructor(id: string) {
|
|
153
|
+
super(`payment recovery fence was lost for ${id}`)
|
|
154
|
+
this.name = 'PaymentRecoveryFenceError'
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export class PaymentRecoveryReplayError extends Error {
|
|
159
|
+
constructor(id: string) {
|
|
160
|
+
super(`payment recovery identity was already reconciled: ${id}`)
|
|
161
|
+
this.name = 'PaymentRecoveryReplayError'
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Update only while the caller still owns the durable row fence. */
|
|
166
|
+
export async function updateOwnedPaymentRecovery(
|
|
167
|
+
store: PaymentRecoveryStore,
|
|
168
|
+
id: string,
|
|
169
|
+
fenceId: string,
|
|
170
|
+
update: (record: PaymentRecoveryRecord) => PaymentRecoveryRecord,
|
|
171
|
+
now = Date.now(),
|
|
172
|
+
): Promise<PaymentRecoveryRecord> {
|
|
173
|
+
for (let attempt = 0; attempt < 16; attempt += 1) {
|
|
174
|
+
const current = await store.get(id)
|
|
175
|
+
if (!current) throw new Error(`payment recovery record ${id} was not found`)
|
|
176
|
+
if (current.state === 'reconciled' || current.lease?.id !== fenceId) {
|
|
177
|
+
throw new PaymentRecoveryFenceError(id)
|
|
178
|
+
}
|
|
179
|
+
const candidate = update(clone(current))
|
|
180
|
+
assertRecoveryUpdate(current, candidate)
|
|
181
|
+
const next: PaymentRecoveryRecord = {
|
|
182
|
+
...candidate,
|
|
183
|
+
id: current.id,
|
|
184
|
+
version: PAYMENT_RECOVERY_VERSION,
|
|
185
|
+
revision: current.revision + 1,
|
|
186
|
+
createdAt: current.createdAt,
|
|
187
|
+
updatedAt: now,
|
|
188
|
+
}
|
|
189
|
+
if (await store.compareAndSet(current, next)) return next
|
|
190
|
+
}
|
|
191
|
+
throw new Error(`payment recovery record ${id} changed too many times`)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const PAYMENT_RECOVERY_TRANSITIONS: Record<PaymentRecoveryState, ReadonlySet<PaymentRecoveryState>> = {
|
|
195
|
+
claiming: new Set(['claiming', 'claimed', 'releasing', 'reconciled']),
|
|
196
|
+
claimed: new Set(['claimed', 'executing', 'releasing', 'reconciled']),
|
|
197
|
+
executing: new Set(['executing', 'retained', 'settling', 'releasing', 'reconciled']),
|
|
198
|
+
retained: new Set(['retained', 'settling', 'reconciled']),
|
|
199
|
+
settling: new Set(['settling', 'reconciled']),
|
|
200
|
+
releasing: new Set(['releasing', 'reconciled']),
|
|
201
|
+
reconciled: new Set(['reconciled']),
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function assertRecoveryUpdate(
|
|
205
|
+
current: PaymentRecoveryRecord,
|
|
206
|
+
candidate: PaymentRecoveryRecord,
|
|
207
|
+
): void {
|
|
208
|
+
if (!PAYMENT_RECOVERY_TRANSITIONS[current.state].has(candidate.state)) {
|
|
209
|
+
throw new Error(`invalid payment recovery transition ${current.state} -> ${candidate.state}`)
|
|
210
|
+
}
|
|
211
|
+
if (JSON.stringify(current.attribution) !== JSON.stringify(candidate.attribution)) {
|
|
212
|
+
throw new Error('payment recovery attribution is immutable')
|
|
213
|
+
}
|
|
214
|
+
if (
|
|
215
|
+
current.payment.kind !== candidate.payment.kind ||
|
|
216
|
+
current.payment.operationId !== candidate.payment.operationId ||
|
|
217
|
+
(current.payment.kind === 'mpp-charge' &&
|
|
218
|
+
candidate.payment.kind === 'mpp-charge' &&
|
|
219
|
+
current.payment.method !== candidate.payment.method)
|
|
220
|
+
) {
|
|
221
|
+
throw new Error('payment recovery identity is immutable')
|
|
222
|
+
}
|
|
223
|
+
if (current.workStarted && !candidate.workStarted) {
|
|
224
|
+
throw new Error('payment recovery cannot forget started work')
|
|
225
|
+
}
|
|
226
|
+
if (current.usageRecorded && !candidate.usageRecorded) {
|
|
227
|
+
throw new Error('payment recovery cannot forget usage attribution')
|
|
228
|
+
}
|
|
229
|
+
if (current.usage && (!candidate.usage || !sameRecoveryUsage(current.usage, candidate.usage))) {
|
|
230
|
+
throw new Error('payment recovery usage receipt is immutable')
|
|
231
|
+
}
|
|
232
|
+
if (
|
|
233
|
+
current.settlementBasis &&
|
|
234
|
+
current.settlementBasis !== candidate.settlementBasis
|
|
235
|
+
) {
|
|
236
|
+
throw new Error('payment recovery settlement basis is immutable')
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function sameRecoveryUsage(
|
|
241
|
+
left: SandboxUsageReceipt,
|
|
242
|
+
right: SandboxUsageReceipt,
|
|
243
|
+
): boolean {
|
|
244
|
+
return left.inputTokens === right.inputTokens &&
|
|
245
|
+
left.outputTokens === right.outputTokens &&
|
|
246
|
+
left.reasoningTokens === right.reasoningTokens &&
|
|
247
|
+
left.toolTokens === right.toolTokens &&
|
|
248
|
+
left.toolCallCount === right.toolCallCount &&
|
|
249
|
+
left.providerCostUsd === right.providerCostUsd &&
|
|
250
|
+
left.budgetEnforced === right.budgetEnforced
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function serializePaymentOperation(
|
|
254
|
+
operation: PaymentOperation,
|
|
255
|
+
): SerializedPaymentOperation {
|
|
256
|
+
return {
|
|
257
|
+
protocolVersion: 2,
|
|
258
|
+
operationId: operation.operationId,
|
|
259
|
+
acquiredByRequestId: operation.acquiredByRequestId,
|
|
260
|
+
...(operation.executionStartedAt !== undefined
|
|
261
|
+
? { executionStartedAt: operation.executionStartedAt }
|
|
262
|
+
: {}),
|
|
263
|
+
...(operation.retentionReason ? { retentionReason: operation.retentionReason } : {}),
|
|
264
|
+
nonceKey: operation.nonceKey,
|
|
265
|
+
authorizationId: operation.authorizationId,
|
|
266
|
+
reservedAmount: operation.reservedAmount.toString(),
|
|
267
|
+
settledAmount: operation.settledAmount.toString(),
|
|
268
|
+
refundAmount: operation.refundAmount.toString(),
|
|
269
|
+
expiresAt: operation.expiresAt,
|
|
270
|
+
state: operation.state,
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function deserializePaymentOperation(
|
|
275
|
+
value: SerializedPaymentOperation,
|
|
276
|
+
): PaymentOperation {
|
|
277
|
+
if (value.protocolVersion !== 2) throw new Error('unsupported payment operation version')
|
|
278
|
+
if (!value.operationId || !value.acquiredByRequestId || !value.nonceKey || !value.authorizationId) {
|
|
279
|
+
throw new Error('incomplete payment operation recovery record')
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
protocolVersion: 2,
|
|
283
|
+
operationId: value.operationId,
|
|
284
|
+
acquiredByRequestId: value.acquiredByRequestId,
|
|
285
|
+
...(value.executionStartedAt !== undefined ? { executionStartedAt: value.executionStartedAt } : {}),
|
|
286
|
+
...(value.retentionReason ? { retentionReason: value.retentionReason } : {}),
|
|
287
|
+
nonceKey: value.nonceKey,
|
|
288
|
+
authorizationId: value.authorizationId,
|
|
289
|
+
reservedAmount: BigInt(value.reservedAmount),
|
|
290
|
+
settledAmount: BigInt(value.settledAmount),
|
|
291
|
+
refundAmount: BigInt(value.refundAmount),
|
|
292
|
+
expiresAt: value.expiresAt,
|
|
293
|
+
state: value.state,
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export function recoveryTiming(config: PaymentRecoveryConfig): {
|
|
298
|
+
staleRequestMs: number
|
|
299
|
+
receiptTimeoutMs: number
|
|
300
|
+
retryDelayMs: number
|
|
301
|
+
leaseMs: number
|
|
302
|
+
} {
|
|
303
|
+
return {
|
|
304
|
+
staleRequestMs: config.staleRequestMs ?? DEFAULT_STALE_REQUEST_MS,
|
|
305
|
+
receiptTimeoutMs: config.receiptTimeoutMs ?? DEFAULT_RECEIPT_TIMEOUT_MS,
|
|
306
|
+
retryDelayMs: config.retryDelayMs ?? DEFAULT_RECOVERY_RETRY_MS,
|
|
307
|
+
leaseMs: config.leaseMs ?? DEFAULT_RECOVERY_LEASE_MS,
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export function assertPaymentRecoveryConfig(config: PaymentRecoveryConfig): void {
|
|
312
|
+
for (const [name, value] of Object.entries(recoveryTiming(config))) {
|
|
313
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
314
|
+
throw new Error(`payment recovery ${name} must be a positive safe integer`)
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
const store = config.store
|
|
318
|
+
if (
|
|
319
|
+
!store ||
|
|
320
|
+
typeof store.createIfAbsent !== 'function' ||
|
|
321
|
+
typeof store.get !== 'function' ||
|
|
322
|
+
typeof store.compareAndSet !== 'function' ||
|
|
323
|
+
typeof store.listDue !== 'function'
|
|
324
|
+
) {
|
|
325
|
+
throw new Error('payment recovery store must provide atomic create, compare-and-set, and due scans')
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function clone<T>(value: T): T {
|
|
330
|
+
return JSON.parse(JSON.stringify(value)) as T
|
|
331
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export type PaymentMethod = 'x402' | 'mpp' | 'apikey' | 'none'
|
|
2
|
+
|
|
3
|
+
export interface SandboxExecutionBudget {
|
|
4
|
+
maxInputTokens: number
|
|
5
|
+
maxOutputTokens: number
|
|
6
|
+
maxReasoningTokens: number
|
|
7
|
+
maxToolTokens: number
|
|
8
|
+
maxToolCalls: number
|
|
9
|
+
maxProviderCostUsd: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface SandboxUsageReceipt {
|
|
13
|
+
inputTokens: number
|
|
14
|
+
outputTokens: number
|
|
15
|
+
reasoningTokens: number
|
|
16
|
+
toolTokens: number
|
|
17
|
+
toolCallCount: number
|
|
18
|
+
providerCostUsd: number
|
|
19
|
+
/** True only when the provider/adapter enforced every supplied budget. */
|
|
20
|
+
budgetEnforced: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type PaymentSettlementBasis = 'usage-receipt' | 'quoted-ceiling'
|
|
24
|
+
|
|
25
|
+
export interface GatewayUsageEvent {
|
|
26
|
+
/** Correlates usage, settlement, and observer records for one request. */
|
|
27
|
+
requestId: string
|
|
28
|
+
agentId: string
|
|
29
|
+
agentSlug: string
|
|
30
|
+
consumerId: string
|
|
31
|
+
paymentMethod: PaymentMethod
|
|
32
|
+
inputTokens: number
|
|
33
|
+
outputTokens: number
|
|
34
|
+
/** Optional in 0.7.2 so 0.7.1 event constructors remain source-compatible. */
|
|
35
|
+
reasoningTokens?: number
|
|
36
|
+
/** Optional in 0.7.2 so 0.7.1 event constructors remain source-compatible. */
|
|
37
|
+
toolTokens?: number
|
|
38
|
+
/** Optional in 0.7.2 so 0.7.1 event constructors remain source-compatible. */
|
|
39
|
+
toolCallCount?: number
|
|
40
|
+
/** Optional in 0.7.2 so 0.7.1 event constructors remain source-compatible. */
|
|
41
|
+
providerCostUsd?: number
|
|
42
|
+
totalCostUsd: number
|
|
43
|
+
ownerEarnedUsd: number
|
|
44
|
+
platformFeeUsd: number
|
|
45
|
+
durationMs: number
|
|
46
|
+
/** Exact receipt in normal operation; quoted ceiling only after receipt timeout. */
|
|
47
|
+
settlementBasis?: PaymentSettlementBasis
|
|
48
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
PaymentAuthorizationContext,
|
|
3
|
+
PaymentOperation,
|
|
4
|
+
PaymentOperations,
|
|
5
|
+
} from './payment-operations'
|
|
6
|
+
import type { MppAuthenticatedCredential, MppChargeLifecycle } from './mpp-payment'
|
|
7
|
+
import type { PaymentRecoveryConfig } from './payment-recovery'
|
|
8
|
+
import type { GatewayObserver } from './observer-types'
|
|
9
|
+
import type {
|
|
10
|
+
GatewayUsageEvent,
|
|
11
|
+
PaymentMethod,
|
|
12
|
+
SandboxExecutionBudget,
|
|
13
|
+
SandboxUsageReceipt,
|
|
14
|
+
} from './payment-types'
|
|
15
|
+
|
|
16
|
+
export type {
|
|
17
|
+
GatewayUsageEvent,
|
|
18
|
+
PaymentMethod,
|
|
19
|
+
SandboxExecutionBudget,
|
|
20
|
+
SandboxUsageReceipt,
|
|
21
|
+
} from './payment-types'
|
|
22
|
+
|
|
1
23
|
// --- Agent resolution ---
|
|
2
24
|
|
|
3
25
|
export interface AgentMeta {
|
|
@@ -71,8 +93,6 @@ export interface AgentMeta {
|
|
|
71
93
|
|
|
72
94
|
// --- Payment ---
|
|
73
95
|
|
|
74
|
-
export type PaymentMethod = 'x402' | 'mpp' | 'apikey' | 'none'
|
|
75
|
-
|
|
76
96
|
export interface X402Config {
|
|
77
97
|
/** Ethereum operator address for SpendAuth verification */
|
|
78
98
|
operatorAddress: string
|
|
@@ -84,8 +104,35 @@ export interface X402Config {
|
|
|
84
104
|
rpcUrl?: string
|
|
85
105
|
/** Demo mode: skip signature verification (default: false). NEVER enable in production. */
|
|
86
106
|
demoMode?: boolean
|
|
87
|
-
/**
|
|
88
|
-
|
|
107
|
+
/** Protocol version for new durable payment operations. Production version 1 is read-only. */
|
|
108
|
+
paymentProtocolVersion?: 1 | 2
|
|
109
|
+
/**
|
|
110
|
+
* Production signature verification. This callback must not reserve, claim,
|
|
111
|
+
* or mutate payment state.
|
|
112
|
+
*/
|
|
113
|
+
verifySigner?: (
|
|
114
|
+
payload: Record<string, unknown>,
|
|
115
|
+
context?: { protocolVersion: 1 | 2; requestId?: string },
|
|
116
|
+
) => Promise<boolean>
|
|
117
|
+
/**
|
|
118
|
+
* Claim the verified payment after all request checks pass and immediately
|
|
119
|
+
* before sandbox work starts. Version 2 returns durable operation ownership.
|
|
120
|
+
* A boolean return is the version 1 demo-only compatibility path.
|
|
121
|
+
* Production version 1 must omit this callback because it has no durable
|
|
122
|
+
* provider operation or recovery identity.
|
|
123
|
+
*/
|
|
124
|
+
authorizePayment?: (
|
|
125
|
+
payload: Record<string, unknown>,
|
|
126
|
+
context: PaymentAuthorizationContext,
|
|
127
|
+
) => Promise<boolean | PaymentOperation>
|
|
128
|
+
/** Version 2 operation store. It owns claim, settle, release, and reclaim. */
|
|
129
|
+
paymentOperations?: PaymentOperations
|
|
130
|
+
/**
|
|
131
|
+
* Number of base-unit decimals used by the payment token. Defaults to 6.
|
|
132
|
+
* The gateway uses this value to reject a payment that cannot cover the
|
|
133
|
+
* request's maximum token charge before it calls `verifySigner`.
|
|
134
|
+
*/
|
|
135
|
+
currencyDecimals?: number
|
|
89
136
|
}
|
|
90
137
|
|
|
91
138
|
export interface MppConfig {
|
|
@@ -94,17 +141,24 @@ export interface MppConfig {
|
|
|
94
141
|
/** MPP method name (default: "blueprintevm") */
|
|
95
142
|
method?: string
|
|
96
143
|
/**
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
144
|
+
* Pure credential authentication. Return stable method-owned identity, or null.
|
|
145
|
+
* This callback must not consume a credential, create a processor object,
|
|
146
|
+
* reserve funds, confirm payment, or perform any other financial mutation.
|
|
147
|
+
*/
|
|
148
|
+
authenticateCredential?: (
|
|
149
|
+
payload: Record<string, unknown>,
|
|
150
|
+
context: { method: string; credential: string },
|
|
151
|
+
) => Promise<MppAuthenticatedCredential | null>
|
|
152
|
+
/**
|
|
153
|
+
* @deprecated Use authenticateCredential and return a stable payment identity.
|
|
154
|
+
* This 0.7.1 callback remains supported through an explicit compatibility adapter.
|
|
103
155
|
*/
|
|
104
156
|
verifySigner?: (
|
|
105
157
|
payload: Record<string, unknown>,
|
|
106
158
|
context: { method: string; credential: string },
|
|
107
159
|
) => Promise<string | null>
|
|
160
|
+
/** Required immediate-charge lifecycle for every non-BlueprinTEVM method. */
|
|
161
|
+
charge?: MppChargeLifecycle
|
|
108
162
|
}
|
|
109
163
|
|
|
110
164
|
export interface PaymentResult {
|
|
@@ -133,30 +187,6 @@ export interface ApiKeyInfo {
|
|
|
133
187
|
dailyLimit?: number
|
|
134
188
|
}
|
|
135
189
|
|
|
136
|
-
// --- Usage tracking ---
|
|
137
|
-
|
|
138
|
-
export interface GatewayUsageEvent {
|
|
139
|
-
/**
|
|
140
|
-
* Per-request id (matches `RequestContext.requestId`). Lets
|
|
141
|
-
* `recordUsage` correlate the usage row to the same request that
|
|
142
|
-
* `settlePayment` settles, observability hooks observe, and
|
|
143
|
-
* `onRequestComplete` reports — without re-deriving from a
|
|
144
|
-
* synthetic key. Required field as of 0.4.0; the gateway always has
|
|
145
|
-
* it in scope at the recordUsage call site.
|
|
146
|
-
*/
|
|
147
|
-
requestId: string
|
|
148
|
-
agentId: string
|
|
149
|
-
agentSlug: string
|
|
150
|
-
consumerId: string
|
|
151
|
-
paymentMethod: PaymentMethod
|
|
152
|
-
inputTokens: number
|
|
153
|
-
outputTokens: number
|
|
154
|
-
totalCostUsd: number
|
|
155
|
-
ownerEarnedUsd: number
|
|
156
|
-
platformFeeUsd: number
|
|
157
|
-
durationMs: number
|
|
158
|
-
}
|
|
159
|
-
|
|
160
190
|
// --- Sandbox interface ---
|
|
161
191
|
|
|
162
192
|
export interface SandboxStreamEvent {
|
|
@@ -174,11 +204,25 @@ export interface SandboxStreamEvent {
|
|
|
174
204
|
* the caller (rendered as the input-required message body).
|
|
175
205
|
*/
|
|
176
206
|
inputRequired?: { prompt?: string }
|
|
207
|
+
/** Provider receipt fields. Version 2 operations require every field. */
|
|
208
|
+
usage?: Partial<SandboxUsageReceipt>
|
|
209
|
+
/** Tool or reasoning events may carry hidden usage without visible text. */
|
|
210
|
+
tool?: { name?: string; inputTokens?: number; outputTokens?: number }
|
|
211
|
+
reasoning?: { tokens?: number }
|
|
177
212
|
}
|
|
178
213
|
}
|
|
179
214
|
|
|
180
215
|
export interface SandboxBox {
|
|
181
|
-
streamPrompt(
|
|
216
|
+
streamPrompt(
|
|
217
|
+
message: string,
|
|
218
|
+
opts?: {
|
|
219
|
+
sessionId?: string
|
|
220
|
+
systemPrompt?: string
|
|
221
|
+
maxOutputTokens?: number
|
|
222
|
+
executionBudget?: SandboxExecutionBudget
|
|
223
|
+
signal?: AbortSignal
|
|
224
|
+
},
|
|
225
|
+
): AsyncIterable<SandboxStreamEvent>
|
|
182
226
|
}
|
|
183
227
|
|
|
184
228
|
// --- Gateway config ---
|
|
@@ -200,7 +244,12 @@ export interface GatewayConfig {
|
|
|
200
244
|
consumer: { method: PaymentMethod; consumerId: string; keyId?: string; requestId: string },
|
|
201
245
|
) => Promise<{ allow: true } | { allow: false; reason: string; code: string }>
|
|
202
246
|
|
|
203
|
-
/**
|
|
247
|
+
/**
|
|
248
|
+
* Record a usage event after request completes.
|
|
249
|
+
* The implementation must atomically upsert by requestId and return
|
|
250
|
+
* success when the row already exists. Recovery may retry after an
|
|
251
|
+
* acknowledgement is lost, so one request ID must produce one usage row.
|
|
252
|
+
*/
|
|
204
253
|
recordUsage: (event: GatewayUsageEvent) => Promise<void>
|
|
205
254
|
|
|
206
255
|
/** x402 payment configuration */
|
|
@@ -209,6 +258,12 @@ export interface GatewayConfig {
|
|
|
209
258
|
/** MPP (Machine Payments Protocol) configuration. It is advertised only when a production verifier or explicit demo mode is available. */
|
|
210
259
|
mpp?: MppConfig
|
|
211
260
|
|
|
261
|
+
/**
|
|
262
|
+
* Durable payment recovery outbox. Production payment protocol version 2
|
|
263
|
+
* and generic MPP charge methods require this configuration.
|
|
264
|
+
*/
|
|
265
|
+
paymentRecovery?: PaymentRecoveryConfig
|
|
266
|
+
|
|
212
267
|
/**
|
|
213
268
|
* Verify an API key. Return key info if valid, null if invalid.
|
|
214
269
|
* In explicit x402 demo mode, the built-in verifier accepts `sk_agent_*` keys.
|
|
@@ -217,10 +272,11 @@ export interface GatewayConfig {
|
|
|
217
272
|
verifyApiKey?: (authHeader: string) => Promise<ApiKeyInfo | null>
|
|
218
273
|
|
|
219
274
|
/**
|
|
220
|
-
* Settle payment after
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
275
|
+
* Settle a legacy payment after usage attribution is recorded.
|
|
276
|
+
* Version 2 x402 operations use `x402.paymentOperations` instead.
|
|
277
|
+
* Production x402 version 1 rejects this callback before nonce claim.
|
|
278
|
+
* For API keys, deduct from the spending limit.
|
|
279
|
+
* Default: no-op in explicit demo mode.
|
|
224
280
|
*/
|
|
225
281
|
settlePayment?: (payment: PaymentResult, cost: number) => Promise<void>
|
|
226
282
|
|
|
@@ -230,6 +286,29 @@ export interface GatewayConfig {
|
|
|
230
286
|
/** Max message length in chars (default: 8000) */
|
|
231
287
|
maxMessageLength?: number
|
|
232
288
|
|
|
289
|
+
/** Maximum output token request the gateway accepts. Defaults to 4096. */
|
|
290
|
+
maxOutputTokens?: number
|
|
291
|
+
|
|
292
|
+
/** Output token limit used when a request omits `max_tokens`. Defaults to 1024. */
|
|
293
|
+
defaultOutputTokens?: number
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Return a safe upper bound for the complete provider input.
|
|
297
|
+
* Include system, chat framing, retained history, tools, harness, and workspace context.
|
|
298
|
+
*/
|
|
299
|
+
inputTokenBound?: (input: {
|
|
300
|
+
agent: AgentMeta
|
|
301
|
+
messages: ChatMessage[]
|
|
302
|
+
}) => number
|
|
303
|
+
|
|
304
|
+
/** Hidden provider spend limits included in the pre-execution payment quote. */
|
|
305
|
+
executionBudget?: {
|
|
306
|
+
maxReasoningTokens?: number
|
|
307
|
+
maxToolTokens?: number
|
|
308
|
+
maxToolCalls?: number
|
|
309
|
+
maxProviderCostUsd?: number
|
|
310
|
+
}
|
|
311
|
+
|
|
233
312
|
/** Required scope for chat endpoint (default: "chat"). API keys must include this scope. */
|
|
234
313
|
requiredScope?: string
|
|
235
314
|
|
|
@@ -251,19 +330,33 @@ export interface GatewayConfig {
|
|
|
251
330
|
* and settlement failures. See ./observer.ts for the interface and
|
|
252
331
|
* ConsoleObserver / CompositeObserver implementations.
|
|
253
332
|
*/
|
|
254
|
-
observer?:
|
|
333
|
+
observer?: GatewayObserver
|
|
255
334
|
|
|
256
335
|
/**
|
|
257
|
-
* A2A protocol configuration.
|
|
258
|
-
*
|
|
336
|
+
* A2A protocol configuration. The gateway exposes A2A with an in-memory
|
|
337
|
+
* task store by default. Set this object to provide durable storage or push:
|
|
259
338
|
* GET /:slug/.well-known/agent.json — AgentCard discovery
|
|
260
339
|
* POST /:slug — JSON-RPC 2.0 endpoint
|
|
261
340
|
* methods: message/send, message/stream, tasks/get, tasks/cancel
|
|
262
341
|
* Auth + rate-limit + injection-filter + authorization all share the
|
|
263
342
|
* same pipeline as the OpenAI-compat path. `taskStore` defaults to
|
|
264
343
|
* `InMemoryTaskStore`; swap in D1/postgres/DO for durable deployments.
|
|
265
|
-
|
|
344
|
+
*/
|
|
266
345
|
a2a?: {
|
|
346
|
+
/**
|
|
347
|
+
* Authorize reads, cancellation, resubscription, and push configuration
|
|
348
|
+
* for an existing task. Production control methods fail closed when this
|
|
349
|
+
* hook is absent; explicit demo mode permits local tests.
|
|
350
|
+
*/
|
|
351
|
+
authorizeTaskAccess?: (
|
|
352
|
+
task: import('./a2a/types').Task,
|
|
353
|
+
context: {
|
|
354
|
+
method: string
|
|
355
|
+
agentSlug: string
|
|
356
|
+
authorization: string
|
|
357
|
+
paymentSignature: string
|
|
358
|
+
},
|
|
359
|
+
) => Promise<boolean>
|
|
267
360
|
/**
|
|
268
361
|
* Where tasks live. Defaults to `InMemoryTaskStore`; swap in
|
|
269
362
|
* `SqlTaskStore` (D1, postgres, sqlite, libSQL) for durability across
|
|
@@ -282,8 +375,8 @@ export interface GatewayConfig {
|
|
|
282
375
|
* Shared HMAC secret used to sign webhook deliveries (`X-A2A-Signature:
|
|
283
376
|
* sha256=<hex>`). The consumer's webhook verifies the body against this
|
|
284
377
|
* secret to confirm the call originated from this gateway. Required when
|
|
285
|
-
* `pushStore` is set
|
|
286
|
-
*
|
|
378
|
+
* `pushStore` is set in production. Explicit demo mode may omit it for
|
|
379
|
+
* local tests; production deliveries never run unsigned.
|
|
287
380
|
*/
|
|
288
381
|
webhookSecret?: string
|
|
289
382
|
/**
|
|
@@ -291,6 +384,11 @@ export interface GatewayConfig {
|
|
|
291
384
|
* `fetch`. Override for tests or to wire a queue-backed sender.
|
|
292
385
|
*/
|
|
293
386
|
pushFetcher?: typeof fetch
|
|
387
|
+
/**
|
|
388
|
+
* DNS-aware policy for push destinations. Required when production
|
|
389
|
+
* push delivery is enabled so private DNS names cannot receive task data.
|
|
390
|
+
*/
|
|
391
|
+
pushUrlValidator?: (url: URL) => boolean | Promise<boolean>
|
|
294
392
|
}
|
|
295
393
|
}
|
|
296
394
|
|