@agentidentitytrustprotocol/aitp

中文文档
0.25.1发布于 2026/11/02MIT OR Apache-2.0TypeScriptCommonJS零依赖构建来源已验证近 30 天下载 2734

Agent Identity Trust Protocol — Node.js SDK

原始使用文档
文档目录10 节
  1. Install
  2. Build from source
  3. Cargo features
  4. Usage
  5. API
  6. Revocation
  7. UUID inputs
  8. OIDC identity and P-256
  9. Pinned keys ( opts.pinnedKeys )
  10. Tests

aitp — Node.js SDK

Node.js bindings for the Agent Identity & Trust Protocol (AITP), built on the pure-Rust aitp-rs protocol crates via NAPI-rs.

A thin SDK: an AitpAgent plus initiator/responder session objects. Their methods take and return JSON strings (the HTTP request/response bodies) and compact-JWS strings (TCTs, grant vouchers, delegation tokens). Agent code never handles a Rust type across the FFI boundary. The API is the camelCase counterpart of the Python SDK (buildManifest ↔ build_manifest); the two differ in a few return shapes, listed in Python vs Node differences.

The feature-by-feature guide, with an example for every capability, is docs/sdk-node.md.

Install

npm install @agentidentitytrustprotocol/aitp

Prebuilt native binaries ship for macOS (x64, arm64) and Linux GNU (x64, arm64).

Build from source

This crate is not part of the aitp-rs Cargo workspace. Build it with the NAPI-rs CLI and a Rust toolchain:

npm install
npm run build:debug                  # default `.node`, as published
npm run build:experimental           # adds the experimental features
npm run build:minimal:debug          # without SPKI pinning
# Release:
npm run build
npm run build:minimal                # --no-default-features

The build produces the platform .node binary and regenerates index.js and index.d.ts, which cover the default surface. build:experimental writes experimental.js and experimental.d.ts instead, which add the experimental API and are not in the published package.

Cargo features

The published .node is the default build:

  • handshake, TCT, delegation, manifest verification and OIDC identity
  • revocation-list signing and verification
  • envelope signing and verification
  • SPKI pinning (feature spki-pinning, on by default)

The experimental features are off by default and are not in the published package. A source build enables them one by one, or all with experimental (npm run build:experimental). They carry no stability promise.

FeatureEnablesRFC
spki-pinning (default)computeSpkiHash, SpkiPinVerifierHPKP (RFC 7469)
experimental-renewalAitpAgent.buildRenewalRequest / processRenewalRequestRFC-AITP-0013 (Draft)
experimental-session-bundleSessionBundleBuilder, verifySessionBundleRFC-AITP-0010 (Draft)
experimental-multihop-delegationverifyDelegationMultihopRFC-AITP-0011 (Draft)
experimental-a2a-agent-carda2aCardManifestUrl, verifyA2aAgentCardBindingRFC-AITP-0014 (Draft)

Usage

import { AitpAgent } from '@agentidentitytrustprotocol/aitp';

const initiator = AitpAgent.generate();
const responder = AitpAgent.generate();

initiator.buildManifest({
  displayName: 'initiator',
  handshakeEndpoint: 'https://localhost:8100/aitp/handshake/',
  offeredCaps: ['demo.echo'],
});
const respManifest = responder.buildManifest({
  displayName: 'responder',
  handshakeEndpoint: 'https://localhost:8200/aitp/handshake/',
  offeredCaps: ['demo.write'],
});

// Each side trusts the other's pinned key by AID. Without `pinnedKeys`
// every pinned_key peer is rejected (fail closed).
const sess  = initiator.newSession(null, { pinnedKeys: [responder.aid] });
const rsess = responder.newResponder(null, { pinnedKeys: [initiator.aid] });

// Four-message mutual handshake — each call's output is the next peer's input.

const hello                   = sess.buildHello(respManifest, ['demo.write']);
const { ackJson: helloAck, sessionId } = rsess.processHello(hello);
const commit                  = sess.processHelloAck(helloAck, sessionId);
const { ackJson: commitAck }   = rsess.processCommit(commit, []);
const completed                = sess.complete(commitAck, []);
// completed = { tct, claims, grantVoucher? }
// `tct` is an opaque compact-JWS string; `claims` is the decoded TCT.

// Each peer now holds a TCT the other issued it.
const ident = initiator.verifyTct(completed.tct, 'demo.write', undefined, []);
console.log(ident.peerAid, ident.grants);

// `completed.grantVoucher` (when present) is what you pass to
// `buildDelegation(grantVoucher, delegateeAid, scope)` to delegate.

In a real deployment each message moves over HTTP. buildHello returns the body for POST /aitp/handshake/hello. processHello returns the response body plus the value for the X-Aitp-Session-Id header, and so on.

API

The generated index.d.ts describes the full public surface; the table below summarizes it. Manifests, revocation lists and handshake envelopes cross the boundary as JSON strings. TCTs, grant vouchers and delegations are opaque compact-JWS token strings (header.payload.signature).

ExportFeatureNotes
AitpAgentdefaultgenerate(opts?) / fromSeed(buffer, opts?) (opts.suite = "ed25519" | "p256"), aid, buildManifest(opts) (opts.revocationListUri), newSession(jwks?, opts?), newResponder(jwks?, opts?) (opts.trustAnchors, opts.pinnedKeys, opts.insecureLocalhostDangerous), verifyTct(token, grant, expectedAudience?, revokedJtis?, revocation?, pop?), verifyTctCached(token, grant, store, expectedAudience?, revokedJtis?, revocation?, pop?) (pop = { challenge, response }, the presented-TCT path), newPopChallenge(tctToken, ttlSecs?), signPopResponse(challengeJson), buildDelegation(voucherToken, delegateeAid, scope, ttlSecs?), issueTctForDelegatee(verified, ttlSecs?) → { tct, grantVoucher? }, signEnvelope(messageType, payloadJson, opts?) (opts.extensionsJson, opts.messageId, opts.timestamp), signRevocationList(entries, expiresInSecs?)
JsInitiatorSessiondefaultReturned by newSession. buildHello(peerManifest, grants, oidcMintJwt?), processHelloAck(...), complete(...) → { tct, claims, grantVoucher? }
JsResponderSessiondefaultReturned by newResponder. processHello(hello, oidcMintJwt?) → { ackJson, sessionId }, processCommit(commit) → { ackJson, completed: { tct, claims, grantVoucher? } }
TctStoredefaultCache for AitpAgent.verifyTctCached(). A byte-identical, still-valid TCT skips the signature check; the key is the SHA-256 of the token bytes
JwksProviderdefaultOIDC JWKS map. upsert(issuer, keys), remove(issuer), issuers()
verifyDelegation(token, verifierAid, issuerManifests, revokedJtis?, revocation?, insecureLocalhostDangerous?)defaultRFC-AITP-0006, strict single-hop; rejects any multi-hop chain. Verifies under the key the delegator's Manifest designates (issuerManifests: {"manifest": ...} JSON strings). Returns { delegator, delegatee, issuedBy, grants, expiresAt, cnfJkt }
verifyManifestJson(json, nowUnixSecs?, insecureLocalhostDangerous?)defaultThrows an Error with code (e.g. signature_invalid, expired, unknown_field, malformed)
verifyEnvelope(json, expectedSenderAid)defaultVerifies an aitp/0.3 envelope's signature against a pinned sender, in the RFC-AITP-0001 §5.4 check order. Throws an Error with code (UNKNOWN_VERSION, UNKNOWN_FIELD, INVALID_ENVELOPE, INVALID_SIGNATURE). Replay and timestamp checks stay with the caller; see Envelope signatures
verifyRevocationList(json, expectedIssuerAid, nowUnixSecs?)defaultVerifies a revocation snapshot against a pinned issuer. Throws an Error with code
revocationSigningBytes(json)defaultThe exact bytes a revocation snapshot's signature covers
verifyPopResponse(challengeJson, responseJson, tctToken)defaultRFC-AITP-0005 §6.2 checks only (not the TCT itself); returns the proven holder AID. Throws with code POP_CHALLENGE_INVALID or POP_RESPONSE_INVALID
computeAidJkt(aid)defaultRFC 7638 thumbprint of an AID's key, for an OIDC JWT's cnf.jkt
buildRenewalRequest() / processRenewalRequest()experimental-renewalRFC-AITP-0013 (Draft). processRenewalRequest → { tct, grantVoucher? }
SessionBundleBuilder, verifySessionBundle(json, verifierAid, coordinatorManifestJson, nowUnixSecs?, revocationCheck?, insecureLocalhostDangerous?)experimental-session-bundleRFC-AITP-0010. Verifies under the key the coordinator's Manifest designates (coordinatorManifestJson: {"manifest": ...} JSON, required)
computeSpkiHash(), SpkiPinVerifierspki-pinningHPKP-style outbound pinning
verifyDelegationMultihop(token, verifierAid, issuerManifests, maxDelegationHops?, revokedJtis?, revocation?, insecureLocalhostDangerous?)experimental-multihop-delegationRFC-AITP-0011 multi-hop opt-in
a2aCardManifestUrl(cardJson), verifyA2aAgentCardBinding(cardJson, selectedInterfaceUrl, manifestJson?, nowUnixSecs?)experimental-a2a-agent-cardRFC-AITP-0014 client verification, no I/O. Throws with error.code. See docs/a2a-agent-card.md

verifyTct returns { peerAid, grants, expiresAt, jti }, where peerAid is the issuer.

Revocation

verifyTct and verifyTctCached take a revokedJtis argument, an array of revoked TCT jti strings, and an optional final revocation argument ('fail_closed', the default, or 'fail_open'). A TCT whose jti is in the array is rejected, even if its signature, audience and expiry are otherwise valid:

// not executed: fragment; see docs/sdk-node.md § TCT verification for the runnable flow
const revoked = ['11111111-2222-4333-8444-555555555555'];
agent.verifyTct(tctToken, 'demo.write', null, revoked);  // throws if revoked

Obligation. The SDK does not fetch or maintain the revoked set for you. Supplying it is the caller's responsibility: source it from a revocation snapshot you fetched and checked with verifyRevocationList (issue one with signRevocationList).

The set is passed up-front rather than through a JS callback invoked per jti, so that it stays sound under napi threading constraints.

Revocation is fail_closed (RFC-AITP-0008 §3.1): if you omit revokedJtis, every TCT is rejected with code 'TCT_REVOKED', because an unknown status counts as revoked. Pass [] when the issuer's list is empty, or 'fail_open' only when the deployment has no revocation source.

As an issuer, advertise your list: buildManifest({ ..., revocationListUri }) puts rfc-aitp-0008.revocation_list_uri in your signed Manifest, as RFC-AITP-0005 §11 and RFC-AITP-0008 §1.4 require. Use an absolute HTTPS URL, conventionally your origin plus REVOCATION_LIST_PATH.

UUID inputs

Identifiers that are signed or sent are strict: signEnvelope's opts.messageId, SessionBundleBuilder.sessionId() and the jti of each signRevocationList entry must be the lowercase hyphenated UUID v4 wire form (xxxxxxxx-xxxx-4xxx-[89ab]xxx-xxxxxxxxxxxx). Uppercase, braced, urn:uuid:, unhyphenated and non-v4 forms throw, naming the field; they are never normalized, so what you pass is what goes on the wire.

A revokedJtis list (on verifyTct, verifyTctCached, processRenewalRequest, verifyDelegation and verifyDelegationMultihop) is a lenient lookup key: each entry may be in any RFC 9562 text form and is normalized before matching, so '{11111111-2222-4333-8444-555555555555}' revokes the same TCT as the lowercase form. An entry that is not a UUID at all throws (revoked_jtis entry is not a UUID) rather than being ignored. The Python SDK behaves identically.

OIDC identity and P-256

See OIDC identity and P-256 signing suite. The oidcMintJwt callback is synchronous.

Breaking change in v0.2: the P-256-specific factory methods were removed in favor of the parameterized generate({ suite }) / fromSeed(seed, { suite }) API, which matches the Python SDK's AitpAgent.generate(suite="p256"). To migrate, call AitpAgent.generate({ suite: 'p256' }) or AitpAgent.fromSeed(seed, { suite: 'p256' }).

pinned_key identities work with Ed25519 and P-256 keys. For P-256 the manifest's public_key is the 33-byte SEC1 compressed point.

Pinned keys (opts.pinnedKeys)

A session accepts a pinned_key peer only if the peer's AID is in the pinnedKeys array passed to newSession / newResponder (RFC-AITP-0002 §3.2: the key must be in the verifier's local trust store, checked before the proof). Omitted or [] trusts no pinned key, so every pinned_key peer is rejected. An entry that is not a valid AID throws.

Upgrading to 0.14: possession-only pinned-key verification is gone. Pass the peer's AID, e.g. agent.newSession(null, { pinnedKeys: [peer.aid] }), or the handshake fails with IDENTITY_FAILED.

Tests

npm install
npm run build:debug
npm test                 # node --test tests/*.mjs

tests/test_docs_samples.mjs runs every JavaScript block in docs/sdk-node.md and the Usage block above.

The cross-language interop suite (Python ↔ Node) lives in bindings/interop. Run it with make interop from the repo root.

版本与发布记录

@agentidentitytrustprotocol/aitp 共发布 29 个版本,首次发布于 2026/06/09;过去 12 个月发布了 29 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.25.1latest2026/11/025 天前
0.25.02026/11/025 天前
0.24.12026/11/016 天前
0.24.02026/11/017 天前
0.23.02026/10/2513 天前
0.22.02026/10/2513 天前

关于 @agentidentitytrustprotocol/aitp 的常见问题

@agentidentitytrustprotocol/aitp 是什么?

Agent Identity Trust Protocol — Node.js SDK

如何安装 @agentidentitytrustprotocol/aitp?

在项目目录运行 npm install @agentidentitytrustprotocol/aitp,或使用 pnpm add / yarn add / bun add。

@agentidentitytrustprotocol/aitp 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@agentidentitytrustprotocol/aitp 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@agentidentitytrustprotocol/aitp 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@agentidentitytrustprotocol/aitp 可以商用吗?

@agentidentitytrustprotocol/aitp 使用 MIT OR Apache-2.0 许可。可在 MIT、Apache-2.0 中任选其一遵守。选择 MIT 时,宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@agentidentitytrustprotocol/aitp 的下载量有多少?

根据 npm 官方下载统计,@agentidentitytrustprotocol/aitp 在 2026/10/05 – 2026/11/03 这 30 天内被下载 2,734 次,日均约 91 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@agentidentitytrustprotocol/aitp 还在维护吗?

最新版本 0.25.1 发布于 2026/11/02(5 天前);至今共发布 29 个版本,其中过去 12 个月发布了 29 个;首次发布于 2026/06/09。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。