@labacacia/nps-sdk 1.0.0-alpha.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.
Files changed (311) hide show
  1. package/CONTRIBUTING.md +33 -0
  2. package/LICENSE +170 -0
  3. package/NOTICE +7 -0
  4. package/README.md +153 -0
  5. package/dist/codec-CmHeovTV.d.cts +120 -0
  6. package/dist/codec-CmHeovTV.d.ts +120 -0
  7. package/dist/core/anchor-cache.d.ts +42 -0
  8. package/dist/core/anchor-cache.d.ts.map +1 -0
  9. package/dist/core/anchor-cache.js +104 -0
  10. package/dist/core/anchor-cache.js.map +1 -0
  11. package/dist/core/cache.d.ts +14 -0
  12. package/dist/core/cache.d.ts.map +1 -0
  13. package/dist/core/cache.js +80 -0
  14. package/dist/core/cache.js.map +1 -0
  15. package/dist/core/canonical-json.d.ts +12 -0
  16. package/dist/core/canonical-json.d.ts.map +1 -0
  17. package/dist/core/canonical-json.js +44 -0
  18. package/dist/core/canonical-json.js.map +1 -0
  19. package/dist/core/codec.d.ts +32 -0
  20. package/dist/core/codec.d.ts.map +1 -0
  21. package/dist/core/codec.js +119 -0
  22. package/dist/core/codec.js.map +1 -0
  23. package/dist/core/codecs/index.d.ts +4 -0
  24. package/dist/core/codecs/index.d.ts.map +1 -0
  25. package/dist/core/codecs/index.js +6 -0
  26. package/dist/core/codecs/index.js.map +1 -0
  27. package/dist/core/codecs/ncp-codec.d.ts +39 -0
  28. package/dist/core/codecs/ncp-codec.d.ts.map +1 -0
  29. package/dist/core/codecs/ncp-codec.js +93 -0
  30. package/dist/core/codecs/ncp-codec.js.map +1 -0
  31. package/dist/core/codecs/tier1-json-codec.d.ts +10 -0
  32. package/dist/core/codecs/tier1-json-codec.d.ts.map +1 -0
  33. package/dist/core/codecs/tier1-json-codec.js +28 -0
  34. package/dist/core/codecs/tier1-json-codec.js.map +1 -0
  35. package/dist/core/codecs/tier2-msgpack-codec.d.ts +10 -0
  36. package/dist/core/codecs/tier2-msgpack-codec.d.ts.map +1 -0
  37. package/dist/core/codecs/tier2-msgpack-codec.js +26 -0
  38. package/dist/core/codecs/tier2-msgpack-codec.js.map +1 -0
  39. package/dist/core/crypto-provider.d.ts +31 -0
  40. package/dist/core/crypto-provider.d.ts.map +1 -0
  41. package/dist/core/crypto-provider.js +10 -0
  42. package/dist/core/crypto-provider.js.map +1 -0
  43. package/dist/core/exceptions.d.ts +27 -0
  44. package/dist/core/exceptions.d.ts.map +1 -0
  45. package/dist/core/exceptions.js +52 -0
  46. package/dist/core/exceptions.js.map +1 -0
  47. package/dist/core/frame-header.d.ts +87 -0
  48. package/dist/core/frame-header.d.ts.map +1 -0
  49. package/dist/core/frame-header.js +185 -0
  50. package/dist/core/frame-header.js.map +1 -0
  51. package/dist/core/frame-registry.d.ts +35 -0
  52. package/dist/core/frame-registry.d.ts.map +1 -0
  53. package/dist/core/frame-registry.js +63 -0
  54. package/dist/core/frame-registry.js.map +1 -0
  55. package/dist/core/frames.d.ts +80 -0
  56. package/dist/core/frames.d.ts.map +1 -0
  57. package/dist/core/frames.js +153 -0
  58. package/dist/core/frames.js.map +1 -0
  59. package/dist/core/index.cjs +371 -0
  60. package/dist/core/index.cjs.map +1 -0
  61. package/dist/core/index.d.cts +41 -0
  62. package/dist/core/index.d.ts +9 -0
  63. package/dist/core/index.d.ts.map +1 -0
  64. package/dist/core/index.js +10 -0
  65. package/dist/core/index.js.map +1 -0
  66. package/dist/core/registry.d.ts +11 -0
  67. package/dist/core/registry.d.ts.map +1 -0
  68. package/dist/core/registry.js +17 -0
  69. package/dist/core/registry.js.map +1 -0
  70. package/dist/core/status-codes.d.ts +28 -0
  71. package/dist/core/status-codes.d.ts.map +1 -0
  72. package/dist/core/status-codes.js +38 -0
  73. package/dist/core/status-codes.js.map +1 -0
  74. package/dist/frames-B3qLdl_g.d.cts +77 -0
  75. package/dist/frames-Ff7-ZPUl.d.ts +77 -0
  76. package/dist/index.cjs +1556 -0
  77. package/dist/index.cjs.map +1 -0
  78. package/dist/index.d.cts +21 -0
  79. package/dist/index.d.ts +2 -0
  80. package/dist/index.d.ts.map +1 -0
  81. package/dist/index.js +10 -0
  82. package/dist/index.js.map +1 -0
  83. package/dist/ncp/frames/anchor-frame.d.ts +29 -0
  84. package/dist/ncp/frames/anchor-frame.d.ts.map +1 -0
  85. package/dist/ncp/frames/anchor-frame.js +54 -0
  86. package/dist/ncp/frames/anchor-frame.js.map +1 -0
  87. package/dist/ncp/frames/caps-frame.d.ts +29 -0
  88. package/dist/ncp/frames/caps-frame.d.ts.map +1 -0
  89. package/dist/ncp/frames/caps-frame.js +29 -0
  90. package/dist/ncp/frames/caps-frame.js.map +1 -0
  91. package/dist/ncp/frames/diff-frame.d.ts +32 -0
  92. package/dist/ncp/frames/diff-frame.d.ts.map +1 -0
  93. package/dist/ncp/frames/diff-frame.js +37 -0
  94. package/dist/ncp/frames/diff-frame.js.map +1 -0
  95. package/dist/ncp/frames/error-frame.d.ts +16 -0
  96. package/dist/ncp/frames/error-frame.d.ts.map +1 -0
  97. package/dist/ncp/frames/error-frame.js +13 -0
  98. package/dist/ncp/frames/error-frame.js.map +1 -0
  99. package/dist/ncp/frames/hello-frame.d.ts +21 -0
  100. package/dist/ncp/frames/hello-frame.d.ts.map +1 -0
  101. package/dist/ncp/frames/hello-frame.js +25 -0
  102. package/dist/ncp/frames/hello-frame.js.map +1 -0
  103. package/dist/ncp/frames/stream-frame.d.ts +16 -0
  104. package/dist/ncp/frames/stream-frame.d.ts.map +1 -0
  105. package/dist/ncp/frames/stream-frame.js +18 -0
  106. package/dist/ncp/frames/stream-frame.js.map +1 -0
  107. package/dist/ncp/frames.d.ts +76 -0
  108. package/dist/ncp/frames.d.ts.map +1 -0
  109. package/dist/ncp/frames.js +147 -0
  110. package/dist/ncp/frames.js.map +1 -0
  111. package/dist/ncp/handshake.d.ts +30 -0
  112. package/dist/ncp/handshake.d.ts.map +1 -0
  113. package/dist/ncp/handshake.js +80 -0
  114. package/dist/ncp/handshake.js.map +1 -0
  115. package/dist/ncp/index.cjs +188 -0
  116. package/dist/ncp/index.cjs.map +1 -0
  117. package/dist/ncp/index.d.cts +6 -0
  118. package/dist/ncp/index.d.ts +11 -0
  119. package/dist/ncp/index.d.ts.map +1 -0
  120. package/dist/ncp/index.js +13 -0
  121. package/dist/ncp/index.js.map +1 -0
  122. package/dist/ncp/ncp-error-codes.d.ts +22 -0
  123. package/dist/ncp/ncp-error-codes.d.ts.map +1 -0
  124. package/dist/ncp/ncp-error-codes.js +32 -0
  125. package/dist/ncp/ncp-error-codes.js.map +1 -0
  126. package/dist/ncp/ncp-patch-format.d.ts +7 -0
  127. package/dist/ncp/ncp-patch-format.d.ts.map +1 -0
  128. package/dist/ncp/ncp-patch-format.js +13 -0
  129. package/dist/ncp/ncp-patch-format.js.map +1 -0
  130. package/dist/ncp/registry.d.ts +3 -0
  131. package/dist/ncp/registry.d.ts.map +1 -0
  132. package/dist/ncp/registry.js +12 -0
  133. package/dist/ncp/registry.js.map +1 -0
  134. package/dist/ncp/stream-manager.d.ts +57 -0
  135. package/dist/ncp/stream-manager.d.ts.map +1 -0
  136. package/dist/ncp/stream-manager.js +163 -0
  137. package/dist/ncp/stream-manager.js.map +1 -0
  138. package/dist/ndp/frames.d.ts +56 -0
  139. package/dist/ndp/frames.d.ts.map +1 -0
  140. package/dist/ndp/frames.js +87 -0
  141. package/dist/ndp/frames.js.map +1 -0
  142. package/dist/ndp/index.cjs +252 -0
  143. package/dist/ndp/index.cjs.map +1 -0
  144. package/dist/ndp/index.d.cts +86 -0
  145. package/dist/ndp/index.d.ts +5 -0
  146. package/dist/ndp/index.d.ts.map +1 -0
  147. package/dist/ndp/index.js +7 -0
  148. package/dist/ndp/index.js.map +1 -0
  149. package/dist/ndp/ndp-registry.d.ts +11 -0
  150. package/dist/ndp/ndp-registry.d.ts.map +1 -0
  151. package/dist/ndp/ndp-registry.js +79 -0
  152. package/dist/ndp/ndp-registry.js.map +1 -0
  153. package/dist/ndp/registry.d.ts +3 -0
  154. package/dist/ndp/registry.d.ts.map +1 -0
  155. package/dist/ndp/registry.js +10 -0
  156. package/dist/ndp/registry.js.map +1 -0
  157. package/dist/ndp/validator.d.ts +18 -0
  158. package/dist/ndp/validator.d.ts.map +1 -0
  159. package/dist/ndp/validator.js +48 -0
  160. package/dist/ndp/validator.js.map +1 -0
  161. package/dist/nip/frames.d.ts +44 -0
  162. package/dist/nip/frames.d.ts.map +1 -0
  163. package/dist/nip/frames.js +81 -0
  164. package/dist/nip/frames.js.map +1 -0
  165. package/dist/nip/identity.d.ts +18 -0
  166. package/dist/nip/identity.d.ts.map +1 -0
  167. package/dist/nip/identity.js +94 -0
  168. package/dist/nip/identity.js.map +1 -0
  169. package/dist/nip/index.cjs +214 -0
  170. package/dist/nip/index.cjs.map +1 -0
  171. package/dist/nip/index.d.cts +65 -0
  172. package/dist/nip/index.d.ts +4 -0
  173. package/dist/nip/index.d.ts.map +1 -0
  174. package/dist/nip/index.js +6 -0
  175. package/dist/nip/index.js.map +1 -0
  176. package/dist/nip/registry.d.ts +3 -0
  177. package/dist/nip/registry.d.ts.map +1 -0
  178. package/dist/nip/registry.js +10 -0
  179. package/dist/nip/registry.js.map +1 -0
  180. package/dist/nop/client.d.ts +34 -0
  181. package/dist/nop/client.d.ts.map +1 -0
  182. package/dist/nop/client.js +90 -0
  183. package/dist/nop/client.js.map +1 -0
  184. package/dist/nop/frames.d.ts +65 -0
  185. package/dist/nop/frames.d.ts.map +1 -0
  186. package/dist/nop/frames.js +148 -0
  187. package/dist/nop/frames.js.map +1 -0
  188. package/dist/nop/index.cjs +762 -0
  189. package/dist/nop/index.cjs.map +1 -0
  190. package/dist/nop/index.d.cts +155 -0
  191. package/dist/nop/index.d.ts +5 -0
  192. package/dist/nop/index.d.ts.map +1 -0
  193. package/dist/nop/index.js +7 -0
  194. package/dist/nop/index.js.map +1 -0
  195. package/dist/nop/models.d.ts +58 -0
  196. package/dist/nop/models.d.ts.map +1 -0
  197. package/dist/nop/models.js +50 -0
  198. package/dist/nop/models.js.map +1 -0
  199. package/dist/nop/nop-types.d.ts +136 -0
  200. package/dist/nop/nop-types.d.ts.map +1 -0
  201. package/dist/nop/nop-types.js +44 -0
  202. package/dist/nop/nop-types.js.map +1 -0
  203. package/dist/nop/registry.d.ts +3 -0
  204. package/dist/nop/registry.d.ts.map +1 -0
  205. package/dist/nop/registry.js +11 -0
  206. package/dist/nop/registry.js.map +1 -0
  207. package/dist/nwp/client.d.ts +22 -0
  208. package/dist/nwp/client.d.ts.map +1 -0
  209. package/dist/nwp/client.js +101 -0
  210. package/dist/nwp/client.js.map +1 -0
  211. package/dist/nwp/frames.d.ts +46 -0
  212. package/dist/nwp/frames.d.ts.map +1 -0
  213. package/dist/nwp/frames.js +81 -0
  214. package/dist/nwp/frames.js.map +1 -0
  215. package/dist/nwp/index.cjs +658 -0
  216. package/dist/nwp/index.cjs.map +1 -0
  217. package/dist/nwp/index.d.cts +65 -0
  218. package/dist/nwp/index.d.ts +4 -0
  219. package/dist/nwp/index.d.ts.map +1 -0
  220. package/dist/nwp/index.js +6 -0
  221. package/dist/nwp/index.js.map +1 -0
  222. package/dist/nwp/registry.d.ts +3 -0
  223. package/dist/nwp/registry.d.ts.map +1 -0
  224. package/dist/nwp/registry.js +9 -0
  225. package/dist/nwp/registry.js.map +1 -0
  226. package/dist/setup.d.ts +10 -0
  227. package/dist/setup.d.ts.map +1 -0
  228. package/dist/setup.js +29 -0
  229. package/dist/setup.js.map +1 -0
  230. package/nip-ca-server/Dockerfile +27 -0
  231. package/nip-ca-server/README.md +45 -0
  232. package/nip-ca-server/db/001_init.sql +25 -0
  233. package/nip-ca-server/docker-compose.yml +29 -0
  234. package/nip-ca-server/package.json +23 -0
  235. package/nip-ca-server/src/ca.ts +155 -0
  236. package/nip-ca-server/src/db.ts +104 -0
  237. package/nip-ca-server/src/index.ts +157 -0
  238. package/nip-ca-server/tsconfig.json +13 -0
  239. package/package.json +47 -0
  240. package/src/core/anchor-cache.ts +129 -0
  241. package/src/core/cache.ts +93 -0
  242. package/src/core/canonical-json.ts +50 -0
  243. package/src/core/codec.ts +158 -0
  244. package/src/core/codecs/index.ts +5 -0
  245. package/src/core/codecs/ncp-codec.ts +170 -0
  246. package/src/core/codecs/tier1-json-codec.ts +33 -0
  247. package/src/core/codecs/tier2-msgpack-codec.ts +30 -0
  248. package/src/core/crypto-provider.ts +47 -0
  249. package/src/core/exceptions.ts +57 -0
  250. package/src/core/frame-header.ts +282 -0
  251. package/src/core/frame-registry.ts +91 -0
  252. package/src/core/frames.ts +183 -0
  253. package/src/core/index.ts +10 -0
  254. package/src/core/registry.ts +28 -0
  255. package/src/core/status-codes.ts +46 -0
  256. package/src/index.ts +10 -0
  257. package/src/ncp/frames/anchor-frame.ts +87 -0
  258. package/src/ncp/frames/caps-frame.ts +59 -0
  259. package/src/ncp/frames/diff-frame.ts +69 -0
  260. package/src/ncp/frames/error-frame.ts +26 -0
  261. package/src/ncp/frames/hello-frame.ts +50 -0
  262. package/src/ncp/frames/stream-frame.ts +35 -0
  263. package/src/ncp/frames.ts +199 -0
  264. package/src/ncp/handshake.ts +95 -0
  265. package/src/ncp/index.ts +12 -0
  266. package/src/ncp/ncp-error-codes.ts +34 -0
  267. package/src/ncp/ncp-patch-format.ts +16 -0
  268. package/src/ncp/registry.ts +14 -0
  269. package/src/ncp/stream-manager.ts +212 -0
  270. package/src/ndp/frames.ts +124 -0
  271. package/src/ndp/index.ts +7 -0
  272. package/src/ndp/ndp-registry.ts +82 -0
  273. package/src/ndp/registry.ts +12 -0
  274. package/src/ndp/validator.ts +64 -0
  275. package/src/nip/frames.ts +106 -0
  276. package/src/nip/identity.ts +113 -0
  277. package/src/nip/index.ts +6 -0
  278. package/src/nip/registry.ts +12 -0
  279. package/src/nop/client.ts +103 -0
  280. package/src/nop/frames.ts +181 -0
  281. package/src/nop/index.ts +7 -0
  282. package/src/nop/models.ts +79 -0
  283. package/src/nop/nop-types.ts +208 -0
  284. package/src/nop/registry.ts +13 -0
  285. package/src/nwp/client.ts +114 -0
  286. package/src/nwp/frames.ts +116 -0
  287. package/src/nwp/index.ts +6 -0
  288. package/src/nwp/registry.ts +11 -0
  289. package/src/setup.ts +32 -0
  290. package/tests/core/anchor-cache.test.ts +242 -0
  291. package/tests/core/codec.test.ts +205 -0
  292. package/tests/core/frame-registry.test.ts +46 -0
  293. package/tests/core.test.ts +327 -0
  294. package/tests/ncp/diff-binary-bitset.test.ts +107 -0
  295. package/tests/ncp/e2e-enc-reject.test.ts +93 -0
  296. package/tests/ncp/err-error-frame.test.ts +152 -0
  297. package/tests/ncp/frames.test.ts +359 -0
  298. package/tests/ncp/framing.test.ts +233 -0
  299. package/tests/ncp/hello-frame.test.ts +122 -0
  300. package/tests/ncp/inline-anchor.test.ts +88 -0
  301. package/tests/ncp/security.test.ts +184 -0
  302. package/tests/ncp/stream-window.test.ts +167 -0
  303. package/tests/ncp/stream.test.ts +242 -0
  304. package/tests/ncp/version-negotiation.test.ts +123 -0
  305. package/tests/ndp.test.ts +271 -0
  306. package/tests/nip.test.ts +184 -0
  307. package/tests/nop.test.ts +344 -0
  308. package/tests/nwp.test.ts +237 -0
  309. package/tsconfig.json +20 -0
  310. package/tsup.config.ts +20 -0
  311. package/vitest.config.ts +10 -0
@@ -0,0 +1,113 @@
1
+ // Copyright 2026 INNO LOTUS PTY LTD
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ /**
5
+ * NipIdentity — Ed25519 key management and signing for NPS NID identity.
6
+ * Uses @noble/ed25519 for signing; node:crypto for key storage encryption.
7
+ */
8
+
9
+ import * as ed25519 from "@noble/ed25519";
10
+ import { sha512 } from "@noble/hashes/sha512";
11
+ import { createCipheriv, createDecipheriv, pbkdf2Sync, randomBytes } from "node:crypto";
12
+ import { readFileSync, writeFileSync } from "node:fs";
13
+
14
+ // noble/ed25519 requires sha512 to be set explicitly in Node environments
15
+ ed25519.etc.sha512Sync = (...m) => sha512(ed25519.etc.concatBytes(...m));
16
+
17
+ const KEY_FILE_VERSION = 1;
18
+ const PBKDF2_ITERS = 600_000;
19
+ const SALT_BYTES = 16;
20
+ const IV_BYTES = 12;
21
+ const KEY_BYTES = 32;
22
+
23
+ interface KeyFileEnvelope {
24
+ version: number;
25
+ salt: string; // hex
26
+ iv: string; // hex
27
+ ciphertext: string; // hex
28
+ pubKey: string; // hex
29
+ }
30
+
31
+ export class NipIdentity {
32
+ private constructor(
33
+ private readonly _privKey: Uint8Array,
34
+ public readonly pubKey: Uint8Array,
35
+ ) {}
36
+
37
+ // ── Factory ───────────────────────────────────────────────────────────────
38
+
39
+ static generate(): NipIdentity {
40
+ const priv = ed25519.utils.randomPrivateKey();
41
+ const pub = ed25519.getPublicKey(priv);
42
+ return new NipIdentity(priv, pub);
43
+ }
44
+
45
+ static fromPrivateKey(privKey: Uint8Array): NipIdentity {
46
+ const pub = ed25519.getPublicKey(privKey);
47
+ return new NipIdentity(privKey, pub);
48
+ }
49
+
50
+ /** Load from an AES-256-GCM encrypted key file. */
51
+ static load(path: string, passphrase: string): NipIdentity {
52
+ const envelope = JSON.parse(readFileSync(path, "utf8")) as KeyFileEnvelope;
53
+ const salt = Buffer.from(envelope.salt, "hex");
54
+ const iv = Buffer.from(envelope.iv, "hex");
55
+ const ct = Buffer.from(envelope.ciphertext, "hex");
56
+
57
+ const dk = pbkdf2Sync(passphrase, salt, PBKDF2_ITERS, KEY_BYTES, "sha256");
58
+ const decipher = createDecipheriv("aes-256-gcm", dk, iv);
59
+ // Last 16 bytes of ciphertext are the GCM auth tag
60
+ const authTag = ct.slice(ct.length - 16);
61
+ const body = ct.slice(0, ct.length - 16);
62
+ (decipher as ReturnType<typeof createDecipheriv> & { setAuthTag(tag: Buffer): void }).setAuthTag(authTag);
63
+ const priv = Buffer.concat([decipher.update(body), decipher.final()]);
64
+ return NipIdentity.fromPrivateKey(new Uint8Array(priv));
65
+ }
66
+
67
+ /** Save to an AES-256-GCM encrypted key file. */
68
+ save(path: string, passphrase: string): void {
69
+ const salt = randomBytes(SALT_BYTES);
70
+ const iv = randomBytes(IV_BYTES);
71
+ const dk = pbkdf2Sync(passphrase, salt, PBKDF2_ITERS, KEY_BYTES, "sha256");
72
+ const cipher = createCipheriv("aes-256-gcm", dk, iv);
73
+ const body = Buffer.concat([cipher.update(Buffer.from(this._privKey)), cipher.final()]);
74
+ const tag = (cipher as ReturnType<typeof createCipheriv> & { getAuthTag(): Buffer }).getAuthTag();
75
+
76
+ const envelope: KeyFileEnvelope = {
77
+ version: KEY_FILE_VERSION,
78
+ salt: salt.toString("hex"),
79
+ iv: iv.toString("hex"),
80
+ ciphertext: Buffer.concat([body, tag]).toString("hex"),
81
+ pubKey: Buffer.from(this.pubKey).toString("hex"),
82
+ };
83
+ writeFileSync(path, JSON.stringify(envelope, null, 2), "utf8");
84
+ }
85
+
86
+ // ── Signing ───────────────────────────────────────────────────────────────
87
+
88
+ /** Sign a dict payload. Returns `ed25519:<base64url>`. */
89
+ sign(payload: Record<string, unknown>): string {
90
+ const canonical = JSON.stringify(payload, Object.keys(payload).sort());
91
+ const bytes = new TextEncoder().encode(canonical);
92
+ const sig = ed25519.sign(bytes, this._privKey);
93
+ return `ed25519:${Buffer.from(sig).toString("base64")}`;
94
+ }
95
+
96
+ /** Verify a signature string against a dict payload. */
97
+ verify(payload: Record<string, unknown>, signature: string): boolean {
98
+ if (!signature.startsWith("ed25519:")) return false;
99
+ try {
100
+ const canonical = JSON.stringify(payload, Object.keys(payload).sort());
101
+ const bytes = new TextEncoder().encode(canonical);
102
+ const sigBytes = Buffer.from(signature.slice("ed25519:".length), "base64");
103
+ return ed25519.verify(sigBytes, bytes, this.pubKey);
104
+ } catch {
105
+ return false;
106
+ }
107
+ }
108
+
109
+ /** Public key as `ed25519:<hex>` string. */
110
+ get pubKeyString(): string {
111
+ return `ed25519:${Buffer.from(this.pubKey).toString("hex")}`;
112
+ }
113
+ }
@@ -0,0 +1,6 @@
1
+ // Copyright 2026 INNO LOTUS PTY LTD
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ export * from "./frames.js";
5
+ export * from "./identity.js";
6
+ export { registerNipFrames } from "./registry.js";
@@ -0,0 +1,12 @@
1
+ // Copyright 2026 INNO LOTUS PTY LTD
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { FrameRegistry } from "../core/registry.js";
5
+ import { FrameType } from "../core/frames.js";
6
+ import { IdentFrame, TrustFrame, RevokeFrame } from "./frames.js";
7
+
8
+ export function registerNipFrames(registry: FrameRegistry): void {
9
+ registry.register(FrameType.IDENT, IdentFrame);
10
+ registry.register(FrameType.TRUST, TrustFrame);
11
+ registry.register(FrameType.REVOKE, RevokeFrame);
12
+ }
@@ -0,0 +1,103 @@
1
+ // Copyright 2026 INNO LOTUS PTY LTD
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { NpsFrameCodec } from "../core/codec.js";
5
+ import { EncodingTier } from "../core/frames.js";
6
+ import { FrameRegistry } from "../core/registry.js";
7
+ import { registerNcpFrames } from "../ncp/registry.js";
8
+ import { registerNopFrames } from "./registry.js";
9
+ import { TaskState } from "./models.js";
10
+ import type { TaskFrame } from "./frames.js";
11
+
12
+ const TERMINAL_STATES = new Set<TaskState>([
13
+ TaskState.COMPLETED,
14
+ TaskState.FAILED,
15
+ TaskState.CANCELLED,
16
+ ]);
17
+
18
+ export class NopTaskStatus {
19
+ constructor(private readonly _raw: Record<string, unknown>) {}
20
+
21
+ get taskId(): string { return this._raw["task_id"] as string; }
22
+ get state(): TaskState { return this._raw["state"] as TaskState; }
23
+ get isTerminal(): boolean { return TERMINAL_STATES.has(this._raw["state"] as TaskState); }
24
+ get aggregatedResult(): unknown { return this._raw["aggregated_result"]; }
25
+ get errorCode(): string | undefined { return (this._raw["error_code"] as string | null) ?? undefined; }
26
+ get errorMessage(): string | undefined { return (this._raw["error_message"] as string | null) ?? undefined; }
27
+ get nodeResults(): Record<string, unknown> { return (this._raw["node_results"] as Record<string, unknown> | undefined) ?? {}; }
28
+ get raw(): Record<string, unknown> { return this._raw; }
29
+
30
+ toString(): string {
31
+ return `NopTaskStatus(taskId=${this.taskId}, state=${String(this._raw["state"])})`;
32
+ }
33
+ }
34
+
35
+ export class NopClient {
36
+ private readonly _baseUrl: string;
37
+ private readonly _codec: NpsFrameCodec;
38
+ private readonly _tier: EncodingTier;
39
+
40
+ constructor(
41
+ baseUrl: string,
42
+ options: { defaultTier?: EncodingTier; registry?: FrameRegistry } = {},
43
+ ) {
44
+ this._baseUrl = baseUrl.replace(/\/$/, "");
45
+ this._tier = options.defaultTier ?? EncodingTier.MSGPACK;
46
+
47
+ const registry = options.registry ?? (() => {
48
+ const r = new FrameRegistry();
49
+ registerNcpFrames(r);
50
+ registerNopFrames(r);
51
+ return r;
52
+ })();
53
+ this._codec = new NpsFrameCodec(registry);
54
+ }
55
+
56
+ async submit(frame: TaskFrame): Promise<string> {
57
+ const wire = this._codec.encode(frame, { overrideTier: this._tier });
58
+ const res = await fetch(`${this._baseUrl}/task`, {
59
+ method: "POST",
60
+ body: wire,
61
+ headers: { "Content-Type": "application/x-nps-frame", "Accept": "application/json" },
62
+ });
63
+ if (!res.ok) throw new Error(`NOP /task failed: HTTP ${res.status}`);
64
+ const data = await res.json() as Record<string, unknown>;
65
+ return data["task_id"] as string;
66
+ }
67
+
68
+ async getStatus(taskId: string): Promise<NopTaskStatus> {
69
+ const res = await fetch(`${this._baseUrl}/task/${taskId}`, {
70
+ headers: { "Accept": "application/json" },
71
+ });
72
+ if (!res.ok) throw new Error(`NOP /task/${taskId} failed: HTTP ${res.status}`);
73
+ return new NopTaskStatus(await res.json() as Record<string, unknown>);
74
+ }
75
+
76
+ async cancel(taskId: string): Promise<void> {
77
+ const res = await fetch(`${this._baseUrl}/task/${taskId}/cancel`, {
78
+ method: "POST",
79
+ headers: { "Accept": "application/json" },
80
+ });
81
+ if (!res.ok) throw new Error(`NOP /task/${taskId}/cancel failed: HTTP ${res.status}`);
82
+ }
83
+
84
+ async wait(
85
+ taskId: string,
86
+ options: { pollIntervalMs?: number; timeoutMs?: number } = {},
87
+ ): Promise<NopTaskStatus> {
88
+ const pollIntervalMs = options.pollIntervalMs ?? 1000;
89
+ const timeoutMs = options.timeoutMs ?? 30_000;
90
+ const deadline = Date.now() + timeoutMs;
91
+
92
+ while (true) {
93
+ const status = await this.getStatus(taskId);
94
+ if (status.isTerminal) return status;
95
+
96
+ const remaining = deadline - Date.now();
97
+ if (remaining <= 0) {
98
+ throw new Error(`Task '${taskId}' did not complete within ${timeoutMs}ms (state: ${String(status.raw["state"])}).`);
99
+ }
100
+ await new Promise((resolve) => setTimeout(resolve, Math.min(pollIntervalMs, remaining)));
101
+ }
102
+ }
103
+ }
@@ -0,0 +1,181 @@
1
+ // Copyright 2026 INNO LOTUS PTY LTD
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { EncodingTier, FrameType } from "../core/frames.js";
5
+ import type { NpsFrame } from "../core/codec.js";
6
+ import type { AggregateStrategy, TaskContext, TaskDag, TaskPriority } from "./models.js";
7
+
8
+ // ── TaskFrame ─────────────────────────────────────────────────────────────────
9
+
10
+ export class TaskFrame implements NpsFrame {
11
+ readonly frameType = FrameType.TASK;
12
+ readonly preferredTier = EncodingTier.MSGPACK;
13
+
14
+ constructor(
15
+ public readonly taskId: string,
16
+ public readonly dag: TaskDag,
17
+ public readonly timeoutMs?: number,
18
+ public readonly callbackUrl?: string,
19
+ public readonly context?: TaskContext,
20
+ public readonly priority?: TaskPriority,
21
+ public readonly depth?: number,
22
+ ) {}
23
+
24
+ toDict(): Record<string, unknown> {
25
+ return {
26
+ task_id: this.taskId,
27
+ dag: this.dag,
28
+ timeout_ms: this.timeoutMs ?? null,
29
+ callback_url: this.callbackUrl ?? null,
30
+ context: this.context ?? null,
31
+ priority: this.priority ?? null,
32
+ depth: this.depth ?? null,
33
+ };
34
+ }
35
+
36
+ static fromDict(data: Record<string, unknown>): TaskFrame {
37
+ return new TaskFrame(
38
+ data["task_id"] as string,
39
+ data["dag"] as TaskDag,
40
+ (data["timeout_ms"] as number | null) ?? undefined,
41
+ (data["callback_url"] as string | null) ?? undefined,
42
+ (data["context"] as TaskContext | null) ?? undefined,
43
+ (data["priority"] as TaskPriority | null) ?? undefined,
44
+ (data["depth"] as number | null) ?? undefined,
45
+ );
46
+ }
47
+ }
48
+
49
+ // ── DelegateFrame ─────────────────────────────────────────────────────────────
50
+
51
+ export class DelegateFrame implements NpsFrame {
52
+ readonly frameType = FrameType.DELEGATE;
53
+ readonly preferredTier = EncodingTier.MSGPACK;
54
+
55
+ constructor(
56
+ public readonly taskId: string,
57
+ public readonly subtaskId: string,
58
+ public readonly action: string,
59
+ public readonly agentNid: string,
60
+ public readonly inputs?: Record<string, unknown>,
61
+ public readonly params?: Record<string, unknown>,
62
+ public readonly idempotencyKey?: string,
63
+ ) {}
64
+
65
+ toDict(): Record<string, unknown> {
66
+ return {
67
+ task_id: this.taskId,
68
+ subtask_id: this.subtaskId,
69
+ action: this.action,
70
+ agent_nid: this.agentNid,
71
+ inputs: this.inputs ?? null,
72
+ params: this.params ?? null,
73
+ idempotency_key: this.idempotencyKey ?? null,
74
+ };
75
+ }
76
+
77
+ static fromDict(data: Record<string, unknown>): DelegateFrame {
78
+ return new DelegateFrame(
79
+ data["task_id"] as string,
80
+ data["subtask_id"] as string,
81
+ data["action"] as string,
82
+ data["agent_nid"] as string,
83
+ (data["inputs"] as Record<string, unknown> | null) ?? undefined,
84
+ (data["params"] as Record<string, unknown> | null) ?? undefined,
85
+ (data["idempotency_key"] as string | null) ?? undefined,
86
+ );
87
+ }
88
+ }
89
+
90
+ // ── SyncFrame ─────────────────────────────────────────────────────────────────
91
+
92
+ export class SyncFrame implements NpsFrame {
93
+ readonly frameType = FrameType.SYNC;
94
+ readonly preferredTier = EncodingTier.MSGPACK;
95
+
96
+ constructor(
97
+ public readonly taskId: string,
98
+ public readonly syncId: string,
99
+ public readonly waitFor: readonly string[],
100
+ public readonly minRequired: number = 0,
101
+ public readonly aggregate: AggregateStrategy | string = "merge",
102
+ public readonly timeoutMs?: number,
103
+ ) {}
104
+
105
+ toDict(): Record<string, unknown> {
106
+ return {
107
+ task_id: this.taskId,
108
+ sync_id: this.syncId,
109
+ wait_for: this.waitFor,
110
+ min_required: this.minRequired,
111
+ aggregate: this.aggregate,
112
+ timeout_ms: this.timeoutMs ?? null,
113
+ };
114
+ }
115
+
116
+ static fromDict(data: Record<string, unknown>): SyncFrame {
117
+ return new SyncFrame(
118
+ data["task_id"] as string,
119
+ data["sync_id"] as string,
120
+ data["wait_for"] as string[],
121
+ (data["min_required"] as number) ?? 0,
122
+ (data["aggregate"] as string) ?? "merge",
123
+ (data["timeout_ms"] as number | null) ?? undefined,
124
+ );
125
+ }
126
+ }
127
+
128
+ // ── StreamError ───────────────────────────────────────────────────────────────
129
+
130
+ export interface StreamError {
131
+ errorCode: string;
132
+ message?: string;
133
+ }
134
+
135
+ // ── AlignStreamFrame ──────────────────────────────────────────────────────────
136
+
137
+ export class AlignStreamFrame implements NpsFrame {
138
+ readonly frameType = FrameType.ALIGN_STREAM;
139
+ readonly preferredTier = EncodingTier.MSGPACK;
140
+
141
+ constructor(
142
+ public readonly streamId: string,
143
+ public readonly taskId: string,
144
+ public readonly subtaskId: string,
145
+ public readonly seq: number,
146
+ public readonly isFinal: boolean,
147
+ public readonly senderNid: string,
148
+ public readonly data?: Record<string, unknown>,
149
+ public readonly error?: StreamError,
150
+ public readonly windowSize?: number,
151
+ ) {}
152
+
153
+ toDict(): Record<string, unknown> {
154
+ return {
155
+ stream_id: this.streamId,
156
+ task_id: this.taskId,
157
+ subtask_id: this.subtaskId,
158
+ seq: this.seq,
159
+ is_final: this.isFinal,
160
+ sender_nid: this.senderNid,
161
+ data: this.data ?? null,
162
+ error: this.error ? { error_code: this.error.errorCode, message: this.error.message ?? null } : null,
163
+ window_size: this.windowSize ?? null,
164
+ };
165
+ }
166
+
167
+ static fromDict(data: Record<string, unknown>): AlignStreamFrame {
168
+ const rawError = data["error"] as { error_code: string; message?: string } | null;
169
+ return new AlignStreamFrame(
170
+ data["stream_id"] as string,
171
+ data["task_id"] as string,
172
+ data["subtask_id"] as string,
173
+ data["seq"] as number,
174
+ data["is_final"] as boolean,
175
+ data["sender_nid"] as string,
176
+ (data["data"] as Record<string, unknown> | null) ?? undefined,
177
+ rawError ? { errorCode: rawError.error_code, ...(rawError.message != null ? { message: rawError.message } : {}) } : undefined,
178
+ (data["window_size"] as number | null) ?? undefined,
179
+ );
180
+ }
181
+ }
@@ -0,0 +1,7 @@
1
+ // Copyright 2026 INNO LOTUS PTY LTD
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ export * from "./models.js";
5
+ export * from "./frames.js";
6
+ export * from "./registry.js";
7
+ export * from "./client.js";
@@ -0,0 +1,79 @@
1
+ // Copyright 2026 INNO LOTUS PTY LTD
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ export enum TaskState {
5
+ PENDING = "pending",
6
+ PREFLIGHT = "preflight",
7
+ RUNNING = "running",
8
+ WAITING_SYNC = "waiting_sync",
9
+ COMPLETED = "completed",
10
+ FAILED = "failed",
11
+ CANCELLED = "cancelled",
12
+ SKIPPED = "skipped",
13
+ }
14
+
15
+ export enum TaskPriority {
16
+ LOW = "low",
17
+ NORMAL = "normal",
18
+ HIGH = "high",
19
+ }
20
+
21
+ export enum BackoffStrategy {
22
+ FIXED = "fixed",
23
+ LINEAR = "linear",
24
+ EXPONENTIAL = "exponential",
25
+ }
26
+
27
+ export enum AggregateStrategy {
28
+ MERGE = "merge",
29
+ FIRST = "first",
30
+ FASTEST_K = "fastest_k",
31
+ ALL = "all",
32
+ }
33
+
34
+ export interface RetryPolicy {
35
+ maxRetries: number;
36
+ backoff: BackoffStrategy;
37
+ baseDelayMs?: number;
38
+ maxDelayMs?: number;
39
+ }
40
+
41
+ export function computeDelayMs(policy: RetryPolicy, attempt: number): number {
42
+ const base = policy.baseDelayMs ?? 1000;
43
+ const cap = policy.maxDelayMs ?? 30_000;
44
+ let delay: number;
45
+ switch (policy.backoff) {
46
+ case BackoffStrategy.FIXED: delay = base; break;
47
+ case BackoffStrategy.LINEAR: delay = base * (attempt + 1); break;
48
+ case BackoffStrategy.EXPONENTIAL: delay = base * Math.pow(2, attempt); break;
49
+ }
50
+ return Math.min(delay, cap);
51
+ }
52
+
53
+ export interface TaskContext {
54
+ sessionKey?: string;
55
+ requesterNid?: string;
56
+ traceId?: string;
57
+ }
58
+
59
+ export interface DagNode {
60
+ id: string;
61
+ action: string;
62
+ agent: string;
63
+ inputFrom?: readonly string[];
64
+ inputMapping?: Record<string, string>;
65
+ timeoutMs?: number;
66
+ retryPolicy?: RetryPolicy;
67
+ condition?: string;
68
+ minRequired?: number;
69
+ }
70
+
71
+ export interface DagEdge {
72
+ from: string;
73
+ to: string;
74
+ }
75
+
76
+ export interface TaskDag {
77
+ nodes: readonly DagNode[];
78
+ edges: readonly DagEdge[];
79
+ }