@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 CHANGED
@@ -43,7 +43,8 @@ app.route('/v1/agents', createAgentGateway({
43
43
  }))
44
44
  ```
45
45
 
46
- `x402.verifySigner` is required for production.
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 userMessage = filtered.filter((m) => m.role === "user").map((m) => m.content).join("\n\n");
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 = ["x402"];
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
- x402: {
749
- operator: config.x402.operatorAddress,
750
- chain_id: config.x402.chainId,
751
- credits_address: config.x402.creditsAddress,
752
- required_amount: requiredPaymentAmount.toString(),
753
- currency_decimals: config.x402.currencyDecimals ?? 6,
754
- max_output_tokens: maxOutputTokens
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
- if (!config.x402.verifySigner && !config.x402.demoMode) {
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: x402.verifySigner is required in production. For tests, set x402.demoMode: true explicitly."
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)) paymentMethods.push({ type: "api_key", prefix: "sk_agent_" });
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
- void 0,
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-MP6IIAIA.js.map
5693
+ //# sourceMappingURL=chunk-C7Z2BRYV.js.map