langflower 0.0.10 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -70
- package/docs/public/README.md +2 -2
- package/docs/public/configuration.md +4 -0
- package/docs/public/extending.md +18 -2
- package/docs/public/getting-started.md +7 -0
- package/docs/public/how-it-works.md +18 -11
- package/docs/public/product.md +35 -25
- package/package.json +1 -1
- package/ui-dist/chunk-QI4IYILJ.js +4 -0
- package/ui-dist/{chunk-7UMVYN3A.js → chunk-UG3GFV4Y.js} +1 -1
- package/ui-dist/index.html +2 -2
- package/ui-dist/main-ODIDCCNX.js +315 -0
- package/ui-dist/styles-2JMBAQC3.css +1 -0
- package/vendor/common-nodes/dist/ai/features/llm-session/llm-session-shell.d.ts +2 -2
- package/vendor/common-nodes/dist/ai/features/llm-session/llm-session-shell.js +39 -7
- package/vendor/common-nodes/dist/ai/features/llm-session/run-session-machine.d.ts +11 -0
- package/vendor/common-nodes/dist/ai/features/llm-session/run-session-machine.js +2 -2
- package/vendor/common-nodes/dist/ai/features/run-host-services.d.ts +10 -0
- package/vendor/common-nodes/dist/ai/nodes/critique/node.js +5 -1
- package/vendor/common-nodes/dist/ai/nodes/fake-llm/node.js +7 -1
- package/vendor/common-nodes/dist/ai/nodes/openai-llm/node.js +5 -1
- package/vendor/common-nodes/dist/ai/nodes/review/node.js +7 -1
- package/vendor/common-nodes/dist/ai/nodes/sub-agent/node.js +62 -22
- package/vendor/common-nodes/dist/catalog.js +10 -0
- package/vendor/common-nodes/dist/crawl/crawl/node.js +11 -3
- package/vendor/common-nodes/dist/crawl/crawl-tools/node.js +5 -1
- package/vendor/common-nodes/dist/crawl/extract-links/node.js +7 -1
- package/vendor/common-nodes/dist/crawl/fetch-url/node.js +13 -4
- package/vendor/common-nodes/dist/crawl/save-page/node.js +11 -3
- package/vendor/common-nodes/dist/embeddings/create-embedding.d.ts +39 -0
- package/vendor/common-nodes/dist/embeddings/create-embedding.js +154 -0
- package/vendor/common-nodes/dist/embeddings/embed-provider/node.d.ts +53 -0
- package/vendor/common-nodes/dist/embeddings/embed-provider/node.js +126 -0
- package/vendor/common-nodes/dist/embeddings/embed-similarity/node.d.ts +20 -0
- package/vendor/common-nodes/dist/embeddings/embed-similarity/node.js +81 -0
- package/vendor/common-nodes/dist/embeddings/embed-text/node.d.ts +53 -0
- package/vendor/common-nodes/dist/embeddings/embed-text/node.js +91 -0
- package/vendor/common-nodes/dist/embeddings/from-embedding.d.ts +8 -0
- package/vendor/common-nodes/dist/embeddings/from-embedding.js +24 -0
- package/vendor/common-nodes/dist/embeddings/resolve-embedding-provider-model.d.ts +11 -0
- package/vendor/common-nodes/dist/embeddings/resolve-embedding-provider-model.js +8 -0
- package/vendor/common-nodes/dist/flow/checkpoint/node.js +7 -3
- package/vendor/common-nodes/dist/flow/delay/node.d.ts +2 -6
- package/vendor/common-nodes/dist/flow/delay/node.js +13 -9
- package/vendor/common-nodes/dist/flow/loop/node.js +7 -1
- package/vendor/common-nodes/dist/flow/merge/node.js +7 -1
- package/vendor/common-nodes/dist/flow/repeat/node.js +5 -1
- package/vendor/common-nodes/dist/flow/router/node.js +7 -1
- package/vendor/common-nodes/dist/hitl/chat-input/node.d.ts +2 -2
- package/vendor/common-nodes/dist/hitl/chat-input/node.js +8 -4
- package/vendor/common-nodes/dist/hitl/review-gate/node.js +11 -5
- package/vendor/common-nodes/dist/langflower-tools/node.js +5 -1
- package/vendor/common-nodes/dist/logic/assert/node.js +7 -1
- package/vendor/common-nodes/dist/logic/compare/node.js +7 -1
- package/vendor/common-nodes/dist/logic/gate/node.js +7 -1
- package/vendor/common-nodes/dist/logic/if/node.js +7 -1
- package/vendor/common-nodes/dist/logic/switch/node.js +7 -1
- package/vendor/common-nodes/dist/mcp/mcp-http/node.js +55 -53
- package/vendor/common-nodes/dist/mcp/mcp-stdio/node.js +52 -50
- package/vendor/common-nodes/dist/memory/memory-tools/node.js +5 -1
- package/vendor/common-nodes/dist/output/finish/node.js +13 -1
- package/vendor/common-nodes/dist/output/preview/node.js +9 -2
- package/vendor/common-nodes/dist/primitives/boolean/node.js +7 -1
- package/vendor/common-nodes/dist/primitives/number/node.js +7 -1
- package/vendor/common-nodes/dist/primitives/string/node.js +7 -1
- package/vendor/common-nodes/dist/primitives/string-multiline/node.d.ts +17 -0
- package/vendor/common-nodes/dist/primitives/string-multiline/node.js +31 -0
- package/vendor/common-nodes/dist/text/append-file/node.js +11 -3
- package/vendor/common-nodes/dist/text/concat/node.js +5 -1
- package/vendor/common-nodes/dist/text/read-file/node.js +9 -3
- package/vendor/common-nodes/dist/text/split-paced/node.d.ts +21 -0
- package/vendor/common-nodes/dist/text/split-paced/node.js +93 -0
- package/vendor/common-nodes/dist/text/write-file/node.js +11 -3
- package/vendor/common-nodes/dist/tools/tool-collection/node.js +5 -2
- package/vendor/common-nodes/package.json +4 -0
- package/vendor/node-sdk/dist/node-factory/define-embed/embed-handle.d.ts +25 -0
- package/vendor/node-sdk/dist/node-factory/define-embed/embed-handle.js +25 -0
- package/vendor/node-sdk/dist/node-factory/define-mcp/mcp-handle.d.ts +1 -0
- package/vendor/node-sdk/dist/node-factory/define-mcp/mcp-handle.js +1 -0
- package/vendor/node-sdk/dist/node-factory/define-node/define-node.d.ts +2 -0
- package/vendor/node-sdk/dist/node-factory/define-node/define-node.js +4 -1
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/define-reactive-node.d.ts +3 -1
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/define-reactive-node.js +2 -1
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/io-helpers.d.ts +15 -0
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/io-helpers.js +17 -1
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/port-meta.d.ts +17 -4
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/test/samples/delay-node.js +4 -2
- package/vendor/node-sdk/dist/node-factory/define-reactive-node/types.d.ts +4 -3
- package/vendor/node-sdk/dist/testing/create-node-harness.d.ts +30 -0
- package/vendor/node-sdk/dist/testing/create-node-harness.js +68 -0
- package/vendor/node-sdk/package.json +4 -4
- package/vendor/runtime/dist/types.d.ts +1 -1
- package/vendor/server/dist/bootstrap/project-bootstrap.service.d.ts +1 -1
- package/vendor/server/dist/bootstrap/seed-skeleton.d.ts +2 -2
- package/vendor/server/dist/bootstrap/seed-skeleton.js +17 -2
- package/vendor/server/dist/bridge/bind-embed-context.d.ts +7 -0
- package/vendor/server/dist/bridge/bind-embed-context.js +16 -0
- package/vendor/server/dist/bridge/build-execution-context.d.ts +2 -2
- package/vendor/server/dist/bridge/build-execution-context.js +6 -2
- package/vendor/server/dist/bridge/emit-bootstrap.js +4 -1
- package/vendor/server/dist/bridge/settings-draft-controller.js +7 -3
- package/vendor/server/dist/bridge/wire-config-handlers.js +2 -0
- package/vendor/server/dist/bridge/wire-editor-handlers.js +14 -0
- package/vendor/server/dist/config/langflower-config.service.d.ts +2 -0
- package/vendor/server/dist/config/langflower-config.service.js +30 -0
- package/vendor/server/dist/session/build-session-bootstrap.js +1 -0
- package/vendor/server/dist/session/langflower-session.d.ts +3 -1
- package/vendor/server/dist/session/langflower-session.js +3 -1
- package/vendor/server/dist/workflow/apply-editor-mutation.js +6 -0
- package/vendor/server/dist/workflow/workflow-persisted-inputs.js +8 -6
- package/vendor/server/skeleton/instructions.md +15 -3
- package/vendor/server/skeleton/nodes/hello-embed/README.md +185 -0
- package/vendor/server/skeleton/nodes/hello-embed/hello-embed.nodes.test.ts +181 -0
- package/vendor/server/skeleton/nodes/hello-embed/ingest.ts +126 -0
- package/vendor/server/skeleton/nodes/hello-embed/lib/chunk-markdown.test.ts +40 -0
- package/vendor/server/skeleton/nodes/hello-embed/lib/chunk-markdown.ts +123 -0
- package/vendor/server/skeleton/nodes/hello-embed/lib/ingest-search.test.ts +180 -0
- package/vendor/server/skeleton/nodes/hello-embed/lib/ingest.ts +128 -0
- package/vendor/server/skeleton/nodes/hello-embed/lib/paths.test.ts +20 -0
- package/vendor/server/skeleton/nodes/hello-embed/lib/paths.ts +70 -0
- package/vendor/server/skeleton/nodes/hello-embed/lib/schema.ts +21 -0
- package/vendor/server/skeleton/nodes/hello-embed/lib/search.ts +254 -0
- package/vendor/server/skeleton/nodes/hello-embed/lib/text-embedder.ts +14 -0
- package/vendor/server/skeleton/nodes/hello-embed/lib/vectors.ts +126 -0
- package/vendor/server/skeleton/nodes/hello-embed/lib/walk-markdown.test.ts +62 -0
- package/vendor/server/skeleton/nodes/hello-embed/lib/walk-markdown.ts +67 -0
- package/vendor/server/skeleton/nodes/hello-embed/package.json +11 -0
- package/vendor/server/skeleton/nodes/hello-embed/search-handle.ts +119 -0
- package/vendor/server/skeleton/nodes/hello-embed/search.ts +98 -0
- package/vendor/server/skeleton/nodes/hello-embed/tsconfig.json +14 -0
- package/vendor/server/skeleton/nodes/my-nodes/README.md +13 -3
- package/vendor/server/skeleton/schemas/langflower-config.schema.json +4 -0
- package/vendor/server/skeleton/skills/langflower-helper/SKILL.md +99 -26
- package/vendor/server/skeleton/skills/langflower-helper/architecture.md +11 -6
- package/vendor/server/skeleton/skills/langflower-helper/layout.md +24 -18
- package/vendor/server/skeleton/skills/langflower-node-writer/SKILL.md +8 -1
- package/vendor/server/skeleton/workflows/kb-ingest.json +88 -0
- package/vendor/server/skeleton/workflows/kb-manual-search.json +105 -0
- package/vendor/server/skeleton/workflows/kb-rag.json +192 -0
- package/vendor/server/skeleton/workflows/kb-tool.json +111 -0
- package/vendor/shared/dist/langflower-bus-config.d.ts +22 -6
- package/vendor/shared/dist/langflower-bus-config.js +23 -7
- package/vendor/shared/dist/langflower-config/merge-langflower-config-layers.test.js +8 -0
- package/vendor/shared/dist/langflower-config/settings-draft.d.ts +5 -0
- package/vendor/shared/dist/langflower-config/settings-draft.js +15 -7
- package/vendor/shared/dist/langflower.d.ts +1 -1
- package/vendor/shared/dist/langflower.js +1 -1
- package/vendor/shared/dist/types/langflower-bootstrap.d.ts +6 -0
- package/vendor/shared/dist/types/langflower-config.d.ts +17 -0
- package/vendor/shared/dist/types/langflower-palette.d.ts +2 -1
- package/vendor/tools/dist/harness-types.d.ts +1 -1
- package/vendor/tools/dist/mcp/build-mcp-handle.d.ts +3 -21
- package/vendor/tools/dist/mcp/build-mcp-handle.js +2 -11
- package/vendor/tools/dist/mcp/create-system-mcp-handles.d.ts +9 -6
- package/vendor/tools/dist/mcp/create-system-mcp-handles.js +21 -17
- package/vendor/tools/package.json +1 -0
- package/ui-dist/chunk-MDGXX73Z.js +0 -4
- package/ui-dist/main-Z7CPFXFA.js +0 -313
- package/ui-dist/styles-2TMDAUD7.css +0 -1
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
export const l2Normalize = (values: readonly number[]): Float32Array => {
|
|
2
|
+
const out = new Float32Array(values.length);
|
|
3
|
+
let sumSq = 0;
|
|
4
|
+
for (let i = 0; i < values.length; i += 1) {
|
|
5
|
+
const n = values[i] ?? 0;
|
|
6
|
+
out[i] = n;
|
|
7
|
+
sumSq += n * n;
|
|
8
|
+
}
|
|
9
|
+
const norm = Math.sqrt(sumSq);
|
|
10
|
+
if (norm === 0) {
|
|
11
|
+
return out;
|
|
12
|
+
}
|
|
13
|
+
const scale = 1 / norm;
|
|
14
|
+
for (let i = 0; i < out.length; i += 1) {
|
|
15
|
+
out[i] = (out[i] ?? 0) * scale;
|
|
16
|
+
}
|
|
17
|
+
return out;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const float32ToBlob = (values: Float32Array): Uint8Array => {
|
|
21
|
+
const bytes = new Uint8Array(values.byteLength);
|
|
22
|
+
bytes.set(
|
|
23
|
+
new Uint8Array(values.buffer, values.byteOffset, values.byteLength),
|
|
24
|
+
);
|
|
25
|
+
return bytes;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const blobToFloat32 = (blob: Uint8Array): Float32Array => {
|
|
29
|
+
const copy = new Uint8Array(blob.byteLength);
|
|
30
|
+
copy.set(blob);
|
|
31
|
+
return new Float32Array(copy.buffer);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type VecIndex = {
|
|
35
|
+
readonly sqlitePath: string;
|
|
36
|
+
readonly mtimeMs: number;
|
|
37
|
+
readonly dim: number;
|
|
38
|
+
readonly ids: readonly string[];
|
|
39
|
+
readonly matrix: Float32Array;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const vecCache = new Map<string, VecIndex>();
|
|
43
|
+
|
|
44
|
+
export const loadVecIndex = (
|
|
45
|
+
sqlitePath: string,
|
|
46
|
+
rows: readonly {
|
|
47
|
+
readonly id: string;
|
|
48
|
+
readonly dim: number;
|
|
49
|
+
readonly vector: Uint8Array;
|
|
50
|
+
}[],
|
|
51
|
+
mtimeMs: number,
|
|
52
|
+
): VecIndex => {
|
|
53
|
+
const cached = vecCache.get(sqlitePath);
|
|
54
|
+
if (cached !== undefined && cached.mtimeMs === mtimeMs) {
|
|
55
|
+
return cached;
|
|
56
|
+
}
|
|
57
|
+
if (rows.length === 0) {
|
|
58
|
+
const empty: VecIndex = {
|
|
59
|
+
sqlitePath,
|
|
60
|
+
mtimeMs,
|
|
61
|
+
dim: 0,
|
|
62
|
+
ids: [],
|
|
63
|
+
matrix: new Float32Array(0),
|
|
64
|
+
};
|
|
65
|
+
vecCache.set(sqlitePath, empty);
|
|
66
|
+
return empty;
|
|
67
|
+
}
|
|
68
|
+
const dim = rows[0]?.dim ?? 0;
|
|
69
|
+
const ids: string[] = [];
|
|
70
|
+
const matrix = new Float32Array(rows.length * dim);
|
|
71
|
+
rows.forEach((row, index) => {
|
|
72
|
+
if (row.dim !== dim) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`chunk_vec dim mismatch: ${String(row.dim)} vs ${String(dim)} at ${row.id}`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
ids.push(row.id);
|
|
78
|
+
const floats = blobToFloat32(row.vector);
|
|
79
|
+
if (floats.length !== dim) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
`chunk_vec blob length ${String(floats.length)} != dim ${String(dim)} at ${row.id}`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
matrix.set(floats, index * dim);
|
|
85
|
+
});
|
|
86
|
+
const index: VecIndex = {
|
|
87
|
+
sqlitePath,
|
|
88
|
+
mtimeMs,
|
|
89
|
+
dim,
|
|
90
|
+
ids,
|
|
91
|
+
matrix,
|
|
92
|
+
};
|
|
93
|
+
vecCache.set(sqlitePath, index);
|
|
94
|
+
return index;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const topCosine = (
|
|
98
|
+
index: VecIndex,
|
|
99
|
+
query: Float32Array,
|
|
100
|
+
limit: number,
|
|
101
|
+
): readonly { readonly id: string; readonly score: number }[] => {
|
|
102
|
+
if (index.dim === 0 || index.ids.length === 0) {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
if (query.length !== index.dim) {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
const scored: { id: string; score: number }[] = [];
|
|
109
|
+
for (let row = 0; row < index.ids.length; row += 1) {
|
|
110
|
+
const offset = row * index.dim;
|
|
111
|
+
let sum = 0;
|
|
112
|
+
for (let i = 0; i < index.dim; i += 1) {
|
|
113
|
+
sum += (query[i] ?? 0) * (index.matrix[offset + i] ?? 0);
|
|
114
|
+
}
|
|
115
|
+
const id = index.ids[row];
|
|
116
|
+
if (id !== undefined) {
|
|
117
|
+
scored.push({ id, score: sum });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
scored.sort((a, b) => b.score - a.score);
|
|
121
|
+
return scored.slice(0, Math.max(0, limit));
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export const clearVecCache = (): void => {
|
|
125
|
+
vecCache.clear();
|
|
126
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import { walkMarkdown } from './walk-markdown.ts';
|
|
6
|
+
|
|
7
|
+
describe('walkMarkdown', () => {
|
|
8
|
+
let root: string;
|
|
9
|
+
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
root = await fs.mkdtemp(path.join(os.tmpdir(), 'hello-embed-walk-'));
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(async () => {
|
|
15
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('walks recursive md and skips node_modules, .git, and .langflower/.cache', async () => {
|
|
19
|
+
await fs.mkdir(path.join(root, 'docs'), { recursive: true });
|
|
20
|
+
await fs.mkdir(path.join(root, 'node_modules', 'pkg'), {
|
|
21
|
+
recursive: true,
|
|
22
|
+
});
|
|
23
|
+
await fs.mkdir(path.join(root, '.git'), { recursive: true });
|
|
24
|
+
await fs.mkdir(
|
|
25
|
+
path.join(root, '.langflower', '.cache', 'hello-embed'),
|
|
26
|
+
{ recursive: true },
|
|
27
|
+
);
|
|
28
|
+
await fs.mkdir(path.join(root, '.langflower', 'skills'), {
|
|
29
|
+
recursive: true,
|
|
30
|
+
});
|
|
31
|
+
await fs.writeFile(path.join(root, 'README.md'), '# Root\n', 'utf8');
|
|
32
|
+
await fs.writeFile(
|
|
33
|
+
path.join(root, 'docs', 'guide.md'),
|
|
34
|
+
'# Guide\n',
|
|
35
|
+
'utf8',
|
|
36
|
+
);
|
|
37
|
+
await fs.writeFile(
|
|
38
|
+
path.join(root, 'node_modules', 'pkg', 'secret.md'),
|
|
39
|
+
'nope',
|
|
40
|
+
'utf8',
|
|
41
|
+
);
|
|
42
|
+
await fs.writeFile(path.join(root, '.git', 'HEAD.md'), 'nope', 'utf8');
|
|
43
|
+
await fs.writeFile(
|
|
44
|
+
path.join(root, '.langflower', '.cache', 'hello-embed', 'x.md'),
|
|
45
|
+
'nope',
|
|
46
|
+
'utf8',
|
|
47
|
+
);
|
|
48
|
+
await fs.writeFile(
|
|
49
|
+
path.join(root, '.langflower', 'skills', 'SKILL.md'),
|
|
50
|
+
'# Skill\n',
|
|
51
|
+
'utf8',
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const files = await walkMarkdown(root);
|
|
55
|
+
const rels = files.map((file) => file.relPath).sort();
|
|
56
|
+
expect(rels).toEqual([
|
|
57
|
+
'.langflower/skills/SKILL.md',
|
|
58
|
+
'README.md',
|
|
59
|
+
'docs/guide.md',
|
|
60
|
+
]);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {
|
|
4
|
+
asPosixRelative,
|
|
5
|
+
isCacheRelative,
|
|
6
|
+
MAX_FILE_BYTES,
|
|
7
|
+
SKIP_DIR_NAMES,
|
|
8
|
+
} from './paths.ts';
|
|
9
|
+
|
|
10
|
+
export type MarkdownFile = {
|
|
11
|
+
readonly absPath: string;
|
|
12
|
+
readonly relPath: string;
|
|
13
|
+
readonly text: string;
|
|
14
|
+
readonly skipped?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const isMarkdownName = (name: string): boolean =>
|
|
18
|
+
name.toLowerCase().endsWith('.md');
|
|
19
|
+
|
|
20
|
+
export const walkMarkdown = async (
|
|
21
|
+
rootDir: string,
|
|
22
|
+
): Promise<readonly MarkdownFile[]> => {
|
|
23
|
+
const files: MarkdownFile[] = [];
|
|
24
|
+
|
|
25
|
+
const visit = async (absDir: string, relPosix: string): Promise<void> => {
|
|
26
|
+
const entries = await fs.readdir(absDir, { withFileTypes: true });
|
|
27
|
+
for (const entry of entries) {
|
|
28
|
+
const childRel =
|
|
29
|
+
relPosix.length === 0
|
|
30
|
+
? entry.name
|
|
31
|
+
: `${relPosix}/${entry.name}`;
|
|
32
|
+
const childAbs = path.join(absDir, entry.name);
|
|
33
|
+
if (entry.isDirectory()) {
|
|
34
|
+
if (SKIP_DIR_NAMES.has(entry.name)) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (isCacheRelative(asPosixRelative(childRel))) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
await visit(childAbs, asPosixRelative(childRel));
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (!entry.isFile() || !isMarkdownName(entry.name)) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (isCacheRelative(asPosixRelative(childRel))) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const stat = await fs.stat(childAbs);
|
|
50
|
+
const relPath = asPosixRelative(childRel);
|
|
51
|
+
if (stat.size > MAX_FILE_BYTES) {
|
|
52
|
+
files.push({
|
|
53
|
+
absPath: childAbs,
|
|
54
|
+
relPath,
|
|
55
|
+
text: '',
|
|
56
|
+
skipped: `skipped ${relPath} (${String(stat.size)} bytes > ${String(MAX_FILE_BYTES)})`,
|
|
57
|
+
});
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const text = await fs.readFile(childAbs, 'utf8');
|
|
61
|
+
files.push({ absPath: childAbs, relPath, text });
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
await visit(rootDir, '');
|
|
66
|
+
return files;
|
|
67
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineReactiveNode,
|
|
3
|
+
EMBED_HANDLE_WIRE_TYPE,
|
|
4
|
+
isEmbedHandle,
|
|
5
|
+
TOOL_HANDLE_WIRE_TYPE,
|
|
6
|
+
type ToolHandle,
|
|
7
|
+
} from '@langflower/node-sdk';
|
|
8
|
+
import {
|
|
9
|
+
clampTopK,
|
|
10
|
+
DEFAULT_SEARCH_TOP_K,
|
|
11
|
+
DEFAULT_SQLITE_PATH,
|
|
12
|
+
resolveSqlitePath,
|
|
13
|
+
} from './lib/paths.ts';
|
|
14
|
+
import { runSearch } from './lib/search.ts';
|
|
15
|
+
import { textEmbedderFromHandle } from './lib/text-embedder.ts';
|
|
16
|
+
|
|
17
|
+
const asString = (value: unknown, fallback: string): string => {
|
|
18
|
+
if (typeof value === 'string') {
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
return fallback;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const buildSearchTool = (
|
|
25
|
+
embedInput: unknown,
|
|
26
|
+
sqlitePath: string,
|
|
27
|
+
): readonly ToolHandle[] => {
|
|
28
|
+
if (!isEmbedHandle(embedInput)) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
'hello-embed-search-handle requires a wired embed input from common-embed-provider.',
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
const embedder = textEmbedderFromHandle(embedInput, 'query');
|
|
34
|
+
return [
|
|
35
|
+
{
|
|
36
|
+
toolId: 'project_search',
|
|
37
|
+
name: 'project_search',
|
|
38
|
+
description:
|
|
39
|
+
'Hybrid search (vector + keyword) over the project markdown index from hello-embed-ingest. Returns Question + Context with full chunk bodies (path, heading, RRF score). Empty only if the index has no chunks or the query is empty.',
|
|
40
|
+
inputSchema: {
|
|
41
|
+
type: 'object',
|
|
42
|
+
properties: {
|
|
43
|
+
query: {
|
|
44
|
+
type: 'string',
|
|
45
|
+
description: 'Search phrase.',
|
|
46
|
+
},
|
|
47
|
+
topK: {
|
|
48
|
+
type: 'integer',
|
|
49
|
+
description: 'Max hits (default 8).',
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
required: ['query'],
|
|
53
|
+
additionalProperties: false,
|
|
54
|
+
},
|
|
55
|
+
invoke: async (args) => {
|
|
56
|
+
const query = typeof args.query === 'string' ? args.query : '';
|
|
57
|
+
const result = await runSearch({
|
|
58
|
+
sqlitePath,
|
|
59
|
+
query,
|
|
60
|
+
topK: clampTopK(args.topK, DEFAULT_SEARCH_TOP_K),
|
|
61
|
+
embedder,
|
|
62
|
+
});
|
|
63
|
+
return result.text;
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
];
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* LLM inventory: project_search over the hello-embed sqlite index.
|
|
71
|
+
*/
|
|
72
|
+
export default defineReactiveNode({
|
|
73
|
+
type: 'hello-embed-search-handle',
|
|
74
|
+
displayName: 'Hello Embed Search Handle',
|
|
75
|
+
category: 'Hello Embed',
|
|
76
|
+
description:
|
|
77
|
+
'Emits project_search (hybrid retrieve, full chunks) for an agent tools port. Wire embed from common-embed-provider. Uses the same sqlitePath default as ingest/search.',
|
|
78
|
+
uiSchema: [
|
|
79
|
+
{
|
|
80
|
+
field: 'sqlitePath',
|
|
81
|
+
type: 'string',
|
|
82
|
+
label: 'SQLite path',
|
|
83
|
+
default: DEFAULT_SQLITE_PATH,
|
|
84
|
+
},
|
|
85
|
+
] as const,
|
|
86
|
+
bind(ctx, { makeInput, configureOutput, combineInputs }) {
|
|
87
|
+
const embed = makeInput<unknown>('embed', {
|
|
88
|
+
name: 'embed',
|
|
89
|
+
wireType: EMBED_HANDLE_WIRE_TYPE,
|
|
90
|
+
required: true,
|
|
91
|
+
description: 'Wire from common-embed-provider (fan-out OK).',
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const tools$ = combineInputs([embed, ctx], ([embedInput, ec]) => {
|
|
95
|
+
const projectDir = String(ec.projectDir ?? '');
|
|
96
|
+
if (projectDir.length === 0) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
'hello-embed-search-handle requires ctx.projectDir.',
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return buildSearchTool(
|
|
102
|
+
embedInput,
|
|
103
|
+
resolveSqlitePath(
|
|
104
|
+
projectDir,
|
|
105
|
+
asString(ec.params.sqlitePath, DEFAULT_SQLITE_PATH),
|
|
106
|
+
),
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
inputs: [embed],
|
|
112
|
+
outputs: [
|
|
113
|
+
configureOutput('tools', tools$, {
|
|
114
|
+
wireType: TOOL_HANDLE_WIRE_TYPE,
|
|
115
|
+
}),
|
|
116
|
+
],
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineNode,
|
|
3
|
+
EMBED_HANDLE_WIRE_TYPE,
|
|
4
|
+
isEmbedHandle,
|
|
5
|
+
} from '@langflower/node-sdk';
|
|
6
|
+
import {
|
|
7
|
+
clampTopK,
|
|
8
|
+
DEFAULT_SEARCH_TOP_K,
|
|
9
|
+
DEFAULT_SQLITE_PATH,
|
|
10
|
+
resolveSqlitePath,
|
|
11
|
+
} from './lib/paths.ts';
|
|
12
|
+
import { runSearch } from './lib/search.ts';
|
|
13
|
+
import { textEmbedderFromHandle } from './lib/text-embedder.ts';
|
|
14
|
+
|
|
15
|
+
const asString = (value: unknown, fallback: string): string => {
|
|
16
|
+
if (typeof value === 'string') {
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
return fallback;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Hybrid search over the hello-embed sqlite index (cosine + FTS5, RRF).
|
|
24
|
+
*/
|
|
25
|
+
export default defineNode({
|
|
26
|
+
type: 'hello-embed-search',
|
|
27
|
+
displayName: 'Hello Embed Search',
|
|
28
|
+
category: 'Hello Embed',
|
|
29
|
+
description:
|
|
30
|
+
'Retrieve markdown chunks (vector cosine + FTS5, RRF top-K). text is Question + full-chunk Context for an LLM userPrompt. Wire embed from common-embed-provider.',
|
|
31
|
+
uiSchema: [
|
|
32
|
+
{
|
|
33
|
+
field: 'sqlitePath',
|
|
34
|
+
type: 'string',
|
|
35
|
+
label: 'SQLite path',
|
|
36
|
+
default: DEFAULT_SQLITE_PATH,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
field: 'topK',
|
|
40
|
+
type: 'number',
|
|
41
|
+
label: 'Top K',
|
|
42
|
+
default: DEFAULT_SEARCH_TOP_K,
|
|
43
|
+
min: 1,
|
|
44
|
+
max: 50,
|
|
45
|
+
step: 1,
|
|
46
|
+
},
|
|
47
|
+
] as const,
|
|
48
|
+
inputs: {
|
|
49
|
+
query: {
|
|
50
|
+
wireType: 'string',
|
|
51
|
+
required: true,
|
|
52
|
+
description: 'Search phrase.',
|
|
53
|
+
},
|
|
54
|
+
embed: {
|
|
55
|
+
wireType: EMBED_HANDLE_WIRE_TYPE,
|
|
56
|
+
required: true,
|
|
57
|
+
description: 'Wire from common-embed-provider (fan-out OK).',
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
outputs: {
|
|
61
|
+
hits: {
|
|
62
|
+
wireType: 'json',
|
|
63
|
+
description:
|
|
64
|
+
'Top-K hits with path, heading, RRF score, and full chunk text.',
|
|
65
|
+
},
|
|
66
|
+
text: {
|
|
67
|
+
wireType: 'string',
|
|
68
|
+
description:
|
|
69
|
+
'Question + Context (full retrieved chunks) for Preview or LLM userPrompt.',
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
async execute(ctx, inputs) {
|
|
73
|
+
const embedInput = inputs['embed'];
|
|
74
|
+
if (!isEmbedHandle(embedInput)) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
'hello-embed-search requires a wired embed input from common-embed-provider.',
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
const projectDir = String(ctx.projectDir ?? '');
|
|
80
|
+
if (projectDir.length === 0) {
|
|
81
|
+
throw new Error('hello-embed-search requires ctx.projectDir.');
|
|
82
|
+
}
|
|
83
|
+
const query = typeof inputs.query === 'string' ? inputs.query : '';
|
|
84
|
+
const result = await runSearch({
|
|
85
|
+
sqlitePath: resolveSqlitePath(
|
|
86
|
+
projectDir,
|
|
87
|
+
asString(ctx.params.sqlitePath, DEFAULT_SQLITE_PATH),
|
|
88
|
+
),
|
|
89
|
+
query,
|
|
90
|
+
topK: clampTopK(ctx.params.topK, DEFAULT_SEARCH_TOP_K),
|
|
91
|
+
embedder: textEmbedderFromHandle(embedInput, 'query'),
|
|
92
|
+
});
|
|
93
|
+
return {
|
|
94
|
+
hits: result.hits,
|
|
95
|
+
text: result.text,
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"noEmit": true,
|
|
8
|
+
"allowImportingTsExtensions": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"types": ["node"]
|
|
11
|
+
},
|
|
12
|
+
"include": ["./**/*.ts"],
|
|
13
|
+
"exclude": ["./**/*.test.ts", "node_modules"]
|
|
14
|
+
}
|
|
@@ -17,7 +17,10 @@ its own `package.json`.
|
|
|
17
17
|
git-diff-tool.ts ← export default defineToolRegistrations({…})
|
|
18
18
|
review-gate.ts ← export default defineReactiveNode({…})
|
|
19
19
|
# add more *.ts files as needed
|
|
20
|
-
|
|
20
|
+
hello-embed/ ← seeded sibling sample (markdown vectors)
|
|
21
|
+
package.json
|
|
22
|
+
ingest.ts / search.ts / search-handle.ts
|
|
23
|
+
other-pack/ ← optional extra shareable pack
|
|
21
24
|
package.json
|
|
22
25
|
…
|
|
23
26
|
```
|
|
@@ -53,6 +56,7 @@ and RxJS stay external.
|
|
|
53
56
|
## Author API
|
|
54
57
|
|
|
55
58
|
Prefer **`defineNode`** for sync/Promise nodes (no RxJS in the author file).
|
|
59
|
+
Async `execute` already stamps pending on outputs — do not call `withLoading`.
|
|
56
60
|
|
|
57
61
|
Use **`defineToolRegistrations`** when the node should emit LLM-callable
|
|
58
62
|
`ToolHandle`s on a `tools` port (wire into an agent / LLM `tools` input).
|
|
@@ -190,8 +194,14 @@ TypeScript is your friend: it catches many mistakes in the **IDE** and at
|
|
|
190
194
|
- Prefer fixing those errors in the editor over “happy path” runtime debugging
|
|
191
195
|
(start workflow → wait → guess which node broke).
|
|
192
196
|
|
|
193
|
-
`tsconfig.json` here is for IDE highlight and `tsc --noEmit
|
|
194
|
-
even though Langflower bundles with esbuild
|
|
197
|
+
`tsconfig.json` here is for IDE highlight and `tsc --noEmit` (Langflower’s
|
|
198
|
+
pack compile gate). Keep using it even though Langflower bundles with esbuild
|
|
199
|
+
for load.
|
|
200
|
+
|
|
201
|
+
If this pack (or a sibling) uses **explicit `.ts` import paths**
|
|
202
|
+
(`from './lib/x.ts'`), add `"allowImportingTsExtensions": true` next to
|
|
203
|
+
`"noEmit": true`. Copy `hello-embed/tsconfig.json`. Without the flag the
|
|
204
|
+
pack does not compile. Extensionless imports (this seed) do not need it.
|
|
195
205
|
|
|
196
206
|
## Add a node
|
|
197
207
|
|
|
@@ -91,6 +91,10 @@
|
|
|
91
91
|
"composerHeight": { "type": "number" }
|
|
92
92
|
}
|
|
93
93
|
},
|
|
94
|
+
"paletteVisible": {
|
|
95
|
+
"type": "boolean",
|
|
96
|
+
"description": "Left node palette shown. Omit = visible. Project-only (not merged from global config as a product key; same class as dividerPositions)."
|
|
97
|
+
},
|
|
94
98
|
"permission": {
|
|
95
99
|
"type": "object",
|
|
96
100
|
"description": "Runtime allow/ask/deny for harness tools (≠ Inspector enabledToolIds).",
|