@tangle-network/agent-gateway 0.8.0 → 0.8.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 +7 -1
- package/dist/{chunk-MP6IIAIA.js → chunk-C7Z2BRYV.js} +66 -24
- package/dist/chunk-C7Z2BRYV.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/middleware.d.ts +2 -2
- package/dist/middleware.js +1 -1
- package/dist/{types-BHISsm7D.d.ts → types-oQ58UakD.d.ts} +34 -3
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
- package/src/dispatch-authorization.ts +43 -12
- package/src/dispatch-sandbox.ts +3 -1
- package/src/dispatch-types.ts +3 -0
- package/src/index.ts +2 -0
- package/src/middleware.ts +35 -12
- package/src/types.ts +44 -3
- package/src/verify.ts +7 -0
- package/dist/chunk-MP6IIAIA.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { createAgentGateway } from './middleware.js';
|
|
2
|
-
import { G as GatewayConfig, P as PaymentOperationRecoveryResult, a as PaymentRecoveryRecord, M as MppAuthenticatedCredential, A as ApiKeyInfo, b as MppConfig, X as X402Config, c as PaymentRecoveryStore, S as SqlAdapter, C as ChatMessage } from './types-
|
|
3
|
-
export { d as A2A_ERROR_CODES, e as AgentCapabilities, f as AgentCard, g as AgentCardAuthentication, h as AgentMeta, i as AgentProvider, j as AgentSkill, k as
|
|
2
|
+
import { G as GatewayConfig, P as PaymentOperationRecoveryResult, a as PaymentRecoveryRecord, M as MppAuthenticatedCredential, A as ApiKeyInfo, b as MppConfig, X as X402Config, c as PaymentRecoveryStore, S as SqlAdapter, C as ChatMessage } from './types-oQ58UakD.js';
|
|
3
|
+
export { d as A2A_ERROR_CODES, e as AgentCapabilities, f as AgentCard, g as AgentCardAuthentication, h as AgentMeta, i as AgentProvider, j as AgentSkill, k as ApiKeyGatewayConfig, l as Artifact, m as ChatCompletionChunk, n as ChatCompletionRequest, o as CreateAgentGatewayConfig, D as D1DatabaseLike, p as D1StmtLike, q as DataPart, F as FilePart, I as InMemoryPushNotificationStore, r as InMemoryTaskStore, J as JSONRPCErrorResponse, s as JSONRPCRequest, t as JSONRPCResponse, u as JSONRPCSuccessResponse, v as MPP_CHARGE_PROTOCOL_VERSION, w as MemoryPaymentOperations, x as MemoryPaymentOperationsOptions, y as MemoryPaymentRecoveryStore, z as Message, B as MessageSendParams, E as MppChargeLifecycle, H as MppChargeOperation, K as MppChargeOperationState, L as MppChargeRecoveryResult, N as MppChargeRequest, O as PAYMENT_PROTOCOL_VERSION, Q as PAYMENT_RECOVERY_VERSION, R as Part, T as PaymentAuthorizationContext, U as PaymentOperation, V as PaymentOperationNotFound, W as PaymentOperationState, Y as PaymentOperations, Z as PaymentRecoveryAttribution, _ as PaymentRecoveryConfig, $ as PaymentRecoveryFenceError, a0 as PaymentRecoveryState, a1 as PaymentRecoveryTarget, a2 as PaymentResult, a3 as PaymentSettlementInput, a4 as PushDeliveryResult, a5 as PushNotificationAuthentication, a6 as PushNotificationConfig, a7 as PushNotificationDeliveryOptions, a8 as PushNotificationStore, a9 as SandboxBox, aa as SandboxStreamEvent, ab as SqlPushNotificationStore, ac as SqlTaskStore, ad as StreamingEvent, ae as Task, af as TaskArtifactUpdateEvent, ag as TaskIdParams, ah as TaskPushNotificationConfig, ai as TaskPushNotificationConfigGetParams, aj as TaskState, ak as TaskStatus, al as TaskStatusUpdateEvent, am as TaskStore, an as TextPart, ao as d1ToSqlAdapter, ap as deliverDemoPushNotifications, aq as deliverPushNotifications, ar as mppPaymentOperationId, as as validatePushNotificationUrl } from './types-oQ58UakD.js';
|
|
4
4
|
import { NonceStore } from './nonce-store.js';
|
|
5
5
|
export { AtomicKvNonceClaim, AtomicNonceStore, KvNonceStore, KvNonceStoreOptions, MemoryNonceStore, isAtomicNonceStore } from './nonce-store.js';
|
|
6
6
|
export { CompositeObserver, ConsoleObserver, generateRequestId } from './observer.js';
|
package/dist/index.js
CHANGED
package/dist/middleware.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as hono_types from 'hono/types';
|
|
2
2
|
import { Hono } from 'hono';
|
|
3
|
-
import {
|
|
3
|
+
import { o as CreateAgentGatewayConfig } from './types-oQ58UakD.js';
|
|
4
4
|
import './nonce-store.js';
|
|
5
5
|
import './rate-limit.js';
|
|
6
6
|
import './observer-types-A0RtA8uL.js';
|
|
@@ -15,7 +15,7 @@ import './observer-types-A0RtA8uL.js';
|
|
|
15
15
|
* GET /:slug/chat/completions — agent discovery metadata (Tangle-native shape)
|
|
16
16
|
* POST /:slug/chat/completions — OpenAI-compatible chat endpoint (paid)
|
|
17
17
|
*/
|
|
18
|
-
declare function createAgentGateway(inputConfig:
|
|
18
|
+
declare function createAgentGateway(inputConfig: CreateAgentGatewayConfig): Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
|
|
19
19
|
/** Public package-boundary version marker for durable payment operations. */
|
|
20
20
|
declare namespace createAgentGateway {
|
|
21
21
|
const paymentProtocolVersion: 2;
|
package/dist/middleware.js
CHANGED
|
@@ -939,11 +939,24 @@ interface SandboxBox {
|
|
|
939
939
|
signal?: AbortSignal;
|
|
940
940
|
}): AsyncIterable<SandboxStreamEvent>;
|
|
941
941
|
}
|
|
942
|
+
/** Authenticated request identity supplied when the host resolves a sandbox. */
|
|
943
|
+
interface GatewaySandboxContext {
|
|
944
|
+
consumerId: string;
|
|
945
|
+
paymentMethod: PaymentMethod;
|
|
946
|
+
keyInfo: ApiKeyInfo | null;
|
|
947
|
+
requestId: string;
|
|
948
|
+
messages: ChatMessage[];
|
|
949
|
+
/** Stable UI conversation id when `conversationMode` is `thread`. */
|
|
950
|
+
threadId?: string;
|
|
951
|
+
}
|
|
942
952
|
interface GatewayConfig {
|
|
943
953
|
/** Resolve agent metadata by slug. Return null if not found or not published. */
|
|
944
954
|
resolveAgent: (slug: string) => Promise<AgentMeta | null>;
|
|
945
|
-
/**
|
|
946
|
-
|
|
955
|
+
/**
|
|
956
|
+
* Get the agent execution adapter after payment is verified.
|
|
957
|
+
* Hosts that use agent-app can drive their normal persisted chat route here.
|
|
958
|
+
*/
|
|
959
|
+
getSandbox: (agent: AgentMeta, context?: GatewaySandboxContext) => Promise<SandboxBox>;
|
|
947
960
|
/**
|
|
948
961
|
* Optional host authorization hook fired after payment verification
|
|
949
962
|
* and before sandbox resolution. Use it for per-agent allowlists,
|
|
@@ -954,6 +967,8 @@ interface GatewayConfig {
|
|
|
954
967
|
consumerId: string;
|
|
955
968
|
keyId?: string;
|
|
956
969
|
requestId: string;
|
|
970
|
+
/** Requested stable conversation id, after syntax validation. */
|
|
971
|
+
threadId?: string;
|
|
957
972
|
}) => Promise<{
|
|
958
973
|
allow: true;
|
|
959
974
|
} | {
|
|
@@ -993,6 +1008,14 @@ interface GatewayConfig {
|
|
|
993
1008
|
settlePayment?: (payment: PaymentResult, cost: number) => Promise<void>;
|
|
994
1009
|
/** Base URL for API key purchase links (e.g. "https://film.tangle.tools") */
|
|
995
1010
|
baseUrl?: string;
|
|
1011
|
+
/** Public API key prefix shown by discovery. Defaults to `sk_agent_`. */
|
|
1012
|
+
apiKeyPrefix?: string;
|
|
1013
|
+
/**
|
|
1014
|
+
* `consumer` keeps the historical session per API consumer.
|
|
1015
|
+
* `thread` accepts `X-Tangle-Thread-Id` or creates one per request and returns
|
|
1016
|
+
* it in the response, so a host can display the same conversation.
|
|
1017
|
+
*/
|
|
1018
|
+
conversationMode?: 'consumer' | 'thread';
|
|
996
1019
|
/** Max message length in chars (default: 8000) */
|
|
997
1020
|
maxMessageLength?: number;
|
|
998
1021
|
/** Maximum output token request the gateway accepts. Defaults to 4096. */
|
|
@@ -1090,6 +1113,14 @@ interface GatewayConfig {
|
|
|
1090
1113
|
pushUrlValidator?: (url: URL) => boolean | Promise<boolean>;
|
|
1091
1114
|
};
|
|
1092
1115
|
}
|
|
1116
|
+
/** API-key-only gateway input. Payment transports stay disabled. */
|
|
1117
|
+
type ApiKeyGatewayConfig = Omit<GatewayConfig, 'mpp' | 'verifyApiKey' | 'x402'> & {
|
|
1118
|
+
mpp?: never;
|
|
1119
|
+
verifyApiKey: NonNullable<GatewayConfig['verifyApiKey']>;
|
|
1120
|
+
x402?: never;
|
|
1121
|
+
};
|
|
1122
|
+
/** Configuration accepted by `createAgentGateway`. */
|
|
1123
|
+
type CreateAgentGatewayConfig = GatewayConfig | ApiKeyGatewayConfig;
|
|
1093
1124
|
interface ChatMessage {
|
|
1094
1125
|
role: 'system' | 'user' | 'assistant' | 'tool';
|
|
1095
1126
|
content: string;
|
|
@@ -1116,4 +1147,4 @@ interface ChatCompletionChunk {
|
|
|
1116
1147
|
}>;
|
|
1117
1148
|
}
|
|
1118
1149
|
|
|
1119
|
-
export {
|
|
1150
|
+
export { PaymentRecoveryFenceError as $, type ApiKeyInfo as A, type MessageSendParams as B, type ChatMessage as C, type D1DatabaseLike as D, type MppChargeLifecycle as E, type FilePart as F, type GatewayConfig as G, type MppChargeOperation as H, InMemoryPushNotificationStore as I, type JSONRPCErrorResponse as J, type MppChargeOperationState as K, type MppChargeRecoveryResult as L, type MppAuthenticatedCredential as M, type MppChargeRequest as N, PAYMENT_PROTOCOL_VERSION as O, type PaymentOperationRecoveryResult as P, PAYMENT_RECOVERY_VERSION as Q, type Part as R, type SqlAdapter as S, type PaymentAuthorizationContext as T, type PaymentOperation as U, type PaymentOperationNotFound as V, type PaymentOperationState as W, type X402Config as X, type PaymentOperations as Y, type PaymentRecoveryAttribution as Z, type PaymentRecoveryConfig as _, type PaymentRecoveryRecord as a, type PaymentRecoveryState as a0, type PaymentRecoveryTarget as a1, type PaymentResult as a2, type PaymentSettlementInput as a3, type PushDeliveryResult as a4, type PushNotificationAuthentication as a5, type PushNotificationConfig as a6, type PushNotificationDeliveryOptions as a7, type PushNotificationStore as a8, type SandboxBox as a9, type SandboxStreamEvent as aa, SqlPushNotificationStore as ab, SqlTaskStore as ac, type StreamingEvent as ad, type Task as ae, type TaskArtifactUpdateEvent as af, type TaskIdParams as ag, type TaskPushNotificationConfig as ah, type TaskPushNotificationConfigGetParams as ai, type TaskState as aj, type TaskStatus as ak, type TaskStatusUpdateEvent as al, type TaskStore as am, type TextPart as an, d1ToSqlAdapter as ao, deliverDemoPushNotifications as ap, deliverPushNotifications as aq, mppPaymentOperationId as ar, validatePushNotificationUrl as as, type GatewaySandboxContext as at, type MppConfig as b, type PaymentRecoveryStore as c, A2A_ERROR_CODES as d, type AgentCapabilities as e, type AgentCard as f, type AgentCardAuthentication as g, type AgentMeta as h, type AgentProvider as i, type AgentSkill as j, type ApiKeyGatewayConfig as k, type Artifact as l, type ChatCompletionChunk as m, type ChatCompletionRequest as n, type CreateAgentGatewayConfig as o, type D1StmtLike as p, type DataPart as q, InMemoryTaskStore as r, type JSONRPCRequest as s, type JSONRPCResponse as t, type JSONRPCSuccessResponse as u, MPP_CHARGE_PROTOCOL_VERSION as v, MemoryPaymentOperations as w, type MemoryPaymentOperationsOptions as x, MemoryPaymentRecoveryStore as y, type Message as z };
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { h as AgentMeta, A as ApiKeyInfo,
|
|
1
|
+
export { h as AgentMeta, k as ApiKeyGatewayConfig, A as ApiKeyInfo, m as ChatCompletionChunk, n as ChatCompletionRequest, C as ChatMessage, o as CreateAgentGatewayConfig, G as GatewayConfig, at as GatewaySandboxContext, b as MppConfig, a2 as PaymentResult, a9 as SandboxBox, aa as SandboxStreamEvent, X as X402Config } from './types-oQ58UakD.js';
|
|
2
2
|
import './nonce-store.js';
|
|
3
3
|
import './rate-limit.js';
|
|
4
4
|
export { a as GatewayUsageEvent, P as PaymentMethod, c as SandboxExecutionBudget, S as SandboxUsageReceipt } from './observer-types-A0RtA8uL.js';
|
package/package.json
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
defaultVerifyApiKey,
|
|
20
20
|
isApiKeyAuthEnabled,
|
|
21
21
|
isMppAuthEnabled,
|
|
22
|
+
isX402AuthEnabled,
|
|
22
23
|
mppPaymentPayload,
|
|
23
24
|
mppPaymentCredential,
|
|
24
25
|
verifyMppCredential,
|
|
@@ -48,6 +49,18 @@ export async function authenticateAndGuard(
|
|
|
48
49
|
const ctx: RequestContext = { requestId, agentSlug: slug, startMs }
|
|
49
50
|
await state.obs?.onRequestStart?.(ctx)
|
|
50
51
|
|
|
52
|
+
let threadId: string | undefined
|
|
53
|
+
if (config.conversationMode === 'thread') {
|
|
54
|
+
const requestedThreadId = c.req.header('X-Tangle-Thread-Id')?.trim()
|
|
55
|
+
if (requestedThreadId && !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(requestedThreadId)) {
|
|
56
|
+
return c.json(
|
|
57
|
+
{ error: { message: 'Invalid X-Tangle-Thread-Id', type: 'invalid_request' } },
|
|
58
|
+
{ status: 400, headers: { 'X-Request-Id': requestId } },
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
threadId = requestedThreadId || requestId
|
|
62
|
+
}
|
|
63
|
+
|
|
51
64
|
const agent = await config.resolveAgent(slug)
|
|
52
65
|
if (!agent || !agent.enabled) {
|
|
53
66
|
return c.json({ error: { message: 'Agent not found', type: 'not_found' } }, 404)
|
|
@@ -83,10 +96,14 @@ export async function authenticateAndGuard(
|
|
|
83
96
|
messages,
|
|
84
97
|
state.maxLen,
|
|
85
98
|
)
|
|
86
|
-
const
|
|
99
|
+
const userMessages = filtered
|
|
87
100
|
.filter((m) => m.role === 'user')
|
|
88
|
-
|
|
89
|
-
|
|
101
|
+
// A thread-backed host already owns the persisted history. Send only the
|
|
102
|
+
// new turn to avoid storing the caller's full history as one new user row.
|
|
103
|
+
// Keep the historical concatenation for the default consumer session.
|
|
104
|
+
const userMessage = config.conversationMode === 'thread'
|
|
105
|
+
? userMessages[userMessages.length - 1]?.content ?? ''
|
|
106
|
+
: userMessages.map((m) => m.content).join('\n\n')
|
|
90
107
|
if (!userMessage) {
|
|
91
108
|
return c.json(
|
|
92
109
|
{ error: { message: 'No user message provided', type: 'invalid_request' } },
|
|
@@ -176,6 +193,12 @@ export async function authenticateAndGuard(
|
|
|
176
193
|
let mppPaymentIdentity: string | undefined
|
|
177
194
|
|
|
178
195
|
if (spendAuthHeader) {
|
|
196
|
+
if (!isX402AuthEnabled(config)) {
|
|
197
|
+
return c.json(
|
|
198
|
+
{ error: { message: 'x402 authentication is not configured', type: 'authentication_error' } },
|
|
199
|
+
{ status: 401, headers: { 'X-Request-Id': requestId } },
|
|
200
|
+
)
|
|
201
|
+
}
|
|
179
202
|
const signer = await verifyX402(
|
|
180
203
|
spendAuthHeader,
|
|
181
204
|
config.x402,
|
|
@@ -301,7 +324,8 @@ export async function authenticateAndGuard(
|
|
|
301
324
|
code: 'payment_required',
|
|
302
325
|
httpStatus: 402,
|
|
303
326
|
})
|
|
304
|
-
const methods: string[] = [
|
|
327
|
+
const methods: string[] = []
|
|
328
|
+
if (isX402AuthEnabled(config)) methods.push('x402')
|
|
305
329
|
if (isMppAuthEnabled(config)) methods.push('mpp')
|
|
306
330
|
if (isApiKeyAuthEnabled(config)) methods.push('api_key')
|
|
307
331
|
const headers: Record<string, string> = {
|
|
@@ -318,14 +342,18 @@ export async function authenticateAndGuard(
|
|
|
318
342
|
message: 'Payment required',
|
|
319
343
|
type: 'payment_required',
|
|
320
344
|
payment_methods: methods,
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
345
|
+
...(isX402AuthEnabled(config)
|
|
346
|
+
? {
|
|
347
|
+
x402: {
|
|
348
|
+
operator: config.x402.operatorAddress,
|
|
349
|
+
chain_id: config.x402.chainId,
|
|
350
|
+
credits_address: config.x402.creditsAddress,
|
|
351
|
+
required_amount: requiredPaymentAmount.toString(),
|
|
352
|
+
currency_decimals: config.x402.currencyDecimals ?? 6,
|
|
353
|
+
max_output_tokens: maxOutputTokens,
|
|
354
|
+
},
|
|
355
|
+
}
|
|
356
|
+
: {}),
|
|
329
357
|
...(isMppAuthEnabled(config) && config.mpp
|
|
330
358
|
? { mpp: { realm: config.mpp.realm, method: config.mpp.method ?? 'blueprintevm' } }
|
|
331
359
|
: {}),
|
|
@@ -399,6 +427,7 @@ export async function authenticateAndGuard(
|
|
|
399
427
|
consumerId: consumerId,
|
|
400
428
|
keyId: keyInfo?.keyId,
|
|
401
429
|
requestId,
|
|
430
|
+
...(threadId ? { threadId } : {}),
|
|
402
431
|
})
|
|
403
432
|
if (!authz.allow) {
|
|
404
433
|
return c.json(
|
|
@@ -422,6 +451,8 @@ export async function authenticateAndGuard(
|
|
|
422
451
|
userMessage,
|
|
423
452
|
rateLimitRemaining: rl.remaining,
|
|
424
453
|
requestId,
|
|
454
|
+
messages: filtered,
|
|
455
|
+
...(threadId ? { threadId } : {}),
|
|
425
456
|
startMs,
|
|
426
457
|
maxOutputTokens,
|
|
427
458
|
executionBudget,
|
package/src/dispatch-sandbox.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import type {
|
|
8
8
|
AgentMeta,
|
|
9
9
|
GatewayConfig,
|
|
10
|
+
GatewaySandboxContext,
|
|
10
11
|
SandboxExecutionBudget,
|
|
11
12
|
SandboxStreamEvent,
|
|
12
13
|
SandboxUsageReceipt,
|
|
@@ -58,9 +59,10 @@ export async function* dispatchSandboxStreamRich(
|
|
|
58
59
|
onSandboxStart?: () => void | Promise<void>,
|
|
59
60
|
maxInputTokens?: number,
|
|
60
61
|
onExecutionHeartbeat?: () => Promise<void>,
|
|
62
|
+
sandboxContext?: GatewaySandboxContext,
|
|
61
63
|
): AsyncIterable<A2ADispatchEvent> {
|
|
62
64
|
if (signal?.aborted) return
|
|
63
|
-
const box = await config.getSandbox(agent)
|
|
65
|
+
const box = await config.getSandbox(agent, sandboxContext)
|
|
64
66
|
if (signal?.aborted) return
|
|
65
67
|
const outputLimit = maxOutputTokens ?? config.defaultOutputTokens ?? 1024
|
|
66
68
|
if (!Number.isSafeInteger(outputLimit) || outputLimit <= 0) {
|
package/src/dispatch-types.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { PaymentSettlementBasis } from './payment-recovery'
|
|
|
7
7
|
import type {
|
|
8
8
|
AgentMeta,
|
|
9
9
|
ApiKeyInfo,
|
|
10
|
+
ChatMessage,
|
|
10
11
|
PaymentMethod,
|
|
11
12
|
SandboxExecutionBudget,
|
|
12
13
|
SandboxUsageReceipt,
|
|
@@ -37,6 +38,8 @@ export interface AuthorizedRequest {
|
|
|
37
38
|
userMessage: string
|
|
38
39
|
rateLimitRemaining: number | undefined
|
|
39
40
|
requestId: string
|
|
41
|
+
messages?: ChatMessage[]
|
|
42
|
+
threadId?: string
|
|
40
43
|
startMs: number
|
|
41
44
|
maxOutputTokens: number
|
|
42
45
|
executionBudget: SandboxExecutionBudget
|
package/src/index.ts
CHANGED
package/src/middleware.ts
CHANGED
|
@@ -23,8 +23,13 @@ import {
|
|
|
23
23
|
assertPaymentRecoveryConfig,
|
|
24
24
|
} from './payment-recovery'
|
|
25
25
|
import { MemoryRateLimitStore, type RateLimitStore } from './rate-limit'
|
|
26
|
-
import type {
|
|
27
|
-
|
|
26
|
+
import type {
|
|
27
|
+
ChatCompletionChunk,
|
|
28
|
+
ChatCompletionRequest,
|
|
29
|
+
CreateAgentGatewayConfig,
|
|
30
|
+
GatewayConfig,
|
|
31
|
+
} from './types'
|
|
32
|
+
import { isApiKeyAuthEnabled, isMppAuthEnabled, isX402AuthEnabled } from './verify'
|
|
28
33
|
|
|
29
34
|
/**
|
|
30
35
|
* Create a Hono router that serves the agent gateway.
|
|
@@ -36,13 +41,19 @@ import { isApiKeyAuthEnabled, isMppAuthEnabled } from './verify'
|
|
|
36
41
|
* GET /:slug/chat/completions — agent discovery metadata (Tangle-native shape)
|
|
37
42
|
* POST /:slug/chat/completions — OpenAI-compatible chat endpoint (paid)
|
|
38
43
|
*/
|
|
39
|
-
export function createAgentGateway(inputConfig:
|
|
40
|
-
let config = inputConfig
|
|
44
|
+
export function createAgentGateway(inputConfig: CreateAgentGatewayConfig) {
|
|
45
|
+
let config: GatewayConfig = inputConfig.x402
|
|
46
|
+
? inputConfig
|
|
47
|
+
: {
|
|
48
|
+
...inputConfig,
|
|
49
|
+
x402: { operatorAddress: '', chainId: 0 },
|
|
50
|
+
}
|
|
41
51
|
// Production gateways must verify x402 signatures. Tests and local
|
|
42
52
|
// dev can opt into the explicit demo path.
|
|
43
|
-
if (!config
|
|
53
|
+
if (!isX402AuthEnabled(config) && !config.verifyApiKey) {
|
|
44
54
|
throw new Error(
|
|
45
|
-
'createAgentGateway:
|
|
55
|
+
'createAgentGateway: verifySigner is required in production unless verifyApiKey is configured; ' +
|
|
56
|
+
'configure x402.verifySigner or verifyApiKey. ' +
|
|
46
57
|
'For tests, set x402.demoMode: true explicitly.',
|
|
47
58
|
)
|
|
48
59
|
}
|
|
@@ -185,14 +196,15 @@ export function createAgentGateway(inputConfig: GatewayConfig) {
|
|
|
185
196
|
const agent = await config.resolveAgent(slug)
|
|
186
197
|
if (!agent || !agent.enabled) return c.json({ error: 'Agent not found or not published' }, 404)
|
|
187
198
|
|
|
188
|
-
const paymentMethods: Array<Record<string, unknown>> = [
|
|
189
|
-
|
|
199
|
+
const paymentMethods: Array<Record<string, unknown>> = []
|
|
200
|
+
if (isX402AuthEnabled(config)) {
|
|
201
|
+
paymentMethods.push({
|
|
190
202
|
type: 'x402',
|
|
191
203
|
operator: config.x402.operatorAddress,
|
|
192
204
|
chain_id: config.x402.chainId,
|
|
193
205
|
credits_contract: config.x402.creditsAddress,
|
|
194
|
-
}
|
|
195
|
-
|
|
206
|
+
})
|
|
207
|
+
}
|
|
196
208
|
if (isMppAuthEnabled(config)) {
|
|
197
209
|
paymentMethods.push({
|
|
198
210
|
type: 'mpp',
|
|
@@ -200,7 +212,9 @@ export function createAgentGateway(inputConfig: GatewayConfig) {
|
|
|
200
212
|
method: config.mpp!.method ?? 'blueprintevm',
|
|
201
213
|
})
|
|
202
214
|
}
|
|
203
|
-
if (isApiKeyAuthEnabled(config))
|
|
215
|
+
if (isApiKeyAuthEnabled(config)) {
|
|
216
|
+
paymentMethods.push({ type: 'api_key', prefix: config.apiKeyPrefix ?? 'sk_agent_' })
|
|
217
|
+
}
|
|
204
218
|
|
|
205
219
|
return c.json({
|
|
206
220
|
slug: agent.slug,
|
|
@@ -412,7 +426,7 @@ function streamChatCompletions(
|
|
|
412
426
|
consumerId,
|
|
413
427
|
config,
|
|
414
428
|
abortController.signal,
|
|
415
|
-
|
|
429
|
+
authz.threadId,
|
|
416
430
|
maxOutputTokens,
|
|
417
431
|
() => beginPaymentExecution(authz, config),
|
|
418
432
|
authz.paymentOperation !== undefined || authz.mppChargeOperation !== undefined,
|
|
@@ -422,6 +436,14 @@ function streamChatCompletions(
|
|
|
422
436
|
},
|
|
423
437
|
authz.executionBudget.maxInputTokens,
|
|
424
438
|
() => renewPaymentExecution(authz, config),
|
|
439
|
+
{
|
|
440
|
+
consumerId,
|
|
441
|
+
paymentMethod,
|
|
442
|
+
keyInfo: authz.keyInfo,
|
|
443
|
+
requestId,
|
|
444
|
+
messages: authz.messages ?? [],
|
|
445
|
+
...(authz.threadId ? { threadId: authz.threadId } : {}),
|
|
446
|
+
},
|
|
425
447
|
)) {
|
|
426
448
|
if (event.kind === 'text') {
|
|
427
449
|
sendChunk(event.delta)
|
|
@@ -499,6 +521,7 @@ function streamChatCompletions(
|
|
|
499
521
|
'X-Request-Id': requestId,
|
|
500
522
|
'X-Agent-Slug': agent.slug,
|
|
501
523
|
'X-Agent-Hosting': agent.sandboxEndpoint ? 'sovereign' : 'centralized',
|
|
524
|
+
...(authz.threadId ? { 'X-Tangle-Thread-Id': authz.threadId } : {}),
|
|
502
525
|
'X-Payment-Method': paymentMethod,
|
|
503
526
|
'X-Payment-Settled': paymentMethod === 'x402' || authz.paymentOperation ? 'pending' : 'true',
|
|
504
527
|
...(authz.mppChargeOperation
|
package/src/types.ts
CHANGED
|
@@ -225,14 +225,28 @@ export interface SandboxBox {
|
|
|
225
225
|
): AsyncIterable<SandboxStreamEvent>
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
+
/** Authenticated request identity supplied when the host resolves a sandbox. */
|
|
229
|
+
export interface GatewaySandboxContext {
|
|
230
|
+
consumerId: string
|
|
231
|
+
paymentMethod: PaymentMethod
|
|
232
|
+
keyInfo: ApiKeyInfo | null
|
|
233
|
+
requestId: string
|
|
234
|
+
messages: ChatMessage[]
|
|
235
|
+
/** Stable UI conversation id when `conversationMode` is `thread`. */
|
|
236
|
+
threadId?: string
|
|
237
|
+
}
|
|
238
|
+
|
|
228
239
|
// --- Gateway config ---
|
|
229
240
|
|
|
230
241
|
export interface GatewayConfig {
|
|
231
242
|
/** Resolve agent metadata by slug. Return null if not found or not published. */
|
|
232
243
|
resolveAgent: (slug: string) => Promise<AgentMeta | null>
|
|
233
244
|
|
|
234
|
-
/**
|
|
235
|
-
|
|
245
|
+
/**
|
|
246
|
+
* Get the agent execution adapter after payment is verified.
|
|
247
|
+
* Hosts that use agent-app can drive their normal persisted chat route here.
|
|
248
|
+
*/
|
|
249
|
+
getSandbox: (agent: AgentMeta, context?: GatewaySandboxContext) => Promise<SandboxBox>
|
|
236
250
|
|
|
237
251
|
/**
|
|
238
252
|
* Optional host authorization hook fired after payment verification
|
|
@@ -241,7 +255,14 @@ export interface GatewayConfig {
|
|
|
241
255
|
*/
|
|
242
256
|
authorizeConsumer?: (
|
|
243
257
|
agent: AgentMeta,
|
|
244
|
-
consumer: {
|
|
258
|
+
consumer: {
|
|
259
|
+
method: PaymentMethod
|
|
260
|
+
consumerId: string
|
|
261
|
+
keyId?: string
|
|
262
|
+
requestId: string
|
|
263
|
+
/** Requested stable conversation id, after syntax validation. */
|
|
264
|
+
threadId?: string
|
|
265
|
+
},
|
|
245
266
|
) => Promise<{ allow: true } | { allow: false; reason: string; code: string }>
|
|
246
267
|
|
|
247
268
|
/**
|
|
@@ -283,6 +304,16 @@ export interface GatewayConfig {
|
|
|
283
304
|
/** Base URL for API key purchase links (e.g. "https://film.tangle.tools") */
|
|
284
305
|
baseUrl?: string
|
|
285
306
|
|
|
307
|
+
/** Public API key prefix shown by discovery. Defaults to `sk_agent_`. */
|
|
308
|
+
apiKeyPrefix?: string
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* `consumer` keeps the historical session per API consumer.
|
|
312
|
+
* `thread` accepts `X-Tangle-Thread-Id` or creates one per request and returns
|
|
313
|
+
* it in the response, so a host can display the same conversation.
|
|
314
|
+
*/
|
|
315
|
+
conversationMode?: 'consumer' | 'thread'
|
|
316
|
+
|
|
286
317
|
/** Max message length in chars (default: 8000) */
|
|
287
318
|
maxMessageLength?: number
|
|
288
319
|
|
|
@@ -392,6 +423,16 @@ export interface GatewayConfig {
|
|
|
392
423
|
}
|
|
393
424
|
}
|
|
394
425
|
|
|
426
|
+
/** API-key-only gateway input. Payment transports stay disabled. */
|
|
427
|
+
export type ApiKeyGatewayConfig = Omit<GatewayConfig, 'mpp' | 'verifyApiKey' | 'x402'> & {
|
|
428
|
+
mpp?: never
|
|
429
|
+
verifyApiKey: NonNullable<GatewayConfig['verifyApiKey']>
|
|
430
|
+
x402?: never
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/** Configuration accepted by `createAgentGateway`. */
|
|
434
|
+
export type CreateAgentGatewayConfig = GatewayConfig | ApiKeyGatewayConfig
|
|
435
|
+
|
|
395
436
|
// --- Chat completion types (OpenAI-compatible) ---
|
|
396
437
|
|
|
397
438
|
export interface ChatMessage {
|
package/src/verify.ts
CHANGED
|
@@ -108,6 +108,13 @@ export function isApiKeyAuthEnabled(
|
|
|
108
108
|
return config.verifyApiKey !== undefined || config.x402.demoMode === true
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
/** x402 is advertised only when the gateway can authenticate it. */
|
|
112
|
+
export function isX402AuthEnabled(
|
|
113
|
+
config: Pick<GatewayConfig, 'x402'>,
|
|
114
|
+
): boolean {
|
|
115
|
+
return config.x402.verifySigner !== undefined || config.x402.demoMode === true
|
|
116
|
+
}
|
|
117
|
+
|
|
111
118
|
/** MPP is enabled only when authentication and method settlement are complete. */
|
|
112
119
|
export function isMppAuthEnabled(
|
|
113
120
|
config: Pick<GatewayConfig, 'mpp' | 'x402'>,
|