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/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this package are documented in this file.
|
|
4
4
|
|
|
5
|
-
## 2.2.
|
|
5
|
+
## 2.2.7 - 2026-02-06
|
|
6
6
|
|
|
7
7
|
- Standardized v2.2 runtime behavior and cross-surface error envelope consistency (CLI/HTTP/MCP).
|
|
8
8
|
- Clarified `compose` output contract: default/pretty output returns full v2.2 envelope, while `compose --trace` returns a debug wrapper object (`result/moduleResults/trace/totalTimeMs`) for diagnostics.
|
package/README.md
CHANGED
|
@@ -10,12 +10,12 @@ Node.js/TypeScript 版本的 Cognitive Modules CLI,提供 `cog` 命令。
|
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
# 全局安装(推荐)
|
|
13
|
-
npm install -g cogn@2.2.
|
|
13
|
+
npm install -g cogn@2.2.7
|
|
14
14
|
# 或使用完整包名(同样提供 `cog` 命令)
|
|
15
|
-
# npm install -g cognitive-modules-cli@2.2.
|
|
15
|
+
# npm install -g cognitive-modules-cli@2.2.7
|
|
16
16
|
|
|
17
17
|
# 或使用 npx 零安装
|
|
18
|
-
npx cogn@2.2.
|
|
18
|
+
npx cogn@2.2.7 --help
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
## 快速开始
|
package/dist/cli.js
CHANGED
|
@@ -41,6 +41,7 @@ async function main() {
|
|
|
41
41
|
pretty: { type: 'boolean', default: false },
|
|
42
42
|
verbose: { type: 'boolean', short: 'V', default: false },
|
|
43
43
|
'no-validate': { type: 'boolean', default: false },
|
|
44
|
+
stream: { type: 'boolean', default: false },
|
|
44
45
|
// Add/update options
|
|
45
46
|
name: { type: 'string', short: 'n' },
|
|
46
47
|
tag: { type: 'string', short: 't' },
|
|
@@ -92,6 +93,7 @@ async function main() {
|
|
|
92
93
|
noValidate: values['no-validate'],
|
|
93
94
|
pretty: values.pretty,
|
|
94
95
|
verbose: values.verbose,
|
|
96
|
+
stream: values.stream,
|
|
95
97
|
});
|
|
96
98
|
if (!result.success) {
|
|
97
99
|
if (result.data) {
|
|
@@ -102,7 +104,10 @@ async function main() {
|
|
|
102
104
|
}
|
|
103
105
|
process.exit(1);
|
|
104
106
|
}
|
|
105
|
-
|
|
107
|
+
// Stream mode prints events as NDJSON already.
|
|
108
|
+
if (!values.stream) {
|
|
109
|
+
console.log(JSON.stringify(result.data, null, values.pretty ? 2 : 0));
|
|
110
|
+
}
|
|
106
111
|
break;
|
|
107
112
|
}
|
|
108
113
|
case 'list': {
|
package/dist/commands/add.js
CHANGED
|
@@ -9,13 +9,15 @@
|
|
|
9
9
|
* cog add ziel-io/cognitive-modules -m code-simplifier
|
|
10
10
|
* cog add https://github.com/org/repo --module name --tag v1.0.0
|
|
11
11
|
*/
|
|
12
|
-
import { createWriteStream, existsSync, mkdirSync, rmSync, readdirSync, statSync, copyFileSync } from 'node:fs';
|
|
12
|
+
import { createWriteStream, existsSync, mkdirSync, rmSync, readdirSync, statSync, copyFileSync, lstatSync } from 'node:fs';
|
|
13
13
|
import { writeFile, readFile, mkdir } from 'node:fs/promises';
|
|
14
|
-
import { pipeline } from 'node:stream/promises';
|
|
14
|
+
import { pipeline, finished } from 'node:stream/promises';
|
|
15
15
|
import { Readable } from 'node:stream';
|
|
16
16
|
import { join, basename, dirname, resolve, sep, isAbsolute } from 'node:path';
|
|
17
17
|
import { homedir, tmpdir } from 'node:os';
|
|
18
|
+
import { createHash } from 'node:crypto';
|
|
18
19
|
import { RegistryClient } from '../registry/client.js';
|
|
20
|
+
import { extractTarGzFile } from '../registry/tar.js';
|
|
19
21
|
// Module storage paths
|
|
20
22
|
const USER_MODULES_DIR = join(homedir(), '.cognitive', 'modules');
|
|
21
23
|
const INSTALLED_MANIFEST = join(homedir(), '.cognitive', 'installed.json');
|
|
@@ -153,7 +155,11 @@ function copyDir(src, dest) {
|
|
|
153
155
|
for (const entry of readdirSync(src)) {
|
|
154
156
|
const srcPath = join(src, entry);
|
|
155
157
|
const destPath = join(dest, entry);
|
|
156
|
-
|
|
158
|
+
const st = lstatSync(srcPath);
|
|
159
|
+
if (st.isSymbolicLink()) {
|
|
160
|
+
throw new Error(`Refusing to install module containing symlink: ${srcPath}`);
|
|
161
|
+
}
|
|
162
|
+
if (st.isDirectory()) {
|
|
157
163
|
copyDir(srcPath, destPath);
|
|
158
164
|
}
|
|
159
165
|
else {
|
|
@@ -161,6 +167,74 @@ function copyDir(src, dest) {
|
|
|
161
167
|
}
|
|
162
168
|
}
|
|
163
169
|
}
|
|
170
|
+
async function downloadTarballWithSha256(url, outPath, maxBytes) {
|
|
171
|
+
const controller = new AbortController();
|
|
172
|
+
const timeout = setTimeout(() => controller.abort(), 10_000);
|
|
173
|
+
const hash = createHash('sha256');
|
|
174
|
+
let received = 0;
|
|
175
|
+
const fileStream = createWriteStream(outPath);
|
|
176
|
+
try {
|
|
177
|
+
const response = await fetch(url, {
|
|
178
|
+
headers: { 'User-Agent': 'cognitive-runtime/2.2' },
|
|
179
|
+
signal: controller.signal,
|
|
180
|
+
});
|
|
181
|
+
if (!response.ok) {
|
|
182
|
+
throw new Error(`Failed to download tarball: ${response.status} ${response.statusText}`);
|
|
183
|
+
}
|
|
184
|
+
const contentLengthHeader = response.headers?.get?.('content-length');
|
|
185
|
+
if (contentLengthHeader) {
|
|
186
|
+
const contentLength = Number(contentLengthHeader);
|
|
187
|
+
if (!Number.isNaN(contentLength) && contentLength > maxBytes) {
|
|
188
|
+
throw new Error(`Tarball too large: ${contentLength} bytes (max ${maxBytes})`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (!response.body) {
|
|
192
|
+
throw new Error('Tarball response has no body');
|
|
193
|
+
}
|
|
194
|
+
const reader = response.body.getReader?.();
|
|
195
|
+
if (!reader) {
|
|
196
|
+
// Fallback: stream pipeline without incremental hash.
|
|
197
|
+
await pipeline(Readable.fromWeb(response.body), fileStream);
|
|
198
|
+
const content = await readFile(outPath);
|
|
199
|
+
return createHash('sha256').update(content).digest('hex');
|
|
200
|
+
}
|
|
201
|
+
while (true) {
|
|
202
|
+
const { done, value } = await reader.read();
|
|
203
|
+
if (done)
|
|
204
|
+
break;
|
|
205
|
+
if (value) {
|
|
206
|
+
received += value.byteLength;
|
|
207
|
+
if (received > maxBytes) {
|
|
208
|
+
controller.abort();
|
|
209
|
+
throw new Error(`Tarball too large: ${received} bytes (max ${maxBytes})`);
|
|
210
|
+
}
|
|
211
|
+
const chunk = Buffer.from(value);
|
|
212
|
+
hash.update(chunk);
|
|
213
|
+
if (!fileStream.write(chunk)) {
|
|
214
|
+
await new Promise((resolve) => fileStream.once('drain', () => resolve()));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
fileStream.end();
|
|
219
|
+
await finished(fileStream);
|
|
220
|
+
return hash.digest('hex');
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
try {
|
|
224
|
+
fileStream.destroy();
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
// ignore
|
|
228
|
+
}
|
|
229
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
230
|
+
throw new Error('Tarball download timed out after 10000ms');
|
|
231
|
+
}
|
|
232
|
+
throw error;
|
|
233
|
+
}
|
|
234
|
+
finally {
|
|
235
|
+
clearTimeout(timeout);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
164
238
|
/**
|
|
165
239
|
* Get module version from module.yaml or MODULE.md
|
|
166
240
|
*/
|
|
@@ -266,6 +340,7 @@ function parseModuleSpec(spec) {
|
|
|
266
340
|
export async function addFromRegistry(moduleSpec, ctx, options = {}) {
|
|
267
341
|
const { name: moduleName, version: requestedVersion } = parseModuleSpec(moduleSpec);
|
|
268
342
|
const { name: customName, registry: registryUrl } = options;
|
|
343
|
+
let tempDir;
|
|
269
344
|
try {
|
|
270
345
|
const client = new RegistryClient(registryUrl);
|
|
271
346
|
const moduleInfo = await client.getModule(moduleName);
|
|
@@ -325,10 +400,83 @@ export async function addFromRegistry(moduleSpec, ctx, options = {}) {
|
|
|
325
400
|
}
|
|
326
401
|
return result;
|
|
327
402
|
}
|
|
328
|
-
//
|
|
403
|
+
// Tarball sources: require checksum, verify, and extract safely.
|
|
404
|
+
if (!downloadInfo.url.startsWith('http')) {
|
|
405
|
+
return { success: false, error: `Unsupported registry download URL: ${downloadInfo.url}` };
|
|
406
|
+
}
|
|
407
|
+
if (!downloadInfo.checksum) {
|
|
408
|
+
return {
|
|
409
|
+
success: false,
|
|
410
|
+
error: `Registry tarball missing checksum (required for safe install): ${moduleName}`,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
tempDir = join(tmpdir(), `cog-reg-${Date.now()}`);
|
|
414
|
+
mkdirSync(tempDir, { recursive: true });
|
|
415
|
+
const tarPath = join(tempDir, 'module.tar.gz');
|
|
416
|
+
const MAX_TARBALL_BYTES = 20 * 1024 * 1024; // 20MB
|
|
417
|
+
const actualSha256 = await downloadTarballWithSha256(downloadInfo.url, tarPath, MAX_TARBALL_BYTES);
|
|
418
|
+
const expected = downloadInfo.checksum;
|
|
419
|
+
const checksumMatch = expected.match(/^sha256:([a-f0-9]{64})$/);
|
|
420
|
+
if (!checksumMatch) {
|
|
421
|
+
throw new Error(`Unsupported checksum format (expected sha256:<64hex>): ${expected}`);
|
|
422
|
+
}
|
|
423
|
+
const expectedHash = checksumMatch[1];
|
|
424
|
+
if (actualSha256 !== expectedHash) {
|
|
425
|
+
throw new Error(`Checksum mismatch for ${moduleName}: expected ${expectedHash}, got ${actualSha256}`);
|
|
426
|
+
}
|
|
427
|
+
const extractedRoot = join(tempDir, 'pkg');
|
|
428
|
+
mkdirSync(extractedRoot, { recursive: true });
|
|
429
|
+
await extractTarGzFile(tarPath, extractedRoot, {
|
|
430
|
+
maxFiles: 5_000,
|
|
431
|
+
maxTotalBytes: 50 * 1024 * 1024,
|
|
432
|
+
maxSingleFileBytes: 20 * 1024 * 1024,
|
|
433
|
+
maxTarBytes: 100 * 1024 * 1024,
|
|
434
|
+
});
|
|
435
|
+
// Find module directory inside extractedRoot.
|
|
436
|
+
const rootNames = readdirSync(extractedRoot).filter((e) => e !== '__MACOSX' && e !== '.DS_Store');
|
|
437
|
+
const rootPaths = rootNames.map((e) => join(extractedRoot, e)).filter((p) => existsSync(p));
|
|
438
|
+
const rootDirs = rootPaths.filter((p) => statSync(p).isDirectory());
|
|
439
|
+
const rootFiles = rootPaths.filter((p) => !statSync(p).isDirectory());
|
|
440
|
+
if (rootDirs.length === 0) {
|
|
441
|
+
throw new Error('Tarball extraction produced no root directory');
|
|
442
|
+
}
|
|
443
|
+
if (rootDirs.length !== 1 || rootFiles.length > 0) {
|
|
444
|
+
throw new Error(`Tarball must contain exactly one module root directory and no other top-level entries. ` +
|
|
445
|
+
`dirs=${rootDirs.map((p) => basename(p)).join(',') || '(none)'} files=${rootFiles.map((p) => basename(p)).join(',') || '(none)'}`);
|
|
446
|
+
}
|
|
447
|
+
// Strict mode: require root dir itself to be a valid module.
|
|
448
|
+
const sourcePath = rootDirs[0];
|
|
449
|
+
if (!isValidModule(sourcePath)) {
|
|
450
|
+
throw new Error('Root directory in tarball is not a valid module');
|
|
451
|
+
}
|
|
452
|
+
const installName = (customName || moduleName);
|
|
453
|
+
const safeInstallName = assertSafeModuleName(installName);
|
|
454
|
+
const targetPath = resolveModuleTarget(safeInstallName);
|
|
455
|
+
if (existsSync(targetPath)) {
|
|
456
|
+
rmSync(targetPath, { recursive: true, force: true });
|
|
457
|
+
}
|
|
458
|
+
await mkdir(USER_MODULES_DIR, { recursive: true });
|
|
459
|
+
copyDir(sourcePath, targetPath);
|
|
460
|
+
const version = await getModuleVersion(sourcePath);
|
|
461
|
+
await recordInstall(safeInstallName, {
|
|
462
|
+
source: downloadInfo.url,
|
|
463
|
+
githubUrl: downloadInfo.url,
|
|
464
|
+
version,
|
|
465
|
+
installedAt: targetPath,
|
|
466
|
+
installedTime: new Date().toISOString(),
|
|
467
|
+
registryModule: moduleName,
|
|
468
|
+
registryUrl,
|
|
469
|
+
});
|
|
329
470
|
return {
|
|
330
|
-
success:
|
|
331
|
-
|
|
471
|
+
success: true,
|
|
472
|
+
data: {
|
|
473
|
+
message: `Added: ${safeInstallName}${version ? ` v${version}` : ''} (registry tarball)`,
|
|
474
|
+
name: safeInstallName,
|
|
475
|
+
version,
|
|
476
|
+
location: targetPath,
|
|
477
|
+
source: 'registry',
|
|
478
|
+
registryModule: moduleName,
|
|
479
|
+
},
|
|
332
480
|
};
|
|
333
481
|
}
|
|
334
482
|
catch (error) {
|
|
@@ -337,6 +485,16 @@ export async function addFromRegistry(moduleSpec, ctx, options = {}) {
|
|
|
337
485
|
error: error instanceof Error ? error.message : String(error),
|
|
338
486
|
};
|
|
339
487
|
}
|
|
488
|
+
finally {
|
|
489
|
+
if (tempDir) {
|
|
490
|
+
try {
|
|
491
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
492
|
+
}
|
|
493
|
+
catch {
|
|
494
|
+
// ignore
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
340
498
|
}
|
|
341
499
|
/**
|
|
342
500
|
* Add a module from GitHub
|
package/dist/commands/run.d.ts
CHANGED
package/dist/commands/run.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* cog run - Run a Cognitive Module
|
|
3
3
|
* Always returns v2.2 envelope format for consistency
|
|
4
4
|
*/
|
|
5
|
-
import { findModule, getDefaultSearchPaths, runModule } from '../modules/index.js';
|
|
5
|
+
import { findModule, getDefaultSearchPaths, runModule, runModuleStream } from '../modules/index.js';
|
|
6
6
|
import { ErrorCodes, attachContext, makeErrorEnvelope } from '../errors/index.js';
|
|
7
7
|
export async function run(moduleName, ctx, options = {}) {
|
|
8
8
|
const searchPaths = getDefaultSearchPaths(ctx.cwd);
|
|
@@ -40,24 +40,47 @@ export async function run(moduleName, ctx, options = {}) {
|
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
43
|
+
if (options.stream) {
|
|
44
|
+
// Stream NDJSON events to stdout. Final exit code is determined by the end event.
|
|
45
|
+
let finalOk = null;
|
|
46
|
+
for await (const ev of runModuleStream(module, ctx.provider, {
|
|
47
|
+
args: options.args,
|
|
48
|
+
input: inputData,
|
|
49
|
+
validateInput: !options.noValidate,
|
|
50
|
+
validateOutput: !options.noValidate,
|
|
51
|
+
useV22: true,
|
|
52
|
+
})) {
|
|
53
|
+
// Write each event as one JSON line (NDJSON).
|
|
54
|
+
process.stdout.write(JSON.stringify(ev) + '\n');
|
|
55
|
+
if (ev.type === 'end' && ev.result) {
|
|
56
|
+
finalOk = Boolean(ev.result.ok);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
success: finalOk === true,
|
|
61
|
+
data: { ok: finalOk === true },
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
// Run module with v2.2 envelope format
|
|
66
|
+
const result = await runModule(module, ctx.provider, {
|
|
67
|
+
args: options.args,
|
|
68
|
+
input: inputData,
|
|
69
|
+
verbose: options.verbose || ctx.verbose,
|
|
70
|
+
validateInput: !options.noValidate,
|
|
71
|
+
validateOutput: !options.noValidate,
|
|
72
|
+
useV22: true, // Always use v2.2 envelope
|
|
73
|
+
});
|
|
74
|
+
const output = attachContext(result, {
|
|
75
|
+
module: moduleName,
|
|
76
|
+
provider: ctx.provider.name,
|
|
77
|
+
});
|
|
78
|
+
// Always return full v2.2 envelope
|
|
79
|
+
return {
|
|
80
|
+
success: result.ok,
|
|
81
|
+
data: output,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
61
84
|
}
|
|
62
85
|
catch (e) {
|
|
63
86
|
const message = e instanceof Error ? e.message : String(e);
|
package/dist/errors/index.d.ts
CHANGED
|
@@ -112,6 +112,13 @@ export interface ErrorEnvelopeOptions {
|
|
|
112
112
|
*/
|
|
113
113
|
export declare function makeErrorEnvelope(options: ErrorEnvelopeOptions): CognitiveErrorEnvelope;
|
|
114
114
|
export declare function attachContext<T extends object>(envelope: T, context?: EnvelopeContext): T & EnvelopeContext;
|
|
115
|
+
/**
|
|
116
|
+
* Map a CEP error code to an HTTP status code.
|
|
117
|
+
*
|
|
118
|
+
* This is used to keep HTTP behavior consistent with the error model while
|
|
119
|
+
* allowing callers to attach context without rebuilding envelopes.
|
|
120
|
+
*/
|
|
121
|
+
export declare function httpStatusForErrorCode(code: string): number;
|
|
115
122
|
/**
|
|
116
123
|
* Create error envelope for HTTP API responses.
|
|
117
124
|
*
|
package/dist/errors/index.js
CHANGED
|
@@ -191,54 +191,47 @@ export function attachContext(envelope, context) {
|
|
|
191
191
|
};
|
|
192
192
|
}
|
|
193
193
|
/**
|
|
194
|
-
*
|
|
194
|
+
* Map a CEP error code to an HTTP status code.
|
|
195
195
|
*
|
|
196
|
-
*
|
|
196
|
+
* This is used to keep HTTP behavior consistent with the error model while
|
|
197
|
+
* allowing callers to attach context without rebuilding envelopes.
|
|
197
198
|
*/
|
|
198
|
-
export function
|
|
199
|
-
const
|
|
200
|
-
const
|
|
201
|
-
// Determine HTTP status code
|
|
202
|
-
let statusCode;
|
|
203
|
-
const category = code.charAt(1);
|
|
199
|
+
export function httpStatusForErrorCode(code) {
|
|
200
|
+
const normalized = normalizeErrorCode(code);
|
|
201
|
+
const category = normalized.charAt(1);
|
|
204
202
|
switch (category) {
|
|
205
203
|
case '1': {
|
|
206
204
|
// Input errors -> Bad Request (with specific overrides)
|
|
207
|
-
if (
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
else {
|
|
211
|
-
statusCode = 400;
|
|
212
|
-
}
|
|
213
|
-
break;
|
|
205
|
+
if (normalized === ErrorCodes.INPUT_TOO_LARGE)
|
|
206
|
+
return 413;
|
|
207
|
+
return 400;
|
|
214
208
|
}
|
|
215
|
-
case '2':
|
|
216
|
-
|
|
217
|
-
break; // Processing errors -> Unprocessable Entity
|
|
218
|
-
case '3':
|
|
219
|
-
statusCode = 500;
|
|
220
|
-
break; // Output errors -> Internal Server Error
|
|
209
|
+
case '2': return 422; // Processing errors -> Unprocessable Entity
|
|
210
|
+
case '3': return 500; // Output errors -> Internal Server Error
|
|
221
211
|
case '4': {
|
|
222
|
-
// Runtime errors
|
|
223
|
-
if (
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
else if (code === ErrorCodes.PERMISSION_DENIED) {
|
|
229
|
-
statusCode = 403; // Forbidden
|
|
230
|
-
}
|
|
231
|
-
else if (code === ErrorCodes.RATE_LIMITED) {
|
|
232
|
-
statusCode = 429; // Too Many Requests
|
|
212
|
+
// Runtime errors -> map to appropriate HTTP status
|
|
213
|
+
if (normalized === ErrorCodes.MODULE_NOT_FOUND ||
|
|
214
|
+
normalized === ErrorCodes.ENDPOINT_NOT_FOUND ||
|
|
215
|
+
normalized === ErrorCodes.RESOURCE_NOT_FOUND) {
|
|
216
|
+
return 404;
|
|
233
217
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
218
|
+
if (normalized === ErrorCodes.PERMISSION_DENIED)
|
|
219
|
+
return 403;
|
|
220
|
+
if (normalized === ErrorCodes.RATE_LIMITED)
|
|
221
|
+
return 429;
|
|
222
|
+
return 500;
|
|
238
223
|
}
|
|
239
|
-
default:
|
|
224
|
+
default: return 500;
|
|
240
225
|
}
|
|
241
|
-
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Create error envelope for HTTP API responses.
|
|
229
|
+
*
|
|
230
|
+
* @returns Tuple of [statusCode, body]
|
|
231
|
+
*/
|
|
232
|
+
export function makeHttpError(options) {
|
|
233
|
+
const envelope = attachContext(makeErrorEnvelope(options), options);
|
|
234
|
+
const statusCode = httpStatusForErrorCode(String(options.code));
|
|
242
235
|
return [statusCode, envelope];
|
|
243
236
|
}
|
|
244
237
|
/**
|
|
@@ -316,16 +309,31 @@ export function shouldRetry(envelope) {
|
|
|
316
309
|
* Use this for consistent success responses across HTTP and MCP layers.
|
|
317
310
|
*/
|
|
318
311
|
export function makeSuccessEnvelope(options) {
|
|
312
|
+
const explain = (options.explain || 'Operation completed successfully').slice(0, 280);
|
|
313
|
+
// Envelope schema requires data to be an object with at least `rationale`.
|
|
314
|
+
// For non-module operations (list/info), we still emit a conforming envelope by
|
|
315
|
+
// injecting a minimal rationale if missing, or wrapping non-objects.
|
|
316
|
+
const normalizedData = (() => {
|
|
317
|
+
const d = options.data;
|
|
318
|
+
const isPlainObject = typeof d === 'object' && d !== null && !Array.isArray(d);
|
|
319
|
+
if (!isPlainObject) {
|
|
320
|
+
return { result: d, rationale: explain };
|
|
321
|
+
}
|
|
322
|
+
const obj = d;
|
|
323
|
+
if (typeof obj.rationale === 'string')
|
|
324
|
+
return options.data;
|
|
325
|
+
return { ...obj, rationale: explain };
|
|
326
|
+
})();
|
|
319
327
|
return {
|
|
320
328
|
ok: true,
|
|
321
329
|
version: options.version || '2.2',
|
|
322
330
|
meta: {
|
|
323
331
|
confidence: options.confidence ?? 1.0,
|
|
324
332
|
risk: options.risk ?? 'none',
|
|
325
|
-
explain
|
|
333
|
+
explain,
|
|
326
334
|
trace_id: options.trace_id,
|
|
327
335
|
},
|
|
328
|
-
data:
|
|
336
|
+
data: normalizedData,
|
|
329
337
|
};
|
|
330
338
|
}
|
|
331
339
|
/**
|
package/dist/modules/runner.d.ts
CHANGED
|
@@ -369,13 +369,15 @@ export interface RunOptions {
|
|
|
369
369
|
}
|
|
370
370
|
export declare function runModule(module: CognitiveModule, provider: Provider, options?: RunOptions): Promise<ModuleResult>;
|
|
371
371
|
/** Event types emitted during streaming execution */
|
|
372
|
-
export type StreamEventType = 'start' | '
|
|
372
|
+
export type StreamEventType = 'start' | 'delta' | 'meta' | 'end' | 'error';
|
|
373
373
|
/** Event emitted during streaming execution */
|
|
374
374
|
export interface StreamEvent {
|
|
375
375
|
type: StreamEventType;
|
|
376
|
+
version: string;
|
|
376
377
|
timestamp_ms: number;
|
|
377
|
-
|
|
378
|
-
|
|
378
|
+
module: string;
|
|
379
|
+
provider?: string;
|
|
380
|
+
delta?: string;
|
|
379
381
|
meta?: EnvelopeMeta;
|
|
380
382
|
result?: EnvelopeResponseV22<unknown>;
|
|
381
383
|
error?: {
|
|
@@ -398,9 +400,9 @@ export interface StreamOptions {
|
|
|
398
400
|
*
|
|
399
401
|
* Yields StreamEvent objects as the module executes:
|
|
400
402
|
* - type="start": Module execution started
|
|
401
|
-
* - type="
|
|
403
|
+
* - type="delta": Incremental output delta (provider streaming chunk)
|
|
402
404
|
* - type="meta": Meta information available early
|
|
403
|
-
* - type="
|
|
405
|
+
* - type="end": Final result envelope (always emitted)
|
|
404
406
|
* - type="error": Error occurred
|
|
405
407
|
*
|
|
406
408
|
* @example
|
package/dist/modules/runner.js
CHANGED
|
@@ -1324,9 +1324,9 @@ export async function runModule(module, provider, options = {}) {
|
|
|
1324
1324
|
*
|
|
1325
1325
|
* Yields StreamEvent objects as the module executes:
|
|
1326
1326
|
* - type="start": Module execution started
|
|
1327
|
-
* - type="
|
|
1327
|
+
* - type="delta": Incremental output delta (provider streaming chunk)
|
|
1328
1328
|
* - type="meta": Meta information available early
|
|
1329
|
-
* - type="
|
|
1329
|
+
* - type="end": Final result envelope (always emitted)
|
|
1330
1330
|
* - type="error": Error occurred
|
|
1331
1331
|
*
|
|
1332
1332
|
* @example
|
|
@@ -1342,11 +1342,14 @@ export async function* runModuleStream(module, provider, options = {}) {
|
|
|
1342
1342
|
const { input, args, validateInput = true, validateOutput = true, useV22 = true, enableRepair = true, traceId, model } = options;
|
|
1343
1343
|
const startTime = Date.now();
|
|
1344
1344
|
const moduleName = module.name;
|
|
1345
|
+
const providerName = provider?.name;
|
|
1345
1346
|
function makeEvent(type, extra = {}) {
|
|
1346
1347
|
return {
|
|
1347
1348
|
type,
|
|
1349
|
+
version: ENVELOPE_VERSION,
|
|
1348
1350
|
timestamp_ms: Date.now() - startTime,
|
|
1349
|
-
|
|
1351
|
+
module: moduleName,
|
|
1352
|
+
...(providerName ? { provider: providerName } : {}),
|
|
1350
1353
|
...extra,
|
|
1351
1354
|
};
|
|
1352
1355
|
}
|
|
@@ -1378,7 +1381,7 @@ export async function* runModuleStream(module, provider, options = {}) {
|
|
|
1378
1381
|
_invokeErrorHooks(module.name, new Error(inputErrors.join('; ')), null);
|
|
1379
1382
|
const errorObj = errorResult.error;
|
|
1380
1383
|
yield makeEvent('error', { error: errorObj });
|
|
1381
|
-
yield makeEvent('
|
|
1384
|
+
yield makeEvent('end', { result: errorResult });
|
|
1382
1385
|
return;
|
|
1383
1386
|
}
|
|
1384
1387
|
}
|
|
@@ -1415,7 +1418,7 @@ export async function* runModuleStream(module, provider, options = {}) {
|
|
|
1415
1418
|
let streamResult;
|
|
1416
1419
|
while (!(streamResult = await stream.next()).done) {
|
|
1417
1420
|
const chunk = streamResult.value;
|
|
1418
|
-
yield makeEvent('
|
|
1421
|
+
yield makeEvent('delta', { delta: chunk });
|
|
1419
1422
|
}
|
|
1420
1423
|
// Get the final result (returned from the generator)
|
|
1421
1424
|
fullContent = streamResult.value.content;
|
|
@@ -1429,7 +1432,7 @@ export async function* runModuleStream(module, provider, options = {}) {
|
|
|
1429
1432
|
});
|
|
1430
1433
|
fullContent = result.content;
|
|
1431
1434
|
// Emit chunk event with full response
|
|
1432
|
-
yield makeEvent('
|
|
1435
|
+
yield makeEvent('delta', { delta: result.content });
|
|
1433
1436
|
}
|
|
1434
1437
|
// Parse response
|
|
1435
1438
|
let parsed;
|
|
@@ -1448,7 +1451,7 @@ export async function* runModuleStream(module, provider, options = {}) {
|
|
|
1448
1451
|
// errorResult is always an error response from makeErrorResponse
|
|
1449
1452
|
const errorObj = errorResult.error;
|
|
1450
1453
|
yield makeEvent('error', { error: errorObj });
|
|
1451
|
-
yield makeEvent('
|
|
1454
|
+
yield makeEvent('end', { result: errorResult });
|
|
1452
1455
|
return;
|
|
1453
1456
|
}
|
|
1454
1457
|
// Convert to v2.2 envelope
|
|
@@ -1502,7 +1505,7 @@ export async function* runModuleStream(module, provider, options = {}) {
|
|
|
1502
1505
|
_invokeErrorHooks(module.name, new Error(dataErrors.join('; ')), response.data);
|
|
1503
1506
|
const errorObj = errorResult.error;
|
|
1504
1507
|
yield makeEvent('error', { error: errorObj });
|
|
1505
|
-
yield makeEvent('
|
|
1508
|
+
yield makeEvent('end', { result: errorResult });
|
|
1506
1509
|
return;
|
|
1507
1510
|
}
|
|
1508
1511
|
const overflowErrors = validateOverflowLimits(dataToValidate, module);
|
|
@@ -1517,7 +1520,7 @@ export async function* runModuleStream(module, provider, options = {}) {
|
|
|
1517
1520
|
_invokeErrorHooks(module.name, new Error(overflowErrors.join('; ')), dataToValidate);
|
|
1518
1521
|
const errorObj = errorResult.error;
|
|
1519
1522
|
yield makeEvent('error', { error: errorObj });
|
|
1520
|
-
yield makeEvent('
|
|
1523
|
+
yield makeEvent('end', { result: errorResult });
|
|
1521
1524
|
return;
|
|
1522
1525
|
}
|
|
1523
1526
|
const enumErrors = validateEnumStrategy(dataToValidate, module);
|
|
@@ -1532,7 +1535,7 @@ export async function* runModuleStream(module, provider, options = {}) {
|
|
|
1532
1535
|
_invokeErrorHooks(module.name, new Error(enumErrors.join('; ')), dataToValidate);
|
|
1533
1536
|
const errorObj = errorResult.error;
|
|
1534
1537
|
yield makeEvent('error', { error: errorObj });
|
|
1535
|
-
yield makeEvent('
|
|
1538
|
+
yield makeEvent('end', { result: errorResult });
|
|
1536
1539
|
return;
|
|
1537
1540
|
}
|
|
1538
1541
|
}
|
|
@@ -1554,7 +1557,7 @@ export async function* runModuleStream(module, provider, options = {}) {
|
|
|
1554
1557
|
_invokeErrorHooks(module.name, new Error(metaErrors.join('; ')), response.data);
|
|
1555
1558
|
const errorObj = errorResult.error;
|
|
1556
1559
|
yield makeEvent('error', { error: errorObj });
|
|
1557
|
-
yield makeEvent('
|
|
1560
|
+
yield makeEvent('end', { result: errorResult });
|
|
1558
1561
|
return;
|
|
1559
1562
|
}
|
|
1560
1563
|
}
|
|
@@ -1566,8 +1569,8 @@ export async function* runModuleStream(module, provider, options = {}) {
|
|
|
1566
1569
|
}
|
|
1567
1570
|
const finalLatencyMs = Date.now() - startTime;
|
|
1568
1571
|
_invokeAfterHooks(module.name, response, finalLatencyMs);
|
|
1569
|
-
// Emit
|
|
1570
|
-
yield makeEvent('
|
|
1572
|
+
// Emit end event
|
|
1573
|
+
yield makeEvent('end', { result: response });
|
|
1571
1574
|
}
|
|
1572
1575
|
catch (e) {
|
|
1573
1576
|
_invokeErrorHooks(module.name, e, null);
|
|
@@ -1579,7 +1582,7 @@ export async function* runModuleStream(module, provider, options = {}) {
|
|
|
1579
1582
|
// errorResult is always an error response from makeErrorResponse
|
|
1580
1583
|
const errorObj = errorResult.error;
|
|
1581
1584
|
yield makeEvent('error', { error: errorObj });
|
|
1582
|
-
yield makeEvent('
|
|
1585
|
+
yield makeEvent('end', { result: errorResult });
|
|
1583
1586
|
}
|
|
1584
1587
|
}
|
|
1585
1588
|
// =============================================================================
|