@anycon/protocol 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/LICENSE +176 -0
  2. package/README.md +146 -0
  3. package/README.zh-CN.md +118 -0
  4. package/dist/constants.d.ts +151 -0
  5. package/dist/constants.d.ts.map +1 -0
  6. package/dist/constants.js +159 -0
  7. package/dist/constants.js.map +1 -0
  8. package/dist/crypto.d.ts +202 -0
  9. package/dist/crypto.d.ts.map +1 -0
  10. package/dist/crypto.js +218 -0
  11. package/dist/crypto.js.map +1 -0
  12. package/dist/envelope.d.ts +105 -0
  13. package/dist/envelope.d.ts.map +1 -0
  14. package/dist/envelope.js +390 -0
  15. package/dist/envelope.js.map +1 -0
  16. package/dist/errors.d.ts +5 -0
  17. package/dist/errors.d.ts.map +1 -0
  18. package/dist/errors.js +5 -0
  19. package/dist/errors.js.map +1 -0
  20. package/dist/generated.d.ts +2007 -0
  21. package/dist/generated.d.ts.map +1 -0
  22. package/dist/generated.js +4 -0
  23. package/dist/generated.js.map +1 -0
  24. package/dist/index.d.ts +17 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +16 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/method-types.d.ts +293 -0
  29. package/dist/method-types.d.ts.map +1 -0
  30. package/dist/method-types.js +2 -0
  31. package/dist/method-types.js.map +1 -0
  32. package/dist/rpc.d.ts +70 -0
  33. package/dist/rpc.d.ts.map +1 -0
  34. package/dist/rpc.js +310 -0
  35. package/dist/rpc.js.map +1 -0
  36. package/package.json +62 -0
  37. package/src/constants.ts +173 -0
  38. package/src/crypto.ts +427 -0
  39. package/src/envelope.ts +469 -0
  40. package/src/errors.ts +4 -0
  41. package/src/generated.ts +2094 -0
  42. package/src/index.ts +18 -0
  43. package/src/method-types.ts +183 -0
  44. package/src/rpc.ts +377 -0
  45. package/wasm/THIRD-PARTY-LICENSES.txt +1957 -0
  46. package/wasm/anycon_crypto.d.ts +248 -0
  47. package/wasm/anycon_crypto.js +1250 -0
  48. package/wasm/anycon_crypto_bg.wasm +0 -0
  49. package/wasm/anycon_crypto_bg.wasm.d.ts +48 -0
package/LICENSE ADDED
@@ -0,0 +1,176 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
package/README.md ADDED
@@ -0,0 +1,146 @@
1
+ # @anycon/protocol
2
+
3
+ [简体中文](https://github.com/anycon-ai/anycon/blob/main/packages/protocol/README.zh-CN.md)
4
+
5
+ The TypeScript side of the [Anycon](https://github.com/anycon-ai/anycon) protocol: the layer with no networking and no state. The Web UI and [`@anycon/sdk`](https://www.npmjs.com/package/@anycon/sdk) both build on it. To pair with a node and drive sessions, use `@anycon/sdk`. This package is for code that works with the wire format directly.
6
+
7
+ ```bash
8
+ npm install @anycon/protocol
9
+ ```
10
+
11
+ The design docs linked below are in Chinese for now.
12
+
13
+ | Module | Contents |
14
+ |---|---|
15
+ | `src/generated.ts` | **Generated, do not edit.** `just gen-ts` generates it from `schema/protocol.json` (exported by `crates/protocol`): `Control`, `Event`, `SessionEvent`, the params / result types of each method, … |
16
+ | `src/envelope.ts` | msgpack codec for the outer envelope and control messages: `encodeEnvelope` / `decodeEnvelope`, `encodeControl` / `decodeControl`, `controlEnvelope`, `CONTROL_TYPES`. Also the fragmentation framing for `data` envelope bodies: `encodeFrames` / `decodeFrame` / `Reassembler` / `resetBody` (see below) |
17
+ | `src/rpc.ts` | JSON-RPC 2.0: `encodeRpc` / `decodeRpc`, `isRequest` / `isNotification` / `isResponse`, `AnyconRpcError`, and the transport-agnostic `RpcPeer` |
18
+ | `src/method-types.ts` | **Generated, do not edit.** `just gen-ts` runs `tools/gen-method-types.ts` on `schema/methods.json` (the method registry in `crates/protocol`) to produce `RequestMethods` (client → daemon requests: method name → `{ params, result }`) and `NotificationMethods` (daemon → client notifications: method name → `{ params }`). `tests/method-types.test.ts` asserts that it is in sync with the schema |
19
+ | `src/crypto.ts` | `loadCrypto()`: wraps the wasm build of `crates/crypto` (Noise XXpsk3 / IK, a transport that fragments long messages, `derivePairSecrets`, `generatePairCodeLong`, `fingerprint`), with `Uint8Array` in and out. Also pure-JS pairing-code helpers: `normalizePairCode` / `isValidPairCode` / `formatPairCode` / `extractPairCode` |
20
+ | `src/constants.ts` | `PROTOCOL_VERSION`, `ENVELOPE_VERSION`, `METHODS` (method name constants, including `PAIR_ACK`), `CLOSE_CODE`, `PAIR_CODE_ALPHABET`, `PAIR_CODE_LEN` / `PAIR_CODE_LEN_LONG`, `ERROR_CODE_NUMBERS` |
21
+
22
+ ## Usage
23
+
24
+ ```ts
25
+ import {
26
+ METHODS,
27
+ Reassembler,
28
+ RpcPeer,
29
+ controlEnvelope,
30
+ decodeEnvelope,
31
+ decodeFrame,
32
+ encodeEnvelope,
33
+ encodeFrames,
34
+ loadCrypto,
35
+ type Control,
36
+ type SessionEvent,
37
+ } from '@anycon/protocol'
38
+
39
+ // Envelopes: body is a Uint8Array (msgpack bin), kind is a snake_case string
40
+ const frame = encodeEnvelope({ v: 1, from: myId, to: nodeId, kind: 'data', seq: 7, body })
41
+ const env = decodeEnvelope(bytes)
42
+
43
+ // Control messages: for messages to the hub, from and to are both empty strings.
44
+ // Pairing sends only match_id. The pairing code itself never leaves this device.
45
+ // derivePairSecrets is one Argon2id run (64 MiB × 3 passes; about 0.1–0.3 s in Node and
46
+ // 0.1–1 s in browsers, blocking the thread). It produces match_id and the PSK in one go, so
47
+ // don't call deriveMatchId / derivePsk separately (each is a full KDF run). Zero psk with fill(0) after use.
48
+ const crypto = await loadCrypto()
49
+ const { matchId, psk } = crypto.derivePairSecrets('7K3P-Q2MX')
50
+ ws.send(encodeEnvelope(controlEnvelope('', seq, { type: 'pair_join', match_id: matchId })))
51
+ // …run the XXpsk3 handshake with crypto.pairing('initiator', keypair, psk)…
52
+ psk.fill(0)
53
+
54
+ // JSON-RPC: one message = one Transport.encrypt = one msg_id. encodeFrames splits the ciphertext
55
+ // into one or more data envelopes (a short message is a single WHOLE frame).
56
+ const rpc = new RpcPeer({
57
+ send: (bytes) => {
58
+ for (const body of encodeFrames(transport.encrypt(bytes), msgId++ >>> 0)) sendData(body)
59
+ },
60
+ })
61
+ // Receiving: const ct = reassembler.push(decodeFrame(env.body)); if (ct) rpc.handle(transport.decrypt(ct))
62
+ rpc.onNotification<SessionEvent>(METHODS.SESSION_EVENT, (e) => {
63
+ /* … */
64
+ })
65
+ // Errors throw AnyconRpcError; .code is a stable snake_case string
66
+ const info = await rpc.request(METHODS.NODE_INFO, {}, { timeoutMs: 10_000 })
67
+ ```
68
+
69
+ Notes:
70
+
71
+ - Pass `{}` for requests without parameters (not `undefined` / `null`). The Rust side's `Empty` is `{}`. `RpcPeer.request` already defaults to `{}`.
72
+ - `decodeControl` throws `UnknownControlError` for an unknown `type` (this can happen when the hub and daemon versions don't match). Callers should ignore that frame instead of disconnecting.
73
+ - Envelope key order, control message key order, and the omission of `skip_serializing_if` fields all match Rust's `rmp_serde::to_vec_named`. `tests/vectors` enforces this.
74
+
75
+ ## Fragmenting data envelopes (ADR-30)
76
+
77
+ The hub limits the size of a single envelope (256 KiB by default), and one RPC (such as a page of history) can be larger, so Noise ciphertext is fragmented at the envelope layer. The first byte of a `data` envelope body is a flags byte. The byte format matches `mod frame` in `crates/protocol/src/envelope.rs` exactly:
78
+
79
+ | flags | Meaning | Payload |
80
+ |---|---|---|
81
+ | `0x00` WHOLE | The complete output of one `Transport.encrypt` | Ciphertext |
82
+ | `0x01` FRAGMENT | One fragment | `[u32 be msg_id][u16 be index][u16 be count]` + a slice of the ciphertext. Fragments of one `msg_id` are sent in `index` order and are not interleaved with other messages |
83
+ | `0x02` RESET | The sender found its transport out of sync and asks the peer to drop the transport and handshake again | None |
84
+
85
+ - `encodeFrames(ciphertext, msgId)` → `Uint8Array[]` (each is the body of one `data` envelope, with at most `MAX_FRAGMENT` = 200 KiB of payload per fragment; `msgId` is a u32, one per RPC). `decodeFrame(body)` → `DataFrame` (a malformed body throws `FrameError`, whose `.code` is `empty | unknown_flags | truncated | out_of_order | count_mismatch | too_large`). `new Reassembler()` (one per peer): `push(frame)` returns the whole ciphertext or `undefined`; the total length is capped at `MAX_MESSAGE` = 16 MiB; on error the reassembly in progress is discarded; `clear()` resets it. Also `resetBody()` and `pushBody(reassembler, body)`.
86
+ - `Reassembler` is strictly ordered, the same as in Rust: `index` must equal the next expected fragment; `index >= count` is `out_of_order`; changing `count` midway through a `msg_id` is `count_mismatch`; `count == 0` is `count_mismatch`; a single FRAGMENT with `count == 1` is accepted; after one message completes, another with the same `msg_id` must start again at `index 0`. `index` / `count` are u16, and comparisons don't overflow (`count = 65535` works through the last fragment).
87
+ - Noise nonces are sequential, so **losing or reordering any fragment desynchronizes the transport**. For a paired `(from, to)`, the hub only forwards an envelope or bounces it back with `backpressure { to, seq }`. It never drops one silently, and bounces are "sticky": once a seq is bounced, every later envelope is bounced too, until that seq is resent successfully ([docs/protocol.md](https://github.com/anycon-ai/anycon/blob/main/docs/protocol.md) § 3.1).
88
+ - Senders keep the raw bytes of recently sent envelopes (capped by size, at least 8 MiB). On `backpressure`, pause that peer, wait `retry_after_ms` (from the latest `rate_limited`, or 100–300 ms plus jitter if there is none), resend every bounced seq **in ascending order, byte for byte** (never re-encrypt), then continue. If a bounced seq is no longer in the buffer, drop the Transport and handshake again.
89
+ - `rate_limited { retry_after_ms }` is only a hint for how long to pause. It doesn't mean an envelope was dropped.
90
+ - `dropped { seq, reason }` arrives at most once per connection per second and means something wrong was sent (`too_large` = not fragmented, or still over the limit after fragmenting; `not_paired` = the devices aren't paired as expected). **Don't resend on it.** Treat it as an error and handshake again.
91
+ - Receivers drop the Transport and handshake again on a decryption failure, a reassembly error, or a RESET.
92
+
93
+ The SDK's implementation is in `packages/sdk/src/link.ts`.
94
+ - Vectors: `tests/vectors/frames.json` (generated from Rust by `tools/gen-vectors`). The ciphertext of large messages is the deterministic pattern `(i % 251) as u8`. Only each fragment's header and length are stored; the TS side rebuilds the data and compares its encoding byte for byte. The file also has decode cases (including various bad bodies) and step-by-step `Reassembler` results (including state cleanup after errors).
95
+
96
+ ## wasm (Noise)
97
+
98
+ ```ts
99
+ const crypto = await loadCrypto() // Node / Bun: reads ../wasm/anycon_crypto_bg.wasm automatically
100
+ const crypto = await loadCrypto({ wasm: wasmUrl }) // Browser: let your bundler provide the .wasm URL, e.g. with Vite's `?url` import
101
+ ```
102
+
103
+ The published package ships the wasm build in `wasm/`. In a repository checkout, `just gen-wasm` builds it into `packages/protocol/wasm/` (gitignored). If the build is missing, `loadCrypto()` throws `CryptoUnavailableError`, and the message says to run `just gen-wasm`. The package exports `@anycon/protocol/wasm/*`, so in the browser you can `import url from '@anycon/protocol/wasm/anycon_crypto_bg.wasm?url'` directly.
104
+
105
+ **Pairing KDF (ADR-29, revised).** `Crypto.derivePairSecrets(code) -> { matchId, psk }` runs `Argon2id(salt = "anycon-pair-v2", m = 64 MiB, t = 3, p = 1, out = 64)` once over the normalized pairing code. The hex of the first 8 bytes is the `matchId` sent to the hub for matching, and the last 32 bytes are the PSK. It takes about 0.1–0.3 s in Node and about 0.1–1 s in browsers, and it **blocks the thread** (a UI should show "Deriving key…" before calling it, so the browser can paint a frame first). The pairing path calls only this function. Don't also call `deriveMatchId` / `derivePsk` separately (each is a full KDF run, so that computes it twice). `psk` is a copy: zero it with `fill(0)` after use. Known vectors: `ABCD2345` → `matchId = 94d67d781538d997`, `psk = 23e8b63d…6012`; `23456789` → `f9f3c6c9940049ac` / `17d04f19…bb20` (`tests/crypto.test.ts`).
106
+
107
+ Keypairs in the `Crypto` interface are plain data (`{ public, secret }`) and are rebuilt inside wasm only for the duration of a handshake, so there are no wasm-bindgen ownership issues.
108
+ Roles: in pairing (XXpsk3) the joiner is `initiator` and the host (daemon) is `responder`. In reconnection (IK) the client is `initiator`. **The initiator writes first.**
109
+
110
+ ## Consuming from a bundler
111
+
112
+ `exports` has two development conditions, `source` and `bun`, which point to `src/index.ts`, so inside the workspace you don't need to `build` first:
113
+
114
+ - tsconfig: `"customConditions": ["source"]` (the root `tsconfig.base.json` already sets it)
115
+ - Vite: `resolve: { conditions: ['source'] }`
116
+ - The published package and plain Node resolution use `dist/`
117
+
118
+ ## Development
119
+
120
+ ```bash
121
+ bun run --cwd packages/protocol test # bun test (with the wasm build present, also runs real handshakes and the Argon2id vectors)
122
+ bun run --cwd packages/protocol test:node # the same tests on Node (tsx)
123
+ bun run --cwd packages/protocol typecheck
124
+ bun run --cwd packages/protocol build # tsc → dist/ (ESM + .d.ts)
125
+ bun run --cwd packages/protocol lint # oxlint
126
+ ```
127
+
128
+ ### Test vectors
129
+
130
+ `tests/vectors/` holds the output of the real Rust encoder (`<name>.hex`) and the logical values (`manifest.json`). They cover every `Control` variant, envelopes with various `seq` / `body` lengths (empty, bin8, bin16, u64), and the four JSON-RPC shapes. `frames.json` holds the vectors for the data-envelope framing. On the TS side, decoding must yield the logical value, and re-encoding must match byte for byte.
131
+ They are generated by `tools/gen-vectors` (a standalone cargo project, not a workspace member):
132
+
133
+ ```bash
134
+ just gen-vectors # only needed when the protocol encoding changes; CI's gen-check diffs the result
135
+ ```
136
+
137
+ `tests/constants.test.ts` parses the source of `crates/protocol/src/{methods,lib,control,error,envelope}.rs` (and `crates/crypto/src/lib.rs` for the pairing-code length)
138
+ and asserts that method names (including `pair.ack`), version numbers, pairing-code lengths, frame constants (flags / fragment size / reassembly limit), close codes, error code numbers, `Control` variants (including `pair_registered` and `dropped`), and `Kind` variants match this package (a drift guard).
139
+
140
+ ### Generated method type table
141
+
142
+ `src/method-types.ts` isn't written by hand. `methods::REGISTRY` in `crates/protocol` is the single source of truth for "method name → params / result type names". `just gen-schema` exports it to `schema/methods.json`, then `just gen-ts` runs `tools/gen-method-types.ts` (`bun packages/protocol/tools/gen-method-types.ts [--check]`) to generate this file. Type names must be names exported by `generated.ts`; an unknown name is an error. After changing the registry, run `just gen` and commit `schema/` together with the two generated files. `serde_json::Value` is already `unknown` in the schema, so there are no hand-written workarounds.
143
+
144
+ ## License
145
+
146
+ [Apache-2.0](https://github.com/anycon-ai/anycon/blob/main/LICENSE)
@@ -0,0 +1,118 @@
1
+ # @anycon/protocol
2
+
3
+ [English](https://github.com/anycon-ai/anycon/blob/main/packages/protocol/README.md)
4
+
5
+ anycon 协议的 TypeScript 侧:无网络、无状态的那一层。Web UI 和 `@anycon/sdk` 共用。
6
+
7
+ | 模块 | 内容 |
8
+ |---|---|
9
+ | `src/generated.ts` | **生成**,不要手改。`just gen-ts` 从 `schema/protocol.json`(`crates/protocol` 导出)生成:`Control`、`Event`、`SessionEvent`、各方法的 params / result…… |
10
+ | `src/envelope.ts` | 外层信封与控制消息的 msgpack 编解码:`encodeEnvelope` / `decodeEnvelope`、`encodeControl` / `decodeControl`、`controlEnvelope`、`CONTROL_TYPES`;以及 `data` 信封 body 的分片框架 `encodeFrames` / `decodeFrame` / `Reassembler` / `resetBody`(见下) |
11
+ | `src/rpc.ts` | JSON-RPC 2.0:`encodeRpc` / `decodeRpc`、`isRequest` / `isNotification` / `isResponse`、`AnyconRpcError`、与传输无关的 `RpcPeer` |
12
+ | `src/method-types.ts` | **生成**,不要手改。`just gen-ts` 用 `tools/gen-method-types.ts` 从 `schema/methods.json`(`crates/protocol` 的方法注册表)生成:`RequestMethods`(C→D 请求:方法名 → `{ params, result }`)、`NotificationMethods`(D→C 通知:方法名 → `{ params }`)。`tests/method-types.test.ts` 断言它与 schema 同步 |
13
+ | `src/crypto.ts` | `loadCrypto()`:封装 `crates/crypto` 的 wasm 产物(Noise XXpsk3 / IK、分片传输、`derivePairSecrets`、`generatePairCodeLong`、`fingerprint`),`Uint8Array` 进出;纯 JS 的短码辅助 `normalizePairCode` / `isValidPairCode` / `formatPairCode` / `extractPairCode` |
14
+ | `src/constants.ts` | `PROTOCOL_VERSION`、`ENVELOPE_VERSION`、`METHODS`(方法名常量,含 `PAIR_ACK`)、`CLOSE_CODE`、`PAIR_CODE_ALPHABET`、`PAIR_CODE_LEN` / `PAIR_CODE_LEN_LONG`、`ERROR_CODE_NUMBERS` |
15
+
16
+ ## 用法
17
+
18
+ ```ts
19
+ import {
20
+ METHODS, RpcPeer, Reassembler, encodeEnvelope, decodeEnvelope, controlEnvelope,
21
+ encodeFrames, decodeFrame, loadCrypto,
22
+ type Control, type SessionEvent,
23
+ } from '@anycon/protocol'
24
+
25
+ // 信封:body 是 Uint8Array(msgpack bin),kind 是 snake_case 字符串
26
+ const frame = encodeEnvelope({ v: 1, from: myId, to: nodeId, kind: 'data', seq: 7, body })
27
+ const env = decodeEnvelope(bytes)
28
+
29
+ // 控制消息:发给 hub 的 from / to 都填空串。配对只发 match_id,短码本身不出本机。
30
+ // derivePairSecrets 是一次 Argon2id(64 MiB × 3 轮,Node 约 0.1–0.3 s、浏览器约 0.1–1 s,同步阻塞):
31
+ // match_id 和 PSK 一次算出,别分别调 deriveMatchId / derivePsk(各是一次完整 KDF);psk 用完 fill(0)
32
+ const crypto = await loadCrypto()
33
+ const { matchId, psk } = crypto.derivePairSecrets('7K3P-Q2MX')
34
+ ws.send(encodeEnvelope(controlEnvelope('', seq, { type: 'pair_join', match_id: matchId })))
35
+ // …用 psk 做 crypto.pairing('initiator', keypair, psk) 的 XXpsk3 握手…
36
+ psk.fill(0)
37
+
38
+ // JSON-RPC:一条消息 = 一次 Transport.encrypt = 一个 msg_id;密文按 frame 切成若干 data 信封(短消息就是一个 WHOLE)
39
+ const rpc = new RpcPeer({
40
+ send: (bytes) => {
41
+ for (const body of encodeFrames(transport.encrypt(bytes), msgId++ >>> 0)) sendData(body)
42
+ },
43
+ })
44
+ // 收:const ct = reassembler.push(decodeFrame(env.body)); if (ct) rpc.handle(transport.decrypt(ct))
45
+ rpc.onNotification<SessionEvent>(METHODS.SESSION_EVENT, (e) => { /* … */ })
46
+ const info = await rpc.request(METHODS.NODE_INFO, {}, { timeoutMs: 10_000 }) // 错误抛 AnyconRpcError,.code 是稳定 snake_case
47
+ ```
48
+
49
+ 注意:
50
+
51
+ - 请求没有参数时传 `{}`(不是 `undefined` / `null`),Rust 侧的 `Empty` 是 `{}`;`RpcPeer.request` 已经默认成 `{}`。
52
+ - `decodeControl` 对未知 `type` 抛 `UnknownControlError`(hub / daemon 版本错位时可能出现),调用方应忽略这一帧而不是断连。
53
+ - 信封键序、控制消息键序、`skip_serializing_if` 字段的省略都与 Rust 的 `rmp_serde::to_vec_named` 一致,由 `tests/vectors` 守着。
54
+
55
+ ## data 信封的分片(ADR-30)
56
+
57
+ hub 对单个信封有大小上限(默认 256 KiB),一条 RPC(如一页历史)可能更大,所以 Noise 密文在信封层分片。`data` 信封 body 的第一个字节是 flags,字节格式与 `crates/protocol/src/envelope.rs` 的 `mod frame` 逐字节一致:
58
+
59
+ | flags | 含义 | 载荷 |
60
+ |---|---|---|
61
+ | `0x00` WHOLE | 一整条 `Transport.encrypt` 的输出 | 密文 |
62
+ | `0x01` FRAGMENT | 分片 | `[u32 be msg_id][u16 be index][u16 be count]` + 密文切片。同一 `msg_id` 的分片按 `index` 顺序、不与其他消息交错地发送 |
63
+ | `0x02` RESET | 发送方发现传输失步,请对端丢弃传输并重新握手 | 无 |
64
+
65
+ - `encodeFrames(ciphertext, msgId)` → `Uint8Array[]`(每个是一个 `data` 信封的 body,每片载荷 ≤ `MAX_FRAGMENT` = 200 KiB;`msgId` 是 u32,每条 RPC 一个)、`decodeFrame(body)` → `DataFrame`(格式错抛 `FrameError`,`.code` 是 `empty | unknown_flags | truncated | out_of_order | count_mismatch | too_large`)、`new Reassembler()`(每个对端一个;`push(frame)` 返回整条密文或 `undefined`;总长上限 `MAX_MESSAGE` = 16 MiB;出错时进行中的重组被丢弃;`clear()`)、`resetBody()`、`pushBody(reassembler, body)`。
66
+ - `Reassembler` 严格按序,与 Rust 一致:`index` 必须等于期望的下一片;`index >= count` 报 `out_of_order`;同一 `msg_id` 中途改 `count` 报 `count_mismatch`;`count == 0` 报 `count_mismatch`;`count == 1` 的单片 FRAGMENT 也能收;一条收完后同一 `msg_id` 再来要从 `index 0` 开始;`index` / `count` 是 u16,比较不会溢出(`count = 65535` 走到最后一片也正常)。
67
+ - Noise 的 nonce 是顺序的,**任何一片丢失或乱序都意味着传输失步**。hub 对已配对的 `(from, to)` 只会转发或退回 `backpressure { to, seq }`,不会静默丢弃,并且「粘住」(一个 seq 被退回后,后续信封一律退回,直到它被重发成功,docs/protocol.md § 3.1)。发送方:保留最近发出的信封原文(按字节封顶,≥ 8 MiB);收到 `backpressure` 后暂停该对端,等 `retry_after_ms`(最近一条 `rate_limited` 给的,没有就 100–300 ms + 抖动),把所有被退回的 seq **按升序原样重发**(不能重新加密),再继续;被退回的 seq 已不在缓冲里 → 丢 Transport 重新握手。`rate_limited { retry_after_ms }` 只是暂停多久的提示,不再意味着有信封被丢。`dropped { seq, reason }` 每连接每秒至多一条,是「发错东西」的信号(`too_large` = 没分片 / 分片后仍超限,`not_paired` = 配对关系不对),**不要据此重发**,当作错误处理并重新握手。接收方解密失败、重组出错、收到 RESET 都要丢 Transport 并重新握手。SDK 的实现见 `packages/sdk/src/link.ts`。
68
+ - 向量:`tests/vectors/frames.json`(`tools/gen-vectors` 用 Rust 生成)。大消息的密文约定为 `(i % 251) as u8` 的确定性图样,只存各片的头和长度,TS 侧还原后逐字节比对 encode;另有 decode(含各种坏 body)和 Reassembler 的逐步结果(含出错后的状态清理)。
69
+
70
+ ## wasm(Noise)
71
+
72
+ ```ts
73
+ const crypto = await loadCrypto() // Node / Bun:自动读 ../wasm/anycon_crypto_bg.wasm
74
+ const crypto = await loadCrypto({ wasm: wasmUrl }) // 浏览器:把 .wasm 的 URL 交给打包器,例如 Vite 的 `?url` 导入
75
+ ```
76
+
77
+ 产物由 `just gen-wasm` 生成到 `packages/protocol/wasm/`(已 gitignore)。产物不存在时 `loadCrypto()` 抛 `CryptoUnavailableError`,消息里写明要跑 `just gen-wasm`。
78
+ 包导出 `@anycon/protocol/wasm/*`,浏览器侧可以直接 `import url from '@anycon/protocol/wasm/anycon_crypto_bg.wasm?url'`。
79
+
80
+ **配对 KDF(ADR-29 修订)**:`Crypto.derivePairSecrets(code) -> { matchId, psk }` 对规范化短码做一次 `Argon2id(salt = "anycon-pair-v2", m = 64 MiB, t = 3, p = 1, out = 64)`:前 8 字节的 hex 是发给 hub 撮合的 `matchId`,后 32 字节是 PSK。耗时 Node / 本机 wasm 约 0.1–0.3 s,浏览器约 0.1–1 s,**同步占用线程**(UI 要先显示「正在派生密钥…」再调用,让浏览器先画一帧)。配对路径只调这一个,不要再分别调 `deriveMatchId` / `derivePsk`(各是一次完整 KDF,等于算两遍);`psk` 是拷贝,用完 `fill(0)`。已知向量:`ABCD2345` → `matchId = 94d67d781538d997`、`psk = 23e8b63d…6012`;`23456789` → `f9f3c6c9940049ac` / `17d04f19…bb20`(`tests/crypto.test.ts`)。
81
+
82
+ `Crypto` 接口里的密钥对是纯数据(`{ public, secret }`),握手时才在 wasm 里临时重建,没有 wasm-bindgen 的所有权问题。
83
+ 角色:配对(XXpsk3)joiner 是 `initiator`、host(daemon)是 `responder`;重连(IK)客户端是 `initiator`。**initiator 先写**。
84
+
85
+ ## 命令
86
+
87
+ ```bash
88
+ bun run --cwd packages/protocol test # bun test(wasm 存在时多跑真实握手和 Argon2id 向量)
89
+ bun run --cwd packages/protocol test:node # 同一批用例在 Node 上跑(tsx)
90
+ bun run --cwd packages/protocol typecheck
91
+ bun run --cwd packages/protocol build # tsc → dist/(ESM + .d.ts)
92
+ bun run --cwd packages/protocol lint # oxlint
93
+ ```
94
+
95
+ ## 测试向量
96
+
97
+ `tests/vectors/` 里是 Rust 真实编码器的输出(`<name>.hex`)和逻辑值(`manifest.json`),覆盖全部 `Control` 变体、
98
+ 信封的各种 `seq` / `body` 长度(空、bin8、bin16、u64)、JSON-RPC 的四种形状;`frames.json` 是 data 信封分片框架的向量。TS 侧解码后必须等于逻辑值,再编码必须逐字节一致。
99
+ 由 `tools/gen-vectors`(独立的 cargo 工程,不是 workspace 成员)生成:
100
+
101
+ ```bash
102
+ just gen-vectors # 协议编码变了才需要;CI 的 gen-check 会 diff
103
+ ```
104
+
105
+ `tests/constants.test.ts` 解析 `crates/protocol/src/{methods,lib,control,error,envelope}.rs` 的源码,
106
+ 断言方法名(含 `pair.ack`)、版本号、短码长度、frame 常量(flags / 片大小 / 重组上限)、close code、错误码数值、`Control` 变体(含 `pair_registered`、`dropped`)、`Kind` 变体与这里一致(漂移守卫)。
107
+
108
+ ## 方法类型表(生成)
109
+
110
+ `src/method-types.ts` 不手写:`crates/protocol` 的 `methods::REGISTRY` 是「方法名 → params / result 类型名」的唯一真源,`just gen-schema` 导出到 `schema/methods.json`,`just gen-ts` 再跑 `tools/gen-method-types.ts`(`bun packages/protocol/tools/gen-method-types.ts [--check]`)生成这个文件。类型名必须是 `generated.ts` 导出的名字,找不到就报错。改了注册表之后 `just gen`,提交 `schema/` 和这两个生成文件。`serde_json::Value` 在 schema 里已经是 `unknown`,没有手写的绕过。
111
+
112
+ ## 在打包器里消费
113
+
114
+ `exports` 带两个开发条件:`source` 和 `bun`,指向 `src/index.ts`,所以 workspace 内不用先 `build`:
115
+
116
+ - tsconfig:`"customConditions": ["source"]`(根 `tsconfig.base.json` 已经带)
117
+ - Vite:`resolve: { conditions: ['source'] }`
118
+ - 发布 / 普通 Node 解析走 `dist/`
@@ -0,0 +1,151 @@
1
+ /**
2
+ * 常量。全部镜像 `crates/protocol`,`constants.test.ts` 会解析 Rust 源码做漂移检查:
3
+ * 改了 Rust 侧的方法名 / 版本号 / close code / 错误码数值,这里不同步测试就会红。
4
+ */
5
+ /** 内层 JSON-RPC 协议版本。相邻 minor 兼容,0.x 阶段可 break。 */
6
+ export declare const PROTOCOL_VERSION = 1;
7
+ /** 外层信封版本。极少改动。 */
8
+ export declare const ENVELOPE_VERSION = 1;
9
+ /** 短码字母表:大写字母数字去掉 0 O 1 I L。 */
10
+ export declare const PAIR_CODE_ALPHABET = "23456789ABCDEFGHJKMNPQRSTUVWXYZ";
11
+ /** 短码长度:8 位(约 40 bit),`anycon pair --short` 生成的;只适合自建 hub,经不信任的 hub 配对不要用。 */
12
+ export declare const PAIR_CODE_LEN = 8;
13
+ /** 长短码长度:16 位(约 79 bit),`anycon pair` 的**默认**长度,链接 / 二维码 / `--json` 都是它。与 `crates/crypto` 的 `PAIR_CODE_LEN_LONG` 一致。 */
14
+ export declare const PAIR_CODE_LEN_LONG = 16;
15
+ /** WebSocket close code(hub → 设备)。 */
16
+ export declare const CLOSE_CODE: {
17
+ /** 同一设备的新连接顶掉了这条。不要自动重连,否则两条连接会互相踢。 */
18
+ readonly SUPERSEDED: 4001;
19
+ /** hub 优雅关停。1–3 秒抖动后重连,而不是指数退避。 */
20
+ readonly RESTARTING: 4002;
21
+ /** 接收太慢,发送缓冲持续满。 */
22
+ readonly SLOW_CONSUMER: 4003;
23
+ /** 设备已被吊销。 */
24
+ readonly REVOKED: 4004;
25
+ /** 第一帧 auth 超时。 */
26
+ readonly AUTH_TIMEOUT: 4005;
27
+ };
28
+ /** 内层方法名。`域.动作`。键名与 `crates/protocol/src/methods.rs` 的常量名一致。 */
29
+ export declare const METHODS: {
30
+ /** D→C notification,params = PairAck */
31
+ readonly PAIR_ACK: "pair.ack";
32
+ readonly NODE_INFO: "node.info";
33
+ readonly NODE_DEVICES: "node.devices";
34
+ readonly NODE_REVOKE_DEVICE: "node.revoke_device";
35
+ readonly NODE_PAIR_BEGIN: "node.pair_begin";
36
+ readonly NODE_STATS: "node.stats";
37
+ readonly NODE_DOCTOR: "node.doctor";
38
+ readonly NODE_AUDIT: "node.audit";
39
+ readonly NODE_SET_AGENT_DEFAULTS: "node.set_agent_defaults";
40
+ /** 进行中的接入会话。 */
41
+ readonly NODE_ENROLL_LIST: "node.enroll_list";
42
+ /** 批准一个接入请求:`code` 返回 6 位码,`scan` 带二维码里的密钥。 */
43
+ readonly NODE_ENROLL_APPROVE: "node.enroll_approve";
44
+ readonly NODE_ENROLL_DENY: "node.enroll_deny";
45
+ /** 主动生成 6 位接入码(「添加设备」、`anycon pair --enroll`)。 */
46
+ readonly NODE_ENROLL_CODE: "node.enroll_code";
47
+ readonly NODE_ENROLL_STATUS: "node.enroll_status";
48
+ readonly NODE_SET_ENROLL_PASSWORD: "node.set_enroll_password";
49
+ readonly NODE_SET_ENROLL_ENABLED: "node.set_enroll_enabled";
50
+ /** D→C notification,params = EnrollInfo */
51
+ readonly NODE_ENROLL_REQUESTED: "node.enroll_requested";
52
+ /** D→C notification,params = NodeEnrollResolved */
53
+ readonly NODE_ENROLL_RESOLVED: "node.enroll_resolved";
54
+ /** D→C notification,params = NodeSecurityEvent */
55
+ readonly NODE_SECURITY_EVENT: "node.security_event";
56
+ readonly AGENT_LIST: "agent.list";
57
+ readonly AGENT_DIAGNOSE: "agent.diagnose";
58
+ /** D→C notification,params = AgentListResult */
59
+ readonly AGENT_CHANGED: "agent.changed";
60
+ readonly WORKSPACE_LIST: "workspace.list";
61
+ readonly WORKSPACE_CREATE: "workspace.create";
62
+ readonly WORKSPACE_CLONE: "workspace.clone";
63
+ readonly WORKSPACE_RENAME: "workspace.rename";
64
+ readonly WORKSPACE_TRASH: "workspace.trash";
65
+ readonly WORKSPACE_TRASH_LIST: "workspace.trash_list";
66
+ readonly WORKSPACE_RESTORE: "workspace.restore";
67
+ readonly WORKSPACE_PURGE: "workspace.purge";
68
+ /** 把本机已有目录关联为项目(params = WorkspaceLinkParams → Project)。 */
69
+ readonly WORKSPACE_LINK: "workspace.link";
70
+ /** 解除关联(不删除目录;params = WorkspaceNameParams)。 */
71
+ readonly WORKSPACE_UNLINK: "workspace.unlink";
72
+ /** 列本机目录供选择(params = WorkspaceBrowseParams → WorkspaceBrowseResult)。 */
73
+ readonly WORKSPACE_BROWSE: "workspace.browse";
74
+ /** 列项目的分支(params = WorkspaceBranchesParams → WorkspaceBranchesResult,ADR-34)。 */
75
+ readonly WORKSPACE_BRANCHES: "workspace.branches";
76
+ /** 列 anycon 建的 worktree(params = WorkspaceWorktreesParams → WorkspaceWorktreesResult)。 */
77
+ readonly WORKSPACE_WORKTREES: "workspace.worktrees";
78
+ /** 删掉一个已保留 / 没有会话的 worktree(params = WorkspaceRemoveWorktreeParams)。 */
79
+ readonly WORKSPACE_REMOVE_WORKTREE: "workspace.remove_worktree";
80
+ /** D→C notification */
81
+ readonly WORKSPACE_CHANGED: "workspace.changed";
82
+ /** D→C notification */
83
+ readonly JOB_PROGRESS: "job.progress";
84
+ /** D→C notification */
85
+ readonly JOB_DONE: "job.done";
86
+ readonly JOB_CANCEL: "job.cancel";
87
+ readonly FS_LIST: "fs.list";
88
+ readonly FS_READ: "fs.read";
89
+ readonly SESSION_LIST: "session.list";
90
+ readonly SESSION_GET: "session.get";
91
+ readonly SESSION_CREATE: "session.create";
92
+ readonly SESSION_SEND: "session.send";
93
+ readonly SESSION_CANCEL: "session.cancel";
94
+ readonly SESSION_SET_SETTINGS: "session.set_settings";
95
+ readonly SESSION_WATCH: "session.watch";
96
+ readonly SESSION_UNWATCH: "session.unwatch";
97
+ readonly SESSION_HISTORY: "session.history";
98
+ /** D→C notification,params = SessionEvent */
99
+ readonly SESSION_EVENT: "session.event";
100
+ /** D→C notification,params = SessionChanged */
101
+ readonly SESSION_CHANGED: "session.changed";
102
+ readonly SESSION_RENAME: "session.rename";
103
+ readonly SESSION_ARCHIVE: "session.archive";
104
+ readonly SESSION_UNARCHIVE: "session.unarchive";
105
+ readonly SESSION_DELETE: "session.delete";
106
+ readonly SESSION_RESTART: "session.restart";
107
+ readonly SESSION_SEARCH: "session.search";
108
+ readonly SESSION_PROMOTE: "session.promote";
109
+ /** params = SessionGitStatusParams → SessionGitStatus(ADR-34) */
110
+ readonly SESSION_GIT_STATUS: "session.git_status";
111
+ /** params = SessionDiffParams → SessionDiffResult */
112
+ readonly SESSION_DIFF: "session.diff";
113
+ /** params = SessionSyncBaseParams → SessionSyncBaseResult */
114
+ readonly SESSION_SYNC_BASE: "session.sync_base";
115
+ /** params = BlobPutBeginParams → BlobPutBeginResult */
116
+ readonly BLOB_PUT_BEGIN: "blob.put_begin";
117
+ /** params = BlobPutChunkParams */
118
+ readonly BLOB_PUT_CHUNK: "blob.put_chunk";
119
+ /** params = BlobIdParams → AttachmentRef */
120
+ readonly BLOB_PUT_END: "blob.put_end";
121
+ /** params = BlobGetParams → BlobGetResult */
122
+ readonly BLOB_GET: "blob.get";
123
+ /** params = BlobIdParams */
124
+ readonly BLOB_DISCARD: "blob.discard";
125
+ /** D→C notification,params = PermissionRequest */
126
+ readonly PERMISSION_REQUEST: "permission.request";
127
+ readonly PERMISSION_RESPOND: "permission.respond";
128
+ /** D→C notification,params = PermissionResolved */
129
+ readonly PERMISSION_RESOLVED: "permission.resolved";
130
+ readonly PERMISSION_LIST: "permission.list";
131
+ };
132
+ export type MethodName = (typeof METHODS)[keyof typeof METHODS];
133
+ /**
134
+ * `ErrorCode` → JSON-RPC 数字 code(`crates/protocol/src/error.rs` 的 `rpc_code`)。
135
+ * 客户端只应依赖 `error.data.code`;数字 code 仅在对端没带 `data` 时做兜底。
136
+ */
137
+ export declare const ERROR_CODE_NUMBERS: {
138
+ readonly invalid: -32602;
139
+ readonly invalid_settings: -32001;
140
+ readonly agent_unavailable: -32002;
141
+ readonly node_busy: -32003;
142
+ readonly busy: -32004;
143
+ readonly already_resolved: -32005;
144
+ readonly exists: -32006;
145
+ readonly not_found: -32007;
146
+ readonly forbidden: -32008;
147
+ readonly unsupported: -32009;
148
+ readonly protocol_mismatch: -32010;
149
+ readonly internal: -32603;
150
+ };
151
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,kDAAkD;AAClD,eAAO,MAAM,gBAAgB,IAAI,CAAA;AACjC,mBAAmB;AACnB,eAAO,MAAM,gBAAgB,IAAI,CAAA;AAEjC,gCAAgC;AAChC,eAAO,MAAM,kBAAkB,oCAAoC,CAAA;AACnE,8EAA8E;AAC9E,eAAO,MAAM,aAAa,IAAI,CAAA;AAC9B,wHAAwH;AACxH,eAAO,MAAM,kBAAkB,KAAK,CAAA;AAEpC,sCAAsC;AACtC,eAAO,MAAM,UAAU;IACrB,uCAAuC;;IAEvC,mCAAmC;;IAEnC,oBAAoB;;IAEpB,cAAc;;IAEd,mBAAmB;;CAEX,CAAA;AAEV,gEAAgE;AAChE,eAAO,MAAM,OAAO;IAElB,wCAAwC;;;;;;;;;;IAcxC,gBAAgB;;IAEhB,+CAA+C;;;IAG/C,kDAAkD;;;;;IAKlD,2CAA2C;;IAE3C,mDAAmD;;IAEnD,kDAAkD;;;;IAMlD,gDAAgD;;;;;;;;;;IAYhD,4DAA4D;;IAE5D,gDAAgD;;IAEhD,wEAAwE;;IAExE,iFAAiF;;IAEjF,0FAA0F;;IAE1F,wEAAwE;;IAExE,uBAAuB;;IAEvB,uBAAuB;;IAEvB,uBAAuB;;;;;;;;;;;;;;IAkBvB,6CAA6C;;IAE7C,+CAA+C;;;;;;;;;IAS/C,iEAAiE;;IAEjE,qDAAqD;;IAErD,6DAA6D;;IAI7D,uDAAuD;;IAEvD,kCAAkC;;IAElC,4CAA4C;;IAE5C,6CAA6C;;IAE7C,4BAA4B;;IAI5B,kDAAkD;;;IAGlD,mDAAmD;;;CAG3C,CAAA;AAEV,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,OAAO,CAAC,CAAC,MAAM,OAAO,OAAO,CAAC,CAAA;AAE/D;;;GAGG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;CAarB,CAAA"}