cognitive-modules-cli 2.2.5 → 2.2.7
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/CHANGELOG.md +1 -1
- package/README.md +3 -3
- package/dist/cli.js +6 -1
- package/dist/commands/add.js +164 -6
- package/dist/commands/run.d.ts +1 -0
- package/dist/commands/run.js +42 -19
- package/dist/errors/index.d.ts +7 -0
- package/dist/errors/index.js +48 -40
- package/dist/modules/runner.d.ts +7 -5
- package/dist/modules/runner.js +17 -14
- package/dist/registry/client.d.ts +12 -4
- package/dist/registry/client.js +5 -2
- package/dist/registry/tar.d.ts +8 -0
- package/dist/registry/tar.js +353 -0
- package/dist/server/http.js +167 -42
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +1 -0
- package/dist/server/sse.d.ts +13 -0
- package/dist/server/sse.js +22 -0
- package/package.json +1 -1
package/dist/server/http.js
CHANGED
|
@@ -13,12 +13,13 @@
|
|
|
13
13
|
import http from 'node:http';
|
|
14
14
|
import { URL } from 'node:url';
|
|
15
15
|
import { findModule, listModules, getDefaultSearchPaths } from '../modules/loader.js';
|
|
16
|
-
import { runModule } from '../modules/runner.js';
|
|
16
|
+
import { runModule, runModuleStream } from '../modules/runner.js';
|
|
17
17
|
import { getProvider } from '../providers/index.js';
|
|
18
18
|
import { VERSION } from '../version.js';
|
|
19
|
-
import { ErrorCodes, makeErrorEnvelope, makeHttpError } from '../errors/index.js';
|
|
19
|
+
import { ErrorCodes, attachContext, makeErrorEnvelope, makeHttpError, httpStatusForErrorCode } from '../errors/index.js';
|
|
20
|
+
import { encodeSseFrame } from './sse.js';
|
|
20
21
|
// Supported protocol versions
|
|
21
|
-
const SUPPORTED_VERSIONS = ['2.2'
|
|
22
|
+
const SUPPORTED_VERSIONS = ['2.2'];
|
|
22
23
|
const DEFAULT_VERSION = '2.2';
|
|
23
24
|
/**
|
|
24
25
|
* Get requested protocol version from request
|
|
@@ -185,7 +186,9 @@ async function handleRun(req, res, searchPaths, url) {
|
|
|
185
186
|
};
|
|
186
187
|
// Verify API key
|
|
187
188
|
if (!verifyApiKey(req)) {
|
|
188
|
-
|
|
189
|
+
const [status, body] = buildHttpError(ErrorCodes.PERMISSION_DENIED, 'Missing or invalid API Key', { suggestion: 'Use header: Authorization: Bearer <your-api-key>' });
|
|
190
|
+
// Auth failures are better represented as 401 even if the CEP code is permission-related.
|
|
191
|
+
jsonResponse(res, 401, body, protocolVersion);
|
|
189
192
|
return;
|
|
190
193
|
}
|
|
191
194
|
// Parse request body
|
|
@@ -213,6 +216,13 @@ async function handleRun(req, res, searchPaths, url) {
|
|
|
213
216
|
const reqBody = request;
|
|
214
217
|
// Determine protocol version (body > header > query > default)
|
|
215
218
|
protocolVersion = getRequestedVersion(req, url, reqBody.version);
|
|
219
|
+
// If the client explicitly requested an unsupported version, return a structured error.
|
|
220
|
+
const requested = reqBody.version ?? req.headers['x-cognitive-version'] ?? url.searchParams.get('version') ?? undefined;
|
|
221
|
+
if (requested && !SUPPORTED_VERSIONS.includes(requested)) {
|
|
222
|
+
const [status, body] = buildHttpError(ErrorCodes.UNSUPPORTED_VALUE, `Unsupported protocol version: ${requested}`, { suggestion: `Use version=${DEFAULT_VERSION}` });
|
|
223
|
+
jsonResponse(res, status, body, protocolVersion);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
216
226
|
// Validate request
|
|
217
227
|
if (!reqBody.module || !reqBody.args) {
|
|
218
228
|
const [status, body] = buildHttpError(ErrorCodes.MISSING_REQUIRED_FIELD, 'Missing required fields: module, args', {
|
|
@@ -241,45 +251,18 @@ async function handleRun(req, res, searchPaths, url) {
|
|
|
241
251
|
args: reqBody.args,
|
|
242
252
|
useV22: true,
|
|
243
253
|
});
|
|
244
|
-
//
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
risk: 'medium',
|
|
253
|
-
explain: 'No meta provided',
|
|
254
|
-
},
|
|
255
|
-
data: v22Result.data,
|
|
256
|
-
module: reqBody.module,
|
|
257
|
-
provider: providerName,
|
|
258
|
-
};
|
|
259
|
-
jsonResponse(res, 200, response, protocolVersion);
|
|
260
|
-
}
|
|
261
|
-
else {
|
|
262
|
-
// Error response - must include meta and full error object
|
|
263
|
-
const errorResult = result;
|
|
264
|
-
const response = {
|
|
265
|
-
ok: false,
|
|
266
|
-
version: protocolVersion,
|
|
267
|
-
meta: errorResult.meta ?? {
|
|
268
|
-
confidence: 0.0,
|
|
269
|
-
risk: 'high',
|
|
270
|
-
explain: errorResult.error?.message ?? 'An error occurred',
|
|
271
|
-
},
|
|
272
|
-
error: errorResult.error ?? {
|
|
273
|
-
code: ErrorCodes.INTERNAL_ERROR,
|
|
274
|
-
message: 'Unknown error',
|
|
275
|
-
recoverable: false,
|
|
276
|
-
},
|
|
277
|
-
partial_data: errorResult.partial_data,
|
|
278
|
-
module: reqBody.module,
|
|
279
|
-
provider: providerName,
|
|
280
|
-
};
|
|
281
|
-
jsonResponse(res, 200, response, protocolVersion);
|
|
254
|
+
// Attach transport context but do not rebuild the envelope.
|
|
255
|
+
const contextual = attachContext(result, {
|
|
256
|
+
module: reqBody.module,
|
|
257
|
+
provider: providerName,
|
|
258
|
+
});
|
|
259
|
+
if (contextual.ok) {
|
|
260
|
+
jsonResponse(res, 200, contextual, protocolVersion);
|
|
261
|
+
return;
|
|
282
262
|
}
|
|
263
|
+
const errorCode = (contextual.error?.code ?? ErrorCodes.INTERNAL_ERROR);
|
|
264
|
+
const status = httpStatusForErrorCode(errorCode);
|
|
265
|
+
jsonResponse(res, status, contextual, protocolVersion);
|
|
283
266
|
}
|
|
284
267
|
catch (error) {
|
|
285
268
|
// Infrastructure error - still return envelope
|
|
@@ -292,6 +275,144 @@ async function handleRun(req, res, searchPaths, url) {
|
|
|
292
275
|
jsonResponse(res, status, response, protocolVersion);
|
|
293
276
|
}
|
|
294
277
|
}
|
|
278
|
+
async function handleRunStream(req, res, searchPaths, url) {
|
|
279
|
+
let protocolVersion = DEFAULT_VERSION;
|
|
280
|
+
let sseStarted = false;
|
|
281
|
+
const beginSse = (version) => {
|
|
282
|
+
if (sseStarted)
|
|
283
|
+
return;
|
|
284
|
+
sseStarted = true;
|
|
285
|
+
res.writeHead(200, {
|
|
286
|
+
'Content-Type': 'text/event-stream; charset=utf-8',
|
|
287
|
+
'Cache-Control': 'no-cache, no-transform',
|
|
288
|
+
'Connection': 'keep-alive',
|
|
289
|
+
'X-Accel-Buffering': 'no',
|
|
290
|
+
'Access-Control-Allow-Origin': '*',
|
|
291
|
+
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
|
292
|
+
'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Cognitive-Version',
|
|
293
|
+
'Access-Control-Expose-Headers': 'X-Cognitive-Version',
|
|
294
|
+
'X-Cognitive-Version': version,
|
|
295
|
+
});
|
|
296
|
+
};
|
|
297
|
+
const writeEvent = (ev, id) => {
|
|
298
|
+
const type = typeof ev.type === 'string' ? ev.type : 'message';
|
|
299
|
+
res.write(encodeSseFrame(ev, { event: type, id }));
|
|
300
|
+
};
|
|
301
|
+
// Helper: send an error as CEP events (start + error + end).
|
|
302
|
+
const sendErrorStream = (envelope) => {
|
|
303
|
+
beginSse(protocolVersion);
|
|
304
|
+
let id = 1;
|
|
305
|
+
writeEvent({ type: 'start', version: protocolVersion, timestamp_ms: 0, module: envelope.module ?? 'unknown' }, id++);
|
|
306
|
+
const err = envelope.error ?? { code: ErrorCodes.INTERNAL_ERROR, message: 'Unknown error' };
|
|
307
|
+
writeEvent({ type: 'error', version: protocolVersion, timestamp_ms: 0, module: envelope.module ?? 'unknown', provider: envelope.provider, error: err }, id++);
|
|
308
|
+
writeEvent({ type: 'end', version: protocolVersion, timestamp_ms: 0, module: envelope.module ?? 'unknown', provider: envelope.provider, result: envelope }, id++);
|
|
309
|
+
res.end();
|
|
310
|
+
};
|
|
311
|
+
// Verify API key
|
|
312
|
+
if (!verifyApiKey(req)) {
|
|
313
|
+
// Auth failures should still be structured; SSE payload carries the error.
|
|
314
|
+
const envelope = attachContext(makeErrorEnvelope({
|
|
315
|
+
code: ErrorCodes.PERMISSION_DENIED,
|
|
316
|
+
message: 'Missing or invalid API Key',
|
|
317
|
+
suggestion: 'Use header: Authorization: Bearer <your-api-key>',
|
|
318
|
+
version: protocolVersion,
|
|
319
|
+
}), { module: 'unknown', provider: 'unknown' });
|
|
320
|
+
sendErrorStream(envelope);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
// Parse request body
|
|
324
|
+
let request;
|
|
325
|
+
try {
|
|
326
|
+
const body = await parseBody(req);
|
|
327
|
+
request = JSON.parse(body);
|
|
328
|
+
}
|
|
329
|
+
catch (e) {
|
|
330
|
+
const err = e;
|
|
331
|
+
const code = err?.code === 'PAYLOAD_TOO_LARGE' ? ErrorCodes.INPUT_TOO_LARGE : ErrorCodes.PARSE_ERROR;
|
|
332
|
+
const message = err?.code === 'PAYLOAD_TOO_LARGE' ? 'Payload too large' : 'Invalid JSON body';
|
|
333
|
+
const envelope = attachContext(makeErrorEnvelope({
|
|
334
|
+
code,
|
|
335
|
+
message,
|
|
336
|
+
suggestion: code === ErrorCodes.INPUT_TOO_LARGE ? 'Reduce input size to under 1MB' : 'Ensure request body is valid JSON',
|
|
337
|
+
version: protocolVersion,
|
|
338
|
+
}), { module: 'unknown', provider: 'unknown' });
|
|
339
|
+
sendErrorStream(envelope);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
if (!request || typeof request !== 'object') {
|
|
343
|
+
const envelope = attachContext(makeErrorEnvelope({
|
|
344
|
+
code: ErrorCodes.INVALID_INPUT,
|
|
345
|
+
message: 'Invalid request body',
|
|
346
|
+
suggestion: 'Ensure request body is a JSON object',
|
|
347
|
+
version: protocolVersion,
|
|
348
|
+
}), { module: 'unknown', provider: 'unknown' });
|
|
349
|
+
sendErrorStream(envelope);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
const reqBody = request;
|
|
353
|
+
protocolVersion = getRequestedVersion(req, url, reqBody.version);
|
|
354
|
+
// If the client explicitly requested an unsupported version, return a structured error.
|
|
355
|
+
const requested = reqBody.version ?? req.headers['x-cognitive-version'] ?? url.searchParams.get('version') ?? undefined;
|
|
356
|
+
if (requested && !SUPPORTED_VERSIONS.includes(requested)) {
|
|
357
|
+
const envelope = attachContext(makeErrorEnvelope({
|
|
358
|
+
code: ErrorCodes.UNSUPPORTED_VALUE,
|
|
359
|
+
message: `Unsupported protocol version: ${requested}`,
|
|
360
|
+
suggestion: `Use version=${DEFAULT_VERSION}`,
|
|
361
|
+
version: protocolVersion,
|
|
362
|
+
}), { module: reqBody?.module ?? 'unknown', provider: reqBody?.provider ?? 'unknown' });
|
|
363
|
+
sendErrorStream(envelope);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
// Validate request
|
|
367
|
+
if (!reqBody.module || !reqBody.args) {
|
|
368
|
+
const envelope = attachContext(makeErrorEnvelope({
|
|
369
|
+
code: ErrorCodes.MISSING_REQUIRED_FIELD,
|
|
370
|
+
message: 'Missing required fields: module, args',
|
|
371
|
+
suggestion: 'Provide both "module" and "args" fields in request body',
|
|
372
|
+
version: protocolVersion,
|
|
373
|
+
}), { module: reqBody?.module ?? 'unknown', provider: reqBody?.provider ?? 'unknown' });
|
|
374
|
+
sendErrorStream(envelope);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
// Find module
|
|
378
|
+
const moduleData = await findModule(reqBody.module, searchPaths);
|
|
379
|
+
if (!moduleData) {
|
|
380
|
+
const envelope = attachContext(makeErrorEnvelope({
|
|
381
|
+
code: ErrorCodes.MODULE_NOT_FOUND,
|
|
382
|
+
message: `Module '${reqBody.module}' not found`,
|
|
383
|
+
suggestion: 'Use GET /modules to list available modules',
|
|
384
|
+
version: protocolVersion,
|
|
385
|
+
}), { module: reqBody.module, provider: reqBody.provider ?? 'unknown' });
|
|
386
|
+
sendErrorStream(envelope);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
// Create provider
|
|
390
|
+
const provider = getProvider(reqBody.provider, reqBody.model);
|
|
391
|
+
const providerName = provider.name;
|
|
392
|
+
// Stream events
|
|
393
|
+
beginSse(protocolVersion);
|
|
394
|
+
let id = 1;
|
|
395
|
+
let closed = false;
|
|
396
|
+
const onClose = () => { closed = true; };
|
|
397
|
+
req.on('close', onClose);
|
|
398
|
+
res.on('close', onClose);
|
|
399
|
+
for await (const ev of runModuleStream(moduleData, provider, {
|
|
400
|
+
args: reqBody.args,
|
|
401
|
+
useV22: true,
|
|
402
|
+
})) {
|
|
403
|
+
if (closed)
|
|
404
|
+
break;
|
|
405
|
+
const contextualEv = {
|
|
406
|
+
...ev,
|
|
407
|
+
module: reqBody.module,
|
|
408
|
+
provider: providerName,
|
|
409
|
+
};
|
|
410
|
+
writeEvent(contextualEv, id++);
|
|
411
|
+
}
|
|
412
|
+
if (!closed) {
|
|
413
|
+
res.end();
|
|
414
|
+
}
|
|
415
|
+
}
|
|
295
416
|
export function createServer(options = {}) {
|
|
296
417
|
const { cwd = process.cwd() } = options;
|
|
297
418
|
const searchPaths = getDefaultSearchPaths(cwd);
|
|
@@ -328,6 +449,9 @@ export function createServer(options = {}) {
|
|
|
328
449
|
else if (path === '/run' && method === 'POST') {
|
|
329
450
|
await handleRun(req, res, searchPaths, url);
|
|
330
451
|
}
|
|
452
|
+
else if (path === '/run/stream' && method === 'POST') {
|
|
453
|
+
await handleRunStream(req, res, searchPaths, url);
|
|
454
|
+
}
|
|
331
455
|
else {
|
|
332
456
|
const envelope = makeErrorEnvelope({
|
|
333
457
|
code: ErrorCodes.ENDPOINT_NOT_FOUND,
|
|
@@ -364,6 +488,7 @@ export async function serve(options = {}) {
|
|
|
364
488
|
console.log(' GET /modules - List modules');
|
|
365
489
|
console.log(' GET /modules/:name - Module info');
|
|
366
490
|
console.log(' POST /run - Run module');
|
|
491
|
+
console.log(' POST /run/stream - Run module (SSE stream)');
|
|
367
492
|
resolve();
|
|
368
493
|
});
|
|
369
494
|
});
|
package/dist/server/index.d.ts
CHANGED
package/dist/server/index.js
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface SseFrameOptions {
|
|
2
|
+
event?: string;
|
|
3
|
+
id?: string | number;
|
|
4
|
+
retryMs?: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Encode a payload into an SSE frame.
|
|
8
|
+
*
|
|
9
|
+
* Notes:
|
|
10
|
+
* - SSE requires each data line to be prefixed with `data:`.
|
|
11
|
+
* - `event:` maps cleanly to CEP `type` to keep transport mapping deterministic.
|
|
12
|
+
*/
|
|
13
|
+
export declare function encodeSseFrame(data: unknown, options?: SseFrameOptions): string;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encode a payload into an SSE frame.
|
|
3
|
+
*
|
|
4
|
+
* Notes:
|
|
5
|
+
* - SSE requires each data line to be prefixed with `data:`.
|
|
6
|
+
* - `event:` maps cleanly to CEP `type` to keep transport mapping deterministic.
|
|
7
|
+
*/
|
|
8
|
+
export function encodeSseFrame(data, options = {}) {
|
|
9
|
+
const lines = [];
|
|
10
|
+
if (options.retryMs !== undefined)
|
|
11
|
+
lines.push(`retry: ${options.retryMs}`);
|
|
12
|
+
if (options.id !== undefined)
|
|
13
|
+
lines.push(`id: ${options.id}`);
|
|
14
|
+
if (options.event)
|
|
15
|
+
lines.push(`event: ${options.event}`);
|
|
16
|
+
const json = JSON.stringify(data);
|
|
17
|
+
for (const line of json.split('\n')) {
|
|
18
|
+
lines.push(`data: ${line}`);
|
|
19
|
+
}
|
|
20
|
+
lines.push(''); // End of event
|
|
21
|
+
return lines.join('\n') + '\n';
|
|
22
|
+
}
|