@ashdev/codex-plugin-sdk 1.9.2 → 1.10.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 +0 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -6
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +87 -11
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +296 -260
- package/dist/server.js.map +1 -1
- package/dist/storage.d.ts +148 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +196 -0
- package/dist/storage.js.map +1 -0
- package/dist/types/capabilities.d.ts +85 -15
- package/dist/types/capabilities.d.ts.map +1 -1
- package/dist/types/capabilities.js +2 -3
- package/dist/types/capabilities.js.map +1 -1
- package/dist/types/index.d.ts +6 -4
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/manifest.d.ts +60 -13
- package/dist/types/manifest.d.ts.map +1 -1
- package/dist/types/manifest.js +10 -0
- package/dist/types/manifest.js.map +1 -1
- package/dist/types/protocol.d.ts +22 -0
- package/dist/types/protocol.d.ts.map +1 -1
- package/dist/types/recommendations.d.ts +133 -0
- package/dist/types/recommendations.d.ts.map +1 -0
- package/dist/types/recommendations.js +20 -0
- package/dist/types/recommendations.js.map +1 -0
- package/dist/types/sync.d.ts +175 -0
- package/dist/types/sync.d.ts.map +1 -0
- package/dist/types/sync.js +26 -0
- package/dist/types/sync.js.map +1 -0
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Plugin server - handles JSON-RPC communication over stdio
|
|
3
|
+
*
|
|
4
|
+
* Provides factory functions for creating different plugin types.
|
|
5
|
+
* All plugin types share a common base server that handles:
|
|
6
|
+
* - stdin readline parsing
|
|
7
|
+
* - JSON-RPC error handling
|
|
8
|
+
* - initialize/ping/shutdown lifecycle methods
|
|
9
|
+
*
|
|
10
|
+
* Each plugin type adds its own method routing on top.
|
|
3
11
|
*/
|
|
4
12
|
import { createInterface } from "node:readline";
|
|
5
13
|
import { PluginError } from "./errors.js";
|
|
6
14
|
import { createLogger } from "./logger.js";
|
|
7
|
-
import {
|
|
15
|
+
import { PluginStorage } from "./storage.js";
|
|
16
|
+
import { JSON_RPC_ERROR_CODES } from "./types/rpc.js";
|
|
8
17
|
/**
|
|
9
18
|
* Validate that the required string fields are present and non-empty
|
|
10
19
|
*/
|
|
@@ -87,78 +96,29 @@ function invalidParamsError(id, error) {
|
|
|
87
96
|
};
|
|
88
97
|
}
|
|
89
98
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* Creates a plugin server that handles JSON-RPC communication over stdio.
|
|
93
|
-
* The TypeScript compiler will ensure you implement all required methods.
|
|
94
|
-
*
|
|
95
|
-
* @example
|
|
96
|
-
* ```typescript
|
|
97
|
-
* import { createMetadataPlugin, type MetadataProvider } from "@ashdev/codex-plugin-sdk";
|
|
99
|
+
* Shared plugin server that handles JSON-RPC communication over stdio.
|
|
98
100
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
* return {
|
|
102
|
-
* results: [{
|
|
103
|
-
* externalId: "123",
|
|
104
|
-
* title: "Example",
|
|
105
|
-
* alternateTitles: [],
|
|
106
|
-
* relevanceScore: 0.95,
|
|
107
|
-
* }],
|
|
108
|
-
* };
|
|
109
|
-
* },
|
|
110
|
-
* async get(params) {
|
|
111
|
-
* return {
|
|
112
|
-
* externalId: params.externalId,
|
|
113
|
-
* externalUrl: "https://example.com/123",
|
|
114
|
-
* alternateTitles: [],
|
|
115
|
-
* genres: [],
|
|
116
|
-
* tags: [],
|
|
117
|
-
* authors: [],
|
|
118
|
-
* artists: [],
|
|
119
|
-
* externalLinks: [],
|
|
120
|
-
* };
|
|
121
|
-
* },
|
|
122
|
-
* };
|
|
123
|
-
*
|
|
124
|
-
* createMetadataPlugin({
|
|
125
|
-
* manifest: {
|
|
126
|
-
* name: "my-plugin",
|
|
127
|
-
* displayName: "My Plugin",
|
|
128
|
-
* version: "1.0.0",
|
|
129
|
-
* description: "Example plugin",
|
|
130
|
-
* author: "Me",
|
|
131
|
-
* protocolVersion: "1.0",
|
|
132
|
-
* capabilities: { metadataProvider: ["series"] },
|
|
133
|
-
* },
|
|
134
|
-
* provider,
|
|
135
|
-
* });
|
|
136
|
-
* ```
|
|
101
|
+
* Handles the common lifecycle methods (initialize, ping, shutdown) and
|
|
102
|
+
* delegates capability-specific methods to the provided router.
|
|
137
103
|
*/
|
|
138
|
-
|
|
139
|
-
const { manifest,
|
|
104
|
+
function createPluginServer(options) {
|
|
105
|
+
const { manifest, onInitialize, logLevel = "info", label, router } = options;
|
|
140
106
|
const logger = createLogger({ name: manifest.name, level: logLevel });
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
throw new Error("Series metadata provider is required when 'series' is in metadataProvider capabilities");
|
|
145
|
-
}
|
|
146
|
-
if (contentTypes.includes("book") && !bookProvider) {
|
|
147
|
-
throw new Error("Book metadata provider is required when 'book' is in metadataProvider capabilities");
|
|
148
|
-
}
|
|
149
|
-
logger.info(`Starting plugin: ${manifest.displayName} v${manifest.version}`);
|
|
107
|
+
const prefix = label ? `${label} plugin` : "plugin";
|
|
108
|
+
const storage = new PluginStorage();
|
|
109
|
+
logger.info(`Starting ${prefix}: ${manifest.displayName} v${manifest.version}`);
|
|
150
110
|
const rl = createInterface({
|
|
151
111
|
input: process.stdin,
|
|
152
112
|
terminal: false,
|
|
153
113
|
});
|
|
154
114
|
rl.on("line", (line) => {
|
|
155
|
-
void handleLine(line, manifest,
|
|
115
|
+
void handleLine(line, manifest, onInitialize, router, logger, storage);
|
|
156
116
|
});
|
|
157
117
|
rl.on("close", () => {
|
|
158
118
|
logger.info("stdin closed, shutting down");
|
|
119
|
+
storage.cancelAll();
|
|
159
120
|
process.exit(0);
|
|
160
121
|
});
|
|
161
|
-
// Handle uncaught errors
|
|
162
122
|
process.on("uncaughtException", (error) => {
|
|
163
123
|
logger.error("Uncaught exception", error);
|
|
164
124
|
process.exit(1);
|
|
@@ -167,47 +127,50 @@ export function createMetadataPlugin(options) {
|
|
|
167
127
|
logger.error("Unhandled rejection", reason);
|
|
168
128
|
});
|
|
169
129
|
}
|
|
170
|
-
// =============================================================================
|
|
171
|
-
// Backwards Compatibility (deprecated)
|
|
172
|
-
// =============================================================================
|
|
173
130
|
/**
|
|
174
|
-
*
|
|
131
|
+
* Detect whether a parsed JSON object is a JSON-RPC response (not a request).
|
|
132
|
+
*
|
|
133
|
+
* A response has `id` and either `result` or `error`, but no `method`.
|
|
134
|
+
* A request always has `method`.
|
|
175
135
|
*/
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
capabilities: {
|
|
183
|
-
...options.manifest.capabilities,
|
|
184
|
-
metadataProvider: ["series"],
|
|
185
|
-
},
|
|
186
|
-
},
|
|
187
|
-
};
|
|
188
|
-
createMetadataPlugin(newOptions);
|
|
136
|
+
function isJsonRpcResponse(obj) {
|
|
137
|
+
if (obj.method !== undefined)
|
|
138
|
+
return false;
|
|
139
|
+
if (obj.id === undefined || obj.id === null)
|
|
140
|
+
return false;
|
|
141
|
+
return "result" in obj || "error" in obj;
|
|
189
142
|
}
|
|
190
|
-
|
|
191
|
-
// Internal Implementation
|
|
192
|
-
// =============================================================================
|
|
193
|
-
async function handleLine(line, manifest, provider, bookProvider, onInitialize, logger) {
|
|
143
|
+
async function handleLine(line, manifest, onInitialize, router, logger, storage) {
|
|
194
144
|
const trimmed = line.trim();
|
|
195
145
|
if (!trimmed)
|
|
196
146
|
return;
|
|
147
|
+
// Try to detect storage responses before full request handling.
|
|
148
|
+
// Storage responses come from the host on stdin — they have id + (result|error)
|
|
149
|
+
// but no method field.
|
|
150
|
+
let parsed;
|
|
151
|
+
try {
|
|
152
|
+
parsed = JSON.parse(trimmed);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
// Will be handled as a parse error below
|
|
156
|
+
}
|
|
157
|
+
if (parsed && isJsonRpcResponse(parsed)) {
|
|
158
|
+
logger.debug("Routing storage response", { id: parsed.id });
|
|
159
|
+
storage.handleResponse(trimmed);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
197
162
|
let id = null;
|
|
198
163
|
try {
|
|
199
|
-
const request = JSON.parse(trimmed);
|
|
164
|
+
const request = (parsed ?? JSON.parse(trimmed));
|
|
200
165
|
id = request.id;
|
|
201
166
|
logger.debug(`Received request: ${request.method}`, { id: request.id });
|
|
202
|
-
const response = await handleRequest(request, manifest,
|
|
203
|
-
// Shutdown handler writes response directly and returns null
|
|
167
|
+
const response = await handleRequest(request, manifest, onInitialize, router, logger, storage);
|
|
204
168
|
if (response !== null) {
|
|
205
169
|
writeResponse(response);
|
|
206
170
|
}
|
|
207
171
|
}
|
|
208
172
|
catch (error) {
|
|
209
173
|
if (error instanceof SyntaxError) {
|
|
210
|
-
// JSON parse error
|
|
211
174
|
writeResponse({
|
|
212
175
|
jsonrpc: "2.0",
|
|
213
176
|
id: null,
|
|
@@ -238,205 +201,278 @@ async function handleLine(line, manifest, provider, bookProvider, onInitialize,
|
|
|
238
201
|
}
|
|
239
202
|
}
|
|
240
203
|
}
|
|
241
|
-
async function handleRequest(request, manifest,
|
|
204
|
+
async function handleRequest(request, manifest, onInitialize, router, logger, storage) {
|
|
242
205
|
const { method, params, id } = request;
|
|
206
|
+
// Common lifecycle methods
|
|
243
207
|
switch (method) {
|
|
244
|
-
case "initialize":
|
|
245
|
-
|
|
208
|
+
case "initialize": {
|
|
209
|
+
const initParams = (params ?? {});
|
|
210
|
+
// Inject the storage client so plugins can persist data
|
|
211
|
+
initParams.storage = storage;
|
|
246
212
|
if (onInitialize) {
|
|
247
|
-
await onInitialize(
|
|
213
|
+
await onInitialize(initParams);
|
|
248
214
|
}
|
|
249
|
-
return {
|
|
250
|
-
|
|
251
|
-
id,
|
|
252
|
-
result: manifest,
|
|
253
|
-
};
|
|
215
|
+
return { jsonrpc: "2.0", id, result: manifest };
|
|
216
|
+
}
|
|
254
217
|
case "ping":
|
|
255
|
-
return {
|
|
256
|
-
jsonrpc: "2.0",
|
|
257
|
-
id,
|
|
258
|
-
result: "pong",
|
|
259
|
-
};
|
|
218
|
+
return { jsonrpc: "2.0", id, result: "pong" };
|
|
260
219
|
case "shutdown": {
|
|
261
220
|
logger.info("Shutdown requested");
|
|
262
|
-
|
|
263
|
-
const response = {
|
|
264
|
-
jsonrpc: "2.0",
|
|
265
|
-
id,
|
|
266
|
-
result: null,
|
|
267
|
-
};
|
|
221
|
+
storage.cancelAll();
|
|
222
|
+
const response = { jsonrpc: "2.0", id, result: null };
|
|
268
223
|
process.stdout.write(`${JSON.stringify(response)}\n`, () => {
|
|
269
|
-
// Callback is called after the write is flushed to the OS
|
|
270
224
|
process.exit(0);
|
|
271
225
|
});
|
|
272
|
-
//
|
|
226
|
+
// Response already written above; return null so handleLine skips the write
|
|
273
227
|
return null;
|
|
274
228
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
229
|
+
}
|
|
230
|
+
// Delegate to capability-specific router
|
|
231
|
+
const response = await router(method, params, id);
|
|
232
|
+
if (response !== null) {
|
|
233
|
+
return response;
|
|
234
|
+
}
|
|
235
|
+
// Unknown method
|
|
236
|
+
return {
|
|
237
|
+
jsonrpc: "2.0",
|
|
238
|
+
id,
|
|
239
|
+
error: {
|
|
240
|
+
code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,
|
|
241
|
+
message: `Method not found: ${method}`,
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function writeResponse(response) {
|
|
246
|
+
process.stdout.write(`${JSON.stringify(response)}\n`);
|
|
247
|
+
}
|
|
248
|
+
// =============================================================================
|
|
249
|
+
// Response Helpers
|
|
250
|
+
// =============================================================================
|
|
251
|
+
function methodNotFound(id, message) {
|
|
252
|
+
return {
|
|
253
|
+
jsonrpc: "2.0",
|
|
254
|
+
id,
|
|
255
|
+
error: {
|
|
256
|
+
code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,
|
|
257
|
+
message,
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
function success(id, result) {
|
|
262
|
+
return { jsonrpc: "2.0", id, result };
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Create and run a metadata provider plugin
|
|
266
|
+
*
|
|
267
|
+
* Creates a plugin server that handles JSON-RPC communication over stdio.
|
|
268
|
+
* The TypeScript compiler will ensure you implement all required methods.
|
|
269
|
+
*
|
|
270
|
+
* @example
|
|
271
|
+
* ```typescript
|
|
272
|
+
* import { createMetadataPlugin, type MetadataProvider } from "@ashdev/codex-plugin-sdk";
|
|
273
|
+
*
|
|
274
|
+
* const provider: MetadataProvider = {
|
|
275
|
+
* async search(params) {
|
|
276
|
+
* return {
|
|
277
|
+
* results: [{
|
|
278
|
+
* externalId: "123",
|
|
279
|
+
* title: "Example",
|
|
280
|
+
* alternateTitles: [],
|
|
281
|
+
* relevanceScore: 0.95,
|
|
282
|
+
* }],
|
|
283
|
+
* };
|
|
284
|
+
* },
|
|
285
|
+
* async get(params) {
|
|
286
|
+
* return {
|
|
287
|
+
* externalId: params.externalId,
|
|
288
|
+
* externalUrl: "https://example.com/123",
|
|
289
|
+
* alternateTitles: [],
|
|
290
|
+
* genres: [],
|
|
291
|
+
* tags: [],
|
|
292
|
+
* authors: [],
|
|
293
|
+
* artists: [],
|
|
294
|
+
* externalLinks: [],
|
|
295
|
+
* };
|
|
296
|
+
* },
|
|
297
|
+
* };
|
|
298
|
+
*
|
|
299
|
+
* createMetadataPlugin({
|
|
300
|
+
* manifest: {
|
|
301
|
+
* name: "my-plugin",
|
|
302
|
+
* displayName: "My Plugin",
|
|
303
|
+
* version: "1.0.0",
|
|
304
|
+
* description: "Example plugin",
|
|
305
|
+
* author: "Me",
|
|
306
|
+
* protocolVersion: "1.0",
|
|
307
|
+
* capabilities: { metadataProvider: ["series"] },
|
|
308
|
+
* },
|
|
309
|
+
* provider,
|
|
310
|
+
* });
|
|
311
|
+
* ```
|
|
312
|
+
*/
|
|
313
|
+
export function createMetadataPlugin(options) {
|
|
314
|
+
const { manifest, provider, bookProvider, onInitialize, logLevel } = options;
|
|
315
|
+
// Validate that required providers are present based on manifest
|
|
316
|
+
const contentTypes = manifest.capabilities.metadataProvider;
|
|
317
|
+
if (contentTypes.includes("series") && !provider) {
|
|
318
|
+
throw new Error("Series metadata provider is required when 'series' is in metadataProvider capabilities");
|
|
319
|
+
}
|
|
320
|
+
if (contentTypes.includes("book") && !bookProvider) {
|
|
321
|
+
throw new Error("Book metadata provider is required when 'book' is in metadataProvider capabilities");
|
|
322
|
+
}
|
|
323
|
+
const router = async (method, params, id) => {
|
|
324
|
+
switch (method) {
|
|
325
|
+
// Series metadata methods
|
|
326
|
+
case "metadata/series/search": {
|
|
327
|
+
if (!provider)
|
|
328
|
+
return methodNotFound(id, "This plugin does not support series metadata");
|
|
329
|
+
const err = validateSearchParams(params);
|
|
330
|
+
if (err)
|
|
331
|
+
return invalidParamsError(id, err);
|
|
332
|
+
return success(id, await provider.search(params));
|
|
313
333
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
if (!provider) {
|
|
322
|
-
return {
|
|
323
|
-
jsonrpc: "2.0",
|
|
324
|
-
id,
|
|
325
|
-
error: {
|
|
326
|
-
code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,
|
|
327
|
-
message: "This plugin does not support series metadata",
|
|
328
|
-
},
|
|
329
|
-
};
|
|
334
|
+
case "metadata/series/get": {
|
|
335
|
+
if (!provider)
|
|
336
|
+
return methodNotFound(id, "This plugin does not support series metadata");
|
|
337
|
+
const err = validateGetParams(params);
|
|
338
|
+
if (err)
|
|
339
|
+
return invalidParamsError(id, err);
|
|
340
|
+
return success(id, await provider.get(params));
|
|
330
341
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
342
|
+
case "metadata/series/match": {
|
|
343
|
+
if (!provider)
|
|
344
|
+
return methodNotFound(id, "This plugin does not support series metadata");
|
|
345
|
+
if (!provider.match)
|
|
346
|
+
return methodNotFound(id, "This plugin does not support series match");
|
|
347
|
+
const err = validateMatchParams(params);
|
|
348
|
+
if (err)
|
|
349
|
+
return invalidParamsError(id, err);
|
|
350
|
+
return success(id, await provider.match(params));
|
|
340
351
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
352
|
+
// Book metadata methods
|
|
353
|
+
case "metadata/book/search": {
|
|
354
|
+
if (!bookProvider)
|
|
355
|
+
return methodNotFound(id, "This plugin does not support book metadata");
|
|
356
|
+
const err = validateBookSearchParams(params);
|
|
357
|
+
if (err)
|
|
358
|
+
return invalidParamsError(id, err);
|
|
359
|
+
return success(id, await bookProvider.search(params));
|
|
344
360
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
// Book metadata methods
|
|
353
|
-
// =========================================================================
|
|
354
|
-
case "metadata/book/search": {
|
|
355
|
-
if (!bookProvider) {
|
|
356
|
-
return {
|
|
357
|
-
jsonrpc: "2.0",
|
|
358
|
-
id,
|
|
359
|
-
error: {
|
|
360
|
-
code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,
|
|
361
|
-
message: "This plugin does not support book metadata",
|
|
362
|
-
},
|
|
363
|
-
};
|
|
361
|
+
case "metadata/book/get": {
|
|
362
|
+
if (!bookProvider)
|
|
363
|
+
return methodNotFound(id, "This plugin does not support book metadata");
|
|
364
|
+
const err = validateGetParams(params);
|
|
365
|
+
if (err)
|
|
366
|
+
return invalidParamsError(id, err);
|
|
367
|
+
return success(id, await bookProvider.get(params));
|
|
364
368
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
369
|
+
case "metadata/book/match": {
|
|
370
|
+
if (!bookProvider)
|
|
371
|
+
return methodNotFound(id, "This plugin does not support book metadata");
|
|
372
|
+
if (!bookProvider.match)
|
|
373
|
+
return methodNotFound(id, "This plugin does not support book match");
|
|
374
|
+
const err = validateBookMatchParams(params);
|
|
375
|
+
if (err)
|
|
376
|
+
return invalidParamsError(id, err);
|
|
377
|
+
return success(id, await bookProvider.match(params));
|
|
368
378
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
id,
|
|
372
|
-
result: await bookProvider.search(params),
|
|
373
|
-
};
|
|
379
|
+
default:
|
|
380
|
+
return null;
|
|
374
381
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
382
|
+
};
|
|
383
|
+
createPluginServer({ manifest, onInitialize, logLevel, router });
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Create and run a sync provider plugin
|
|
387
|
+
*
|
|
388
|
+
* Creates a plugin server that handles JSON-RPC communication over stdio
|
|
389
|
+
* for sync operations (push/pull reading progress with external services).
|
|
390
|
+
*
|
|
391
|
+
* @example
|
|
392
|
+
* ```typescript
|
|
393
|
+
* import { createSyncPlugin, type SyncProvider } from "@ashdev/codex-plugin-sdk";
|
|
394
|
+
*
|
|
395
|
+
* const provider: SyncProvider = {
|
|
396
|
+
* async getUserInfo() {
|
|
397
|
+
* return { externalId: "123", username: "user" };
|
|
398
|
+
* },
|
|
399
|
+
* async pushProgress(params) {
|
|
400
|
+
* return { success: [], failed: [] };
|
|
401
|
+
* },
|
|
402
|
+
* async pullProgress(params) {
|
|
403
|
+
* return { entries: [], hasMore: false };
|
|
404
|
+
* },
|
|
405
|
+
* };
|
|
406
|
+
*
|
|
407
|
+
* createSyncPlugin({
|
|
408
|
+
* manifest: {
|
|
409
|
+
* name: "my-sync-plugin",
|
|
410
|
+
* displayName: "My Sync Plugin",
|
|
411
|
+
* version: "1.0.0",
|
|
412
|
+
* description: "Syncs reading progress",
|
|
413
|
+
* author: "Me",
|
|
414
|
+
* protocolVersion: "1.0",
|
|
415
|
+
* capabilities: { userReadSync: true },
|
|
416
|
+
* },
|
|
417
|
+
* provider,
|
|
418
|
+
* });
|
|
419
|
+
* ```
|
|
420
|
+
*/
|
|
421
|
+
export function createSyncPlugin(options) {
|
|
422
|
+
const { manifest, provider, onInitialize, logLevel } = options;
|
|
423
|
+
const router = async (method, params, id) => {
|
|
424
|
+
switch (method) {
|
|
425
|
+
case "sync/getUserInfo":
|
|
426
|
+
return success(id, await provider.getUserInfo());
|
|
427
|
+
case "sync/pushProgress":
|
|
428
|
+
return success(id, await provider.pushProgress(params));
|
|
429
|
+
case "sync/pullProgress":
|
|
430
|
+
return success(id, await provider.pullProgress(params));
|
|
431
|
+
case "sync/status": {
|
|
432
|
+
if (!provider.status)
|
|
433
|
+
return methodNotFound(id, "This plugin does not support sync/status");
|
|
434
|
+
return success(id, await provider.status());
|
|
389
435
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
id,
|
|
393
|
-
result: await bookProvider.get(params),
|
|
394
|
-
};
|
|
436
|
+
default:
|
|
437
|
+
return null;
|
|
395
438
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
439
|
+
};
|
|
440
|
+
createPluginServer({ manifest, onInitialize, logLevel, label: "sync", router });
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Create and run a recommendation provider plugin
|
|
444
|
+
*
|
|
445
|
+
* Creates a plugin server that handles JSON-RPC communication over stdio
|
|
446
|
+
* for recommendation operations (get recommendations, update profile, dismiss).
|
|
447
|
+
*/
|
|
448
|
+
export function createRecommendationPlugin(options) {
|
|
449
|
+
const { manifest, provider, onInitialize, logLevel } = options;
|
|
450
|
+
const router = async (method, params, id) => {
|
|
451
|
+
switch (method) {
|
|
452
|
+
case "recommendations/get":
|
|
453
|
+
return success(id, await provider.get(params));
|
|
454
|
+
case "recommendations/updateProfile": {
|
|
455
|
+
if (!provider.updateProfile)
|
|
456
|
+
return methodNotFound(id, "This plugin does not support recommendations/updateProfile");
|
|
457
|
+
return success(id, await provider.updateProfile(params));
|
|
406
458
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
error: {
|
|
412
|
-
code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,
|
|
413
|
-
message: "This plugin does not support book match",
|
|
414
|
-
},
|
|
415
|
-
};
|
|
459
|
+
case "recommendations/clear": {
|
|
460
|
+
if (!provider.clear)
|
|
461
|
+
return methodNotFound(id, "This plugin does not support recommendations/clear");
|
|
462
|
+
return success(id, await provider.clear());
|
|
416
463
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
464
|
+
case "recommendations/dismiss": {
|
|
465
|
+
if (!provider.dismiss)
|
|
466
|
+
return methodNotFound(id, "This plugin does not support recommendations/dismiss");
|
|
467
|
+
const err = validateStringFields(params, ["externalId"]);
|
|
468
|
+
if (err)
|
|
469
|
+
return invalidParamsError(id, err);
|
|
470
|
+
return success(id, await provider.dismiss(params));
|
|
420
471
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
id,
|
|
424
|
-
result: await bookProvider.match(params),
|
|
425
|
-
};
|
|
472
|
+
default:
|
|
473
|
+
return null;
|
|
426
474
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
jsonrpc: "2.0",
|
|
430
|
-
id,
|
|
431
|
-
error: {
|
|
432
|
-
code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,
|
|
433
|
-
message: `Method not found: ${method}`,
|
|
434
|
-
},
|
|
435
|
-
};
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
function writeResponse(response) {
|
|
439
|
-
// Write to stdout - this is the JSON-RPC channel
|
|
440
|
-
process.stdout.write(`${JSON.stringify(response)}\n`);
|
|
475
|
+
};
|
|
476
|
+
createPluginServer({ manifest, onInitialize, logLevel, label: "recommendation", router });
|
|
441
477
|
}
|
|
442
478
|
//# sourceMappingURL=server.js.map
|