@labacacia/nps-sdk 1.0.0-alpha.1 → 1.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.npmrc.publish +1 -0
- package/CHANGELOG.cn.md +39 -0
- package/CHANGELOG.md +39 -0
- package/CONTRIBUTING.cn.md +35 -0
- package/CONTRIBUTING.md +2 -0
- package/README.cn.md +155 -0
- package/README.md +5 -3
- package/dist/core/frames.d.ts +1 -0
- package/dist/core/frames.d.ts.map +1 -1
- package/dist/core/frames.js +1 -0
- package/dist/core/frames.js.map +1 -1
- package/dist/core/index.d.ts +6 -4
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +17 -5
- package/dist/core/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/ncp/frames.d.ts +18 -0
- package/dist/ncp/frames.d.ts.map +1 -1
- package/dist/ncp/frames.js +45 -0
- package/dist/ncp/frames.js.map +1 -1
- package/dist/ncp/registry.d.ts.map +1 -1
- package/dist/ncp/registry.js +2 -1
- package/dist/ncp/registry.js.map +1 -1
- package/doc/nps-sdk.core.cn.md +321 -0
- package/doc/nps-sdk.core.md +326 -0
- package/doc/nps-sdk.ncp.cn.md +270 -0
- package/doc/nps-sdk.ncp.md +276 -0
- package/doc/nps-sdk.ndp.cn.md +267 -0
- package/doc/nps-sdk.ndp.md +273 -0
- package/doc/nps-sdk.nip.cn.md +235 -0
- package/doc/nps-sdk.nip.md +242 -0
- package/doc/nps-sdk.nop.cn.md +329 -0
- package/doc/nps-sdk.nop.md +332 -0
- package/doc/nps-sdk.nwp.cn.md +217 -0
- package/doc/nps-sdk.nwp.md +224 -0
- package/doc/overview.cn.md +149 -0
- package/doc/overview.md +153 -0
- package/package.json +21 -4
- package/src/core/frames.ts +1 -0
- package/src/core/index.ts +37 -5
- package/src/index.ts +1 -1
- package/src/ncp/frames.ts +52 -0
- package/src/ncp/registry.ts +2 -1
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
[English Version](./nps-sdk.ncp.md) | 中文版
|
|
2
|
+
|
|
3
|
+
# `@labacacia/nps-sdk/ncp` — 类与方法参考
|
|
4
|
+
|
|
5
|
+
> 规范:[NPS-1 NCP v0.4](https://github.com/labacacia/NPS-Release/blob/main/spec/NPS-1-NCP.md)
|
|
6
|
+
|
|
7
|
+
NCP 是线路与 schema 层。本模块提供五个核心帧
|
|
8
|
+
(`AnchorFrame`、`DiffFrame`、`StreamFrame`、`CapsFrame`、`ErrorFrame`),
|
|
9
|
+
以及原生模式握手辅助(`HelloFrame`)和接收方用于重组分块流的 stream manager。
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 目录
|
|
14
|
+
|
|
15
|
+
- [`AnchorFrame` (0x01)](#anchorframe-0x01)
|
|
16
|
+
- [`DiffFrame` (0x02)](#diffframe-0x02)
|
|
17
|
+
- [`StreamFrame` (0x03)](#streamframe-0x03)
|
|
18
|
+
- [`CapsFrame` (0x04)](#capsframe-0x04)
|
|
19
|
+
- [`HelloFrame` (0x06)](#helloframe-0x06)
|
|
20
|
+
- [`ErrorFrame` (0xFE)](#errorframe-0xfe)
|
|
21
|
+
- [握手辅助](#握手辅助)
|
|
22
|
+
- [`StreamManager`](#streammanager)
|
|
23
|
+
- [`NCP_ERROR_CODES`](#ncp_error_codes)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## `AnchorFrame` (0x01)
|
|
28
|
+
|
|
29
|
+
Schema 锚点 —— 内容寻址的 schema 广告。
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
interface SchemaField {
|
|
33
|
+
name: string;
|
|
34
|
+
type: string; // "string" | "uint64" | "int64" | "decimal" |
|
|
35
|
+
// "bool" | "timestamp" | "bytes" | "object" | "array"
|
|
36
|
+
semantic?: string; // 可选的 NPS 语义标签
|
|
37
|
+
nullable?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface FrameSchema {
|
|
41
|
+
fields: SchemaField[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface AnchorFrame {
|
|
45
|
+
frame: "0x01";
|
|
46
|
+
anchor_id: string; // "sha256:{64 位小写 hex}"
|
|
47
|
+
schema: FrameSchema;
|
|
48
|
+
ttl?: number; // 秒;0 = 仅 session
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function computeAnchorId(schema: FrameSchema): string;
|
|
52
|
+
function validateAnchorFrame(frame: AnchorFrame): void;
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`computeAnchorId` 通过 RFC 8785 JCS 规范化 schema,用 SHA-256 哈希,
|
|
56
|
+
返回 `"sha256:{hex}"`。
|
|
57
|
+
|
|
58
|
+
`validateAnchorFrame` 在以下情况抛 `NcpError("NCP-ANCHOR-SCHEMA-INVALID")`:
|
|
59
|
+
- 字段拥有未知的 `type`,或
|
|
60
|
+
- `anchor_id` 与计算出的规范哈希不匹配。
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## `DiffFrame` (0x02)
|
|
65
|
+
|
|
66
|
+
锚定到之前某个 `AnchorFrame` 的增量数据补丁。
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
interface JsonPatchOperation {
|
|
70
|
+
op: "add" | "remove" | "replace" | "move" | "copy" | "test";
|
|
71
|
+
path: string; // JSON Pointer
|
|
72
|
+
value?: unknown;
|
|
73
|
+
from?: string; // 用于 move/copy
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
type PatchFormat = "json_patch" | "binary_bitset";
|
|
77
|
+
|
|
78
|
+
interface DiffFrame {
|
|
79
|
+
frame: "0x02";
|
|
80
|
+
anchor_ref: string;
|
|
81
|
+
base_seq: number;
|
|
82
|
+
patch_format?: PatchFormat;
|
|
83
|
+
patch: JsonPatchOperation[] | Uint8Array;
|
|
84
|
+
entity_id?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function validateDiffSeq(frame: DiffFrame, currentSeq: number): void;
|
|
88
|
+
function validateDiffFrame(frame: DiffFrame, encodingTier: EncodingTier): void;
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
- 若 `base_seq !== currentSeq`,`validateDiffSeq` 抛
|
|
92
|
+
`NcpError("NCP-STREAM-SEQ-GAP")`。
|
|
93
|
+
- 当 `patch_format` 未知,或在非 Tier-2 MsgPack 帧上使用 `binary_bitset` 时,
|
|
94
|
+
`validateDiffFrame` 抛 `NcpError("NCP-DIFF-FORMAT-UNSUPPORTED")`。
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## `StreamFrame` (0x03)
|
|
99
|
+
|
|
100
|
+
流式分块帧。
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
interface StreamFrame {
|
|
104
|
+
frame: "0x03";
|
|
105
|
+
stream_id: string; // UUID v4
|
|
106
|
+
seq: number;
|
|
107
|
+
is_last: boolean;
|
|
108
|
+
anchor_ref?: string;
|
|
109
|
+
data: unknown[];
|
|
110
|
+
window_size?: number; // 反压提示
|
|
111
|
+
error_code?: string; // 终止错误 —— 隐含 is_last=true
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function validateStreamFrame(frame: StreamFrame): void;
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`stream_id` 必须匹配 UUID-v4 形状,否则抛
|
|
118
|
+
`NcpError("NPS-CLIENT-BAD-FRAME")`。
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## `CapsFrame` (0x04)
|
|
123
|
+
|
|
124
|
+
胶囊 —— 引用某个已缓存 schema 的完整结果页。
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
interface CapsFrameInlineAnchor {
|
|
128
|
+
anchor_id: string;
|
|
129
|
+
schema: FrameSchema;
|
|
130
|
+
ttl?: number;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
interface CapsFrame {
|
|
134
|
+
frame: "0x04";
|
|
135
|
+
anchor_ref: string;
|
|
136
|
+
count: number;
|
|
137
|
+
data: unknown[];
|
|
138
|
+
next_cursor?: string | null;
|
|
139
|
+
token_est?: number;
|
|
140
|
+
tokenizer_used?: string;
|
|
141
|
+
cached?: boolean;
|
|
142
|
+
inline_anchor?: CapsFrameInlineAnchor;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function validateCapsFrame(frame: CapsFrame): void;
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`validateCapsFrame` 强制:
|
|
149
|
+
- `count === data.length` —— 否则 `NpsStatusCodes.NPS_CLIENT_BAD_FRAME`。
|
|
150
|
+
- 若 `inline_anchor` 存在,`inline_anchor.anchor_id` 必须与
|
|
151
|
+
`inline_anchor.schema` 的规范哈希匹配 —— 否则
|
|
152
|
+
`NcpError("NCP-ANCHOR-SCHEMA-INVALID")`。
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## `HelloFrame` (0x06)
|
|
157
|
+
|
|
158
|
+
原生模式客户端握手(NPS-1 §4.6)。
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
interface HelloFrame {
|
|
162
|
+
frame: "0x06";
|
|
163
|
+
nps_version: string;
|
|
164
|
+
min_version?: string;
|
|
165
|
+
supported_encodings: string[]; // 非空
|
|
166
|
+
supported_protocols: string[]; // 非空
|
|
167
|
+
agent_id?: string;
|
|
168
|
+
max_frame_payload?: number;
|
|
169
|
+
ext_support?: boolean;
|
|
170
|
+
max_concurrent_streams?: number;
|
|
171
|
+
e2e_enc_algorithms?: string[];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function validateHelloFrame(frame: HelloFrame): void;
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
`validateHelloFrame` 检查三个必填字段存在且两个数组字段非空。
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## `ErrorFrame` (0xFE)
|
|
182
|
+
|
|
183
|
+
所有 NPS 协议层共用的统一错误帧(NPS-0 §9)。
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
interface ErrorFrame {
|
|
187
|
+
frame: "0xFE";
|
|
188
|
+
status: string; // NPS 状态码,如 "NPS-CLIENT-NOT-FOUND"
|
|
189
|
+
error: string; // 协议码,如 "NCP-ANCHOR-NOT-FOUND"
|
|
190
|
+
message?: string;
|
|
191
|
+
details?: Record<string, unknown>;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function isErrorFrame(obj: unknown): obj is ErrorFrame;
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
在解码后的 payload 上把 `isErrorFrame` 用作类型 guard。
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## 握手辅助
|
|
202
|
+
|
|
203
|
+
原生模式的版本与编码协商(NPS-1 §2.6)。
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
function negotiateVersion(
|
|
207
|
+
client: { nps_version: string; min_version?: string },
|
|
208
|
+
server: { nps_version: string },
|
|
209
|
+
): { session_version: string; compatible: boolean; error_code?: string };
|
|
210
|
+
|
|
211
|
+
function negotiateEncoding(
|
|
212
|
+
client: string[],
|
|
213
|
+
server: string[],
|
|
214
|
+
): { encoding: string | null };
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
- 版本比较按分量数字进行 —— `"0.9" < "0.10" < "1.0"`。
|
|
218
|
+
- 当双方都广告 `"msgpack"` 时 `negotiateEncoding` 返回它,
|
|
219
|
+
否则降级到 `"json"`,再否则按客户端偏好顺序选择第一个共有项。
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## `StreamManager`
|
|
224
|
+
|
|
225
|
+
追踪并发 `StreamFrame` 流,强制顺序与流控窗口。
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
class StreamManager {
|
|
229
|
+
constructor(options?: { maxConcurrent?: number }); // 默认 32
|
|
230
|
+
|
|
231
|
+
receive(frame: StreamFrame): boolean; // 流完成时返回 true
|
|
232
|
+
send(frame: StreamFrame): void; // 强制发出窗口
|
|
233
|
+
updateWindow(streamId: string, newSize: number): void;
|
|
234
|
+
isPaused(streamId: string): boolean;
|
|
235
|
+
|
|
236
|
+
getData(streamId: string): unknown[] | null; // 完成后扁平化分块
|
|
237
|
+
getError(streamId: string): string | undefined;
|
|
238
|
+
readonly activeCount: number;
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### 失败模式
|
|
243
|
+
|
|
244
|
+
| 条件 | 抛出 |
|
|
245
|
+
|------|------|
|
|
246
|
+
| 未知流上 `seq !== 0` | `NcpError("NCP-STREAM-NOT-FOUND")` |
|
|
247
|
+
| 并发流过多 | `NcpError("NCP-STREAM-LIMIT-EXCEEDED")` |
|
|
248
|
+
| 写入已完成的流 | `NcpError("NPS-CLIENT-CONFLICT")` |
|
|
249
|
+
| 接收顺序中的 `seq` 跳号 | `NcpError("NCP-STREAM-SEQ-GAP")` |
|
|
250
|
+
| 发送窗口耗尽 | `NcpError("NCP-STREAM-WINDOW-OVERFLOW")` |
|
|
251
|
+
|
|
252
|
+
`seq` 小于 `expectedSeq` 的重复帧被静默忽略(幂等)。
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## `NCP_ERROR_CODES`
|
|
257
|
+
|
|
258
|
+
NCP 层协议码常量包(NPS-1 §6、§7.4)。
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
import { NCP_ERROR_CODES } from "@labacacia/nps-sdk/ncp";
|
|
262
|
+
|
|
263
|
+
NCP_ERROR_CODES.NCP_ANCHOR_NOT_FOUND; // "NCP-ANCHOR-NOT-FOUND"
|
|
264
|
+
NCP_ERROR_CODES.NCP_STREAM_SEQ_GAP; // "NCP-STREAM-SEQ-GAP"
|
|
265
|
+
NCP_ERROR_CODES.NCP_VERSION_INCOMPATIBLE; // "NCP-VERSION-INCOMPATIBLE"
|
|
266
|
+
// …
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
`NCP_FRAME_PARSE_ERROR` 和 `NCP_FRAME_INCOMPLETE` 为规范 §6 未定义的
|
|
270
|
+
实现专属码;其余均为规范标准。
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
English | [中文版](./nps-sdk.ncp.cn.md)
|
|
2
|
+
|
|
3
|
+
# `@labacacia/nps-sdk/ncp` — Class and Method Reference
|
|
4
|
+
|
|
5
|
+
> Spec: [NPS-1 NCP v0.4](https://github.com/labacacia/NPS-Release/blob/main/spec/NPS-1-NCP.md)
|
|
6
|
+
|
|
7
|
+
NCP is the wire-and-schema layer. This module provides the five core frames
|
|
8
|
+
(`AnchorFrame`, `DiffFrame`, `StreamFrame`, `CapsFrame`, `ErrorFrame`) plus
|
|
9
|
+
the native-mode handshake helpers (`HelloFrame`) and the stream manager used
|
|
10
|
+
by receivers to reassemble chunked streams.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Table of contents
|
|
15
|
+
|
|
16
|
+
- [`AnchorFrame` (0x01)](#anchorframe-0x01)
|
|
17
|
+
- [`DiffFrame` (0x02)](#diffframe-0x02)
|
|
18
|
+
- [`StreamFrame` (0x03)](#streamframe-0x03)
|
|
19
|
+
- [`CapsFrame` (0x04)](#capsframe-0x04)
|
|
20
|
+
- [`HelloFrame` (0x06)](#helloframe-0x06)
|
|
21
|
+
- [`ErrorFrame` (0xFE)](#errorframe-0xfe)
|
|
22
|
+
- [Handshake helpers](#handshake-helpers)
|
|
23
|
+
- [`StreamManager`](#streammanager)
|
|
24
|
+
- [`NCP_ERROR_CODES`](#ncp_error_codes)
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## `AnchorFrame` (0x01)
|
|
29
|
+
|
|
30
|
+
Schema anchor — content-addressed schema advertisement.
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
interface SchemaField {
|
|
34
|
+
name: string;
|
|
35
|
+
type: string; // "string" | "uint64" | "int64" | "decimal" |
|
|
36
|
+
// "bool" | "timestamp" | "bytes" | "object" | "array"
|
|
37
|
+
semantic?: string; // optional NPS semantic tag
|
|
38
|
+
nullable?: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface FrameSchema {
|
|
42
|
+
fields: SchemaField[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface AnchorFrame {
|
|
46
|
+
frame: "0x01";
|
|
47
|
+
anchor_id: string; // "sha256:{64 lowercase hex}"
|
|
48
|
+
schema: FrameSchema;
|
|
49
|
+
ttl?: number; // seconds; 0 = session-only
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function computeAnchorId(schema: FrameSchema): string;
|
|
53
|
+
function validateAnchorFrame(frame: AnchorFrame): void;
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`computeAnchorId` canonicalises the schema via RFC 8785 JCS, hashes with
|
|
57
|
+
SHA-256 and returns `"sha256:{hex}"`.
|
|
58
|
+
|
|
59
|
+
`validateAnchorFrame` raises `NcpError("NCP-ANCHOR-SCHEMA-INVALID")` when:
|
|
60
|
+
- a field has an unknown `type`, OR
|
|
61
|
+
- `anchor_id` does not match the computed canonical hash.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## `DiffFrame` (0x02)
|
|
66
|
+
|
|
67
|
+
Incremental data patch anchored to a prior `AnchorFrame`.
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
interface JsonPatchOperation {
|
|
71
|
+
op: "add" | "remove" | "replace" | "move" | "copy" | "test";
|
|
72
|
+
path: string; // JSON Pointer
|
|
73
|
+
value?: unknown;
|
|
74
|
+
from?: string; // for move/copy
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
type PatchFormat = "json_patch" | "binary_bitset";
|
|
78
|
+
|
|
79
|
+
interface DiffFrame {
|
|
80
|
+
frame: "0x02";
|
|
81
|
+
anchor_ref: string;
|
|
82
|
+
base_seq: number;
|
|
83
|
+
patch_format?: PatchFormat;
|
|
84
|
+
patch: JsonPatchOperation[] | Uint8Array;
|
|
85
|
+
entity_id?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function validateDiffSeq(frame: DiffFrame, currentSeq: number): void;
|
|
89
|
+
function validateDiffFrame(frame: DiffFrame, encodingTier: EncodingTier): void;
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- `validateDiffSeq` throws `NcpError("NCP-STREAM-SEQ-GAP")` if
|
|
93
|
+
`base_seq !== currentSeq`.
|
|
94
|
+
- `validateDiffFrame` throws `NcpError("NCP-DIFF-FORMAT-UNSUPPORTED")`
|
|
95
|
+
on unknown `patch_format`, or when `binary_bitset` is used on a
|
|
96
|
+
non-Tier-2 MsgPack frame.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## `StreamFrame` (0x03)
|
|
101
|
+
|
|
102
|
+
Streaming chunk frame.
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
interface StreamFrame {
|
|
106
|
+
frame: "0x03";
|
|
107
|
+
stream_id: string; // UUID v4
|
|
108
|
+
seq: number;
|
|
109
|
+
is_last: boolean;
|
|
110
|
+
anchor_ref?: string;
|
|
111
|
+
data: unknown[];
|
|
112
|
+
window_size?: number; // back-pressure hint
|
|
113
|
+
error_code?: string; // terminal error — implies is_last=true
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function validateStreamFrame(frame: StreamFrame): void;
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`stream_id` must match the UUID-v4 shape, otherwise
|
|
120
|
+
`NcpError("NPS-CLIENT-BAD-FRAME")` is raised.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## `CapsFrame` (0x04)
|
|
125
|
+
|
|
126
|
+
Capsule — a complete result page that references a cached schema.
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
interface CapsFrameInlineAnchor {
|
|
130
|
+
anchor_id: string;
|
|
131
|
+
schema: FrameSchema;
|
|
132
|
+
ttl?: number;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
interface CapsFrame {
|
|
136
|
+
frame: "0x04";
|
|
137
|
+
anchor_ref: string;
|
|
138
|
+
count: number;
|
|
139
|
+
data: unknown[];
|
|
140
|
+
next_cursor?: string | null;
|
|
141
|
+
token_est?: number;
|
|
142
|
+
tokenizer_used?: string;
|
|
143
|
+
cached?: boolean;
|
|
144
|
+
inline_anchor?: CapsFrameInlineAnchor;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function validateCapsFrame(frame: CapsFrame): void;
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`validateCapsFrame` enforces:
|
|
151
|
+
- `count === data.length` — otherwise `NpsStatusCodes.NPS_CLIENT_BAD_FRAME`.
|
|
152
|
+
- If `inline_anchor` is present, `inline_anchor.anchor_id` matches the
|
|
153
|
+
canonical hash of `inline_anchor.schema` — otherwise
|
|
154
|
+
`NcpError("NCP-ANCHOR-SCHEMA-INVALID")`.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## `HelloFrame` (0x06)
|
|
159
|
+
|
|
160
|
+
Native-mode client handshake (NPS-1 §4.6).
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
interface HelloFrame {
|
|
164
|
+
frame: "0x06";
|
|
165
|
+
nps_version: string;
|
|
166
|
+
min_version?: string;
|
|
167
|
+
supported_encodings: string[]; // non-empty
|
|
168
|
+
supported_protocols: string[]; // non-empty
|
|
169
|
+
agent_id?: string;
|
|
170
|
+
max_frame_payload?: number;
|
|
171
|
+
ext_support?: boolean;
|
|
172
|
+
max_concurrent_streams?: number;
|
|
173
|
+
e2e_enc_algorithms?: string[];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function validateHelloFrame(frame: HelloFrame): void;
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
`validateHelloFrame` checks that the three required fields are present and
|
|
180
|
+
the two array fields are non-empty.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## `ErrorFrame` (0xFE)
|
|
185
|
+
|
|
186
|
+
Unified error frame shared by every NPS protocol layer (NPS-0 §9).
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
interface ErrorFrame {
|
|
190
|
+
frame: "0xFE";
|
|
191
|
+
status: string; // NPS status code, e.g. "NPS-CLIENT-NOT-FOUND"
|
|
192
|
+
error: string; // protocol code, e.g. "NCP-ANCHOR-NOT-FOUND"
|
|
193
|
+
message?: string;
|
|
194
|
+
details?: Record<string, unknown>;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function isErrorFrame(obj: unknown): obj is ErrorFrame;
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Use `isErrorFrame` as a type guard on decoded payloads.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Handshake helpers
|
|
205
|
+
|
|
206
|
+
Version & encoding negotiation for native mode (NPS-1 §2.6).
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
function negotiateVersion(
|
|
210
|
+
client: { nps_version: string; min_version?: string },
|
|
211
|
+
server: { nps_version: string },
|
|
212
|
+
): { session_version: string; compatible: boolean; error_code?: string };
|
|
213
|
+
|
|
214
|
+
function negotiateEncoding(
|
|
215
|
+
client: string[],
|
|
216
|
+
server: string[],
|
|
217
|
+
): { encoding: string | null };
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
- Version comparison is component-wise numeric — `"0.9" < "0.10" < "1.0"`.
|
|
221
|
+
- `negotiateEncoding` returns `"msgpack"` when both sides advertise it,
|
|
222
|
+
falling back to `"json"`, then the first mutual entry in the client's
|
|
223
|
+
preference order.
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## `StreamManager`
|
|
228
|
+
|
|
229
|
+
Tracks concurrent `StreamFrame` streams, enforces sequence ordering and
|
|
230
|
+
flow-control windows.
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
class StreamManager {
|
|
234
|
+
constructor(options?: { maxConcurrent?: number }); // default 32
|
|
235
|
+
|
|
236
|
+
receive(frame: StreamFrame): boolean; // true when stream is complete
|
|
237
|
+
send(frame: StreamFrame): void; // enforces outgoing window
|
|
238
|
+
updateWindow(streamId: string, newSize: number): void;
|
|
239
|
+
isPaused(streamId: string): boolean;
|
|
240
|
+
|
|
241
|
+
getData(streamId: string): unknown[] | null; // flattened chunks after completion
|
|
242
|
+
getError(streamId: string): string | undefined;
|
|
243
|
+
readonly activeCount: number;
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Failure modes
|
|
248
|
+
|
|
249
|
+
| Condition | Thrown |
|
|
250
|
+
|-----------|--------|
|
|
251
|
+
| `seq !== 0` on an unknown stream | `NcpError("NCP-STREAM-NOT-FOUND")` |
|
|
252
|
+
| Too many concurrent streams open | `NcpError("NCP-STREAM-LIMIT-EXCEEDED")` |
|
|
253
|
+
| Writing to a stream already completed | `NcpError("NPS-CLIENT-CONFLICT")` |
|
|
254
|
+
| `seq` gap in received order | `NcpError("NCP-STREAM-SEQ-GAP")` |
|
|
255
|
+
| Outgoing window exhausted | `NcpError("NCP-STREAM-WINDOW-OVERFLOW")` |
|
|
256
|
+
|
|
257
|
+
Duplicate frames with a `seq` lower than `expectedSeq` are silently
|
|
258
|
+
ignored (idempotent).
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## `NCP_ERROR_CODES`
|
|
263
|
+
|
|
264
|
+
Constant bundle of NCP-layer protocol codes (NPS-1 §6, §7.4).
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
import { NCP_ERROR_CODES } from "@labacacia/nps-sdk/ncp";
|
|
268
|
+
|
|
269
|
+
NCP_ERROR_CODES.NCP_ANCHOR_NOT_FOUND; // "NCP-ANCHOR-NOT-FOUND"
|
|
270
|
+
NCP_ERROR_CODES.NCP_STREAM_SEQ_GAP; // "NCP-STREAM-SEQ-GAP"
|
|
271
|
+
NCP_ERROR_CODES.NCP_VERSION_INCOMPATIBLE; // "NCP-VERSION-INCOMPATIBLE"
|
|
272
|
+
// …
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
`NCP_FRAME_PARSE_ERROR` and `NCP_FRAME_INCOMPLETE` are implementation-only
|
|
276
|
+
codes not defined in the spec § 6; everything else is spec-canonical.
|