@tangle-network/agent-gateway 0.7.1 → 0.8.1

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 (64) hide show
  1. package/README.md +90 -3
  2. package/dist/chunk-C7Z2BRYV.js +5693 -0
  3. package/dist/chunk-C7Z2BRYV.js.map +1 -0
  4. package/dist/chunk-GITV7CPT.js +84 -0
  5. package/dist/chunk-GITV7CPT.js.map +1 -0
  6. package/dist/chunk-J5SDVHOL.js +104 -0
  7. package/dist/chunk-J5SDVHOL.js.map +1 -0
  8. package/dist/index.d.ts +70 -10
  9. package/dist/index.js +303 -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-DEsMmS-X.d.ts → types-oQ58UakD.d.ts} +447 -172
  20. package/dist/types.d.ts +2 -1
  21. package/package.json +1 -1
  22. package/src/a2a/execution-fence.ts +162 -0
  23. package/src/a2a/handler.ts +506 -560
  24. package/src/a2a/message-send-execution.ts +241 -0
  25. package/src/a2a/message-stream-execution.ts +392 -0
  26. package/src/a2a/payment-recovery.ts +431 -0
  27. package/src/a2a/push-config-methods.ts +158 -0
  28. package/src/a2a/push-notifications.ts +172 -22
  29. package/src/a2a/task-cancellation.ts +50 -0
  30. package/src/a2a/task-finalization.ts +451 -0
  31. package/src/a2a/task-lifecycle.ts +54 -0
  32. package/src/a2a/task-methods.ts +163 -0
  33. package/src/a2a/task-push-delivery.ts +119 -0
  34. package/src/a2a/task-recovery.ts +11 -0
  35. package/src/a2a/task-state.ts +99 -0
  36. package/src/a2a/task-store-sql.ts +222 -24
  37. package/src/a2a/task-store.ts +58 -1
  38. package/src/a2a/task-submission-recovery.ts +178 -0
  39. package/src/a2a/types.ts +1 -0
  40. package/src/dispatch-authorization.ts +468 -0
  41. package/src/dispatch-payment-recovery.ts +248 -0
  42. package/src/dispatch-payment.ts +425 -0
  43. package/src/dispatch-pricing.ts +108 -0
  44. package/src/dispatch-sandbox.ts +424 -0
  45. package/src/dispatch-settlement.ts +139 -0
  46. package/src/dispatch-types.ts +84 -0
  47. package/src/dispatch.ts +35 -483
  48. package/src/index.ts +59 -1
  49. package/src/middleware.ts +339 -35
  50. package/src/mpp-payment.ts +117 -0
  51. package/src/nonce-store.ts +122 -20
  52. package/src/observer-types.ts +63 -0
  53. package/src/observer.ts +3 -63
  54. package/src/payment-operations.ts +485 -0
  55. package/src/payment-recovery-sql.ts +108 -0
  56. package/src/payment-recovery-worker.ts +488 -0
  57. package/src/payment-recovery.ts +331 -0
  58. package/src/payment-types.ts +48 -0
  59. package/src/types.ts +188 -49
  60. package/src/verify.ts +240 -71
  61. package/dist/chunk-M7ZJAK4K.js +0 -53
  62. package/dist/chunk-M7ZJAK4K.js.map +0 -1
  63. package/dist/chunk-Q4YAIEZY.js +0 -1763
  64. package/dist/chunk-Q4YAIEZY.js.map +0 -1
@@ -0,0 +1,84 @@
1
+ import type { MppChargeOperation } from './mpp-payment'
2
+ import type { NonceStore } from './nonce-store'
3
+ import type { GatewayObserver } from './observer'
4
+ import type { RateLimitStore } from './rate-limit'
5
+ import type { PaymentOperation } from './payment-operations'
6
+ import type { PaymentSettlementBasis } from './payment-recovery'
7
+ import type {
8
+ AgentMeta,
9
+ ApiKeyInfo,
10
+ ChatMessage,
11
+ PaymentMethod,
12
+ SandboxExecutionBudget,
13
+ SandboxUsageReceipt,
14
+ } from './types'
15
+
16
+ /** Long-lived state shared by all handlers created for one gateway. */
17
+ export interface GatewayState {
18
+ rateLimitStore: RateLimitStore
19
+ nonceStore: NonceStore
20
+ globalRateLimit: { limit: number; windowSeconds: number }
21
+ requiredScope: string
22
+ maxLen: number
23
+ maxOutputTokens: number
24
+ defaultOutputTokens: number
25
+ maxReasoningTokens: number
26
+ maxToolTokens: number
27
+ maxToolCalls: number
28
+ maxProviderCostUsd?: number
29
+ obs?: GatewayObserver
30
+ }
31
+
32
+ /** Successful output from the request authorization pipeline. */
33
+ export interface AuthorizedRequest {
34
+ agent: AgentMeta
35
+ consumerId: string
36
+ paymentMethod: PaymentMethod
37
+ keyInfo: ApiKeyInfo | null
38
+ userMessage: string
39
+ rateLimitRemaining: number | undefined
40
+ requestId: string
41
+ messages?: ChatMessage[]
42
+ threadId?: string
43
+ startMs: number
44
+ maxOutputTokens: number
45
+ executionBudget: SandboxExecutionBudget
46
+ requiredPaymentAmount: bigint
47
+ paymentPayload: Record<string, unknown> | null
48
+ paymentNonceKey?: string
49
+ mppMethod?: string
50
+ /** Live generic MPP credential. Never write it to the recovery store. */
51
+ mppCredential?: string
52
+ /** Stable method identity. Persist only its digest. */
53
+ mppPaymentIdentity?: string
54
+ mppChargeOperation?: MppChargeOperation
55
+ paymentOperation?: PaymentOperation
56
+ paymentOperationAcquired?: boolean
57
+ paymentRecoveryId?: string
58
+ /** Unique ownership fence for live or recovery transitions. */
59
+ paymentRecoveryFence?: string
60
+ }
61
+
62
+ export interface PaymentClaimHooks {
63
+ /** Persist the recovery identity before the provider can mutate payment state. */
64
+ onRecoveryPrepared?: (recoveryId: string) => Promise<void>
65
+ }
66
+
67
+ export type A2ADispatchEvent =
68
+ | { kind: 'text'; delta: string }
69
+ | { kind: 'input-required'; prompt?: string }
70
+ | { kind: 'activity' }
71
+ | { kind: 'usage'; usage: SandboxUsageReceipt }
72
+
73
+ export interface SettleAndRecordOptions {
74
+ /** Skip attribution after a durable finalization marker confirms it ran. */
75
+ usageAlreadyRecorded?: boolean
76
+ /** Skip provider settlement after an authoritative read found it settled. */
77
+ paymentAlreadySettled?: boolean
78
+ /** Persist the recovery marker after attribution succeeds. */
79
+ onUsageRecorded?: () => Promise<void>
80
+ /** Recovery uses the original quoted ceiling when no receipt arrives. */
81
+ settlementBasis?: PaymentSettlementBasis
82
+ /** Exact base-unit charge selected by the recovery policy. */
83
+ paymentAmount?: bigint
84
+ }
package/src/dispatch.ts CHANGED
@@ -1,486 +1,38 @@
1
1
  /**
2
- * Shared inner pipeline used by every wire-format the gateway exposes
3
- * (OpenAI-compatible chat completions, A2A JSON-RPC). Each handler parses its
4
- * own protocol's request body into a canonical `messages[]` form + headers,
5
- * then calls into here for auth → rate-limit → injection filter →
6
- * authorize → sandbox stream → settle. Keeping the pipeline single-sourced
7
- * means every protocol surface gets the same security and billing guarantees
8
- * for free; bugs fixed here fix every wrapper.
2
+ * Stable dispatch surface shared by the OpenAI-compatible and A2A handlers.
3
+ * Each implementation lives in the module that owns its state transitions.
9
4
  */
10
5
 
11
- import type { Context } from 'hono'
12
-
13
- import { filterConsumerMessagesStrict, redactSystemPromptFromOutput } from './filter'
14
- import { type GatewayObserver, type RequestContext, generateRequestId } from './observer'
15
- import { type RateLimitStore, checkRateLimit } from './rate-limit'
16
- import type { NonceStore } from './nonce-store'
17
- import type {
18
- AgentMeta,
19
- ApiKeyInfo,
20
- ChatMessage,
21
- GatewayConfig,
22
- PaymentMethod,
23
- } from './types'
24
- import {
25
- defaultVerifyApiKey,
26
- isApiKeyAuthEnabled,
27
- isMppAuthEnabled,
28
- verifyMpp,
29
- verifyX402,
30
- } from './verify'
31
-
32
- /** Single bundle of long-lived gateway state shared across all handlers in one createAgentGateway call. */
33
- export interface GatewayState {
34
- rateLimitStore: RateLimitStore
35
- nonceStore: NonceStore
36
- globalRateLimit: { limit: number; windowSeconds: number }
37
- requiredScope: string
38
- maxLen: number
39
- obs?: GatewayObserver
40
- }
41
-
42
- /** Returned by {@link authenticateAndGuard} on the success path. */
43
- export interface AuthorizedRequest {
44
- agent: AgentMeta
45
- consumerId: string
46
- paymentMethod: PaymentMethod
47
- keyInfo: ApiKeyInfo | null
48
- userMessage: string
49
- rateLimitRemaining: number | undefined
50
- requestId: string
51
- startMs: number
52
- }
53
-
54
- /**
55
- * Resolve the agent, then run the full pre-dispatch pipeline: payment +
56
- * rate-limit + injection filter + user-message extraction + optional
57
- * `authorizeConsumer` hook. Returns the success record on the happy path
58
- * or a fully-formed `Response` (402/404/429/400/403) on any short-circuit.
59
- *
60
- * Body parsing is the caller's responsibility — different wire formats
61
- * (OpenAI chat completions vs A2A JSON-RPC) have different envelopes; both
62
- * still ultimately produce a `ChatMessage[]`.
63
- */
64
- export async function authenticateAndGuard(
65
- c: Context,
66
- slug: string,
67
- messages: ChatMessage[],
68
- config: GatewayConfig,
69
- state: GatewayState,
70
- ): Promise<AuthorizedRequest | Response> {
71
- const startMs = Date.now()
72
- const requestId = generateRequestId()
73
- const ctx: RequestContext = { requestId, agentSlug: slug, startMs }
74
- await state.obs?.onRequestStart?.(ctx)
75
-
76
- const agent = await config.resolveAgent(slug)
77
- if (!agent || !agent.enabled) {
78
- return c.json({ error: { message: 'Agent not found', type: 'not_found' } }, 404)
79
- }
80
- if (!messages?.length) {
81
- return c.json(
82
- { error: { message: 'messages array required', type: 'invalid_request' } },
83
- 400,
84
- )
85
- }
86
-
87
- // Payment / auth.
88
- const spendAuthHeader = c.req.header('X-Payment-Signature')
89
- const authHeader = c.req.header('Authorization') ?? ''
90
- let consumerId: string | null = null
91
- let paymentMethod: PaymentMethod = 'none'
92
- let keyInfo: ApiKeyInfo | null = null
93
-
94
- if (spendAuthHeader) {
95
- const signer = await verifyX402(spendAuthHeader, config.x402, state.nonceStore)
96
- if (!signer) {
97
- await state.obs?.onAuthFailure?.(ctx, {
98
- method: 'x402',
99
- code: 'invalid_spend_auth',
100
- httpStatus: 402,
101
- })
102
- return c.json(
103
- {
104
- error: {
105
- message: 'Invalid X-Payment-Signature',
106
- type: 'authentication_error',
107
- code: 'invalid_spend_auth',
108
- },
109
- },
110
- {
111
- status: 402,
112
- headers: { 'X-Payment-Required': 'spendauth', 'X-Request-Id': requestId },
113
- },
114
- )
115
- }
116
- consumerId = signer
117
- paymentMethod = 'x402'
118
- } else if (isMppAuthEnabled(config) && authHeader.toLowerCase().startsWith('payment ')) {
119
- const signer = await verifyMpp(authHeader, config.mpp!, config.x402, state.nonceStore)
120
- if (!signer) {
121
- const realm = config.mpp!.realm
122
- const method = config.mpp!.method ?? 'blueprintevm'
123
- await state.obs?.onAuthFailure?.(ctx, {
124
- method: 'mpp',
125
- code: 'invalid_mpp_credential',
126
- httpStatus: 401,
127
- })
128
- return c.json(
129
- {
130
- error: {
131
- message: 'Invalid Payment credential',
132
- type: 'authentication_error',
133
- code: 'invalid_mpp_credential',
134
- },
135
- },
136
- {
137
- status: 401,
138
- headers: {
139
- 'WWW-Authenticate': `Payment realm="${realm}", method="${method}"`,
140
- 'X-Request-Id': requestId,
141
- },
142
- },
143
- )
144
- }
145
- consumerId = signer
146
- paymentMethod = 'mpp'
147
- } else if (authHeader.startsWith('Bearer ')) {
148
- const verify = config.verifyApiKey ?? (config.x402.demoMode ? defaultVerifyApiKey : null)
149
- if (!verify || !isApiKeyAuthEnabled(config)) {
150
- await state.obs?.onAuthFailure?.(ctx, {
151
- method: 'apikey',
152
- code: 'api_keys_not_configured',
153
- httpStatus: 401,
154
- })
155
- return c.json(
156
- { error: { message: 'API key authentication is not configured', type: 'authentication_error' } },
157
- { status: 401, headers: { 'X-Request-Id': requestId } },
158
- )
159
- }
160
- const key = await verify(authHeader)
161
- if (!key) {
162
- await state.obs?.onAuthFailure?.(ctx, {
163
- method: 'apikey',
164
- code: 'invalid_api_key',
165
- httpStatus: 401,
166
- })
167
- return c.json(
168
- { error: { message: 'Invalid API key', type: 'authentication_error' } },
169
- { status: 401, headers: { 'X-Request-Id': requestId } },
170
- )
171
- }
172
- if (key.scopes && key.scopes.length > 0 && !key.scopes.includes(state.requiredScope)) {
173
- await state.obs?.onAuthFailure?.(ctx, {
174
- method: 'apikey',
175
- code: 'insufficient_scope',
176
- httpStatus: 403,
177
- })
178
- return c.json(
179
- {
180
- error: {
181
- message: `API key missing required scope: ${state.requiredScope}`,
182
- type: 'forbidden',
183
- code: 'insufficient_scope',
184
- },
185
- },
186
- { status: 403, headers: { 'X-Request-Id': requestId } },
187
- )
188
- }
189
- consumerId = key.consumerId
190
- paymentMethod = 'apikey'
191
- keyInfo = key
192
- } else {
193
- await state.obs?.onAuthFailure?.(ctx, {
194
- method: 'none',
195
- code: 'payment_required',
196
- httpStatus: 402,
197
- })
198
- const methods: string[] = ['x402']
199
- if (isMppAuthEnabled(config)) methods.push('mpp')
200
- if (isApiKeyAuthEnabled(config)) methods.push('api_key')
201
- const headers: Record<string, string> = {
202
- 'X-Payment-Required': methods.join(', '),
203
- 'X-Request-Id': requestId,
204
- }
205
- if (isMppAuthEnabled(config) && config.mpp) {
206
- headers['WWW-Authenticate'] =
207
- `Payment realm="${config.mpp.realm}", method="${config.mpp.method ?? 'blueprintevm'}"`
208
- }
209
- return c.json(
210
- {
211
- error: {
212
- message: 'Payment required',
213
- type: 'payment_required',
214
- payment_methods: methods,
215
- x402: {
216
- operator: config.x402.operatorAddress,
217
- chain_id: config.x402.chainId,
218
- credits_address: config.x402.creditsAddress,
219
- estimated_amount_per_request: '20000',
220
- },
221
- ...(isMppAuthEnabled(config) && config.mpp
222
- ? { mpp: { realm: config.mpp.realm, method: config.mpp.method ?? 'blueprintevm' } }
223
- : {}),
224
- ...(isApiKeyAuthEnabled(config)
225
- ? {
226
- api_key: {
227
- purchase_url: config.baseUrl
228
- ? `${config.baseUrl}/agents/${slug}/api-keys`
229
- : undefined,
230
- },
231
- }
232
- : {}),
233
- },
234
- },
235
- { status: 402, headers },
236
- )
237
- }
238
-
239
- await state.obs?.onPaymentVerified?.(ctx, {
240
- method: paymentMethod,
241
- consumerId: consumerId,
242
- keyId: keyInfo?.keyId,
243
- })
244
-
245
- // Rate limit.
246
- const effectiveRateLimit = keyInfo?.rateLimitPerMinute
247
- ? { limit: keyInfo.rateLimitPerMinute, windowSeconds: 60 }
248
- : state.globalRateLimit
249
- const rl = await checkRateLimit(consumerId, effectiveRateLimit, state.rateLimitStore)
250
- if (!rl.allowed) {
251
- await state.obs?.onRateLimited?.(ctx, {
252
- consumerId: consumerId,
253
- retryAfterSeconds: rl.retryAfterSeconds ?? 60,
254
- })
255
- return c.json(
256
- {
257
- error: {
258
- message: 'Rate limit exceeded',
259
- type: 'rate_limit_error',
260
- retry_after: rl.retryAfterSeconds,
261
- },
262
- },
263
- {
264
- status: 429,
265
- headers: {
266
- 'Retry-After': String(rl.retryAfterSeconds ?? 60),
267
- 'X-Request-Id': requestId,
268
- },
269
- },
270
- )
271
- }
272
-
273
- // Filter consumer messages — strip consumer-side system, length-cap, injection scan.
274
- const { messages: filtered, injectionWarnings } = filterConsumerMessagesStrict(
275
- messages,
276
- state.maxLen,
277
- )
278
- if (injectionWarnings.length > 0) {
279
- await state.obs?.onInjectionDetected?.(ctx, {
280
- consumerId: consumerId,
281
- patterns: injectionWarnings,
282
- blocked: !!config.blockInjection,
283
- })
284
- if (config.blockInjection) {
285
- return c.json(
286
- {
287
- error: {
288
- message: 'Request rejected: potential prompt injection detected',
289
- type: 'content_policy_violation',
290
- },
291
- },
292
- { status: 400, headers: { 'X-Request-Id': requestId } },
293
- )
294
- }
295
- }
296
-
297
- const userMessage = filtered
298
- .filter((m) => m.role === 'user')
299
- .map((m) => m.content)
300
- .join('\n\n')
301
- if (!userMessage) {
302
- return c.json(
303
- { error: { message: 'No user message provided', type: 'invalid_request' } },
304
- 400,
305
- )
306
- }
307
-
308
- if (config.authorizeConsumer) {
309
- const authz = await config.authorizeConsumer(agent, {
310
- method: paymentMethod,
311
- consumerId: consumerId,
312
- keyId: keyInfo?.keyId,
313
- requestId,
314
- })
315
- if (!authz.allow) {
316
- return c.json(
317
- {
318
- error: {
319
- message: authz.reason,
320
- type: 'authorization_denied',
321
- code: authz.code,
322
- },
323
- },
324
- { status: 403, headers: { 'X-Request-Id': requestId } },
325
- )
326
- }
327
- }
328
-
329
- return {
330
- agent,
331
- consumerId,
332
- paymentMethod,
333
- keyInfo,
334
- userMessage,
335
- rateLimitRemaining: rl.remaining,
336
- requestId,
337
- startMs,
338
- }
339
- }
340
-
341
- /**
342
- * Yield the inner sandbox's response as text deltas, applying the
343
- * system-prompt redaction filter on each delta so leakage of the agent's
344
- * system prompt back through the model's output is suppressed identically
345
- * whether the caller is on the OpenAI-compat path or A2A.
346
- *
347
- * Aborts when `signal` fires (used by A2A `tasks/cancel`).
348
- */
349
- export async function* dispatchSandboxStream(
350
- agent: AgentMeta,
351
- userMessage: string,
352
- consumerId: string,
353
- config: GatewayConfig,
354
- signal?: AbortSignal,
355
- sessionId?: string,
356
- ): AsyncIterable<string> {
357
- for await (const event of dispatchSandboxStreamRich(
358
- agent,
359
- userMessage,
360
- consumerId,
361
- config,
362
- signal,
363
- sessionId,
364
- )) {
365
- if (event.kind === 'text') yield event.delta
366
- }
367
- }
368
-
369
- /**
370
- * A2A-shaped dispatch event. Distinguishes text deltas from sandbox-signalled
371
- * pause-for-input events. The A2A handler uses this richer variant so it can
372
- * emit `input-required` status updates; the OpenAI-compat path consumes the
373
- * text-only `dispatchSandboxStream` adapter above.
374
- */
375
- export type A2ADispatchEvent =
376
- | { kind: 'text'; delta: string }
377
- | { kind: 'input-required'; prompt?: string }
378
-
379
- /**
380
- * Like `dispatchSandboxStream` but yields a discriminated union so callers can
381
- * react to `input-required` signals from the sandbox. The sandbox opts in by
382
- * emitting `{ type: 'input-required', data: { inputRequired: { prompt? } } }`
383
- * (or by setting `data.inputRequired` on any event); sandboxes that don't
384
- * emit such events see identical behavior.
385
- *
386
- * `sessionId` defaults to `consumer:<id>` matching the existing single-turn
387
- * path; multi-turn continuations pass an explicit `taskId` so the sandbox can
388
- * keep per-task conversation memory.
389
- */
390
- export async function* dispatchSandboxStreamRich(
391
- agent: AgentMeta,
392
- userMessage: string,
393
- consumerId: string,
394
- config: GatewayConfig,
395
- signal?: AbortSignal,
396
- sessionId?: string,
397
- ): AsyncIterable<A2ADispatchEvent> {
398
- const box = await config.getSandbox(agent)
399
- const promptStream = box.streamPrompt(userMessage, {
400
- sessionId: sessionId ?? `consumer:${consumerId}`,
401
- systemPrompt: agent.systemPrompt,
402
- })
403
- for await (const event of promptStream) {
404
- if (signal?.aborted) return
405
- if (
406
- event.type === 'message.part.updated' &&
407
- event.data?.part?.type === 'text' &&
408
- event.data.delta
409
- ) {
410
- yield {
411
- kind: 'text',
412
- delta: redactSystemPromptFromOutput(event.data.delta, agent.systemPrompt),
413
- }
414
- continue
415
- }
416
- if (event.type === 'input-required' || event.data?.inputRequired) {
417
- yield { kind: 'input-required', prompt: event.data?.inputRequired?.prompt }
418
- // Terminal for the sandbox stream — sandbox SHOULD stop emitting until
419
- // the gateway dispatches a continuation message with the new user input.
420
- return
421
- }
422
- }
423
- }
424
-
425
- /**
426
- * Record usage event + settle payment + invoke the observer. Both wire
427
- * formats call this once their stream has drained, so settlement happens
428
- * exactly once per request regardless of protocol.
429
- */
430
- export async function settleAndRecord(
431
- agent: AgentMeta,
432
- authz: AuthorizedRequest,
433
- inputTokens: number,
434
- outputTokens: number,
435
- config: GatewayConfig,
436
- obs: GatewayObserver | undefined,
437
- ): Promise<void> {
438
- const totalCost = (inputTokens + outputTokens) * agent.pricePerTokenUsd
439
- const ownerEarned = totalCost * (1 - agent.platformFeePercent)
440
- const platformFee = totalCost * agent.platformFeePercent
441
- const usageEvent = {
442
- requestId: authz.requestId,
443
- agentId: agent.id,
444
- agentSlug: agent.slug,
445
- consumerId: authz.consumerId,
446
- paymentMethod: authz.paymentMethod,
447
- inputTokens,
448
- outputTokens,
449
- totalCostUsd: totalCost,
450
- ownerEarnedUsd: ownerEarned,
451
- platformFeeUsd: platformFee,
452
- durationMs: Date.now() - authz.startMs,
453
- }
454
- await config.recordUsage(usageEvent)
455
- const ctx: RequestContext = {
456
- requestId: authz.requestId,
457
- agentSlug: agent.slug,
458
- startMs: authz.startMs,
459
- }
460
- await obs?.onRequestComplete?.(ctx, usageEvent)
461
- if (config.settlePayment) {
462
- await config
463
- .settlePayment(
464
- {
465
- method: authz.paymentMethod,
466
- consumerId: authz.consumerId,
467
- requestId: authz.requestId,
468
- },
469
- totalCost,
470
- )
471
- .catch(async (err) => {
472
- const msg = err instanceof Error ? err.message : String(err)
473
- console.error(`[agent-gateway] settlement failed for ${authz.consumerId}: ${msg}`)
474
- await obs?.onSettlementError?.(ctx, {
475
- consumerId: authz.consumerId,
476
- method: authz.paymentMethod,
477
- errorMessage: msg,
478
- })
479
- })
480
- }
481
- }
482
-
483
- /** Token estimate matching the existing chat-completions handler (4 chars ≈ 1 token). */
484
- export function estimateTokens(text: string): number {
485
- return Math.ceil(text.length / 4)
486
- }
6
+ export type {
7
+ A2ADispatchEvent,
8
+ AuthorizedRequest,
9
+ GatewayState,
10
+ PaymentClaimHooks,
11
+ SettleAndRecordOptions,
12
+ } from './dispatch-types'
13
+
14
+ export {
15
+ estimateBillableInputTokens,
16
+ estimateTokens,
17
+ maximumBillableInputTokens,
18
+ requiredX402Amount,
19
+ } from './dispatch-pricing'
20
+
21
+ export { authenticateAndGuard } from './dispatch-authorization'
22
+
23
+ export {
24
+ beginPaymentExecution,
25
+ claimPayment,
26
+ markPaymentExecutionStarted,
27
+ reclaimPayment,
28
+ releasePayment,
29
+ releasePaymentAfterFailure,
30
+ renewPaymentExecution,
31
+ } from './dispatch-payment'
32
+
33
+ export {
34
+ dispatchSandboxStream,
35
+ dispatchSandboxStreamRich,
36
+ } from './dispatch-sandbox'
37
+
38
+ export { settleAndRecord } from './dispatch-settlement'
package/src/index.ts CHANGED
@@ -1,11 +1,58 @@
1
1
  export { createAgentGateway } from './middleware'
2
+ export { reclaimPayment } from './dispatch'
3
+ export {
4
+ recoverPayment,
5
+ recoverPayments,
6
+ type RecoverPaymentOptions,
7
+ type RecoverPaymentsOptions,
8
+ type PaymentRecoveryRun,
9
+ } from './payment-recovery-worker'
2
10
  export {
3
11
  verifyX402,
4
12
  verifyMpp,
13
+ verifyMppCredential,
5
14
  defaultVerifyApiKey,
6
15
  isApiKeyAuthEnabled,
7
16
  isMppAuthEnabled,
17
+ mppReplayNonceKey,
18
+ mppPaymentCredential,
19
+ type VerifiedMppCredential,
8
20
  } from './verify'
21
+ export {
22
+ MPP_CHARGE_PROTOCOL_VERSION,
23
+ mppPaymentOperationId,
24
+ type MppAuthenticatedCredential,
25
+ type MppChargeLifecycle,
26
+ type MppChargeOperation,
27
+ type MppChargeOperationState,
28
+ type MppChargeRecoveryResult,
29
+ type MppChargeRequest,
30
+ } from './mpp-payment'
31
+ export {
32
+ PAYMENT_RECOVERY_VERSION,
33
+ MemoryPaymentRecoveryStore,
34
+ PaymentRecoveryFenceError,
35
+ type PaymentRecoveryAttribution,
36
+ type PaymentRecoveryConfig,
37
+ type PaymentRecoveryRecord,
38
+ type PaymentRecoveryState,
39
+ type PaymentRecoveryStore,
40
+ type PaymentRecoveryTarget,
41
+ type PaymentSettlementBasis,
42
+ } from './payment-recovery'
43
+ export { SqlPaymentRecoveryStore } from './payment-recovery-sql'
44
+ export {
45
+ PAYMENT_PROTOCOL_VERSION,
46
+ MemoryPaymentOperations,
47
+ type MemoryPaymentOperationsOptions,
48
+ type PaymentAuthorizationContext,
49
+ type PaymentOperation,
50
+ type PaymentOperationNotFound,
51
+ type PaymentOperationRecoveryResult,
52
+ type PaymentOperationState,
53
+ type PaymentOperations,
54
+ type PaymentSettlementInput,
55
+ } from './payment-operations'
9
56
  export {
10
57
  filterConsumerMessages,
11
58
  filterConsumerMessagesStrict,
@@ -31,6 +78,10 @@ export {
31
78
  export {
32
79
  MemoryNonceStore,
33
80
  KvNonceStore,
81
+ isAtomicNonceStore,
82
+ type AtomicKvNonceClaim,
83
+ type AtomicNonceStore,
84
+ type KvNonceStoreOptions,
34
85
  type NonceStore,
35
86
  } from './nonce-store'
36
87
  export {
@@ -56,8 +107,12 @@ export type {
56
107
  PaymentResult,
57
108
  ApiKeyInfo,
58
109
  GatewayUsageEvent,
110
+ SandboxExecutionBudget,
111
+ SandboxUsageReceipt,
59
112
  SandboxStreamEvent,
60
113
  SandboxBox,
114
+ ApiKeyGatewayConfig,
115
+ CreateAgentGatewayConfig,
61
116
  GatewayConfig,
62
117
  ChatMessage,
63
118
  ChatCompletionRequest,
@@ -66,7 +121,7 @@ export type {
66
121
 
67
122
  // --- A2A protocol surface (Google Agent-to-Agent) ---
68
123
  // Types + task-store adapter. Handlers are wired automatically by
69
- // createAgentGateway when `GatewayConfig.a2a` (or its default) is honored;
124
+ // createAgentGateway with an in-memory store by default;
70
125
  // consumers only import these to BYO a durable TaskStore (D1, postgres, DO)
71
126
  // or to declare richer AgentMeta.skills for the Agent Card.
72
127
  export { InMemoryTaskStore, type TaskStore } from './a2a/task-store'
@@ -78,9 +133,12 @@ export {
78
133
  SqlTaskStore,
79
134
  } from './a2a/task-store-sql'
80
135
  export {
136
+ deliverDemoPushNotifications,
81
137
  deliverPushNotifications,
82
138
  InMemoryPushNotificationStore,
139
+ validatePushNotificationUrl,
83
140
  type PushDeliveryResult,
141
+ type PushNotificationDeliveryOptions,
84
142
  type PushNotificationAuthentication,
85
143
  type PushNotificationConfig,
86
144
  type PushNotificationStore,