@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.
Files changed (103) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +103 -0
  3. package/dist/browser.d.ts +3 -0
  4. package/dist/browser.d.ts.map +1 -0
  5. package/dist/browser.js +14 -0
  6. package/dist/browser.js.map +1 -0
  7. package/dist/connection.d.ts +50 -0
  8. package/dist/connection.d.ts.map +1 -0
  9. package/dist/connection.js +38 -0
  10. package/dist/connection.js.map +1 -0
  11. package/dist/errors.d.ts +33 -0
  12. package/dist/errors.d.ts.map +1 -0
  13. package/dist/errors.js +34 -0
  14. package/dist/errors.js.map +1 -0
  15. package/dist/index.d.ts +11 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +10 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/methods.d.ts +28 -0
  20. package/dist/methods.d.ts.map +1 -0
  21. package/dist/methods.js +32 -0
  22. package/dist/methods.js.map +1 -0
  23. package/dist/node.d.ts +3 -0
  24. package/dist/node.d.ts.map +1 -0
  25. package/dist/node.js +15 -0
  26. package/dist/node.js.map +1 -0
  27. package/dist/schemas/download-add.d.ts +72 -0
  28. package/dist/schemas/download-add.d.ts.map +1 -0
  29. package/dist/schemas/download-add.js +49 -0
  30. package/dist/schemas/download-add.js.map +1 -0
  31. package/dist/schemas/download.d.ts +156 -0
  32. package/dist/schemas/download.d.ts.map +1 -0
  33. package/dist/schemas/download.js +34 -0
  34. package/dist/schemas/download.js.map +1 -0
  35. package/dist/schemas/engine.d.ts +41 -0
  36. package/dist/schemas/engine.d.ts.map +1 -0
  37. package/dist/schemas/engine.js +24 -0
  38. package/dist/schemas/engine.js.map +1 -0
  39. package/dist/schemas/events.d.ts +36 -0
  40. package/dist/schemas/events.d.ts.map +1 -0
  41. package/dist/schemas/events.js +26 -0
  42. package/dist/schemas/events.js.map +1 -0
  43. package/dist/schemas/index.d.ts +12 -0
  44. package/dist/schemas/index.d.ts.map +1 -0
  45. package/dist/schemas/index.js +12 -0
  46. package/dist/schemas/index.js.map +1 -0
  47. package/dist/schemas/initialize.d.ts +70 -0
  48. package/dist/schemas/initialize.d.ts.map +1 -0
  49. package/dist/schemas/initialize.js +72 -0
  50. package/dist/schemas/initialize.js.map +1 -0
  51. package/dist/schemas/resource.d.ts +39 -0
  52. package/dist/schemas/resource.d.ts.map +1 -0
  53. package/dist/schemas/resource.js +21 -0
  54. package/dist/schemas/resource.js.map +1 -0
  55. package/dist/schemas/selection.d.ts +129 -0
  56. package/dist/schemas/selection.d.ts.map +1 -0
  57. package/dist/schemas/selection.js +30 -0
  58. package/dist/schemas/selection.js.map +1 -0
  59. package/dist/schemas/stats.d.ts +18 -0
  60. package/dist/schemas/stats.d.ts.map +1 -0
  61. package/dist/schemas/stats.js +14 -0
  62. package/dist/schemas/stats.js.map +1 -0
  63. package/dist/schemas/system.d.ts +11 -0
  64. package/dist/schemas/system.d.ts.map +1 -0
  65. package/dist/schemas/system.js +9 -0
  66. package/dist/schemas/system.js.map +1 -0
  67. package/dist/schemas/task.d.ts +202 -0
  68. package/dist/schemas/task.d.ts.map +1 -0
  69. package/dist/schemas/task.js +90 -0
  70. package/dist/schemas/task.js.map +1 -0
  71. package/dist/schemas/url.d.ts +162 -0
  72. package/dist/schemas/url.d.ts.map +1 -0
  73. package/dist/schemas/url.js +73 -0
  74. package/dist/schemas/url.js.map +1 -0
  75. package/dist/tools.d.ts +35 -0
  76. package/dist/tools.d.ts.map +1 -0
  77. package/dist/tools.js +124 -0
  78. package/dist/tools.js.map +1 -0
  79. package/dist/types.d.ts +12 -0
  80. package/dist/types.d.ts.map +1 -0
  81. package/dist/types.js +2 -0
  82. package/dist/types.js.map +1 -0
  83. package/package.json +73 -0
  84. package/src/browser.ts +14 -0
  85. package/src/connection.ts +162 -0
  86. package/src/errors.ts +60 -0
  87. package/src/index.ts +31 -0
  88. package/src/methods.ts +36 -0
  89. package/src/node.ts +15 -0
  90. package/src/schemas/download-add.ts +55 -0
  91. package/src/schemas/download.ts +42 -0
  92. package/src/schemas/engine.ts +32 -0
  93. package/src/schemas/events.ts +36 -0
  94. package/src/schemas/index.ts +11 -0
  95. package/src/schemas/initialize.ts +88 -0
  96. package/src/schemas/resource.ts +26 -0
  97. package/src/schemas/selection.ts +32 -0
  98. package/src/schemas/stats.ts +20 -0
  99. package/src/schemas/system.ts +13 -0
  100. package/src/schemas/task.ts +113 -0
  101. package/src/schemas/url.ts +86 -0
  102. package/src/tools.ts +175 -0
  103. 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>