@tangle-network/agent-gateway 0.6.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 +0,0 @@
1
- {"version":3,"sources":["../src/middleware.ts","../src/verify.ts","../src/filter.ts","../src/observer.ts"],"sourcesContent":["import { Hono } from 'hono'\nimport type {\n GatewayConfig,\n ChatCompletionRequest,\n ChatCompletionChunk,\n PaymentMethod,\n ApiKeyInfo,\n} from './types'\nimport { verifyX402, verifyMpp, defaultVerifyApiKey } from './verify'\nimport { filterConsumerMessagesStrict, redactSystemPromptFromOutput } from './filter'\nimport { checkRateLimit, MemoryRateLimitStore, type RateLimitStore } from './rate-limit'\nimport { MemoryNonceStore } from './nonce-store'\nimport { generateRequestId, type GatewayObserver, type RequestContext } from './observer'\n\n/**\n * Create a Hono router that serves the agent gateway.\n *\n * Mount at any path:\n * app.route('/v1/agents', createAgentGateway(config))\n *\n * Exposes:\n * GET /:slug/chat/completions — agent discovery metadata\n * POST /:slug/chat/completions — OpenAI-compatible chat endpoint (paid)\n */\nexport function createAgentGateway(config: GatewayConfig) {\n // Production gateways must verify x402 signatures. Tests and local\n // dev can opt into the explicit demo path.\n if (!config.x402.verifySigner && !config.x402.demoMode) {\n throw new Error(\n 'createAgentGateway: x402.verifySigner is required in production. ' +\n 'For tests, set x402.demoMode: true explicitly.',\n )\n }\n const gw = new Hono()\n const maxLen = config.maxMessageLength ?? 8000\n const rateLimitStore: RateLimitStore = config.rateLimitStore ?? new MemoryRateLimitStore()\n const globalRateLimit = config.rateLimit ?? { limit: 60, windowSeconds: 60 }\n const nonceStore = config.nonceStore ?? new MemoryNonceStore()\n const requiredScope = config.requiredScope ?? 'chat'\n const obs: GatewayObserver | undefined = config.observer\n\n // --- Discovery endpoint (no auth) ---\n\n gw.get('/:slug/chat/completions', async (c) => {\n const slug = c.req.param('slug')\n const agent = await config.resolveAgent(slug)\n if (!agent) return c.json({ error: 'Agent not found or not published' }, 404)\n\n const paymentMethods: Array<Record<string, unknown>> = [\n {\n type: 'x402',\n operator: config.x402.operatorAddress,\n chain_id: config.x402.chainId,\n credits_contract: config.x402.creditsAddress,\n },\n ]\n if (config.mpp) {\n paymentMethods.push({\n type: 'mpp',\n realm: config.mpp.realm,\n method: config.mpp.method ?? 'blueprintevm',\n })\n }\n paymentMethods.push({ type: 'api_key', prefix: 'sk_agent_' })\n\n return c.json({\n slug: agent.slug,\n pricing: {\n per_token_usd: agent.pricePerTokenUsd,\n currency: 'USD',\n platform_fee_percent: agent.platformFeePercent,\n },\n hosting: {\n mode: agent.sandboxEndpoint ? 'sovereign' : 'centralized',\n endpoint: agent.sandboxEndpoint ?? config.baseUrl ?? 'tangle.tools',\n },\n payment_methods: paymentMethods,\n capabilities: ['chat.completions', 'streaming'],\n openai_compatible: true,\n })\n })\n\n // --- Chat completions endpoint (paid) ---\n\n gw.post('/:slug/chat/completions', async (c) => {\n const slug = c.req.param('slug')\n const startMs = Date.now()\n const requestId = generateRequestId()\n const ctx: RequestContext = { requestId, agentSlug: slug, startMs }\n\n await obs?.onRequestStart?.(ctx)\n\n // 1. Resolve agent\n const agent = await config.resolveAgent(slug)\n if (!agent) {\n return c.json({ error: { message: 'Agent not found', type: 'not_found' } }, 404)\n }\n\n // 2. Body size limit (before parsing — DoS prevention)\n const contentLength = parseInt(c.req.header('Content-Length') ?? '0', 10)\n if (contentLength > 65536) {\n await obs?.onBodyTooLarge?.(ctx, contentLength)\n return c.json(\n { error: { message: 'Request body too large (max 64KB)', type: 'invalid_request' } },\n 413,\n )\n }\n\n let body: ChatCompletionRequest\n try {\n body = await c.req.json()\n } catch {\n return c.json({ error: { message: 'Invalid JSON', type: 'invalid_request' } }, 400)\n }\n if (!body.messages?.length) {\n return c.json({ error: { message: 'messages array required', type: 'invalid_request' } }, 400)\n }\n\n // 3. Authenticate — x402 SpendAuth, MPP, or API key\n const spendAuthHeader = c.req.header('X-Payment-Signature')\n const authHeader = c.req.header('Authorization') ?? ''\n let consumerId: string | null = null\n let paymentMethod: PaymentMethod = 'none'\n let keyInfo: ApiKeyInfo | null = null\n\n if (spendAuthHeader) {\n const signer = await verifyX402(spendAuthHeader, config.x402, nonceStore)\n if (!signer) {\n await obs?.onAuthFailure?.(ctx, { method: 'x402', code: 'invalid_spend_auth', httpStatus: 402 })\n return c.json(\n { error: { message: 'Invalid X-Payment-Signature', type: 'authentication_error', code: 'invalid_spend_auth' } },\n { status: 402, headers: { 'X-Payment-Required': 'spendauth', 'X-Request-Id': requestId } },\n )\n }\n consumerId = signer\n paymentMethod = 'x402'\n } else if (config.mpp && authHeader.toLowerCase().startsWith('payment ')) {\n const signer = await verifyMpp(authHeader, config.mpp, config.x402)\n if (!signer) {\n const realm = config.mpp.realm\n const method = config.mpp.method ?? 'blueprintevm'\n await obs?.onAuthFailure?.(ctx, { method: 'mpp', code: 'invalid_mpp_credential', httpStatus: 401 })\n return c.json(\n { error: { message: 'Invalid Payment credential', type: 'authentication_error', code: 'invalid_mpp_credential' } },\n { status: 401, headers: { 'WWW-Authenticate': `Payment realm=\"${realm}\", method=\"${method}\"`, 'X-Request-Id': requestId } },\n )\n }\n consumerId = signer\n paymentMethod = 'mpp'\n } else if (authHeader.startsWith('Bearer ')) {\n const verify = config.verifyApiKey ?? defaultVerifyApiKey\n const key = await verify(authHeader)\n if (!key) {\n await obs?.onAuthFailure?.(ctx, { method: 'apikey', code: 'invalid_api_key', httpStatus: 401 })\n return c.json(\n { error: { message: 'Invalid API key', type: 'authentication_error' } },\n { status: 401, headers: { 'X-Request-Id': requestId } },\n )\n }\n\n // Scope enforcement — API key must include the required scope\n if (key.scopes && key.scopes.length > 0 && !key.scopes.includes(requiredScope)) {\n await obs?.onAuthFailure?.(ctx, { method: 'apikey', code: 'insufficient_scope', httpStatus: 403 })\n return c.json(\n { error: { message: `API key missing required scope: ${requiredScope}`, type: 'forbidden', code: 'insufficient_scope' } },\n { status: 403, headers: { 'X-Request-Id': requestId } },\n )\n }\n\n consumerId = key.consumerId\n paymentMethod = 'apikey'\n keyInfo = key\n } else {\n // No payment — return 402 with instructions\n await obs?.onAuthFailure?.(ctx, { method: 'none', code: 'payment_required', httpStatus: 402 })\n const methods: string[] = ['x402']\n if (config.mpp) methods.push('mpp')\n methods.push('api_key')\n\n const headers: Record<string, string> = {\n 'X-Payment-Required': methods.join(', '),\n 'X-Request-Id': requestId,\n }\n if (config.mpp) {\n headers['WWW-Authenticate'] = `Payment realm=\"${config.mpp.realm}\", method=\"${config.mpp.method ?? 'blueprintevm'}\"`\n }\n\n return c.json({\n error: {\n message: 'Payment required',\n type: 'payment_required',\n payment_methods: methods,\n x402: {\n operator: config.x402.operatorAddress,\n chain_id: config.x402.chainId,\n credits_address: config.x402.creditsAddress,\n estimated_amount_per_request: '20000',\n },\n ...(config.mpp ? {\n mpp: { realm: config.mpp.realm, method: config.mpp.method ?? 'blueprintevm' },\n } : {}),\n api_key: {\n purchase_url: config.baseUrl ? `${config.baseUrl}/agents/${slug}/api-keys` : undefined,\n },\n },\n }, { status: 402, headers })\n }\n\n await obs?.onPaymentVerified?.(ctx, { method: paymentMethod, consumerId: consumerId!, keyId: keyInfo?.keyId })\n\n // 4. Rate limit — per-key override or global\n const effectiveRateLimit = keyInfo?.rateLimitPerMinute\n ? { limit: keyInfo.rateLimitPerMinute, windowSeconds: 60 }\n : globalRateLimit\n\n const rl = await checkRateLimit(consumerId!, effectiveRateLimit, rateLimitStore)\n if (!rl.allowed) {\n await obs?.onRateLimited?.(ctx, { consumerId: consumerId!, retryAfterSeconds: rl.retryAfterSeconds ?? 60 })\n return c.json(\n { error: { message: 'Rate limit exceeded', type: 'rate_limit_error', retry_after: rl.retryAfterSeconds } },\n { status: 429, headers: { 'Retry-After': String(rl.retryAfterSeconds ?? 60), 'X-Request-Id': requestId } },\n )\n }\n\n // 5. Filter messages — injection detection + sanitization\n const { messages: filtered, injectionWarnings } = filterConsumerMessagesStrict(body.messages, maxLen)\n\n if (injectionWarnings.length > 0) {\n await obs?.onInjectionDetected?.(ctx, {\n consumerId: consumerId!,\n patterns: injectionWarnings,\n blocked: !!config.blockInjection,\n })\n\n if (config.blockInjection) {\n return c.json(\n { error: { message: 'Request rejected: potential prompt injection detected', type: 'content_policy_violation' } },\n { status: 400, headers: { 'X-Request-Id': requestId } },\n )\n }\n // In non-blocking mode, continue but the warning is logged for auditing\n }\n\n const userMessage = filtered\n .filter((m) => m.role === 'user')\n .map((m) => m.content)\n .join('\\n\\n')\n\n if (!userMessage) {\n return c.json({ error: { message: 'No user message provided', type: 'invalid_request' } }, 400)\n }\n\n // 5b. Optional host authorization. Runs after payment/rate-limit\n // checks and before sandbox allocation.\n if (config.authorizeConsumer) {\n const authz = await config.authorizeConsumer(agent, {\n method: paymentMethod,\n consumerId: consumerId!,\n keyId: keyInfo?.keyId,\n requestId,\n })\n if (!authz.allow) {\n return c.json(\n {\n error: {\n message: authz.reason,\n type: 'authorization_denied',\n code: authz.code,\n },\n },\n { status: 403, headers: { 'X-Request-Id': requestId } },\n )\n }\n }\n\n // 6. Get sandbox and stream response with output filtering\n const inputTokens = Math.ceil(userMessage.length / 4)\n let outputTokens = 0\n\n const stream = new ReadableStream({\n async start(controller) {\n const encoder = new TextEncoder()\n const sendChunk = (rawDelta: string) => {\n // Redact system prompt leakage from output\n const delta = redactSystemPromptFromOutput(rawDelta, agent.systemPrompt)\n outputTokens += Math.ceil(delta.length / 4)\n const chunk: ChatCompletionChunk = {\n id: `chatcmpl-${Date.now()}`,\n object: 'chat.completion.chunk',\n created: Math.floor(Date.now() / 1000),\n model: agent.slug,\n choices: [{ index: 0, delta: { content: delta }, finish_reason: null }],\n }\n controller.enqueue(encoder.encode(`data: ${JSON.stringify(chunk)}\\n\\n`))\n }\n\n try {\n const box = await config.getSandbox(agent)\n const promptStream = box.streamPrompt(userMessage, {\n sessionId: `consumer:${consumerId}`,\n systemPrompt: agent.systemPrompt,\n })\n\n for await (const event of promptStream) {\n if (\n event.type === 'message.part.updated' &&\n event.data?.part?.type === 'text' &&\n event.data.delta\n ) {\n sendChunk(event.data.delta)\n }\n }\n\n // Final chunk\n const done: ChatCompletionChunk = {\n id: `chatcmpl-${Date.now()}`,\n object: 'chat.completion.chunk',\n created: Math.floor(Date.now() / 1000),\n model: agent.slug,\n choices: [{ index: 0, delta: {}, finish_reason: 'stop' }],\n }\n controller.enqueue(encoder.encode(`data: ${JSON.stringify(done)}\\n\\n`))\n controller.enqueue(encoder.encode('data: [DONE]\\n\\n'))\n\n // 7. Record usage + settle payment\n const totalCost = (inputTokens + outputTokens) * agent.pricePerTokenUsd\n const ownerEarned = totalCost * (1 - agent.platformFeePercent)\n const platformFee = totalCost * agent.platformFeePercent\n\n const usageEvent = {\n requestId: ctx.requestId,\n agentId: agent.id,\n agentSlug: agent.slug,\n consumerId: consumerId!,\n paymentMethod,\n inputTokens,\n outputTokens,\n totalCostUsd: totalCost,\n ownerEarnedUsd: ownerEarned,\n platformFeeUsd: platformFee,\n durationMs: Date.now() - startMs,\n }\n\n await config.recordUsage(usageEvent)\n await obs?.onRequestComplete?.(ctx, usageEvent)\n\n if (config.settlePayment) {\n await config.settlePayment(\n { method: paymentMethod, consumerId: consumerId!, requestId: ctx.requestId },\n totalCost,\n ).catch(async err => {\n const msg = err instanceof Error ? err.message : String(err)\n console.error(`[agent-gateway] settlement failed for ${consumerId}: ${msg}`)\n await obs?.onSettlementError?.(ctx, { consumerId: consumerId!, method: paymentMethod, errorMessage: msg })\n })\n }\n } catch (err) {\n // Sanitize error — never expose stack traces or internal paths\n const rawMessage = err instanceof Error ? err.message : String(err)\n const safeMessage =\n rawMessage.includes('/') || rawMessage.includes('\\\\')\n ? 'Internal agent error'\n : rawMessage\n await obs?.onStreamError?.(ctx, { consumerId: consumerId!, errorMessage: rawMessage })\n controller.enqueue(\n encoder.encode(`data: ${JSON.stringify({ error: { message: safeMessage, type: 'server_error' } })}\\n\\n`),\n )\n } finally {\n controller.close()\n }\n },\n })\n\n return new Response(stream, {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'X-Request-Id': requestId,\n 'X-Agent-Slug': agent.slug,\n 'X-Agent-Hosting': agent.sandboxEndpoint ? 'sovereign' : 'centralized',\n 'X-Payment-Method': paymentMethod,\n 'X-Payment-Settled': paymentMethod === 'x402' ? 'pending' : 'true',\n ...(rl.remaining !== undefined ? { 'X-RateLimit-Remaining': String(rl.remaining) } : {}),\n },\n })\n })\n\n return gw\n}\n","import type { X402Config, MppConfig, ApiKeyInfo } from './types'\nimport type { NonceStore } from './nonce-store'\n\n/**\n * Verify x402 SpendAuth signature (EIP-712).\n * Returns the signer address (commitment) if valid, null otherwise.\n *\n * demoMode: true — accepts any well-formed header\n * shape after replay/expiry checks.\n * Tests + local dev only.\n * verifySigner present — production verification path.\n * neither — rejected by createAgentGateway and\n * by this function as defense-in-depth.\n */\nexport async function verifyX402(\n spendAuthHeader: string,\n config: X402Config,\n nonceStore?: NonceStore,\n): Promise<string | null> {\n try {\n const raw = JSON.parse(spendAuthHeader)\n if (!raw.commitment || !raw.signature || !raw.amount) return null\n if (raw.operator?.toLowerCase() !== config.operatorAddress.toLowerCase()) return null\n\n const amount = BigInt(raw.amount)\n const nonce = BigInt(raw.nonce)\n const expiry = BigInt(raw.expiry)\n\n // Reject expired payments\n if (expiry < BigInt(Math.floor(Date.now() / 1000))) return null\n\n // Reject zero-amount payments\n if (amount <= 0n) return null\n\n // Reject replayed nonces\n const nonceKey = `${raw.commitment}:${nonce.toString()}`\n if (nonceStore) {\n if (await nonceStore.hasSeen(nonceKey)) return null\n // Mark seen with TTL matching the expiry window (max 1 hour)\n const ttl = Math.min(Number(expiry) - Math.floor(Date.now() / 1000), 3600)\n await nonceStore.markSeen(nonceKey, Math.max(ttl, 60))\n }\n\n if (config.verifySigner) {\n const verified = await config.verifySigner(raw)\n if (!verified) return null\n } else if (!config.demoMode) {\n return null\n }\n\n return raw.commitment\n } catch {\n return null\n }\n}\n\n/**\n * Verify MPP (Machine Payments Protocol) Authorization: Payment header.\n *\n * MPP uses `Authorization: Payment <method> <credential>` format where\n * the credential is a base64url-encoded JSON wrapping the same EIP-3009\n * payment payload that x402 uses. This means existing x402 wallets work\n * unchanged over the MPP wire format.\n *\n * Returns the signer address if valid, null otherwise.\n * In demo mode, accepts any well-formed Payment header.\n */\nexport async function verifyMpp(\n authHeader: string,\n _config: MppConfig,\n x402Config: X402Config,\n): Promise<string | null> {\n // MPP format: \"Payment <method> <base64url-credential>\"\n const match = authHeader.match(/^Payment\\s+(\\S+)\\s+(\\S+)$/i)\n if (!match) return null\n\n const [, , credentialB64] = match\n\n try {\n // Decode base64url credential → JSON with the same EIP-3009 payload\n const decoded = Buffer.from(credentialB64, 'base64url').toString('utf-8')\n const credential = JSON.parse(decoded)\n\n // The credential payload wraps the same fields x402 uses\n const payload = credential.payload ?? credential\n if (!payload.commitment && !payload.from) return null\n\n // Validate operator match (same as x402)\n const operator = payload.operator ?? payload.to\n if (operator && operator.toLowerCase() !== x402Config.operatorAddress.toLowerCase()) return null\n\n // Validate bigint fields if present\n if (payload.amount) BigInt(payload.amount)\n if (payload.nonce) BigInt(payload.nonce)\n\n return payload.commitment ?? payload.from ?? null\n } catch {\n return null\n }\n}\n\n/**\n * Default API key verifier — accepts any `sk_agent_*` key (demo mode).\n * Override in GatewayConfig.verifyApiKey for production.\n */\nexport async function defaultVerifyApiKey(\n authHeader: string,\n): Promise<ApiKeyInfo | null> {\n if (!authHeader.startsWith('Bearer sk_agent_')) return null\n const key = authHeader.slice(7)\n return {\n keyId: key.slice(0, 16),\n consumerId: `apikey:${key.slice(0, 16)}`,\n }\n}\n","import type { ChatMessage } from './types'\n\n// --- Injection detection patterns ---\n\nconst INJECTION_PATTERNS = [\n // Direct instruction override\n /ignore\\s+(all\\s+)?(previous|prior|above|earlier)\\s+(instructions?|prompts?|rules?|directives?)/i,\n /disregard\\s+(all\\s+)?(previous|prior|system)/i,\n /forget\\s+(everything|all|your)\\s+(previous|instructions?|training)/i,\n // Role assumption\n /you\\s+are\\s+now\\s+(a|an|the)\\s+/i,\n /pretend\\s+(you\\s+are|to\\s+be)\\s+/i,\n /act\\s+as\\s+(if\\s+you\\s+are|a|an|the)\\s+/i,\n /new\\s+instructions?:/i,\n /\\[system\\]/i,\n /\\[INST\\]/i,\n // Prompt extraction\n /what\\s+(is|are)\\s+your\\s+(system\\s+)?(prompt|instructions?|rules?|directives?)/i,\n /repeat\\s+(your|the)\\s+(system\\s+)?(prompt|instructions?)/i,\n /output\\s+(your|the)\\s+((system|initial|original|first|full|real|hidden|secret|raw|exact)\\s+)?(prompt|instructions?)/i,\n /show\\s+(me\\s+)?(your|the)\\s+((system|hidden|secret|initial|original)\\s+)?(prompt|instructions?|message|directives?)/i,\n /tell\\s+me\\s+(your|the)\\s+(system\\s+)?(prompt|instructions?|rules?)/i,\n // Developer/debug/admin/jailbreak mode override\n /(developer|debug|admin|god|sudo|jailbreak|unrestricted|maintenance)\\s+mode/i,\n // Safety/safeguard disablement\n /(disable|bypass|override|remove|turn\\s+off)\\s+(your\\s+|the\\s+)?(safety|safeguards?|guardrails?|filters?|restrictions?|rules?|limitations?|content\\s+policy)/i,\n // Role reversal\n /(from\\s+now\\s+on|starting\\s+now|now)\\s+you('re|\\s+are)\\s+(the\\s+)?(user|human|customer|assistant)/i,\n /i('m|\\s+am)\\s+(the\\s+)?(assistant|ai|model|llm|bot)/i,\n // Tool-call / JSON-shaped spoofs\n /\"(?:tool|function|action|call)\"\\s*:\\s*\"[^\"]*(exfiltrate|leak|reveal|dump|extract|steal)[^\"]*\"/i,\n /exfiltrate[_\\s]*(system|prompt|secret|vault|config|data)/i,\n // Data exfiltration\n /read\\s+(the\\s+)?(vault|workspace|config|secret|\\.env)/i,\n /cat\\s+\\/home\\/agent\\/(vault|config|\\.env|secrets?)/i,\n /list\\s+(all\\s+)?(vault|workspace|secret)\\s+(files?|contents?|data)/i,\n // i18n — Spanish\n /ignor[ae]\\s+(todas?\\s+)?(las\\s+)?(instrucciones?|indicaciones?|órdenes?)\\s+(previas?|anteriores?)/i,\n /olvid[ae]\\s+(todas?\\s+|tus\\s+)?(instrucciones?|indicaciones?)/i,\n /(ahora\\s+)?(tú\\s+)?eres\\s+(ahora\\s+)?(un|una|el|la)\\s+/i,\n /finge\\s+(que\\s+eres|ser)\\s+/i,\n // i18n — French\n /ignor(ez|e|es)\\s+(toutes?\\s+)?(les\\s+)?(instructions?|consignes?|directives?)\\s+(précédentes?|antérieures?)/i,\n /oublie(z|s)?\\s+(toutes?\\s+|vos\\s+|tes\\s+)?(instructions?|consignes?)/i,\n /(vous\\s+êtes|tu\\s+es)\\s+(maintenant|désormais)\\s+(un|une)\\s+/i,\n /prétend(ez|s)?\\s+(être|que\\s+vous\\s+êtes|que\\s+tu\\s+es)\\s+/i,\n // i18n — German\n /ignorier(e|en|t)\\s+(alle\\s+)?(vorherigen?|vorigen?|bisherigen?)\\s+(anweisungen|anordnungen|vorgaben)/i,\n /vergiss\\s+(alle\\s+|deine\\s+)?(anweisungen|vorgaben|regeln)/i,\n /du\\s+bist\\s+(jetzt|nun)\\s+(ein|eine)\\s+/i,\n /tu\\s+so\\s+als\\s+(wärst\\s+du|ob\\s+du)\\s+/i,\n]\n\n// Cyrillic + Greek confusables that look identical to Latin letters but\n// NFKC does NOT collapse (they are distinct characters in Unicode, not\n// compatibility equivalents). A payload like `ignоre аll previоus\n// instructiоns` (o's are Cyrillic U+043E) evades the pattern set\n// entirely because `ignore` written with Cyrillic chars never matches\n// the Latin `[o]` in our regexes even after NFKC.\n//\n// Mapping derived from Unicode's confusables.txt, intentionally limited\n// to letters that routinely appear in English prompt-injection payloads.\n// Not a full confusable table — adding every entry would risk mangling\n// legitimate Russian/Greek content that happens to contain these\n// letters. Our INJECTION_PATTERNS and redaction keywords are English\n// ASCII, so collapsing to ASCII only touches payloads impersonating\n// those tokens.\nconst HOMOGLYPHS: Record<string, string> = {\n 'а': 'a', 'е': 'e', 'о': 'o', 'р': 'p',\n 'с': 'c', 'у': 'y', 'х': 'x', 'і': 'i',\n 'ј': 'j', 'ѕ': 's', 'һ': 'h',\n 'А': 'A', 'В': 'B', 'Е': 'E', 'К': 'K',\n 'М': 'M', 'Н': 'H', 'О': 'O', 'Р': 'P',\n 'С': 'C', 'Т': 'T', 'Х': 'X', 'І': 'I',\n 'Ј': 'J', 'Ѕ': 'S',\n 'α': 'a', 'ε': 'e', 'ο': 'o', 'ρ': 'p',\n 'υ': 'y', 'ν': 'v', 'ι': 'i', 'τ': 't',\n 'κ': 'k', 'χ': 'x', 'η': 'n', 'μ': 'u',\n 'Α': 'A', 'Β': 'B', 'Ε': 'E', 'Ζ': 'Z',\n 'Η': 'H', 'Ι': 'I', 'Κ': 'K', 'Μ': 'M',\n 'Ν': 'N', 'Ο': 'O', 'Ρ': 'P', 'Τ': 'T',\n 'Χ': 'X', 'Υ': 'Y',\n}\n\nfunction collapseHomoglyphs(text: string): string {\n let out = ''\n for (const ch of text) out += HOMOGLYPHS[ch] ?? ch\n return out\n}\n\n// Unicode normalization for REDACTION.\n//\n// Strip zero-widths entirely (no space substitute) so cross-character\n// elisions like \"v​aul​t/secret\" → \"vault/secret\" are matched\n// by the redaction keyword pass. NFKC collapses compatibility chars\n// (fullwidth `ignore` → `ignore`). collapseHomoglyphs catches the\n// Cyrillic/Greek lookalikes NFKC leaves alone.\nfunction normalizeUnicode(text: string): string {\n return collapseHomoglyphs(\n text.replace(/[\\u200B-\\u200F\\u2028-\\u202F\\u2060\\uFEFF]/g, '').normalize('NFKC'),\n )\n}\n\n// Unicode normalization for DETECTION.\n//\n// Two adversarial concerns diverge from redaction:\n//\n// 1. Between-word zero-widths (\"ignore​all​previous\") — the\n// attacker separates tokens with ZWSP so \\s+-anchored regexes do\n// not fire. Strip-then-detect glues the tokens into one\n// unmatchable word; replace ZWSP with a real SPACE instead so\n// /ignore\\s+all\\s+previous/ fires.\n// 2. Punctuation noise (\"Ignore,,, all...previous!!!\") — collapse\n// punctuation runs to a single space. Preserve `[` `]` because\n// literal `[system]` / `[INST]` are first-class injection signals\n// we match directly.\nfunction normalizeForDetection(text: string): string {\n const spaced = text\n .replace(/[\\u200B\\u200C\\uFEFF]/g, ' ')\n .replace(/[\\u200D\\u200E\\u200F\\u2028-\\u202F\\u2060]/g, '')\n .normalize('NFKC')\n return collapseHomoglyphs(spaced)\n .replace(/[(){}<>,.!?:;\"`~]+/g, ' ')\n .replace(/\\s+/g, ' ')\n}\n\n// Third pass: also collapse square brackets. Literal `[system]` /\n// `[INST]` patterns are matched against the other two passes (which\n// preserve brackets); this pass catches the OPPOSITE evasion where\n// the attacker wraps tokens in brackets to split them for \\s+-anchored\n// regexes — e.g. `(ignore) [all] {previous} <instructions>` should\n// still match the baseline pattern.\nfunction normalizeForDetectionNoBrackets(text: string): string {\n return normalizeForDetection(text)\n .replace(/[\\[\\]]+/g, ' ')\n .replace(/\\s+/g, ' ')\n}\n\n/**\n * Detect prompt injection attempts.\n * Returns array of matched pattern descriptions, empty if clean.\n *\n * Patterns run against TWO normalizations so both within-word and\n * between-word zero-width evasions are caught, and so `[system]` /\n * `[INST]` literals are not lost to punctuation-collapse:\n * - normalizeUnicode — strips zero-widths, preserves brackets\n * - normalizeForDetection — spaces zero-widths, collapses punct\n */\nexport function detectInjection(content: string): string[] {\n const stripNorm = normalizeUnicode(content)\n const detectNorm = normalizeForDetection(content)\n const noBracketNorm = normalizeForDetectionNoBrackets(content)\n const matches: string[] = []\n\n for (const pattern of INJECTION_PATTERNS) {\n if (pattern.test(stripNorm) || pattern.test(detectNorm) || pattern.test(noBracketNorm)) {\n matches.push(pattern.source.slice(0, 60))\n }\n }\n\n // Check for base64-encoded injection attempts in either normalization.\n for (const norm of [stripNorm, detectNorm]) {\n const b64Matches = norm.match(/[A-Za-z0-9+/]{40,}={0,2}/g)\n if (!b64Matches) continue\n for (const b64 of b64Matches) {\n try {\n const decoded = atob(b64)\n if (INJECTION_PATTERNS.some(p => p.test(decoded))) {\n if (!matches.includes('base64-encoded injection')) {\n matches.push('base64-encoded injection')\n }\n }\n } catch { /* not valid b64 */ }\n }\n }\n\n return matches\n}\n\n/**\n * Security boundary — filter consumer messages before forwarding to agent.\n *\n * Defense in depth:\n * 1. Strip system messages (consumers cannot set system prompt)\n * 2. Normalize Unicode (collapse homoglyphs, remove zero-width chars)\n * 3. Detect injection patterns (instruction override, prompt extraction, data exfil)\n * 4. Redact sensitive keywords\n * 5. Cap message length\n *\n * Returns filtered messages and any injection warnings detected.\n */\nexport function filterConsumerMessages(\n messages: ChatMessage[],\n maxLength = 8000,\n): ChatMessage[] {\n return messages\n .filter((m) => m.role !== 'system')\n .map((m) => {\n const normalized = normalizeUnicode(m.content)\n const redacted = normalized\n .replace(/\\b(vault|workspace|owner|admin|secret|\\.env|config\\.json)[\\s/:][^\\s]*/gi, '[REDACTED]')\n .slice(0, maxLength)\n return { role: m.role, content: redacted }\n })\n}\n\n/**\n * Filter consumer messages with injection detection.\n * Returns { messages, injectionWarnings }.\n * If injectionWarnings is non-empty, the gateway should log and optionally reject.\n */\nexport function filterConsumerMessagesStrict(\n messages: ChatMessage[],\n maxLength = 8000,\n): { messages: ChatMessage[]; injectionWarnings: string[] } {\n const filtered = filterConsumerMessages(messages, maxLength)\n const allContent = filtered.map(m => m.content).join(' ')\n const injectionWarnings = detectInjection(allContent)\n return { messages: filtered, injectionWarnings }\n}\n\n/**\n * Redact system prompt content from agent output.\n * Prevents the agent from leaking its own instructions in responses.\n *\n * Strategy: if any chunk of the system prompt appears verbatim (>40 chars)\n * in the output, replace it with [REDACTED].\n */\nexport function redactSystemPromptFromOutput(\n output: string,\n systemPrompt: string | undefined,\n): string {\n if (!systemPrompt || systemPrompt.length < 40) return output\n\n // Split system prompt into meaningful chunks (sentences or lines)\n const chunks = systemPrompt\n .split(/[.\\n]/)\n .map(s => s.trim())\n .filter(s => s.length >= 40)\n\n let redacted = output\n for (const chunk of chunks) {\n // Case-insensitive substring match\n const idx = redacted.toLowerCase().indexOf(chunk.toLowerCase())\n if (idx >= 0) {\n redacted = redacted.slice(0, idx) + '[REDACTED — system instructions]' + redacted.slice(idx + chunk.length)\n }\n }\n\n return redacted\n}\n","/**\n * Observability hook surface.\n *\n * Consumers implement GatewayObserver to wire the gateway into their existing\n * telemetry stack (Langfuse, OTEL, structured logs, Prometheus, etc.) without\n * the gateway itself depending on any of those libraries.\n *\n * Every event carries a requestId so downstream metrics can correlate the\n * payment verification, sandbox execution, and settlement for one request.\n * When no observer is configured, the gateway stays silent.\n */\n\nimport type { PaymentMethod, GatewayUsageEvent } from './types'\n\nexport interface RequestContext {\n requestId: string\n agentSlug: string\n startMs: number\n}\n\nexport interface AuthFailureReason {\n method: 'x402' | 'mpp' | 'apikey' | 'none'\n code: string\n httpStatus: number\n}\n\nexport interface GatewayObserver {\n /** Called at the start of every chat completions POST. */\n onRequestStart?: (ctx: RequestContext) => void | Promise<void>\n\n /** Called when a payment method has been successfully verified. */\n onPaymentVerified?: (ctx: RequestContext, info: {\n method: PaymentMethod\n consumerId: string\n keyId?: string\n }) => void | Promise<void>\n\n /** Called when auth fails — every branch. */\n onAuthFailure?: (ctx: RequestContext, reason: AuthFailureReason) => void | Promise<void>\n\n /** Called when a consumer hits the rate limit. */\n onRateLimited?: (ctx: RequestContext, info: {\n consumerId: string\n retryAfterSeconds: number\n }) => void | Promise<void>\n\n /** Called when the request body exceeds the 64KB limit. */\n onBodyTooLarge?: (ctx: RequestContext, contentLength: number) => void | Promise<void>\n\n /**\n * Called when prompt-injection patterns are detected.\n * `blocked` is true when blockInjection config is on and the request was\n * rejected; false when the patterns were logged but the request proceeded.\n */\n onInjectionDetected?: (ctx: RequestContext, info: {\n consumerId: string\n patterns: string[]\n blocked: boolean\n }) => void | Promise<void>\n\n /** Called after a successful stream completes and recordUsage has fired. */\n onRequestComplete?: (ctx: RequestContext, usage: GatewayUsageEvent) => void | Promise<void>\n\n /** Called when the sandbox throws. The error message is pre-scrubbed. */\n onStreamError?: (ctx: RequestContext, info: {\n consumerId: string\n errorMessage: string\n }) => void | Promise<void>\n\n /** Called when settlement fails. Payment already occurred; this is async bookkeeping. */\n onSettlementError?: (ctx: RequestContext, info: {\n consumerId: string\n method: PaymentMethod\n errorMessage: string\n }) => void | Promise<void>\n}\n\n// ---------------------------------------------------------------------------\n// Convenience implementations\n// ---------------------------------------------------------------------------\n\n/**\n * Structured-log observer. Emits one JSON line per event on the `log` function.\n * Default sink: console.log. Production consumers usually pipe their own\n * structured logger (pino, winston, the cf Logs binding).\n *\n * Usage:\n * new ConsoleObserver(({ level, event, ...rest }) => logger.info({ event, ...rest }))\n */\nexport class ConsoleObserver implements GatewayObserver {\n constructor(\n private readonly log: (entry: Record<string, unknown>) => void = (e) => console.log(JSON.stringify(e)),\n ) {}\n\n private emit(level: 'info' | 'warn' | 'error', event: string, ctx: RequestContext, rest: Record<string, unknown> = {}) {\n this.log({\n level,\n event,\n time: new Date().toISOString(),\n requestId: ctx.requestId,\n agentSlug: ctx.agentSlug,\n durationMs: Date.now() - ctx.startMs,\n ...rest,\n })\n }\n\n onRequestStart(ctx: RequestContext) { this.emit('info', 'gateway.request.start', ctx) }\n onPaymentVerified(ctx: RequestContext, info: { method: PaymentMethod; consumerId: string; keyId?: string }) {\n this.emit('info', 'gateway.payment.verified', ctx, info)\n }\n onAuthFailure(ctx: RequestContext, reason: AuthFailureReason) {\n this.emit('warn', 'gateway.auth.failure', ctx, reason as unknown as Record<string, unknown>)\n }\n onRateLimited(ctx: RequestContext, info: { consumerId: string; retryAfterSeconds: number }) {\n this.emit('warn', 'gateway.rate_limit', ctx, info)\n }\n onBodyTooLarge(ctx: RequestContext, contentLength: number) {\n this.emit('warn', 'gateway.body_too_large', ctx, { contentLength })\n }\n onInjectionDetected(ctx: RequestContext, info: { consumerId: string; patterns: string[]; blocked: boolean }) {\n this.emit('warn', 'gateway.injection', ctx, info)\n }\n onRequestComplete(ctx: RequestContext, usage: GatewayUsageEvent) {\n this.emit('info', 'gateway.request.complete', ctx, usage as unknown as Record<string, unknown>)\n }\n onStreamError(ctx: RequestContext, info: { consumerId: string; errorMessage: string }) {\n this.emit('error', 'gateway.stream.error', ctx, info)\n }\n onSettlementError(ctx: RequestContext, info: { consumerId: string; method: PaymentMethod; errorMessage: string }) {\n this.emit('error', 'gateway.settlement.error', ctx, info)\n }\n}\n\n/**\n * Compose multiple observers into one. Errors in any individual observer\n * don't break the others (fire-and-forget telemetry).\n */\nexport class CompositeObserver implements GatewayObserver {\n constructor(private readonly observers: GatewayObserver[]) {}\n\n private async fanOut<K extends keyof GatewayObserver>(event: K, ...args: unknown[]): Promise<void> {\n for (const obs of this.observers) {\n const fn = obs[event] as ((...a: unknown[]) => void | Promise<void>) | undefined\n if (!fn) continue\n try {\n await fn.apply(obs, args)\n } catch (err) {\n console.warn(`[agent-gateway] observer ${event} threw:`, err instanceof Error ? err.message : err)\n }\n }\n }\n\n onRequestStart = (ctx: RequestContext) => this.fanOut('onRequestStart', ctx)\n onPaymentVerified = (ctx: RequestContext, info: Parameters<Required<GatewayObserver>['onPaymentVerified']>[1]) =>\n this.fanOut('onPaymentVerified', ctx, info)\n onAuthFailure = (ctx: RequestContext, reason: AuthFailureReason) =>\n this.fanOut('onAuthFailure', ctx, reason)\n onRateLimited = (ctx: RequestContext, info: Parameters<Required<GatewayObserver>['onRateLimited']>[1]) =>\n this.fanOut('onRateLimited', ctx, info)\n onBodyTooLarge = (ctx: RequestContext, contentLength: number) =>\n this.fanOut('onBodyTooLarge', ctx, contentLength)\n onInjectionDetected = (ctx: RequestContext, info: Parameters<Required<GatewayObserver>['onInjectionDetected']>[1]) =>\n this.fanOut('onInjectionDetected', ctx, info)\n onRequestComplete = (ctx: RequestContext, usage: GatewayUsageEvent) =>\n this.fanOut('onRequestComplete', ctx, usage)\n onStreamError = (ctx: RequestContext, info: Parameters<Required<GatewayObserver>['onStreamError']>[1]) =>\n this.fanOut('onStreamError', ctx, info)\n onSettlementError = (ctx: RequestContext, info: Parameters<Required<GatewayObserver>['onSettlementError']>[1]) =>\n this.fanOut('onSettlementError', ctx, info)\n}\n\n/**\n * Generate a request-id. Crypto-random 16 bytes, hex-encoded with an `req_` prefix.\n * Works in Workers, Node, and browsers — all have globalThis.crypto.\n */\nexport function generateRequestId(): string {\n const bytes = new Uint8Array(16)\n globalThis.crypto.getRandomValues(bytes)\n const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, '0')).join('')\n return `req_${hex}`\n}\n"],"mappings":"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","names":[]}
@@ -1,362 +0,0 @@
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
- * CLI harness backend that runs this agent inside the sandbox sidecar.
155
- *
156
- * When set, the host's `getSandbox()` SHOULD return a `SandboxBox`
157
- * whose `streamPrompt` POSTs to the sidecar's
158
- * `POST /agent/invoke/chat/completions` endpoint with
159
- * `model: "<harness>/<harnessModel>"` — that endpoint runs the
160
- * harness against the sandbox workspace and streams OpenAI-shape
161
- * `chat.completion.chunk` frames back.
162
- *
163
- * When unset (legacy / template mode), the host's `streamPrompt`
164
- * falls back to the template's own `/api/chat/completions` (proxied
165
- * via the sidecar's `/agent/invoke`).
166
- *
167
- * Known harnesses (registered in agent-dev-container's
168
- * cli-agent-bindings.ts): opencode, claude-code, codex, kimi-code,
169
- * amp, factory-droids, pi, hermes, openclaw, forge, acp, cursor.
170
- * Aliases the sidecar canonicalizes: claude → claude-code,
171
- * kimi → kimi-code, factory → factory-droids.
172
- */
173
- harness?: string;
174
- /**
175
- * Model identifier to pass after the harness in the
176
- * `<harness>/<model>` slash form. Format is harness-specific:
177
- * claude-code: "sonnet", "opus", or a versioned id like
178
- * "claude-sonnet-4-20250514"
179
- * opencode: "anthropic/claude-sonnet-4-5", "openai/gpt-4o", …
180
- * (opencode embeds provider before model)
181
- * codex: "gpt-5-codex"
182
- * kimi-code: "kimi-for-coding"
183
- *
184
- * Only meaningful when `harness` is set; ignored otherwise.
185
- */
186
- harnessModel?: string;
187
- }
188
- type PaymentMethod = 'x402' | 'mpp' | 'apikey' | 'none';
189
- interface X402Config {
190
- /** Ethereum operator address for SpendAuth verification */
191
- operatorAddress: string;
192
- /** Blockchain network ID (default: 3799) */
193
- chainId: number;
194
- /** ShieldedCredits contract address */
195
- creditsAddress?: string;
196
- /** RPC URL for on-chain verification (optional, demo mode skips this) */
197
- rpcUrl?: string;
198
- /** Demo mode: skip signature verification (default: false). NEVER enable in production. */
199
- demoMode?: boolean;
200
- /** Production signer verification. Called with the raw SpendAuth payload. Return true if signature is valid. */
201
- verifySigner?: (payload: Record<string, unknown>) => Promise<boolean>;
202
- }
203
- interface MppConfig {
204
- /** MPP realm (e.g. "agents.tangle.tools") */
205
- realm: string;
206
- /** MPP method name (default: "blueprintevm") */
207
- method?: string;
208
- }
209
- interface PaymentResult {
210
- method: PaymentMethod;
211
- consumerId: string;
212
- /**
213
- * Per-request id (matches `RequestContext.requestId` from the
214
- * Observer pattern). Threaded into `settlePayment` so callers can
215
- * attribute revenue deterministically per-request without scanning
216
- * a FIFO queue keyed by consumerId — when the same consumer is
217
- * paying for two concurrent requests against agents A and B, FIFO
218
- * misroutes one. With `requestId` the call site can write a
219
- * settlement row keyed exactly to the request that earned it.
220
- */
221
- requestId: string;
222
- }
223
- interface ApiKeyInfo {
224
- keyId: string;
225
- consumerId: string;
226
- /** Scopes this key is authorized for (e.g. ["chat", "forms"]) */
227
- scopes?: string[];
228
- /** Per-key rate limit override (requests per minute). If set, overrides global rate limit. */
229
- rateLimitPerMinute?: number;
230
- /** Per-key daily limit override. */
231
- dailyLimit?: number;
232
- }
233
- interface GatewayUsageEvent {
234
- /**
235
- * Per-request id (matches `RequestContext.requestId`). Lets
236
- * `recordUsage` correlate the usage row to the same request that
237
- * `settlePayment` settles, observability hooks observe, and
238
- * `onRequestComplete` reports — without re-deriving from a
239
- * synthetic key. Required field as of 0.4.0; the gateway always has
240
- * it in scope at the recordUsage call site.
241
- */
242
- requestId: string;
243
- agentId: string;
244
- agentSlug: string;
245
- consumerId: string;
246
- paymentMethod: PaymentMethod;
247
- inputTokens: number;
248
- outputTokens: number;
249
- totalCostUsd: number;
250
- ownerEarnedUsd: number;
251
- platformFeeUsd: number;
252
- durationMs: number;
253
- }
254
- interface SandboxStreamEvent {
255
- type?: string;
256
- data?: {
257
- part?: {
258
- type?: string;
259
- text?: string;
260
- };
261
- delta?: string;
262
- finalText?: string;
263
- };
264
- }
265
- interface SandboxBox {
266
- streamPrompt(message: string, opts?: {
267
- sessionId?: string;
268
- systemPrompt?: string;
269
- }): AsyncIterable<SandboxStreamEvent>;
270
- }
271
- interface GatewayConfig {
272
- /** Resolve agent metadata by slug. Return null if not found or not published. */
273
- resolveAgent: (slug: string) => Promise<AgentMeta | null>;
274
- /** Get a sandbox instance for the agent. Called after payment is verified. */
275
- getSandbox: (agent: AgentMeta) => Promise<SandboxBox>;
276
- /**
277
- * Optional host authorization hook fired after payment verification
278
- * and before sandbox resolution. Use it for per-agent allowlists,
279
- * per-consumer quotas, contract scope checks, and instance ownership.
280
- */
281
- authorizeConsumer?: (agent: AgentMeta, consumer: {
282
- method: PaymentMethod;
283
- consumerId: string;
284
- keyId?: string;
285
- requestId: string;
286
- }) => Promise<{
287
- allow: true;
288
- } | {
289
- allow: false;
290
- reason: string;
291
- code: string;
292
- }>;
293
- /** Record a usage event after request completes. */
294
- recordUsage: (event: GatewayUsageEvent) => Promise<void>;
295
- /** x402 payment configuration */
296
- x402: X402Config;
297
- /** MPP (Machine Payments Protocol) configuration. If provided, gateway accepts Authorization: Payment headers. */
298
- mpp?: MppConfig;
299
- /**
300
- * Verify an API key. Return key info if valid, null if invalid.
301
- * Default: accepts any `sk_agent_*` key (demo mode).
302
- */
303
- verifyApiKey?: (authHeader: string) => Promise<ApiKeyInfo | null>;
304
- /**
305
- * Settle payment after successful response.
306
- * For x402: call ShieldedCredits.claimPayment()
307
- * For API key: deduct from spending limit
308
- * Default: no-op (demo mode).
309
- */
310
- settlePayment?: (payment: PaymentResult, cost: number) => Promise<void>;
311
- /** Base URL for API key purchase links (e.g. "https://film.tangle.tools") */
312
- baseUrl?: string;
313
- /** Max message length in chars (default: 8000) */
314
- maxMessageLength?: number;
315
- /** Required scope for chat endpoint (default: "chat"). API keys must include this scope. */
316
- requiredScope?: string;
317
- /** Block requests with detected injection patterns (default: false — log only) */
318
- blockInjection?: boolean;
319
- /** Rate limiting config. Default: 60 requests per 60 seconds per consumer. */
320
- rateLimit?: {
321
- limit: number;
322
- windowSeconds: number;
323
- };
324
- /** Custom rate limit store (default: in-memory). Use KV-backed for Workers. */
325
- rateLimitStore?: RateLimitStore;
326
- /** Nonce replay protection store (default: in-memory). Rejects reused x402 nonces. */
327
- nonceStore?: NonceStore;
328
- /**
329
- * Observability hook. When set, the gateway emits typed events for request
330
- * lifecycle, auth outcomes, rate limits, injection detection, usage, errors,
331
- * and settlement failures. See ./observer.ts for the interface and
332
- * ConsoleObserver / CompositeObserver implementations.
333
- */
334
- observer?: GatewayObserver;
335
- }
336
- interface ChatMessage {
337
- role: 'system' | 'user' | 'assistant' | 'tool';
338
- content: string;
339
- }
340
- interface ChatCompletionRequest {
341
- model?: string;
342
- messages: ChatMessage[];
343
- stream?: boolean;
344
- temperature?: number;
345
- max_tokens?: number;
346
- }
347
- interface ChatCompletionChunk {
348
- id: string;
349
- object: 'chat.completion.chunk';
350
- created: number;
351
- model: string;
352
- choices: Array<{
353
- index: number;
354
- delta: {
355
- content?: string;
356
- role?: string;
357
- };
358
- finish_reason: string | null;
359
- }>;
360
- }
361
-
362
- 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 };