@agentmesh-p2p/sdk 0.0.0-stage → 0.1.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.
Files changed (68) hide show
  1. package/README.md +182 -2
  2. package/dist/auth.d.ts +66 -0
  3. package/dist/auth.js +164 -0
  4. package/dist/authorizer.d.ts +41 -0
  5. package/dist/authorizer.js +201 -0
  6. package/dist/base58.d.ts +2 -0
  7. package/dist/base58.js +57 -0
  8. package/dist/biscuit.d.ts +75 -0
  9. package/dist/biscuit.js +250 -0
  10. package/dist/bytes.d.ts +10 -0
  11. package/dist/bytes.js +51 -0
  12. package/dist/challenges.d.ts +16 -0
  13. package/dist/challenges.js +55 -0
  14. package/dist/conformance-join.d.ts +1 -0
  15. package/dist/conformance-join.js +257 -0
  16. package/dist/conformance.d.ts +1 -0
  17. package/dist/conformance.js +60 -0
  18. package/dist/controlplane.d.ts +123 -0
  19. package/dist/controlplane.js +368 -0
  20. package/dist/credential.d.ts +66 -0
  21. package/dist/credential.js +124 -0
  22. package/dist/discovery.d.ts +50 -0
  23. package/dist/discovery.js +150 -0
  24. package/dist/gen/agentmesh_pb.d.ts +2740 -0
  25. package/dist/gen/agentmesh_pb.js +591 -0
  26. package/dist/gen/datalog.d.ts +39 -0
  27. package/dist/gen/datalog.js +84 -0
  28. package/dist/host.d.ts +47 -0
  29. package/dist/host.js +109 -0
  30. package/dist/http1.d.ts +68 -0
  31. package/dist/http1.js +371 -0
  32. package/dist/identity.d.ts +31 -0
  33. package/dist/identity.js +125 -0
  34. package/dist/index.d.ts +18 -0
  35. package/dist/index.js +30 -0
  36. package/dist/libp2p-http-node.d.ts +17 -0
  37. package/dist/libp2p-http-node.js +116 -0
  38. package/dist/libp2p-http.d.ts +165 -0
  39. package/dist/libp2p-http.js +581 -0
  40. package/dist/mcp.d.ts +67 -0
  41. package/dist/mcp.js +157 -0
  42. package/dist/mesh.d.ts +151 -0
  43. package/dist/mesh.js +370 -0
  44. package/dist/platform/ingress.browser.d.ts +1 -0
  45. package/dist/platform/ingress.browser.js +16 -0
  46. package/dist/platform/ingress.d.ts +1 -0
  47. package/dist/platform/ingress.js +15 -0
  48. package/dist/platform/state.browser.d.ts +5 -0
  49. package/dist/platform/state.browser.js +47 -0
  50. package/dist/platform/state.d.ts +5 -0
  51. package/dist/platform/state.js +44 -0
  52. package/dist/platform/transports.browser.d.ts +3 -0
  53. package/dist/platform/transports.browser.js +27 -0
  54. package/dist/platform/transports.d.ts +3 -0
  55. package/dist/platform/transports.js +27 -0
  56. package/dist/platform/types.d.ts +7 -0
  57. package/dist/platform/types.js +14 -0
  58. package/dist/platform/wasm.browser.d.ts +2 -0
  59. package/dist/platform/wasm.browser.js +16 -0
  60. package/dist/platform/wasm.d.ts +2 -0
  61. package/dist/platform/wasm.js +56 -0
  62. package/dist/session.d.ts +316 -0
  63. package/dist/session.js +1269 -0
  64. package/dist/sync.d.ts +41 -0
  65. package/dist/sync.js +111 -0
  66. package/dist/tar.d.ts +25 -0
  67. package/dist/tar.js +406 -0
  68. package/package.json +74 -4
package/dist/sync.d.ts ADDED
@@ -0,0 +1,41 @@
1
+ import { type Timestamp } from "@bufbuild/protobuf/wkt";
2
+ import { MeshEvent_Type, type MeshEvent } from "./gen/agentmesh_pb.ts";
3
+ /** The GossipSub topic the control plane publishes mesh events on (api.GossipEvents). */
4
+ export declare const GOSSIP_EVENTS_TOPIC = "/mesh/events/v1";
5
+ /** How far an event's timestamp may be from now; older or later ones are ignored. */
6
+ export declare const EVENT_FRESHNESS_MS: number;
7
+ /**
8
+ * The peers the control plane has banned, with when each ban was learned.
9
+ * A ban recorded at or after the instant a /info answer was requested is
10
+ * kept when that answer omits it: the answer predates the ban and cannot
11
+ * speak to it.
12
+ */
13
+ export declare class BanSet {
14
+ #private;
15
+ has(peerId: string): boolean;
16
+ get size(): number;
17
+ peers(): string[];
18
+ /** Records a ban; returns false when the ban was already known. */
19
+ add(peerId: string, atMs: number): boolean;
20
+ /**
21
+ * Makes the set match the control plane's ban set as of fetchedAt.
22
+ * Returns the peers newly banned and the peers whose ban was lifted.
23
+ */
24
+ reconcile(bannedPeerIds: string[], fetchedAt: Date): {
25
+ banned: string[];
26
+ unbanned: string[];
27
+ };
28
+ }
29
+ /** A MeshEvent that verified: signed by a trusted key and carrying a fresh event_time. */
30
+ /** A MeshEvent that verified: signed by a trusted key, carrying a fresh event_time, its peer id in canonical form. */
31
+ export type VerifiedMeshEvent = MeshEvent & {
32
+ eventTime: Timestamp;
33
+ };
34
+ /**
35
+ * Verifies a MeshEvent as agentmesh-node's verifyEvent does: the signature covers
36
+ * the deterministic encoding of the event with the signature cleared, under
37
+ * any trusted control plane key. Returns the event, or undefined when it
38
+ * does not verify, is not fresh, or bans something that is not a peer ID.
39
+ */
40
+ export declare function verifyMeshEvent(data: Uint8Array, trustedKeys: Uint8Array[], now?: Date): VerifiedMeshEvent | undefined;
41
+ export { MeshEvent_Type };
package/dist/sync.js ADDED
@@ -0,0 +1,111 @@
1
+ // Copyright 2026 Google LLC
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ // What a member keeps in step with the control plane while it runs, as
15
+ // agentmesh-node's controlplane_sync.go: the ban set, reconciled from /info, and
16
+ // the gossip events that bring the next pull forward.
17
+ import { fromBinary, toBinary } from "@bufbuild/protobuf";
18
+ import { timestampMs } from "@bufbuild/protobuf/wkt";
19
+ import { canonicalPeerId, verifyEd25519 } from "./identity.js";
20
+ import { MeshEvent_Type, MeshEventSchema } from "./gen/agentmesh_pb.js";
21
+ /** The GossipSub topic the control plane publishes mesh events on (api.GossipEvents). */
22
+ export const GOSSIP_EVENTS_TOPIC = "/mesh/events/v1";
23
+ /** How far an event's timestamp may be from now; older or later ones are ignored. */
24
+ export const EVENT_FRESHNESS_MS = 5 * 60 * 1000;
25
+ /**
26
+ * The peers the control plane has banned, with when each ban was learned.
27
+ * A ban recorded at or after the instant a /info answer was requested is
28
+ * kept when that answer omits it: the answer predates the ban and cannot
29
+ * speak to it.
30
+ */
31
+ export class BanSet {
32
+ #bannedAt = new Map();
33
+ has(peerId) {
34
+ return this.#bannedAt.has(peerId);
35
+ }
36
+ get size() {
37
+ return this.#bannedAt.size;
38
+ }
39
+ peers() {
40
+ return [...this.#bannedAt.keys()].sort();
41
+ }
42
+ /** Records a ban; returns false when the ban was already known. */
43
+ add(peerId, atMs) {
44
+ if (this.#bannedAt.has(peerId)) {
45
+ return false;
46
+ }
47
+ this.#bannedAt.set(peerId, atMs);
48
+ return true;
49
+ }
50
+ /**
51
+ * Makes the set match the control plane's ban set as of fetchedAt.
52
+ * Returns the peers newly banned and the peers whose ban was lifted.
53
+ */
54
+ reconcile(bannedPeerIds, fetchedAt) {
55
+ const fetchedAtMs = fetchedAt.getTime();
56
+ const current = new Set(bannedPeerIds);
57
+ const unbanned = [];
58
+ for (const [peerId, at] of this.#bannedAt) {
59
+ if (current.has(peerId) || at >= fetchedAtMs) {
60
+ continue;
61
+ }
62
+ this.#bannedAt.delete(peerId);
63
+ unbanned.push(peerId);
64
+ }
65
+ const banned = [];
66
+ for (const peerId of current) {
67
+ if (this.add(peerId, fetchedAtMs)) {
68
+ banned.push(peerId);
69
+ }
70
+ }
71
+ return { banned, unbanned };
72
+ }
73
+ }
74
+ /**
75
+ * Verifies a MeshEvent as agentmesh-node's verifyEvent does: the signature covers
76
+ * the deterministic encoding of the event with the signature cleared, under
77
+ * any trusted control plane key. Returns the event, or undefined when it
78
+ * does not verify, is not fresh, or bans something that is not a peer ID.
79
+ */
80
+ export function verifyMeshEvent(data, trustedKeys, now = new Date()) {
81
+ let event;
82
+ try {
83
+ event = fromBinary(MeshEventSchema, data);
84
+ }
85
+ catch {
86
+ return undefined;
87
+ }
88
+ const signature = event.signature;
89
+ const unsigned = toBinary(MeshEventSchema, { ...event, signature: new Uint8Array() });
90
+ if (!trustedKeys.some((key) => verifyEd25519(key, unsigned, signature))) {
91
+ return undefined;
92
+ }
93
+ if (event.eventTime === undefined) {
94
+ return undefined;
95
+ }
96
+ const skew = Math.abs(now.getTime() - timestampMs(event.eventTime));
97
+ if (skew > EVENT_FRESHNESS_MS) {
98
+ return undefined;
99
+ }
100
+ if (event.type === MeshEvent_Type.BANNED) {
101
+ // Canonicalized after the signature check, which covers the bytes as sent.
102
+ try {
103
+ return { ...event, peerId: canonicalPeerId(event.peerId) };
104
+ }
105
+ catch {
106
+ return undefined;
107
+ }
108
+ }
109
+ return event;
110
+ }
111
+ export { MeshEvent_Type };
package/dist/tar.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ import { type MessageInitShape } from "@bufbuild/protobuf";
2
+ import { TaskAuthorizationRuleSchema, type TaskAuthorizationRule, type TaskRule } from "./gen/agentmesh_pb.ts";
3
+ export declare function validateServicePattern(s: string): void;
4
+ export declare function validateHTTPGrantPath(p: string): void;
5
+ export declare function validateTaskRule(r: TaskRule): void;
6
+ export declare function validateTaskAuthorizationRule(rule: TaskAuthorizationRule, requireNonEmptyRules: boolean): void;
7
+ export declare function encodeTARBlockPayload(ruleInput: MessageInitShape<typeof TaskAuthorizationRuleSchema>): string;
8
+ export declare function encodeTARBlockFact(ruleInput: MessageInitShape<typeof TaskAuthorizationRuleSchema>): string;
9
+ export declare function decodeTARBlockPayload(b64Payload: string): TaskAuthorizationRule;
10
+ export declare function parseTARBlockSource(blockSource: string): TaskAuthorizationRule;
11
+ export declare function effectiveTARExpiration(authorityExpiration: Date, rules: TaskAuthorizationRule[]): Date;
12
+ export interface TaskRequestContext {
13
+ serviceType: string;
14
+ serviceName: string;
15
+ hasHttp: boolean;
16
+ method: string;
17
+ path: string;
18
+ mcpTool: string;
19
+ allowMCPStreamInit: boolean;
20
+ }
21
+ export declare function matchServicePattern(pattern: string, serviceType: string, serviceName: string): boolean;
22
+ export declare function isSafeRequestHTTPPath(reqPath: string): boolean;
23
+ export declare function matchHTTPPath(pattern: string, reqPath: string): boolean;
24
+ export declare function matchTaskRule(rule: TaskRule, req: TaskRequestContext): boolean;
25
+ export declare function evaluateTaskRules(chain: TaskAuthorizationRule[], req: TaskRequestContext, now: Date): void;
package/dist/tar.js ADDED
@@ -0,0 +1,406 @@
1
+ // Copyright 2026 Google LLC
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ import { create, fromBinary, toBinary } from "@bufbuild/protobuf";
15
+ import { timestampDate } from "@bufbuild/protobuf/wkt";
16
+ import { fromBase64Url, toBase64Url } from "./bytes.js";
17
+ import { isServiceType } from "./discovery.js";
18
+ import { BASELINE_DATALOG } from "./gen/datalog.js";
19
+ import { TaskAuthorizationRuleSchema } from "./gen/agentmesh_pb.js";
20
+ const TAR_BLOCK_SOURCE_RE = new RegExp(BASELINE_DATALOG.tar_block_source_pattern);
21
+ const HTTP_METHOD_RE = new RegExp(BASELINE_DATALOG.http_method_syntax);
22
+ const DNS_NAME_RE = /^(?:(?:\*\.)?[a-zA-Z0-9_](?:[a-zA-Z0-9_-]{0,61}[a-zA-Z0-9_])?(?:\.[a-zA-Z0-9_](?:[a-zA-Z0-9_-]{0,61}[a-zA-Z0-9_])?)*(?:\.\*)?|\*)$/;
23
+ const TEXT_ENCODER = new TextEncoder();
24
+ function utf8Length(s) {
25
+ return TEXT_ENCODER.encode(s).length;
26
+ }
27
+ function isPrintableASCII(s) {
28
+ for (let i = 0; i < s.length; i++) {
29
+ const code = s.charCodeAt(i);
30
+ if (code < 0x20 || code > 0x7e) {
31
+ return false;
32
+ }
33
+ }
34
+ return true;
35
+ }
36
+ function hasASCIIControl(s) {
37
+ for (let i = 0; i < s.length; i++) {
38
+ const code = s.charCodeAt(i);
39
+ if (code < 0x20 || code === 0x7f) {
40
+ return true;
41
+ }
42
+ }
43
+ return false;
44
+ }
45
+ function hasEncodedPathTraversal(p) {
46
+ for (let i = 0; i < p.length; i++) {
47
+ const code = p.charCodeAt(i);
48
+ if (code < 0x20 || code === 0x7f || p[i] === "\\" || p[i] === '"') {
49
+ return true;
50
+ }
51
+ if (p[i] === "%" && i + 2 < p.length) {
52
+ const hex = p.slice(i + 1, i + 3).toLowerCase();
53
+ if (hex === "2e" || hex === "2f" || hex === "5c" || hex === "00") {
54
+ return true;
55
+ }
56
+ }
57
+ }
58
+ return false;
59
+ }
60
+ function countChar(s, ch) {
61
+ let count = 0;
62
+ for (let i = 0; i < s.length; i++) {
63
+ if (s[i] === ch) {
64
+ count++;
65
+ }
66
+ }
67
+ return count;
68
+ }
69
+ export function validateServicePattern(s) {
70
+ if (s === "") {
71
+ throw new Error("allowed_services entry cannot be empty");
72
+ }
73
+ if (utf8Length(s) > BASELINE_DATALOG.max_tar_name_length) {
74
+ throw new Error(`allowed_services entry "${s}" exceeds max length ${BASELINE_DATALOG.max_tar_name_length}`);
75
+ }
76
+ if (s === "*") {
77
+ return;
78
+ }
79
+ const idx = s.indexOf("://");
80
+ if (idx <= 0) {
81
+ throw new Error(`invalid service format: ${s}`);
82
+ }
83
+ const serviceType = s.slice(0, idx);
84
+ const target = s.slice(idx + 3);
85
+ if (!isServiceType(serviceType) && serviceType !== "http" && serviceType !== "system") {
86
+ throw new Error(`invalid service type "${serviceType}" in ${s}`);
87
+ }
88
+ if (target === "" || target.includes("/") || target.includes("?") || target.includes("#") || target.includes("@") || countChar(target, "*") > 1) {
89
+ throw new Error(`invalid service target "${target}" in ${s}`);
90
+ }
91
+ if (target !== "*" && !DNS_NAME_RE.test(target)) {
92
+ throw new Error(`invalid service format "${s}": "${target}" is not a valid DNS name`);
93
+ }
94
+ if (serviceType === "egress" && target !== target.toLowerCase()) {
95
+ throw new Error(`invalid egress service "${s}": hostname must be lowercase`);
96
+ }
97
+ if (target.includes("*") && target !== "*") {
98
+ const validSuffix = target.startsWith("*.") && !target.endsWith(".*") && target.slice(2).length > 0;
99
+ const validPrefix = target.endsWith(".*") && !target.startsWith("*.") && target.slice(0, -2).length > 0;
100
+ if (!validSuffix && !validPrefix) {
101
+ throw new Error(`wildcard in "${s}" must be '*', '*.<suffix>' or '<prefix>.*'`);
102
+ }
103
+ }
104
+ }
105
+ export function validateHTTPGrantPath(p) {
106
+ if (utf8Length(p) > BASELINE_DATALOG.max_tar_name_length) {
107
+ throw new Error(`path ${JSON.stringify(p)} exceeds max length ${BASELINE_DATALOG.max_tar_name_length}`);
108
+ }
109
+ if (!p.startsWith("/")) {
110
+ throw new Error(`path ${JSON.stringify(p)} must start with '/'`);
111
+ }
112
+ if (p.includes("?") || p.includes("#")) {
113
+ throw new Error(`path ${JSON.stringify(p)} must not contain '?' or '#'`);
114
+ }
115
+ if (hasEncodedPathTraversal(p)) {
116
+ throw new Error(`path ${JSON.stringify(p)} must not contain encoded traversal sequences or control characters`);
117
+ }
118
+ const stars = countChar(p, "*");
119
+ if (stars > 1 || (stars === 1 && !p.endsWith("*"))) {
120
+ throw new Error(`path ${JSON.stringify(p)}: '*' is only valid once, at the end`);
121
+ }
122
+ for (const seg of p.replace(/\*$/, "").split("/")) {
123
+ if (seg === "." || seg === "..") {
124
+ throw new Error(`path ${JSON.stringify(p)} must not contain '.' or '..' segments`);
125
+ }
126
+ }
127
+ }
128
+ function validateTARStringList(fieldName, values, checkElem) {
129
+ if (values.length > BASELINE_DATALOG.max_entries_per_tar_list) {
130
+ throw new Error(`TaskRule.${fieldName} has ${values.length} entries; maximum is ${BASELINE_DATALOG.max_entries_per_tar_list}`);
131
+ }
132
+ for (const [i, val] of values.entries()) {
133
+ try {
134
+ checkElem(val);
135
+ }
136
+ catch (err) {
137
+ throw new Error(`TaskRule.${fieldName}[${i}]: ${err instanceof Error ? err.message : String(err)}`);
138
+ }
139
+ }
140
+ }
141
+ export function validateTaskRule(r) {
142
+ if (utf8Length(r.description) > BASELINE_DATALOG.max_tar_description_length) {
143
+ throw new Error(`TaskRule.description exceeds ${BASELINE_DATALOG.max_tar_description_length} bytes`);
144
+ }
145
+ if (hasASCIIControl(r.description)) {
146
+ throw new Error("TaskRule.description must not contain control characters");
147
+ }
148
+ if (r.allowedServices.length === 0) {
149
+ throw new Error("TaskRule.allowed_services must not be empty");
150
+ }
151
+ validateTARStringList("allowed_services", r.allowedServices, validateServicePattern);
152
+ validateTARStringList("allowed_resources", r.allowedResources, (res) => {
153
+ if (res === "") {
154
+ throw new Error("resource must not be empty");
155
+ }
156
+ if (utf8Length(res) > BASELINE_DATALOG.max_tar_resource_length) {
157
+ throw new Error(`resource exceeds ${BASELINE_DATALOG.max_tar_resource_length} bytes`);
158
+ }
159
+ if (hasASCIIControl(res)) {
160
+ throw new Error("resource must not contain control characters");
161
+ }
162
+ });
163
+ if (r.operation !== undefined) {
164
+ const op = r.operation;
165
+ validateTARStringList("operation.allowed_tools", op.allowedTools, (tool) => {
166
+ if (tool === "" ||
167
+ utf8Length(tool) > BASELINE_DATALOG.max_tar_name_length ||
168
+ /[/\?# \t\r\n]/.test(tool) ||
169
+ !isPrintableASCII(tool)) {
170
+ throw new Error(`invalid tool name ${JSON.stringify(tool)} in allowed_tools`);
171
+ }
172
+ });
173
+ validateTARStringList("operation.allowed_methods", op.allowedMethods, (m) => {
174
+ if (!HTTP_METHOD_RE.test(m)) {
175
+ throw new Error(`invalid HTTP method ${JSON.stringify(m)}`);
176
+ }
177
+ });
178
+ validateTARStringList("operation.allowed_paths", op.allowedPaths, validateHTTPGrantPath);
179
+ validateTARStringList("operation.allowed_permissions", op.allowedPermissions, (perm) => {
180
+ if (perm === "" ||
181
+ utf8Length(perm) > BASELINE_DATALOG.max_tar_name_length ||
182
+ /[ \t\r\n]/.test(perm) ||
183
+ !isPrintableASCII(perm)) {
184
+ throw new Error(`invalid permission ${JSON.stringify(perm)} in allowed_permissions`);
185
+ }
186
+ });
187
+ }
188
+ }
189
+ export function validateTaskAuthorizationRule(rule, requireNonEmptyRules) {
190
+ if (utf8Length(rule.name) > BASELINE_DATALOG.max_tar_name_length) {
191
+ throw new Error(`TaskAuthorizationRule.name exceeds ${BASELINE_DATALOG.max_tar_name_length} bytes`);
192
+ }
193
+ if (!isPrintableASCII(rule.name)) {
194
+ throw new Error("TaskAuthorizationRule.name must contain only printable ASCII characters");
195
+ }
196
+ if (utf8Length(rule.displayName) > BASELINE_DATALOG.max_tar_description_length) {
197
+ throw new Error(`TaskAuthorizationRule.display_name exceeds ${BASELINE_DATALOG.max_tar_description_length} bytes`);
198
+ }
199
+ if (hasASCIIControl(rule.displayName)) {
200
+ throw new Error("TaskAuthorizationRule.display_name must not contain control characters");
201
+ }
202
+ if (rule.expireTime !== undefined) {
203
+ const { seconds, nanos } = rule.expireTime;
204
+ if (nanos < 0 || nanos >= 1_000_000_000) {
205
+ throw new Error("TaskAuthorizationRule.expire_time has invalid nanos");
206
+ }
207
+ if (seconds <= 0n || seconds > 253402300799n) {
208
+ throw new Error("TaskAuthorizationRule.expire_time must be after the Unix epoch");
209
+ }
210
+ }
211
+ if (requireNonEmptyRules && rule.rules.length === 0) {
212
+ throw new Error("TaskAuthorizationRule.rules must not be empty");
213
+ }
214
+ if (rule.rules.length > BASELINE_DATALOG.max_rules_per_tar) {
215
+ throw new Error(`TaskAuthorizationRule.rules has ${rule.rules.length} entries; maximum is ${BASELINE_DATALOG.max_rules_per_tar}`);
216
+ }
217
+ for (const [i, r] of rule.rules.entries()) {
218
+ try {
219
+ validateTaskRule(r);
220
+ }
221
+ catch (err) {
222
+ throw new Error(`TaskAuthorizationRule.rules[${i}]: ${err instanceof Error ? err.message : String(err)}`);
223
+ }
224
+ }
225
+ }
226
+ function hasUnknownWireFields(rule) {
227
+ const unknownOf = (msg) => {
228
+ if (typeof msg !== "object" || msg === null) {
229
+ return false;
230
+ }
231
+ const unk = msg.$unknown;
232
+ return Array.isArray(unk) && unk.length > 0;
233
+ };
234
+ if (unknownOf(rule) || unknownOf(rule.expireTime)) {
235
+ return true;
236
+ }
237
+ for (const r of rule.rules) {
238
+ if (unknownOf(r) || unknownOf(r.operation)) {
239
+ return true;
240
+ }
241
+ }
242
+ return false;
243
+ }
244
+ export function encodeTARBlockPayload(ruleInput) {
245
+ const rule = create(TaskAuthorizationRuleSchema, ruleInput);
246
+ validateTaskAuthorizationRule(rule, true);
247
+ const raw = toBinary(TaskAuthorizationRuleSchema, rule);
248
+ if (raw.length > BASELINE_DATALOG.max_tar_bytes) {
249
+ throw new Error(`TaskAuthorizationRule serialized size ${raw.length} exceeds ${BASELINE_DATALOG.max_tar_bytes} bytes`);
250
+ }
251
+ return toBase64Url(raw);
252
+ }
253
+ export function encodeTARBlockFact(ruleInput) {
254
+ const b64 = encodeTARBlockPayload(ruleInput);
255
+ return `${BASELINE_DATALOG.fact_tar_block}(${JSON.stringify(b64)})`;
256
+ }
257
+ export function decodeTARBlockPayload(b64Payload) {
258
+ if (b64Payload === "") {
259
+ throw new Error("empty tar_block payload");
260
+ }
261
+ let raw;
262
+ try {
263
+ raw = fromBase64Url(b64Payload);
264
+ }
265
+ catch (err) {
266
+ throw new Error(`invalid base64url in tar_block: ${err instanceof Error ? err.message : String(err)}`);
267
+ }
268
+ if (toBase64Url(raw) !== b64Payload) {
269
+ throw new Error("non-canonical base64url in tar_block");
270
+ }
271
+ if (raw.length === 0 || raw.length > BASELINE_DATALOG.max_tar_bytes) {
272
+ throw new Error(`tar_block decoded payload size ${raw.length} out of bounds [1, ${BASELINE_DATALOG.max_tar_bytes}]`);
273
+ }
274
+ let rule;
275
+ try {
276
+ rule = fromBinary(TaskAuthorizationRuleSchema, raw);
277
+ }
278
+ catch (err) {
279
+ throw new Error(`invalid TaskAuthorizationRule protobuf in tar_block: ${err instanceof Error ? err.message : String(err)}`);
280
+ }
281
+ if (hasUnknownWireFields(rule)) {
282
+ throw new Error("TaskAuthorizationRule in tar_block contains unknown protobuf wire fields");
283
+ }
284
+ validateTaskAuthorizationRule(rule, false);
285
+ return rule;
286
+ }
287
+ export function parseTARBlockSource(blockSource) {
288
+ const m = TAR_BLOCK_SOURCE_RE.exec(blockSource.trim());
289
+ if (!m || m[1] === undefined) {
290
+ throw new Error(`non-authority block does not match single tar_block("<base64url>") grammar`);
291
+ }
292
+ return decodeTARBlockPayload(m[1]);
293
+ }
294
+ export function effectiveTARExpiration(authorityExpiration, rules) {
295
+ let effective = authorityExpiration;
296
+ for (const r of rules) {
297
+ if (r.expireTime !== undefined) {
298
+ const exp = timestampDate(r.expireTime);
299
+ if (exp.getTime() < effective.getTime()) {
300
+ effective = exp;
301
+ }
302
+ }
303
+ }
304
+ return effective;
305
+ }
306
+ export function matchServicePattern(pattern, serviceType, serviceName) {
307
+ if (serviceType === "" || serviceName === "" || pattern === "") {
308
+ return false;
309
+ }
310
+ if (pattern === "*") {
311
+ return true;
312
+ }
313
+ const idx = pattern.indexOf("://");
314
+ if (idx <= 0) {
315
+ return false;
316
+ }
317
+ const patType = pattern.slice(0, idx);
318
+ const patTarget = pattern.slice(idx + 3);
319
+ if (patType !== serviceType || patTarget === "") {
320
+ return false;
321
+ }
322
+ if (patTarget === "*") {
323
+ return true;
324
+ }
325
+ if (patTarget.startsWith("*.") && !patTarget.endsWith(".*")) {
326
+ return serviceName.endsWith(patTarget.slice(1));
327
+ }
328
+ if (patTarget.endsWith(".*") && !patTarget.startsWith("*.")) {
329
+ return serviceName.startsWith(patTarget.slice(0, -1));
330
+ }
331
+ return serviceName === patTarget;
332
+ }
333
+ export function isSafeRequestHTTPPath(reqPath) {
334
+ if (!reqPath.startsWith("/") || reqPath.includes("?") || reqPath.includes("#") || hasEncodedPathTraversal(reqPath)) {
335
+ return false;
336
+ }
337
+ for (const seg of reqPath.split("/")) {
338
+ if (seg === "." || seg === "..") {
339
+ return false;
340
+ }
341
+ }
342
+ return true;
343
+ }
344
+ export function matchHTTPPath(pattern, reqPath) {
345
+ if (pattern === "" || !isSafeRequestHTTPPath(reqPath)) {
346
+ return false;
347
+ }
348
+ if (pattern.endsWith("*")) {
349
+ return reqPath.startsWith(pattern.slice(0, -1));
350
+ }
351
+ return reqPath === pattern;
352
+ }
353
+ export function matchTaskRule(rule, req) {
354
+ if (rule.allowedServices.length === 0) {
355
+ return false;
356
+ }
357
+ if (!rule.allowedServices.some((pat) => matchServicePattern(pat, req.serviceType, req.serviceName))) {
358
+ return false;
359
+ }
360
+ // Note: rule.allowedResources and operation.allowedPermissions are opaque to
361
+ // the wire PEP and consumed by CloudTokenExchanger at egress.
362
+ if (rule.operation !== undefined) {
363
+ const op = rule.operation;
364
+ if (op.allowedTools.length > 0) {
365
+ if (req.serviceType !== "mcp") {
366
+ return false;
367
+ }
368
+ if (req.mcpTool === "") {
369
+ if (!req.allowMCPStreamInit) {
370
+ return false;
371
+ }
372
+ }
373
+ else if (!op.allowedTools.includes(req.mcpTool)) {
374
+ return false;
375
+ }
376
+ }
377
+ if (op.allowedMethods.length > 0 || op.allowedPaths.length > 0) {
378
+ if (!req.hasHttp || req.method === "" || req.method === "CONNECT" || !isSafeRequestHTTPPath(req.path)) {
379
+ return false;
380
+ }
381
+ if (op.allowedMethods.length > 0 && !op.allowedMethods.includes(req.method)) {
382
+ return false;
383
+ }
384
+ if (op.allowedPaths.length > 0 && !op.allowedPaths.some((pat) => matchHTTPPath(pat, req.path))) {
385
+ return false;
386
+ }
387
+ }
388
+ }
389
+ return true;
390
+ }
391
+ export function evaluateTaskRules(chain, req, now) {
392
+ for (const [i, tar] of chain.entries()) {
393
+ if (tar.expireTime !== undefined) {
394
+ const exp = timestampDate(tar.expireTime);
395
+ if (now.getTime() > exp.getTime()) {
396
+ throw new Error(`TaskAuthorizationRule block ${i + 1} (${JSON.stringify(tar.name)}) is expired`);
397
+ }
398
+ }
399
+ if (tar.rules.length === 0) {
400
+ throw new Error(`TaskAuthorizationRule block ${i + 1} (${JSON.stringify(tar.name)}) has no rules (fail-closed)`);
401
+ }
402
+ if (!tar.rules.some((rule) => matchTaskRule(rule, req))) {
403
+ throw new Error(`request denied by TaskAuthorizationRule block ${i + 1} (${JSON.stringify(tar.name)})`);
404
+ }
405
+ }
406
+ }
package/package.json CHANGED
@@ -1,6 +1,76 @@
1
1
  {
2
2
  "name": "@agentmesh-p2p/sdk",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Native SDK for joining an Agent Mesh from a Node.js agent process or a browser page",
5
+ "license": "Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/google/agentmesh.git",
9
+ "directory": "sdk/js"
10
+ },
11
+ "type": "module",
12
+ "engines": {
13
+ "node": ">=22.18"
14
+ },
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "default": "./dist/index.js"
19
+ }
20
+ },
21
+ "browser": {
22
+ "./dist/platform/ingress.js": "./dist/platform/ingress.browser.js",
23
+ "./dist/platform/state.js": "./dist/platform/state.browser.js",
24
+ "./dist/platform/transports.js": "./dist/platform/transports.browser.js",
25
+ "./dist/platform/wasm.js": "./dist/platform/wasm.browser.js"
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "README.md"
30
+ ],
31
+ "publishConfig": {
32
+ "registry": "https://registry.npmjs.org/",
33
+ "access": "public"
34
+ },
35
+ "scripts": {
36
+ "build": "tsc -p tsconfig.build.json",
37
+ "examples": "tsc -p tsconfig.examples.json",
38
+ "typecheck": "tsc -p tsconfig.json --noEmit",
39
+ "pretest": "tsc -p tsconfig.test.json",
40
+ "test": "node --test 'build/**/*.test.js'",
41
+ "bundle:browser": "node scripts/bundle-browser.mjs"
42
+ },
43
+ "dependencies": {
44
+ "@biscuit-auth/biscuit-wasm": "^0.6.0",
45
+ "@bufbuild/protobuf": "^2.16.0",
46
+ "@chainsafe/libp2p-noise": "^17.0.0",
47
+ "@chainsafe/libp2p-yamux": "^8.0.1",
48
+ "@libp2p/circuit-relay-v2": "^4.2.13",
49
+ "@libp2p/crypto": "^5.1.23",
50
+ "@libp2p/gossipsub": "^17.1.2",
51
+ "@libp2p/identify": "^4.1.14",
52
+ "@libp2p/interface": "^3.3.0",
53
+ "@libp2p/peer-id": "^6.0.15",
54
+ "@libp2p/ping": "^3.1.13",
55
+ "@libp2p/tcp": "^11.0.28",
56
+ "@libp2p/tls": "^3.1.10",
57
+ "@libp2p/utils": "^7.4.1",
58
+ "@libp2p/websockets": "^10.1.21",
59
+ "@modelcontextprotocol/sdk": "^1.32.1",
60
+ "@multiformats/multiaddr": "^13.0.3",
61
+ "@noble/curves": "^2.0.1",
62
+ "libp2p": "^3.3.11",
63
+ "multiformats": "^14.0.5",
64
+ "uint8arrays": "^6.1.1",
65
+ "zod": "^4.6.5"
66
+ },
67
+ "devDependencies": {
68
+ "@a2a-js/sdk": "^1.3.0",
69
+ "@bufbuild/protoc-gen-es": "^2.16.0",
70
+ "@types/express": "^5.0.6",
71
+ "@types/node": "^26.6.4",
72
+ "esbuild": "^0.28.2",
73
+ "express": "^5.2.1",
74
+ "typescript": "^7.0.2"
75
+ }
76
+ }