@labacacia/nps-sdk 1.0.0-alpha.5 → 1.0.0-alpha.7

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 (145) hide show
  1. package/CHANGELOG.cn.md +29 -5
  2. package/CHANGELOG.md +29 -5
  3. package/LICENSE +0 -0
  4. package/NOTICE +0 -0
  5. package/README.cn.md +8 -13
  6. package/README.md +8 -13
  7. package/dist/nip/index.d.ts +1 -0
  8. package/dist/nip/index.d.ts.map +1 -1
  9. package/dist/nip/index.js +2 -0
  10. package/dist/nip/index.js.map +1 -1
  11. package/dist/nip/reputation-client.d.ts +116 -0
  12. package/dist/nip/reputation-client.d.ts.map +1 -0
  13. package/dist/nip/reputation-client.js +261 -0
  14. package/dist/nip/reputation-client.js.map +1 -0
  15. package/dist/nip/x509/oids.d.ts +9 -10
  16. package/dist/nip/x509/oids.d.ts.map +1 -1
  17. package/dist/nip/x509/oids.js +3 -4
  18. package/dist/nip/x509/oids.js.map +1 -1
  19. package/dist/nwp/anchor-client.d.ts +109 -0
  20. package/dist/nwp/anchor-client.d.ts.map +1 -0
  21. package/dist/nwp/anchor-client.js +279 -0
  22. package/dist/nwp/anchor-client.js.map +1 -0
  23. package/dist/nwp/index.d.ts +1 -1
  24. package/dist/nwp/index.d.ts.map +1 -1
  25. package/dist/nwp/index.js +1 -1
  26. package/dist/nwp/index.js.map +1 -1
  27. package/doc/nps-sdk.core.cn.md +0 -0
  28. package/doc/nps-sdk.core.md +0 -0
  29. package/doc/nps-sdk.ncp.cn.md +0 -0
  30. package/doc/nps-sdk.ncp.md +0 -0
  31. package/doc/nps-sdk.ndp.cn.md +0 -0
  32. package/doc/nps-sdk.ndp.md +0 -0
  33. package/doc/nps-sdk.nop.cn.md +0 -0
  34. package/doc/nps-sdk.nop.md +0 -0
  35. package/doc/overview.cn.md +0 -0
  36. package/doc/overview.md +0 -0
  37. package/package.json +12 -1
  38. package/CONTRIBUTING.cn.md +0 -35
  39. package/CONTRIBUTING.md +0 -35
  40. package/dist/nwp/error-codes.d.ts +0 -42
  41. package/dist/nwp/error-codes.d.ts.map +0 -1
  42. package/dist/nwp/error-codes.js +0 -53
  43. package/dist/nwp/error-codes.js.map +0 -1
  44. package/nip-ca-server/Dockerfile +0 -27
  45. package/nip-ca-server/README.md +0 -45
  46. package/nip-ca-server/db/001_init.sql +0 -25
  47. package/nip-ca-server/docker-compose.yml +0 -29
  48. package/nip-ca-server/package.json +0 -23
  49. package/nip-ca-server/src/ca.ts +0 -155
  50. package/nip-ca-server/src/db.ts +0 -104
  51. package/nip-ca-server/src/index.ts +0 -157
  52. package/nip-ca-server/tsconfig.json +0 -13
  53. package/src/core/anchor-cache.ts +0 -129
  54. package/src/core/cache.ts +0 -93
  55. package/src/core/canonical-json.ts +0 -50
  56. package/src/core/codec.ts +0 -158
  57. package/src/core/codecs/index.ts +0 -5
  58. package/src/core/codecs/ncp-codec.ts +0 -170
  59. package/src/core/codecs/tier1-json-codec.ts +0 -33
  60. package/src/core/codecs/tier2-msgpack-codec.ts +0 -30
  61. package/src/core/crypto-provider.ts +0 -47
  62. package/src/core/exceptions.ts +0 -57
  63. package/src/core/frame-header.ts +0 -282
  64. package/src/core/frame-registry.ts +0 -91
  65. package/src/core/frames.ts +0 -184
  66. package/src/core/index.ts +0 -42
  67. package/src/core/registry.ts +0 -28
  68. package/src/core/status-codes.ts +0 -47
  69. package/src/index.ts +0 -10
  70. package/src/ncp/frames/anchor-frame.ts +0 -87
  71. package/src/ncp/frames/caps-frame.ts +0 -59
  72. package/src/ncp/frames/diff-frame.ts +0 -69
  73. package/src/ncp/frames/error-frame.ts +0 -26
  74. package/src/ncp/frames/hello-frame.ts +0 -50
  75. package/src/ncp/frames/stream-frame.ts +0 -35
  76. package/src/ncp/frames.ts +0 -251
  77. package/src/ncp/handshake.ts +0 -95
  78. package/src/ncp/index.ts +0 -13
  79. package/src/ncp/ncp-error-codes.ts +0 -36
  80. package/src/ncp/ncp-patch-format.ts +0 -16
  81. package/src/ncp/preamble.ts +0 -79
  82. package/src/ncp/registry.ts +0 -15
  83. package/src/ncp/stream-manager.ts +0 -212
  84. package/src/ndp/dns-txt.ts +0 -86
  85. package/src/ndp/frames.ts +0 -124
  86. package/src/ndp/index.ts +0 -8
  87. package/src/ndp/ndp-registry.ts +0 -116
  88. package/src/ndp/registry.ts +0 -12
  89. package/src/ndp/validator.ts +0 -64
  90. package/src/nip/acme/client.ts +0 -185
  91. package/src/nip/acme/index.ts +0 -8
  92. package/src/nip/acme/jws.ts +0 -109
  93. package/src/nip/acme/messages.ts +0 -85
  94. package/src/nip/acme/server.ts +0 -480
  95. package/src/nip/acme/wire.ts +0 -24
  96. package/src/nip/assurance-level.ts +0 -40
  97. package/src/nip/cert-format.ts +0 -9
  98. package/src/nip/error-codes.ts +0 -38
  99. package/src/nip/frames.ts +0 -138
  100. package/src/nip/identity.ts +0 -113
  101. package/src/nip/index.ts +0 -14
  102. package/src/nip/registry.ts +0 -12
  103. package/src/nip/verifier.ts +0 -122
  104. package/src/nip/x509/builder.ts +0 -91
  105. package/src/nip/x509/index.ts +0 -6
  106. package/src/nip/x509/oids.ts +0 -28
  107. package/src/nip/x509/verifier.ts +0 -214
  108. package/src/nop/client.ts +0 -103
  109. package/src/nop/frames.ts +0 -181
  110. package/src/nop/index.ts +0 -7
  111. package/src/nop/models.ts +0 -79
  112. package/src/nop/nop-types.ts +0 -208
  113. package/src/nop/registry.ts +0 -13
  114. package/src/nwp/client.ts +0 -114
  115. package/src/nwp/error-codes.ts +0 -62
  116. package/src/nwp/frames.ts +0 -116
  117. package/src/nwp/index.ts +0 -7
  118. package/src/nwp/registry.ts +0 -11
  119. package/src/setup.ts +0 -32
  120. package/tests/_rfc0002-keys.ts +0 -57
  121. package/tests/core/anchor-cache.test.ts +0 -242
  122. package/tests/core/codec.test.ts +0 -205
  123. package/tests/core/frame-registry.test.ts +0 -46
  124. package/tests/core.test.ts +0 -327
  125. package/tests/ncp/diff-binary-bitset.test.ts +0 -107
  126. package/tests/ncp/e2e-enc-reject.test.ts +0 -93
  127. package/tests/ncp/err-error-frame.test.ts +0 -152
  128. package/tests/ncp/frames.test.ts +0 -359
  129. package/tests/ncp/framing.test.ts +0 -233
  130. package/tests/ncp/hello-frame.test.ts +0 -122
  131. package/tests/ncp/inline-anchor.test.ts +0 -88
  132. package/tests/ncp/preamble.test.ts +0 -93
  133. package/tests/ncp/security.test.ts +0 -184
  134. package/tests/ncp/stream-window.test.ts +0 -167
  135. package/tests/ncp/stream.test.ts +0 -242
  136. package/tests/ncp/version-negotiation.test.ts +0 -123
  137. package/tests/ndp.test.ts +0 -377
  138. package/tests/nip-acme-agent01.test.ts +0 -192
  139. package/tests/nip-x509.test.ts +0 -280
  140. package/tests/nip.test.ts +0 -184
  141. package/tests/nop.test.ts +0 -344
  142. package/tests/nwp.test.ts +0 -237
  143. package/tsconfig.json +0 -20
  144. package/tsup.config.ts +0 -20
  145. package/vitest.config.ts +0 -10
@@ -1,28 +0,0 @@
1
- // Copyright 2026 INNO LOTUS PTY LTD
2
- // SPDX-License-Identifier: Apache-2.0
3
-
4
- import { NpsFrameError } from "./exceptions.js";
5
- import { FrameType } from "./frames.js";
6
- import type { NpsFrame } from "./codec.js";
7
-
8
- export interface FrameClass {
9
- fromDict(data: Record<string, unknown>): NpsFrame;
10
- }
11
-
12
- export class FrameRegistry {
13
- private readonly _map = new Map<FrameType, FrameClass>();
14
-
15
- register(frameType: FrameType, cls: FrameClass): void {
16
- this._map.set(frameType, cls);
17
- }
18
-
19
- resolve(frameType: FrameType): FrameClass {
20
- const cls = this._map.get(frameType);
21
- if (cls === undefined) {
22
- throw new NpsFrameError(
23
- `No frame class registered for type 0x${frameType.toString(16).padStart(2, "0")}.`,
24
- );
25
- }
26
- return cls;
27
- }
28
- }
@@ -1,47 +0,0 @@
1
- // SPDX-License-Identifier: Apache-2.0
2
- // Copyright (c) 2026 LabAcacia / INNO LOTUS PTY LTD
3
- //
4
- // NPS native status codes — per spec/status-codes.md
5
-
6
- /** NPS native status code constants. */
7
- export const NpsStatusCodes = {
8
- // Success
9
- NPS_OK: "NPS-OK",
10
- NPS_OK_ACCEPTED: "NPS-OK-ACCEPTED",
11
- NPS_OK_NO_CONTENT: "NPS-OK-NO-CONTENT",
12
-
13
- // Auth
14
- NPS_AUTH_UNAUTHENTICATED: "NPS-AUTH-UNAUTHENTICATED",
15
- NPS_AUTH_FORBIDDEN: "NPS-AUTH-FORBIDDEN",
16
-
17
- // Client errors
18
- NPS_CLIENT_BAD_FRAME: "NPS-CLIENT-BAD-FRAME",
19
- NPS_CLIENT_BAD_PARAM: "NPS-CLIENT-BAD-PARAM",
20
- NPS_CLIENT_NOT_FOUND: "NPS-CLIENT-NOT-FOUND",
21
- NPS_CLIENT_CONFLICT: "NPS-CLIENT-CONFLICT",
22
- NPS_CLIENT_GONE: "NPS-CLIENT-GONE",
23
- NPS_CLIENT_UNPROCESSABLE: "NPS-CLIENT-UNPROCESSABLE",
24
-
25
- // Server errors
26
- NPS_SERVER_INTERNAL: "NPS-SERVER-INTERNAL",
27
- NPS_SERVER_UNAVAILABLE: "NPS-SERVER-UNAVAILABLE",
28
- NPS_SERVER_TIMEOUT: "NPS-SERVER-TIMEOUT",
29
- NPS_SERVER_ENCODING_UNSUPPORTED: "NPS-SERVER-ENCODING-UNSUPPORTED",
30
- NPS_SERVER_UNSUPPORTED: "NPS-SERVER-UNSUPPORTED",
31
-
32
- // Stream
33
- NPS_STREAM_SEQ_GAP: "NPS-STREAM-SEQ-GAP",
34
- NPS_STREAM_NOT_FOUND: "NPS-STREAM-NOT-FOUND",
35
- NPS_STREAM_LIMIT: "NPS-STREAM-LIMIT",
36
-
37
- // Limit
38
- NPS_LIMIT_RATE: "NPS-LIMIT-RATE",
39
- NPS_LIMIT_BUDGET: "NPS-LIMIT-BUDGET",
40
- NPS_LIMIT_PAYLOAD: "NPS-LIMIT-PAYLOAD",
41
- NPS_LIMIT_EXCEEDED: "NPS-LIMIT-EXCEEDED",
42
-
43
- // Protocol
44
- NPS_PROTO_VERSION_INCOMPATIBLE: "NPS-PROTO-VERSION-INCOMPATIBLE",
45
- } as const;
46
-
47
- export type NpsStatusCode = (typeof NpsStatusCodes)[keyof typeof NpsStatusCodes];
package/src/index.ts DELETED
@@ -1,10 +0,0 @@
1
- // SPDX-License-Identifier: Apache-2.0
2
- // Copyright (c) 2026 LabAcacia / INNO LOTUS PTY LTD
3
- //
4
- // @labacacia/nps-sdk — root entry point.
5
- // Mirrors Python root __init__.py: exports only the SDK version.
6
- // All protocol APIs are accessed via sub-package imports:
7
- // import { ... } from "@labacacia/nps-sdk/core"
8
- // import { ... } from "@labacacia/nps-sdk/ncp"
9
-
10
- export const VERSION = "1.0.0-alpha.2";
@@ -1,87 +0,0 @@
1
- // SPDX-License-Identifier: Apache-2.0
2
- // Copyright (c) 2026 LabAcacia / INNO LOTUS PTY LTD
3
- //
4
- // AnchorFrame (0x01) — Schema anchor for global reference
5
- // NPS-1 §4.1
6
-
7
- import { createHash } from "node:crypto";
8
- // canonicalize ships CJS with TS `export default` — NodeNext resolves as namespace
9
- import canonicalizeDefault from "canonicalize";
10
- const canonicalize = canonicalizeDefault as unknown as (input: unknown) => string | undefined;
11
- import { NcpError } from "../../core/frame-header.js";
12
-
13
- // ---------------------------------------------------------------------------
14
- // Types
15
- // ---------------------------------------------------------------------------
16
-
17
- const VALID_FIELD_TYPES = [
18
- "string", "uint64", "int64", "decimal", "bool",
19
- "timestamp", "bytes", "object", "array",
20
- ] as const;
21
-
22
- export type SchemaFieldType = (typeof VALID_FIELD_TYPES)[number];
23
-
24
- export interface SchemaField {
25
- name: string;
26
- type: string;
27
- semantic?: string;
28
- nullable?: boolean;
29
- }
30
-
31
- export interface FrameSchema {
32
- fields: SchemaField[];
33
- }
34
-
35
- export interface AnchorFrame {
36
- frame: string;
37
- anchor_id: string;
38
- schema: FrameSchema;
39
- ttl?: number;
40
- }
41
-
42
- // ---------------------------------------------------------------------------
43
- // anchor_id computation (RFC 8785 JCS + SHA-256)
44
- // ---------------------------------------------------------------------------
45
-
46
- /**
47
- * Compute anchor_id from schema using RFC 8785 JCS canonicalization + SHA-256.
48
- * Format: "sha256:{64 lowercase hex chars}"
49
- */
50
- export function computeAnchorId(schema: FrameSchema): string {
51
- const canonical = canonicalize(schema);
52
- if (!canonical) {
53
- throw new NcpError("NCP-ANCHOR-SCHEMA-INVALID", "Schema cannot be canonicalized");
54
- }
55
- const hash = createHash("sha256").update(canonical).digest("hex");
56
- return `sha256:${hash}`;
57
- }
58
-
59
- // ---------------------------------------------------------------------------
60
- // Validation
61
- // ---------------------------------------------------------------------------
62
-
63
- /**
64
- * Validate an AnchorFrame.
65
- * @throws {NcpError} NCP-ANCHOR-SCHEMA-INVALID if anchor_id doesn't match or schema is invalid.
66
- */
67
- export function validateAnchorFrame(frame: AnchorFrame): void {
68
- // Validate schema field types
69
- for (const field of frame.schema.fields) {
70
- if (!VALID_FIELD_TYPES.includes(field.type as SchemaFieldType)) {
71
- throw new NcpError(
72
- "NCP-ANCHOR-SCHEMA-INVALID",
73
- `Unsupported field type "${field.type}" for field "${field.name}". ` +
74
- `Valid types: ${VALID_FIELD_TYPES.join(", ")}`,
75
- );
76
- }
77
- }
78
-
79
- // Validate anchor_id matches computed hash
80
- const expected = computeAnchorId(frame.schema);
81
- if (frame.anchor_id !== expected) {
82
- throw new NcpError(
83
- "NCP-ANCHOR-SCHEMA-INVALID",
84
- `anchor_id mismatch: expected ${expected}, got ${frame.anchor_id}`,
85
- );
86
- }
87
- }
@@ -1,59 +0,0 @@
1
- // SPDX-License-Identifier: Apache-2.0
2
- // Copyright (c) 2026 LabAcacia / INNO LOTUS PTY LTD
3
- //
4
- // CapsFrame (0x04) — Capsule response envelope
5
- // NPS-1 §4.4
6
-
7
- import { NcpError } from "../../core/frame-header.js";
8
- import {
9
- computeAnchorId,
10
- type AnchorFrame,
11
- type FrameSchema,
12
- } from "./anchor-frame.js";
13
-
14
- export interface CapsFrameInlineAnchor {
15
- anchor_id: string;
16
- schema: FrameSchema;
17
- ttl?: number;
18
- }
19
-
20
- export interface CapsFrame {
21
- frame: string;
22
- anchor_ref: string;
23
- count: number;
24
- data: unknown[];
25
- next_cursor?: string | null;
26
- token_est?: number;
27
- tokenizer_used?: string;
28
- cached?: boolean;
29
- inline_anchor?: CapsFrameInlineAnchor;
30
- }
31
-
32
- /**
33
- * Validate a CapsFrame.
34
- *
35
- * Checks:
36
- * - count matches data.length (NPS-CLIENT-BAD-FRAME)
37
- * - if inline_anchor present, recomputes anchor_id and validates match (NCP-ANCHOR-SCHEMA-INVALID)
38
- *
39
- * @throws {NcpError} NPS-CLIENT-BAD-FRAME if count doesn't match data length.
40
- * @throws {NcpError} NCP-ANCHOR-SCHEMA-INVALID if inline_anchor.anchor_id doesn't match schema.
41
- */
42
- export function validateCapsFrame(frame: CapsFrame): void {
43
- if (frame.count !== frame.data.length) {
44
- throw new NcpError(
45
- "NPS-CLIENT-BAD-FRAME",
46
- `CapsFrame count mismatch: count=${frame.count}, data.length=${frame.data.length}`,
47
- );
48
- }
49
-
50
- if (frame.inline_anchor !== undefined) {
51
- const computed = computeAnchorId(frame.inline_anchor.schema);
52
- if (frame.inline_anchor.anchor_id !== computed) {
53
- throw new NcpError(
54
- "NCP-ANCHOR-SCHEMA-INVALID",
55
- `inline_anchor anchor_id mismatch: expected ${computed}, got ${frame.inline_anchor.anchor_id}`,
56
- );
57
- }
58
- }
59
- }
@@ -1,69 +0,0 @@
1
- // SPDX-License-Identifier: Apache-2.0
2
- // Copyright (c) 2026 LabAcacia / INNO LOTUS PTY LTD
3
- //
4
- // DiffFrame (0x02) — Incremental data patch
5
- // NPS-1 §4.2
6
-
7
- import { NcpError, EncodingTier } from "../../core/frame-header.js";
8
- import { isValidPatchFormat, type PatchFormat } from "../ncp-patch-format.js";
9
-
10
- export interface JsonPatchOperation {
11
- op: "add" | "remove" | "replace" | "move" | "copy" | "test";
12
- path: string;
13
- value?: unknown;
14
- from?: string;
15
- }
16
-
17
- export interface DiffFrame {
18
- frame: string;
19
- anchor_ref: string;
20
- base_seq: number;
21
- patch_format?: PatchFormat;
22
- patch: JsonPatchOperation[] | Uint8Array;
23
- entity_id?: string;
24
- }
25
-
26
- /**
27
- * Validate DiffFrame base_seq against current sequence.
28
- * @throws {NcpError} NCP-STREAM-SEQ-GAP if sequences don't match.
29
- */
30
- export function validateDiffSeq(frame: DiffFrame, currentSeq: number): void {
31
- if (frame.base_seq !== currentSeq) {
32
- throw new NcpError(
33
- "NCP-STREAM-SEQ-GAP",
34
- `DiffFrame base_seq=${frame.base_seq} does not match current seq=${currentSeq}`,
35
- );
36
- }
37
- }
38
-
39
- /**
40
- * Validate DiffFrame patch_format against the encoding tier.
41
- *
42
- * binary_bitset is only supported on Tier-2 (MsgPack) frames.
43
- * Unknown patch_format values are also rejected.
44
- *
45
- * @throws {NcpError} NCP-DIFF-FORMAT-UNSUPPORTED if binary_bitset on non-Tier-2,
46
- * or if patch_format is an unknown value.
47
- */
48
- export function validateDiffFrame(
49
- frame: DiffFrame,
50
- encodingTier: EncodingTier | number,
51
- ): void {
52
- const fmt = frame.patch_format;
53
-
54
- // Unknown patch_format
55
- if (fmt !== undefined && !isValidPatchFormat(fmt)) {
56
- throw new NcpError(
57
- "NCP-DIFF-FORMAT-UNSUPPORTED",
58
- `Unknown patch_format "${String(fmt)}"`,
59
- );
60
- }
61
-
62
- // binary_bitset requires Tier-2 MsgPack
63
- if (fmt === "binary_bitset" && encodingTier !== EncodingTier.MsgPack) {
64
- throw new NcpError(
65
- "NCP-DIFF-FORMAT-UNSUPPORTED",
66
- "patch_format=binary_bitset requires Tier-2 MsgPack encoding",
67
- );
68
- }
69
- }
@@ -1,26 +0,0 @@
1
- // SPDX-License-Identifier: Apache-2.0
2
- // Copyright (c) 2026 LabAcacia / INNO LOTUS PTY LTD
3
- //
4
- // ErrorFrame (0xFE) — Unified error frame for all NPS protocol layers
5
- // NPS-1 §4.7
6
-
7
- /** Unified error frame shared across all NPS protocol layers. */
8
- export interface ErrorFrame {
9
- /** Fixed value "0xFE". */
10
- frame: string;
11
- /** NPS status code, e.g. "NPS-CLIENT-NOT-FOUND". */
12
- status: string;
13
- /** Protocol-level error code, e.g. "NCP-ANCHOR-NOT-FOUND". */
14
- error: string;
15
- /** Human-readable error description. */
16
- message?: string;
17
- /** Structured error details (e.g. anchor_ref, stream_id). */
18
- details?: Record<string, unknown>;
19
- }
20
-
21
- /** Type guard for ErrorFrame. */
22
- export function isErrorFrame(obj: unknown): obj is ErrorFrame {
23
- if (typeof obj !== "object" || obj === null) return false;
24
- const o = obj as Record<string, unknown>;
25
- return o.frame === "0xFE" && typeof o.status === "string" && typeof o.error === "string";
26
- }
@@ -1,50 +0,0 @@
1
- // SPDX-License-Identifier: Apache-2.0
2
- // Copyright (c) 2026 LabAcacia / INNO LOTUS PTY LTD
3
- //
4
- // HelloFrame (0x06) — Native-mode client handshake
5
- // NPS-1 §4.6
6
-
7
- import { NcpError } from "../../core/frame-header.js";
8
-
9
- export interface HelloFrame {
10
- frame: string;
11
- nps_version: string;
12
- min_version?: string;
13
- supported_encodings: string[];
14
- supported_protocols: string[];
15
- agent_id?: string;
16
- max_frame_payload?: number;
17
- ext_support?: boolean;
18
- max_concurrent_streams?: number;
19
- e2e_enc_algorithms?: string[];
20
- }
21
-
22
- /**
23
- * Validate a HelloFrame.
24
- *
25
- * Required fields: nps_version, supported_encodings (non-empty), supported_protocols (non-empty).
26
- *
27
- * @throws {NcpError} NPS-CLIENT-BAD-FRAME if any required field is missing or empty.
28
- */
29
- export function validateHelloFrame(frame: HelloFrame): void {
30
- if (!frame.nps_version) {
31
- throw new NcpError(
32
- "NPS-CLIENT-BAD-FRAME",
33
- "HelloFrame missing required field: nps_version",
34
- );
35
- }
36
-
37
- if (!frame.supported_encodings || frame.supported_encodings.length === 0) {
38
- throw new NcpError(
39
- "NPS-CLIENT-BAD-FRAME",
40
- "HelloFrame missing required field: supported_encodings (must be non-empty)",
41
- );
42
- }
43
-
44
- if (!frame.supported_protocols || frame.supported_protocols.length === 0) {
45
- throw new NcpError(
46
- "NPS-CLIENT-BAD-FRAME",
47
- "HelloFrame missing required field: supported_protocols (must be non-empty)",
48
- );
49
- }
50
- }
@@ -1,35 +0,0 @@
1
- // SPDX-License-Identifier: Apache-2.0
2
- // Copyright (c) 2026 LabAcacia / INNO LOTUS PTY LTD
3
- //
4
- // StreamFrame (0x03) — Streaming data chunks with flow control
5
- // NPS-1 §4.3
6
-
7
- import { NcpError } from "../../core/frame-header.js";
8
-
9
- export interface StreamFrame {
10
- frame: string;
11
- stream_id: string;
12
- seq: number;
13
- is_last: boolean;
14
- anchor_ref?: string;
15
- data: unknown[];
16
- window_size?: number;
17
- error_code?: string;
18
- }
19
-
20
- // UUID v4 format: xxxxxxxx-xxxx-4xxx-[89ab]xxx-xxxxxxxxxxxx
21
- const UUID_V4_RE =
22
- /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
23
-
24
- /**
25
- * Validate stream_id is a valid UUID v4.
26
- * @throws {NcpError} NPS-CLIENT-BAD-FRAME if stream_id is not a valid UUID v4.
27
- */
28
- export function validateStreamFrame(frame: StreamFrame): void {
29
- if (!UUID_V4_RE.test(frame.stream_id)) {
30
- throw new NcpError(
31
- "NPS-CLIENT-BAD-FRAME",
32
- `stream_id "${frame.stream_id}" is not a valid UUID v4`,
33
- );
34
- }
35
- }
package/src/ncp/frames.ts DELETED
@@ -1,251 +0,0 @@
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
-
7
- // ── FrameSchema ───────────────────────────────────────────────────────────────
8
-
9
- export interface SchemaField {
10
- name: string;
11
- type: string;
12
- semantic?: string;
13
- nullable?: boolean;
14
- }
15
-
16
- export interface FrameSchema {
17
- fields: readonly SchemaField[];
18
- }
19
-
20
- // ── AnchorFrame ───────────────────────────────────────────────────────────────
21
-
22
- export class AnchorFrame implements NpsFrame {
23
- readonly frameType = FrameType.ANCHOR;
24
- readonly preferredTier = EncodingTier.MSGPACK;
25
-
26
- constructor(
27
- public readonly anchorId: string,
28
- public readonly schema: FrameSchema,
29
- public readonly ttl: number = 3600,
30
- ) {}
31
-
32
- toDict(): Record<string, unknown> {
33
- return {
34
- anchor_id: this.anchorId,
35
- schema: { fields: this.schema.fields.map((f) => ({ ...f })) },
36
- ttl: this.ttl,
37
- };
38
- }
39
-
40
- static fromDict(data: Record<string, unknown>): AnchorFrame {
41
- const schemaRaw = data["schema"] as { fields: SchemaField[] };
42
- return new AnchorFrame(
43
- data["anchor_id"] as string,
44
- { fields: schemaRaw.fields },
45
- (data["ttl"] as number | undefined) ?? 3600,
46
- );
47
- }
48
- }
49
-
50
- // ── JsonPatchOperation ────────────────────────────────────────────────────────
51
-
52
- export interface JsonPatchOperation {
53
- op: string;
54
- path: string;
55
- value?: unknown;
56
- }
57
-
58
- // ── DiffFrame ─────────────────────────────────────────────────────────────────
59
-
60
- export class DiffFrame implements NpsFrame {
61
- readonly frameType = FrameType.DIFF;
62
- readonly preferredTier = EncodingTier.MSGPACK;
63
-
64
- constructor(
65
- public readonly anchorRef: string,
66
- public readonly baseSeq: number,
67
- public readonly patch: readonly JsonPatchOperation[],
68
- public readonly entityId?: string,
69
- ) {}
70
-
71
- toDict(): Record<string, unknown> {
72
- return {
73
- anchor_ref: this.anchorRef,
74
- base_seq: this.baseSeq,
75
- patch: this.patch.map((p) => ({ ...p })),
76
- entity_id: this.entityId ?? null,
77
- };
78
- }
79
-
80
- static fromDict(data: Record<string, unknown>): DiffFrame {
81
- return new DiffFrame(
82
- data["anchor_ref"] as string,
83
- data["base_seq"] as number,
84
- data["patch"] as JsonPatchOperation[],
85
- (data["entity_id"] as string | null) ?? undefined,
86
- );
87
- }
88
- }
89
-
90
- // ── StreamFrame ───────────────────────────────────────────────────────────────
91
-
92
- export class StreamFrame implements NpsFrame {
93
- readonly frameType = FrameType.STREAM;
94
- readonly preferredTier = EncodingTier.MSGPACK;
95
-
96
- constructor(
97
- public readonly streamId: string,
98
- public readonly seq: number,
99
- public readonly isLast: boolean,
100
- public readonly data: readonly Record<string, unknown>[],
101
- public readonly anchorRef?: string,
102
- public readonly windowSize?: number,
103
- ) {}
104
-
105
- toDict(): Record<string, unknown> {
106
- return {
107
- stream_id: this.streamId,
108
- seq: this.seq,
109
- is_last: this.isLast,
110
- data: this.data,
111
- anchor_ref: this.anchorRef ?? null,
112
- window_size: this.windowSize ?? null,
113
- };
114
- }
115
-
116
- static fromDict(data: Record<string, unknown>): StreamFrame {
117
- return new StreamFrame(
118
- data["stream_id"] as string,
119
- data["seq"] as number,
120
- data["is_last"] as boolean,
121
- data["data"] as Record<string, unknown>[],
122
- (data["anchor_ref"] as string | null) ?? undefined,
123
- (data["window_size"] as number | null) ?? undefined,
124
- );
125
- }
126
- }
127
-
128
- // ── CapsFrame ─────────────────────────────────────────────────────────────────
129
-
130
- export class CapsFrame implements NpsFrame {
131
- readonly frameType = FrameType.CAPS;
132
- readonly preferredTier = EncodingTier.MSGPACK;
133
-
134
- constructor(
135
- public readonly anchorRef: string,
136
- public readonly count: number,
137
- public readonly data: readonly Record<string, unknown>[],
138
- public readonly nextCursor?: string,
139
- public readonly tokenEst?: number,
140
- public readonly cached?: boolean,
141
- public readonly tokenizerUsed?: string,
142
- ) {}
143
-
144
- toDict(): Record<string, unknown> {
145
- return {
146
- anchor_ref: this.anchorRef,
147
- count: this.count,
148
- data: this.data,
149
- next_cursor: this.nextCursor ?? null,
150
- token_est: this.tokenEst ?? null,
151
- cached: this.cached ?? null,
152
- tokenizer_used: this.tokenizerUsed ?? null,
153
- };
154
- }
155
-
156
- static fromDict(data: Record<string, unknown>): CapsFrame {
157
- return new CapsFrame(
158
- data["anchor_ref"] as string,
159
- data["count"] as number,
160
- data["data"] as Record<string, unknown>[],
161
- (data["next_cursor"] as string | null) ?? undefined,
162
- (data["token_est"] as number | null) ?? undefined,
163
- (data["cached"] as boolean | null) ?? undefined,
164
- (data["tokenizer_used"] as string | null) ?? undefined,
165
- );
166
- }
167
- }
168
-
169
- // ── ErrorFrame ────────────────────────────────────────────────────────────────
170
-
171
- export class ErrorFrame implements NpsFrame {
172
- readonly frameType = FrameType.ERROR;
173
- readonly preferredTier = EncodingTier.MSGPACK;
174
-
175
- constructor(
176
- public readonly status: string,
177
- public readonly error: string,
178
- public readonly message?: string,
179
- public readonly details?: Record<string, unknown>,
180
- ) {}
181
-
182
- toDict(): Record<string, unknown> {
183
- return {
184
- status: this.status,
185
- error: this.error,
186
- message: this.message ?? null,
187
- details: this.details ?? null,
188
- };
189
- }
190
-
191
- static fromDict(data: Record<string, unknown>): ErrorFrame {
192
- return new ErrorFrame(
193
- data["status"] as string,
194
- data["error"] as string,
195
- (data["message"] as string | null) ?? undefined,
196
- (data["details"] as Record<string, unknown> | null) ?? undefined,
197
- );
198
- }
199
- }
200
-
201
- // ── HelloFrame ────────────────────────────────────────────────────────────────
202
- // NPS-1 §4.6 — Native-mode handshake (0x06). Always Tier-1 JSON: encoding
203
- // is not yet negotiated when this frame is sent.
204
-
205
- export class HelloFrame implements NpsFrame {
206
- readonly frameType = FrameType.HELLO;
207
- readonly preferredTier = EncodingTier.JSON;
208
-
209
- static readonly DEFAULT_MAX_FRAME_PAYLOAD = 0xFFFF;
210
- static readonly DEFAULT_MAX_CONCURRENT_STREAMS = 32;
211
-
212
- constructor(
213
- public readonly npsVersion: string,
214
- public readonly supportedEncodings: readonly string[],
215
- public readonly supportedProtocols: readonly string[],
216
- public minVersion?: string,
217
- public agentId?: string,
218
- public maxFramePayload: number = HelloFrame.DEFAULT_MAX_FRAME_PAYLOAD,
219
- public extSupport: boolean = false,
220
- public maxConcurrentStreams: number = HelloFrame.DEFAULT_MAX_CONCURRENT_STREAMS,
221
- public e2eEncAlgorithms?: readonly string[],
222
- ) {}
223
-
224
- toDict(): Record<string, unknown> {
225
- return {
226
- nps_version: this.npsVersion,
227
- supported_encodings: [...this.supportedEncodings],
228
- supported_protocols: [...this.supportedProtocols],
229
- min_version: this.minVersion ?? null,
230
- agent_id: this.agentId ?? null,
231
- max_frame_payload: this.maxFramePayload,
232
- ext_support: this.extSupport,
233
- max_concurrent_streams: this.maxConcurrentStreams,
234
- e2e_enc_algorithms: this.e2eEncAlgorithms ? [...this.e2eEncAlgorithms] : null,
235
- };
236
- }
237
-
238
- static fromDict(data: Record<string, unknown>): HelloFrame {
239
- return new HelloFrame(
240
- data["nps_version"] as string,
241
- (data["supported_encodings"] as string[]) ?? [],
242
- (data["supported_protocols"] as string[]) ?? [],
243
- (data["min_version"] as string | null) ?? undefined,
244
- (data["agent_id"] as string | null) ?? undefined,
245
- (data["max_frame_payload"] as number | null) ?? HelloFrame.DEFAULT_MAX_FRAME_PAYLOAD,
246
- (data["ext_support"] as boolean | null) ?? false,
247
- (data["max_concurrent_streams"] as number | null) ?? HelloFrame.DEFAULT_MAX_CONCURRENT_STREAMS,
248
- (data["e2e_enc_algorithms"] as string[] | null) ?? undefined,
249
- );
250
- }
251
- }