ai-fly 0.0.0 → 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.
@@ -0,0 +1,67 @@
1
+ import { n as UsageError, r as reportCliError } from "./errors-BGtxPkM7.mjs";
2
+ import { o as parseArgv } from "./config-CO3WyW2H.mjs";
3
+ import { c as requireString, d as str, l as resolveDataDir, r as openStore } from "./common-BULbPtYn.mjs";
4
+ import { homedir } from "node:os";
5
+ //#region src/cli/commands/provider/key.ts
6
+ const SPEC = {
7
+ data: {
8
+ type: "string",
9
+ tilde: true
10
+ },
11
+ group: { type: "string" }
12
+ };
13
+ const USAGE = `usage:
14
+ ai-fly key issue --group <name> [--data <dir>]
15
+ ai-fly key list [--data <dir>]
16
+ ai-fly key revoke <keyId> [--data <dir>]`;
17
+ async function run(argv, ctx = {}) {
18
+ try {
19
+ const home = ctx.homedir ?? homedir();
20
+ const { options, positionals } = parseArgv(argv, SPEC, { homedir: home });
21
+ const sub = positionals[0];
22
+ if (sub === "issue") return issue(options, home);
23
+ if (sub === "list") return list(options, home);
24
+ if (sub === "revoke") return revoke(options, positionals, home);
25
+ if (sub === void 0) throw new UsageError(USAGE);
26
+ throw new UsageError(`error: unknown key subcommand '${sub}' (known: issue, list, revoke)`);
27
+ } catch (err) {
28
+ return reportCliError(err);
29
+ }
30
+ }
31
+ function issue(options, home) {
32
+ const group = requireString(str(options.group), "group");
33
+ const issued = openStore(resolveDataDir(str(options.data), home)).issueKey(group);
34
+ process.stdout.write([
35
+ `key issued for group '${group}'`,
36
+ ` keyId: ${issued.keyId}`,
37
+ ` key : ${issued.key}`,
38
+ "",
39
+ "This secret is shown only once. Store it now (password manager or share link).",
40
+ "Revoke with: ai-fly key revoke " + issued.keyId,
41
+ ""
42
+ ].join("\n"));
43
+ return 0;
44
+ }
45
+ function list(options, home) {
46
+ const keys = openStore(resolveDataDir(str(options.data), home)).listKeys();
47
+ if (keys.length === 0) {
48
+ process.stdout.write("no keys issued (see: ai-fly key issue --group <name>)\n");
49
+ return 0;
50
+ }
51
+ const lines = [];
52
+ for (const k of keys) {
53
+ const status = k.revokedAt === void 0 ? "active" : `revoked at ${new Date(k.revokedAt).toISOString()}`;
54
+ lines.push(`${k.keyId} group=${k.group} created=${new Date(k.createdAt).toISOString()} ${status}`);
55
+ }
56
+ process.stdout.write(`${lines.join("\n")}\n`);
57
+ return 0;
58
+ }
59
+ function revoke(options, positionals, home) {
60
+ const keyId = positionals[1];
61
+ if (keyId === void 0) throw new UsageError("error: key revoke requires a <keyId> argument");
62
+ const record = openStore(resolveDataDir(str(options.data), home)).revokeKey(keyId);
63
+ process.stdout.write(record.revokedAt === void 0 ? `key revoked: ${keyId}\n` : `key was already revoked: ${keyId}\n`);
64
+ return 0;
65
+ }
66
+ //#endregion
67
+ export { run };
@@ -0,0 +1,630 @@
1
+ import { i as randomZ32 } from "./config-CO3WyW2H.mjs";
2
+ import { a as FRAME_TYPE, d as classifySchemaFailure, f as isKnownFrameType, i as FRAME_HEADER_SCHEMAS, n as ERROR_CODE, o as FRAME_TYPE_NAME } from "./sdk-C6xDRbzg.mjs";
3
+ //#region src/wire/codec.ts
4
+ const WIRE_MAGIC_PREFIX = "aifly";
5
+ const WIRE_MAGIC = "aifly1";
6
+ /** 会话层帧上限(fabric MAX_FRAME 同值;含 9 字节本协议帧头)。 */
7
+ const MAX_FRAME_BYTES = 1 << 20;
8
+ /** JSON 头结构上限(spec「帧资源上限」)。 */
9
+ const MAX_JSON_HEADER_BYTES = 16384;
10
+ /** 正文分片默认上限(队头阻塞实证 → 远小于帧上限)。 */
11
+ const DEFAULT_BODY_CHUNK_BYTES = 262144;
12
+ /** 正文分片可配置上限的硬顶(spec:MUST ≤ 960 KiB)。 */
13
+ const MAX_CONFIGURABLE_BODY_CHUNK_BYTES = 983040;
14
+ const TEXT_ENCODER = new TextEncoder();
15
+ const TEXT_DECODER = new TextDecoder();
16
+ const MAGIC_PREFIX_BYTES = TEXT_ENCODER.encode(WIRE_MAGIC_PREFIX);
17
+ const MAGIC_BYTES = TEXT_ENCODER.encode(WIRE_MAGIC);
18
+ const EMPTY_BODY = /* @__PURE__ */ new Uint8Array(0);
19
+ /** 本地编码断言失败(编程 / 配置错误,非对端可观测事件)。 */
20
+ var WireEncodeError = class extends Error {
21
+ code;
22
+ constructor(code, message) {
23
+ super(message);
24
+ this.name = "WireEncodeError";
25
+ this.code = code;
26
+ }
27
+ };
28
+ function assertChunkLimit(limit) {
29
+ if (!Number.isInteger(limit) || limit <= 0 || limit > 983040) throw new WireEncodeError("invalid_chunk_config", `body chunk limit must be an integer in (0, ${MAX_CONFIGURABLE_BODY_CHUNK_BYTES}], got ${limit}`);
30
+ }
31
+ /** 编码单帧。断言:JSON 头 ≤16KiB、正文 ≤ 分片上限、整帧 ≤1MiB。 */
32
+ function encodeFrame(frame, opts = {}) {
33
+ const chunkLimit = opts.bodyChunkLimitBytes ?? 262144;
34
+ assertChunkLimit(chunkLimit);
35
+ if (!isKnownFrameType(frame.type)) throw new WireEncodeError("unknown_frame_type", `cannot encode unknown frame type 0x${frame.type.toString(16)}`);
36
+ const headerBytes = TEXT_ENCODER.encode(JSON.stringify(frame.header));
37
+ if (headerBytes.length > 16384) throw new WireEncodeError("json_header_too_large", `json header is ${headerBytes.length} bytes (max ${MAX_JSON_HEADER_BYTES})`);
38
+ const body = frame.body ?? EMPTY_BODY;
39
+ if (body.length > chunkLimit) throw new WireEncodeError("body_chunk_too_large", `body is ${body.length} bytes (chunk limit ${chunkLimit})`);
40
+ const total = 9 + headerBytes.length + body.length;
41
+ if (total > 1048576) throw new WireEncodeError("frame_too_large", `frame is ${total} bytes (max ${MAX_FRAME_BYTES})`);
42
+ const out = new Uint8Array(total);
43
+ out.set(MAGIC_BYTES, 0);
44
+ out[6] = frame.type;
45
+ out[7] = headerBytes.length >> 8 & 255;
46
+ out[8] = headerBytes.length & 255;
47
+ out.set(headerBytes, 9);
48
+ out.set(body, 9 + headerBytes.length);
49
+ return out;
50
+ }
51
+ /**
52
+ * 解码单帧(每条 fabric envelope 即一个完整 buffer)。
53
+ * - 非 "aifly" 家族 → null(静默忽略,混流共存);
54
+ * - 家族匹配但版本位非 '1'、或 JSON 头 v 字段 ≠1 → unknown-version(可回送
55
+ * protocol_version ERROR);
56
+ * - 版本匹配但类型号未定义 → unknown-type(记录并忽略,不影响后续帧);
57
+ * - 结构损坏(截断 / JSON 非法 / 头非对象 / 超长)→ malformed。
58
+ */
59
+ function decodeFrame(buffer) {
60
+ if (buffer.length < MAGIC_PREFIX_BYTES.length) return null;
61
+ for (let i = 0; i < MAGIC_PREFIX_BYTES.length; i++) if (buffer[i] !== MAGIC_PREFIX_BYTES[i]) return null;
62
+ if (buffer.length > 1048576) return {
63
+ kind: "malformed",
64
+ reason: "frame_too_large"
65
+ };
66
+ if (buffer.length >= 6 && buffer[5] !== MAGIC_BYTES[5]) return { kind: "unknown-version" };
67
+ if (buffer.length < 9) return {
68
+ kind: "malformed",
69
+ reason: "truncated_header"
70
+ };
71
+ const type = buffer[6];
72
+ const jsonLen = buffer[7] << 8 | buffer[8];
73
+ if (jsonLen > 16384) return {
74
+ kind: "malformed",
75
+ reason: "json_header_too_large"
76
+ };
77
+ if (9 + jsonLen > buffer.length) return {
78
+ kind: "malformed",
79
+ reason: "truncated_json"
80
+ };
81
+ let header;
82
+ try {
83
+ header = JSON.parse(TEXT_DECODER.decode(buffer.subarray(9, 9 + jsonLen)));
84
+ } catch {
85
+ return {
86
+ kind: "malformed",
87
+ reason: "invalid_json"
88
+ };
89
+ }
90
+ if (typeof header !== "object" || header === null || Array.isArray(header)) return {
91
+ kind: "malformed",
92
+ reason: "header_not_object"
93
+ };
94
+ const v = header.v;
95
+ if (v !== void 0 && v !== 1) return { kind: "unknown-version" };
96
+ if (!isKnownFrameType(type)) return {
97
+ kind: "unknown-type",
98
+ type
99
+ };
100
+ return {
101
+ kind: "frame",
102
+ type,
103
+ header,
104
+ body: buffer.subarray(9 + jsonLen)
105
+ };
106
+ }
107
+ /**
108
+ * 按 design A3「body 内联(≤分片上限)或空」拆分:超限正文的 REQ 帧体为空,
109
+ * 全部字节经 REQ_BODY 分片承载;接收侧按 seq 从 0 重组(mux.ts 负责)。
110
+ */
111
+ function splitBody(body, chunkLimitBytes = DEFAULT_BODY_CHUNK_BYTES) {
112
+ assertChunkLimit(chunkLimitBytes);
113
+ if (body.length <= chunkLimitBytes) return {
114
+ firstInline: true,
115
+ inlineBody: body,
116
+ chunks: []
117
+ };
118
+ const chunks = [];
119
+ for (let off = 0; off < body.length; off += chunkLimitBytes) {
120
+ const end = off + chunkLimitBytes >= body.length;
121
+ chunks.push({
122
+ seq: chunks.length,
123
+ end,
124
+ data: body.subarray(off, end ? body.length : off + chunkLimitBytes)
125
+ });
126
+ }
127
+ return {
128
+ firstInline: false,
129
+ inlineBody: EMPTY_BODY,
130
+ chunks
131
+ };
132
+ }
133
+ //#endregion
134
+ //#region src/wire/fabric-adapter.ts
135
+ var FabricWireAdapter = class {
136
+ fabric;
137
+ peerId;
138
+ frameCbs = /* @__PURE__ */ new Set();
139
+ closeCbs = /* @__PURE__ */ new Set();
140
+ unsubscribes = [];
141
+ closed = false;
142
+ constructor(fabric, peerId) {
143
+ this.fabric = fabric;
144
+ this.peerId = peerId;
145
+ this.unsubscribes.push(fabric.on((event) => {
146
+ if (event.type === "message" && event.from === peerId) {
147
+ const decoded = decodeFrame(new Uint8Array(event.data));
148
+ if (decoded === null) return;
149
+ for (const cb of this.frameCbs) cb(decoded);
150
+ } else if (event.type === "peer-disconnected" && event.endpointId === peerId) this.handleClose("peer-disconnected");
151
+ }));
152
+ }
153
+ get endpointId() {
154
+ return this.peerId;
155
+ }
156
+ async send(data) {
157
+ if (this.closed) throw new Error("transport closed");
158
+ await this.fabric.send(this.peerId, Buffer.from(data));
159
+ }
160
+ onFrame(cb) {
161
+ this.frameCbs.add(cb);
162
+ }
163
+ close(reason) {
164
+ this.handleClose(reason ?? "local-close");
165
+ }
166
+ onClose(cb) {
167
+ this.closeCbs.add(cb);
168
+ }
169
+ /** 释放事件订阅(进程退出路径);不断开 fabric 连接本身。 */
170
+ dispose() {
171
+ for (const off of this.unsubscribes.splice(0)) off();
172
+ }
173
+ handleClose(reason) {
174
+ if (this.closed) return;
175
+ this.closed = true;
176
+ for (const off of this.unsubscribes.splice(0)) off();
177
+ for (const cb of this.closeCbs) cb(reason);
178
+ }
179
+ };
180
+ const NO_BODY = /* @__PURE__ */ new Uint8Array(0);
181
+ /** 本地误用(出站方向违规 / 未知类型 / 已关闭后发送)。 */
182
+ var WireSessionError = class extends Error {
183
+ code;
184
+ constructor(code, message) {
185
+ super(message);
186
+ this.name = "WireSessionError";
187
+ this.code = code;
188
+ }
189
+ };
190
+ /** 帧方向表(spec:ERROR 仅提供方发出;CLOSE 双向;使用方侧终结均为本地动作)。 */
191
+ const FRAME_META = {
192
+ [FRAME_TYPE.AUTH]: { dir: "c2p" },
193
+ [FRAME_TYPE.AUTH_OK]: { dir: "p2c" },
194
+ [FRAME_TYPE.AUTH_ERR]: { dir: "p2c" },
195
+ [FRAME_TYPE.REQ]: { dir: "c2p" },
196
+ [FRAME_TYPE.REQ_BODY]: {
197
+ dir: "c2p",
198
+ seqStream: "reqBody"
199
+ },
200
+ [FRAME_TYPE.RESP_META]: { dir: "p2c" },
201
+ [FRAME_TYPE.RESP_CHUNK]: {
202
+ dir: "p2c",
203
+ seqStream: "respChunk"
204
+ },
205
+ [FRAME_TYPE.RESP_END]: {
206
+ dir: "p2c",
207
+ terminal: true
208
+ },
209
+ [FRAME_TYPE.ERROR]: {
210
+ dir: "p2c",
211
+ terminal: true
212
+ },
213
+ [FRAME_TYPE.ABORT]: { dir: "c2p" },
214
+ [FRAME_TYPE.PING]: { dir: "p2c" },
215
+ [FRAME_TYPE.DATA_UP]: {
216
+ dir: "c2p",
217
+ seqStream: "dataUp"
218
+ },
219
+ [FRAME_TYPE.DATA_DOWN]: {
220
+ dir: "p2c",
221
+ seqStream: "dataDown"
222
+ },
223
+ [FRAME_TYPE.CLOSE]: {
224
+ dir: "both",
225
+ terminal: true
226
+ }
227
+ };
228
+ /** 提供方自动回送 ERROR 的脱敏 message(英文 ASCII,产品约定)。 */
229
+ function autoErrorMessage(code) {
230
+ switch (code) {
231
+ case ERROR_CODE.forbidden_method: return "http method not allowed";
232
+ case ERROR_CODE.forbidden_header: return "http header not allowed";
233
+ case ERROR_CODE.protocol_seq: return "chunk sequence gap";
234
+ case ERROR_CODE.body_too_large: return "request body exceeds reassembly limit";
235
+ case ERROR_CODE.protocol_version: return "unsupported aifly protocol version";
236
+ default: return "wire protocol violation";
237
+ }
238
+ }
239
+ var WireSession = class {
240
+ role;
241
+ transport;
242
+ hooks;
243
+ idleTimeoutMs;
244
+ maxQueuedPerId;
245
+ reassemblyLimitBytes;
246
+ unauthFrameLimit;
247
+ ids = /* @__PURE__ */ new Map();
248
+ usedIds = /* @__PURE__ */ new Set();
249
+ globalLane = {
250
+ sendLane: Promise.resolve(),
251
+ queuedCount: 0,
252
+ slotWaiters: []
253
+ };
254
+ counters = {
255
+ unauthDropped: 0,
256
+ directionDropped: 0,
257
+ schemaDropped: 0,
258
+ unknownIdDropped: 0,
259
+ terminalDropped: 0,
260
+ malformedDropped: 0,
261
+ unknownTypeIgnored: 0,
262
+ unknownVersionDropped: 0,
263
+ poisonedDropped: 0
264
+ };
265
+ authedFlag = false;
266
+ authPending = false;
267
+ poisonedFlag = false;
268
+ deadFlag = false;
269
+ unauthClosed = false;
270
+ constructor(opts) {
271
+ this.role = opts.role;
272
+ this.transport = opts.transport;
273
+ this.hooks = opts.hooks ?? {};
274
+ this.idleTimeoutMs = opts.idleTimeoutMs ?? 3e5;
275
+ this.maxQueuedPerId = opts.maxQueuedPerId ?? 64;
276
+ this.reassemblyLimitBytes = opts.reassemblyLimitBytes ?? 8388608;
277
+ this.unauthFrameLimit = opts.unauthFrameLimit ?? 32;
278
+ this.transport.onFrame((frame) => this.handleIncoming(frame));
279
+ this.transport.onClose((reason) => this.handleClosed(reason));
280
+ }
281
+ /** AUTH_OK 交换完成后由上层调用(provider 发出 AUTH_OK 后 / consumer 收到并接受后)。 */
282
+ markAuthed() {
283
+ this.authedFlag = true;
284
+ this.authPending = false;
285
+ }
286
+ get authed() {
287
+ return this.authedFlag;
288
+ }
289
+ get poisoned() {
290
+ return this.poisonedFlag;
291
+ }
292
+ get dead() {
293
+ return this.deadFlag;
294
+ }
295
+ /** 观测计数(静默丢弃路径均有计数,供状态上报与测试断言)。 */
296
+ stats() {
297
+ return {
298
+ ...this.counters,
299
+ authed: this.authedFlag,
300
+ poisoned: this.poisonedFlag,
301
+ dead: this.deadFlag
302
+ };
303
+ }
304
+ /** 关闭传输(触发双端 disconnected → 全量在途 id 终结)。 */
305
+ dispose(reason) {
306
+ this.transport.close(reason ?? "local-dispose");
307
+ }
308
+ /** 分配全局唯一 request-id(randomZ32(16));一经分配永不复用。 */
309
+ allocId() {
310
+ for (;;) {
311
+ const id = randomZ32(16);
312
+ if (!this.usedIds.has(id)) {
313
+ this.usedIds.add(id);
314
+ return id;
315
+ }
316
+ }
317
+ }
318
+ /** 显式本地终结(不发帧):之后该 id 出入站帧均静默丢弃。 */
319
+ terminal(id) {
320
+ this.finishId(id, { source: "local" });
321
+ }
322
+ /**
323
+ * 发送一帧(编码断言失败抛 WireEncodeError;出站方向违规抛 WireSessionError)。
324
+ * - 同 id 帧经内部 promise 链顺序 await,保证交付顺序(不同 id 之间无顺序承诺);
325
+ * - 该 id 在途未终结分片 ≥ maxQueuedPerId 时,本次 send 等待队列回落(上层可改用
326
+ * waitOutboundQueue 实现“暂停读上游”);
327
+ * - 终结帧(RESP_END / ERROR / CLOSE)发送即登记终结,且不受队列门控(必然出队)。
328
+ */
329
+ async send(type, header, body = NO_BODY) {
330
+ if (this.deadFlag) throw new WireSessionError("closed", "wire session is closed");
331
+ const meta = FRAME_META[type];
332
+ if (meta === void 0) throw new WireSessionError("misuse", `unknown frame type 0x${type.toString(16)}`);
333
+ if (!this.outboundDirectionOk(meta)) throw new WireSessionError("misuse", `${FRAME_TYPE_NAME[type] ?? type} is not outbound for role '${this.role}'`);
334
+ if (type === FRAME_TYPE.AUTH && this.role === "consumer") this.authPending = true;
335
+ const rawId = header.id;
336
+ const id = typeof rawId === "string" ? rawId : void 0;
337
+ let ctx;
338
+ if (id !== void 0) {
339
+ ctx = this.ids.get(id);
340
+ if (ctx !== void 0 && ctx.terminal) {
341
+ this.counters.terminalDropped++;
342
+ return;
343
+ }
344
+ if (ctx === void 0 && !meta.terminal) ctx = this.registerId(id);
345
+ if (ctx !== void 0) {
346
+ if (this.role === "provider" && ctx.idleSuspended && (type === FRAME_TYPE.RESP_META || type === FRAME_TYPE.RESP_CHUNK || type === FRAME_TYPE.DATA_DOWN)) ctx.idleSuspended = false;
347
+ if (meta.terminal) this.finishId(id, { source: "local" });
348
+ else this.touchIdle(ctx);
349
+ }
350
+ }
351
+ const bytes = encodeFrame({
352
+ type,
353
+ header,
354
+ body
355
+ });
356
+ if (ctx !== void 0 && !meta.terminal) while (ctx.queuedCount >= this.maxQueuedPerId) {
357
+ if (this.deadFlag) throw new WireSessionError("closed", "wire session is closed");
358
+ await new Promise((resolve) => ctx.slotWaiters.push(resolve));
359
+ if (ctx.terminal) {
360
+ this.counters.terminalDropped++;
361
+ return;
362
+ }
363
+ }
364
+ await this.transmit(ctx ?? this.globalLane, bytes);
365
+ }
366
+ /** 该 id 在途未终结分片数(本地队列观测)。 */
367
+ outboundQueueDepth(id) {
368
+ return this.ids.get(id)?.queuedCount ?? 0;
369
+ }
370
+ /** 等待该 id 队列回落到 maxDepth 以下(默认上限-1;上层“暂停读上游”用)。 */
371
+ async waitOutboundQueue(id, maxDepth = this.maxQueuedPerId - 1) {
372
+ const ctx = this.ids.get(id);
373
+ if (ctx === void 0) return;
374
+ while (ctx.queuedCount > maxDepth) {
375
+ if (this.deadFlag) return;
376
+ await new Promise((resolve) => ctx.slotWaiters.push(resolve));
377
+ }
378
+ }
379
+ /**
380
+ * 挂起该 id 的空闲计时(provider 专用:请求已派发上游、等待首字节阶段;活度由
381
+ * PING 发送节奏与上游首字节超时管辖)。首个 RESP_META / RESP_CHUNK / DATA_DOWN
382
+ * 出站时自动恢复,也可显式 resumeProviderIdle。
383
+ */
384
+ suspendProviderIdle(id) {
385
+ if (this.role !== "provider") return;
386
+ const ctx = this.ids.get(id);
387
+ if (ctx === void 0 || ctx.terminal) return;
388
+ ctx.idleSuspended = true;
389
+ if (ctx.idleTimer !== null) {
390
+ clearTimeout(ctx.idleTimer);
391
+ ctx.idleTimer = null;
392
+ }
393
+ }
394
+ /** 显式恢复该 id 的空闲计时(自恢复点重计完整窗口)。 */
395
+ resumeProviderIdle(id) {
396
+ if (this.role !== "provider") return;
397
+ const ctx = this.ids.get(id);
398
+ if (ctx === void 0 || ctx.terminal || !ctx.idleSuspended) return;
399
+ ctx.idleSuspended = false;
400
+ this.armIdle(ctx);
401
+ }
402
+ handleIncoming(frame) {
403
+ if (this.deadFlag) return;
404
+ if (this.poisonedFlag) {
405
+ this.counters.poisonedDropped++;
406
+ return;
407
+ }
408
+ if (frame.kind === "unknown-version") {
409
+ this.counters.unknownVersionDropped++;
410
+ if (this.role === "provider") this.replyError(ERROR_CODE.protocol_version, void 0);
411
+ return;
412
+ }
413
+ if (frame.kind === "unknown-type") {
414
+ this.counters.unknownTypeIgnored++;
415
+ return;
416
+ }
417
+ if (frame.kind === "malformed") {
418
+ this.counters.malformedDropped++;
419
+ return;
420
+ }
421
+ const meta = FRAME_META[frame.type];
422
+ if (meta === void 0) {
423
+ this.counters.unknownTypeIgnored++;
424
+ return;
425
+ }
426
+ const { type, header, body } = frame;
427
+ if (!this.authedFlag) {
428
+ if (!(this.role === "provider" ? type === FRAME_TYPE.AUTH : this.authPending && (type === FRAME_TYPE.AUTH_OK || type === FRAME_TYPE.AUTH_ERR))) {
429
+ this.counters.unauthDropped++;
430
+ if (this.counters.unauthDropped > this.unauthFrameLimit && !this.unauthClosed) {
431
+ this.unauthClosed = true;
432
+ this.hooks.onUnauthViolation?.(this.counters.unauthDropped);
433
+ this.transport.close("too-many-preauth-frames");
434
+ }
435
+ return;
436
+ }
437
+ }
438
+ if (!this.inboundDirectionOk(meta)) {
439
+ if (this.role === "provider") {
440
+ const rawId = header.id;
441
+ const id = typeof rawId === "string" ? rawId : void 0;
442
+ if (id !== void 0) this.finishId(id, { source: "local" });
443
+ this.replyError(ERROR_CODE.protocol_error, id);
444
+ }
445
+ this.counters.directionDropped++;
446
+ return;
447
+ }
448
+ const parsed = FRAME_HEADER_SCHEMAS[type].safeParse(header);
449
+ if (!parsed.success) {
450
+ const failure = classifySchemaFailure(type, header);
451
+ if (this.role === "provider") {
452
+ if (failure.id !== void 0) this.finishId(failure.id, { source: "local" });
453
+ this.replyError(failure.code, failure.id);
454
+ }
455
+ this.counters.schemaDropped++;
456
+ return;
457
+ }
458
+ const parsedHeader = parsed.data;
459
+ const rawId = parsedHeader.id;
460
+ const id = typeof rawId === "string" ? rawId : void 0;
461
+ if (id === void 0) {
462
+ this.deliver(type, parsedHeader, body);
463
+ return;
464
+ }
465
+ if (type === FRAME_TYPE.REQ) {
466
+ if (this.ids.has(id)) {
467
+ this.counters.unknownIdDropped++;
468
+ return;
469
+ }
470
+ const ctx = this.registerId(id);
471
+ if (parsedHeader.bodyLen > this.reassemblyLimitBytes || body.length > this.reassemblyLimitBytes) {
472
+ this.finishId(id, { source: "body-too-large" });
473
+ this.replyError(ERROR_CODE.body_too_large, id);
474
+ return;
475
+ }
476
+ ctx.reassembledBytes = body.length;
477
+ this.deliver(type, parsedHeader, body);
478
+ return;
479
+ }
480
+ const ctx = this.ids.get(id);
481
+ if (ctx === void 0) {
482
+ this.counters.unknownIdDropped++;
483
+ return;
484
+ }
485
+ if (ctx.terminal) {
486
+ this.counters.terminalDropped++;
487
+ return;
488
+ }
489
+ if (meta.seqStream !== void 0) {
490
+ const stream = meta.seqStream;
491
+ const expected = ctx.expectedSeq.get(stream) ?? 0;
492
+ if (parsedHeader.seq !== expected) {
493
+ this.finishId(id, { source: "protocol-seq" });
494
+ if (this.role === "provider") this.replyError(ERROR_CODE.protocol_seq, id);
495
+ this.markPoisoned(id);
496
+ return;
497
+ }
498
+ ctx.expectedSeq.set(stream, expected + 1);
499
+ }
500
+ if (type === FRAME_TYPE.REQ_BODY) {
501
+ ctx.reassembledBytes += body.length;
502
+ if (ctx.reassembledBytes > this.reassemblyLimitBytes) {
503
+ this.finishId(id, { source: "body-too-large" });
504
+ this.replyError(ERROR_CODE.body_too_large, id);
505
+ return;
506
+ }
507
+ }
508
+ this.touchIdle(ctx);
509
+ if (meta.terminal === true) this.finishId(id, {
510
+ source: "peer",
511
+ frameType: type
512
+ });
513
+ this.deliver(type, parsedHeader, body);
514
+ }
515
+ deliver(type, header, body) {
516
+ const frame = {
517
+ type,
518
+ header,
519
+ body
520
+ };
521
+ this.hooks.onFrame?.(frame);
522
+ }
523
+ markPoisoned(id) {
524
+ if (this.poisonedFlag) return;
525
+ this.poisonedFlag = true;
526
+ this.hooks.onPoison?.({
527
+ id,
528
+ reason: "protocol_seq"
529
+ });
530
+ }
531
+ registerId(id) {
532
+ const existing = this.ids.get(id);
533
+ if (existing !== void 0) return existing;
534
+ const ctx = {
535
+ id,
536
+ idleTimer: null,
537
+ idleSuspended: false,
538
+ terminal: false,
539
+ reassembledBytes: 0,
540
+ expectedSeq: /* @__PURE__ */ new Map(),
541
+ sendLane: Promise.resolve(),
542
+ queuedCount: 0,
543
+ slotWaiters: []
544
+ };
545
+ this.ids.set(id, ctx);
546
+ this.usedIds.add(id);
547
+ this.armIdle(ctx);
548
+ return ctx;
549
+ }
550
+ touchIdle(ctx) {
551
+ if (ctx.idleSuspended) return;
552
+ this.armIdle(ctx);
553
+ }
554
+ armIdle(ctx) {
555
+ if (ctx.idleTimer !== null) clearTimeout(ctx.idleTimer);
556
+ ctx.idleTimer = setTimeout(() => {
557
+ ctx.idleTimer = null;
558
+ if (this.deadFlag || ctx.terminal) return;
559
+ this.hooks.onIdleTimeout?.(ctx.id);
560
+ }, this.idleTimeoutMs);
561
+ }
562
+ finishId(id, cause) {
563
+ const ctx = this.ids.get(id);
564
+ if (ctx === void 0 || ctx.terminal) return;
565
+ ctx.terminal = true;
566
+ if (ctx.idleTimer !== null) {
567
+ clearTimeout(ctx.idleTimer);
568
+ ctx.idleTimer = null;
569
+ }
570
+ this.hooks.onTerminate?.(id, cause);
571
+ }
572
+ handleClosed(reason) {
573
+ if (this.deadFlag) return;
574
+ this.deadFlag = true;
575
+ const cause = reason === void 0 ? { source: "disconnected" } : {
576
+ source: "disconnected",
577
+ reason
578
+ };
579
+ for (const ctx of this.ids.values()) {
580
+ if (ctx.idleTimer !== null) {
581
+ clearTimeout(ctx.idleTimer);
582
+ ctx.idleTimer = null;
583
+ }
584
+ if (!ctx.terminal) {
585
+ ctx.terminal = true;
586
+ this.hooks.onTerminate?.(ctx.id, cause);
587
+ }
588
+ }
589
+ this.hooks.onDisconnect?.(reason);
590
+ }
591
+ inboundDirectionOk(meta) {
592
+ if (meta.dir === "both") return true;
593
+ return this.role === "provider" ? meta.dir === "c2p" : meta.dir === "p2c";
594
+ }
595
+ outboundDirectionOk(meta) {
596
+ if (meta.dir === "both") return true;
597
+ return this.role === "provider" ? meta.dir === "p2c" : meta.dir === "c2p";
598
+ }
599
+ /** 提供方自动回送 ERROR(无队列门控、经全局通道;发送失败静默——连接已坏时由关闭路径处置)。 */
600
+ replyError(code, id) {
601
+ const header = {
602
+ code,
603
+ message: autoErrorMessage(code)
604
+ };
605
+ if (id !== void 0) header.id = id;
606
+ const bytes = encodeFrame({
607
+ type: FRAME_TYPE.ERROR,
608
+ header
609
+ });
610
+ this.transmit(this.globalLane, bytes).catch(() => void 0);
611
+ }
612
+ transmit(lane, bytes) {
613
+ lane.queuedCount++;
614
+ const settled = lane.sendLane.then(() => this.transport.send(bytes)).then(() => {
615
+ this.slotReleased(lane);
616
+ }, (err) => {
617
+ this.slotReleased(lane);
618
+ throw err;
619
+ });
620
+ lane.sendLane = settled.catch(() => void 0);
621
+ return settled;
622
+ }
623
+ slotReleased(lane) {
624
+ lane.queuedCount--;
625
+ const waiters = lane.slotWaiters.splice(0);
626
+ for (const wake of waiters) wake();
627
+ }
628
+ };
629
+ //#endregion
630
+ export { splitBody as i, FabricWireAdapter as n, DEFAULT_BODY_CHUNK_BYTES as r, WireSession as t };