@tangle-network/agent-gateway 0.2.0 → 0.3.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/dist/{chunk-EMGS63QE.js → chunk-FI5HMLGY.js} +141 -20
- package/dist/chunk-FI5HMLGY.js.map +1 -0
- package/dist/{chunk-Z22ALGHW.js → chunk-M7ZJAK4K.js} +22 -2
- package/dist/chunk-M7ZJAK4K.js.map +1 -0
- package/dist/{chunk-4FULF5LW.js → chunk-XCTXHZ76.js} +27 -1
- package/dist/chunk-XCTXHZ76.js.map +1 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +13 -3
- package/dist/middleware.d.ts +1 -1
- package/dist/middleware.js +3 -3
- package/dist/nonce-store.d.ts +44 -2
- package/dist/nonce-store.js +3 -1
- package/dist/rate-limit.d.ts +35 -3
- package/dist/rate-limit.js +3 -1
- package/dist/types-14xV8J4G.d.ts +292 -0
- package/dist/types.d.ts +3 -157
- package/package.json +5 -1
- package/src/filter.ts +25 -6
- package/src/index.ts +10 -0
- package/src/middleware.ts +50 -17
- package/src/nonce-store.ts +60 -1
- package/src/observer.ts +181 -0
- package/src/rate-limit.ts +63 -2
- package/src/types.ts +8 -0
- package/dist/chunk-4FULF5LW.js.map +0 -1
- package/dist/chunk-EMGS63QE.js.map +0 -1
- package/dist/chunk-Z22ALGHW.js.map +0 -1
package/dist/nonce-store.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ interface NonceStore {
|
|
|
8
8
|
/** Mark nonce as used. TTL = how long to remember it (seconds). */
|
|
9
9
|
markSeen(nonce: string, ttlSeconds: number): Promise<void>;
|
|
10
10
|
}
|
|
11
|
-
/** In-memory nonce store with automatic eviction */
|
|
11
|
+
/** In-memory nonce store with automatic eviction. Use in tests or single-worker deploys. */
|
|
12
12
|
declare class MemoryNonceStore implements NonceStore {
|
|
13
13
|
private seen;
|
|
14
14
|
private lastEviction;
|
|
@@ -16,5 +16,47 @@ declare class MemoryNonceStore implements NonceStore {
|
|
|
16
16
|
markSeen(nonce: string, ttlSeconds: number): Promise<void>;
|
|
17
17
|
private evictExpired;
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Minimal KVNamespace shape — matches Cloudflare Workers' @cloudflare/workers-types
|
|
21
|
+
* without pulling that package as a dep. Production consumers cast their KV
|
|
22
|
+
* binding to this interface at the construction site.
|
|
23
|
+
*/
|
|
24
|
+
interface KVNamespace {
|
|
25
|
+
get(key: string, options?: {
|
|
26
|
+
type?: 'text' | 'json';
|
|
27
|
+
}): Promise<string | null>;
|
|
28
|
+
put(key: string, value: string, options?: {
|
|
29
|
+
expirationTtl?: number;
|
|
30
|
+
}): Promise<void>;
|
|
31
|
+
delete(key: string): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* KV-backed NonceStore for distributed Cloudflare Workers deployments.
|
|
35
|
+
*
|
|
36
|
+
* Why this exists: MemoryNonceStore works on a single worker instance, but
|
|
37
|
+
* Cloudflare routes requests across multiple isolates. Without shared state,
|
|
38
|
+
* an attacker could retry a replayed nonce against a different isolate and
|
|
39
|
+
* have it accepted. This implementation uses Workers KV with native TTL so
|
|
40
|
+
* the nonce automatically expires at payment-expiry time.
|
|
41
|
+
*
|
|
42
|
+
* TTL precision: KV is eventually consistent (propagation ~60s). For x402
|
|
43
|
+
* with 10-minute expiry windows this is fine — by the time KV propagates,
|
|
44
|
+
* the payment itself would be expired anyway.
|
|
45
|
+
*
|
|
46
|
+
* Usage:
|
|
47
|
+
* const nonceStore = new KvNonceStore(env.NONCE_KV, 'x402')
|
|
48
|
+
* createAgentGateway({ ...config, nonceStore })
|
|
49
|
+
*/
|
|
50
|
+
declare class KvNonceStore implements NonceStore {
|
|
51
|
+
private readonly kv;
|
|
52
|
+
/** Key prefix to namespace within a shared KV (default: "nonce"). */
|
|
53
|
+
private readonly prefix;
|
|
54
|
+
constructor(kv: KVNamespace,
|
|
55
|
+
/** Key prefix to namespace within a shared KV (default: "nonce"). */
|
|
56
|
+
prefix?: string);
|
|
57
|
+
hasSeen(nonce: string): Promise<boolean>;
|
|
58
|
+
markSeen(nonce: string, ttlSeconds: number): Promise<void>;
|
|
59
|
+
private key;
|
|
60
|
+
}
|
|
19
61
|
|
|
20
|
-
export { MemoryNonceStore, type NonceStore };
|
|
62
|
+
export { type KVNamespace, KvNonceStore, MemoryNonceStore, type NonceStore };
|
package/dist/nonce-store.js
CHANGED
package/dist/rate-limit.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Sliding
|
|
3
|
-
*
|
|
2
|
+
* Sliding-window rate limiter.
|
|
3
|
+
*
|
|
4
|
+
* Two implementations:
|
|
5
|
+
* - MemoryRateLimitStore — single-worker, ephemeral, good for tests
|
|
6
|
+
* - KvRateLimitStore — Cloudflare Workers KV, distributed
|
|
4
7
|
*/
|
|
5
8
|
interface RateLimitConfig {
|
|
6
9
|
/** Max requests per window (default: 60) */
|
|
@@ -28,6 +31,35 @@ declare class MemoryRateLimitStore implements RateLimitStore {
|
|
|
28
31
|
set(key: string, timestamps: number[], ttlSeconds: number): Promise<void>;
|
|
29
32
|
private evictExpired;
|
|
30
33
|
}
|
|
34
|
+
/** Minimal KV shape — see nonce-store.ts for rationale. */
|
|
35
|
+
interface KVNamespace {
|
|
36
|
+
get(key: string, options?: {
|
|
37
|
+
type?: 'text' | 'json';
|
|
38
|
+
}): Promise<string | null>;
|
|
39
|
+
put(key: string, value: string, options?: {
|
|
40
|
+
expirationTtl?: number;
|
|
41
|
+
}): Promise<void>;
|
|
42
|
+
delete(key: string): Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* KV-backed RateLimitStore for distributed Cloudflare Workers deployments.
|
|
46
|
+
*
|
|
47
|
+
* Stores timestamp arrays per consumer. Reads are O(1); writes replace the
|
|
48
|
+
* full array (cap is already filtered by checkRateLimit before write).
|
|
49
|
+
*
|
|
50
|
+
* Consistency note: Workers KV is eventually consistent within ~60s. An
|
|
51
|
+
* attacker sitting on two isolates could technically exceed the limit by
|
|
52
|
+
* ~2x for that window. For payment rate limits this is acceptable; for
|
|
53
|
+
* abuse prevention on free endpoints consider Durable Objects instead.
|
|
54
|
+
*/
|
|
55
|
+
declare class KvRateLimitStore implements RateLimitStore {
|
|
56
|
+
private readonly kv;
|
|
57
|
+
private readonly prefix;
|
|
58
|
+
constructor(kv: KVNamespace, prefix?: string);
|
|
59
|
+
get(key: string): Promise<number[]>;
|
|
60
|
+
set(key: string, timestamps: number[], ttlSeconds: number): Promise<void>;
|
|
61
|
+
private key;
|
|
62
|
+
}
|
|
31
63
|
declare function checkRateLimit(consumerId: string, config: RateLimitConfig, store: RateLimitStore): Promise<RateLimitResult>;
|
|
32
64
|
|
|
33
|
-
export { MemoryRateLimitStore, type RateLimitConfig, type RateLimitResult, type RateLimitStore, checkRateLimit };
|
|
65
|
+
export { type KVNamespace, KvRateLimitStore, MemoryRateLimitStore, type RateLimitConfig, type RateLimitResult, type RateLimitStore, checkRateLimit };
|
package/dist/rate-limit.js
CHANGED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { NonceStore } from './nonce-store.js';
|
|
2
|
+
import { RateLimitStore } from './rate-limit.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Observability hook surface.
|
|
6
|
+
*
|
|
7
|
+
* Consumers implement GatewayObserver to wire the gateway into their existing
|
|
8
|
+
* telemetry stack (Langfuse, OTEL, structured logs, Prometheus, etc.) without
|
|
9
|
+
* the gateway itself depending on any of those libraries.
|
|
10
|
+
*
|
|
11
|
+
* Every event carries a requestId so downstream metrics can correlate the
|
|
12
|
+
* payment verification, sandbox execution, and settlement for one request.
|
|
13
|
+
* When no observer is configured, the gateway stays silent.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
interface RequestContext {
|
|
17
|
+
requestId: string;
|
|
18
|
+
agentSlug: string;
|
|
19
|
+
startMs: number;
|
|
20
|
+
}
|
|
21
|
+
interface AuthFailureReason {
|
|
22
|
+
method: 'x402' | 'mpp' | 'apikey' | 'none';
|
|
23
|
+
code: string;
|
|
24
|
+
httpStatus: number;
|
|
25
|
+
}
|
|
26
|
+
interface GatewayObserver {
|
|
27
|
+
/** Called at the start of every chat completions POST. */
|
|
28
|
+
onRequestStart?: (ctx: RequestContext) => void | Promise<void>;
|
|
29
|
+
/** Called when a payment method has been successfully verified. */
|
|
30
|
+
onPaymentVerified?: (ctx: RequestContext, info: {
|
|
31
|
+
method: PaymentMethod;
|
|
32
|
+
consumerId: string;
|
|
33
|
+
keyId?: string;
|
|
34
|
+
}) => void | Promise<void>;
|
|
35
|
+
/** Called when auth fails — every branch. */
|
|
36
|
+
onAuthFailure?: (ctx: RequestContext, reason: AuthFailureReason) => void | Promise<void>;
|
|
37
|
+
/** Called when a consumer hits the rate limit. */
|
|
38
|
+
onRateLimited?: (ctx: RequestContext, info: {
|
|
39
|
+
consumerId: string;
|
|
40
|
+
retryAfterSeconds: number;
|
|
41
|
+
}) => void | Promise<void>;
|
|
42
|
+
/** Called when the request body exceeds the 64KB limit. */
|
|
43
|
+
onBodyTooLarge?: (ctx: RequestContext, contentLength: number) => void | Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Called when prompt-injection patterns are detected.
|
|
46
|
+
* `blocked` is true when blockInjection config is on and the request was
|
|
47
|
+
* rejected; false when the patterns were logged but the request proceeded.
|
|
48
|
+
*/
|
|
49
|
+
onInjectionDetected?: (ctx: RequestContext, info: {
|
|
50
|
+
consumerId: string;
|
|
51
|
+
patterns: string[];
|
|
52
|
+
blocked: boolean;
|
|
53
|
+
}) => void | Promise<void>;
|
|
54
|
+
/** Called after a successful stream completes and recordUsage has fired. */
|
|
55
|
+
onRequestComplete?: (ctx: RequestContext, usage: GatewayUsageEvent) => void | Promise<void>;
|
|
56
|
+
/** Called when the sandbox throws. The error message is pre-scrubbed. */
|
|
57
|
+
onStreamError?: (ctx: RequestContext, info: {
|
|
58
|
+
consumerId: string;
|
|
59
|
+
errorMessage: string;
|
|
60
|
+
}) => void | Promise<void>;
|
|
61
|
+
/** Called when settlement fails. Payment already occurred; this is async bookkeeping. */
|
|
62
|
+
onSettlementError?: (ctx: RequestContext, info: {
|
|
63
|
+
consumerId: string;
|
|
64
|
+
method: PaymentMethod;
|
|
65
|
+
errorMessage: string;
|
|
66
|
+
}) => void | Promise<void>;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Structured-log observer. Emits one JSON line per event on the `log` function.
|
|
70
|
+
* Default sink: console.log. Production consumers usually pipe their own
|
|
71
|
+
* structured logger (pino, winston, the cf Logs binding).
|
|
72
|
+
*
|
|
73
|
+
* Usage:
|
|
74
|
+
* new ConsoleObserver(({ level, event, ...rest }) => logger.info({ event, ...rest }))
|
|
75
|
+
*/
|
|
76
|
+
declare class ConsoleObserver implements GatewayObserver {
|
|
77
|
+
private readonly log;
|
|
78
|
+
constructor(log?: (entry: Record<string, unknown>) => void);
|
|
79
|
+
private emit;
|
|
80
|
+
onRequestStart(ctx: RequestContext): void;
|
|
81
|
+
onPaymentVerified(ctx: RequestContext, info: {
|
|
82
|
+
method: PaymentMethod;
|
|
83
|
+
consumerId: string;
|
|
84
|
+
keyId?: string;
|
|
85
|
+
}): void;
|
|
86
|
+
onAuthFailure(ctx: RequestContext, reason: AuthFailureReason): void;
|
|
87
|
+
onRateLimited(ctx: RequestContext, info: {
|
|
88
|
+
consumerId: string;
|
|
89
|
+
retryAfterSeconds: number;
|
|
90
|
+
}): void;
|
|
91
|
+
onBodyTooLarge(ctx: RequestContext, contentLength: number): void;
|
|
92
|
+
onInjectionDetected(ctx: RequestContext, info: {
|
|
93
|
+
consumerId: string;
|
|
94
|
+
patterns: string[];
|
|
95
|
+
blocked: boolean;
|
|
96
|
+
}): void;
|
|
97
|
+
onRequestComplete(ctx: RequestContext, usage: GatewayUsageEvent): void;
|
|
98
|
+
onStreamError(ctx: RequestContext, info: {
|
|
99
|
+
consumerId: string;
|
|
100
|
+
errorMessage: string;
|
|
101
|
+
}): void;
|
|
102
|
+
onSettlementError(ctx: RequestContext, info: {
|
|
103
|
+
consumerId: string;
|
|
104
|
+
method: PaymentMethod;
|
|
105
|
+
errorMessage: string;
|
|
106
|
+
}): void;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Compose multiple observers into one. Errors in any individual observer
|
|
110
|
+
* don't break the others (fire-and-forget telemetry).
|
|
111
|
+
*/
|
|
112
|
+
declare class CompositeObserver implements GatewayObserver {
|
|
113
|
+
private readonly observers;
|
|
114
|
+
constructor(observers: GatewayObserver[]);
|
|
115
|
+
private fanOut;
|
|
116
|
+
onRequestStart: (ctx: RequestContext) => Promise<void>;
|
|
117
|
+
onPaymentVerified: (ctx: RequestContext, info: Parameters<Required<GatewayObserver>["onPaymentVerified"]>[1]) => Promise<void>;
|
|
118
|
+
onAuthFailure: (ctx: RequestContext, reason: AuthFailureReason) => Promise<void>;
|
|
119
|
+
onRateLimited: (ctx: RequestContext, info: Parameters<Required<GatewayObserver>["onRateLimited"]>[1]) => Promise<void>;
|
|
120
|
+
onBodyTooLarge: (ctx: RequestContext, contentLength: number) => Promise<void>;
|
|
121
|
+
onInjectionDetected: (ctx: RequestContext, info: Parameters<Required<GatewayObserver>["onInjectionDetected"]>[1]) => Promise<void>;
|
|
122
|
+
onRequestComplete: (ctx: RequestContext, usage: GatewayUsageEvent) => Promise<void>;
|
|
123
|
+
onStreamError: (ctx: RequestContext, info: Parameters<Required<GatewayObserver>["onStreamError"]>[1]) => Promise<void>;
|
|
124
|
+
onSettlementError: (ctx: RequestContext, info: Parameters<Required<GatewayObserver>["onSettlementError"]>[1]) => Promise<void>;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Generate a request-id. Crypto-random 16 bytes, hex-encoded with an `req_` prefix.
|
|
128
|
+
* Works in Workers, Node, and browsers — all have globalThis.crypto.
|
|
129
|
+
*/
|
|
130
|
+
declare function generateRequestId(): string;
|
|
131
|
+
|
|
132
|
+
interface AgentMeta {
|
|
133
|
+
/** Unique agent identifier (workspace ID, session ID, etc.) */
|
|
134
|
+
id: string;
|
|
135
|
+
/** Owner/creator user ID */
|
|
136
|
+
ownerId: string;
|
|
137
|
+
/** Public URL slug */
|
|
138
|
+
slug: string;
|
|
139
|
+
/** System prompt for the agent (injected before consumer messages) */
|
|
140
|
+
systemPrompt?: string;
|
|
141
|
+
/** Per-token price in USD (default: 0.00002) */
|
|
142
|
+
pricePerTokenUsd: number;
|
|
143
|
+
/** Platform fee as decimal 0-1 (default: 0.20 = 20%) */
|
|
144
|
+
platformFeePercent: number;
|
|
145
|
+
/** Remote operator endpoint for sovereignty mode (null = centralized) */
|
|
146
|
+
sandboxEndpoint: string | null;
|
|
147
|
+
/** Sandbox ID on remote operator */
|
|
148
|
+
remoteSandboxId: string | null;
|
|
149
|
+
/** PASETO bearer token for remote operator auth */
|
|
150
|
+
remoteBearerToken: string | null;
|
|
151
|
+
/** Whether agent is published and accepting requests */
|
|
152
|
+
enabled: boolean;
|
|
153
|
+
}
|
|
154
|
+
type PaymentMethod = 'x402' | 'mpp' | 'apikey' | 'none';
|
|
155
|
+
interface X402Config {
|
|
156
|
+
/** Ethereum operator address for SpendAuth verification */
|
|
157
|
+
operatorAddress: string;
|
|
158
|
+
/** Blockchain network ID (default: 3799) */
|
|
159
|
+
chainId: number;
|
|
160
|
+
/** ShieldedCredits contract address */
|
|
161
|
+
creditsAddress?: string;
|
|
162
|
+
/** RPC URL for on-chain verification (optional, demo mode skips this) */
|
|
163
|
+
rpcUrl?: string;
|
|
164
|
+
/** Demo mode: skip signature verification (default: false). NEVER enable in production. */
|
|
165
|
+
demoMode?: boolean;
|
|
166
|
+
/** Production signer verification. Called with the raw SpendAuth payload. Return true if signature is valid. */
|
|
167
|
+
verifySigner?: (payload: Record<string, unknown>) => Promise<boolean>;
|
|
168
|
+
}
|
|
169
|
+
interface MppConfig {
|
|
170
|
+
/** MPP realm (e.g. "agents.tangle.tools") */
|
|
171
|
+
realm: string;
|
|
172
|
+
/** MPP method name (default: "blueprintevm") */
|
|
173
|
+
method?: string;
|
|
174
|
+
}
|
|
175
|
+
interface PaymentResult {
|
|
176
|
+
method: PaymentMethod;
|
|
177
|
+
consumerId: string;
|
|
178
|
+
}
|
|
179
|
+
interface ApiKeyInfo {
|
|
180
|
+
keyId: string;
|
|
181
|
+
consumerId: string;
|
|
182
|
+
/** Scopes this key is authorized for (e.g. ["chat", "forms"]) */
|
|
183
|
+
scopes?: string[];
|
|
184
|
+
/** Per-key rate limit override (requests per minute). If set, overrides global rate limit. */
|
|
185
|
+
rateLimitPerMinute?: number;
|
|
186
|
+
/** Per-key daily limit override. */
|
|
187
|
+
dailyLimit?: number;
|
|
188
|
+
}
|
|
189
|
+
interface GatewayUsageEvent {
|
|
190
|
+
agentId: string;
|
|
191
|
+
agentSlug: string;
|
|
192
|
+
consumerId: string;
|
|
193
|
+
paymentMethod: PaymentMethod;
|
|
194
|
+
inputTokens: number;
|
|
195
|
+
outputTokens: number;
|
|
196
|
+
totalCostUsd: number;
|
|
197
|
+
ownerEarnedUsd: number;
|
|
198
|
+
platformFeeUsd: number;
|
|
199
|
+
durationMs: number;
|
|
200
|
+
}
|
|
201
|
+
interface SandboxStreamEvent {
|
|
202
|
+
type?: string;
|
|
203
|
+
data?: {
|
|
204
|
+
part?: {
|
|
205
|
+
type?: string;
|
|
206
|
+
text?: string;
|
|
207
|
+
};
|
|
208
|
+
delta?: string;
|
|
209
|
+
finalText?: string;
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
interface SandboxBox {
|
|
213
|
+
streamPrompt(message: string, opts?: {
|
|
214
|
+
sessionId?: string;
|
|
215
|
+
systemPrompt?: string;
|
|
216
|
+
}): AsyncIterable<SandboxStreamEvent>;
|
|
217
|
+
}
|
|
218
|
+
interface GatewayConfig {
|
|
219
|
+
/** Resolve agent metadata by slug. Return null if not found or not published. */
|
|
220
|
+
resolveAgent: (slug: string) => Promise<AgentMeta | null>;
|
|
221
|
+
/** Get a sandbox instance for the agent. Called after payment is verified. */
|
|
222
|
+
getSandbox: (agent: AgentMeta) => Promise<SandboxBox>;
|
|
223
|
+
/** Record a usage event after request completes. */
|
|
224
|
+
recordUsage: (event: GatewayUsageEvent) => Promise<void>;
|
|
225
|
+
/** x402 payment configuration */
|
|
226
|
+
x402: X402Config;
|
|
227
|
+
/** MPP (Machine Payments Protocol) configuration. If provided, gateway accepts Authorization: Payment headers. */
|
|
228
|
+
mpp?: MppConfig;
|
|
229
|
+
/**
|
|
230
|
+
* Verify an API key. Return key info if valid, null if invalid.
|
|
231
|
+
* Default: accepts any `sk_agent_*` key (demo mode).
|
|
232
|
+
*/
|
|
233
|
+
verifyApiKey?: (authHeader: string) => Promise<ApiKeyInfo | null>;
|
|
234
|
+
/**
|
|
235
|
+
* Settle payment after successful response.
|
|
236
|
+
* For x402: call ShieldedCredits.claimPayment()
|
|
237
|
+
* For API key: deduct from spending limit
|
|
238
|
+
* Default: no-op (demo mode).
|
|
239
|
+
*/
|
|
240
|
+
settlePayment?: (payment: PaymentResult, cost: number) => Promise<void>;
|
|
241
|
+
/** Base URL for API key purchase links (e.g. "https://film.tangle.tools") */
|
|
242
|
+
baseUrl?: string;
|
|
243
|
+
/** Max message length in chars (default: 8000) */
|
|
244
|
+
maxMessageLength?: number;
|
|
245
|
+
/** Required scope for chat endpoint (default: "chat"). API keys must include this scope. */
|
|
246
|
+
requiredScope?: string;
|
|
247
|
+
/** Block requests with detected injection patterns (default: false — log only) */
|
|
248
|
+
blockInjection?: boolean;
|
|
249
|
+
/** Rate limiting config. Default: 60 requests per 60 seconds per consumer. */
|
|
250
|
+
rateLimit?: {
|
|
251
|
+
limit: number;
|
|
252
|
+
windowSeconds: number;
|
|
253
|
+
};
|
|
254
|
+
/** Custom rate limit store (default: in-memory). Use KV-backed for Workers. */
|
|
255
|
+
rateLimitStore?: RateLimitStore;
|
|
256
|
+
/** Nonce replay protection store (default: in-memory). Rejects reused x402 nonces. */
|
|
257
|
+
nonceStore?: NonceStore;
|
|
258
|
+
/**
|
|
259
|
+
* Observability hook. When set, the gateway emits typed events for request
|
|
260
|
+
* lifecycle, auth outcomes, rate limits, injection detection, usage, errors,
|
|
261
|
+
* and settlement failures. See ./observer.ts for the interface and
|
|
262
|
+
* ConsoleObserver / CompositeObserver implementations.
|
|
263
|
+
*/
|
|
264
|
+
observer?: GatewayObserver;
|
|
265
|
+
}
|
|
266
|
+
interface ChatMessage {
|
|
267
|
+
role: 'system' | 'user' | 'assistant' | 'tool';
|
|
268
|
+
content: string;
|
|
269
|
+
}
|
|
270
|
+
interface ChatCompletionRequest {
|
|
271
|
+
model?: string;
|
|
272
|
+
messages: ChatMessage[];
|
|
273
|
+
stream?: boolean;
|
|
274
|
+
temperature?: number;
|
|
275
|
+
max_tokens?: number;
|
|
276
|
+
}
|
|
277
|
+
interface ChatCompletionChunk {
|
|
278
|
+
id: string;
|
|
279
|
+
object: 'chat.completion.chunk';
|
|
280
|
+
created: number;
|
|
281
|
+
model: string;
|
|
282
|
+
choices: Array<{
|
|
283
|
+
index: number;
|
|
284
|
+
delta: {
|
|
285
|
+
content?: string;
|
|
286
|
+
role?: string;
|
|
287
|
+
};
|
|
288
|
+
finish_reason: string | null;
|
|
289
|
+
}>;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export { type ApiKeyInfo as A, type ChatMessage as C, type GatewayConfig as G, type MppConfig as M, type PaymentMethod as P, type RequestContext as R, type SandboxBox as S, type X402Config as X, type AgentMeta as a, type AuthFailureReason as b, type ChatCompletionChunk as c, type ChatCompletionRequest as d, CompositeObserver as e, ConsoleObserver as f, type GatewayObserver as g, type GatewayUsageEvent as h, type PaymentResult as i, type SandboxStreamEvent as j, generateRequestId as k };
|
package/dist/types.d.ts
CHANGED
|
@@ -1,157 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
interface AgentMeta {
|
|
5
|
-
/** Unique agent identifier (workspace ID, session ID, etc.) */
|
|
6
|
-
id: string;
|
|
7
|
-
/** Owner/creator user ID */
|
|
8
|
-
ownerId: string;
|
|
9
|
-
/** Public URL slug */
|
|
10
|
-
slug: string;
|
|
11
|
-
/** System prompt for the agent (injected before consumer messages) */
|
|
12
|
-
systemPrompt?: string;
|
|
13
|
-
/** Per-token price in USD (default: 0.00002) */
|
|
14
|
-
pricePerTokenUsd: number;
|
|
15
|
-
/** Platform fee as decimal 0-1 (default: 0.20 = 20%) */
|
|
16
|
-
platformFeePercent: number;
|
|
17
|
-
/** Remote operator endpoint for sovereignty mode (null = centralized) */
|
|
18
|
-
sandboxEndpoint: string | null;
|
|
19
|
-
/** Sandbox ID on remote operator */
|
|
20
|
-
remoteSandboxId: string | null;
|
|
21
|
-
/** PASETO bearer token for remote operator auth */
|
|
22
|
-
remoteBearerToken: string | null;
|
|
23
|
-
/** Whether agent is published and accepting requests */
|
|
24
|
-
enabled: boolean;
|
|
25
|
-
}
|
|
26
|
-
type PaymentMethod = 'x402' | 'mpp' | 'apikey' | 'none';
|
|
27
|
-
interface X402Config {
|
|
28
|
-
/** Ethereum operator address for SpendAuth verification */
|
|
29
|
-
operatorAddress: string;
|
|
30
|
-
/** Blockchain network ID (default: 3799) */
|
|
31
|
-
chainId: number;
|
|
32
|
-
/** ShieldedCredits contract address */
|
|
33
|
-
creditsAddress?: string;
|
|
34
|
-
/** RPC URL for on-chain verification (optional, demo mode skips this) */
|
|
35
|
-
rpcUrl?: string;
|
|
36
|
-
/** Demo mode: skip signature verification (default: false). NEVER enable in production. */
|
|
37
|
-
demoMode?: boolean;
|
|
38
|
-
/** Production signer verification. Called with the raw SpendAuth payload. Return true if signature is valid. */
|
|
39
|
-
verifySigner?: (payload: Record<string, unknown>) => Promise<boolean>;
|
|
40
|
-
}
|
|
41
|
-
interface MppConfig {
|
|
42
|
-
/** MPP realm (e.g. "agents.tangle.tools") */
|
|
43
|
-
realm: string;
|
|
44
|
-
/** MPP method name (default: "blueprintevm") */
|
|
45
|
-
method?: string;
|
|
46
|
-
}
|
|
47
|
-
interface PaymentResult {
|
|
48
|
-
method: PaymentMethod;
|
|
49
|
-
consumerId: string;
|
|
50
|
-
}
|
|
51
|
-
interface ApiKeyInfo {
|
|
52
|
-
keyId: string;
|
|
53
|
-
consumerId: string;
|
|
54
|
-
/** Scopes this key is authorized for (e.g. ["chat", "forms"]) */
|
|
55
|
-
scopes?: string[];
|
|
56
|
-
/** Per-key rate limit override (requests per minute). If set, overrides global rate limit. */
|
|
57
|
-
rateLimitPerMinute?: number;
|
|
58
|
-
/** Per-key daily limit override. */
|
|
59
|
-
dailyLimit?: number;
|
|
60
|
-
}
|
|
61
|
-
interface GatewayUsageEvent {
|
|
62
|
-
agentId: string;
|
|
63
|
-
agentSlug: string;
|
|
64
|
-
consumerId: string;
|
|
65
|
-
paymentMethod: PaymentMethod;
|
|
66
|
-
inputTokens: number;
|
|
67
|
-
outputTokens: number;
|
|
68
|
-
totalCostUsd: number;
|
|
69
|
-
ownerEarnedUsd: number;
|
|
70
|
-
platformFeeUsd: number;
|
|
71
|
-
durationMs: number;
|
|
72
|
-
}
|
|
73
|
-
interface SandboxStreamEvent {
|
|
74
|
-
type?: string;
|
|
75
|
-
data?: {
|
|
76
|
-
part?: {
|
|
77
|
-
type?: string;
|
|
78
|
-
text?: string;
|
|
79
|
-
};
|
|
80
|
-
delta?: string;
|
|
81
|
-
finalText?: string;
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
interface SandboxBox {
|
|
85
|
-
streamPrompt(message: string, opts?: {
|
|
86
|
-
sessionId?: string;
|
|
87
|
-
systemPrompt?: string;
|
|
88
|
-
}): AsyncIterable<SandboxStreamEvent>;
|
|
89
|
-
}
|
|
90
|
-
interface GatewayConfig {
|
|
91
|
-
/** Resolve agent metadata by slug. Return null if not found or not published. */
|
|
92
|
-
resolveAgent: (slug: string) => Promise<AgentMeta | null>;
|
|
93
|
-
/** Get a sandbox instance for the agent. Called after payment is verified. */
|
|
94
|
-
getSandbox: (agent: AgentMeta) => Promise<SandboxBox>;
|
|
95
|
-
/** Record a usage event after request completes. */
|
|
96
|
-
recordUsage: (event: GatewayUsageEvent) => Promise<void>;
|
|
97
|
-
/** x402 payment configuration */
|
|
98
|
-
x402: X402Config;
|
|
99
|
-
/** MPP (Machine Payments Protocol) configuration. If provided, gateway accepts Authorization: Payment headers. */
|
|
100
|
-
mpp?: MppConfig;
|
|
101
|
-
/**
|
|
102
|
-
* Verify an API key. Return key info if valid, null if invalid.
|
|
103
|
-
* Default: accepts any `sk_agent_*` key (demo mode).
|
|
104
|
-
*/
|
|
105
|
-
verifyApiKey?: (authHeader: string) => Promise<ApiKeyInfo | null>;
|
|
106
|
-
/**
|
|
107
|
-
* Settle payment after successful response.
|
|
108
|
-
* For x402: call ShieldedCredits.claimPayment()
|
|
109
|
-
* For API key: deduct from spending limit
|
|
110
|
-
* Default: no-op (demo mode).
|
|
111
|
-
*/
|
|
112
|
-
settlePayment?: (payment: PaymentResult, cost: number) => Promise<void>;
|
|
113
|
-
/** Base URL for API key purchase links (e.g. "https://film.tangle.tools") */
|
|
114
|
-
baseUrl?: string;
|
|
115
|
-
/** Max message length in chars (default: 8000) */
|
|
116
|
-
maxMessageLength?: number;
|
|
117
|
-
/** Required scope for chat endpoint (default: "chat"). API keys must include this scope. */
|
|
118
|
-
requiredScope?: string;
|
|
119
|
-
/** Block requests with detected injection patterns (default: false — log only) */
|
|
120
|
-
blockInjection?: boolean;
|
|
121
|
-
/** Rate limiting config. Default: 60 requests per 60 seconds per consumer. */
|
|
122
|
-
rateLimit?: {
|
|
123
|
-
limit: number;
|
|
124
|
-
windowSeconds: number;
|
|
125
|
-
};
|
|
126
|
-
/** Custom rate limit store (default: in-memory). Use KV-backed for Workers. */
|
|
127
|
-
rateLimitStore?: RateLimitStore;
|
|
128
|
-
/** Nonce replay protection store (default: in-memory). Rejects reused x402 nonces. */
|
|
129
|
-
nonceStore?: NonceStore;
|
|
130
|
-
}
|
|
131
|
-
interface ChatMessage {
|
|
132
|
-
role: 'system' | 'user' | 'assistant' | 'tool';
|
|
133
|
-
content: string;
|
|
134
|
-
}
|
|
135
|
-
interface ChatCompletionRequest {
|
|
136
|
-
model?: string;
|
|
137
|
-
messages: ChatMessage[];
|
|
138
|
-
stream?: boolean;
|
|
139
|
-
temperature?: number;
|
|
140
|
-
max_tokens?: number;
|
|
141
|
-
}
|
|
142
|
-
interface ChatCompletionChunk {
|
|
143
|
-
id: string;
|
|
144
|
-
object: 'chat.completion.chunk';
|
|
145
|
-
created: number;
|
|
146
|
-
model: string;
|
|
147
|
-
choices: Array<{
|
|
148
|
-
index: number;
|
|
149
|
-
delta: {
|
|
150
|
-
content?: string;
|
|
151
|
-
role?: string;
|
|
152
|
-
};
|
|
153
|
-
finish_reason: string | null;
|
|
154
|
-
}>;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
export type { AgentMeta, ApiKeyInfo, ChatCompletionChunk, ChatCompletionRequest, ChatMessage, GatewayConfig, GatewayUsageEvent, MppConfig, PaymentMethod, PaymentResult, SandboxBox, SandboxStreamEvent, X402Config };
|
|
1
|
+
export { a as AgentMeta, A as ApiKeyInfo, c as ChatCompletionChunk, d as ChatCompletionRequest, C as ChatMessage, G as GatewayConfig, h as GatewayUsageEvent, M as MppConfig, P as PaymentMethod, i as PaymentResult, S as SandboxBox, j as SandboxStreamEvent, X as X402Config } from './types-14xV8J4G.js';
|
|
2
|
+
import './nonce-store.js';
|
|
3
|
+
import './rate-limit.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-gateway",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "x402 + API key payment gateway for Tangle agent products. Mount on any Hono app to expose agents as paid OpenAI-compatible endpoints.",
|
|
6
6
|
"exports": {
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
"./types": {
|
|
16
16
|
"types": "./dist/types.d.ts",
|
|
17
17
|
"import": "./dist/types.js"
|
|
18
|
+
},
|
|
19
|
+
"./observer": {
|
|
20
|
+
"types": "./dist/observer.d.ts",
|
|
21
|
+
"import": "./dist/observer.js"
|
|
18
22
|
}
|
|
19
23
|
},
|
|
20
24
|
"files": [
|
package/src/filter.ts
CHANGED
|
@@ -17,20 +17,39 @@ const INJECTION_PATTERNS = [
|
|
|
17
17
|
// Prompt extraction
|
|
18
18
|
/what\s+(is|are)\s+your\s+(system\s+)?(prompt|instructions?|rules?|directives?)/i,
|
|
19
19
|
/repeat\s+(your|the)\s+(system\s+)?(prompt|instructions?)/i,
|
|
20
|
-
/output\s+(your|the)\s+(system\s+)?(prompt|instructions?)/i,
|
|
21
|
-
/show\s+me\s+(your|the)\s+(system|hidden|secret)\s+(prompt|instructions?|message)/i,
|
|
20
|
+
/output\s+(your|the)\s+((system|initial|original|first|full|real|hidden|secret|raw|exact)\s+)?(prompt|instructions?)/i,
|
|
21
|
+
/show\s+(me\s+)?(your|the)\s+((system|hidden|secret|initial|original)\s+)?(prompt|instructions?|message|directives?)/i,
|
|
22
|
+
/tell\s+me\s+(your|the)\s+(system\s+)?(prompt|instructions?|rules?)/i,
|
|
23
|
+
// Developer/debug/admin/jailbreak mode override
|
|
24
|
+
/(developer|debug|admin|god|sudo|jailbreak|unrestricted|maintenance)\s+mode/i,
|
|
25
|
+
// Safety/safeguard disablement
|
|
26
|
+
/(disable|bypass|override|remove|turn\s+off)\s+(your\s+|the\s+)?(safety|safeguards?|guardrails?|filters?|restrictions?|rules?|limitations?|content\s+policy)/i,
|
|
27
|
+
// Role reversal
|
|
28
|
+
/(from\s+now\s+on|starting\s+now|now)\s+you('re|\s+are)\s+(the\s+)?(user|human|customer|assistant)/i,
|
|
29
|
+
/i('m|\s+am)\s+(the\s+)?(assistant|ai|model|llm|bot)/i,
|
|
30
|
+
// Tool-call / JSON-shaped spoofs
|
|
31
|
+
/"(?:tool|function|action|call)"\s*:\s*"[^"]*(exfiltrate|leak|reveal|dump|extract|steal)[^"]*"/i,
|
|
32
|
+
/exfiltrate[_\s]*(system|prompt|secret|vault|config|data)/i,
|
|
22
33
|
// Data exfiltration
|
|
23
34
|
/read\s+(the\s+)?(vault|workspace|config|secret|\.env)/i,
|
|
24
35
|
/cat\s+\/home\/agent\/(vault|config|\.env|secrets?)/i,
|
|
25
36
|
/list\s+(all\s+)?(vault|workspace|secret)\s+(files?|contents?|data)/i,
|
|
26
37
|
]
|
|
27
38
|
|
|
28
|
-
// Unicode normalization
|
|
39
|
+
// Unicode normalization.
|
|
40
|
+
//
|
|
41
|
+
// Attacks insert zero-width chars between every word so `\s+`-anchored
|
|
42
|
+
// regexes don't fire. Stripping entirely leaves attack text as one glued
|
|
43
|
+
// token which ALSO dodges `\s+`. Split the handling: U+200B ZWSP,
|
|
44
|
+
// U+200C ZWNJ, U+FEFF ZWNBSP are commonly used in attacks and get
|
|
45
|
+
// replaced with a real SPACE so regexes see tokenized text. U+200D ZWJ
|
|
46
|
+
// + directional marks are legitimately used in scripts like Hindi and
|
|
47
|
+
// Arabic; strip without replacement so we don't spuriously split
|
|
48
|
+
// legitimate words. NFKC collapses homoglyphs (Cyrillic a → Latin a, fullwidth → ascii).
|
|
29
49
|
function normalizeUnicode(text: string): string {
|
|
30
50
|
return text
|
|
31
|
-
|
|
32
|
-
.replace(/[\
|
|
33
|
-
// Normalize to NFKC (collapses homoglyphs like а→a, е→e)
|
|
51
|
+
.replace(/[\u200B\u200C\uFEFF]/g, ' ')
|
|
52
|
+
.replace(/[\u200D\u200E\u200F\u2028-\u202F\u2060]/g, '')
|
|
34
53
|
.normalize('NFKC')
|
|
35
54
|
}
|
|
36
55
|
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ export {
|
|
|
9
9
|
export {
|
|
10
10
|
checkRateLimit,
|
|
11
11
|
MemoryRateLimitStore,
|
|
12
|
+
KvRateLimitStore,
|
|
12
13
|
type RateLimitConfig,
|
|
13
14
|
type RateLimitResult,
|
|
14
15
|
type RateLimitStore,
|
|
@@ -23,8 +24,17 @@ export {
|
|
|
23
24
|
} from './api-keys'
|
|
24
25
|
export {
|
|
25
26
|
MemoryNonceStore,
|
|
27
|
+
KvNonceStore,
|
|
26
28
|
type NonceStore,
|
|
27
29
|
} from './nonce-store'
|
|
30
|
+
export {
|
|
31
|
+
ConsoleObserver,
|
|
32
|
+
CompositeObserver,
|
|
33
|
+
generateRequestId,
|
|
34
|
+
type GatewayObserver,
|
|
35
|
+
type RequestContext,
|
|
36
|
+
type AuthFailureReason,
|
|
37
|
+
} from './observer'
|
|
28
38
|
export {
|
|
29
39
|
createPublishRoutes,
|
|
30
40
|
type PublishedConfig,
|