n2-qln 3.4.2 → 4.1.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.ko.md +459 -470
- package/README.md +459 -490
- package/dist/index.d.ts +3 -0
- package/dist/index.js +87 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/config.d.ts +9 -0
- package/{lib → dist/lib}/config.js +23 -27
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/embedding.d.ts +28 -0
- package/{lib → dist/lib}/embedding.js +45 -47
- package/dist/lib/embedding.js.map +1 -0
- package/dist/lib/executor.d.ts +57 -0
- package/dist/lib/executor.js +175 -0
- package/dist/lib/executor.js.map +1 -0
- package/dist/lib/mcp-discovery.d.ts +83 -0
- package/dist/lib/mcp-discovery.js +206 -0
- package/dist/lib/mcp-discovery.js.map +1 -0
- package/dist/lib/provider-loader.d.ts +13 -0
- package/dist/lib/provider-loader.js +144 -0
- package/dist/lib/provider-loader.js.map +1 -0
- package/dist/lib/registry.d.ts +38 -0
- package/{lib → dist/lib}/registry.js +103 -101
- package/dist/lib/registry.js.map +1 -0
- package/dist/lib/router.d.ts +63 -0
- package/{lib → dist/lib}/router.js +75 -117
- package/dist/lib/router.js.map +1 -0
- package/dist/lib/schema.d.ts +20 -0
- package/{lib → dist/lib}/schema.js +38 -30
- package/dist/lib/schema.js.map +1 -0
- package/dist/lib/store.d.ts +48 -0
- package/dist/lib/store.js +234 -0
- package/dist/lib/store.js.map +1 -0
- package/dist/lib/validator.d.ts +37 -0
- package/dist/lib/validator.js +114 -0
- package/dist/lib/validator.js.map +1 -0
- package/dist/lib/vector-index.d.ts +37 -0
- package/{lib → dist/lib}/vector-index.js +19 -36
- package/dist/lib/vector-index.js.map +1 -0
- package/dist/tools/qln-call.d.ts +41 -0
- package/dist/tools/qln-call.js +353 -0
- package/dist/tools/qln-call.js.map +1 -0
- package/dist/tools/qln-helpers.d.ts +55 -0
- package/dist/tools/qln-helpers.js +88 -0
- package/dist/tools/qln-helpers.js.map +1 -0
- package/dist/types.d.ts +243 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/package.json +11 -4
- package/.github/FUNDING.yml +0 -3
- package/docs/README.md +0 -2
- package/docs/architecture.png +0 -0
- package/index.js +0 -80
- package/lib/executor.js +0 -104
- package/lib/provider-loader.js +0 -126
- package/lib/store.js +0 -217
- package/lib/validator.js +0 -171
- package/tools/qln-call.js +0 -257
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/** Normalized tool entry stored in registry + SQLite */
|
|
2
|
+
export interface ToolEntry {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
source: string;
|
|
6
|
+
category: string;
|
|
7
|
+
provider: string;
|
|
8
|
+
inputSchema: Record<string, unknown> | null;
|
|
9
|
+
triggers: string[];
|
|
10
|
+
tags: string[];
|
|
11
|
+
examples: string[];
|
|
12
|
+
endpoint: string;
|
|
13
|
+
searchText: string;
|
|
14
|
+
/** 3-10 word capability phrase for BM25 search boosting */
|
|
15
|
+
boostKeywords: string;
|
|
16
|
+
usageCount: number;
|
|
17
|
+
successRate: number;
|
|
18
|
+
/** Consecutive failure count for circuit breaker */
|
|
19
|
+
consecutiveFailures: number;
|
|
20
|
+
lastUsedAt: string | null;
|
|
21
|
+
embedding: number[] | null;
|
|
22
|
+
registeredAt: string;
|
|
23
|
+
updatedAt: string;
|
|
24
|
+
}
|
|
25
|
+
/** Raw tool data before normalization (partial fields accepted) */
|
|
26
|
+
export interface RawToolEntry {
|
|
27
|
+
name: string;
|
|
28
|
+
description?: string;
|
|
29
|
+
source?: string;
|
|
30
|
+
category?: string;
|
|
31
|
+
provider?: string;
|
|
32
|
+
pluginName?: string;
|
|
33
|
+
inputSchema?: Record<string, unknown> | null;
|
|
34
|
+
triggers?: string[];
|
|
35
|
+
tags?: string[];
|
|
36
|
+
examples?: string[];
|
|
37
|
+
endpoint?: string;
|
|
38
|
+
/** 3-10 word capability phrase for BM25 search boosting */
|
|
39
|
+
boostKeywords?: string;
|
|
40
|
+
usageCount?: number;
|
|
41
|
+
successRate?: number;
|
|
42
|
+
lastUsedAt?: string | null;
|
|
43
|
+
embedding?: number[] | null;
|
|
44
|
+
registeredAt?: string;
|
|
45
|
+
}
|
|
46
|
+
/** QLN configuration (config.js + config.local.js merged) */
|
|
47
|
+
export interface QLNConfig {
|
|
48
|
+
dataDir: string;
|
|
49
|
+
embedding: {
|
|
50
|
+
enabled: boolean;
|
|
51
|
+
model: string;
|
|
52
|
+
endpoint: string;
|
|
53
|
+
};
|
|
54
|
+
executor: {
|
|
55
|
+
httpEndpoint: string | null;
|
|
56
|
+
timeout: number;
|
|
57
|
+
};
|
|
58
|
+
providers: {
|
|
59
|
+
enabled: boolean;
|
|
60
|
+
dir: string;
|
|
61
|
+
};
|
|
62
|
+
search: {
|
|
63
|
+
defaultTopK: number;
|
|
64
|
+
threshold: number;
|
|
65
|
+
/** Per-source score multiplier (default: 1.0 for all) */
|
|
66
|
+
sourceWeights: Record<string, number>;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/** Search result returned by Router */
|
|
70
|
+
export interface SearchResult {
|
|
71
|
+
name: string;
|
|
72
|
+
score: number;
|
|
73
|
+
stages: {
|
|
74
|
+
trigger: number;
|
|
75
|
+
keyword: number;
|
|
76
|
+
semantic: number;
|
|
77
|
+
usage: number;
|
|
78
|
+
success: number;
|
|
79
|
+
recencyFactor: number;
|
|
80
|
+
};
|
|
81
|
+
description: string;
|
|
82
|
+
source: string;
|
|
83
|
+
category: string;
|
|
84
|
+
inputSchema: Record<string, unknown> | null;
|
|
85
|
+
explorer?: boolean;
|
|
86
|
+
}
|
|
87
|
+
/** Timing info from Router search */
|
|
88
|
+
export interface SearchTiming {
|
|
89
|
+
stage1: number;
|
|
90
|
+
stage2: number;
|
|
91
|
+
stage3: number;
|
|
92
|
+
merge: number;
|
|
93
|
+
total: number;
|
|
94
|
+
}
|
|
95
|
+
/** Validation error from validator */
|
|
96
|
+
export interface ValidationError {
|
|
97
|
+
field: string;
|
|
98
|
+
message: string;
|
|
99
|
+
severity: 'error' | 'warning';
|
|
100
|
+
}
|
|
101
|
+
/** Provider manifest JSON structure */
|
|
102
|
+
export interface ProviderManifest {
|
|
103
|
+
provider: string;
|
|
104
|
+
version?: string;
|
|
105
|
+
description?: string;
|
|
106
|
+
endpoint?: string;
|
|
107
|
+
tools: Array<{
|
|
108
|
+
name: string;
|
|
109
|
+
description: string;
|
|
110
|
+
category?: string;
|
|
111
|
+
inputSchema?: Record<string, unknown>;
|
|
112
|
+
triggers?: string[];
|
|
113
|
+
tags?: string[];
|
|
114
|
+
examples?: string[];
|
|
115
|
+
endpoint?: string;
|
|
116
|
+
boostKeywords?: string;
|
|
117
|
+
}>;
|
|
118
|
+
}
|
|
119
|
+
/** Tool execution result */
|
|
120
|
+
export interface ExecResult {
|
|
121
|
+
result: unknown;
|
|
122
|
+
source: 'local' | 'http';
|
|
123
|
+
elapsed: number;
|
|
124
|
+
/** Whether this result came from a fallback tool */
|
|
125
|
+
fallback?: boolean;
|
|
126
|
+
/** Original tool name if fallback was used */
|
|
127
|
+
originalTool?: string;
|
|
128
|
+
}
|
|
129
|
+
/** Circuit breaker configuration */
|
|
130
|
+
export interface CircuitBreakerConfig {
|
|
131
|
+
/** Consecutive failures before tripping (default: 3) */
|
|
132
|
+
failureThreshold: number;
|
|
133
|
+
/** Milliseconds before attempting recovery (default: 60000) */
|
|
134
|
+
recoveryTimeout: number;
|
|
135
|
+
}
|
|
136
|
+
/** Circuit breaker states */
|
|
137
|
+
export type CircuitState = 'closed' | 'open' | 'half-open';
|
|
138
|
+
/** MCP tool response content */
|
|
139
|
+
export interface McpToolResponse {
|
|
140
|
+
content: Array<{
|
|
141
|
+
type: string;
|
|
142
|
+
text: string;
|
|
143
|
+
}>;
|
|
144
|
+
isError?: boolean;
|
|
145
|
+
}
|
|
146
|
+
/** SQLite row from tools table (snake_case columns) */
|
|
147
|
+
export interface ToolRow {
|
|
148
|
+
name: string;
|
|
149
|
+
description: string;
|
|
150
|
+
source: string;
|
|
151
|
+
category: string;
|
|
152
|
+
provider: string;
|
|
153
|
+
plugin_name?: string;
|
|
154
|
+
input_schema: string;
|
|
155
|
+
triggers: string;
|
|
156
|
+
tags: string;
|
|
157
|
+
examples: string;
|
|
158
|
+
endpoint: string;
|
|
159
|
+
search_text: string;
|
|
160
|
+
embedding: string;
|
|
161
|
+
usage_count: number;
|
|
162
|
+
success_rate: number;
|
|
163
|
+
last_used_at: string | null;
|
|
164
|
+
registered_at: string;
|
|
165
|
+
updated_at: string;
|
|
166
|
+
}
|
|
167
|
+
/** sql.js Database interface (minimal) */
|
|
168
|
+
export interface SqlJsDatabase {
|
|
169
|
+
run(sql: string, params?: unknown[]): void;
|
|
170
|
+
exec(sql: string): Array<{
|
|
171
|
+
columns: string[];
|
|
172
|
+
values: unknown[][];
|
|
173
|
+
}>;
|
|
174
|
+
export(): Uint8Array;
|
|
175
|
+
close(): void;
|
|
176
|
+
}
|
|
177
|
+
/** sql.js static module interface */
|
|
178
|
+
export interface SqlJsStatic {
|
|
179
|
+
Database: new (data?: ArrayLike<number>) => SqlJsDatabase;
|
|
180
|
+
}
|
|
181
|
+
/** Stage score accumulator used internally by Router */
|
|
182
|
+
export interface StageScores {
|
|
183
|
+
stage1: number;
|
|
184
|
+
stage2: number;
|
|
185
|
+
stage3: number;
|
|
186
|
+
}
|
|
187
|
+
/** Registry stats */
|
|
188
|
+
export interface RegistryStats {
|
|
189
|
+
total: number;
|
|
190
|
+
bySource: Record<string, number>;
|
|
191
|
+
byCategory: Record<string, number>;
|
|
192
|
+
withEmbedding: number;
|
|
193
|
+
embeddingCoverage: string;
|
|
194
|
+
}
|
|
195
|
+
/** VectorIndex build result */
|
|
196
|
+
export interface VectorBuildResult {
|
|
197
|
+
indexed: number;
|
|
198
|
+
categories: number;
|
|
199
|
+
dimension: number;
|
|
200
|
+
}
|
|
201
|
+
/** VectorIndex stats */
|
|
202
|
+
export interface VectorStats {
|
|
203
|
+
built: boolean;
|
|
204
|
+
tools: number;
|
|
205
|
+
dimension: number;
|
|
206
|
+
categories: number;
|
|
207
|
+
categoryList: string[];
|
|
208
|
+
memoryKB: number;
|
|
209
|
+
}
|
|
210
|
+
/** Router stats */
|
|
211
|
+
export interface RouterStats {
|
|
212
|
+
registrySize: number;
|
|
213
|
+
vectorIndex: VectorStats;
|
|
214
|
+
embeddingAvailable: boolean;
|
|
215
|
+
bm25: {
|
|
216
|
+
idfTerms: number;
|
|
217
|
+
avgDocLen: number;
|
|
218
|
+
k1: number;
|
|
219
|
+
b: number;
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
/** Provider load result */
|
|
223
|
+
export interface ProviderLoadResult {
|
|
224
|
+
loaded: number;
|
|
225
|
+
skipped: number;
|
|
226
|
+
failed: number;
|
|
227
|
+
details: Array<{
|
|
228
|
+
file: string;
|
|
229
|
+
status: 'loaded' | 'skipped' | 'failed';
|
|
230
|
+
reason?: string;
|
|
231
|
+
provider?: string;
|
|
232
|
+
toolCount?: number;
|
|
233
|
+
}>;
|
|
234
|
+
}
|
|
235
|
+
/** Embedding precompute result */
|
|
236
|
+
export interface EmbeddingPrecomputeResult {
|
|
237
|
+
embedded: number;
|
|
238
|
+
skipped: number;
|
|
239
|
+
failed: number;
|
|
240
|
+
}
|
|
241
|
+
/** Tool handler function signature */
|
|
242
|
+
export type ToolHandler = (args: Record<string, unknown>) => Promise<unknown>;
|
|
243
|
+
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA,gDAAgD"}
|
package/package.json
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n2-qln",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.1.1",
|
|
4
4
|
"description": "Query Layer Network — Semantic tool dispatcher for MCP. Route 1000 tools through 1 router.",
|
|
5
|
-
"main": "index.js",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
6
7
|
"bin": {
|
|
7
|
-
"n2-qln": "./index.js"
|
|
8
|
+
"n2-qln": "./dist/index.js"
|
|
8
9
|
},
|
|
9
10
|
"scripts": {
|
|
10
|
-
"
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"start": "node dist/index.js",
|
|
13
|
+
"dev": "tsc --watch",
|
|
11
14
|
"test": "node test/test-provider-loader.js"
|
|
12
15
|
},
|
|
13
16
|
"dependencies": {
|
|
@@ -30,5 +33,9 @@
|
|
|
30
33
|
},
|
|
31
34
|
"engines": {
|
|
32
35
|
"node": ">=18.0.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^25.5.0",
|
|
39
|
+
"typescript": "^6.0.2"
|
|
33
40
|
}
|
|
34
41
|
}
|
package/.github/FUNDING.yml
DELETED
package/docs/README.md
DELETED
package/docs/architecture.png
DELETED
|
Binary file
|
package/index.js
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// QLN — Query Layer Network MCP server entry point
|
|
3
|
-
// Semantic tool dispatcher: route 1000 tools through 1 router
|
|
4
|
-
const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
|
|
5
|
-
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
6
|
-
const { z } = require('zod');
|
|
7
|
-
|
|
8
|
-
// Core
|
|
9
|
-
const path = require('path');
|
|
10
|
-
const { loadConfig } = require('./lib/config');
|
|
11
|
-
const { Store } = require('./lib/store');
|
|
12
|
-
const { Embedding } = require('./lib/embedding');
|
|
13
|
-
const { Registry } = require('./lib/registry');
|
|
14
|
-
const { VectorIndex } = require('./lib/vector-index');
|
|
15
|
-
const { Router } = require('./lib/router');
|
|
16
|
-
const { Executor } = require('./lib/executor');
|
|
17
|
-
const { loadProviders } = require('./lib/provider-loader');
|
|
18
|
-
|
|
19
|
-
// MCP Tool (unified)
|
|
20
|
-
const { registerQlnCall } = require('./tools/qln-call');
|
|
21
|
-
|
|
22
|
-
async function main() {
|
|
23
|
-
const config = loadConfig();
|
|
24
|
-
|
|
25
|
-
// 1. Core engine initialization
|
|
26
|
-
const store = new Store(config.dataDir);
|
|
27
|
-
await store.init();
|
|
28
|
-
|
|
29
|
-
const embedding = config.embedding?.enabled
|
|
30
|
-
? new Embedding(config.embedding)
|
|
31
|
-
: null;
|
|
32
|
-
|
|
33
|
-
const registry = new Registry(store, embedding);
|
|
34
|
-
registry.load();
|
|
35
|
-
|
|
36
|
-
// 1.5. Provider auto-indexing (providers/*.json → registry)
|
|
37
|
-
if (config.providers?.enabled !== false) {
|
|
38
|
-
const provDir = config.providers?.dir || path.join(__dirname, 'providers');
|
|
39
|
-
const provResult = loadProviders(provDir, registry);
|
|
40
|
-
if (provResult.loaded > 0) {
|
|
41
|
-
console.error(`[QLN] Providers: ${provResult.loaded} tools from ${provResult.details.filter(d => d.status === 'loaded').length} files`);
|
|
42
|
-
}
|
|
43
|
-
if (provResult.failed > 0) {
|
|
44
|
-
console.error(`[QLN] Provider warnings: ${provResult.failed} files failed to load`);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const vectorIndex = new VectorIndex();
|
|
49
|
-
const router = new Router(registry, vectorIndex, embedding);
|
|
50
|
-
const executor = new Executor(config.executor || {});
|
|
51
|
-
|
|
52
|
-
// 2. Precompute embeddings + build vector index (async, non-blocking)
|
|
53
|
-
if (embedding) {
|
|
54
|
-
setImmediate(async () => {
|
|
55
|
-
try {
|
|
56
|
-
await registry.precomputeEmbeddings();
|
|
57
|
-
router.buildIndex();
|
|
58
|
-
} catch { /* Ollama not available — Stage 1+2 still work */ }
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// 3. Create MCP server
|
|
63
|
-
const pkg = require('./package.json');
|
|
64
|
-
const server = new McpServer({
|
|
65
|
-
name: 'n2-qln',
|
|
66
|
-
version: pkg.version,
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
// 4. Register unified MCP tool (1 tool, 5 actions)
|
|
70
|
-
registerQlnCall(server, z, router, executor, registry);
|
|
71
|
-
|
|
72
|
-
// 5. Connect stdio transport
|
|
73
|
-
const transport = new StdioServerTransport();
|
|
74
|
-
await server.connect(transport);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
main().catch(err => {
|
|
78
|
-
console.error(`[QLN] Fatal: ${err.message}`);
|
|
79
|
-
process.exit(1);
|
|
80
|
-
});
|
package/lib/executor.js
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
// QLN — L3 tool executor
|
|
2
|
-
// Execute tools via HTTP (localhost) or registered local handlers
|
|
3
|
-
const http = require('http');
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Tool executor — HTTP proxy + local function calls.
|
|
7
|
-
*
|
|
8
|
-
* Execution priority:
|
|
9
|
-
* 1. Local registered handler (via addHandler)
|
|
10
|
-
* 2. HTTP proxy (when endpoint configured)
|
|
11
|
-
*/
|
|
12
|
-
class Executor {
|
|
13
|
-
/**
|
|
14
|
-
* @param {object} config
|
|
15
|
-
* @param {string} [config.httpEndpoint] - Tool execution HTTP endpoint (e.g. "http://127.0.0.1:PORT")
|
|
16
|
-
* @param {number} [config.timeout=20000] - HTTP timeout (ms)
|
|
17
|
-
*/
|
|
18
|
-
constructor(config = {}) {
|
|
19
|
-
this._httpEndpoint = config.httpEndpoint || null;
|
|
20
|
-
this._timeout = config.timeout || 20000;
|
|
21
|
-
/** @type {Map<string, Function>} Local handlers */
|
|
22
|
-
this._handlers = new Map();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Register a local tool handler.
|
|
27
|
-
* @param {string} name - Tool name
|
|
28
|
-
* @param {Function} handler - (args) => Promise<unknown>
|
|
29
|
-
*/
|
|
30
|
-
addHandler(name, handler) {
|
|
31
|
-
this._handlers.set(name, handler);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Execute a tool.
|
|
36
|
-
* @param {string} name - Tool name
|
|
37
|
-
* @param {object} args - Tool arguments
|
|
38
|
-
* @returns {Promise<{result: unknown, source: string, elapsed: number}>}
|
|
39
|
-
*/
|
|
40
|
-
async exec(name, args = {}) {
|
|
41
|
-
const t0 = Date.now();
|
|
42
|
-
|
|
43
|
-
// 1. Local handler first
|
|
44
|
-
if (this._handlers.has(name)) {
|
|
45
|
-
const handler = this._handlers.get(name);
|
|
46
|
-
const result = await handler(args);
|
|
47
|
-
return { result, source: 'local', elapsed: Date.now() - t0 };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// 2. HTTP proxy
|
|
51
|
-
if (this._httpEndpoint) {
|
|
52
|
-
const result = await this._execHttp(name, args);
|
|
53
|
-
return { result, source: 'http', elapsed: Date.now() - t0 };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
throw new Error(`No handler found for tool: ${name}. Register with addHandler() or set httpEndpoint.`);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Dynamically set HTTP endpoint.
|
|
61
|
-
* @param {string} endpoint - "http://127.0.0.1:PORT" format
|
|
62
|
-
*/
|
|
63
|
-
setHttpEndpoint(endpoint) {
|
|
64
|
-
this._httpEndpoint = endpoint;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** @private HTTP POST /call → tool execution */
|
|
68
|
-
_execHttp(name, args) {
|
|
69
|
-
return new Promise((resolve, reject) => {
|
|
70
|
-
const url = new URL(this._httpEndpoint);
|
|
71
|
-
const bodyStr = JSON.stringify({ tool: name, args });
|
|
72
|
-
const timer = setTimeout(() => reject(new Error(`timeout (${this._timeout}ms)`)), this._timeout);
|
|
73
|
-
|
|
74
|
-
const req = http.request({
|
|
75
|
-
hostname: url.hostname,
|
|
76
|
-
port: url.port,
|
|
77
|
-
path: '/call',
|
|
78
|
-
method: 'POST',
|
|
79
|
-
headers: {
|
|
80
|
-
'Content-Type': 'application/json',
|
|
81
|
-
'Content-Length': Buffer.byteLength(bodyStr),
|
|
82
|
-
},
|
|
83
|
-
}, (res) => {
|
|
84
|
-
let body = '';
|
|
85
|
-
res.on('data', c => body += c);
|
|
86
|
-
res.on('end', () => {
|
|
87
|
-
clearTimeout(timer);
|
|
88
|
-
try {
|
|
89
|
-
const parsed = JSON.parse(body);
|
|
90
|
-
if (parsed.error) reject(new Error(parsed.error));
|
|
91
|
-
else resolve(parsed.result);
|
|
92
|
-
} catch {
|
|
93
|
-
reject(new Error(`Invalid response: ${body.slice(0, 200)}`));
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
req.on('error', e => { clearTimeout(timer); reject(e); });
|
|
98
|
-
req.write(bodyStr);
|
|
99
|
-
req.end();
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
module.exports = { Executor };
|
package/lib/provider-loader.js
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
// QLN — Provider manifest loader (providers/*.json → registry auto-registration)
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const { inferCategory } = require('./schema');
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Required fields for a valid provider manifest.
|
|
8
|
-
* @type {string[]}
|
|
9
|
-
*/
|
|
10
|
-
const REQUIRED_MANIFEST_FIELDS = ['provider', 'tools'];
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Required fields for each tool entry within a manifest.
|
|
14
|
-
* @type {string[]}
|
|
15
|
-
*/
|
|
16
|
-
const REQUIRED_TOOL_FIELDS = ['name', 'description'];
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Load all provider manifests from a directory and register their tools.
|
|
20
|
-
*
|
|
21
|
-
* @param {string} providersDir - Absolute path to providers/ directory
|
|
22
|
-
* @param {import('./registry').Registry} registry - QLN registry instance
|
|
23
|
-
* @returns {{ loaded: number, skipped: number, failed: number, details: object[] }}
|
|
24
|
-
*/
|
|
25
|
-
function loadProviders(providersDir, registry) {
|
|
26
|
-
const result = { loaded: 0, skipped: 0, failed: 0, details: [] };
|
|
27
|
-
|
|
28
|
-
if (!fs.existsSync(providersDir)) return result;
|
|
29
|
-
|
|
30
|
-
/** @type {string[]} */
|
|
31
|
-
const files = fs.readdirSync(providersDir)
|
|
32
|
-
.filter(f => f.endsWith('.json'));
|
|
33
|
-
|
|
34
|
-
if (files.length === 0) return result;
|
|
35
|
-
|
|
36
|
-
for (const file of files) {
|
|
37
|
-
const filePath = path.join(providersDir, file);
|
|
38
|
-
try {
|
|
39
|
-
const manifest = _parseManifest(filePath);
|
|
40
|
-
if (!manifest) {
|
|
41
|
-
result.skipped++;
|
|
42
|
-
result.details.push({ file, status: 'skipped', reason: 'invalid manifest' });
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const tools = _normalizeTools(manifest);
|
|
47
|
-
if (tools.length === 0) {
|
|
48
|
-
result.skipped++;
|
|
49
|
-
result.details.push({ file, status: 'skipped', reason: 'no valid tools' });
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Idempotent: purge old entries from this provider before re-registering
|
|
54
|
-
registry.purgeBySource(`provider:${manifest.provider}`);
|
|
55
|
-
|
|
56
|
-
const count = registry.registerBatch(tools);
|
|
57
|
-
result.loaded += count;
|
|
58
|
-
result.details.push({
|
|
59
|
-
file,
|
|
60
|
-
status: 'loaded',
|
|
61
|
-
provider: manifest.provider,
|
|
62
|
-
toolCount: count,
|
|
63
|
-
});
|
|
64
|
-
} catch (err) {
|
|
65
|
-
result.failed++;
|
|
66
|
-
result.details.push({ file, status: 'failed', reason: err.message });
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return result;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Parse and validate a manifest JSON file.
|
|
75
|
-
*
|
|
76
|
-
* @param {string} filePath - Absolute path to JSON file
|
|
77
|
-
* @returns {object|null} Parsed manifest or null if invalid
|
|
78
|
-
*/
|
|
79
|
-
function _parseManifest(filePath) {
|
|
80
|
-
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
81
|
-
const manifest = JSON.parse(raw);
|
|
82
|
-
|
|
83
|
-
// Validate required fields
|
|
84
|
-
for (const field of REQUIRED_MANIFEST_FIELDS) {
|
|
85
|
-
if (!manifest[field]) return null;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// tools must be a non-empty array
|
|
89
|
-
if (!Array.isArray(manifest.tools)) return null;
|
|
90
|
-
|
|
91
|
-
return manifest;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Normalize tool entries from a manifest for registry registration.
|
|
96
|
-
* Injects provider metadata and assigns source = "provider:{name}".
|
|
97
|
-
*
|
|
98
|
-
* @param {object} manifest - Validated manifest object
|
|
99
|
-
* @returns {object[]} Array of normalized tool entries ready for registerBatch()
|
|
100
|
-
*/
|
|
101
|
-
function _normalizeTools(manifest) {
|
|
102
|
-
const providerName = manifest.provider;
|
|
103
|
-
const tools = [];
|
|
104
|
-
|
|
105
|
-
for (const raw of manifest.tools) {
|
|
106
|
-
// Skip tools missing required fields
|
|
107
|
-
if (!raw.name || !raw.description) continue;
|
|
108
|
-
|
|
109
|
-
tools.push({
|
|
110
|
-
name: raw.name,
|
|
111
|
-
description: raw.description,
|
|
112
|
-
source: `provider:${providerName}`,
|
|
113
|
-
category: raw.category || inferCategory(raw.name, 'provider'),
|
|
114
|
-
provider: providerName,
|
|
115
|
-
inputSchema: raw.inputSchema || null,
|
|
116
|
-
triggers: raw.triggers || undefined, // let schema.js extract
|
|
117
|
-
tags: raw.tags || [],
|
|
118
|
-
examples: raw.examples || [],
|
|
119
|
-
endpoint: raw.endpoint || '',
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return tools;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
module.exports = { loadProviders };
|