@motrix/mdxp 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.
- package/LICENSE +9 -0
- package/README.md +103 -0
- package/dist/browser.d.ts +3 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +14 -0
- package/dist/browser.js.map +1 -0
- package/dist/connection.d.ts +50 -0
- package/dist/connection.d.ts.map +1 -0
- package/dist/connection.js +38 -0
- package/dist/connection.js.map +1 -0
- package/dist/errors.d.ts +33 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +34 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/methods.d.ts +28 -0
- package/dist/methods.d.ts.map +1 -0
- package/dist/methods.js +32 -0
- package/dist/methods.js.map +1 -0
- package/dist/node.d.ts +3 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +15 -0
- package/dist/node.js.map +1 -0
- package/dist/schemas/download-add.d.ts +72 -0
- package/dist/schemas/download-add.d.ts.map +1 -0
- package/dist/schemas/download-add.js +49 -0
- package/dist/schemas/download-add.js.map +1 -0
- package/dist/schemas/download.d.ts +156 -0
- package/dist/schemas/download.d.ts.map +1 -0
- package/dist/schemas/download.js +34 -0
- package/dist/schemas/download.js.map +1 -0
- package/dist/schemas/engine.d.ts +41 -0
- package/dist/schemas/engine.d.ts.map +1 -0
- package/dist/schemas/engine.js +24 -0
- package/dist/schemas/engine.js.map +1 -0
- package/dist/schemas/events.d.ts +36 -0
- package/dist/schemas/events.d.ts.map +1 -0
- package/dist/schemas/events.js +26 -0
- package/dist/schemas/events.js.map +1 -0
- package/dist/schemas/index.d.ts +12 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +12 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/initialize.d.ts +70 -0
- package/dist/schemas/initialize.d.ts.map +1 -0
- package/dist/schemas/initialize.js +72 -0
- package/dist/schemas/initialize.js.map +1 -0
- package/dist/schemas/resource.d.ts +39 -0
- package/dist/schemas/resource.d.ts.map +1 -0
- package/dist/schemas/resource.js +21 -0
- package/dist/schemas/resource.js.map +1 -0
- package/dist/schemas/selection.d.ts +129 -0
- package/dist/schemas/selection.d.ts.map +1 -0
- package/dist/schemas/selection.js +30 -0
- package/dist/schemas/selection.js.map +1 -0
- package/dist/schemas/stats.d.ts +18 -0
- package/dist/schemas/stats.d.ts.map +1 -0
- package/dist/schemas/stats.js +14 -0
- package/dist/schemas/stats.js.map +1 -0
- package/dist/schemas/system.d.ts +11 -0
- package/dist/schemas/system.d.ts.map +1 -0
- package/dist/schemas/system.js +9 -0
- package/dist/schemas/system.js.map +1 -0
- package/dist/schemas/task.d.ts +202 -0
- package/dist/schemas/task.d.ts.map +1 -0
- package/dist/schemas/task.js +90 -0
- package/dist/schemas/task.js.map +1 -0
- package/dist/schemas/url.d.ts +162 -0
- package/dist/schemas/url.d.ts.map +1 -0
- package/dist/schemas/url.js +73 -0
- package/dist/schemas/url.js.map +1 -0
- package/dist/tools.d.ts +35 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +124 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +73 -0
- package/src/browser.ts +14 -0
- package/src/connection.ts +162 -0
- package/src/errors.ts +60 -0
- package/src/index.ts +31 -0
- package/src/methods.ts +36 -0
- package/src/node.ts +15 -0
- package/src/schemas/download-add.ts +55 -0
- package/src/schemas/download.ts +42 -0
- package/src/schemas/engine.ts +32 -0
- package/src/schemas/events.ts +36 -0
- package/src/schemas/index.ts +11 -0
- package/src/schemas/initialize.ts +88 -0
- package/src/schemas/resource.ts +26 -0
- package/src/schemas/selection.ts +32 -0
- package/src/schemas/stats.ts +20 -0
- package/src/schemas/system.ts +13 -0
- package/src/schemas/task.ts +113 -0
- package/src/schemas/url.ts +86 -0
- package/src/tools.ts +175 -0
- package/src/types.ts +63 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CancellationToken,
|
|
3
|
+
MessageConnection,
|
|
4
|
+
MessageReader,
|
|
5
|
+
MessageWriter,
|
|
6
|
+
} from 'vscode-jsonrpc'
|
|
7
|
+
import { createMessageConnection } from 'vscode-jsonrpc'
|
|
8
|
+
import type {
|
|
9
|
+
CancelRequestParams,
|
|
10
|
+
DownloadAddParams,
|
|
11
|
+
DownloadAddResult,
|
|
12
|
+
DownloadCancelParams,
|
|
13
|
+
DownloadCancelResult,
|
|
14
|
+
DownloadSubmitParams,
|
|
15
|
+
DownloadSubmitResult,
|
|
16
|
+
EngineStatusParams,
|
|
17
|
+
EngineStatusResult,
|
|
18
|
+
InitializeParams,
|
|
19
|
+
InitializeResult,
|
|
20
|
+
PairRevokedParams,
|
|
21
|
+
StatsGetParams,
|
|
22
|
+
StatsResult,
|
|
23
|
+
StatsUpdateParams,
|
|
24
|
+
SystemPingParams,
|
|
25
|
+
SystemPingResult,
|
|
26
|
+
TaskCompletedParams,
|
|
27
|
+
TaskErrorParams,
|
|
28
|
+
TaskGetParams,
|
|
29
|
+
TaskGetResult,
|
|
30
|
+
TaskListParams,
|
|
31
|
+
TaskListResult,
|
|
32
|
+
TaskPauseParams,
|
|
33
|
+
TaskPauseResult,
|
|
34
|
+
TaskProgressParams,
|
|
35
|
+
TaskRemoveParams,
|
|
36
|
+
TaskRemoveResult,
|
|
37
|
+
TaskResumeParams,
|
|
38
|
+
TaskResumeResult,
|
|
39
|
+
UrlProbeParams,
|
|
40
|
+
UrlProbeResult,
|
|
41
|
+
UrlResolveParams,
|
|
42
|
+
UrlResolveResult,
|
|
43
|
+
} from './types.js'
|
|
44
|
+
|
|
45
|
+
// Maps method name → [params type, result type]
|
|
46
|
+
export interface MdxpRequestMap {
|
|
47
|
+
'motrix/initialize': [InitializeParams, InitializeResult]
|
|
48
|
+
'download/submit': [DownloadSubmitParams, DownloadSubmitResult]
|
|
49
|
+
'download/cancel': [DownloadCancelParams, DownloadCancelResult]
|
|
50
|
+
'url/probe': [UrlProbeParams, UrlProbeResult]
|
|
51
|
+
'url/resolve': [UrlResolveParams, UrlResolveResult]
|
|
52
|
+
'system/ping': [SystemPingParams, SystemPingResult]
|
|
53
|
+
// v1 control-plane (Spec 2)
|
|
54
|
+
'download/add': [DownloadAddParams, DownloadAddResult]
|
|
55
|
+
'task/list': [TaskListParams, TaskListResult]
|
|
56
|
+
'task/get': [TaskGetParams, TaskGetResult]
|
|
57
|
+
'task/pause': [TaskPauseParams, TaskPauseResult]
|
|
58
|
+
'task/resume': [TaskResumeParams, TaskResumeResult]
|
|
59
|
+
'task/remove': [TaskRemoveParams, TaskRemoveResult]
|
|
60
|
+
'stats/get': [StatsGetParams, StatsResult]
|
|
61
|
+
'engine/status': [EngineStatusParams, EngineStatusResult]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Maps notification name → params type
|
|
65
|
+
export interface MdxpNotificationMap {
|
|
66
|
+
'motrix/initialized': undefined
|
|
67
|
+
'$/task/progress': TaskProgressParams
|
|
68
|
+
'$/task/completed': TaskCompletedParams
|
|
69
|
+
'$/task/error': TaskErrorParams
|
|
70
|
+
'$/pair/revoked': PairRevokedParams
|
|
71
|
+
'$/cancelRequest': CancelRequestParams
|
|
72
|
+
'$/stats': StatsUpdateParams
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface MdxpConnection {
|
|
76
|
+
/** Begin processing incoming messages. Call AFTER attaching handlers. */
|
|
77
|
+
listen(): void
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Send a request. Optionally pass a `CancellationToken` so the caller can
|
|
81
|
+
* abort an in-flight request — vscode-jsonrpc emits `$/cancelRequest`
|
|
82
|
+
* automatically per spec §9.
|
|
83
|
+
*/
|
|
84
|
+
sendRequest<M extends keyof MdxpRequestMap>(
|
|
85
|
+
method: M,
|
|
86
|
+
params: MdxpRequestMap[M][0],
|
|
87
|
+
token?: CancellationToken
|
|
88
|
+
): Promise<MdxpRequestMap[M][1]>
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Handler can accept an optional `CancellationToken`. vscode-jsonrpc wires
|
|
92
|
+
* it to the underlying `$/cancelRequest` notification — the handler MUST
|
|
93
|
+
* observe `token.isCancellationRequested` for cooperative cancellation.
|
|
94
|
+
*/
|
|
95
|
+
onRequest<M extends keyof MdxpRequestMap>(
|
|
96
|
+
method: M,
|
|
97
|
+
handler: (
|
|
98
|
+
params: MdxpRequestMap[M][0],
|
|
99
|
+
token: CancellationToken
|
|
100
|
+
) => MdxpRequestMap[M][1] | Promise<MdxpRequestMap[M][1]>
|
|
101
|
+
): void
|
|
102
|
+
|
|
103
|
+
sendNotification<N extends keyof MdxpNotificationMap>(
|
|
104
|
+
name: N,
|
|
105
|
+
params: MdxpNotificationMap[N]
|
|
106
|
+
): void
|
|
107
|
+
|
|
108
|
+
onNotification<N extends keyof MdxpNotificationMap>(
|
|
109
|
+
name: N,
|
|
110
|
+
handler: (params: MdxpNotificationMap[N]) => void
|
|
111
|
+
): void
|
|
112
|
+
|
|
113
|
+
dispose(): void
|
|
114
|
+
|
|
115
|
+
/** Escape hatch: access the underlying vscode-jsonrpc connection. */
|
|
116
|
+
readonly raw: MessageConnection
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function createMdxpConnection(
|
|
120
|
+
reader: MessageReader,
|
|
121
|
+
writer: MessageWriter
|
|
122
|
+
): MdxpConnection {
|
|
123
|
+
const raw = createMessageConnection(reader, writer)
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
listen: () => raw.listen(),
|
|
127
|
+
// Promise<never> is assignable to all Promise<X>, satisfying the generic
|
|
128
|
+
// constraint for all M simultaneously at the implementation site.
|
|
129
|
+
sendRequest: (method, params, token) =>
|
|
130
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
131
|
+
(token === undefined
|
|
132
|
+
? raw.sendRequest(method as string, params)
|
|
133
|
+
: raw.sendRequest(method as string, params, token)) as Promise<never>,
|
|
134
|
+
onRequest: (method, handler) => {
|
|
135
|
+
// vscode-jsonrpc v9 tightened GenericRequestHandler/HandlerResult, so a
|
|
136
|
+
// generically-typed handler is no longer directly assignable. The runtime
|
|
137
|
+
// function is correct for every M; cast the whole handler via `never`
|
|
138
|
+
// (matching the `as never` escape hatches used elsewhere in this file).
|
|
139
|
+
raw.onRequest(
|
|
140
|
+
method as string,
|
|
141
|
+
((params: unknown, token: CancellationToken) =>
|
|
142
|
+
handler(params as never, token)) as never
|
|
143
|
+
)
|
|
144
|
+
},
|
|
145
|
+
sendNotification: (name, params) => {
|
|
146
|
+
if (params === undefined) {
|
|
147
|
+
raw.sendNotification(name as string)
|
|
148
|
+
} else {
|
|
149
|
+
raw.sendNotification(name as string, params)
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
onNotification: (name, handler) => {
|
|
153
|
+
raw.onNotification(name as string, (params: unknown) =>
|
|
154
|
+
// Cast via `never`: handler accepts the concrete notification params
|
|
155
|
+
// type for N, but raw.onNotification gives us `unknown`.
|
|
156
|
+
handler(params as never)
|
|
157
|
+
)
|
|
158
|
+
},
|
|
159
|
+
dispose: () => raw.dispose(),
|
|
160
|
+
raw,
|
|
161
|
+
}
|
|
162
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// MDXP error code model — see docs/01-protocol-mdxp.md §8
|
|
2
|
+
|
|
3
|
+
export const ErrorCodes = Object.freeze({
|
|
4
|
+
// JSON-RPC 2.0 reserved (-32768 to -32000)
|
|
5
|
+
ParseError: -32700,
|
|
6
|
+
InvalidRequest: -32600,
|
|
7
|
+
MethodNotFound: -32601,
|
|
8
|
+
InvalidParams: -32602,
|
|
9
|
+
InternalError: -32603,
|
|
10
|
+
RequestCancelled: -32800, // LSP extension
|
|
11
|
+
|
|
12
|
+
// Motrix-defined (-32000 to -32099, JSON-RPC server-implementation reserved)
|
|
13
|
+
AdapterError: -32001,
|
|
14
|
+
ResourceUnavailable: -32002,
|
|
15
|
+
PermissionDenied: -32003,
|
|
16
|
+
RateLimited: -32004,
|
|
17
|
+
CapabilityNotSupported: -32005,
|
|
18
|
+
PairRevoked: -32006,
|
|
19
|
+
} as const)
|
|
20
|
+
|
|
21
|
+
export type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes]
|
|
22
|
+
|
|
23
|
+
/** True for JSON-RPC 2.0 reserved codes (parse/invalid/method/internal/cancel) */
|
|
24
|
+
export function isProtocolError(code: number): boolean {
|
|
25
|
+
return (
|
|
26
|
+
code === ErrorCodes.ParseError ||
|
|
27
|
+
code === ErrorCodes.InvalidRequest ||
|
|
28
|
+
code === ErrorCodes.MethodNotFound ||
|
|
29
|
+
code === ErrorCodes.InvalidParams ||
|
|
30
|
+
code === ErrorCodes.InternalError ||
|
|
31
|
+
code === ErrorCodes.RequestCancelled
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** True for codes in Motrix's -32000..-32099 reserved range */
|
|
36
|
+
export function isMotrixError(code: number): boolean {
|
|
37
|
+
return code <= -32000 && code >= -32099
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface MdxpErrorData {
|
|
41
|
+
appCode?: string
|
|
42
|
+
localizedMessage?: string
|
|
43
|
+
retryable?: boolean
|
|
44
|
+
retryAfterSec?: number
|
|
45
|
+
context?: Record<string, unknown>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface MdxpError {
|
|
49
|
+
code: number
|
|
50
|
+
message: string
|
|
51
|
+
data?: MdxpErrorData
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function makeMdxpError(
|
|
55
|
+
code: number,
|
|
56
|
+
message: string,
|
|
57
|
+
data?: MdxpErrorData
|
|
58
|
+
): MdxpError {
|
|
59
|
+
return data === undefined ? { code, message } : { code, message, data }
|
|
60
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// MDXP — Motrix Download eXchange Protocol
|
|
2
|
+
// Public API surface for both Motrix desktop and the browser extension.
|
|
3
|
+
|
|
4
|
+
export type {
|
|
5
|
+
MdxpConnection,
|
|
6
|
+
MdxpNotificationMap,
|
|
7
|
+
MdxpRequestMap,
|
|
8
|
+
} from './connection.js'
|
|
9
|
+
export { createMdxpConnection } from './connection.js'
|
|
10
|
+
export type { ErrorCode, MdxpError, MdxpErrorData } from './errors.js'
|
|
11
|
+
export {
|
|
12
|
+
ErrorCodes,
|
|
13
|
+
isMotrixError,
|
|
14
|
+
isProtocolError,
|
|
15
|
+
makeMdxpError,
|
|
16
|
+
} from './errors.js'
|
|
17
|
+
export type { Method, Notification } from './methods.js'
|
|
18
|
+
export { Methods, Notifications } from './methods.js'
|
|
19
|
+
|
|
20
|
+
// Schemas (for callers that want runtime validation)
|
|
21
|
+
export * from './schemas/index.js'
|
|
22
|
+
export type { AgentTool, MdxpToolDef } from './tools.js'
|
|
23
|
+
|
|
24
|
+
// Tool registry / AI-agent tool catalog
|
|
25
|
+
export {
|
|
26
|
+
SERVER_INITIATED_METHODS,
|
|
27
|
+
Tools,
|
|
28
|
+
toAgentToolCatalog,
|
|
29
|
+
} from './tools.js'
|
|
30
|
+
// Types (re-exported from schemas via types barrel)
|
|
31
|
+
export type * from './types.js'
|
package/src/methods.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// MDXP method names (request/response) — see docs/01-protocol-mdxp.md §6
|
|
2
|
+
export const Methods = Object.freeze({
|
|
3
|
+
MotrixInitialize: 'motrix/initialize',
|
|
4
|
+
DownloadSubmit: 'download/submit',
|
|
5
|
+
DownloadCancel: 'download/cancel',
|
|
6
|
+
UrlProbe: 'url/probe',
|
|
7
|
+
UrlResolve: 'url/resolve',
|
|
8
|
+
SystemPing: 'system/ping',
|
|
9
|
+
// v1 control-plane (Spec 2)
|
|
10
|
+
DownloadAdd: 'download/add',
|
|
11
|
+
TaskList: 'task/list',
|
|
12
|
+
TaskGet: 'task/get',
|
|
13
|
+
TaskPause: 'task/pause',
|
|
14
|
+
TaskResume: 'task/resume',
|
|
15
|
+
TaskRemove: 'task/remove',
|
|
16
|
+
StatsGet: 'stats/get',
|
|
17
|
+
EngineStatus: 'engine/status',
|
|
18
|
+
} as const)
|
|
19
|
+
|
|
20
|
+
export type Method = (typeof Methods)[keyof typeof Methods]
|
|
21
|
+
|
|
22
|
+
// MDXP notification names — see docs/01-protocol-mdxp.md §7.
|
|
23
|
+
// `motrix/initialized` follows LSP convention (no $/ prefix because it's
|
|
24
|
+
// a one-way handshake, not a protocol meta-event).
|
|
25
|
+
export const Notifications = Object.freeze({
|
|
26
|
+
MotrixInitialized: 'motrix/initialized',
|
|
27
|
+
TaskProgress: '$/task/progress',
|
|
28
|
+
TaskCompleted: '$/task/completed',
|
|
29
|
+
TaskError: '$/task/error',
|
|
30
|
+
PairRevoked: '$/pair/revoked',
|
|
31
|
+
CancelRequest: '$/cancelRequest',
|
|
32
|
+
// v1 control-plane (Spec 2): periodic aggregate stats push.
|
|
33
|
+
StatsUpdate: '$/stats',
|
|
34
|
+
} as const)
|
|
35
|
+
|
|
36
|
+
export type Notification = (typeof Notifications)[keyof typeof Notifications]
|
package/src/node.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Node platform entry.
|
|
2
|
+
//
|
|
3
|
+
// vscode-jsonrpc v9 split the runtime abstraction layer (RAL) out of the bare
|
|
4
|
+
// `vscode-jsonrpc` entry: creating/listening on a `MessageConnection` now
|
|
5
|
+
// requires a platform RAL to be installed first. Importing this module installs
|
|
6
|
+
// the Node RAL into the SAME `vscode-jsonrpc` instance that this package's
|
|
7
|
+
// `createMdxpConnection` resolves, so it is immune to duplicate-instance issues
|
|
8
|
+
// across linked workspaces.
|
|
9
|
+
//
|
|
10
|
+
// A Node host (e.g. Motrix Turbo's bridge) only needs `import '@motrix/mdxp/node'`
|
|
11
|
+
// once at bootstrap. The full public API is re-exported for convenience, so
|
|
12
|
+
// `import { createMdxpConnection } from '@motrix/mdxp/node'` also works.
|
|
13
|
+
import 'vscode-jsonrpc/node'
|
|
14
|
+
|
|
15
|
+
export * from './index.js'
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { MdxpTaskSchema } from './task.js'
|
|
3
|
+
|
|
4
|
+
const HttpHeaderSchema = z.object({ name: z.string(), value: z.string() })
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Network download schemes allowed on the public, agent-facing `download/add`
|
|
8
|
+
* surface. Pinned at the contract boundary so an LLM-callable tool can never
|
|
9
|
+
* be coerced into `file://` (local file read / path traversal), `javascript:`,
|
|
10
|
+
* `data:`, etc. — independent of whatever the host handler does later.
|
|
11
|
+
*/
|
|
12
|
+
const SAFE_DOWNLOAD_URL = /^(https?|ftps?|sftp):\/\//i
|
|
13
|
+
const safeDownloadUrl = z
|
|
14
|
+
.string()
|
|
15
|
+
.url()
|
|
16
|
+
.refine((u) => SAFE_DOWNLOAD_URL.test(u), {
|
|
17
|
+
message: 'unsupported URL scheme (allowed: http, https, ftp, ftps, sftp)',
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* URL-shaped public "add a download" params — distinct from the browser-page
|
|
22
|
+
* `download/submit`. Discriminated on `kind`; strict variants so an agent's
|
|
23
|
+
* typo is loudly rejected rather than silently dropped. The host maps this onto
|
|
24
|
+
* its native taskCreateRequest in Spec 4.
|
|
25
|
+
*/
|
|
26
|
+
export const DownloadAddParamsSchema = z.discriminatedUnion('kind', [
|
|
27
|
+
z.strictObject({
|
|
28
|
+
kind: z.literal('url'),
|
|
29
|
+
saveDir: z.string().min(1),
|
|
30
|
+
uris: z.array(safeDownloadUrl).min(1),
|
|
31
|
+
filename: z.string().optional(),
|
|
32
|
+
headers: z.array(HttpHeaderSchema).optional(),
|
|
33
|
+
connections: z.number().int().min(1).max(128).optional(),
|
|
34
|
+
proxy: z.string().optional(),
|
|
35
|
+
}),
|
|
36
|
+
z.strictObject({
|
|
37
|
+
kind: z.literal('magnet'),
|
|
38
|
+
saveDir: z.string().min(1),
|
|
39
|
+
uri: z.string().startsWith('magnet:?'),
|
|
40
|
+
selectedFiles: z.array(z.number().int().nonnegative()).optional(),
|
|
41
|
+
}),
|
|
42
|
+
z.strictObject({
|
|
43
|
+
kind: z.literal('torrent'),
|
|
44
|
+
saveDir: z.string().min(1),
|
|
45
|
+
base64: z.string().min(1),
|
|
46
|
+
selectedFiles: z.array(z.number().int().nonnegative()).optional(),
|
|
47
|
+
displayName: z.string().optional(),
|
|
48
|
+
}),
|
|
49
|
+
])
|
|
50
|
+
|
|
51
|
+
/** Result is the created task snapshot so callers render without polling. */
|
|
52
|
+
export const DownloadAddResultSchema = MdxpTaskSchema
|
|
53
|
+
|
|
54
|
+
export type DownloadAddParams = z.infer<typeof DownloadAddParamsSchema>
|
|
55
|
+
export type DownloadAddResult = z.infer<typeof DownloadAddResultSchema>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { SelectionSchema } from './selection.js'
|
|
3
|
+
|
|
4
|
+
export const DownloadSubmitParamsSchema = z.object({
|
|
5
|
+
source: z.object({
|
|
6
|
+
pageUrl: z.string().url(),
|
|
7
|
+
pageTitle: z.string().max(500),
|
|
8
|
+
detectedAt: z.number(),
|
|
9
|
+
siteHint: z.string().max(64).optional(),
|
|
10
|
+
}),
|
|
11
|
+
selection: SelectionSchema,
|
|
12
|
+
meta: z.object({
|
|
13
|
+
suggestedFilename: z.string().max(255),
|
|
14
|
+
qualityLabel: z.string().max(64),
|
|
15
|
+
estimatedBytes: z.number().nonnegative().optional(),
|
|
16
|
+
durationSec: z.number().nonnegative().optional(),
|
|
17
|
+
}),
|
|
18
|
+
/**
|
|
19
|
+
* Client-generated key identifying ONE logical submit. Motrix dedups on
|
|
20
|
+
* (session, key): a retransmit of the same submission — lost response,
|
|
21
|
+
* reconnect replay — returns the original task instead of creating a
|
|
22
|
+
* duplicate. Optional for backward compatibility.
|
|
23
|
+
*/
|
|
24
|
+
idempotencyKey: z.string().min(8).max(128).optional(),
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
export const DownloadSubmitResultSchema = z.object({
|
|
28
|
+
taskId: z.string().min(1),
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
export const DownloadCancelParamsSchema = z.object({
|
|
32
|
+
taskId: z.string().min(1),
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
export const DownloadCancelResultSchema = z.object({
|
|
36
|
+
ok: z.literal(true),
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
export type DownloadSubmitParams = z.infer<typeof DownloadSubmitParamsSchema>
|
|
40
|
+
export type DownloadSubmitResult = z.infer<typeof DownloadSubmitResultSchema>
|
|
41
|
+
export type DownloadCancelParams = z.infer<typeof DownloadCancelParamsSchema>
|
|
42
|
+
export type DownloadCancelResult = z.infer<typeof DownloadCancelResultSchema>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
export const EngineStateSchema = z.enum([
|
|
4
|
+
'stopped',
|
|
5
|
+
'starting',
|
|
6
|
+
'ready',
|
|
7
|
+
'restarting',
|
|
8
|
+
'failed',
|
|
9
|
+
])
|
|
10
|
+
|
|
11
|
+
/** Capability-discovery info; all fields are external-safe. `features` is the
|
|
12
|
+
* raw engine feature-string list (e.g. contains 'SQLite3-Persistence'). */
|
|
13
|
+
export const EngineFeatureReportSchema = z.object({
|
|
14
|
+
version: z.string(),
|
|
15
|
+
features: z.array(z.string()),
|
|
16
|
+
hasBtSeedUnverified: z.boolean(),
|
|
17
|
+
hasBtSaveMetadata: z.boolean(),
|
|
18
|
+
hasMoveStorage: z.boolean(),
|
|
19
|
+
hasSqlitePersistence: z.boolean(),
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
export const EngineStatusParamsSchema = z.strictObject({})
|
|
23
|
+
|
|
24
|
+
export const EngineStatusResultSchema = z.object({
|
|
25
|
+
state: EngineStateSchema,
|
|
26
|
+
featureReport: EngineFeatureReportSchema.nullable(),
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
export type EngineState = z.infer<typeof EngineStateSchema>
|
|
30
|
+
export type EngineFeatureReport = z.infer<typeof EngineFeatureReportSchema>
|
|
31
|
+
export type EngineStatusParams = z.infer<typeof EngineStatusParamsSchema>
|
|
32
|
+
export type EngineStatusResult = z.infer<typeof EngineStatusResultSchema>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
export const TaskProgressParamsSchema = z.object({
|
|
4
|
+
taskId: z.string().min(1),
|
|
5
|
+
bytesDone: z.number().nonnegative(),
|
|
6
|
+
bytesTotal: z.number().nonnegative().nullable(),
|
|
7
|
+
speedBps: z.number().nonnegative(),
|
|
8
|
+
etaSec: z.number().nonnegative().nullable(),
|
|
9
|
+
phase: z.enum(['queued', 'downloading', 'muxing', 'finalizing']),
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
export const TaskCompletedParamsSchema = z.object({
|
|
13
|
+
taskId: z.string().min(1),
|
|
14
|
+
filePath: z.string().min(1),
|
|
15
|
+
durationMs: z.number().nonnegative(),
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
export const TaskErrorParamsSchema = z.object({
|
|
19
|
+
taskId: z.string().min(1),
|
|
20
|
+
code: z.string(),
|
|
21
|
+
message: z.string(),
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
export const PairRevokedParamsSchema = z.object({
|
|
25
|
+
reason: z.string(),
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
export const CancelRequestParamsSchema = z.object({
|
|
29
|
+
id: z.string().min(1),
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
export type TaskProgressParams = z.infer<typeof TaskProgressParamsSchema>
|
|
33
|
+
export type TaskCompletedParams = z.infer<typeof TaskCompletedParamsSchema>
|
|
34
|
+
export type TaskErrorParams = z.infer<typeof TaskErrorParamsSchema>
|
|
35
|
+
export type PairRevokedParams = z.infer<typeof PairRevokedParamsSchema>
|
|
36
|
+
export type CancelRequestParams = z.infer<typeof CancelRequestParamsSchema>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from './download.js'
|
|
2
|
+
export * from './download-add.js'
|
|
3
|
+
export * from './engine.js'
|
|
4
|
+
export * from './events.js'
|
|
5
|
+
export * from './initialize.js'
|
|
6
|
+
export * from './resource.js'
|
|
7
|
+
export * from './selection.js'
|
|
8
|
+
export * from './stats.js'
|
|
9
|
+
export * from './system.js'
|
|
10
|
+
export * from './task.js'
|
|
11
|
+
export * from './url.js'
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
const PROTOCOL_VERSION = '1.0'
|
|
4
|
+
|
|
5
|
+
const AdapterDeclSchema = z.object({
|
|
6
|
+
id: z.string().min(1),
|
|
7
|
+
version: z.string().min(1),
|
|
8
|
+
urlPatterns: z.array(z.string()),
|
|
9
|
+
capabilities: z.array(z.enum(['resolve', 'sniff', 'batch'])),
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
const ClientCapabilitiesSchema = z.object({
|
|
13
|
+
submitDownload: z.boolean().optional(),
|
|
14
|
+
resolveUrl: z.boolean().optional(),
|
|
15
|
+
probeUrl: z.boolean().optional(),
|
|
16
|
+
cancellation: z.boolean().optional(),
|
|
17
|
+
progress: z.boolean().optional(),
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const ExtensionClientSchema = z.object({
|
|
21
|
+
kind: z.literal('extension'),
|
|
22
|
+
name: z.string().min(1),
|
|
23
|
+
version: z.string().min(1),
|
|
24
|
+
extensionId: z.string().min(1),
|
|
25
|
+
browser: z.enum(['chromium', 'firefox']),
|
|
26
|
+
browserVersion: z.string().min(1),
|
|
27
|
+
locale: z.string().min(1),
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const CliClientSchema = z.object({
|
|
31
|
+
kind: z.literal('cli'),
|
|
32
|
+
name: z.string().min(1),
|
|
33
|
+
version: z.string().min(1),
|
|
34
|
+
locale: z.string().min(1).optional(),
|
|
35
|
+
pid: z.number().int().optional(),
|
|
36
|
+
host: z.string().optional(),
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Client identity, generalized to a `kind` discriminated union so non-extension
|
|
41
|
+
* clients (CLI/agents) can connect. Compat shim: the shipped extension sends NO
|
|
42
|
+
* `kind` field, so inject `kind: 'extension'` when absent before discriminating
|
|
43
|
+
* — keeping `PROTOCOL_VERSION` '1.0' and the existing extension payload valid.
|
|
44
|
+
*/
|
|
45
|
+
const ClientSchema = z.preprocess(
|
|
46
|
+
(value) =>
|
|
47
|
+
value &&
|
|
48
|
+
typeof value === 'object' &&
|
|
49
|
+
!Array.isArray(value) &&
|
|
50
|
+
(value as { kind?: unknown }).kind === undefined
|
|
51
|
+
? { ...(value as Record<string, unknown>), kind: 'extension' }
|
|
52
|
+
: value,
|
|
53
|
+
z.discriminatedUnion('kind', [ExtensionClientSchema, CliClientSchema])
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
export const InitializeParamsSchema = z.object({
|
|
57
|
+
protocolVersion: z.literal(PROTOCOL_VERSION),
|
|
58
|
+
client: ClientSchema,
|
|
59
|
+
capabilities: ClientCapabilitiesSchema,
|
|
60
|
+
adapters: z.array(AdapterDeclSchema),
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
const ServerAdapterSchema = z.object({
|
|
64
|
+
id: z.string().min(1),
|
|
65
|
+
urlPatterns: z.array(z.string()),
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const ServerCapabilitiesSchema = z.object({
|
|
69
|
+
ffmpegAvailable: z.boolean(),
|
|
70
|
+
selectionKinds: z.array(z.enum(['direct', 'hls', 'dash', 'mux'])),
|
|
71
|
+
progress: z.boolean(),
|
|
72
|
+
cancellation: z.boolean(),
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
export const InitializeResultSchema = z.object({
|
|
76
|
+
protocolVersion: z.literal(PROTOCOL_VERSION),
|
|
77
|
+
server: z.object({
|
|
78
|
+
name: z.string().min(1),
|
|
79
|
+
version: z.string().min(1),
|
|
80
|
+
runtime: z.enum(['electron', 'server']),
|
|
81
|
+
}),
|
|
82
|
+
capabilities: ServerCapabilitiesSchema,
|
|
83
|
+
serverAdapters: z.array(ServerAdapterSchema),
|
|
84
|
+
pairToken: z.string().optional(),
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
export type InitializeParams = z.infer<typeof InitializeParamsSchema>
|
|
88
|
+
export type InitializeResult = z.infer<typeof InitializeResultSchema>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
const HttpUrl = z.httpUrl({ message: 'URL must be http: or https:' })
|
|
4
|
+
|
|
5
|
+
export const CookieSchema = z.object({
|
|
6
|
+
name: z.string(),
|
|
7
|
+
value: z.string(),
|
|
8
|
+
domain: z.string(),
|
|
9
|
+
path: z.string().default('/'),
|
|
10
|
+
secure: z.boolean().default(false),
|
|
11
|
+
httpOnly: z.boolean().default(false),
|
|
12
|
+
sameSite: z
|
|
13
|
+
.enum(['strict', 'lax', 'none', 'unspecified'])
|
|
14
|
+
.default('unspecified'),
|
|
15
|
+
expiresAt: z.number().optional(),
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
export const ResourceSchema = z.object({
|
|
19
|
+
url: HttpUrl,
|
|
20
|
+
headers: z.record(z.string(), z.string()).default({}),
|
|
21
|
+
cookies: z.array(CookieSchema).default([]),
|
|
22
|
+
refererPolicy: z.string().default('strict-origin-when-cross-origin'),
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
export type Resource = z.infer<typeof ResourceSchema>
|
|
26
|
+
export type Cookie = z.infer<typeof CookieSchema>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { ResourceSchema } from './resource.js'
|
|
3
|
+
|
|
4
|
+
export const SelectionSchema = z.discriminatedUnion('kind', [
|
|
5
|
+
z.object({
|
|
6
|
+
kind: z.literal('direct'),
|
|
7
|
+
primary: ResourceSchema,
|
|
8
|
+
container: z.string().optional(),
|
|
9
|
+
}),
|
|
10
|
+
z.object({
|
|
11
|
+
kind: z.literal('hls'),
|
|
12
|
+
primary: ResourceSchema,
|
|
13
|
+
container: z.enum(['mp4', 'mkv', 'ts']).default('mp4'),
|
|
14
|
+
}),
|
|
15
|
+
z.object({
|
|
16
|
+
kind: z.literal('dash'),
|
|
17
|
+
primary: ResourceSchema,
|
|
18
|
+
container: z.enum(['mp4', 'mkv']).default('mp4'),
|
|
19
|
+
}),
|
|
20
|
+
z.object({
|
|
21
|
+
kind: z.literal('mux'),
|
|
22
|
+
video: ResourceSchema,
|
|
23
|
+
audio: ResourceSchema,
|
|
24
|
+
container: z.enum(['mp4', 'mkv']).default('mp4'),
|
|
25
|
+
}),
|
|
26
|
+
z.object({
|
|
27
|
+
kind: z.literal('magnet'),
|
|
28
|
+
uri: z.string().startsWith('magnet:?'),
|
|
29
|
+
}),
|
|
30
|
+
])
|
|
31
|
+
|
|
32
|
+
export type Selection = z.infer<typeof SelectionSchema>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Aggregate global stats — mirrors the host's domain `GlobalStats` exactly.
|
|
5
|
+
* Also the payload of the `$/stats` notification.
|
|
6
|
+
*/
|
|
7
|
+
export const StatsResultSchema = z.object({
|
|
8
|
+
totalDownloadSpeed: z.number(),
|
|
9
|
+
totalUploadSpeed: z.number(),
|
|
10
|
+
activeTasks: z.number(),
|
|
11
|
+
waitingTasks: z.number(),
|
|
12
|
+
stoppedTasks: z.number(),
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
export const StatsGetParamsSchema = z.strictObject({})
|
|
16
|
+
|
|
17
|
+
export type StatsResult = z.infer<typeof StatsResultSchema>
|
|
18
|
+
export type StatsGetParams = z.infer<typeof StatsGetParamsSchema>
|
|
19
|
+
/** `$/stats` notification carries the same shape as `stats/get`'s result. */
|
|
20
|
+
export type StatsUpdateParams = StatsResult
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
export const SystemPingParamsSchema = z.object({
|
|
4
|
+
sentAt: z.number(),
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
export const SystemPingResultSchema = z.object({
|
|
8
|
+
sentAt: z.number(),
|
|
9
|
+
recvAt: z.number(),
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
export type SystemPingParams = z.infer<typeof SystemPingParamsSchema>
|
|
13
|
+
export type SystemPingResult = z.infer<typeof SystemPingResultSchema>
|