@motrix/mdxp 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +341 -53
- package/README.zh-CN.md +375 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -1,103 +1,391 @@
|
|
|
1
1
|
# @motrix/mdxp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@motrix/mdxp)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
[](./dist/index.d.ts)
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
**English** | [简体中文](./README.zh-CN.md)
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
> **MDXP** (Motrix Download eXchange Protocol) — the JSON-RPC 2.0 wire types, Zod
|
|
10
|
+
> schemas, and bidirectional connection helper that let a browser, CLI, or AI
|
|
11
|
+
> agent hand downloads to a Motrix desktop downloader over any duplex transport.
|
|
12
|
+
|
|
13
|
+
`@motrix/mdxp` is the single source of truth for the MDXP wire contract. Both
|
|
14
|
+
sides of the bridge — the [Motrix](https://github.com/agalwood/Motrix) desktop app
|
|
15
|
+
(the **server**, which owns the download engine) and its **clients** (the
|
|
16
|
+
browser extension, a CLI, or an agent) — depend on this package so the protocol
|
|
17
|
+
shape is defined exactly once.
|
|
18
|
+
|
|
19
|
+
It ships nothing transport-specific: you bring any
|
|
20
|
+
[`vscode-jsonrpc`](https://www.npmjs.com/package/vscode-jsonrpc)
|
|
21
|
+
`MessageReader`/`MessageWriter` pair (stdio, a socket, a WebSocket, a
|
|
22
|
+
`MessagePort`) and the library builds a fully typed, bidirectional connection on
|
|
23
|
+
top of it.
|
|
24
|
+
|
|
25
|
+
## Highlights
|
|
26
|
+
|
|
27
|
+
- **Schema-first.** Every wire shape is a [Zod](https://zod.dev) schema; the
|
|
28
|
+
TypeScript types are `z.infer` of those schemas, so validation and types can
|
|
29
|
+
never drift apart.
|
|
30
|
+
- **Fully typed connection.** `sendRequest`/`onRequest`/`sendNotification`/
|
|
31
|
+
`onNotification` are generic over the method name — params and result types
|
|
32
|
+
are inferred from that name, with no casts at the call site.
|
|
33
|
+
- **Transport-agnostic.** Works over anything that implements
|
|
34
|
+
`MessageReader`/`MessageWriter`.
|
|
35
|
+
- **Platform RAL entry points.** `./node` and `./browser` install the matching
|
|
36
|
+
`vscode-jsonrpc` runtime abstraction layer for you.
|
|
37
|
+
- **Agent-ready.** A built-in tool registry emits a JSON-Schema tool catalog you
|
|
38
|
+
can feed straight into an LLM function-calling API.
|
|
39
|
+
- **Forward-compatible.** Unknown methods and fields are ignored, not rejected.
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
8
42
|
|
|
9
43
|
```bash
|
|
10
|
-
|
|
44
|
+
npm install @motrix/mdxp
|
|
45
|
+
# or: pnpm add @motrix/mdxp · yarn add @motrix/mdxp
|
|
11
46
|
```
|
|
12
47
|
|
|
13
|
-
|
|
48
|
+
Runtime dependency: [`vscode-jsonrpc`](https://www.npmjs.com/package/vscode-jsonrpc)
|
|
49
|
+
`^9`, a direct dependency installed alongside this package. The examples below
|
|
50
|
+
also import reader/writer classes straight from `vscode-jsonrpc/node` and
|
|
51
|
+
`vscode-jsonrpc/browser`; if you do the same, declare `vscode-jsonrpc` in your
|
|
52
|
+
own dependencies rather than relying on hoisting. ESM-only; requires
|
|
53
|
+
Node.js ≥ 18 or a modern bundler.
|
|
14
54
|
|
|
15
|
-
###
|
|
55
|
+
### Entry points
|
|
56
|
+
|
|
57
|
+
| Import | Installs a RAL? | Use it from |
|
|
58
|
+
| --- | --- | --- |
|
|
59
|
+
| `@motrix/mdxp` | No — platform-agnostic core | Shared code, tests, type-only imports |
|
|
60
|
+
| `@motrix/mdxp/node` | Node RAL | A Node host (Electron main, a CLI, a native-messaging host) |
|
|
61
|
+
| `@motrix/mdxp/browser` | Browser RAL | A browser host (extension service worker, page) |
|
|
62
|
+
|
|
63
|
+
`vscode-jsonrpc` v9 requires a runtime abstraction layer (RAL) to be installed
|
|
64
|
+
before a connection can be created. Importing `@motrix/mdxp/node` or
|
|
65
|
+
`@motrix/mdxp/browser` installs the right one into the **same** `vscode-jsonrpc`
|
|
66
|
+
instance this package uses, and re-exports the entire public API — so a host
|
|
67
|
+
imports everything from a single place.
|
|
68
|
+
|
|
69
|
+
## Quick start
|
|
70
|
+
|
|
71
|
+
### Node host (over stdio)
|
|
16
72
|
|
|
17
73
|
```ts
|
|
18
|
-
import { createMdxpConnection } from '@motrix/mdxp'
|
|
74
|
+
import { createMdxpConnection } from '@motrix/mdxp/node'
|
|
19
75
|
import { StreamMessageReader, StreamMessageWriter } from 'vscode-jsonrpc/node'
|
|
20
76
|
|
|
21
77
|
const conn = createMdxpConnection(
|
|
22
78
|
new StreamMessageReader(process.stdin),
|
|
23
|
-
new StreamMessageWriter(process.stdout)
|
|
79
|
+
new StreamMessageWriter(process.stdout),
|
|
24
80
|
)
|
|
25
81
|
|
|
26
|
-
//
|
|
27
|
-
conn.
|
|
28
|
-
|
|
82
|
+
// Register handlers BEFORE listen().
|
|
83
|
+
conn.onNotification('$/task/progress', (p) => {
|
|
84
|
+
const pct = p.bytesTotal ? Math.round((p.bytesDone / p.bytesTotal) * 100) : null
|
|
85
|
+
console.log(`[${p.taskId}] ${p.phase} ${pct ?? '?'}% @ ${p.speedBps} B/s`)
|
|
29
86
|
})
|
|
30
87
|
|
|
31
|
-
conn.
|
|
32
|
-
|
|
33
|
-
|
|
88
|
+
conn.listen()
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Browser host
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
// Installs the browser RAL, then re-exports the full API.
|
|
95
|
+
import { createMdxpConnection } from '@motrix/mdxp/browser'
|
|
96
|
+
import { BrowserMessageReader, BrowserMessageWriter } from 'vscode-jsonrpc/browser'
|
|
34
97
|
|
|
98
|
+
// e.g. a MessagePort / Worker; adapt your WebSocket to reader/writer as needed.
|
|
99
|
+
const conn = createMdxpConnection(
|
|
100
|
+
new BrowserMessageReader(worker),
|
|
101
|
+
new BrowserMessageWriter(worker),
|
|
102
|
+
)
|
|
35
103
|
conn.listen()
|
|
36
104
|
```
|
|
37
105
|
|
|
38
|
-
|
|
106
|
+
## Core concepts
|
|
107
|
+
|
|
108
|
+
**Server vs. client.** The Motrix desktop app is the **server** — it owns the
|
|
109
|
+
download engine. A **client** is whatever drives it: the browser extension, a
|
|
110
|
+
CLI, or an agent. The connection is symmetric, but methods flow in a defined
|
|
111
|
+
direction (below).
|
|
112
|
+
|
|
113
|
+
**The handshake comes first.** `motrix/initialize` MUST be the first message of
|
|
114
|
+
every session. It negotiates the protocol version, exchanges identity, and
|
|
115
|
+
declares capabilities. Nothing else should be sent until it resolves.
|
|
116
|
+
|
|
117
|
+
**Message direction.** Most methods are client→server (the client asks the
|
|
118
|
+
downloader to do something). Two are server→client — the server asks the client
|
|
119
|
+
to inspect a page: `url/probe` and `url/resolve` (see
|
|
120
|
+
[`SERVER_INITIATED_METHODS`](#api-reference)). Because the server initiates both,
|
|
121
|
+
a client answers them with `onRequest`, while the server side calls them with
|
|
122
|
+
`sendRequest`.
|
|
123
|
+
|
|
124
|
+
## Usage
|
|
125
|
+
|
|
126
|
+
### Handshake
|
|
39
127
|
|
|
40
128
|
```ts
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
129
|
+
const hello = await conn.sendRequest('motrix/initialize', {
|
|
130
|
+
protocolVersion: '1.0',
|
|
131
|
+
client: {
|
|
132
|
+
kind: 'cli', // or 'extension'
|
|
133
|
+
name: 'my-download-agent',
|
|
134
|
+
version: '1.0.0',
|
|
135
|
+
locale: 'en-US',
|
|
136
|
+
},
|
|
137
|
+
capabilities: { submitDownload: true, progress: true, cancellation: true },
|
|
138
|
+
adapters: [], // page adapters this client can resolve, if any
|
|
44
139
|
})
|
|
45
|
-
|
|
140
|
+
|
|
141
|
+
console.log(hello.server.name, hello.server.version)
|
|
142
|
+
console.log(hello.capabilities.selectionKinds) // e.g. ['direct', 'hls', 'mux']
|
|
46
143
|
```
|
|
47
144
|
|
|
48
|
-
###
|
|
145
|
+
### Add a download (client → server)
|
|
146
|
+
|
|
147
|
+
`download/add` is the public, agent-facing entry point. It accepts a direct
|
|
148
|
+
URL list, a magnet link, or a base64 torrent, and returns the created task
|
|
149
|
+
snapshot so you can render it without polling.
|
|
49
150
|
|
|
50
151
|
```ts
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
152
|
+
// Direct HTTP(S) file
|
|
153
|
+
const task = await conn.sendRequest('download/add', {
|
|
154
|
+
kind: 'url',
|
|
155
|
+
saveDir: '/Users/me/Downloads',
|
|
156
|
+
uris: ['https://cdn.example.com/releases/app-1.4.2-arm64.dmg'],
|
|
157
|
+
connections: 8,
|
|
158
|
+
})
|
|
159
|
+
console.log(task.id, task.status) // "t_01H…", "downloading"
|
|
160
|
+
|
|
161
|
+
// Magnet link
|
|
162
|
+
await conn.sendRequest('download/add', {
|
|
163
|
+
kind: 'magnet',
|
|
164
|
+
saveDir: '/Users/me/Downloads',
|
|
165
|
+
uri: 'magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a',
|
|
58
166
|
})
|
|
59
167
|
```
|
|
60
168
|
|
|
61
|
-
|
|
169
|
+
> Only `http`, `https`, `ftp`, `ftps`, and `sftp` URLs are accepted — the schema
|
|
170
|
+
> rejects `file:`, `data:`, and `javascript:` at the contract boundary, so an
|
|
171
|
+
> agent can never be coerced into a local-file read.
|
|
172
|
+
|
|
173
|
+
### Query and control tasks (client → server)
|
|
62
174
|
|
|
63
175
|
```ts
|
|
64
|
-
|
|
176
|
+
const { tasks, total } = await conn.sendRequest('task/list', {
|
|
177
|
+
status: 'downloading',
|
|
178
|
+
limit: 20,
|
|
179
|
+
})
|
|
65
180
|
|
|
66
|
-
|
|
67
|
-
|
|
181
|
+
await conn.sendRequest('task/pause', { taskId: task.id })
|
|
182
|
+
await conn.sendRequest('task/resume', { taskId: task.id })
|
|
183
|
+
await conn.sendRequest('task/remove', { taskId: task.id, deleteFiles: false })
|
|
68
184
|
```
|
|
69
185
|
|
|
70
|
-
###
|
|
186
|
+
### Resolve a page (server → client)
|
|
187
|
+
|
|
188
|
+
The desktop app asks a client whether it can handle a page (`url/probe`), then
|
|
189
|
+
asks it to extract the downloadable resources (`url/resolve`). A client answers
|
|
190
|
+
by registering handlers:
|
|
71
191
|
|
|
72
192
|
```ts
|
|
73
|
-
|
|
193
|
+
conn.onRequest('url/probe', async ({ url }) => ({
|
|
194
|
+
handled: /videos\.example\.com/.test(url),
|
|
195
|
+
adapterId: 'example-video',
|
|
196
|
+
confidence: 'high',
|
|
197
|
+
}))
|
|
198
|
+
|
|
199
|
+
conn.onRequest('url/resolve', async ({ url, preferences }) => ({
|
|
200
|
+
selections: [
|
|
201
|
+
{
|
|
202
|
+
kind: 'direct',
|
|
203
|
+
primary: {
|
|
204
|
+
url: 'https://cdn.example.com/v/abc123/1080p.mp4',
|
|
205
|
+
headers: {},
|
|
206
|
+
cookies: [],
|
|
207
|
+
refererPolicy: 'strict-origin-when-cross-origin',
|
|
208
|
+
},
|
|
209
|
+
container: 'mp4',
|
|
210
|
+
quality: preferences?.maxQuality ?? '1080p',
|
|
211
|
+
sizeBytes: 734_003_200,
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
meta: { title: 'Sample clip', author: 'example.com', durationSec: 372 },
|
|
215
|
+
extractedBy: {
|
|
216
|
+
adapterId: 'example-video',
|
|
217
|
+
adapterVersion: '1.0.0',
|
|
218
|
+
extractedAt: Date.now(),
|
|
219
|
+
},
|
|
220
|
+
}))
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
A `selection` is a discriminated union on `kind`: `direct` (one file), `hls`
|
|
224
|
+
(a playlist), or `mux` (separate video + audio streams the server muxes). Each
|
|
225
|
+
`Resource` carries the `headers`/`cookies` needed to re-fetch it server-side.
|
|
74
226
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
227
|
+
### Progress and lifecycle (server → client)
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
conn.onNotification('$/task/progress', (p) => {
|
|
231
|
+
// p.phase: 'queued' | 'downloading' | 'muxing' | 'finalizing'
|
|
232
|
+
})
|
|
233
|
+
conn.onNotification('$/task/completed', (p) => {
|
|
234
|
+
console.log('done →', p.filePath, `(${p.durationMs} ms)`)
|
|
235
|
+
})
|
|
236
|
+
conn.onNotification('$/task/error', (p) => {
|
|
237
|
+
console.error(`task ${p.taskId} failed: [${p.code}] ${p.message}`)
|
|
79
238
|
})
|
|
80
239
|
```
|
|
81
240
|
|
|
82
|
-
|
|
241
|
+
### Cancellation
|
|
242
|
+
|
|
243
|
+
`sendRequest` accepts an optional `CancellationToken`. Cancelling emits
|
|
244
|
+
`$/cancelRequest` on the wire (handled by `vscode-jsonrpc`); a cooperative
|
|
245
|
+
handler observes `token.isCancellationRequested`.
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
import { CancellationTokenSource } from 'vscode-jsonrpc'
|
|
249
|
+
|
|
250
|
+
const cts = new CancellationTokenSource()
|
|
251
|
+
const pending = conn.sendRequest('url/resolve', { url }, cts.token)
|
|
252
|
+
// …the user navigated away:
|
|
253
|
+
cts.cancel()
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Runtime validation
|
|
257
|
+
|
|
258
|
+
Every wire shape has a schema. Validate untrusted input at your boundary with
|
|
259
|
+
`safeParse` before acting on it:
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
import { DownloadAddParamsSchema } from '@motrix/mdxp'
|
|
263
|
+
|
|
264
|
+
const parsed = DownloadAddParamsSchema.safeParse(untrusted)
|
|
265
|
+
if (!parsed.success) {
|
|
266
|
+
// parsed.error — a ZodError describing exactly what was wrong
|
|
267
|
+
return
|
|
268
|
+
}
|
|
269
|
+
await conn.sendRequest('download/add', parsed.data)
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Error model
|
|
273
|
+
|
|
274
|
+
Return structured errors from a handler with `makeMdxpError`. The `code` is a
|
|
275
|
+
JSON-RPC error code; `data` carries a machine-readable `appCode`, a retry hint,
|
|
276
|
+
and free-form context.
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
import { ErrorCodes, makeMdxpError } from '@motrix/mdxp'
|
|
280
|
+
|
|
281
|
+
throw makeMdxpError(
|
|
282
|
+
ErrorCodes.ResourceUnavailable,
|
|
283
|
+
'The requested file is no longer available',
|
|
284
|
+
{ appCode: 'http.gone', retryable: false, context: { status: 410 } },
|
|
285
|
+
)
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Classify a received code with `isProtocolError(code)` (JSON-RPC reserved) or
|
|
289
|
+
`isMotrixError(code)` (Motrix's `-32001…-32099` range).
|
|
290
|
+
|
|
291
|
+
### AI-agent tool catalog
|
|
292
|
+
|
|
293
|
+
The agent-facing methods are exposed as a JSON-Schema tool catalog, ready for an
|
|
294
|
+
LLM function-calling / tool-use API:
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
import { toAgentToolCatalog } from '@motrix/mdxp'
|
|
298
|
+
|
|
299
|
+
const tools = toAgentToolCatalog()
|
|
300
|
+
// [
|
|
301
|
+
// { name: 'download/add', description, inputSchema: {…JSON Schema}, outputSchema },
|
|
302
|
+
// { name: 'task/list', … },
|
|
303
|
+
// …
|
|
304
|
+
// ]
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## API reference
|
|
308
|
+
|
|
309
|
+
### Exports
|
|
310
|
+
|
|
311
|
+
| Export | Kind | Purpose |
|
|
312
|
+
| --- | --- | --- |
|
|
313
|
+
| `createMdxpConnection(reader, writer)` | function | Wrap a reader/writer pair in a typed `MdxpConnection`. |
|
|
314
|
+
| `MdxpConnection` | type | The connection interface (`sendRequest`, `onRequest`, `sendNotification`, `onNotification`, `dispose`, `raw`). |
|
|
315
|
+
| `MdxpRequestMap` / `MdxpNotificationMap` | type | Method/notification name → params/result type maps. |
|
|
316
|
+
| `Methods` / `Notifications` | const | Wire-name constants (`Methods.DownloadAdd === 'download/add'`). |
|
|
317
|
+
| `ErrorCodes` | const | JSON-RPC + Motrix-defined error codes. |
|
|
318
|
+
| `makeMdxpError(code, msg, data?)` | function | Build a structured `MdxpError`. |
|
|
319
|
+
| `isProtocolError` / `isMotrixError` | function | Classify an error code. |
|
|
320
|
+
| `Tools` | const | Registry of every client→server method → `{ description, paramsSchema, resultSchema, agentFacing }`. |
|
|
321
|
+
| `toAgentToolCatalog()` | function | The `agentFacing` subset as JSON-Schema tools. |
|
|
322
|
+
| `SERVER_INITIATED_METHODS` | const | Methods the server calls on the client (`url/probe`, `url/resolve`). |
|
|
323
|
+
| `*Schema` | Zod schema | Every wire shape, for runtime validation. |
|
|
324
|
+
|
|
325
|
+
### Methods
|
|
326
|
+
|
|
327
|
+
| Method | Direction | Agent-facing | Purpose |
|
|
328
|
+
| --- | --- | :---: | --- |
|
|
329
|
+
| `motrix/initialize` | client → server | | Handshake: version, identity, capabilities. |
|
|
330
|
+
| `system/ping` | client → server | | Liveness probe; echoes `sentAt` with `recvAt`. |
|
|
331
|
+
| `download/submit` | client → server | | Submit a browser-detected, page-shaped download. |
|
|
332
|
+
| `download/cancel` | client → server | | Cancel a submitted download by task id. |
|
|
333
|
+
| `download/add` | client → server | ✓ | Add a download by URL(s), magnet, or torrent. |
|
|
334
|
+
| `task/list` | client → server | ✓ | List tasks, filterable + paginated. |
|
|
335
|
+
| `task/get` | client → server | ✓ | Get one task by id. |
|
|
336
|
+
| `task/pause` · `task/resume` | client → server | ✓ | Pause / resume a task. |
|
|
337
|
+
| `task/remove` | client → server | ✓ | Remove a task, optionally deleting files. |
|
|
338
|
+
| `stats/get` | client → server | ✓ | Aggregate global stats (speeds + counts). |
|
|
339
|
+
| `engine/status` | client → server | ✓ | Engine lifecycle state + feature report. |
|
|
340
|
+
| `url/probe` | **server → client** | | Can this client's adapters handle a page? |
|
|
341
|
+
| `url/resolve` | **server → client** | | Extract downloadable resources from a page. |
|
|
342
|
+
|
|
343
|
+
### Notifications
|
|
344
|
+
|
|
345
|
+
| Notification | Direction | Payload |
|
|
346
|
+
| --- | --- | --- |
|
|
347
|
+
| `motrix/initialized` | client → server | Handshake completion (no payload). |
|
|
348
|
+
| `$/task/progress` | server → client | `bytesDone`, `bytesTotal`, `speedBps`, `etaSec`, `phase`. |
|
|
349
|
+
| `$/task/completed` | server → client | `filePath`, `durationMs`. |
|
|
350
|
+
| `$/task/error` | server → client | `code`, `message`. |
|
|
351
|
+
| `$/stats` | server → client | Periodic aggregate stats push. |
|
|
352
|
+
| `$/pair/revoked` | server → client | Pairing was revoked (`reason`). |
|
|
353
|
+
| `$/cancelRequest` | either | Cancellation — handled by `vscode-jsonrpc`. |
|
|
354
|
+
|
|
355
|
+
### Error codes
|
|
356
|
+
|
|
357
|
+
| Code | Value | Range |
|
|
358
|
+
| --- | --- | --- |
|
|
359
|
+
| `ParseError` | `-32700` | JSON-RPC reserved |
|
|
360
|
+
| `InvalidRequest` | `-32600` | JSON-RPC reserved |
|
|
361
|
+
| `MethodNotFound` | `-32601` | JSON-RPC reserved |
|
|
362
|
+
| `InvalidParams` | `-32602` | JSON-RPC reserved |
|
|
363
|
+
| `InternalError` | `-32603` | JSON-RPC reserved |
|
|
364
|
+
| `RequestCancelled` | `-32800` | LSP extension |
|
|
365
|
+
| `AdapterError` | `-32001` | Motrix |
|
|
366
|
+
| `ResourceUnavailable` | `-32002` | Motrix |
|
|
367
|
+
| `PermissionDenied` | `-32003` | Motrix |
|
|
368
|
+
| `RateLimited` | `-32004` | Motrix |
|
|
369
|
+
| `CapabilityNotSupported` | `-32005` | Motrix |
|
|
370
|
+
| `PairRevoked` | `-32006` | Motrix |
|
|
371
|
+
|
|
372
|
+
## Protocol notes
|
|
83
373
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
| `makeMdxpError(code, msg, data?)` | error 构造 helper |
|
|
91
|
-
| `createMdxpConnection(reader, writer)` | 主入口,返回 `MdxpConnection` |
|
|
92
|
-
| `*Schema` | 全部 Zod schemas(运行时校验) |
|
|
93
|
-
| `MdxpRequestMap`, `MdxpNotificationMap` | 类型表(method → [params, result]) |
|
|
374
|
+
- **Version.** `protocolVersion` is `'1.0'`. This is the wire-compatibility
|
|
375
|
+
version and is independent of this package's npm version.
|
|
376
|
+
- **No batching.** JSON-RPC batching is forbidden — one frame, one message.
|
|
377
|
+
- **Forward-compatible.** Result and notification payloads are non-strict:
|
|
378
|
+
a newer server may add fields that older clients ignore. An unknown method is
|
|
379
|
+
rejected with `MethodNotFound` rather than crashing the session.
|
|
94
380
|
|
|
95
|
-
##
|
|
381
|
+
## Design principles
|
|
96
382
|
|
|
97
|
-
- **
|
|
98
|
-
|
|
99
|
-
- **
|
|
383
|
+
- **Transport-agnostic** — the library never assumes a specific transport; any
|
|
384
|
+
`MessageReader`/`MessageWriter` duplex works.
|
|
385
|
+
- **Schema-first** — define the Zod schema, infer the type; never hand-write a
|
|
386
|
+
type that has a corresponding schema.
|
|
387
|
+
- **Forward-compatible** — ignore the unknown rather than reject it.
|
|
100
388
|
|
|
101
389
|
## License
|
|
102
390
|
|
|
103
|
-
MIT
|
|
391
|
+
[MIT](./LICENSE) © Dr_rOot
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
# @motrix/mdxp
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@motrix/mdxp)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
[](./dist/index.d.ts)
|
|
6
|
+
|
|
7
|
+
[English](./README.md) | **简体中文**
|
|
8
|
+
|
|
9
|
+
> **MDXP**(Motrix Download eXchange Protocol)—— 一套 JSON-RPC 2.0 wire 类型、
|
|
10
|
+
> Zod schema 与双向连接封装,让浏览器、CLI 或 AI agent 通过任意双工 transport,
|
|
11
|
+
> 把下载任务移交给 Motrix 桌面下载器。
|
|
12
|
+
|
|
13
|
+
`@motrix/mdxp` 是 MDXP wire 契约的唯一真源。bridge 的两端 —— 作为 **server** 的
|
|
14
|
+
[Motrix](https://github.com/agalwood/Motrix) 桌面端(持有下载引擎),与各类 **client**
|
|
15
|
+
(浏览器扩展、CLI、agent)—— 都依赖本包,从而让协议形态只需定义一次。
|
|
16
|
+
|
|
17
|
+
本包不含任何 transport 相关实现:你提供一对
|
|
18
|
+
[`vscode-jsonrpc`](https://www.npmjs.com/package/vscode-jsonrpc) 的
|
|
19
|
+
`MessageReader`/`MessageWriter`(stdio、socket、WebSocket、`MessagePort` 皆可),
|
|
20
|
+
本库便在其上构建出一个完全类型化的双向连接。
|
|
21
|
+
|
|
22
|
+
## 特性
|
|
23
|
+
|
|
24
|
+
- **Schema-first**:每个 wire 形态都是一个 [Zod](https://zod.dev) schema,
|
|
25
|
+
TypeScript 类型再由 schema `z.infer` 推导而来 —— 校验与类型因此永远不会脱节。
|
|
26
|
+
- **完全类型化的连接**:`sendRequest`/`onRequest`/`sendNotification`/
|
|
27
|
+
`onNotification` 均以 method 名为泛型参数,params 与 result 类型据此自动推断,
|
|
28
|
+
调用处无需任何 cast。
|
|
29
|
+
- **transport-agnostic**:只要实现了 `MessageReader`/`MessageWriter`,任何双工流都能用。
|
|
30
|
+
- **平台 RAL 入口**:`./node` 与 `./browser` 会替你装好对应的 `vscode-jsonrpc`
|
|
31
|
+
runtime abstraction layer。
|
|
32
|
+
- **面向 agent**:内置的 tool registry 可产出一份 JSON-Schema tool catalog,
|
|
33
|
+
能直接对接 LLM 的 function-calling API。
|
|
34
|
+
- **forward-compatible**:遇到未知的 method 或字段选择忽略,而非 reject。
|
|
35
|
+
|
|
36
|
+
## 安装
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install @motrix/mdxp
|
|
40
|
+
# 或:pnpm add @motrix/mdxp · yarn add @motrix/mdxp
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
运行时依赖:[`vscode-jsonrpc`](https://www.npmjs.com/package/vscode-jsonrpc)
|
|
44
|
+
`^9`,是本包的直接 `dependency`,会随本包一并装上。下面的示例还直接从
|
|
45
|
+
`vscode-jsonrpc/node` 和 `vscode-jsonrpc/browser` import reader/writer 类;若你也
|
|
46
|
+
这样用,请在自己的 dependencies 里显式声明 `vscode-jsonrpc`,不要依赖 hoisting。
|
|
47
|
+
仅 ESM;需要 Node.js ≥ 18 或现代 bundler。
|
|
48
|
+
|
|
49
|
+
### 入口点
|
|
50
|
+
|
|
51
|
+
| import | 是否装 RAL | 使用场景 |
|
|
52
|
+
| --- | --- | --- |
|
|
53
|
+
| `@motrix/mdxp` | 否 —— 平台无关的核心 | 共享代码、测试、仅类型 import |
|
|
54
|
+
| `@motrix/mdxp/node` | Node RAL | Node host(Electron main、CLI、native-messaging host) |
|
|
55
|
+
| `@motrix/mdxp/browser` | Browser RAL | 浏览器 host(扩展 service worker、页面) |
|
|
56
|
+
|
|
57
|
+
`vscode-jsonrpc` v9 要求先装好一层 runtime abstraction layer(RAL),才能创建连接。
|
|
58
|
+
import `@motrix/mdxp/node` 或 `@motrix/mdxp/browser` 会把对应的 RAL 装进本包所用的
|
|
59
|
+
**同一个** `vscode-jsonrpc` 实例,并 re-export 完整的公开 API —— 于是 host 只需从
|
|
60
|
+
一个入口 import 一切。
|
|
61
|
+
|
|
62
|
+
## 快速开始
|
|
63
|
+
|
|
64
|
+
### Node host(走 stdio)
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import { createMdxpConnection } from '@motrix/mdxp/node'
|
|
68
|
+
import { StreamMessageReader, StreamMessageWriter } from 'vscode-jsonrpc/node'
|
|
69
|
+
|
|
70
|
+
const conn = createMdxpConnection(
|
|
71
|
+
new StreamMessageReader(process.stdin),
|
|
72
|
+
new StreamMessageWriter(process.stdout),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
// 务必在 listen() 之前注册 handler。
|
|
76
|
+
conn.onNotification('$/task/progress', (p) => {
|
|
77
|
+
const pct = p.bytesTotal ? Math.round((p.bytesDone / p.bytesTotal) * 100) : null
|
|
78
|
+
console.log(`[${p.taskId}] ${p.phase} ${pct ?? '?'}% @ ${p.speedBps} B/s`)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
conn.listen()
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 浏览器 host
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
// 装好 browser RAL,并 re-export 全量 API。
|
|
88
|
+
import { createMdxpConnection } from '@motrix/mdxp/browser'
|
|
89
|
+
import { BrowserMessageReader, BrowserMessageWriter } from 'vscode-jsonrpc/browser'
|
|
90
|
+
|
|
91
|
+
// 例如一个 MessagePort / Worker;若用 WebSocket,需自行适配成 reader/writer。
|
|
92
|
+
const conn = createMdxpConnection(
|
|
93
|
+
new BrowserMessageReader(worker),
|
|
94
|
+
new BrowserMessageWriter(worker),
|
|
95
|
+
)
|
|
96
|
+
conn.listen()
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## 核心概念
|
|
100
|
+
|
|
101
|
+
**server 与 client**:Motrix 桌面端是 **server** —— 下载引擎由它持有。**client**
|
|
102
|
+
则是驱动它的一方:浏览器扩展、CLI 或 agent。连接本身是对称的,但每个 method 都有
|
|
103
|
+
既定的调用方向(见下)。
|
|
104
|
+
|
|
105
|
+
**握手先行**:`motrix/initialize` **必须**是每个 session 的第一条消息 —— 它负责协商
|
|
106
|
+
protocol version、交换身份、声明 capabilities。在它 resolve 之前,不应发送任何其它消息。
|
|
107
|
+
|
|
108
|
+
**消息方向**:绝大多数 method 是 client→server(由 client 请求下载器做事)。只有两个是
|
|
109
|
+
server→client —— server 请求 client 去检视某个页面:`url/probe` 与 `url/resolve`
|
|
110
|
+
(见 [`SERVER_INITIATED_METHODS`](#api-参考))。既然这两个都由 server 发起,client
|
|
111
|
+
一侧就用 `onRequest` 应答,server 一侧则用 `sendRequest` 调用。
|
|
112
|
+
|
|
113
|
+
## 用法
|
|
114
|
+
|
|
115
|
+
### 握手
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
const hello = await conn.sendRequest('motrix/initialize', {
|
|
119
|
+
protocolVersion: '1.0',
|
|
120
|
+
client: {
|
|
121
|
+
kind: 'cli', // 或 'extension'
|
|
122
|
+
name: 'my-download-agent',
|
|
123
|
+
version: '1.0.0',
|
|
124
|
+
locale: 'zh-CN',
|
|
125
|
+
},
|
|
126
|
+
capabilities: { submitDownload: true, progress: true, cancellation: true },
|
|
127
|
+
adapters: [], // 该 client 能解析的页面 adapter(如有)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
console.log(hello.server.name, hello.server.version)
|
|
131
|
+
console.log(hello.capabilities.selectionKinds) // 例如 ['direct', 'hls', 'mux']
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### 添加下载(client → server)
|
|
135
|
+
|
|
136
|
+
`download/add` 是面向 agent 的公开入口,接受直连 URL 列表、magnet 链接或 base64
|
|
137
|
+
torrent,并直接返回新建的 task 快照 —— 调用方无需轮询即可渲染。
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
// 直连 HTTP(S) 文件
|
|
141
|
+
const task = await conn.sendRequest('download/add', {
|
|
142
|
+
kind: 'url',
|
|
143
|
+
saveDir: '/Users/me/Downloads',
|
|
144
|
+
uris: ['https://cdn.example.com/releases/app-1.4.2-arm64.dmg'],
|
|
145
|
+
connections: 8,
|
|
146
|
+
})
|
|
147
|
+
console.log(task.id, task.status) // "t_01H…", "downloading"
|
|
148
|
+
|
|
149
|
+
// magnet 链接
|
|
150
|
+
await conn.sendRequest('download/add', {
|
|
151
|
+
kind: 'magnet',
|
|
152
|
+
saveDir: '/Users/me/Downloads',
|
|
153
|
+
uri: 'magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a',
|
|
154
|
+
})
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
> 只接受 `http`、`https`、`ftp`、`ftps`、`sftp` 协议的 URL —— schema 会在契约边界
|
|
158
|
+
> 直接 reject 掉 `file:`、`data:`、`javascript:`,因此 agent 绝无可能被诱导去读取
|
|
159
|
+
> 本地文件。
|
|
160
|
+
|
|
161
|
+
### 查询与控制 task(client → server)
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
const { tasks, total } = await conn.sendRequest('task/list', {
|
|
165
|
+
status: 'downloading',
|
|
166
|
+
limit: 20,
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
await conn.sendRequest('task/pause', { taskId: task.id })
|
|
170
|
+
await conn.sendRequest('task/resume', { taskId: task.id })
|
|
171
|
+
await conn.sendRequest('task/remove', { taskId: task.id, deleteFiles: false })
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### 解析页面(server → client)
|
|
175
|
+
|
|
176
|
+
桌面端会先问某个 client 能否处理这个页面(`url/probe`),再请它抽取出可下载的资源
|
|
177
|
+
(`url/resolve`)。client 通过注册 handler 来应答:
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
conn.onRequest('url/probe', async ({ url }) => ({
|
|
181
|
+
handled: /videos\.example\.com/.test(url),
|
|
182
|
+
adapterId: 'example-video',
|
|
183
|
+
confidence: 'high',
|
|
184
|
+
}))
|
|
185
|
+
|
|
186
|
+
conn.onRequest('url/resolve', async ({ url, preferences }) => ({
|
|
187
|
+
selections: [
|
|
188
|
+
{
|
|
189
|
+
kind: 'direct',
|
|
190
|
+
primary: {
|
|
191
|
+
url: 'https://cdn.example.com/v/abc123/1080p.mp4',
|
|
192
|
+
headers: {},
|
|
193
|
+
cookies: [],
|
|
194
|
+
refererPolicy: 'strict-origin-when-cross-origin',
|
|
195
|
+
},
|
|
196
|
+
container: 'mp4',
|
|
197
|
+
quality: preferences?.maxQuality ?? '1080p',
|
|
198
|
+
sizeBytes: 734_003_200,
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
meta: { title: 'Sample clip', author: 'example.com', durationSec: 372 },
|
|
202
|
+
extractedBy: {
|
|
203
|
+
adapterId: 'example-video',
|
|
204
|
+
adapterVersion: '1.0.0',
|
|
205
|
+
extractedAt: Date.now(),
|
|
206
|
+
},
|
|
207
|
+
}))
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
一个 `selection` 是按 `kind` 区分的 discriminated union:`direct`(单个文件)、
|
|
211
|
+
`hls`(一份 playlist),或 `mux`(分离的 video 与 audio 流,由 server 端合流)。
|
|
212
|
+
每个 `Resource` 都带有 server 端重新抓取时所需的 `headers`/`cookies`。
|
|
213
|
+
|
|
214
|
+
### 进度与生命周期(server → client)
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
conn.onNotification('$/task/progress', (p) => {
|
|
218
|
+
// p.phase: 'queued' | 'downloading' | 'muxing' | 'finalizing'
|
|
219
|
+
})
|
|
220
|
+
conn.onNotification('$/task/completed', (p) => {
|
|
221
|
+
console.log('完成 →', p.filePath, `(${p.durationMs} ms)`)
|
|
222
|
+
})
|
|
223
|
+
conn.onNotification('$/task/error', (p) => {
|
|
224
|
+
console.error(`task ${p.taskId} 失败:[${p.code}] ${p.message}`)
|
|
225
|
+
})
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### 取消
|
|
229
|
+
|
|
230
|
+
`sendRequest` 可接受一个可选的 `CancellationToken`。取消时会在 wire 上发出
|
|
231
|
+
`$/cancelRequest`(由 `vscode-jsonrpc` 处理);采用协作式取消的 handler 只需观察
|
|
232
|
+
`token.isCancellationRequested` 即可响应。
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
import { CancellationTokenSource } from 'vscode-jsonrpc'
|
|
236
|
+
|
|
237
|
+
const cts = new CancellationTokenSource()
|
|
238
|
+
const pending = conn.sendRequest('url/resolve', { url }, cts.token)
|
|
239
|
+
// …用户离开了页面:
|
|
240
|
+
cts.cancel()
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### 运行时校验
|
|
244
|
+
|
|
245
|
+
每个 wire 形态都配有 schema。在边界处先用 `safeParse` 校验不可信输入,通过后再执行:
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
import { DownloadAddParamsSchema } from '@motrix/mdxp'
|
|
249
|
+
|
|
250
|
+
const parsed = DownloadAddParamsSchema.safeParse(untrusted)
|
|
251
|
+
if (!parsed.success) {
|
|
252
|
+
// parsed.error —— 一个精确指出问题所在的 ZodError
|
|
253
|
+
return
|
|
254
|
+
}
|
|
255
|
+
await conn.sendRequest('download/add', parsed.data)
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Error 模型
|
|
259
|
+
|
|
260
|
+
在 handler 里用 `makeMdxpError` 返回结构化的错误。`code` 是 JSON-RPC error code;
|
|
261
|
+
`data` 则携带机器可读的 `appCode`、重试提示,以及任意自定义上下文。
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
import { ErrorCodes, makeMdxpError } from '@motrix/mdxp'
|
|
265
|
+
|
|
266
|
+
throw makeMdxpError(
|
|
267
|
+
ErrorCodes.ResourceUnavailable,
|
|
268
|
+
'The requested file is no longer available',
|
|
269
|
+
{ appCode: 'http.gone', retryable: false, context: { status: 410 } },
|
|
270
|
+
)
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
收到 code 后,可用 `isProtocolError(code)`(JSON-RPC 保留段)或 `isMotrixError(code)`
|
|
274
|
+
(Motrix 的 `-32001…-32099` 段)为它归类。
|
|
275
|
+
|
|
276
|
+
### AI-agent tool catalog
|
|
277
|
+
|
|
278
|
+
面向 agent 的那些 method 会被导出成一份 JSON-Schema tool catalog,可直接对接 LLM 的
|
|
279
|
+
function-calling / tool-use API:
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
import { toAgentToolCatalog } from '@motrix/mdxp'
|
|
283
|
+
|
|
284
|
+
const tools = toAgentToolCatalog()
|
|
285
|
+
// [
|
|
286
|
+
// { name: 'download/add', description, inputSchema: {…JSON Schema}, outputSchema },
|
|
287
|
+
// { name: 'task/list', … },
|
|
288
|
+
// …
|
|
289
|
+
// ]
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## API 参考
|
|
293
|
+
|
|
294
|
+
### 导出
|
|
295
|
+
|
|
296
|
+
| 导出 | 类型 | 作用 |
|
|
297
|
+
| --- | --- | --- |
|
|
298
|
+
| `createMdxpConnection(reader, writer)` | function | 把一对 reader/writer 封装成类型化的 `MdxpConnection`。 |
|
|
299
|
+
| `MdxpConnection` | type | 连接接口(`sendRequest`、`onRequest`、`sendNotification`、`onNotification`、`dispose`、`raw`)。 |
|
|
300
|
+
| `MdxpRequestMap` / `MdxpNotificationMap` | type | method / notification 名 → params/result 类型的映射表。 |
|
|
301
|
+
| `Methods` / `Notifications` | const | wire 名常量(`Methods.DownloadAdd === 'download/add'`)。 |
|
|
302
|
+
| `ErrorCodes` | const | JSON-RPC 与 Motrix 自定义的 error code。 |
|
|
303
|
+
| `makeMdxpError(code, msg, data?)` | function | 构造结构化的 `MdxpError`。 |
|
|
304
|
+
| `isProtocolError` / `isMotrixError` | function | 为 error code 归类。 |
|
|
305
|
+
| `Tools` | const | 每个 client→server method 的注册表 → `{ description, paramsSchema, resultSchema, agentFacing }`。 |
|
|
306
|
+
| `toAgentToolCatalog()` | function | 取 `agentFacing` 子集,转成 JSON-Schema tools。 |
|
|
307
|
+
| `SERVER_INITIATED_METHODS` | const | 由 server 向 client 发起的 method(`url/probe`、`url/resolve`)。 |
|
|
308
|
+
| `*Schema` | Zod schema | 全部 wire 形态,供运行时校验。 |
|
|
309
|
+
|
|
310
|
+
### Methods
|
|
311
|
+
|
|
312
|
+
| Method | 方向 | agent-facing | 作用 |
|
|
313
|
+
| --- | --- | :---: | --- |
|
|
314
|
+
| `motrix/initialize` | client → server | | 握手:协商 version、身份、capabilities。 |
|
|
315
|
+
| `system/ping` | client → server | | 存活探测;回显 `sentAt` 与 `recvAt`。 |
|
|
316
|
+
| `download/submit` | client → server | | 提交浏览器侦测到的 page 形态下载。 |
|
|
317
|
+
| `download/cancel` | client → server | | 按 task id 取消已提交的下载。 |
|
|
318
|
+
| `download/add` | client → server | ✓ | 按 URL / magnet / torrent 添加下载。 |
|
|
319
|
+
| `task/list` | client → server | ✓ | 列出 task,可过滤、可分页。 |
|
|
320
|
+
| `task/get` | client → server | ✓ | 按 id 取单个 task。 |
|
|
321
|
+
| `task/pause` · `task/resume` | client → server | ✓ | 暂停 / 恢复 task。 |
|
|
322
|
+
| `task/remove` | client → server | ✓ | 移除 task,可选一并删除文件。 |
|
|
323
|
+
| `stats/get` | client → server | ✓ | 取聚合的全局统计(速度 + 计数)。 |
|
|
324
|
+
| `engine/status` | client → server | ✓ | 取下载引擎的生命周期状态与 feature report。 |
|
|
325
|
+
| `url/probe` | **server → client** | | 该 client 的 adapter 能否处理某页面? |
|
|
326
|
+
| `url/resolve` | **server → client** | | 从页面中抽取可下载资源。 |
|
|
327
|
+
|
|
328
|
+
### Notifications
|
|
329
|
+
|
|
330
|
+
| Notification | 方向 | 载荷 |
|
|
331
|
+
| --- | --- | --- |
|
|
332
|
+
| `motrix/initialized` | client → server | 握手完成(无载荷)。 |
|
|
333
|
+
| `$/task/progress` | server → client | `bytesDone`、`bytesTotal`、`speedBps`、`etaSec`、`phase`。 |
|
|
334
|
+
| `$/task/completed` | server → client | `filePath`、`durationMs`。 |
|
|
335
|
+
| `$/task/error` | server → client | `code`、`message`。 |
|
|
336
|
+
| `$/stats` | server → client | 周期性推送的聚合统计。 |
|
|
337
|
+
| `$/pair/revoked` | server → client | 配对被撤销(`reason`)。 |
|
|
338
|
+
| `$/cancelRequest` | 双向 | 取消 —— 由 `vscode-jsonrpc` 处理。 |
|
|
339
|
+
|
|
340
|
+
### Error codes
|
|
341
|
+
|
|
342
|
+
| Code | 值 | 所属段 |
|
|
343
|
+
| --- | --- | --- |
|
|
344
|
+
| `ParseError` | `-32700` | JSON-RPC 保留 |
|
|
345
|
+
| `InvalidRequest` | `-32600` | JSON-RPC 保留 |
|
|
346
|
+
| `MethodNotFound` | `-32601` | JSON-RPC 保留 |
|
|
347
|
+
| `InvalidParams` | `-32602` | JSON-RPC 保留 |
|
|
348
|
+
| `InternalError` | `-32603` | JSON-RPC 保留 |
|
|
349
|
+
| `RequestCancelled` | `-32800` | LSP 扩展 |
|
|
350
|
+
| `AdapterError` | `-32001` | Motrix |
|
|
351
|
+
| `ResourceUnavailable` | `-32002` | Motrix |
|
|
352
|
+
| `PermissionDenied` | `-32003` | Motrix |
|
|
353
|
+
| `RateLimited` | `-32004` | Motrix |
|
|
354
|
+
| `CapabilityNotSupported` | `-32005` | Motrix |
|
|
355
|
+
| `PairRevoked` | `-32006` | Motrix |
|
|
356
|
+
|
|
357
|
+
## 协议说明
|
|
358
|
+
|
|
359
|
+
- **版本**:`protocolVersion` 为 `'1.0'`。这是 wire 的兼容性版本,与本包的 npm
|
|
360
|
+
version 相互独立。
|
|
361
|
+
- **不支持 batching**:JSON-RPC batching 被明确禁止 —— 一帧一消息。
|
|
362
|
+
- **forward-compatible**:result 与 notification 的载荷都是 non-strict 的 ——
|
|
363
|
+
较新的 server 可以新增字段,较旧的 client 忽略即可;未知的 method 会以
|
|
364
|
+
`MethodNotFound` 被拒绝,而不会拖垮整个 session。
|
|
365
|
+
|
|
366
|
+
## 设计原则
|
|
367
|
+
|
|
368
|
+
- **transport-agnostic** —— 本库不假定任何特定 transport;任何 `MessageReader`/
|
|
369
|
+
`MessageWriter` 双工流都能承载它。
|
|
370
|
+
- **schema-first** —— 先定义 Zod schema,再推断类型;绝不手写已有对应 schema 的类型。
|
|
371
|
+
- **forward-compatible** —— 对未知之物选择忽略,而非 reject。
|
|
372
|
+
|
|
373
|
+
## License
|
|
374
|
+
|
|
375
|
+
[MIT](./LICENSE) © Dr_rOot
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@motrix/mdxp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Motrix Download eXchange Protocol — JSON-RPC 2.0 wire types and connection helpers",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Motrix",
|
|
@@ -46,9 +46,10 @@
|
|
|
46
46
|
"dist",
|
|
47
47
|
"src",
|
|
48
48
|
"!src/__tests__",
|
|
49
|
-
"README.md"
|
|
49
|
+
"README.md",
|
|
50
|
+
"README.zh-CN.md"
|
|
50
51
|
],
|
|
51
|
-
"packageManager": "pnpm@11.
|
|
52
|
+
"packageManager": "pnpm@11.13.0",
|
|
52
53
|
"publishConfig": {
|
|
53
54
|
"access": "public"
|
|
54
55
|
},
|