@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/README.md
CHANGED
|
@@ -43,7 +43,8 @@ app.route('/v1/agents', createAgentGateway({
|
|
|
43
43
|
}))
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
`x402.verifySigner`
|
|
46
|
+
Production requires either `x402.verifySigner` or `verifyApiKey`.
|
|
47
|
+
API-key-only apps can omit `x402` entirely.
|
|
47
48
|
Set `x402.demoMode: true` only for local development and tests; that explicit mode also enables the built-in `sk_agent_*` demo key verifier.
|
|
48
49
|
Keep `verifySigner` free of side effects.
|
|
49
50
|
Use version 2's `authorizePayment` to reserve or claim funds after rate limits, content checks, and product authorization succeed.
|
|
@@ -109,6 +110,11 @@ The 0.7.1 `mpp.verifySigner` callback is also supported; the gateway derives a s
|
|
|
109
110
|
The same authentication, authorization, rate-limit, filtering, sandbox, settlement, and usage-recording pipeline is used by the OpenAI-compatible and A2A endpoints.
|
|
110
111
|
Wire protocol handlers only translate their request and response shapes.
|
|
111
112
|
|
|
113
|
+
Set `conversationMode: 'thread'` when API calls must use the app's visible conversations.
|
|
114
|
+
The gateway accepts an optional `X-Tangle-Thread-Id` and returns the resolved ID in the same response header.
|
|
115
|
+
Its authenticated `getSandbox` context contains that thread ID, the API-key identity, and the filtered messages.
|
|
116
|
+
An `agent-app` host can use this context to drive its normal persisted chat route instead of opening a second sandbox session.
|
|
117
|
+
|
|
112
118
|
## A2A protocol
|
|
113
119
|
|
|
114
120
|
The gateway speaks Google's A2A protocol alongside its OpenAI-compatible surface: discovery via `.well-known/agent.json`, JSON-RPC 2.0 dispatch for `message/send`, `message/stream`, `tasks/get`, `tasks/cancel`, `tasks/resubscribe`, and the four `tasks/pushNotificationConfig/*` methods. Long-horizon agents — durable tasks across worker restarts, webhook delivery on terminal state, `input-required` pauses with multi-turn continuation — are documented in [`docs/a2a-long-horizon.md`](./docs/a2a-long-horizon.md).
|
|
@@ -357,6 +357,9 @@ function legacyMppPaymentIdentity(method, payload, credential) {
|
|
|
357
357
|
function isApiKeyAuthEnabled(config) {
|
|
358
358
|
return config.verifyApiKey !== void 0 || config.x402.demoMode === true;
|
|
359
359
|
}
|
|
360
|
+
function isX402AuthEnabled(config) {
|
|
361
|
+
return config.x402.verifySigner !== void 0 || config.x402.demoMode === true;
|
|
362
|
+
}
|
|
360
363
|
function isMppAuthEnabled(config) {
|
|
361
364
|
const method = (config.mpp?.method ?? "blueprintevm").toLowerCase();
|
|
362
365
|
if (!config.mpp) return false;
|
|
@@ -492,6 +495,17 @@ async function authenticateAndGuard(c, slug, messages, config, state, requestedM
|
|
|
492
495
|
const requestId = generateRequestId();
|
|
493
496
|
const ctx = { requestId, agentSlug: slug, startMs };
|
|
494
497
|
await state.obs?.onRequestStart?.(ctx);
|
|
498
|
+
let threadId;
|
|
499
|
+
if (config.conversationMode === "thread") {
|
|
500
|
+
const requestedThreadId = c.req.header("X-Tangle-Thread-Id")?.trim();
|
|
501
|
+
if (requestedThreadId && !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(requestedThreadId)) {
|
|
502
|
+
return c.json(
|
|
503
|
+
{ error: { message: "Invalid X-Tangle-Thread-Id", type: "invalid_request" } },
|
|
504
|
+
{ status: 400, headers: { "X-Request-Id": requestId } }
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
threadId = requestedThreadId || requestId;
|
|
508
|
+
}
|
|
495
509
|
const agent = await config.resolveAgent(slug);
|
|
496
510
|
if (!agent || !agent.enabled) {
|
|
497
511
|
return c.json({ error: { message: "Agent not found", type: "not_found" } }, 404);
|
|
@@ -519,7 +533,8 @@ async function authenticateAndGuard(c, slug, messages, config, state, requestedM
|
|
|
519
533
|
messages,
|
|
520
534
|
state.maxLen
|
|
521
535
|
);
|
|
522
|
-
const
|
|
536
|
+
const userMessages = filtered.filter((m) => m.role === "user");
|
|
537
|
+
const userMessage = config.conversationMode === "thread" ? userMessages[userMessages.length - 1]?.content ?? "" : userMessages.map((m) => m.content).join("\n\n");
|
|
523
538
|
if (!userMessage) {
|
|
524
539
|
return c.json(
|
|
525
540
|
{ error: { message: "No user message provided", type: "invalid_request" } },
|
|
@@ -604,6 +619,12 @@ async function authenticateAndGuard(c, slug, messages, config, state, requestedM
|
|
|
604
619
|
let mppCredential;
|
|
605
620
|
let mppPaymentIdentity;
|
|
606
621
|
if (spendAuthHeader) {
|
|
622
|
+
if (!isX402AuthEnabled(config)) {
|
|
623
|
+
return c.json(
|
|
624
|
+
{ error: { message: "x402 authentication is not configured", type: "authentication_error" } },
|
|
625
|
+
{ status: 401, headers: { "X-Request-Id": requestId } }
|
|
626
|
+
);
|
|
627
|
+
}
|
|
607
628
|
const signer = await verifyX402(
|
|
608
629
|
spendAuthHeader,
|
|
609
630
|
config.x402,
|
|
@@ -729,7 +750,8 @@ async function authenticateAndGuard(c, slug, messages, config, state, requestedM
|
|
|
729
750
|
code: "payment_required",
|
|
730
751
|
httpStatus: 402
|
|
731
752
|
});
|
|
732
|
-
const methods = [
|
|
753
|
+
const methods = [];
|
|
754
|
+
if (isX402AuthEnabled(config)) methods.push("x402");
|
|
733
755
|
if (isMppAuthEnabled(config)) methods.push("mpp");
|
|
734
756
|
if (isApiKeyAuthEnabled(config)) methods.push("api_key");
|
|
735
757
|
const headers = {
|
|
@@ -745,14 +767,16 @@ async function authenticateAndGuard(c, slug, messages, config, state, requestedM
|
|
|
745
767
|
message: "Payment required",
|
|
746
768
|
type: "payment_required",
|
|
747
769
|
payment_methods: methods,
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
770
|
+
...isX402AuthEnabled(config) ? {
|
|
771
|
+
x402: {
|
|
772
|
+
operator: config.x402.operatorAddress,
|
|
773
|
+
chain_id: config.x402.chainId,
|
|
774
|
+
credits_address: config.x402.creditsAddress,
|
|
775
|
+
required_amount: requiredPaymentAmount.toString(),
|
|
776
|
+
currency_decimals: config.x402.currencyDecimals ?? 6,
|
|
777
|
+
max_output_tokens: maxOutputTokens
|
|
778
|
+
}
|
|
779
|
+
} : {},
|
|
756
780
|
...isMppAuthEnabled(config) && config.mpp ? { mpp: { realm: config.mpp.realm, method: config.mpp.method ?? "blueprintevm" } } : {},
|
|
757
781
|
...isApiKeyAuthEnabled(config) ? {
|
|
758
782
|
api_key: {
|
|
@@ -811,7 +835,8 @@ async function authenticateAndGuard(c, slug, messages, config, state, requestedM
|
|
|
811
835
|
method: paymentMethod,
|
|
812
836
|
consumerId,
|
|
813
837
|
keyId: keyInfo?.keyId,
|
|
814
|
-
requestId
|
|
838
|
+
requestId,
|
|
839
|
+
...threadId ? { threadId } : {}
|
|
815
840
|
});
|
|
816
841
|
if (!authz.allow) {
|
|
817
842
|
return c.json(
|
|
@@ -834,6 +859,8 @@ async function authenticateAndGuard(c, slug, messages, config, state, requestedM
|
|
|
834
859
|
userMessage,
|
|
835
860
|
rateLimitRemaining: rl.remaining,
|
|
836
861
|
requestId,
|
|
862
|
+
messages: filtered,
|
|
863
|
+
...threadId ? { threadId } : {},
|
|
837
864
|
startMs,
|
|
838
865
|
maxOutputTokens,
|
|
839
866
|
executionBudget,
|
|
@@ -1821,9 +1848,9 @@ function durableMppPaymentPayload(payload) {
|
|
|
1821
1848
|
}
|
|
1822
1849
|
|
|
1823
1850
|
// src/dispatch-sandbox.ts
|
|
1824
|
-
async function* dispatchSandboxStreamRich(agent, userMessage, consumerId, config, signal, sessionId, maxOutputTokens, onExecutionStart, requiresReceipt = config.x402.paymentOperations !== void 0, onSandboxStart, maxInputTokens, onExecutionHeartbeat) {
|
|
1851
|
+
async function* dispatchSandboxStreamRich(agent, userMessage, consumerId, config, signal, sessionId, maxOutputTokens, onExecutionStart, requiresReceipt = config.x402.paymentOperations !== void 0, onSandboxStart, maxInputTokens, onExecutionHeartbeat, sandboxContext) {
|
|
1825
1852
|
if (signal?.aborted) return;
|
|
1826
|
-
const box = await config.getSandbox(agent);
|
|
1853
|
+
const box = await config.getSandbox(agent, sandboxContext);
|
|
1827
1854
|
if (signal?.aborted) return;
|
|
1828
1855
|
const outputLimit = maxOutputTokens ?? config.defaultOutputTokens ?? 1024;
|
|
1829
1856
|
if (!Number.isSafeInteger(outputLimit) || outputLimit <= 0) {
|
|
@@ -5230,10 +5257,13 @@ function clone2(value) {
|
|
|
5230
5257
|
|
|
5231
5258
|
// src/middleware.ts
|
|
5232
5259
|
function createAgentGateway(inputConfig) {
|
|
5233
|
-
let config = inputConfig
|
|
5234
|
-
|
|
5260
|
+
let config = inputConfig.x402 ? inputConfig : {
|
|
5261
|
+
...inputConfig,
|
|
5262
|
+
x402: { operatorAddress: "", chainId: 0 }
|
|
5263
|
+
};
|
|
5264
|
+
if (!isX402AuthEnabled(config) && !config.verifyApiKey) {
|
|
5235
5265
|
throw new Error(
|
|
5236
|
-
"createAgentGateway:
|
|
5266
|
+
"createAgentGateway: verifySigner is required in production unless verifyApiKey is configured; configure x402.verifySigner or verifyApiKey. For tests, set x402.demoMode: true explicitly."
|
|
5237
5267
|
);
|
|
5238
5268
|
}
|
|
5239
5269
|
const maxOutputTokens = config.maxOutputTokens ?? 4096;
|
|
@@ -5334,14 +5364,15 @@ function createAgentGateway(inputConfig) {
|
|
|
5334
5364
|
const slug = c.req.param("slug");
|
|
5335
5365
|
const agent = await config.resolveAgent(slug);
|
|
5336
5366
|
if (!agent || !agent.enabled) return c.json({ error: "Agent not found or not published" }, 404);
|
|
5337
|
-
const paymentMethods = [
|
|
5338
|
-
|
|
5367
|
+
const paymentMethods = [];
|
|
5368
|
+
if (isX402AuthEnabled(config)) {
|
|
5369
|
+
paymentMethods.push({
|
|
5339
5370
|
type: "x402",
|
|
5340
5371
|
operator: config.x402.operatorAddress,
|
|
5341
5372
|
chain_id: config.x402.chainId,
|
|
5342
5373
|
credits_contract: config.x402.creditsAddress
|
|
5343
|
-
}
|
|
5344
|
-
|
|
5374
|
+
});
|
|
5375
|
+
}
|
|
5345
5376
|
if (isMppAuthEnabled(config)) {
|
|
5346
5377
|
paymentMethods.push({
|
|
5347
5378
|
type: "mpp",
|
|
@@ -5349,7 +5380,9 @@ function createAgentGateway(inputConfig) {
|
|
|
5349
5380
|
method: config.mpp.method ?? "blueprintevm"
|
|
5350
5381
|
});
|
|
5351
5382
|
}
|
|
5352
|
-
if (isApiKeyAuthEnabled(config))
|
|
5383
|
+
if (isApiKeyAuthEnabled(config)) {
|
|
5384
|
+
paymentMethods.push({ type: "api_key", prefix: config.apiKeyPrefix ?? "sk_agent_" });
|
|
5385
|
+
}
|
|
5353
5386
|
return c.json({
|
|
5354
5387
|
slug: agent.slug,
|
|
5355
5388
|
pricing: {
|
|
@@ -5522,7 +5555,7 @@ function streamChatCompletions(c, authz, config, obs) {
|
|
|
5522
5555
|
consumerId,
|
|
5523
5556
|
config,
|
|
5524
5557
|
abortController.signal,
|
|
5525
|
-
|
|
5558
|
+
authz.threadId,
|
|
5526
5559
|
maxOutputTokens,
|
|
5527
5560
|
() => beginPaymentExecution(authz, config),
|
|
5528
5561
|
authz.paymentOperation !== void 0 || authz.mppChargeOperation !== void 0,
|
|
@@ -5531,7 +5564,15 @@ function streamChatCompletions(c, authz, config, obs) {
|
|
|
5531
5564
|
await markPaymentExecutionStarted(authz, config);
|
|
5532
5565
|
},
|
|
5533
5566
|
authz.executionBudget.maxInputTokens,
|
|
5534
|
-
() => renewPaymentExecution(authz, config)
|
|
5567
|
+
() => renewPaymentExecution(authz, config),
|
|
5568
|
+
{
|
|
5569
|
+
consumerId,
|
|
5570
|
+
paymentMethod,
|
|
5571
|
+
keyInfo: authz.keyInfo,
|
|
5572
|
+
requestId,
|
|
5573
|
+
messages: authz.messages ?? [],
|
|
5574
|
+
...authz.threadId ? { threadId: authz.threadId } : {}
|
|
5575
|
+
}
|
|
5535
5576
|
)) {
|
|
5536
5577
|
if (event.kind === "text") {
|
|
5537
5578
|
sendChunk(event.delta);
|
|
@@ -5605,6 +5646,7 @@ function streamChatCompletions(c, authz, config, obs) {
|
|
|
5605
5646
|
"X-Request-Id": requestId,
|
|
5606
5647
|
"X-Agent-Slug": agent.slug,
|
|
5607
5648
|
"X-Agent-Hosting": agent.sandboxEndpoint ? "sovereign" : "centralized",
|
|
5649
|
+
...authz.threadId ? { "X-Tangle-Thread-Id": authz.threadId } : {},
|
|
5608
5650
|
"X-Payment-Method": paymentMethod,
|
|
5609
5651
|
"X-Payment-Settled": paymentMethod === "x402" || authz.paymentOperation ? "pending" : "true",
|
|
5610
5652
|
...authz.mppChargeOperation ? { "Payment-Receipt": authz.mppChargeOperation.receipt } : {},
|
|
@@ -5648,4 +5690,4 @@ export {
|
|
|
5648
5690
|
InMemoryTaskStore,
|
|
5649
5691
|
createAgentGateway
|
|
5650
5692
|
};
|
|
5651
|
-
//# sourceMappingURL=chunk-
|
|
5693
|
+
//# sourceMappingURL=chunk-C7Z2BRYV.js.map
|