@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.
Files changed (65) hide show
  1. package/README.md +108 -6
  2. package/dist/chunk-GITV7CPT.js +84 -0
  3. package/dist/chunk-GITV7CPT.js.map +1 -0
  4. package/dist/chunk-J5SDVHOL.js +104 -0
  5. package/dist/chunk-J5SDVHOL.js.map +1 -0
  6. package/dist/chunk-MP6IIAIA.js +5651 -0
  7. package/dist/chunk-MP6IIAIA.js.map +1 -0
  8. package/dist/index.d.ts +76 -12
  9. package/dist/index.js +307 -21
  10. package/dist/index.js.map +1 -1
  11. package/dist/middleware.d.ts +7 -2
  12. package/dist/middleware.js +3 -2
  13. package/dist/nonce-store.d.ts +47 -11
  14. package/dist/nonce-store.js +9 -3
  15. package/dist/observer-types-A0RtA8uL.d.ts +95 -0
  16. package/dist/observer.d.ts +79 -0
  17. package/dist/observer.js +11 -0
  18. package/dist/observer.js.map +1 -0
  19. package/dist/{types-CX2V06cN.d.ts → types-BHISsm7D.d.ts} +423 -166
  20. package/dist/types.d.ts +2 -1
  21. package/package.json +1 -1
  22. package/src/a2a/agent-card.ts +4 -3
  23. package/src/a2a/execution-fence.ts +162 -0
  24. package/src/a2a/handler.ts +507 -562
  25. package/src/a2a/message-send-execution.ts +241 -0
  26. package/src/a2a/message-stream-execution.ts +392 -0
  27. package/src/a2a/payment-recovery.ts +431 -0
  28. package/src/a2a/push-config-methods.ts +158 -0
  29. package/src/a2a/push-notifications.ts +172 -22
  30. package/src/a2a/task-cancellation.ts +50 -0
  31. package/src/a2a/task-finalization.ts +451 -0
  32. package/src/a2a/task-lifecycle.ts +54 -0
  33. package/src/a2a/task-methods.ts +163 -0
  34. package/src/a2a/task-push-delivery.ts +119 -0
  35. package/src/a2a/task-recovery.ts +11 -0
  36. package/src/a2a/task-state.ts +99 -0
  37. package/src/a2a/task-store-sql.ts +222 -24
  38. package/src/a2a/task-store.ts +58 -1
  39. package/src/a2a/task-submission-recovery.ts +178 -0
  40. package/src/a2a/types.ts +1 -0
  41. package/src/dispatch-authorization.ts +437 -0
  42. package/src/dispatch-payment-recovery.ts +248 -0
  43. package/src/dispatch-payment.ts +425 -0
  44. package/src/dispatch-pricing.ts +108 -0
  45. package/src/dispatch-sandbox.ts +422 -0
  46. package/src/dispatch-settlement.ts +139 -0
  47. package/src/dispatch-types.ts +81 -0
  48. package/src/dispatch.ts +35 -462
  49. package/src/index.ts +64 -2
  50. package/src/middleware.ts +313 -32
  51. package/src/mpp-payment.ts +117 -0
  52. package/src/nonce-store.ts +122 -20
  53. package/src/observer-types.ts +63 -0
  54. package/src/observer.ts +3 -63
  55. package/src/payment-operations.ts +485 -0
  56. package/src/payment-recovery-sql.ts +108 -0
  57. package/src/payment-recovery-worker.ts +488 -0
  58. package/src/payment-recovery.ts +331 -0
  59. package/src/payment-types.ts +48 -0
  60. package/src/types.ts +153 -42
  61. package/src/verify.ts +265 -36
  62. package/dist/chunk-3IKQWFKX.js +0 -1703
  63. package/dist/chunk-3IKQWFKX.js.map +0 -1
  64. package/dist/chunk-M7ZJAK4K.js +0 -53
  65. 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
- /** Production signer verification. Called with the raw SpendAuth payload. Return true if signature is valid. */
88
- verifySigner?: (payload: Record<string, unknown>) => Promise<boolean>
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(message: string, opts?: { sessionId?: string; systemPrompt?: string }): AsyncIterable<SandboxStreamEvent>
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
- /** Record a usage event after request completes. */
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. If provided, gateway accepts Authorization: Payment headers. */
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
- * Default: accepts any `sk_agent_*` key (demo mode).
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 successful response.
208
- * For x402: call ShieldedCredits.claimPayment()
209
- * For API key: deduct from spending limit
210
- * Default: no-op (demo mode).
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?: import('./observer').GatewayObserver
333
+ observer?: GatewayObserver
242
334
 
243
335
  /**
244
- * A2A protocol configuration. When set, the gateway exposes the A2A
245
- * surface alongside its OpenAI-compatible endpoints:
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; without it, deliveries fire unsigned and a
273
- * malicious party that knows the webhook URL can forge deliveries.
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