@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.
- package/README.md +90 -3
- package/dist/chunk-C7Z2BRYV.js +5693 -0
- package/dist/chunk-C7Z2BRYV.js.map +1 -0
- 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/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-oQ58UakD.d.ts} +447 -172
- 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 +468 -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 +424 -0
- package/src/dispatch-settlement.ts +139 -0
- package/src/dispatch-types.ts +84 -0
- package/src/dispatch.ts +35 -483
- package/src/index.ts +59 -1
- package/src/middleware.ts +339 -35
- 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 +188 -49
- package/src/verify.ts +240 -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
|
+
}
|