@tangle-network/agent-gateway 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -2
- package/dist/chunk-GITV7CPT.js +84 -0
- package/dist/chunk-GITV7CPT.js.map +1 -0
- package/dist/chunk-J5SDVHOL.js +104 -0
- package/dist/chunk-J5SDVHOL.js.map +1 -0
- package/dist/chunk-MP6IIAIA.js +5651 -0
- package/dist/chunk-MP6IIAIA.js.map +1 -0
- package/dist/index.d.ts +70 -10
- package/dist/index.js +303 -21
- package/dist/index.js.map +1 -1
- package/dist/middleware.d.ts +7 -2
- package/dist/middleware.js +3 -2
- package/dist/nonce-store.d.ts +47 -11
- package/dist/nonce-store.js +9 -3
- package/dist/observer-types-A0RtA8uL.d.ts +95 -0
- package/dist/observer.d.ts +79 -0
- package/dist/observer.js +11 -0
- package/dist/observer.js.map +1 -0
- package/dist/{types-DEsMmS-X.d.ts → types-BHISsm7D.d.ts} +414 -170
- package/dist/types.d.ts +2 -1
- package/package.json +1 -1
- package/src/a2a/execution-fence.ts +162 -0
- package/src/a2a/handler.ts +506 -560
- package/src/a2a/message-send-execution.ts +241 -0
- package/src/a2a/message-stream-execution.ts +392 -0
- package/src/a2a/payment-recovery.ts +431 -0
- package/src/a2a/push-config-methods.ts +158 -0
- package/src/a2a/push-notifications.ts +172 -22
- package/src/a2a/task-cancellation.ts +50 -0
- package/src/a2a/task-finalization.ts +451 -0
- package/src/a2a/task-lifecycle.ts +54 -0
- package/src/a2a/task-methods.ts +163 -0
- package/src/a2a/task-push-delivery.ts +119 -0
- package/src/a2a/task-recovery.ts +11 -0
- package/src/a2a/task-state.ts +99 -0
- package/src/a2a/task-store-sql.ts +222 -24
- package/src/a2a/task-store.ts +58 -1
- package/src/a2a/task-submission-recovery.ts +178 -0
- package/src/a2a/types.ts +1 -0
- package/src/dispatch-authorization.ts +437 -0
- package/src/dispatch-payment-recovery.ts +248 -0
- package/src/dispatch-payment.ts +425 -0
- package/src/dispatch-pricing.ts +108 -0
- package/src/dispatch-sandbox.ts +422 -0
- package/src/dispatch-settlement.ts +139 -0
- package/src/dispatch-types.ts +81 -0
- package/src/dispatch.ts +35 -483
- package/src/index.ts +57 -1
- package/src/middleware.ts +307 -26
- package/src/mpp-payment.ts +117 -0
- package/src/nonce-store.ts +122 -20
- package/src/observer-types.ts +63 -0
- package/src/observer.ts +3 -63
- package/src/payment-operations.ts +485 -0
- package/src/payment-recovery-sql.ts +108 -0
- package/src/payment-recovery-worker.ts +488 -0
- package/src/payment-recovery.ts +331 -0
- package/src/payment-types.ts +48 -0
- package/src/types.ts +144 -46
- package/src/verify.ts +233 -71
- package/dist/chunk-M7ZJAK4K.js +0 -53
- package/dist/chunk-M7ZJAK4K.js.map +0 -1
- package/dist/chunk-Q4YAIEZY.js +0 -1763
- package/dist/chunk-Q4YAIEZY.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,57 @@
|
|
|
1
1
|
export { createAgentGateway } from './middleware.js';
|
|
2
|
-
import {
|
|
3
|
-
export {
|
|
2
|
+
import { G as GatewayConfig, P as PaymentOperationRecoveryResult, a as PaymentRecoveryRecord, M as MppAuthenticatedCredential, A as ApiKeyInfo, b as MppConfig, X as X402Config, c as PaymentRecoveryStore, S as SqlAdapter, C as ChatMessage } from './types-BHISsm7D.js';
|
|
3
|
+
export { d as A2A_ERROR_CODES, e as AgentCapabilities, f as AgentCard, g as AgentCardAuthentication, h as AgentMeta, i as AgentProvider, j as AgentSkill, k as Artifact, l as ChatCompletionChunk, m as ChatCompletionRequest, D as D1DatabaseLike, n as D1StmtLike, o as DataPart, F as FilePart, I as InMemoryPushNotificationStore, p as InMemoryTaskStore, J as JSONRPCErrorResponse, q as JSONRPCRequest, r as JSONRPCResponse, s as JSONRPCSuccessResponse, t as MPP_CHARGE_PROTOCOL_VERSION, u as MemoryPaymentOperations, v as MemoryPaymentOperationsOptions, w as MemoryPaymentRecoveryStore, x as Message, y as MessageSendParams, z as MppChargeLifecycle, B as MppChargeOperation, E as MppChargeOperationState, H as MppChargeRecoveryResult, K as MppChargeRequest, L as PAYMENT_PROTOCOL_VERSION, N as PAYMENT_RECOVERY_VERSION, O as Part, Q as PaymentAuthorizationContext, R as PaymentOperation, T as PaymentOperationNotFound, U as PaymentOperationState, V as PaymentOperations, W as PaymentRecoveryAttribution, Y as PaymentRecoveryConfig, Z as PaymentRecoveryFenceError, _ as PaymentRecoveryState, $ as PaymentRecoveryTarget, a0 as PaymentResult, a1 as PaymentSettlementInput, a2 as PushDeliveryResult, a3 as PushNotificationAuthentication, a4 as PushNotificationConfig, a5 as PushNotificationDeliveryOptions, a6 as PushNotificationStore, a7 as SandboxBox, a8 as SandboxStreamEvent, a9 as SqlPushNotificationStore, aa as SqlTaskStore, ab as StreamingEvent, ac as Task, ad as TaskArtifactUpdateEvent, ae as TaskIdParams, af as TaskPushNotificationConfig, ag as TaskPushNotificationConfigGetParams, ah as TaskState, ai as TaskStatus, aj as TaskStatusUpdateEvent, ak as TaskStore, al as TextPart, am as d1ToSqlAdapter, an as deliverDemoPushNotifications, ao as deliverPushNotifications, ap as mppPaymentOperationId, aq as validatePushNotificationUrl } from './types-BHISsm7D.js';
|
|
4
4
|
import { NonceStore } from './nonce-store.js';
|
|
5
|
-
export { KvNonceStore, MemoryNonceStore } from './nonce-store.js';
|
|
5
|
+
export { AtomicKvNonceClaim, AtomicNonceStore, KvNonceStore, KvNonceStoreOptions, MemoryNonceStore, isAtomicNonceStore } from './nonce-store.js';
|
|
6
|
+
export { CompositeObserver, ConsoleObserver, generateRequestId } from './observer.js';
|
|
6
7
|
export { KvRateLimitStore, MemoryRateLimitStore, RateLimitConfig, RateLimitResult, RateLimitStore, checkRateLimit } from './rate-limit.js';
|
|
8
|
+
import { S as SandboxUsageReceipt } from './observer-types-A0RtA8uL.js';
|
|
9
|
+
export { A as AuthFailureReason, G as GatewayObserver, a as GatewayUsageEvent, P as PaymentMethod, b as PaymentSettlementBasis, R as RequestContext, c as SandboxExecutionBudget } from './observer-types-A0RtA8uL.js';
|
|
7
10
|
export { ApiKey, ApiKeyCreateRequest, ApiKeyRoutesConfig, ApiKeyStore, createApiKeyRoutes, verifyApiKeyFromStore } from './api-keys.js';
|
|
8
11
|
export { PublishRequest, PublishRoutesConfig, PublishStore, PublishedConfig, createPublishRoutes } from './publish.js';
|
|
9
12
|
import 'hono/types';
|
|
10
13
|
import 'hono';
|
|
11
14
|
|
|
15
|
+
declare function reclaimPayment(operationId: string, config: GatewayConfig): Promise<PaymentOperationRecoveryResult>;
|
|
16
|
+
|
|
17
|
+
interface RecoveryWorkerOptions {
|
|
18
|
+
now?: number;
|
|
19
|
+
/** Fresh wall-clock source for each row's lease and retry timestamps. */
|
|
20
|
+
clock?: () => number;
|
|
21
|
+
workerId?: string;
|
|
22
|
+
}
|
|
23
|
+
interface RecoverPaymentOptions extends RecoveryWorkerOptions {
|
|
24
|
+
/** Process one requested row even when its normal retry time is later. */
|
|
25
|
+
force?: boolean;
|
|
26
|
+
/** Exact receipt recovered from a durable protocol record, such as an A2A task. */
|
|
27
|
+
usage?: SandboxUsageReceipt;
|
|
28
|
+
}
|
|
29
|
+
/** Batch recovery never accepts receipt data because each receipt belongs to one row. */
|
|
30
|
+
interface RecoverPaymentsOptions extends RecoveryWorkerOptions {
|
|
31
|
+
limit?: number;
|
|
32
|
+
}
|
|
33
|
+
interface PaymentRecoveryRun {
|
|
34
|
+
scanned: number;
|
|
35
|
+
reconciled: number;
|
|
36
|
+
deferred: number;
|
|
37
|
+
failed: number;
|
|
38
|
+
}
|
|
39
|
+
/** Scan and reconcile due payment rows. Safe to run concurrently on many workers. */
|
|
40
|
+
declare function recoverPayments(config: GatewayConfig, options?: RecoverPaymentsOptions): Promise<PaymentRecoveryRun>;
|
|
41
|
+
/** Reconcile one payment identity. Hosts can expose this through a private worker API. */
|
|
42
|
+
declare function recoverPayment(recoveryId: string, config: GatewayConfig, options?: RecoverPaymentOptions): Promise<PaymentRecoveryRecord | undefined>;
|
|
43
|
+
|
|
44
|
+
interface VerifiedMppCredential extends MppAuthenticatedCredential {
|
|
45
|
+
/** Opaque replay key. BlueprinTEVM shares the x402 nonce namespace. */
|
|
46
|
+
replayKey: string;
|
|
47
|
+
}
|
|
48
|
+
/** Return the legacy opaque nonce key used by older consumers. */
|
|
49
|
+
declare function mppReplayNonceKey(authHeader: string): string | undefined;
|
|
50
|
+
/** Return the decoded method credential for the post-guard charge lifecycle. */
|
|
51
|
+
declare function mppPaymentCredential(authHeader: string): string | undefined;
|
|
12
52
|
/** Pure capability checks shared by discovery and every request protocol. */
|
|
13
53
|
declare function isApiKeyAuthEnabled(config: Pick<GatewayConfig, 'verifyApiKey' | 'x402'>): boolean;
|
|
14
|
-
/** MPP is enabled only when
|
|
54
|
+
/** MPP is enabled only when authentication and method settlement are complete. */
|
|
15
55
|
declare function isMppAuthEnabled(config: Pick<GatewayConfig, 'mpp' | 'x402'>): boolean;
|
|
16
56
|
/**
|
|
17
57
|
* Verify x402 SpendAuth signature (EIP-712).
|
|
@@ -24,25 +64,45 @@ declare function isMppAuthEnabled(config: Pick<GatewayConfig, 'mpp' | 'x402'>):
|
|
|
24
64
|
* neither — rejected by createAgentGateway and
|
|
25
65
|
* by this function as defense-in-depth.
|
|
26
66
|
*/
|
|
27
|
-
declare function verifyX402(spendAuthHeader: string, config: X402Config, nonceStore?: NonceStore): Promise<string | null>;
|
|
67
|
+
declare function verifyX402(spendAuthHeader: string, config: X402Config, nonceStore?: NonceStore, minimumAmount?: bigint, markNonce?: boolean): Promise<string | null>;
|
|
28
68
|
/**
|
|
29
69
|
* Verify MPP (Machine Payments Protocol) Authorization: Payment header.
|
|
30
70
|
*
|
|
31
71
|
* MPP uses `Authorization: Payment <method> <credential>` format. The
|
|
32
|
-
* credential is method-specific; `MppConfig.
|
|
33
|
-
* and returns the consumer identity. The built-in `blueprintevm` path can
|
|
72
|
+
* credential is method-specific; `MppConfig.authenticateCredential` owns authentication
|
|
73
|
+
* and returns the consumer plus stable payment identity. The built-in `blueprintevm` path can
|
|
34
74
|
* reuse the x402 verifier for credentials with the compatible payload shape.
|
|
35
75
|
*
|
|
36
|
-
* Returns
|
|
76
|
+
* Returns authenticated identity if valid, null otherwise.
|
|
37
77
|
* In demo mode, accepts any well-formed Payment header with an identity.
|
|
38
78
|
*/
|
|
39
|
-
declare function
|
|
79
|
+
declare function verifyMppCredential(authHeader: string, config: MppConfig, x402Config: X402Config, nonceStore?: NonceStore, minimumAmount?: bigint, markNonce?: boolean): Promise<VerifiedMppCredential | null>;
|
|
80
|
+
/**
|
|
81
|
+
* Verify an MPP credential using the 0.7.1 public return shape.
|
|
82
|
+
* Rich durable callers use verifyMppCredential instead.
|
|
83
|
+
*/
|
|
84
|
+
declare function verifyMpp(authHeader: string, config: MppConfig, x402Config: X402Config, nonceStore?: NonceStore, minimumAmount?: bigint, markNonce?: boolean): Promise<string | null>;
|
|
40
85
|
/**
|
|
41
86
|
* Default API key verifier — accepts any `sk_agent_*` key (demo mode).
|
|
42
87
|
* Override in GatewayConfig.verifyApiKey for production.
|
|
43
88
|
*/
|
|
44
89
|
declare function defaultVerifyApiKey(authHeader: string): Promise<ApiKeyInfo | null>;
|
|
45
90
|
|
|
91
|
+
/** Durable recovery outbox for D1, sqlite, libSQL, or an adapted SQL driver. */
|
|
92
|
+
declare class SqlPaymentRecoveryStore implements PaymentRecoveryStore {
|
|
93
|
+
private readonly db;
|
|
94
|
+
private readonly table;
|
|
95
|
+
constructor(db: SqlAdapter, options?: {
|
|
96
|
+
table?: string;
|
|
97
|
+
});
|
|
98
|
+
/** Idempotent. Run this before the gateway starts accepting traffic. */
|
|
99
|
+
migrate(): Promise<void>;
|
|
100
|
+
createIfAbsent(record: PaymentRecoveryRecord): Promise<boolean>;
|
|
101
|
+
get(id: string): Promise<PaymentRecoveryRecord | undefined>;
|
|
102
|
+
compareAndSet(expected: PaymentRecoveryRecord, next: PaymentRecoveryRecord): Promise<boolean>;
|
|
103
|
+
listDue(now: number, limit: number): Promise<PaymentRecoveryRecord[]>;
|
|
104
|
+
}
|
|
105
|
+
|
|
46
106
|
/**
|
|
47
107
|
* Detect prompt injection attempts.
|
|
48
108
|
* Returns array of matched pattern descriptions, empty if clean.
|
|
@@ -85,4 +145,4 @@ declare function filterConsumerMessagesStrict(messages: ChatMessage[], maxLength
|
|
|
85
145
|
*/
|
|
86
146
|
declare function redactSystemPromptFromOutput(output: string, systemPrompt: string | undefined): string;
|
|
87
147
|
|
|
88
|
-
export { ApiKeyInfo, ChatMessage, GatewayConfig, MppConfig, NonceStore, X402Config, defaultVerifyApiKey, detectInjection, filterConsumerMessages, filterConsumerMessagesStrict, isApiKeyAuthEnabled, isMppAuthEnabled, redactSystemPromptFromOutput, verifyMpp, verifyX402 };
|
|
148
|
+
export { ApiKeyInfo, ChatMessage, GatewayConfig, MppAuthenticatedCredential, MppConfig, NonceStore, PaymentOperationRecoveryResult, PaymentRecoveryRecord, type PaymentRecoveryRun, PaymentRecoveryStore, type RecoverPaymentOptions, type RecoverPaymentsOptions, SandboxUsageReceipt, SqlAdapter, SqlPaymentRecoveryStore, type VerifiedMppCredential, X402Config, defaultVerifyApiKey, detectInjection, filterConsumerMessages, filterConsumerMessagesStrict, isApiKeyAuthEnabled, isMppAuthEnabled, mppPaymentCredential, mppReplayNonceKey, reclaimPayment, recoverPayment, recoverPayments, redactSystemPromptFromOutput, verifyMpp, verifyMppCredential, verifyX402 };
|
package/dist/index.js
CHANGED
|
@@ -4,28 +4,48 @@ import {
|
|
|
4
4
|
} from "./chunk-5O75YDQP.js";
|
|
5
5
|
import {
|
|
6
6
|
A2A_ERROR_CODES,
|
|
7
|
-
CompositeObserver,
|
|
8
|
-
ConsoleObserver,
|
|
9
7
|
InMemoryPushNotificationStore,
|
|
10
8
|
InMemoryTaskStore,
|
|
9
|
+
MPP_CHARGE_PROTOCOL_VERSION,
|
|
10
|
+
MemoryPaymentOperations,
|
|
11
|
+
MemoryPaymentRecoveryStore,
|
|
12
|
+
PAYMENT_PROTOCOL_VERSION,
|
|
13
|
+
PAYMENT_RECOVERY_VERSION,
|
|
14
|
+
PaymentRecoveryFenceError,
|
|
11
15
|
SqlPushNotificationStore,
|
|
12
16
|
createAgentGateway,
|
|
13
17
|
defaultVerifyApiKey,
|
|
18
|
+
deliverDemoPushNotifications,
|
|
14
19
|
deliverPushNotifications,
|
|
15
20
|
detectInjection,
|
|
16
21
|
filterConsumerMessages,
|
|
17
22
|
filterConsumerMessagesStrict,
|
|
18
|
-
|
|
23
|
+
hasPendingPaymentRecovery,
|
|
24
|
+
inspectTaskExecution,
|
|
19
25
|
isApiKeyAuthEnabled,
|
|
20
26
|
isMppAuthEnabled,
|
|
27
|
+
mppPaymentCredential,
|
|
28
|
+
mppPaymentOperationId,
|
|
29
|
+
mppReplayNonceKey,
|
|
30
|
+
reclaimPayment,
|
|
31
|
+
recoverPayment,
|
|
32
|
+
recoverPayments,
|
|
21
33
|
redactSystemPromptFromOutput,
|
|
34
|
+
validatePushNotificationUrl,
|
|
22
35
|
verifyMpp,
|
|
36
|
+
verifyMppCredential,
|
|
23
37
|
verifyX402
|
|
24
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-MP6IIAIA.js";
|
|
25
39
|
import {
|
|
26
40
|
KvNonceStore,
|
|
27
|
-
MemoryNonceStore
|
|
28
|
-
|
|
41
|
+
MemoryNonceStore,
|
|
42
|
+
isAtomicNonceStore
|
|
43
|
+
} from "./chunk-J5SDVHOL.js";
|
|
44
|
+
import {
|
|
45
|
+
CompositeObserver,
|
|
46
|
+
ConsoleObserver,
|
|
47
|
+
generateRequestId
|
|
48
|
+
} from "./chunk-GITV7CPT.js";
|
|
29
49
|
import {
|
|
30
50
|
createPublishRoutes
|
|
31
51
|
} from "./chunk-5ZJPIIIV.js";
|
|
@@ -35,6 +55,96 @@ import {
|
|
|
35
55
|
checkRateLimit
|
|
36
56
|
} from "./chunk-XCTXHZ76.js";
|
|
37
57
|
|
|
58
|
+
// src/payment-recovery-sql.ts
|
|
59
|
+
var TABLE_DDL = (table) => `
|
|
60
|
+
CREATE TABLE IF NOT EXISTS ${table} (
|
|
61
|
+
id TEXT PRIMARY KEY,
|
|
62
|
+
state TEXT NOT NULL,
|
|
63
|
+
next_attempt_at INTEGER NOT NULL,
|
|
64
|
+
revision INTEGER NOT NULL,
|
|
65
|
+
payload TEXT NOT NULL,
|
|
66
|
+
updated_at INTEGER NOT NULL
|
|
67
|
+
)
|
|
68
|
+
`;
|
|
69
|
+
var DUE_INDEX_DDL = (table) => `
|
|
70
|
+
CREATE INDEX IF NOT EXISTS idx_${table}_due
|
|
71
|
+
ON ${table} (state, next_attempt_at)
|
|
72
|
+
`;
|
|
73
|
+
var SqlPaymentRecoveryStore = class {
|
|
74
|
+
constructor(db, options = {}) {
|
|
75
|
+
this.db = db;
|
|
76
|
+
this.table = options.table ?? "payment_recovery";
|
|
77
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(this.table)) {
|
|
78
|
+
throw new Error("payment recovery table name is invalid");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
db;
|
|
82
|
+
table;
|
|
83
|
+
/** Idempotent. Run this before the gateway starts accepting traffic. */
|
|
84
|
+
async migrate() {
|
|
85
|
+
await this.db.exec(TABLE_DDL(this.table));
|
|
86
|
+
await this.db.exec(DUE_INDEX_DDL(this.table));
|
|
87
|
+
}
|
|
88
|
+
async createIfAbsent(record) {
|
|
89
|
+
try {
|
|
90
|
+
const result = await this.db.exec(
|
|
91
|
+
`INSERT INTO ${this.table} (id, state, next_attempt_at, revision, payload, updated_at) VALUES (?, ?, ?, ?, ?, ?)`,
|
|
92
|
+
[
|
|
93
|
+
record.id,
|
|
94
|
+
record.state,
|
|
95
|
+
record.nextAttemptAt,
|
|
96
|
+
record.revision,
|
|
97
|
+
JSON.stringify(record),
|
|
98
|
+
record.updatedAt
|
|
99
|
+
]
|
|
100
|
+
);
|
|
101
|
+
return result.rowsAffected === 1;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (await this.get(record.id)) return false;
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async get(id) {
|
|
108
|
+
const rows = await this.db.query(
|
|
109
|
+
`SELECT payload FROM ${this.table} WHERE id = ?`,
|
|
110
|
+
[id]
|
|
111
|
+
);
|
|
112
|
+
return rows[0] ? parseRecord(rows[0].payload) : void 0;
|
|
113
|
+
}
|
|
114
|
+
async compareAndSet(expected, next) {
|
|
115
|
+
const result = await this.db.exec(
|
|
116
|
+
`UPDATE ${this.table} SET state = ?, next_attempt_at = ?, revision = ?, payload = ?, updated_at = ? WHERE id = ? AND revision = ?`,
|
|
117
|
+
[
|
|
118
|
+
next.state,
|
|
119
|
+
next.nextAttemptAt,
|
|
120
|
+
next.revision,
|
|
121
|
+
JSON.stringify(next),
|
|
122
|
+
next.updatedAt,
|
|
123
|
+
expected.id,
|
|
124
|
+
expected.revision
|
|
125
|
+
]
|
|
126
|
+
);
|
|
127
|
+
return result.rowsAffected === 1;
|
|
128
|
+
}
|
|
129
|
+
async listDue(now, limit) {
|
|
130
|
+
if (!Number.isSafeInteger(limit) || limit <= 0) {
|
|
131
|
+
throw new Error("payment recovery scan limit must be a positive safe integer");
|
|
132
|
+
}
|
|
133
|
+
const rows = await this.db.query(
|
|
134
|
+
`SELECT payload FROM ${this.table} WHERE state <> ? AND next_attempt_at <= ? ORDER BY next_attempt_at ASC LIMIT ?`,
|
|
135
|
+
["reconciled", now, limit]
|
|
136
|
+
);
|
|
137
|
+
return rows.map((row) => parseRecord(row.payload));
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
function parseRecord(payload) {
|
|
141
|
+
const value = JSON.parse(payload);
|
|
142
|
+
if (value.version !== 1 || !value.id || !Number.isSafeInteger(value.revision)) {
|
|
143
|
+
throw new Error("stored payment recovery record is invalid");
|
|
144
|
+
}
|
|
145
|
+
return value;
|
|
146
|
+
}
|
|
147
|
+
|
|
38
148
|
// src/a2a/task-store-sql.ts
|
|
39
149
|
function d1ToSqlAdapter(db) {
|
|
40
150
|
return {
|
|
@@ -60,7 +170,9 @@ var TASKS_TABLE_DDL = (table) => `
|
|
|
60
170
|
context_id TEXT NOT NULL,
|
|
61
171
|
state TEXT NOT NULL,
|
|
62
172
|
payload TEXT NOT NULL,
|
|
63
|
-
updated_at INTEGER NOT NULL
|
|
173
|
+
updated_at INTEGER NOT NULL,
|
|
174
|
+
execution_request_id TEXT,
|
|
175
|
+
execution_lease_expires_at REAL
|
|
64
176
|
)
|
|
65
177
|
`;
|
|
66
178
|
var CTX_INDEX_DDL = (table) => `
|
|
@@ -79,40 +191,187 @@ var SqlTaskStore = class {
|
|
|
79
191
|
get table() {
|
|
80
192
|
return this.opts.table ?? "a2a_tasks";
|
|
81
193
|
}
|
|
194
|
+
async readRow(id) {
|
|
195
|
+
const rows = await this.db.query(
|
|
196
|
+
`SELECT payload, updated_at, execution_request_id, execution_lease_expires_at FROM ${this.table} WHERE id = ?`,
|
|
197
|
+
[id]
|
|
198
|
+
);
|
|
199
|
+
const row = rows[0];
|
|
200
|
+
return row ? {
|
|
201
|
+
payload: row.payload,
|
|
202
|
+
updatedAt: row.updated_at,
|
|
203
|
+
executionRequestId: row.execution_request_id ?? null,
|
|
204
|
+
executionLeaseExpiresAt: row.execution_lease_expires_at ?? null
|
|
205
|
+
} : void 0;
|
|
206
|
+
}
|
|
207
|
+
isExpired(updatedAt, task) {
|
|
208
|
+
return Date.now() - updatedAt > this.ttlMs && !hasPendingPaymentRecovery(task);
|
|
209
|
+
}
|
|
210
|
+
async deleteObservedRow(id, payload, updatedAt) {
|
|
211
|
+
const result = await this.db.exec(
|
|
212
|
+
`DELETE FROM ${this.table} WHERE id = ? AND payload = ? AND updated_at = ?`,
|
|
213
|
+
[id, payload, updatedAt]
|
|
214
|
+
);
|
|
215
|
+
return result.rowsAffected;
|
|
216
|
+
}
|
|
82
217
|
/** Idempotent. Call once at deploy. */
|
|
83
218
|
async migrate() {
|
|
84
219
|
await this.db.exec(TASKS_TABLE_DDL(this.table));
|
|
220
|
+
for (const column of [
|
|
221
|
+
"execution_request_id TEXT",
|
|
222
|
+
"execution_lease_expires_at REAL"
|
|
223
|
+
]) {
|
|
224
|
+
try {
|
|
225
|
+
await this.db.exec(`ALTER TABLE ${this.table} ADD COLUMN ${column}`);
|
|
226
|
+
} catch (error) {
|
|
227
|
+
const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
|
|
228
|
+
if (!message.includes("duplicate column") && !message.includes("already exists")) throw error;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
85
231
|
await this.db.exec(CTX_INDEX_DDL(this.table));
|
|
86
232
|
}
|
|
87
233
|
async get(id) {
|
|
88
|
-
const
|
|
89
|
-
`SELECT payload, updated_at FROM ${this.table} WHERE id = ?`,
|
|
90
|
-
[id]
|
|
91
|
-
);
|
|
92
|
-
const row = rows[0];
|
|
234
|
+
const row = await this.readRow(id);
|
|
93
235
|
if (!row) return void 0;
|
|
94
|
-
|
|
95
|
-
|
|
236
|
+
const task = JSON.parse(row.payload);
|
|
237
|
+
if (this.isExpired(row.updatedAt, task)) {
|
|
238
|
+
await this.deleteObservedRow(id, row.payload, row.updatedAt);
|
|
96
239
|
return void 0;
|
|
97
240
|
}
|
|
98
|
-
return
|
|
241
|
+
return task;
|
|
242
|
+
}
|
|
243
|
+
async insert(task) {
|
|
244
|
+
const payload = JSON.stringify(task);
|
|
245
|
+
const [executionRequestId, executionLeaseExpiresAt] = executionColumns(task);
|
|
246
|
+
const result = await this.db.exec(
|
|
247
|
+
`INSERT INTO ${this.table} (id, context_id, state, payload, updated_at, execution_request_id, execution_lease_expires_at)
|
|
248
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
249
|
+
[
|
|
250
|
+
task.id,
|
|
251
|
+
task.contextId,
|
|
252
|
+
task.status.state,
|
|
253
|
+
payload,
|
|
254
|
+
Date.now(),
|
|
255
|
+
executionRequestId,
|
|
256
|
+
executionLeaseExpiresAt
|
|
257
|
+
]
|
|
258
|
+
);
|
|
259
|
+
return result.rowsAffected;
|
|
99
260
|
}
|
|
100
261
|
async put(task) {
|
|
101
262
|
const payload = JSON.stringify(task);
|
|
102
263
|
const updatedAt = Date.now();
|
|
264
|
+
const [executionRequestId, executionLeaseExpiresAt] = executionColumns(task);
|
|
103
265
|
const updated = await this.db.exec(
|
|
104
|
-
`UPDATE ${this.table}
|
|
105
|
-
|
|
266
|
+
`UPDATE ${this.table}
|
|
267
|
+
SET context_id = ?, state = ?, payload = ?, updated_at = ?,
|
|
268
|
+
execution_request_id = ?, execution_lease_expires_at = ?
|
|
269
|
+
WHERE id = ?`,
|
|
270
|
+
[
|
|
271
|
+
task.contextId,
|
|
272
|
+
task.status.state,
|
|
273
|
+
payload,
|
|
274
|
+
updatedAt,
|
|
275
|
+
executionRequestId,
|
|
276
|
+
executionLeaseExpiresAt,
|
|
277
|
+
task.id
|
|
278
|
+
]
|
|
106
279
|
);
|
|
107
280
|
if (updated.rowsAffected === 0) {
|
|
108
281
|
await this.db.exec(
|
|
109
|
-
`INSERT INTO ${this.table} (id, context_id, state, payload, updated_at
|
|
110
|
-
|
|
282
|
+
`INSERT INTO ${this.table} (id, context_id, state, payload, updated_at, execution_request_id, execution_lease_expires_at)
|
|
283
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
284
|
+
[
|
|
285
|
+
task.id,
|
|
286
|
+
task.contextId,
|
|
287
|
+
task.status.state,
|
|
288
|
+
payload,
|
|
289
|
+
updatedAt,
|
|
290
|
+
executionRequestId,
|
|
291
|
+
executionLeaseExpiresAt
|
|
292
|
+
]
|
|
111
293
|
);
|
|
112
294
|
}
|
|
113
295
|
}
|
|
296
|
+
async createIfAbsent(task) {
|
|
297
|
+
try {
|
|
298
|
+
return await this.insert(task) === 1;
|
|
299
|
+
} catch (error) {
|
|
300
|
+
const row = await this.readRow(task.id);
|
|
301
|
+
if (!row) throw error;
|
|
302
|
+
const existing = JSON.parse(row.payload);
|
|
303
|
+
if (!this.isExpired(row.updatedAt, existing)) return false;
|
|
304
|
+
await this.deleteObservedRow(task.id, row.payload, row.updatedAt);
|
|
305
|
+
try {
|
|
306
|
+
return await this.insert(task) === 1;
|
|
307
|
+
} catch (retryError) {
|
|
308
|
+
if (await this.get(task.id)) return false;
|
|
309
|
+
throw retryError;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
async compareAndSet(expected, next) {
|
|
314
|
+
const expectedPayload = JSON.stringify(expected);
|
|
315
|
+
const payload = JSON.stringify(next);
|
|
316
|
+
const [executionRequestId, executionLeaseExpiresAt] = executionColumns(next);
|
|
317
|
+
const result = await this.db.exec(
|
|
318
|
+
`UPDATE ${this.table}
|
|
319
|
+
SET context_id = ?, state = ?, payload = ?, updated_at = ?,
|
|
320
|
+
execution_request_id = ?, execution_lease_expires_at = ?
|
|
321
|
+
WHERE id = ? AND payload = ?`,
|
|
322
|
+
[
|
|
323
|
+
next.contextId,
|
|
324
|
+
next.status.state,
|
|
325
|
+
payload,
|
|
326
|
+
Date.now(),
|
|
327
|
+
executionRequestId,
|
|
328
|
+
executionLeaseExpiresAt,
|
|
329
|
+
expected.id,
|
|
330
|
+
expectedPayload
|
|
331
|
+
]
|
|
332
|
+
);
|
|
333
|
+
return result.rowsAffected === 1;
|
|
334
|
+
}
|
|
335
|
+
async compareAndSetExecution(expected, next, requestId, now) {
|
|
336
|
+
const expectedMarker = inspectTaskExecution(expected);
|
|
337
|
+
const nextMarker = inspectTaskExecution(next);
|
|
338
|
+
if (expectedMarker.state !== "valid" || nextMarker.state !== "valid" || expectedMarker.marker.requestId !== requestId || nextMarker.marker.requestId !== requestId || expectedMarker.marker.lease.expiresAt <= now || nextMarker.marker.lease.expiresAt <= now) return false;
|
|
339
|
+
const expectedPayload = JSON.stringify(expected);
|
|
340
|
+
const payload = JSON.stringify(next);
|
|
341
|
+
const updatedAt = Date.now();
|
|
342
|
+
const result = await this.db.exec(
|
|
343
|
+
`UPDATE ${this.table}
|
|
344
|
+
SET context_id = ?, state = ?, payload = ?, updated_at = ?,
|
|
345
|
+
execution_request_id = ?, execution_lease_expires_at = ?
|
|
346
|
+
WHERE id = ?
|
|
347
|
+
AND payload = ?
|
|
348
|
+
AND state = 'working'
|
|
349
|
+
AND (
|
|
350
|
+
(execution_request_id = ? AND execution_lease_expires_at > ?)
|
|
351
|
+
OR (execution_request_id IS NULL AND execution_lease_expires_at IS NULL)
|
|
352
|
+
)`,
|
|
353
|
+
[
|
|
354
|
+
next.contextId,
|
|
355
|
+
next.status.state,
|
|
356
|
+
payload,
|
|
357
|
+
updatedAt,
|
|
358
|
+
nextMarker.marker.requestId,
|
|
359
|
+
nextMarker.marker.lease.expiresAt,
|
|
360
|
+
expected.id,
|
|
361
|
+
expectedPayload,
|
|
362
|
+
requestId,
|
|
363
|
+
now
|
|
364
|
+
]
|
|
365
|
+
);
|
|
366
|
+
return result.rowsAffected === 1;
|
|
367
|
+
}
|
|
114
368
|
async delete(id) {
|
|
115
|
-
await this.
|
|
369
|
+
const task = await this.get(id);
|
|
370
|
+
if (!task || hasPendingPaymentRecovery(task)) return;
|
|
371
|
+
await this.db.exec(
|
|
372
|
+
`DELETE FROM ${this.table} WHERE id = ? AND payload = ?`,
|
|
373
|
+
[id, JSON.stringify(task)]
|
|
374
|
+
);
|
|
116
375
|
}
|
|
117
376
|
/**
|
|
118
377
|
* Lookup tasks by contextId — used by `tasks/resubscribe` and the multi-turn
|
|
@@ -126,9 +385,15 @@ var SqlTaskStore = class {
|
|
|
126
385
|
[contextId]
|
|
127
386
|
);
|
|
128
387
|
const now = Date.now();
|
|
129
|
-
return rows.
|
|
388
|
+
return rows.map((r) => ({ task: JSON.parse(r.payload), updatedAt: r.updated_at })).filter(
|
|
389
|
+
({ task, updatedAt }) => now - updatedAt <= this.ttlMs || hasPendingPaymentRecovery(task)
|
|
390
|
+
).map(({ task }) => task);
|
|
130
391
|
}
|
|
131
392
|
};
|
|
393
|
+
function executionColumns(task) {
|
|
394
|
+
const inspection = inspectTaskExecution(task);
|
|
395
|
+
return inspection.state === "valid" ? [inspection.marker.requestId, inspection.marker.lease.expiresAt] : [null, null];
|
|
396
|
+
}
|
|
132
397
|
export {
|
|
133
398
|
A2A_ERROR_CODES,
|
|
134
399
|
CompositeObserver,
|
|
@@ -137,8 +402,15 @@ export {
|
|
|
137
402
|
InMemoryTaskStore,
|
|
138
403
|
KvNonceStore,
|
|
139
404
|
KvRateLimitStore,
|
|
405
|
+
MPP_CHARGE_PROTOCOL_VERSION,
|
|
140
406
|
MemoryNonceStore,
|
|
407
|
+
MemoryPaymentOperations,
|
|
408
|
+
MemoryPaymentRecoveryStore,
|
|
141
409
|
MemoryRateLimitStore,
|
|
410
|
+
PAYMENT_PROTOCOL_VERSION,
|
|
411
|
+
PAYMENT_RECOVERY_VERSION,
|
|
412
|
+
PaymentRecoveryFenceError,
|
|
413
|
+
SqlPaymentRecoveryStore,
|
|
142
414
|
SqlPushNotificationStore,
|
|
143
415
|
SqlTaskStore,
|
|
144
416
|
checkRateLimit,
|
|
@@ -147,16 +419,26 @@ export {
|
|
|
147
419
|
createPublishRoutes,
|
|
148
420
|
d1ToSqlAdapter,
|
|
149
421
|
defaultVerifyApiKey,
|
|
422
|
+
deliverDemoPushNotifications,
|
|
150
423
|
deliverPushNotifications,
|
|
151
424
|
detectInjection,
|
|
152
425
|
filterConsumerMessages,
|
|
153
426
|
filterConsumerMessagesStrict,
|
|
154
427
|
generateRequestId,
|
|
155
428
|
isApiKeyAuthEnabled,
|
|
429
|
+
isAtomicNonceStore,
|
|
156
430
|
isMppAuthEnabled,
|
|
431
|
+
mppPaymentCredential,
|
|
432
|
+
mppPaymentOperationId,
|
|
433
|
+
mppReplayNonceKey,
|
|
434
|
+
reclaimPayment,
|
|
435
|
+
recoverPayment,
|
|
436
|
+
recoverPayments,
|
|
157
437
|
redactSystemPromptFromOutput,
|
|
438
|
+
validatePushNotificationUrl,
|
|
158
439
|
verifyApiKeyFromStore,
|
|
159
440
|
verifyMpp,
|
|
441
|
+
verifyMppCredential,
|
|
160
442
|
verifyX402
|
|
161
443
|
};
|
|
162
444
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/a2a/task-store-sql.ts"],"sourcesContent":["/**\n * @stable\n *\n * Durable `TaskStore` against any SQL store. Adapter-agnostic: callers wire a\n * `SqlAdapter` against their driver (D1, postgres, sqlite, libSQL, Turso) and\n * the same store survives gateway restarts so an in-flight task (and its\n * artifacts) is recoverable after a Worker recycle.\n *\n * Schema is one table: tasks keyed by id with the full JSON payload, plus a\n * secondary index on `context_id` so `tasks/resubscribe` and conversational\n * lookups by context are O(log n). TTL is enforced at read time the same way\n * `InMemoryTaskStore` does — the gateway is single-writer per task id so a\n * stale row is invisible to callers regardless of when the row is physically\n * deleted.\n *\n * Why not bake in a specific driver? Hono workers run on Cloudflare (D1),\n * Node (pg / sqlite), Bun, Deno. Burning a hard dependency on one client\n * limits the gateway's reach. The adapter indirection costs ~5 lines per\n * driver in the consumer's code and keeps the package free of native deps.\n *\n * @example D1\n * import { SqlTaskStore, d1ToSqlAdapter } from '@tangle-network/agent-gateway'\n * const store = new SqlTaskStore(d1ToSqlAdapter(env.DB))\n * await store.migrate()\n * const gw = createAgentGateway({ ..., a2a: { taskStore: store } })\n *\n * @example libSQL / Turso\n * import { createClient } from '@libsql/client'\n * const client = createClient({ url: process.env.TURSO_URL!, authToken: process.env.TURSO_TOKEN! })\n * const libsql: SqlAdapter = {\n * exec: async (sql, params = []) => {\n * const r = await client.execute({ sql, args: params as never[] })\n * return { rowsAffected: Number(r.rowsAffected ?? 0) }\n * },\n * query: async (sql, params = []) => {\n * const r = await client.execute({ sql, args: params as never[] })\n * return r.rows as unknown as Record<string, unknown>[]\n * },\n * }\n * const store = new SqlTaskStore(libsql)\n * await store.migrate()\n */\n\nimport type { TaskStore } from './task-store'\nimport type { Task } from './types'\n\n/**\n * Minimal SQL driver shape — identical to agent-runtime's `SqlAdapter` so the\n * same wrapper code works for both packages. Parameter placeholders MUST be\n * `?` (positional); driver wrappers that use `$1`, `$2`, … should rewrite at\n * the adapter boundary (see node-postgres example in the durability docs).\n */\nexport interface SqlAdapter {\n exec(sql: string, params?: readonly unknown[]): Promise<{ rowsAffected: number }>\n query<TRow = Record<string, unknown>>(\n sql: string,\n params?: readonly unknown[],\n ): Promise<TRow[]>\n}\n\n/**\n * Adapt a Cloudflare D1 binding to `SqlAdapter`. The package never imports\n * `@cloudflare/workers-types`; the binding's structural shape lines up via\n * TypeScript structural compatibility.\n */\nexport function d1ToSqlAdapter(db: D1DatabaseLike): SqlAdapter {\n return {\n async exec(sql, params = []) {\n const stmt = db.prepare(sql)\n const bound = params.length > 0 ? stmt.bind(...params) : stmt\n const result = await bound.run()\n const meta = (result as { meta?: { rows_written?: number; changes?: number } }).meta\n return { rowsAffected: meta?.rows_written ?? meta?.changes ?? 0 }\n },\n async query<TRow>(sql: string, params: readonly unknown[] = []): Promise<TRow[]> {\n const stmt = db.prepare(sql)\n const bound = params.length > 0 ? stmt.bind(...params) : stmt\n const result = await bound.all<TRow>()\n return result.results ?? []\n },\n }\n}\n\nexport interface D1DatabaseLike {\n prepare(sql: string): D1StmtLike\n}\nexport interface D1StmtLike {\n bind(...params: unknown[]): D1StmtLike\n run(): Promise<unknown>\n all<TRow = unknown>(): Promise<{ results?: TRow[] }>\n}\n\nconst DEFAULT_TTL_MS = 60 * 60 * 1000\n\nconst TASKS_TABLE_DDL = (table: string) => `\n CREATE TABLE IF NOT EXISTS ${table} (\n id TEXT PRIMARY KEY,\n context_id TEXT NOT NULL,\n state TEXT NOT NULL,\n payload TEXT NOT NULL,\n updated_at INTEGER NOT NULL\n )\n`\nconst CTX_INDEX_DDL = (table: string) => `\n CREATE INDEX IF NOT EXISTS idx_${table}_context ON ${table} (context_id, updated_at)\n`\n\n/**\n * SQL-backed TaskStore. Stores the full Task JSON; reads return a deep clone\n * so callers never observe shared references. TTL is enforced at read time:\n * expired rows are filtered out and (best-effort) deleted, matching the\n * in-memory store's semantics so behavior is portable across both adapters.\n */\nexport class SqlTaskStore implements TaskStore {\n constructor(\n private readonly db: SqlAdapter,\n private readonly opts: { ttlMs?: number; table?: string } = {},\n ) {}\n\n private get ttlMs(): number {\n return this.opts.ttlMs ?? DEFAULT_TTL_MS\n }\n private get table(): string {\n return this.opts.table ?? 'a2a_tasks'\n }\n\n /** Idempotent. Call once at deploy. */\n async migrate(): Promise<void> {\n await this.db.exec(TASKS_TABLE_DDL(this.table))\n await this.db.exec(CTX_INDEX_DDL(this.table))\n }\n\n async get(id: string): Promise<Task | undefined> {\n const rows = await this.db.query<{ payload: string; updated_at: number }>(\n `SELECT payload, updated_at FROM ${this.table} WHERE id = ?`,\n [id],\n )\n const row = rows[0]\n if (!row) return undefined\n if (Date.now() - row.updated_at > this.ttlMs) {\n // Lazy GC. If the delete loses a race with another reader, that reader\n // observes either the stale-then-deleted task (returning undefined here)\n // or, after this delete commits, observes undefined directly — either\n // way callers see consistent \"expired\" semantics.\n void this.db.exec(`DELETE FROM ${this.table} WHERE id = ?`, [id])\n return undefined\n }\n return JSON.parse(row.payload) as Task\n }\n\n async put(task: Task): Promise<void> {\n const payload = JSON.stringify(task)\n const updatedAt = Date.now()\n // Adapter-agnostic upsert: try update, fall back to insert if no row\n // existed. Avoids needing ON CONFLICT (postgres) vs INSERT OR REPLACE\n // (sqlite/libSQL) divergence at the SQL layer.\n const updated = await this.db.exec(\n `UPDATE ${this.table} SET context_id = ?, state = ?, payload = ?, updated_at = ? WHERE id = ?`,\n [task.contextId, task.status.state, payload, updatedAt, task.id],\n )\n if (updated.rowsAffected === 0) {\n await this.db.exec(\n `INSERT INTO ${this.table} (id, context_id, state, payload, updated_at) VALUES (?, ?, ?, ?, ?)`,\n [task.id, task.contextId, task.status.state, payload, updatedAt],\n )\n }\n }\n\n async delete(id: string): Promise<void> {\n await this.db.exec(`DELETE FROM ${this.table} WHERE id = ?`, [id])\n }\n\n /**\n * Lookup tasks by contextId — used by `tasks/resubscribe` and the multi-turn\n * dispatcher. Returns most-recent-first. Not part of the base TaskStore\n * interface since the in-memory store doesn't expose it; consumers that\n * specifically wire SqlTaskStore can use it for richer queries.\n */\n async listByContext(contextId: string): Promise<Task[]> {\n const rows = await this.db.query<{ payload: string; updated_at: number }>(\n `SELECT payload, updated_at FROM ${this.table} WHERE context_id = ? ORDER BY updated_at DESC`,\n [contextId],\n )\n const now = Date.now()\n return rows\n .filter((r) => now - r.updated_at <= this.ttlMs)\n .map((r) => JSON.parse(r.payload) as Task)\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/payment-recovery-sql.ts","../src/a2a/task-store-sql.ts"],"sourcesContent":["import type { SqlAdapter } from './a2a/task-store-sql'\nimport type {\n PaymentRecoveryRecord,\n PaymentRecoveryStore,\n} from './payment-recovery'\n\nconst TABLE_DDL = (table: string) => `\n CREATE TABLE IF NOT EXISTS ${table} (\n id TEXT PRIMARY KEY,\n state TEXT NOT NULL,\n next_attempt_at INTEGER NOT NULL,\n revision INTEGER NOT NULL,\n payload TEXT NOT NULL,\n updated_at INTEGER NOT NULL\n )\n`\n\nconst DUE_INDEX_DDL = (table: string) => `\n CREATE INDEX IF NOT EXISTS idx_${table}_due\n ON ${table} (state, next_attempt_at)\n`\n\n/** Durable recovery outbox for D1, sqlite, libSQL, or an adapted SQL driver. */\nexport class SqlPaymentRecoveryStore implements PaymentRecoveryStore {\n private readonly table: string\n\n constructor(\n private readonly db: SqlAdapter,\n options: { table?: string } = {},\n ) {\n this.table = options.table ?? 'payment_recovery'\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(this.table)) {\n throw new Error('payment recovery table name is invalid')\n }\n }\n\n /** Idempotent. Run this before the gateway starts accepting traffic. */\n async migrate(): Promise<void> {\n await this.db.exec(TABLE_DDL(this.table))\n await this.db.exec(DUE_INDEX_DDL(this.table))\n }\n\n async createIfAbsent(record: PaymentRecoveryRecord): Promise<boolean> {\n try {\n const result = await this.db.exec(\n `INSERT INTO ${this.table} (id, state, next_attempt_at, revision, payload, updated_at) VALUES (?, ?, ?, ?, ?, ?)`,\n [\n record.id,\n record.state,\n record.nextAttemptAt,\n record.revision,\n JSON.stringify(record),\n record.updatedAt,\n ],\n )\n return result.rowsAffected === 1\n } catch (error) {\n if (await this.get(record.id)) return false\n throw error\n }\n }\n\n async get(id: string): Promise<PaymentRecoveryRecord | undefined> {\n const rows = await this.db.query<{ payload: string }>(\n `SELECT payload FROM ${this.table} WHERE id = ?`,\n [id],\n )\n return rows[0] ? parseRecord(rows[0].payload) : undefined\n }\n\n async compareAndSet(\n expected: PaymentRecoveryRecord,\n next: PaymentRecoveryRecord,\n ): Promise<boolean> {\n const result = await this.db.exec(\n `UPDATE ${this.table} SET state = ?, next_attempt_at = ?, revision = ?, payload = ?, updated_at = ? WHERE id = ? AND revision = ?`,\n [\n next.state,\n next.nextAttemptAt,\n next.revision,\n JSON.stringify(next),\n next.updatedAt,\n expected.id,\n expected.revision,\n ],\n )\n return result.rowsAffected === 1\n }\n\n async listDue(now: number, limit: number): Promise<PaymentRecoveryRecord[]> {\n if (!Number.isSafeInteger(limit) || limit <= 0) {\n throw new Error('payment recovery scan limit must be a positive safe integer')\n }\n const rows = await this.db.query<{ payload: string }>(\n `SELECT payload FROM ${this.table} WHERE state <> ? AND next_attempt_at <= ? ORDER BY next_attempt_at ASC LIMIT ?`,\n ['reconciled', now, limit],\n )\n return rows.map((row) => parseRecord(row.payload))\n }\n}\n\nfunction parseRecord(payload: string): PaymentRecoveryRecord {\n const value = JSON.parse(payload) as PaymentRecoveryRecord\n if (value.version !== 1 || !value.id || !Number.isSafeInteger(value.revision)) {\n throw new Error('stored payment recovery record is invalid')\n }\n return value\n}\n","/**\n * @stable\n *\n * Durable `TaskStore` against any SQL store. Adapter-agnostic: callers wire a\n * `SqlAdapter` against their driver (D1, postgres, sqlite, libSQL, Turso) and\n * the same store survives gateway restarts so an in-flight task (and its\n * artifacts) is recoverable after a Worker recycle.\n *\n * Schema is one table: tasks keyed by id with the full JSON payload, plus a\n * secondary index on `context_id` so `tasks/resubscribe` and conversational\n * lookups by context are O(log n). TTL is enforced at read time the same way\n * `InMemoryTaskStore` does — `createIfAbsent` and `compareAndSet` make task\n * ownership safe when multiple gateway workers share the database.\n *\n * Why not bake in a specific driver? Hono workers run on Cloudflare (D1),\n * Node (pg / sqlite), Bun, Deno. Burning a hard dependency on one client\n * limits the gateway's reach. The adapter indirection costs ~5 lines per\n * driver in the consumer's code and keeps the package free of native deps.\n *\n * @example D1\n * import { SqlTaskStore, d1ToSqlAdapter } from '@tangle-network/agent-gateway'\n * const store = new SqlTaskStore(d1ToSqlAdapter(env.DB))\n * await store.migrate()\n * const gw = createAgentGateway({ ..., a2a: { taskStore: store } })\n *\n * @example libSQL / Turso\n * import { createClient } from '@libsql/client'\n * const client = createClient({ url: process.env.TURSO_URL!, authToken: process.env.TURSO_TOKEN! })\n * const libsql: SqlAdapter = {\n * exec: async (sql, params = []) => {\n * const r = await client.execute({ sql, args: params as never[] })\n * return { rowsAffected: Number(r.rowsAffected ?? 0) }\n * },\n * query: async (sql, params = []) => {\n * const r = await client.execute({ sql, args: params as never[] })\n * return r.rows as unknown as Record<string, unknown>[]\n * },\n * }\n * const store = new SqlTaskStore(libsql)\n * await store.migrate()\n */\n\nimport { inspectTaskExecution } from './execution-fence'\nimport { hasPendingPaymentRecovery, type TaskStore } from './task-store'\nimport type { Task } from './types'\n\n/**\n * Minimal SQL driver shape — identical to agent-runtime's `SqlAdapter` so the\n * same wrapper code works for both packages. Parameter placeholders MUST be\n * `?` (positional); driver wrappers that use `$1`, `$2`, … should rewrite at\n * the adapter boundary (see node-postgres example in the durability docs).\n */\nexport interface SqlAdapter {\n exec(sql: string, params?: readonly unknown[]): Promise<{ rowsAffected: number }>\n query<TRow = Record<string, unknown>>(\n sql: string,\n params?: readonly unknown[],\n ): Promise<TRow[]>\n}\n\n/**\n * Adapt a Cloudflare D1 binding to `SqlAdapter`. The package never imports\n * `@cloudflare/workers-types`; the binding's structural shape lines up via\n * TypeScript structural compatibility.\n */\nexport function d1ToSqlAdapter(db: D1DatabaseLike): SqlAdapter {\n return {\n async exec(sql, params = []) {\n const stmt = db.prepare(sql)\n const bound = params.length > 0 ? stmt.bind(...params) : stmt\n const result = await bound.run()\n const meta = (result as { meta?: { rows_written?: number; changes?: number } }).meta\n return { rowsAffected: meta?.rows_written ?? meta?.changes ?? 0 }\n },\n async query<TRow>(sql: string, params: readonly unknown[] = []): Promise<TRow[]> {\n const stmt = db.prepare(sql)\n const bound = params.length > 0 ? stmt.bind(...params) : stmt\n const result = await bound.all<TRow>()\n return result.results ?? []\n },\n }\n}\n\nexport interface D1DatabaseLike {\n prepare(sql: string): D1StmtLike\n}\nexport interface D1StmtLike {\n bind(...params: unknown[]): D1StmtLike\n run(): Promise<unknown>\n all<TRow = unknown>(): Promise<{ results?: TRow[] }>\n}\n\nconst DEFAULT_TTL_MS = 60 * 60 * 1000\n\nconst TASKS_TABLE_DDL = (table: string) => `\n CREATE TABLE IF NOT EXISTS ${table} (\n id TEXT PRIMARY KEY,\n context_id TEXT NOT NULL,\n state TEXT NOT NULL,\n payload TEXT NOT NULL,\n updated_at INTEGER NOT NULL,\n execution_request_id TEXT,\n execution_lease_expires_at REAL\n )\n`\nconst CTX_INDEX_DDL = (table: string) => `\n CREATE INDEX IF NOT EXISTS idx_${table}_context ON ${table} (context_id, updated_at)\n`\n\n/**\n * SQL-backed TaskStore. Stores the full Task JSON; reads return a deep clone\n * so callers never observe shared references. TTL is enforced at read time:\n * expired rows are filtered out and (best-effort) deleted, matching the\n * in-memory store's semantics so behavior is portable across both adapters.\n */\nexport class SqlTaskStore implements TaskStore {\n constructor(\n private readonly db: SqlAdapter,\n private readonly opts: { ttlMs?: number; table?: string } = {},\n ) {}\n\n private get ttlMs(): number {\n return this.opts.ttlMs ?? DEFAULT_TTL_MS\n }\n private get table(): string {\n return this.opts.table ?? 'a2a_tasks'\n }\n\n private async readRow(id: string): Promise<{\n payload: string\n updatedAt: number\n executionRequestId: string | null\n executionLeaseExpiresAt: number | null\n } | undefined> {\n const rows = await this.db.query<{\n payload: string\n updated_at: number\n execution_request_id?: string | null\n execution_lease_expires_at?: number | null\n }>(\n `SELECT payload, updated_at, execution_request_id, execution_lease_expires_at FROM ${this.table} WHERE id = ?`,\n [id],\n )\n const row = rows[0]\n return row\n ? {\n payload: row.payload,\n updatedAt: row.updated_at,\n executionRequestId: row.execution_request_id ?? null,\n executionLeaseExpiresAt: row.execution_lease_expires_at ?? null,\n }\n : undefined\n }\n\n private isExpired(updatedAt: number, task: Task): boolean {\n return Date.now() - updatedAt > this.ttlMs && !hasPendingPaymentRecovery(task)\n }\n\n private async deleteObservedRow(\n id: string,\n payload: string,\n updatedAt: number,\n ): Promise<number> {\n const result = await this.db.exec(\n `DELETE FROM ${this.table} WHERE id = ? AND payload = ? AND updated_at = ?`,\n [id, payload, updatedAt],\n )\n return result.rowsAffected\n }\n\n /** Idempotent. Call once at deploy. */\n async migrate(): Promise<void> {\n await this.db.exec(TASKS_TABLE_DDL(this.table))\n for (const column of [\n 'execution_request_id TEXT',\n 'execution_lease_expires_at REAL',\n ]) {\n try {\n await this.db.exec(`ALTER TABLE ${this.table} ADD COLUMN ${column}`)\n } catch (error) {\n const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase()\n if (!message.includes('duplicate column') && !message.includes('already exists')) throw error\n }\n }\n await this.db.exec(CTX_INDEX_DDL(this.table))\n }\n\n async get(id: string): Promise<Task | undefined> {\n const row = await this.readRow(id)\n if (!row) return undefined\n const task = JSON.parse(row.payload) as Task\n if (this.isExpired(row.updatedAt, task)) {\n // Delete only the version that was observed as stale. A refresh can reuse\n // the same payload, so payload equality and updated_at both fence the row.\n await this.deleteObservedRow(id, row.payload, row.updatedAt)\n return undefined\n }\n return task\n }\n\n private async insert(task: Task): Promise<number> {\n const payload = JSON.stringify(task)\n const [executionRequestId, executionLeaseExpiresAt] = executionColumns(task)\n const result = await this.db.exec(\n `INSERT INTO ${this.table} (id, context_id, state, payload, updated_at, execution_request_id, execution_lease_expires_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n [\n task.id,\n task.contextId,\n task.status.state,\n payload,\n Date.now(),\n executionRequestId,\n executionLeaseExpiresAt,\n ],\n )\n return result.rowsAffected\n }\n\n async put(task: Task): Promise<void> {\n const payload = JSON.stringify(task)\n const updatedAt = Date.now()\n const [executionRequestId, executionLeaseExpiresAt] = executionColumns(task)\n // Adapter-agnostic upsert: try update, fall back to insert if no row\n // existed. Avoids needing ON CONFLICT (postgres) vs INSERT OR REPLACE\n // (sqlite/libSQL) divergence at the SQL layer.\n const updated = await this.db.exec(\n `UPDATE ${this.table}\n SET context_id = ?, state = ?, payload = ?, updated_at = ?,\n execution_request_id = ?, execution_lease_expires_at = ?\n WHERE id = ?`,\n [\n task.contextId,\n task.status.state,\n payload,\n updatedAt,\n executionRequestId,\n executionLeaseExpiresAt,\n task.id,\n ],\n )\n if (updated.rowsAffected === 0) {\n await this.db.exec(\n `INSERT INTO ${this.table} (id, context_id, state, payload, updated_at, execution_request_id, execution_lease_expires_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n [\n task.id,\n task.contextId,\n task.status.state,\n payload,\n updatedAt,\n executionRequestId,\n executionLeaseExpiresAt,\n ],\n )\n }\n }\n\n async createIfAbsent(task: Task): Promise<boolean> {\n try {\n return (await this.insert(task)) === 1\n } catch (error) {\n // SQL dialects report duplicate primary keys as errors. Inspect the raw\n // row so an expired row can be removed and retried in the same call.\n const row = await this.readRow(task.id)\n if (!row) throw error\n const existing = JSON.parse(row.payload) as Task\n if (!this.isExpired(row.updatedAt, existing)) return false\n\n await this.deleteObservedRow(task.id, row.payload, row.updatedAt)\n try {\n return (await this.insert(task)) === 1\n } catch (retryError) {\n // Another writer may have won the retry after the stale row was\n // removed. Return the normal idempotency result in that case.\n if (await this.get(task.id)) return false\n throw retryError\n }\n }\n }\n\n async compareAndSet(expected: Task, next: Task): Promise<boolean> {\n const expectedPayload = JSON.stringify(expected)\n const payload = JSON.stringify(next)\n const [executionRequestId, executionLeaseExpiresAt] = executionColumns(next)\n const result = await this.db.exec(\n `UPDATE ${this.table}\n SET context_id = ?, state = ?, payload = ?, updated_at = ?,\n execution_request_id = ?, execution_lease_expires_at = ?\n WHERE id = ? AND payload = ?`,\n [\n next.contextId,\n next.status.state,\n payload,\n Date.now(),\n executionRequestId,\n executionLeaseExpiresAt,\n expected.id,\n expectedPayload,\n ],\n )\n return result.rowsAffected === 1\n }\n\n async compareAndSetExecution(\n expected: Task,\n next: Task,\n requestId: string,\n now: number,\n ): Promise<boolean> {\n const expectedMarker = inspectTaskExecution(expected)\n const nextMarker = inspectTaskExecution(next)\n if (\n expectedMarker.state !== 'valid' ||\n nextMarker.state !== 'valid' ||\n expectedMarker.marker.requestId !== requestId ||\n nextMarker.marker.requestId !== requestId ||\n expectedMarker.marker.lease.expiresAt <= now ||\n nextMarker.marker.lease.expiresAt <= now\n ) return false\n const expectedPayload = JSON.stringify(expected)\n const payload = JSON.stringify(next)\n const updatedAt = Date.now()\n // Both NULL columns identify a legacy row whose payload still owns the fence.\n const result = await this.db.exec(\n `UPDATE ${this.table}\n SET context_id = ?, state = ?, payload = ?, updated_at = ?,\n execution_request_id = ?, execution_lease_expires_at = ?\n WHERE id = ?\n AND payload = ?\n AND state = 'working'\n AND (\n (execution_request_id = ? AND execution_lease_expires_at > ?)\n OR (execution_request_id IS NULL AND execution_lease_expires_at IS NULL)\n )`,\n [\n next.contextId,\n next.status.state,\n payload,\n updatedAt,\n nextMarker.marker.requestId,\n nextMarker.marker.lease.expiresAt,\n expected.id,\n expectedPayload,\n requestId,\n now,\n ],\n )\n return result.rowsAffected === 1\n }\n\n async delete(id: string): Promise<void> {\n const task = await this.get(id)\n if (!task || hasPendingPaymentRecovery(task)) return\n await this.db.exec(\n `DELETE FROM ${this.table} WHERE id = ? AND payload = ?`,\n [id, JSON.stringify(task)],\n )\n }\n\n /**\n * Lookup tasks by contextId — used by `tasks/resubscribe` and the multi-turn\n * dispatcher. Returns most-recent-first. Not part of the base TaskStore\n * interface since the in-memory store doesn't expose it; consumers that\n * specifically wire SqlTaskStore can use it for richer queries.\n */\n async listByContext(contextId: string): Promise<Task[]> {\n const rows = await this.db.query<{ payload: string; updated_at: number }>(\n `SELECT payload, updated_at FROM ${this.table} WHERE context_id = ? ORDER BY updated_at DESC`,\n [contextId],\n )\n const now = Date.now()\n return rows\n .map((r) => ({ task: JSON.parse(r.payload) as Task, updatedAt: r.updated_at }))\n .filter(({ task, updatedAt }) =>\n now - updatedAt <= this.ttlMs || hasPendingPaymentRecovery(task),\n )\n .map(({ task }) => task)\n }\n}\n\nfunction executionColumns(task: Task): [string | null, number | null] {\n const inspection = inspectTaskExecution(task)\n return inspection.state === 'valid'\n ? [inspection.marker.requestId, inspection.marker.lease.expiresAt]\n : [null, null]\n}\n"],"mappings":"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","names":[]}
|
package/dist/middleware.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as hono_types from 'hono/types';
|
|
2
2
|
import { Hono } from 'hono';
|
|
3
|
-
import { G as GatewayConfig } from './types-
|
|
3
|
+
import { G as GatewayConfig } from './types-BHISsm7D.js';
|
|
4
4
|
import './nonce-store.js';
|
|
5
5
|
import './rate-limit.js';
|
|
6
|
+
import './observer-types-A0RtA8uL.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Create a Hono router that serves the agent gateway.
|
|
@@ -14,6 +15,10 @@ import './rate-limit.js';
|
|
|
14
15
|
* GET /:slug/chat/completions — agent discovery metadata (Tangle-native shape)
|
|
15
16
|
* POST /:slug/chat/completions — OpenAI-compatible chat endpoint (paid)
|
|
16
17
|
*/
|
|
17
|
-
declare function createAgentGateway(
|
|
18
|
+
declare function createAgentGateway(inputConfig: GatewayConfig): Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
|
|
19
|
+
/** Public package-boundary version marker for durable payment operations. */
|
|
20
|
+
declare namespace createAgentGateway {
|
|
21
|
+
const paymentProtocolVersion: 2;
|
|
22
|
+
}
|
|
18
23
|
|
|
19
24
|
export { createAgentGateway };
|
package/dist/middleware.js
CHANGED