voyageai-cli 1.29.0 → 1.30.1
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 +82 -8
- package/package.json +1 -1
- package/src/cli.js +6 -0
- package/src/commands/benchmark.js +22 -8
- package/src/commands/chat.js +50 -11
- package/src/commands/chunk.js +10 -0
- package/src/commands/demo.js +4 -0
- package/src/commands/embed.js +13 -0
- package/src/commands/estimate.js +3 -0
- package/src/commands/eval.js +6 -0
- package/src/commands/explain.js +2 -0
- package/src/commands/export.js +124 -0
- package/src/commands/generate.js +2 -0
- package/src/commands/import.js +195 -0
- package/src/commands/index-workspace.js +239 -0
- package/src/commands/ingest.js +4 -0
- package/src/commands/init.js +2 -0
- package/src/commands/mcp-server.js +115 -3
- package/src/commands/models.js +2 -0
- package/src/commands/ping.js +7 -0
- package/src/commands/pipeline.js +15 -0
- package/src/commands/playground.js +163 -9
- package/src/commands/query.js +16 -0
- package/src/commands/rerank.js +12 -0
- package/src/commands/scaffold.js +2 -0
- package/src/commands/search.js +11 -0
- package/src/commands/similarity.js +9 -0
- package/src/commands/store.js +4 -0
- package/src/commands/workflow.js +286 -0
- package/src/lib/capability-report.js +134 -0
- package/src/lib/chat.js +32 -1
- package/src/lib/config.js +2 -0
- package/src/lib/cost-display.js +107 -0
- package/src/lib/explanations.js +6 -0
- package/src/lib/export/contexts/benchmark-export.js +27 -0
- package/src/lib/export/contexts/chat-export.js +41 -0
- package/src/lib/export/contexts/explore-export.js +22 -0
- package/src/lib/export/contexts/search-export.js +54 -0
- package/src/lib/export/contexts/workflow-export.js +80 -0
- package/src/lib/export/formats/clipboard-export.js +29 -0
- package/src/lib/export/formats/csv-export.js +45 -0
- package/src/lib/export/formats/json-export.js +50 -0
- package/src/lib/export/formats/markdown-export.js +189 -0
- package/src/lib/export/formats/mermaid-export.js +274 -0
- package/src/lib/export/formats/pdf-export.js +117 -0
- package/src/lib/export/formats/png-export.js +96 -0
- package/src/lib/export/formats/svg-export.js +116 -0
- package/src/lib/export/index.js +175 -0
- package/src/lib/llm.js +125 -18
- package/src/lib/quality-audit.js +71 -0
- package/src/lib/security/blocked-domains.json +17 -0
- package/src/lib/security-audit.js +198 -0
- package/src/lib/telemetry.js +23 -1
- package/src/lib/workflow-scaffold.js +61 -0
- package/src/lib/workflow-test-runner.js +208 -0
- package/src/lib/workflow.js +333 -28
- package/src/mcp/install.js +280 -7
- package/src/mcp/schemas/index.js +40 -0
- package/src/mcp/server.js +2 -0
- package/src/mcp/tools/workspace.js +463 -0
- package/src/playground/announcements.md +56 -0
- package/src/playground/help/workflow-nodes.js +472 -0
- package/src/playground/index.html +13134 -8507
- package/src/playground/vendor/mermaid.min.js +2811 -0
- package/src/workflows/rag-chat.json +165 -0
- package/src/workflows/tests/consistency-check.happy-path.test.json +28 -0
- package/src/workflows/tests/consistency-check.missing-source.test.json +26 -0
- package/src/workflows/tests/cost-analysis.happy-path.test.json +28 -0
- package/src/workflows/tests/enrich-and-ingest.happy-path.test.json +38 -0
- package/src/workflows/tests/enrich-and-ingest.notify-fails.test.json +38 -0
- package/src/workflows/tests/intelligent-ingest.all-filtered.test.json +26 -0
- package/src/workflows/tests/intelligent-ingest.happy-path.test.json +28 -0
- package/src/workflows/tests/kb-health-report.custom-queries.test.json +24 -0
- package/src/workflows/tests/kb-health-report.happy-path.test.json +26 -0
- package/src/workflows/tests/multi-collection-search.happy-path.test.json +40 -0
- package/src/workflows/tests/multi-collection-search.one-empty.test.json +28 -0
- package/src/workflows/tests/rag-chat.happy-path.test.json +26 -0
- package/src/workflows/tests/rag-chat.no-relevant-results.test.json +25 -0
- package/src/workflows/tests/research-and-summarize.happy-path.test.json +33 -0
- package/src/workflows/tests/research-and-summarize.no-results.test.json +29 -0
- package/src/workflows/tests/search-with-fallback.empty-both.test.json +24 -0
- package/src/workflows/tests/search-with-fallback.fallback-branch.test.json +24 -0
- package/src/workflows/tests/search-with-fallback.happy-path.test.json +27 -0
- package/src/workflows/tests/smart-ingest.duplicate-detected.test.json +34 -0
- package/src/workflows/tests/smart-ingest.happy-path.test.json +31 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalize benchmark data for export.
|
|
5
|
+
* @param {object} data - Raw benchmark data
|
|
6
|
+
* @param {object} options
|
|
7
|
+
* @returns {object} normalized
|
|
8
|
+
*/
|
|
9
|
+
function normalizeBenchmark(data, options = {}) {
|
|
10
|
+
const results = data.results || data.rows || [];
|
|
11
|
+
const rows = results.map((r) => {
|
|
12
|
+
const row = { ...r };
|
|
13
|
+
return row;
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
_context: 'benchmark',
|
|
18
|
+
name: data.name || data.title || 'Benchmark',
|
|
19
|
+
date: data.date || new Date().toISOString(),
|
|
20
|
+
results: rows,
|
|
21
|
+
rows, // alias for CSV renderer
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const BENCHMARK_FORMATS = ['json', 'csv', 'markdown', 'svg', 'png', 'clipboard'];
|
|
26
|
+
|
|
27
|
+
module.exports = { normalizeBenchmark, BENCHMARK_FORMATS };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalize a chat session for export.
|
|
5
|
+
* @param {object} data - Raw chat session data
|
|
6
|
+
* @param {object} options
|
|
7
|
+
* @returns {object} normalized
|
|
8
|
+
*/
|
|
9
|
+
function normalizeChat(data, options = {}) {
|
|
10
|
+
const turns = (data.turns || data.messages || []).map((t) => {
|
|
11
|
+
const turn = {
|
|
12
|
+
role: t.role,
|
|
13
|
+
content: t.content,
|
|
14
|
+
timestamp: t.timestamp,
|
|
15
|
+
};
|
|
16
|
+
if (options.includeSources !== false && t.context) {
|
|
17
|
+
turn.context = t.context;
|
|
18
|
+
}
|
|
19
|
+
if (options.includeMetadata && t.metadata) {
|
|
20
|
+
turn.metadata = t.metadata;
|
|
21
|
+
}
|
|
22
|
+
if (options.includeContextChunks && t.contextChunks) {
|
|
23
|
+
turn.contextChunks = t.contextChunks;
|
|
24
|
+
}
|
|
25
|
+
return turn;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
_context: 'chat',
|
|
30
|
+
sessionId: data.sessionId || data.id,
|
|
31
|
+
startedAt: data.startedAt,
|
|
32
|
+
provider: data.provider,
|
|
33
|
+
model: data.model,
|
|
34
|
+
collection: data.collection,
|
|
35
|
+
turns,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const CHAT_FORMATS = ['json', 'markdown', 'pdf', 'clipboard'];
|
|
40
|
+
|
|
41
|
+
module.exports = { normalizeChat, CHAT_FORMATS };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalize explore/visualization data for export.
|
|
5
|
+
* Phase 1 stub — only JSON supported.
|
|
6
|
+
* @param {object} data
|
|
7
|
+
* @param {object} options
|
|
8
|
+
* @returns {object} normalized
|
|
9
|
+
*/
|
|
10
|
+
function normalizeExplore(data, options = {}) {
|
|
11
|
+
return {
|
|
12
|
+
_context: 'explore',
|
|
13
|
+
points: data.points || [],
|
|
14
|
+
labels: data.labels || [],
|
|
15
|
+
dimensions: data.dimensions || 2,
|
|
16
|
+
method: data.method || 'pca',
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const EXPLORE_FORMATS = ['json', 'svg', 'png'];
|
|
21
|
+
|
|
22
|
+
module.exports = { normalizeExplore, EXPLORE_FORMATS };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalize search results for export.
|
|
5
|
+
* @param {object} data - Raw search results data
|
|
6
|
+
* @param {object} options
|
|
7
|
+
* @returns {object} normalized
|
|
8
|
+
*/
|
|
9
|
+
function normalizeSearch(data, options = {}) {
|
|
10
|
+
const results = (data.results || []).map((r, i) => {
|
|
11
|
+
const item = {
|
|
12
|
+
rank: r.rank || i + 1,
|
|
13
|
+
score: r.score,
|
|
14
|
+
source: r.source || r.path || '',
|
|
15
|
+
};
|
|
16
|
+
if (r.rerankedScore !== undefined) item.rerankedScore = r.rerankedScore;
|
|
17
|
+
if (options.includeFullText) {
|
|
18
|
+
item.text = r.text || '';
|
|
19
|
+
} else {
|
|
20
|
+
item.text = (r.text || '').slice(0, 200);
|
|
21
|
+
}
|
|
22
|
+
if (options.includeMetadata !== false && r.metadata) {
|
|
23
|
+
item.metadata = r.metadata;
|
|
24
|
+
}
|
|
25
|
+
return item;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const normalized = {
|
|
29
|
+
_context: 'search',
|
|
30
|
+
results,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
if (options.includeQuery !== false && data.query) {
|
|
34
|
+
normalized.query = data.query;
|
|
35
|
+
normalized._exportMeta = { query: data.query };
|
|
36
|
+
}
|
|
37
|
+
if (data.collection) normalized.collection = data.collection;
|
|
38
|
+
if (data.model) normalized.model = data.model;
|
|
39
|
+
|
|
40
|
+
// Flat rows for CSV
|
|
41
|
+
normalized.rows = results.map((r) => ({
|
|
42
|
+
rank: r.rank,
|
|
43
|
+
score: r.score,
|
|
44
|
+
reranked_score: r.rerankedScore ?? '',
|
|
45
|
+
source: r.source,
|
|
46
|
+
text_excerpt: (r.text || '').slice(0, 200),
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
return normalized;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const SEARCH_FORMATS = ['json', 'jsonl', 'csv', 'markdown', 'clipboard'];
|
|
53
|
+
|
|
54
|
+
module.exports = { normalizeSearch, SEARCH_FORMATS };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { buildDependencyGraph } = require('../../workflow');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Normalize a workflow definition for export.
|
|
7
|
+
* @param {object} workflow - Raw workflow JSON
|
|
8
|
+
* @param {object} options
|
|
9
|
+
* @param {boolean} [options.includeExecution=false]
|
|
10
|
+
* @param {boolean} [options.includeMetadata=false]
|
|
11
|
+
* @returns {object} normalized
|
|
12
|
+
*/
|
|
13
|
+
function normalizeWorkflow(workflow, options = {}) {
|
|
14
|
+
const normalized = {
|
|
15
|
+
_context: 'workflow',
|
|
16
|
+
name: workflow.name,
|
|
17
|
+
description: workflow.description,
|
|
18
|
+
version: workflow.version,
|
|
19
|
+
inputs: workflow.inputs || {},
|
|
20
|
+
defaults: workflow.defaults,
|
|
21
|
+
steps: workflow.steps || [],
|
|
22
|
+
output: workflow.output,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Compute dependency map for markdown rendering (Map<string, Set> → plain obj)
|
|
26
|
+
const depGraphMap = buildDependencyGraph(workflow.steps || []);
|
|
27
|
+
const depGraph = {};
|
|
28
|
+
for (const [id, deps] of depGraphMap) {
|
|
29
|
+
depGraph[id] = [...deps];
|
|
30
|
+
}
|
|
31
|
+
normalized._dependencyMap = depGraph;
|
|
32
|
+
|
|
33
|
+
// Count execution layers
|
|
34
|
+
const layerCount = computeLayerCount(workflow.steps || [], depGraph);
|
|
35
|
+
normalized._executionLayers = layerCount;
|
|
36
|
+
|
|
37
|
+
if (options.includeExecution && workflow._execution) {
|
|
38
|
+
normalized._execution = workflow._execution;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (options.includeMetadata) {
|
|
42
|
+
normalized._metadata = {
|
|
43
|
+
_exportedAt: new Date().toISOString(),
|
|
44
|
+
_source: workflow._source || 'local',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return normalized;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function computeLayerCount(steps, depGraph) {
|
|
52
|
+
if (steps.length === 0) return 0;
|
|
53
|
+
const inDegree = {};
|
|
54
|
+
const ids = steps.map((s) => s.id);
|
|
55
|
+
for (const id of ids) inDegree[id] = (depGraph[id] || []).length;
|
|
56
|
+
const remaining = new Set(ids);
|
|
57
|
+
let layers = 0;
|
|
58
|
+
while (remaining.size > 0) {
|
|
59
|
+
const layer = [];
|
|
60
|
+
for (const id of remaining) {
|
|
61
|
+
if ((inDegree[id] || 0) === 0) layer.push(id);
|
|
62
|
+
}
|
|
63
|
+
if (layer.length === 0) break;
|
|
64
|
+
layers++;
|
|
65
|
+
for (const id of layer) {
|
|
66
|
+
remaining.delete(id);
|
|
67
|
+
for (const [depId, deps] of Object.entries(depGraph)) {
|
|
68
|
+
if (remaining.has(depId) && deps.includes(id)) {
|
|
69
|
+
inDegree[depId]--;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return layers;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Supported export formats for workflows */
|
|
78
|
+
const WORKFLOW_FORMATS = ['json', 'markdown', 'mermaid', 'svg', 'png', 'clipboard'];
|
|
79
|
+
|
|
80
|
+
module.exports = { normalizeWorkflow, WORKFLOW_FORMATS };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Copy text content to the system clipboard.
|
|
8
|
+
* @param {string} content - Text to copy
|
|
9
|
+
* @returns {boolean} success
|
|
10
|
+
*/
|
|
11
|
+
function copyToClipboard(content) {
|
|
12
|
+
const platform = os.platform();
|
|
13
|
+
try {
|
|
14
|
+
if (platform === 'darwin') {
|
|
15
|
+
execSync('pbcopy', { input: content, stdio: ['pipe', 'ignore', 'ignore'] });
|
|
16
|
+
} else if (platform === 'linux') {
|
|
17
|
+
execSync('xclip -selection clipboard', { input: content, stdio: ['pipe', 'ignore', 'ignore'] });
|
|
18
|
+
} else if (platform === 'win32') {
|
|
19
|
+
execSync('clip', { input: content, stdio: ['pipe', 'ignore', 'ignore'] });
|
|
20
|
+
} else {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = { copyToClipboard };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Escape a CSV field value.
|
|
5
|
+
* Wraps in quotes if the value contains commas, quotes, or newlines.
|
|
6
|
+
* @param {*} value
|
|
7
|
+
* @returns {string}
|
|
8
|
+
*/
|
|
9
|
+
function escapeField(value) {
|
|
10
|
+
if (value === null || value === undefined) return '';
|
|
11
|
+
const str = String(value);
|
|
12
|
+
if (str.includes('"') || str.includes(',') || str.includes('\n') || str.includes('\r')) {
|
|
13
|
+
return '"' + str.replace(/"/g, '""') + '"';
|
|
14
|
+
}
|
|
15
|
+
return str;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Render an array of objects as CSV.
|
|
20
|
+
* @param {object} normalized - Must have a `rows` or `results` array of flat objects
|
|
21
|
+
* @param {object} options
|
|
22
|
+
* @param {string[]} [options.columns] - Explicit column order; auto-detected if omitted
|
|
23
|
+
* @returns {{ content: string, mimeType: string }}
|
|
24
|
+
*/
|
|
25
|
+
function renderCsv(normalized, options = {}) {
|
|
26
|
+
const rows = normalized.rows || normalized.results || [];
|
|
27
|
+
if (!Array.isArray(rows) || rows.length === 0) {
|
|
28
|
+
return { content: '', mimeType: 'text/csv' };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Determine columns
|
|
32
|
+
const columns = options.columns || Object.keys(rows[0]);
|
|
33
|
+
|
|
34
|
+
const header = columns.map(escapeField).join(',');
|
|
35
|
+
const body = rows.map((row) =>
|
|
36
|
+
columns.map((col) => escapeField(row[col])).join(',')
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
content: [header, ...body].join('\n'),
|
|
41
|
+
mimeType: 'text/csv',
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = { renderCsv, escapeField };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const pkg = require(path.resolve(__dirname, '..', '..', '..', '..', 'package.json'));
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Render normalized data as JSON.
|
|
8
|
+
* @param {object} normalized
|
|
9
|
+
* @param {object} options
|
|
10
|
+
* @returns {{ content: string, mimeType: string }}
|
|
11
|
+
*/
|
|
12
|
+
function renderJson(normalized, options = {}) {
|
|
13
|
+
const output = { ...normalized };
|
|
14
|
+
if (options.includeMetadata !== false) {
|
|
15
|
+
output._exportMeta = {
|
|
16
|
+
exportedAt: new Date().toISOString(),
|
|
17
|
+
vaiVersion: pkg.version,
|
|
18
|
+
...(normalized._exportMeta || {}),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
// Remove internal _exportMeta from source if we rebuilt it
|
|
22
|
+
if (normalized._exportMeta && output._exportMeta !== normalized._exportMeta) {
|
|
23
|
+
delete output._exportMeta;
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
content: JSON.stringify(output, null, 2),
|
|
27
|
+
mimeType: 'application/json',
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Render normalized data as JSONL (one record per line).
|
|
33
|
+
* Expects normalized.results or normalized.items to be an array.
|
|
34
|
+
* @param {object} normalized
|
|
35
|
+
* @param {object} options
|
|
36
|
+
* @returns {{ content: string, mimeType: string }}
|
|
37
|
+
*/
|
|
38
|
+
function renderJsonl(normalized, options = {}) {
|
|
39
|
+
const records = normalized.results || normalized.items || [];
|
|
40
|
+
if (!Array.isArray(records)) {
|
|
41
|
+
throw new Error('JSONL export requires an array of records (results or items)');
|
|
42
|
+
}
|
|
43
|
+
const lines = records.map((r) => JSON.stringify(r));
|
|
44
|
+
return {
|
|
45
|
+
content: lines.join('\n'),
|
|
46
|
+
mimeType: 'application/x-ndjson',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = { renderJson, renderJsonl };
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Render normalized data as Markdown.
|
|
5
|
+
* The shape of `normalized` varies by context — the context module sets a `_context` key.
|
|
6
|
+
* @param {object} normalized
|
|
7
|
+
* @param {object} options
|
|
8
|
+
* @returns {{ content: string, mimeType: string }}
|
|
9
|
+
*/
|
|
10
|
+
function renderMarkdown(normalized, options = {}) {
|
|
11
|
+
const ctx = normalized._context;
|
|
12
|
+
let md;
|
|
13
|
+
switch (ctx) {
|
|
14
|
+
case 'workflow':
|
|
15
|
+
md = renderWorkflowMd(normalized, options);
|
|
16
|
+
break;
|
|
17
|
+
case 'search':
|
|
18
|
+
md = renderSearchMd(normalized, options);
|
|
19
|
+
break;
|
|
20
|
+
case 'chat':
|
|
21
|
+
md = renderChatMd(normalized, options);
|
|
22
|
+
break;
|
|
23
|
+
case 'benchmark':
|
|
24
|
+
md = renderBenchmarkMd(normalized, options);
|
|
25
|
+
break;
|
|
26
|
+
default:
|
|
27
|
+
md = '# Export\n\n```json\n' + JSON.stringify(normalized, null, 2) + '\n```\n';
|
|
28
|
+
}
|
|
29
|
+
return { content: md, mimeType: 'text/markdown' };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ── Workflow ──
|
|
33
|
+
|
|
34
|
+
function renderWorkflowMd(n, opts) {
|
|
35
|
+
const lines = [];
|
|
36
|
+
lines.push(`# ${n.name || 'Workflow'}`);
|
|
37
|
+
lines.push('');
|
|
38
|
+
if (n.description) {
|
|
39
|
+
lines.push(n.description);
|
|
40
|
+
lines.push('');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Metadata
|
|
44
|
+
if (n.version) lines.push(`**Version:** ${n.version}`);
|
|
45
|
+
const tools = (n.steps || []).map((s) => s.tool).filter(Boolean);
|
|
46
|
+
const unique = [...new Set(tools)];
|
|
47
|
+
if (unique.length) lines.push(`**Tools used:** ${unique.join(', ')}`);
|
|
48
|
+
const layers = n._executionLayers || null;
|
|
49
|
+
if (layers) lines.push(`**Execution layers:** ${layers} (sequential)`);
|
|
50
|
+
lines.push('');
|
|
51
|
+
|
|
52
|
+
// Inputs table
|
|
53
|
+
if (n.inputs && Object.keys(n.inputs).length > 0) {
|
|
54
|
+
lines.push('## Inputs');
|
|
55
|
+
lines.push('');
|
|
56
|
+
lines.push('| Parameter | Type | Required | Default | Description |');
|
|
57
|
+
lines.push('|-----------|------|----------|---------|-------------|');
|
|
58
|
+
for (const [key, schema] of Object.entries(n.inputs)) {
|
|
59
|
+
const type = schema.type || 'string';
|
|
60
|
+
const req = schema.required ? 'Yes' : 'No';
|
|
61
|
+
const def = schema.default !== undefined ? String(schema.default) : '—';
|
|
62
|
+
const desc = schema.description || '';
|
|
63
|
+
lines.push(`| ${key} | ${type} | ${req} | ${def} | ${desc} |`);
|
|
64
|
+
}
|
|
65
|
+
lines.push('');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Steps
|
|
69
|
+
if (n.steps && n.steps.length > 0) {
|
|
70
|
+
lines.push('## Steps');
|
|
71
|
+
lines.push('');
|
|
72
|
+
const deps = n._dependencyMap || {};
|
|
73
|
+
n.steps.forEach((step, i) => {
|
|
74
|
+
const label = step.name || step.description || step.id;
|
|
75
|
+
lines.push(`### ${i + 1}. ${label}`);
|
|
76
|
+
if (step.tool) lines.push(`- **Tool:** ${step.tool}`);
|
|
77
|
+
const stepDeps = deps[step.id];
|
|
78
|
+
if (stepDeps && stepDeps.length > 0) {
|
|
79
|
+
lines.push(`- **Depends on:** ${stepDeps.join(', ')}`);
|
|
80
|
+
} else {
|
|
81
|
+
lines.push('- **Depends on:** (none — first step)');
|
|
82
|
+
}
|
|
83
|
+
if (step.description && step.description !== label) {
|
|
84
|
+
lines.push(`- **Configuration:** ${step.description}`);
|
|
85
|
+
}
|
|
86
|
+
lines.push('');
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Output
|
|
91
|
+
if (n.output) {
|
|
92
|
+
lines.push('## Output');
|
|
93
|
+
lines.push('');
|
|
94
|
+
lines.push(`Returns: \`${n.output}\``);
|
|
95
|
+
lines.push('');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return lines.join('\n');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ── Search Results ──
|
|
102
|
+
|
|
103
|
+
function renderSearchMd(n, opts) {
|
|
104
|
+
const lines = [];
|
|
105
|
+
lines.push('# Search Results');
|
|
106
|
+
lines.push('');
|
|
107
|
+
if (n.query) lines.push(`**Query:** ${n.query}`);
|
|
108
|
+
if (n.collection) lines.push(`**Collection:** ${n.collection}`);
|
|
109
|
+
if (n.model) lines.push(`**Model:** ${n.model}`);
|
|
110
|
+
const total = (n.results || []).length;
|
|
111
|
+
lines.push(`**Results:** ${total}`);
|
|
112
|
+
lines.push('');
|
|
113
|
+
|
|
114
|
+
(n.results || []).forEach((r, i) => {
|
|
115
|
+
lines.push(`### ${i + 1}. ${r.source || 'Result'} (score: ${r.score ?? '—'})`);
|
|
116
|
+
if (r.rerankedScore !== undefined) lines.push(`- **Reranked score:** ${r.rerankedScore}`);
|
|
117
|
+
const text = opts.includeFullText ? r.text : (r.text || '').slice(0, 200);
|
|
118
|
+
if (text) {
|
|
119
|
+
lines.push('');
|
|
120
|
+
lines.push(`> ${text}`);
|
|
121
|
+
}
|
|
122
|
+
lines.push('');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return lines.join('\n');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── Chat Session ──
|
|
129
|
+
|
|
130
|
+
function renderChatMd(n, opts) {
|
|
131
|
+
const lines = [];
|
|
132
|
+
const sessionId = n.sessionId || n.id || 'unknown';
|
|
133
|
+
lines.push(`# Chat Session: ${sessionId}`);
|
|
134
|
+
lines.push('');
|
|
135
|
+
if (n.startedAt) lines.push(`**Date:** ${n.startedAt}`);
|
|
136
|
+
if (n.provider && n.model) lines.push(`**Provider:** ${n.provider} (${n.model})`);
|
|
137
|
+
else if (n.model) lines.push(`**Model:** ${n.model}`);
|
|
138
|
+
if (n.collection) lines.push(`**Knowledge Base:** ${n.collection}`);
|
|
139
|
+
const turns = (n.turns || []).length;
|
|
140
|
+
lines.push(`**Turns:** ${turns}`);
|
|
141
|
+
lines.push('');
|
|
142
|
+
lines.push('---');
|
|
143
|
+
lines.push('');
|
|
144
|
+
|
|
145
|
+
(n.turns || []).forEach((turn) => {
|
|
146
|
+
const role = turn.role === 'user' ? 'User' : 'vai';
|
|
147
|
+
lines.push(`**${role}:**`);
|
|
148
|
+
lines.push(turn.content || '');
|
|
149
|
+
if (opts.includeSources !== false && turn.context && turn.context.length > 0) {
|
|
150
|
+
lines.push('');
|
|
151
|
+
lines.push('> **Sources:**');
|
|
152
|
+
turn.context.forEach((src, i) => {
|
|
153
|
+
const rel = src.score !== undefined ? ` (relevance: ${src.score})` : '';
|
|
154
|
+
lines.push(`> ${i + 1}. ${src.source || 'unknown'}${rel}`);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
lines.push('');
|
|
158
|
+
lines.push('---');
|
|
159
|
+
lines.push('');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
return lines.join('\n');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ── Benchmark ──
|
|
166
|
+
|
|
167
|
+
function renderBenchmarkMd(n, opts) {
|
|
168
|
+
const lines = [];
|
|
169
|
+
lines.push('# Benchmark Results');
|
|
170
|
+
lines.push('');
|
|
171
|
+
if (n.name) lines.push(`**Benchmark:** ${n.name}`);
|
|
172
|
+
if (n.date) lines.push(`**Date:** ${n.date}`);
|
|
173
|
+
lines.push('');
|
|
174
|
+
|
|
175
|
+
const results = n.results || n.rows || [];
|
|
176
|
+
if (results.length > 0) {
|
|
177
|
+
const cols = Object.keys(results[0]);
|
|
178
|
+
lines.push('| ' + cols.join(' | ') + ' |');
|
|
179
|
+
lines.push('| ' + cols.map(() => '---').join(' | ') + ' |');
|
|
180
|
+
results.forEach((row) => {
|
|
181
|
+
lines.push('| ' + cols.map((c) => String(row[c] ?? '')).join(' | ') + ' |');
|
|
182
|
+
});
|
|
183
|
+
lines.push('');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return lines.join('\n');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
module.exports = { renderMarkdown };
|