@tangle-network/agent-gateway 0.7.0 → 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 +108 -6
- 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 +76 -12
- package/dist/index.js +307 -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-CX2V06cN.d.ts → types-BHISsm7D.d.ts} +423 -166
- package/dist/types.d.ts +2 -1
- package/package.json +1 -1
- package/src/a2a/agent-card.ts +4 -3
- package/src/a2a/execution-fence.ts +162 -0
- package/src/a2a/handler.ts +507 -562
- 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 -462
- package/src/index.ts +64 -2
- package/src/middleware.ts +313 -32
- 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 +153 -42
- package/src/verify.ts +265 -36
- package/dist/chunk-3IKQWFKX.js +0 -1703
- package/dist/chunk-3IKQWFKX.js.map +0 -1
- package/dist/chunk-M7ZJAK4K.js +0 -53
- package/dist/chunk-M7ZJAK4K.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 {
|
|
@@ -93,6 +140,25 @@ export interface MppConfig {
|
|
|
93
140
|
realm: string
|
|
94
141
|
/** MPP method name (default: "blueprintevm") */
|
|
95
142
|
method?: string
|
|
143
|
+
/**
|
|
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.
|
|
155
|
+
*/
|
|
156
|
+
verifySigner?: (
|
|
157
|
+
payload: Record<string, unknown>,
|
|
158
|
+
context: { method: string; credential: string },
|
|
159
|
+
) => Promise<string | null>
|
|
160
|
+
/** Required immediate-charge lifecycle for every non-BlueprinTEVM method. */
|
|
161
|
+
charge?: MppChargeLifecycle
|
|
96
162
|
}
|
|
97
163
|
|
|
98
164
|
export interface PaymentResult {
|
|
@@ -121,30 +187,6 @@ export interface ApiKeyInfo {
|
|
|
121
187
|
dailyLimit?: number
|
|
122
188
|
}
|
|
123
189
|
|
|
124
|
-
// --- Usage tracking ---
|
|
125
|
-
|
|
126
|
-
export interface GatewayUsageEvent {
|
|
127
|
-
/**
|
|
128
|
-
* Per-request id (matches `RequestContext.requestId`). Lets
|
|
129
|
-
* `recordUsage` correlate the usage row to the same request that
|
|
130
|
-
* `settlePayment` settles, observability hooks observe, and
|
|
131
|
-
* `onRequestComplete` reports — without re-deriving from a
|
|
132
|
-
* synthetic key. Required field as of 0.4.0; the gateway always has
|
|
133
|
-
* it in scope at the recordUsage call site.
|
|
134
|
-
*/
|
|
135
|
-
requestId: string
|
|
136
|
-
agentId: string
|
|
137
|
-
agentSlug: string
|
|
138
|
-
consumerId: string
|
|
139
|
-
paymentMethod: PaymentMethod
|
|
140
|
-
inputTokens: number
|
|
141
|
-
outputTokens: number
|
|
142
|
-
totalCostUsd: number
|
|
143
|
-
ownerEarnedUsd: number
|
|
144
|
-
platformFeeUsd: number
|
|
145
|
-
durationMs: number
|
|
146
|
-
}
|
|
147
|
-
|
|
148
190
|
// --- Sandbox interface ---
|
|
149
191
|
|
|
150
192
|
export interface SandboxStreamEvent {
|
|
@@ -162,11 +204,25 @@ export interface SandboxStreamEvent {
|
|
|
162
204
|
* the caller (rendered as the input-required message body).
|
|
163
205
|
*/
|
|
164
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 }
|
|
165
212
|
}
|
|
166
213
|
}
|
|
167
214
|
|
|
168
215
|
export interface SandboxBox {
|
|
169
|
-
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>
|
|
170
226
|
}
|
|
171
227
|
|
|
172
228
|
// --- Gateway config ---
|
|
@@ -188,26 +244,39 @@ export interface GatewayConfig {
|
|
|
188
244
|
consumer: { method: PaymentMethod; consumerId: string; keyId?: string; requestId: string },
|
|
189
245
|
) => Promise<{ allow: true } | { allow: false; reason: string; code: string }>
|
|
190
246
|
|
|
191
|
-
/**
|
|
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
|
+
*/
|
|
192
253
|
recordUsage: (event: GatewayUsageEvent) => Promise<void>
|
|
193
254
|
|
|
194
255
|
/** x402 payment configuration */
|
|
195
256
|
x402: X402Config
|
|
196
257
|
|
|
197
|
-
/** MPP (Machine Payments Protocol) configuration.
|
|
258
|
+
/** MPP (Machine Payments Protocol) configuration. It is advertised only when a production verifier or explicit demo mode is available. */
|
|
198
259
|
mpp?: MppConfig
|
|
199
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
|
+
|
|
200
267
|
/**
|
|
201
268
|
* Verify an API key. Return key info if valid, null if invalid.
|
|
202
|
-
*
|
|
269
|
+
* In explicit x402 demo mode, the built-in verifier accepts `sk_agent_*` keys.
|
|
270
|
+
* Production gateways must provide this callback.
|
|
203
271
|
*/
|
|
204
272
|
verifyApiKey?: (authHeader: string) => Promise<ApiKeyInfo | null>
|
|
205
273
|
|
|
206
274
|
/**
|
|
207
|
-
* Settle payment after
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
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.
|
|
211
280
|
*/
|
|
212
281
|
settlePayment?: (payment: PaymentResult, cost: number) => Promise<void>
|
|
213
282
|
|
|
@@ -217,6 +286,29 @@ export interface GatewayConfig {
|
|
|
217
286
|
/** Max message length in chars (default: 8000) */
|
|
218
287
|
maxMessageLength?: number
|
|
219
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
|
+
|
|
220
312
|
/** Required scope for chat endpoint (default: "chat"). API keys must include this scope. */
|
|
221
313
|
requiredScope?: string
|
|
222
314
|
|
|
@@ -238,19 +330,33 @@ export interface GatewayConfig {
|
|
|
238
330
|
* and settlement failures. See ./observer.ts for the interface and
|
|
239
331
|
* ConsoleObserver / CompositeObserver implementations.
|
|
240
332
|
*/
|
|
241
|
-
observer?:
|
|
333
|
+
observer?: GatewayObserver
|
|
242
334
|
|
|
243
335
|
/**
|
|
244
|
-
* A2A protocol configuration.
|
|
245
|
-
*
|
|
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:
|
|
246
338
|
* GET /:slug/.well-known/agent.json — AgentCard discovery
|
|
247
339
|
* POST /:slug — JSON-RPC 2.0 endpoint
|
|
248
340
|
* methods: message/send, message/stream, tasks/get, tasks/cancel
|
|
249
341
|
* Auth + rate-limit + injection-filter + authorization all share the
|
|
250
342
|
* same pipeline as the OpenAI-compat path. `taskStore` defaults to
|
|
251
343
|
* `InMemoryTaskStore`; swap in D1/postgres/DO for durable deployments.
|
|
252
|
-
|
|
344
|
+
*/
|
|
253
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>
|
|
254
360
|
/**
|
|
255
361
|
* Where tasks live. Defaults to `InMemoryTaskStore`; swap in
|
|
256
362
|
* `SqlTaskStore` (D1, postgres, sqlite, libSQL) for durability across
|
|
@@ -269,8 +375,8 @@ export interface GatewayConfig {
|
|
|
269
375
|
* Shared HMAC secret used to sign webhook deliveries (`X-A2A-Signature:
|
|
270
376
|
* sha256=<hex>`). The consumer's webhook verifies the body against this
|
|
271
377
|
* secret to confirm the call originated from this gateway. Required when
|
|
272
|
-
* `pushStore` is set
|
|
273
|
-
*
|
|
378
|
+
* `pushStore` is set in production. Explicit demo mode may omit it for
|
|
379
|
+
* local tests; production deliveries never run unsigned.
|
|
274
380
|
*/
|
|
275
381
|
webhookSecret?: string
|
|
276
382
|
/**
|
|
@@ -278,6 +384,11 @@ export interface GatewayConfig {
|
|
|
278
384
|
* `fetch`. Override for tests or to wire a queue-backed sender.
|
|
279
385
|
*/
|
|
280
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>
|
|
281
392
|
}
|
|
282
393
|
}
|
|
283
394
|
|