@tangle-network/agent-gateway 0.5.0 → 0.7.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.
@@ -1,635 +0,0 @@
1
- import {
2
- MemoryNonceStore
3
- } from "./chunk-M7ZJAK4K.js";
4
- import {
5
- MemoryRateLimitStore,
6
- checkRateLimit
7
- } from "./chunk-XCTXHZ76.js";
8
-
9
- // src/middleware.ts
10
- import { Hono } from "hono";
11
-
12
- // src/verify.ts
13
- async function verifyX402(spendAuthHeader, config, nonceStore) {
14
- try {
15
- const raw = JSON.parse(spendAuthHeader);
16
- if (!raw.commitment || !raw.signature || !raw.amount) return null;
17
- if (raw.operator?.toLowerCase() !== config.operatorAddress.toLowerCase()) return null;
18
- const amount = BigInt(raw.amount);
19
- const nonce = BigInt(raw.nonce);
20
- const expiry = BigInt(raw.expiry);
21
- if (expiry < BigInt(Math.floor(Date.now() / 1e3))) return null;
22
- if (amount <= 0n) return null;
23
- const nonceKey = `${raw.commitment}:${nonce.toString()}`;
24
- if (nonceStore) {
25
- if (await nonceStore.hasSeen(nonceKey)) return null;
26
- const ttl = Math.min(Number(expiry) - Math.floor(Date.now() / 1e3), 3600);
27
- await nonceStore.markSeen(nonceKey, Math.max(ttl, 60));
28
- }
29
- if (config.verifySigner) {
30
- const verified = await config.verifySigner(raw);
31
- if (!verified) return null;
32
- } else if (!config.demoMode) {
33
- return null;
34
- }
35
- return raw.commitment;
36
- } catch {
37
- return null;
38
- }
39
- }
40
- async function verifyMpp(authHeader, _config, x402Config) {
41
- const match = authHeader.match(/^Payment\s+(\S+)\s+(\S+)$/i);
42
- if (!match) return null;
43
- const [, , credentialB64] = match;
44
- try {
45
- const decoded = Buffer.from(credentialB64, "base64url").toString("utf-8");
46
- const credential = JSON.parse(decoded);
47
- const payload = credential.payload ?? credential;
48
- if (!payload.commitment && !payload.from) return null;
49
- const operator = payload.operator ?? payload.to;
50
- if (operator && operator.toLowerCase() !== x402Config.operatorAddress.toLowerCase()) return null;
51
- if (payload.amount) BigInt(payload.amount);
52
- if (payload.nonce) BigInt(payload.nonce);
53
- return payload.commitment ?? payload.from ?? null;
54
- } catch {
55
- return null;
56
- }
57
- }
58
- async function defaultVerifyApiKey(authHeader) {
59
- if (!authHeader.startsWith("Bearer sk_agent_")) return null;
60
- const key = authHeader.slice(7);
61
- return {
62
- keyId: key.slice(0, 16),
63
- consumerId: `apikey:${key.slice(0, 16)}`
64
- };
65
- }
66
-
67
- // src/filter.ts
68
- var INJECTION_PATTERNS = [
69
- // Direct instruction override
70
- /ignore\s+(all\s+)?(previous|prior|above|earlier)\s+(instructions?|prompts?|rules?|directives?)/i,
71
- /disregard\s+(all\s+)?(previous|prior|system)/i,
72
- /forget\s+(everything|all|your)\s+(previous|instructions?|training)/i,
73
- // Role assumption
74
- /you\s+are\s+now\s+(a|an|the)\s+/i,
75
- /pretend\s+(you\s+are|to\s+be)\s+/i,
76
- /act\s+as\s+(if\s+you\s+are|a|an|the)\s+/i,
77
- /new\s+instructions?:/i,
78
- /\[system\]/i,
79
- /\[INST\]/i,
80
- // Prompt extraction
81
- /what\s+(is|are)\s+your\s+(system\s+)?(prompt|instructions?|rules?|directives?)/i,
82
- /repeat\s+(your|the)\s+(system\s+)?(prompt|instructions?)/i,
83
- /output\s+(your|the)\s+((system|initial|original|first|full|real|hidden|secret|raw|exact)\s+)?(prompt|instructions?)/i,
84
- /show\s+(me\s+)?(your|the)\s+((system|hidden|secret|initial|original)\s+)?(prompt|instructions?|message|directives?)/i,
85
- /tell\s+me\s+(your|the)\s+(system\s+)?(prompt|instructions?|rules?)/i,
86
- // Developer/debug/admin/jailbreak mode override
87
- /(developer|debug|admin|god|sudo|jailbreak|unrestricted|maintenance)\s+mode/i,
88
- // Safety/safeguard disablement
89
- /(disable|bypass|override|remove|turn\s+off)\s+(your\s+|the\s+)?(safety|safeguards?|guardrails?|filters?|restrictions?|rules?|limitations?|content\s+policy)/i,
90
- // Role reversal
91
- /(from\s+now\s+on|starting\s+now|now)\s+you('re|\s+are)\s+(the\s+)?(user|human|customer|assistant)/i,
92
- /i('m|\s+am)\s+(the\s+)?(assistant|ai|model|llm|bot)/i,
93
- // Tool-call / JSON-shaped spoofs
94
- /"(?:tool|function|action|call)"\s*:\s*"[^"]*(exfiltrate|leak|reveal|dump|extract|steal)[^"]*"/i,
95
- /exfiltrate[_\s]*(system|prompt|secret|vault|config|data)/i,
96
- // Data exfiltration
97
- /read\s+(the\s+)?(vault|workspace|config|secret|\.env)/i,
98
- /cat\s+\/home\/agent\/(vault|config|\.env|secrets?)/i,
99
- /list\s+(all\s+)?(vault|workspace|secret)\s+(files?|contents?|data)/i,
100
- // i18n — Spanish
101
- /ignor[ae]\s+(todas?\s+)?(las\s+)?(instrucciones?|indicaciones?|órdenes?)\s+(previas?|anteriores?)/i,
102
- /olvid[ae]\s+(todas?\s+|tus\s+)?(instrucciones?|indicaciones?)/i,
103
- /(ahora\s+)?(tú\s+)?eres\s+(ahora\s+)?(un|una|el|la)\s+/i,
104
- /finge\s+(que\s+eres|ser)\s+/i,
105
- // i18n — French
106
- /ignor(ez|e|es)\s+(toutes?\s+)?(les\s+)?(instructions?|consignes?|directives?)\s+(précédentes?|antérieures?)/i,
107
- /oublie(z|s)?\s+(toutes?\s+|vos\s+|tes\s+)?(instructions?|consignes?)/i,
108
- /(vous\s+êtes|tu\s+es)\s+(maintenant|désormais)\s+(un|une)\s+/i,
109
- /prétend(ez|s)?\s+(être|que\s+vous\s+êtes|que\s+tu\s+es)\s+/i,
110
- // i18n — German
111
- /ignorier(e|en|t)\s+(alle\s+)?(vorherigen?|vorigen?|bisherigen?)\s+(anweisungen|anordnungen|vorgaben)/i,
112
- /vergiss\s+(alle\s+|deine\s+)?(anweisungen|vorgaben|regeln)/i,
113
- /du\s+bist\s+(jetzt|nun)\s+(ein|eine)\s+/i,
114
- /tu\s+so\s+als\s+(wärst\s+du|ob\s+du)\s+/i
115
- ];
116
- var HOMOGLYPHS = {
117
- "\u0430": "a",
118
- "\u0435": "e",
119
- "\u043E": "o",
120
- "\u0440": "p",
121
- "\u0441": "c",
122
- "\u0443": "y",
123
- "\u0445": "x",
124
- "\u0456": "i",
125
- "\u0458": "j",
126
- "\u0455": "s",
127
- "\u04BB": "h",
128
- "\u0410": "A",
129
- "\u0412": "B",
130
- "\u0415": "E",
131
- "\u041A": "K",
132
- "\u041C": "M",
133
- "\u041D": "H",
134
- "\u041E": "O",
135
- "\u0420": "P",
136
- "\u0421": "C",
137
- "\u0422": "T",
138
- "\u0425": "X",
139
- "\u0406": "I",
140
- "\u0408": "J",
141
- "\u0405": "S",
142
- "\u03B1": "a",
143
- "\u03B5": "e",
144
- "\u03BF": "o",
145
- "\u03C1": "p",
146
- "\u03C5": "y",
147
- "\u03BD": "v",
148
- "\u03B9": "i",
149
- "\u03C4": "t",
150
- "\u03BA": "k",
151
- "\u03C7": "x",
152
- "\u03B7": "n",
153
- "\u03BC": "u",
154
- "\u0391": "A",
155
- "\u0392": "B",
156
- "\u0395": "E",
157
- "\u0396": "Z",
158
- "\u0397": "H",
159
- "\u0399": "I",
160
- "\u039A": "K",
161
- "\u039C": "M",
162
- "\u039D": "N",
163
- "\u039F": "O",
164
- "\u03A1": "P",
165
- "\u03A4": "T",
166
- "\u03A7": "X",
167
- "\u03A5": "Y"
168
- };
169
- function collapseHomoglyphs(text) {
170
- let out = "";
171
- for (const ch of text) out += HOMOGLYPHS[ch] ?? ch;
172
- return out;
173
- }
174
- function normalizeUnicode(text) {
175
- return collapseHomoglyphs(
176
- text.replace(/[\u200B-\u200F\u2028-\u202F\u2060\uFEFF]/g, "").normalize("NFKC")
177
- );
178
- }
179
- function normalizeForDetection(text) {
180
- const spaced = text.replace(/[\u200B\u200C\uFEFF]/g, " ").replace(/[\u200D\u200E\u200F\u2028-\u202F\u2060]/g, "").normalize("NFKC");
181
- return collapseHomoglyphs(spaced).replace(/[(){}<>,.!?:;"`~]+/g, " ").replace(/\s+/g, " ");
182
- }
183
- function normalizeForDetectionNoBrackets(text) {
184
- return normalizeForDetection(text).replace(/[\[\]]+/g, " ").replace(/\s+/g, " ");
185
- }
186
- function detectInjection(content) {
187
- const stripNorm = normalizeUnicode(content);
188
- const detectNorm = normalizeForDetection(content);
189
- const noBracketNorm = normalizeForDetectionNoBrackets(content);
190
- const matches = [];
191
- for (const pattern of INJECTION_PATTERNS) {
192
- if (pattern.test(stripNorm) || pattern.test(detectNorm) || pattern.test(noBracketNorm)) {
193
- matches.push(pattern.source.slice(0, 60));
194
- }
195
- }
196
- for (const norm of [stripNorm, detectNorm]) {
197
- const b64Matches = norm.match(/[A-Za-z0-9+/]{40,}={0,2}/g);
198
- if (!b64Matches) continue;
199
- for (const b64 of b64Matches) {
200
- try {
201
- const decoded = atob(b64);
202
- if (INJECTION_PATTERNS.some((p) => p.test(decoded))) {
203
- if (!matches.includes("base64-encoded injection")) {
204
- matches.push("base64-encoded injection");
205
- }
206
- }
207
- } catch {
208
- }
209
- }
210
- }
211
- return matches;
212
- }
213
- function filterConsumerMessages(messages, maxLength = 8e3) {
214
- return messages.filter((m) => m.role !== "system").map((m) => {
215
- const normalized = normalizeUnicode(m.content);
216
- const redacted = normalized.replace(/\b(vault|workspace|owner|admin|secret|\.env|config\.json)[\s/:][^\s]*/gi, "[REDACTED]").slice(0, maxLength);
217
- return { role: m.role, content: redacted };
218
- });
219
- }
220
- function filterConsumerMessagesStrict(messages, maxLength = 8e3) {
221
- const filtered = filterConsumerMessages(messages, maxLength);
222
- const allContent = filtered.map((m) => m.content).join(" ");
223
- const injectionWarnings = detectInjection(allContent);
224
- return { messages: filtered, injectionWarnings };
225
- }
226
- function redactSystemPromptFromOutput(output, systemPrompt) {
227
- if (!systemPrompt || systemPrompt.length < 40) return output;
228
- const chunks = systemPrompt.split(/[.\n]/).map((s) => s.trim()).filter((s) => s.length >= 40);
229
- let redacted = output;
230
- for (const chunk of chunks) {
231
- const idx = redacted.toLowerCase().indexOf(chunk.toLowerCase());
232
- if (idx >= 0) {
233
- redacted = redacted.slice(0, idx) + "[REDACTED \u2014 system instructions]" + redacted.slice(idx + chunk.length);
234
- }
235
- }
236
- return redacted;
237
- }
238
-
239
- // src/observer.ts
240
- var ConsoleObserver = class {
241
- constructor(log = (e) => console.log(JSON.stringify(e))) {
242
- this.log = log;
243
- }
244
- log;
245
- emit(level, event, ctx, rest = {}) {
246
- this.log({
247
- level,
248
- event,
249
- time: (/* @__PURE__ */ new Date()).toISOString(),
250
- requestId: ctx.requestId,
251
- agentSlug: ctx.agentSlug,
252
- durationMs: Date.now() - ctx.startMs,
253
- ...rest
254
- });
255
- }
256
- onRequestStart(ctx) {
257
- this.emit("info", "gateway.request.start", ctx);
258
- }
259
- onPaymentVerified(ctx, info) {
260
- this.emit("info", "gateway.payment.verified", ctx, info);
261
- }
262
- onAuthFailure(ctx, reason) {
263
- this.emit("warn", "gateway.auth.failure", ctx, reason);
264
- }
265
- onRateLimited(ctx, info) {
266
- this.emit("warn", "gateway.rate_limit", ctx, info);
267
- }
268
- onBodyTooLarge(ctx, contentLength) {
269
- this.emit("warn", "gateway.body_too_large", ctx, { contentLength });
270
- }
271
- onInjectionDetected(ctx, info) {
272
- this.emit("warn", "gateway.injection", ctx, info);
273
- }
274
- onRequestComplete(ctx, usage) {
275
- this.emit("info", "gateway.request.complete", ctx, usage);
276
- }
277
- onStreamError(ctx, info) {
278
- this.emit("error", "gateway.stream.error", ctx, info);
279
- }
280
- onSettlementError(ctx, info) {
281
- this.emit("error", "gateway.settlement.error", ctx, info);
282
- }
283
- };
284
- var CompositeObserver = class {
285
- constructor(observers) {
286
- this.observers = observers;
287
- }
288
- observers;
289
- async fanOut(event, ...args) {
290
- for (const obs of this.observers) {
291
- const fn = obs[event];
292
- if (!fn) continue;
293
- try {
294
- await fn.apply(obs, args);
295
- } catch (err) {
296
- console.warn(`[agent-gateway] observer ${event} threw:`, err instanceof Error ? err.message : err);
297
- }
298
- }
299
- }
300
- onRequestStart = (ctx) => this.fanOut("onRequestStart", ctx);
301
- onPaymentVerified = (ctx, info) => this.fanOut("onPaymentVerified", ctx, info);
302
- onAuthFailure = (ctx, reason) => this.fanOut("onAuthFailure", ctx, reason);
303
- onRateLimited = (ctx, info) => this.fanOut("onRateLimited", ctx, info);
304
- onBodyTooLarge = (ctx, contentLength) => this.fanOut("onBodyTooLarge", ctx, contentLength);
305
- onInjectionDetected = (ctx, info) => this.fanOut("onInjectionDetected", ctx, info);
306
- onRequestComplete = (ctx, usage) => this.fanOut("onRequestComplete", ctx, usage);
307
- onStreamError = (ctx, info) => this.fanOut("onStreamError", ctx, info);
308
- onSettlementError = (ctx, info) => this.fanOut("onSettlementError", ctx, info);
309
- };
310
- function generateRequestId() {
311
- const bytes = new Uint8Array(16);
312
- globalThis.crypto.getRandomValues(bytes);
313
- const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
314
- return `req_${hex}`;
315
- }
316
-
317
- // src/middleware.ts
318
- function createAgentGateway(config) {
319
- if (!config.x402.verifySigner && !config.x402.demoMode) {
320
- throw new Error(
321
- "createAgentGateway: x402.verifySigner is required in production. For tests, set x402.demoMode: true explicitly."
322
- );
323
- }
324
- const gw = new Hono();
325
- const maxLen = config.maxMessageLength ?? 8e3;
326
- const rateLimitStore = config.rateLimitStore ?? new MemoryRateLimitStore();
327
- const globalRateLimit = config.rateLimit ?? { limit: 60, windowSeconds: 60 };
328
- const nonceStore = config.nonceStore ?? new MemoryNonceStore();
329
- const requiredScope = config.requiredScope ?? "chat";
330
- const obs = config.observer;
331
- gw.get("/:slug/chat/completions", async (c) => {
332
- const slug = c.req.param("slug");
333
- const agent = await config.resolveAgent(slug);
334
- if (!agent) return c.json({ error: "Agent not found or not published" }, 404);
335
- const paymentMethods = [
336
- {
337
- type: "x402",
338
- operator: config.x402.operatorAddress,
339
- chain_id: config.x402.chainId,
340
- credits_contract: config.x402.creditsAddress
341
- }
342
- ];
343
- if (config.mpp) {
344
- paymentMethods.push({
345
- type: "mpp",
346
- realm: config.mpp.realm,
347
- method: config.mpp.method ?? "blueprintevm"
348
- });
349
- }
350
- paymentMethods.push({ type: "api_key", prefix: "sk_agent_" });
351
- return c.json({
352
- slug: agent.slug,
353
- pricing: {
354
- per_token_usd: agent.pricePerTokenUsd,
355
- currency: "USD",
356
- platform_fee_percent: agent.platformFeePercent
357
- },
358
- hosting: {
359
- mode: agent.sandboxEndpoint ? "sovereign" : "centralized",
360
- endpoint: agent.sandboxEndpoint ?? config.baseUrl ?? "tangle.tools"
361
- },
362
- payment_methods: paymentMethods,
363
- capabilities: ["chat.completions", "streaming"],
364
- openai_compatible: true
365
- });
366
- });
367
- gw.post("/:slug/chat/completions", async (c) => {
368
- const slug = c.req.param("slug");
369
- const startMs = Date.now();
370
- const requestId = generateRequestId();
371
- const ctx = { requestId, agentSlug: slug, startMs };
372
- await obs?.onRequestStart?.(ctx);
373
- const agent = await config.resolveAgent(slug);
374
- if (!agent) {
375
- return c.json({ error: { message: "Agent not found", type: "not_found" } }, 404);
376
- }
377
- const contentLength = parseInt(c.req.header("Content-Length") ?? "0", 10);
378
- if (contentLength > 65536) {
379
- await obs?.onBodyTooLarge?.(ctx, contentLength);
380
- return c.json(
381
- { error: { message: "Request body too large (max 64KB)", type: "invalid_request" } },
382
- 413
383
- );
384
- }
385
- let body;
386
- try {
387
- body = await c.req.json();
388
- } catch {
389
- return c.json({ error: { message: "Invalid JSON", type: "invalid_request" } }, 400);
390
- }
391
- if (!body.messages?.length) {
392
- return c.json({ error: { message: "messages array required", type: "invalid_request" } }, 400);
393
- }
394
- const spendAuthHeader = c.req.header("X-Payment-Signature");
395
- const authHeader = c.req.header("Authorization") ?? "";
396
- let consumerId = null;
397
- let paymentMethod = "none";
398
- let keyInfo = null;
399
- if (spendAuthHeader) {
400
- const signer = await verifyX402(spendAuthHeader, config.x402, nonceStore);
401
- if (!signer) {
402
- await obs?.onAuthFailure?.(ctx, { method: "x402", code: "invalid_spend_auth", httpStatus: 402 });
403
- return c.json(
404
- { error: { message: "Invalid X-Payment-Signature", type: "authentication_error", code: "invalid_spend_auth" } },
405
- { status: 402, headers: { "X-Payment-Required": "spendauth", "X-Request-Id": requestId } }
406
- );
407
- }
408
- consumerId = signer;
409
- paymentMethod = "x402";
410
- } else if (config.mpp && authHeader.toLowerCase().startsWith("payment ")) {
411
- const signer = await verifyMpp(authHeader, config.mpp, config.x402);
412
- if (!signer) {
413
- const realm = config.mpp.realm;
414
- const method = config.mpp.method ?? "blueprintevm";
415
- await obs?.onAuthFailure?.(ctx, { method: "mpp", code: "invalid_mpp_credential", httpStatus: 401 });
416
- return c.json(
417
- { error: { message: "Invalid Payment credential", type: "authentication_error", code: "invalid_mpp_credential" } },
418
- { status: 401, headers: { "WWW-Authenticate": `Payment realm="${realm}", method="${method}"`, "X-Request-Id": requestId } }
419
- );
420
- }
421
- consumerId = signer;
422
- paymentMethod = "mpp";
423
- } else if (authHeader.startsWith("Bearer ")) {
424
- const verify = config.verifyApiKey ?? defaultVerifyApiKey;
425
- const key = await verify(authHeader);
426
- if (!key) {
427
- await obs?.onAuthFailure?.(ctx, { method: "apikey", code: "invalid_api_key", httpStatus: 401 });
428
- return c.json(
429
- { error: { message: "Invalid API key", type: "authentication_error" } },
430
- { status: 401, headers: { "X-Request-Id": requestId } }
431
- );
432
- }
433
- if (key.scopes && key.scopes.length > 0 && !key.scopes.includes(requiredScope)) {
434
- await obs?.onAuthFailure?.(ctx, { method: "apikey", code: "insufficient_scope", httpStatus: 403 });
435
- return c.json(
436
- { error: { message: `API key missing required scope: ${requiredScope}`, type: "forbidden", code: "insufficient_scope" } },
437
- { status: 403, headers: { "X-Request-Id": requestId } }
438
- );
439
- }
440
- consumerId = key.consumerId;
441
- paymentMethod = "apikey";
442
- keyInfo = key;
443
- } else {
444
- await obs?.onAuthFailure?.(ctx, { method: "none", code: "payment_required", httpStatus: 402 });
445
- const methods = ["x402"];
446
- if (config.mpp) methods.push("mpp");
447
- methods.push("api_key");
448
- const headers = {
449
- "X-Payment-Required": methods.join(", "),
450
- "X-Request-Id": requestId
451
- };
452
- if (config.mpp) {
453
- headers["WWW-Authenticate"] = `Payment realm="${config.mpp.realm}", method="${config.mpp.method ?? "blueprintevm"}"`;
454
- }
455
- return c.json({
456
- error: {
457
- message: "Payment required",
458
- type: "payment_required",
459
- payment_methods: methods,
460
- x402: {
461
- operator: config.x402.operatorAddress,
462
- chain_id: config.x402.chainId,
463
- credits_address: config.x402.creditsAddress,
464
- estimated_amount_per_request: "20000"
465
- },
466
- ...config.mpp ? {
467
- mpp: { realm: config.mpp.realm, method: config.mpp.method ?? "blueprintevm" }
468
- } : {},
469
- api_key: {
470
- purchase_url: config.baseUrl ? `${config.baseUrl}/agents/${slug}/api-keys` : void 0
471
- }
472
- }
473
- }, { status: 402, headers });
474
- }
475
- await obs?.onPaymentVerified?.(ctx, { method: paymentMethod, consumerId, keyId: keyInfo?.keyId });
476
- const effectiveRateLimit = keyInfo?.rateLimitPerMinute ? { limit: keyInfo.rateLimitPerMinute, windowSeconds: 60 } : globalRateLimit;
477
- const rl = await checkRateLimit(consumerId, effectiveRateLimit, rateLimitStore);
478
- if (!rl.allowed) {
479
- await obs?.onRateLimited?.(ctx, { consumerId, retryAfterSeconds: rl.retryAfterSeconds ?? 60 });
480
- return c.json(
481
- { error: { message: "Rate limit exceeded", type: "rate_limit_error", retry_after: rl.retryAfterSeconds } },
482
- { status: 429, headers: { "Retry-After": String(rl.retryAfterSeconds ?? 60), "X-Request-Id": requestId } }
483
- );
484
- }
485
- const { messages: filtered, injectionWarnings } = filterConsumerMessagesStrict(body.messages, maxLen);
486
- if (injectionWarnings.length > 0) {
487
- await obs?.onInjectionDetected?.(ctx, {
488
- consumerId,
489
- patterns: injectionWarnings,
490
- blocked: !!config.blockInjection
491
- });
492
- if (config.blockInjection) {
493
- return c.json(
494
- { error: { message: "Request rejected: potential prompt injection detected", type: "content_policy_violation" } },
495
- { status: 400, headers: { "X-Request-Id": requestId } }
496
- );
497
- }
498
- }
499
- const userMessage = filtered.filter((m) => m.role === "user").map((m) => m.content).join("\n\n");
500
- if (!userMessage) {
501
- return c.json({ error: { message: "No user message provided", type: "invalid_request" } }, 400);
502
- }
503
- if (config.authorizeConsumer) {
504
- const authz = await config.authorizeConsumer(agent, {
505
- method: paymentMethod,
506
- consumerId,
507
- keyId: keyInfo?.keyId,
508
- requestId
509
- });
510
- if (!authz.allow) {
511
- return c.json(
512
- {
513
- error: {
514
- message: authz.reason,
515
- type: "authorization_denied",
516
- code: authz.code
517
- }
518
- },
519
- { status: 403, headers: { "X-Request-Id": requestId } }
520
- );
521
- }
522
- }
523
- const inputTokens = Math.ceil(userMessage.length / 4);
524
- let outputTokens = 0;
525
- const stream = new ReadableStream({
526
- async start(controller) {
527
- const encoder = new TextEncoder();
528
- const sendChunk = (rawDelta) => {
529
- const delta = redactSystemPromptFromOutput(rawDelta, agent.systemPrompt);
530
- outputTokens += Math.ceil(delta.length / 4);
531
- const chunk = {
532
- id: `chatcmpl-${Date.now()}`,
533
- object: "chat.completion.chunk",
534
- created: Math.floor(Date.now() / 1e3),
535
- model: agent.slug,
536
- choices: [{ index: 0, delta: { content: delta }, finish_reason: null }]
537
- };
538
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(chunk)}
539
-
540
- `));
541
- };
542
- try {
543
- const box = await config.getSandbox(agent);
544
- const promptStream = box.streamPrompt(userMessage, {
545
- sessionId: `consumer:${consumerId}`,
546
- systemPrompt: agent.systemPrompt
547
- });
548
- for await (const event of promptStream) {
549
- if (event.type === "message.part.updated" && event.data?.part?.type === "text" && event.data.delta) {
550
- sendChunk(event.data.delta);
551
- }
552
- }
553
- const done = {
554
- id: `chatcmpl-${Date.now()}`,
555
- object: "chat.completion.chunk",
556
- created: Math.floor(Date.now() / 1e3),
557
- model: agent.slug,
558
- choices: [{ index: 0, delta: {}, finish_reason: "stop" }]
559
- };
560
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(done)}
561
-
562
- `));
563
- controller.enqueue(encoder.encode("data: [DONE]\n\n"));
564
- const totalCost = (inputTokens + outputTokens) * agent.pricePerTokenUsd;
565
- const ownerEarned = totalCost * (1 - agent.platformFeePercent);
566
- const platformFee = totalCost * agent.platformFeePercent;
567
- const usageEvent = {
568
- requestId: ctx.requestId,
569
- agentId: agent.id,
570
- agentSlug: agent.slug,
571
- consumerId,
572
- paymentMethod,
573
- inputTokens,
574
- outputTokens,
575
- totalCostUsd: totalCost,
576
- ownerEarnedUsd: ownerEarned,
577
- platformFeeUsd: platformFee,
578
- durationMs: Date.now() - startMs
579
- };
580
- await config.recordUsage(usageEvent);
581
- await obs?.onRequestComplete?.(ctx, usageEvent);
582
- if (config.settlePayment) {
583
- await config.settlePayment(
584
- { method: paymentMethod, consumerId, requestId: ctx.requestId },
585
- totalCost
586
- ).catch(async (err) => {
587
- const msg = err instanceof Error ? err.message : String(err);
588
- console.error(`[agent-gateway] settlement failed for ${consumerId}: ${msg}`);
589
- await obs?.onSettlementError?.(ctx, { consumerId, method: paymentMethod, errorMessage: msg });
590
- });
591
- }
592
- } catch (err) {
593
- const rawMessage = err instanceof Error ? err.message : String(err);
594
- const safeMessage = rawMessage.includes("/") || rawMessage.includes("\\") ? "Internal agent error" : rawMessage;
595
- await obs?.onStreamError?.(ctx, { consumerId, errorMessage: rawMessage });
596
- controller.enqueue(
597
- encoder.encode(`data: ${JSON.stringify({ error: { message: safeMessage, type: "server_error" } })}
598
-
599
- `)
600
- );
601
- } finally {
602
- controller.close();
603
- }
604
- }
605
- });
606
- return new Response(stream, {
607
- headers: {
608
- "Content-Type": "text/event-stream",
609
- "Cache-Control": "no-cache",
610
- "X-Request-Id": requestId,
611
- "X-Agent-Slug": agent.slug,
612
- "X-Agent-Hosting": agent.sandboxEndpoint ? "sovereign" : "centralized",
613
- "X-Payment-Method": paymentMethod,
614
- "X-Payment-Settled": paymentMethod === "x402" ? "pending" : "true",
615
- ...rl.remaining !== void 0 ? { "X-RateLimit-Remaining": String(rl.remaining) } : {}
616
- }
617
- });
618
- });
619
- return gw;
620
- }
621
-
622
- export {
623
- verifyX402,
624
- verifyMpp,
625
- defaultVerifyApiKey,
626
- detectInjection,
627
- filterConsumerMessages,
628
- filterConsumerMessagesStrict,
629
- redactSystemPromptFromOutput,
630
- ConsoleObserver,
631
- CompositeObserver,
632
- generateRequestId,
633
- createAgentGateway
634
- };
635
- //# sourceMappingURL=chunk-373QHRKV.js.map