@tangle-network/agent-gateway 0.6.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -6
- package/dist/chunk-Q4YAIEZY.js +1763 -0
- package/dist/chunk-Q4YAIEZY.js.map +1 -0
- package/dist/index.d.ts +13 -9
- package/dist/index.js +112 -1
- package/dist/index.js.map +1 -1
- package/dist/middleware.d.ts +2 -2
- package/dist/middleware.js +1 -1
- package/dist/types-DEsMmS-X.d.ts +875 -0
- package/dist/types.d.ts +1 -1
- package/package.json +14 -10
- package/src/a2a/agent-card.ts +55 -0
- package/src/a2a/handler.ts +797 -0
- package/src/a2a/jsonrpc.ts +65 -0
- package/src/a2a/push-notifications.ts +299 -0
- package/src/a2a/task-store-sql.ts +189 -0
- package/src/a2a/task-store.ts +53 -0
- package/src/a2a/translate.ts +77 -0
- package/src/a2a/types.ts +217 -0
- package/src/dispatch.ts +486 -0
- package/src/index.ts +58 -1
- package/src/middleware.ts +139 -294
- package/src/types.ts +76 -2
- package/src/verify.ts +93 -26
- package/dist/chunk-373QHRKV.js +0 -635
- package/dist/chunk-373QHRKV.js.map +0 -1
- package/dist/types-C_L7yXXI.d.ts +0 -362
package/dist/chunk-373QHRKV.js
DELETED
|
@@ -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
|