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