@juspay/neurolink 9.84.1 → 9.85.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/CHANGELOG.md +12 -0
- package/dist/browser/neurolink.min.js +441 -407
- package/dist/cli/factories/commandFactory.d.ts +17 -0
- package/dist/cli/factories/commandFactory.js +254 -0
- package/dist/cli/parser.js +2 -0
- package/dist/cli/utils/skillsFlags.d.ts +10 -0
- package/dist/cli/utils/skillsFlags.js +22 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -0
- package/dist/lib/index.d.ts +5 -0
- package/dist/lib/index.js +6 -0
- package/dist/lib/neurolink.d.ts +28 -0
- package/dist/lib/neurolink.js +117 -0
- package/dist/lib/server/routes/agentRoutes.js +156 -1
- package/dist/lib/server/utils/validation.d.ts +32 -0
- package/dist/lib/server/utils/validation.js +18 -0
- package/dist/lib/session/globalSessionState.d.ts +10 -1
- package/dist/lib/session/globalSessionState.js +18 -0
- package/dist/lib/skills/skillMatcher.d.ts +20 -0
- package/dist/lib/skills/skillMatcher.js +80 -0
- package/dist/lib/skills/skillStoreRedis.d.ts +22 -0
- package/dist/lib/skills/skillStoreRedis.js +98 -0
- package/dist/lib/skills/skillStoreS3.d.ts +41 -0
- package/dist/lib/skills/skillStoreS3.js +233 -0
- package/dist/lib/skills/skillStores.d.ts +44 -0
- package/dist/lib/skills/skillStores.js +252 -0
- package/dist/lib/skills/skillTools.d.ts +19 -0
- package/dist/lib/skills/skillTools.js +340 -0
- package/dist/lib/skills/skillsManager.d.ts +54 -0
- package/dist/lib/skills/skillsManager.js +220 -0
- package/dist/lib/types/config.d.ts +10 -0
- package/dist/lib/types/generate.d.ts +8 -0
- package/dist/lib/types/index.d.ts +1 -0
- package/dist/lib/types/index.js +1 -0
- package/dist/lib/types/skills.d.ts +296 -0
- package/dist/lib/types/skills.js +17 -0
- package/dist/lib/types/stream.d.ts +8 -0
- package/dist/lib/utils/csvProcessor.js +59 -8
- package/dist/neurolink.d.ts +28 -0
- package/dist/neurolink.js +117 -0
- package/dist/server/routes/agentRoutes.js +156 -1
- package/dist/server/utils/validation.d.ts +32 -0
- package/dist/server/utils/validation.js +18 -0
- package/dist/session/globalSessionState.d.ts +10 -1
- package/dist/session/globalSessionState.js +18 -0
- package/dist/skills/skillMatcher.d.ts +20 -0
- package/dist/skills/skillMatcher.js +79 -0
- package/dist/skills/skillStoreRedis.d.ts +22 -0
- package/dist/skills/skillStoreRedis.js +97 -0
- package/dist/skills/skillStoreS3.d.ts +41 -0
- package/dist/skills/skillStoreS3.js +232 -0
- package/dist/skills/skillStores.d.ts +44 -0
- package/dist/skills/skillStores.js +251 -0
- package/dist/skills/skillTools.d.ts +19 -0
- package/dist/skills/skillTools.js +339 -0
- package/dist/skills/skillsManager.d.ts +54 -0
- package/dist/skills/skillsManager.js +219 -0
- package/dist/types/config.d.ts +10 -0
- package/dist/types/generate.d.ts +8 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/skills.d.ts +296 -0
- package/dist/types/skills.js +16 -0
- package/dist/types/stream.d.ts +8 -0
- package/dist/utils/csvProcessor.js +59 -8
- package/package.json +4 -3
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native skills support — versioned, discoverable instruction packs
|
|
3
|
+
* (SOPs, playbooks, workflows) exposed to the model via progressive
|
|
4
|
+
* disclosure: a cheap name+description index up front, full instructions
|
|
5
|
+
* loaded on demand through built-in skill tools.
|
|
6
|
+
*
|
|
7
|
+
* Architecture mirrors the Hippocampus memory subsystem: a `skills` config
|
|
8
|
+
* on the NeuroLink constructor lazily initializes a SkillsManager, which
|
|
9
|
+
* auto-registers built-in tools (search_skills / list_skills, plus gated
|
|
10
|
+
* mutation tools) and optionally injects a compact skills index into the
|
|
11
|
+
* system prompt of each generate()/stream() call.
|
|
12
|
+
*
|
|
13
|
+
* Naming: every exported type carries a `Skill` prefix to satisfy the
|
|
14
|
+
* `unique-type-names` ESLint rule.
|
|
15
|
+
*/
|
|
16
|
+
/** Visibility of a skill: available everywhere, or only in specific scopes. */
|
|
17
|
+
export type SkillScopeKind = "global" | "scoped";
|
|
18
|
+
/** Lifecycle status. Deletes are soft — deprecated skills stay in storage. */
|
|
19
|
+
export type SkillLifecycleStatus = "active" | "deprecated";
|
|
20
|
+
/**
|
|
21
|
+
* A complete skill: index metadata plus the full `instructions` body.
|
|
22
|
+
* `instructions` is the expensive part — it is only hydrated for matched
|
|
23
|
+
* skills, never included in index listings or the prompt index.
|
|
24
|
+
*/
|
|
25
|
+
export type SkillDefinition = {
|
|
26
|
+
/** Stable unique identifier (UUID for created skills, or derived from filename). */
|
|
27
|
+
id: string;
|
|
28
|
+
/** Machine-friendly unique name (snake_case recommended), used for matching. */
|
|
29
|
+
name: string;
|
|
30
|
+
/** Human-readable display name shown in listings. */
|
|
31
|
+
displayName?: string;
|
|
32
|
+
/** One or two sentences describing when the skill applies — the matching signal. */
|
|
33
|
+
description: string;
|
|
34
|
+
/** Full step-by-step instructions the model follows when the skill matches. */
|
|
35
|
+
instructions: string;
|
|
36
|
+
/** Domain tags for filtering (e.g. ["payments", "escalation"]). */
|
|
37
|
+
tags?: string[];
|
|
38
|
+
/** Visibility. Default: "global". */
|
|
39
|
+
scope?: SkillScopeKind;
|
|
40
|
+
/** Scope identifiers this skill is limited to when scope === "scoped" (e.g. channel/team/tenant ids). */
|
|
41
|
+
scopeIds?: string[];
|
|
42
|
+
/** Monotonic version, bumped on every approved update. Default: 1. */
|
|
43
|
+
version?: number;
|
|
44
|
+
/** Lifecycle status. Default: "active". */
|
|
45
|
+
status?: SkillLifecycleStatus;
|
|
46
|
+
/** ISO timestamp of creation. */
|
|
47
|
+
createdAt?: string;
|
|
48
|
+
/** ISO timestamp of last update. */
|
|
49
|
+
updatedAt?: string;
|
|
50
|
+
/** Free-form host metadata (audit fields, approval references, …). */
|
|
51
|
+
metadata?: Record<string, unknown>;
|
|
52
|
+
};
|
|
53
|
+
/** Lightweight index entry — everything except the instructions body. */
|
|
54
|
+
export type SkillIndexItem = Omit<SkillDefinition, "instructions">;
|
|
55
|
+
/**
|
|
56
|
+
* Pluggable persistence backend. NeuroLink ships memory and filesystem
|
|
57
|
+
* stores; hosts plug their own (S3, database, …) via the "custom" storage
|
|
58
|
+
* type. `index()` must be cheap relative to `get()` — it backs every
|
|
59
|
+
* search and prompt-index build.
|
|
60
|
+
*/
|
|
61
|
+
export type SkillStore = {
|
|
62
|
+
/** Fetch one skill (with instructions) by id. Null when absent. */
|
|
63
|
+
get(id: string): Promise<SkillDefinition | null>;
|
|
64
|
+
/** Create or replace a skill. */
|
|
65
|
+
put(skill: SkillDefinition): Promise<void>;
|
|
66
|
+
/** Hard-remove a skill from storage. (Soft deletes go through put().) */
|
|
67
|
+
delete(id: string): Promise<void>;
|
|
68
|
+
/** List index entries (no instructions) for all stored skills. */
|
|
69
|
+
index(): Promise<SkillIndexItem[]>;
|
|
70
|
+
/** Optional: drop any internal caches (called after mutations). */
|
|
71
|
+
invalidate?(): void;
|
|
72
|
+
};
|
|
73
|
+
/** In-process store, optionally seeded. Good for tests and embedded use. */
|
|
74
|
+
export type SkillMemoryStorageConfig = {
|
|
75
|
+
type: "memory";
|
|
76
|
+
/** Initial skills to seed the store with. */
|
|
77
|
+
skills?: SkillDefinition[];
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Directory-backed store. Reads three layouts:
|
|
81
|
+
* - `<dir>/<id>.json` — one JSON-serialized SkillDefinition per file
|
|
82
|
+
* - `<dir>/<name>.md` — markdown with YAML frontmatter; body = instructions
|
|
83
|
+
* - `<dir>/<name>/SKILL.md` — Claude-skills-style directory layout
|
|
84
|
+
* Mutations always write `<id>.json`; markdown sources are read-only.
|
|
85
|
+
*/
|
|
86
|
+
export type SkillFilesystemStorageConfig = {
|
|
87
|
+
type: "filesystem";
|
|
88
|
+
/** Directory containing skill files. Created on first write if absent. */
|
|
89
|
+
path: string;
|
|
90
|
+
};
|
|
91
|
+
/** Host-provided store implementation (e.g. curator's S3 repository). */
|
|
92
|
+
export type SkillCustomStorageConfig = {
|
|
93
|
+
type: "custom";
|
|
94
|
+
store: SkillStore;
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* S3-backed store. Layout: `<prefix>skills/<id>.json` per skill plus a
|
|
98
|
+
* `<prefix>index.json` document that is upserted on writes and rebuilt
|
|
99
|
+
* from a bucket listing when missing or corrupt (self-healing).
|
|
100
|
+
*
|
|
101
|
+
* Requires the optional peer `@aws-sdk/client-s3` to be installed in the
|
|
102
|
+
* host application (same pattern as memory's optional @juspay/hippocampus).
|
|
103
|
+
* Credentials default to the standard AWS provider chain when omitted.
|
|
104
|
+
*/
|
|
105
|
+
export type SkillS3StorageConfig = {
|
|
106
|
+
type: "s3";
|
|
107
|
+
bucket: string;
|
|
108
|
+
/** Key prefix inside the bucket. Default: "neurolink-skills/". */
|
|
109
|
+
prefix?: string;
|
|
110
|
+
region?: string;
|
|
111
|
+
/** Custom endpoint (MinIO, LocalStack, …). */
|
|
112
|
+
endpoint?: string;
|
|
113
|
+
/** Use path-style addressing (required by most S3-compatible stores). */
|
|
114
|
+
forcePathStyle?: boolean;
|
|
115
|
+
credentials?: {
|
|
116
|
+
accessKeyId: string;
|
|
117
|
+
secretAccessKey: string;
|
|
118
|
+
sessionToken?: string;
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Redis-backed store using NeuroLink's pooled Redis client (`redis` v5,
|
|
123
|
+
* already a core dependency). One JSON value per skill under
|
|
124
|
+
* `<keyPrefix><id>`; the index is derived via SCAN + MGET. Skills are
|
|
125
|
+
* persistent — no TTL is applied.
|
|
126
|
+
*/
|
|
127
|
+
export type SkillRedisStorageConfig = {
|
|
128
|
+
type: "redis";
|
|
129
|
+
url?: string;
|
|
130
|
+
host?: string;
|
|
131
|
+
port?: number;
|
|
132
|
+
username?: string;
|
|
133
|
+
password?: string;
|
|
134
|
+
db?: number;
|
|
135
|
+
/** Key prefix. Default: "neurolink:skills:". */
|
|
136
|
+
keyPrefix?: string;
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* Structural surface of the lazily-required @aws-sdk/client-s3 module —
|
|
140
|
+
* only the pieces the S3 skill store touches (same pattern as
|
|
141
|
+
* HippocampusModule for the optional memory peer).
|
|
142
|
+
*/
|
|
143
|
+
export type SkillS3ModuleSurface = {
|
|
144
|
+
S3Client: new (config: Record<string, unknown>) => {
|
|
145
|
+
send: (command: unknown) => Promise<unknown>;
|
|
146
|
+
};
|
|
147
|
+
GetObjectCommand: new (input: Record<string, unknown>) => unknown;
|
|
148
|
+
PutObjectCommand: new (input: Record<string, unknown>) => unknown;
|
|
149
|
+
DeleteObjectCommand: new (input: Record<string, unknown>) => unknown;
|
|
150
|
+
ListObjectsV2Command: new (input: Record<string, unknown>) => unknown;
|
|
151
|
+
};
|
|
152
|
+
/** Shape of the `<prefix>index.json` document maintained by the S3 store. */
|
|
153
|
+
export type SkillS3IndexDocument = {
|
|
154
|
+
lastUpdated: string;
|
|
155
|
+
skills: SkillIndexItem[];
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* Minimal object-storage operations the S3 skill store runs on. The
|
|
159
|
+
* default implementation is created lazily from @aws-sdk/client-s3;
|
|
160
|
+
* tests and hosts with pre-built clients can inject their own.
|
|
161
|
+
*/
|
|
162
|
+
export type SkillS3ObjectOps = {
|
|
163
|
+
/** Fetch an object's body as a UTF-8 string. Null when the key is absent. */
|
|
164
|
+
getObject(key: string): Promise<string | null>;
|
|
165
|
+
putObject(key: string, body: string): Promise<void>;
|
|
166
|
+
deleteObject(key: string): Promise<void>;
|
|
167
|
+
/** List all object keys under a prefix (paginated internally). */
|
|
168
|
+
listKeys(prefix: string): Promise<string[]>;
|
|
169
|
+
};
|
|
170
|
+
export type SkillsStorageConfig = SkillMemoryStorageConfig | SkillFilesystemStorageConfig | SkillS3StorageConfig | SkillRedisStorageConfig | SkillCustomStorageConfig;
|
|
171
|
+
/** Query accepted by SkillsManager.search() and the search_skills tool. */
|
|
172
|
+
export type SkillSearchQuery = {
|
|
173
|
+
/** Keyword matched (case-insensitive substring) against name, displayName, and description. */
|
|
174
|
+
query?: string;
|
|
175
|
+
/** Tag filter, applied on top of the keyword match. */
|
|
176
|
+
tag?: string;
|
|
177
|
+
/** Scope filter: include global skills plus skills scoped to this id. */
|
|
178
|
+
scopeId?: string;
|
|
179
|
+
/** Maximum matches to hydrate. Defaults to SkillsConfig.maxMatches. */
|
|
180
|
+
limit?: number;
|
|
181
|
+
};
|
|
182
|
+
/** Input for creating a skill (id/version/status/timestamps are assigned by the manager). */
|
|
183
|
+
export type SkillCreateInput = {
|
|
184
|
+
name: string;
|
|
185
|
+
displayName?: string;
|
|
186
|
+
description: string;
|
|
187
|
+
instructions: string;
|
|
188
|
+
tags?: string[];
|
|
189
|
+
scope?: SkillScopeKind;
|
|
190
|
+
scopeIds?: string[];
|
|
191
|
+
metadata?: Record<string, unknown>;
|
|
192
|
+
};
|
|
193
|
+
/** Patch for updating a skill. Only provided fields change; version is bumped. */
|
|
194
|
+
export type SkillUpdateInput = Partial<SkillCreateInput>;
|
|
195
|
+
/** A proposed mutation, passed to the host's onMutationRequest gate. */
|
|
196
|
+
export type SkillMutationAction = {
|
|
197
|
+
type: "create";
|
|
198
|
+
skill: SkillCreateInput;
|
|
199
|
+
requestedBy?: string;
|
|
200
|
+
} | {
|
|
201
|
+
type: "update";
|
|
202
|
+
skillId: string;
|
|
203
|
+
patch: SkillUpdateInput;
|
|
204
|
+
requestedBy?: string;
|
|
205
|
+
} | {
|
|
206
|
+
type: "delete";
|
|
207
|
+
skillId: string;
|
|
208
|
+
requestedBy?: string;
|
|
209
|
+
};
|
|
210
|
+
/**
|
|
211
|
+
* Host decision for a proposed mutation.
|
|
212
|
+
* - "approved": NeuroLink applies the mutation immediately.
|
|
213
|
+
* - "rejected": nothing is written; `reason` is surfaced to the model.
|
|
214
|
+
* - "pending": the host queued the action for out-of-band approval
|
|
215
|
+
* (e.g. a Slack maker-checker flow) and will apply it itself later;
|
|
216
|
+
* `reference` is surfaced to the model (e.g. an approval ticket id).
|
|
217
|
+
*/
|
|
218
|
+
export type SkillMutationDecision = {
|
|
219
|
+
outcome: "approved";
|
|
220
|
+
} | {
|
|
221
|
+
outcome: "rejected";
|
|
222
|
+
reason?: string;
|
|
223
|
+
} | {
|
|
224
|
+
outcome: "pending";
|
|
225
|
+
reference?: string;
|
|
226
|
+
};
|
|
227
|
+
/** Result envelope returned by SkillsManager.requestMutation(). */
|
|
228
|
+
export type SkillMutationResult = {
|
|
229
|
+
decision: SkillMutationDecision;
|
|
230
|
+
/** The resulting skill after an applied create/update (absent for delete/pending/rejected). */
|
|
231
|
+
skill?: SkillDefinition;
|
|
232
|
+
};
|
|
233
|
+
/**
|
|
234
|
+
* Instance-level skills configuration (NeuroLink constructor `skills` option).
|
|
235
|
+
* Opt-in: nothing is registered or injected unless `enabled: true`.
|
|
236
|
+
*/
|
|
237
|
+
export type SkillsConfig = {
|
|
238
|
+
enabled: boolean;
|
|
239
|
+
/** Persistence backend. Default: `{ type: "memory" }`. */
|
|
240
|
+
storage?: SkillsStorageConfig;
|
|
241
|
+
/**
|
|
242
|
+
* Inject a compact skills index (names + descriptions, never instructions)
|
|
243
|
+
* into the system prompt of each generate()/stream() call. Default: true.
|
|
244
|
+
* Set false for curator-style pure tool-driven disclosure.
|
|
245
|
+
*/
|
|
246
|
+
promptIndex?: boolean;
|
|
247
|
+
/** Maximum skills hydrated (with instructions) per search. Default: 5. */
|
|
248
|
+
maxMatches?: number;
|
|
249
|
+
/** Maximum entries rendered in the prompt index before truncation. Default: 50. */
|
|
250
|
+
promptIndexMaxItems?: number;
|
|
251
|
+
/** Index cache TTL in milliseconds. Default: 30000. 0 disables caching. */
|
|
252
|
+
indexCacheTtlMs?: number;
|
|
253
|
+
/** Default scope filter applied when a call/tool provides none. */
|
|
254
|
+
defaultScopeId?: string;
|
|
255
|
+
/**
|
|
256
|
+
* Register skill_create / skill_update / skill_delete tools so the model
|
|
257
|
+
* can propose skill changes. Default: false. Combine with
|
|
258
|
+
* `onMutationRequest` to gate writes behind host approval.
|
|
259
|
+
*/
|
|
260
|
+
allowMutations?: boolean;
|
|
261
|
+
/**
|
|
262
|
+
* Host approval gate invoked before any mutation is applied. When absent
|
|
263
|
+
* and allowMutations is true, mutations apply directly. Errors thrown
|
|
264
|
+
* here reject the mutation (fail closed for writes).
|
|
265
|
+
*/
|
|
266
|
+
onMutationRequest?: (action: SkillMutationAction) => Promise<SkillMutationDecision>;
|
|
267
|
+
};
|
|
268
|
+
/**
|
|
269
|
+
* Structural view of SkillsManager consumed by the skill tools factory —
|
|
270
|
+
* keeps skillTools.ts decoupled from the concrete manager class.
|
|
271
|
+
*/
|
|
272
|
+
export type SkillsManagerLike = {
|
|
273
|
+
search: (query: SkillSearchQuery) => Promise<SkillDefinition[]>;
|
|
274
|
+
list: (scopeId?: string) => Promise<SkillIndexItem[]>;
|
|
275
|
+
requestMutation: (action: SkillMutationAction) => Promise<SkillMutationResult>;
|
|
276
|
+
};
|
|
277
|
+
/** Options for the createSkillTools factory. */
|
|
278
|
+
export type SkillToolsOptions = {
|
|
279
|
+
/** Include skill_create / skill_update / skill_delete. Default: false. */
|
|
280
|
+
allowMutations?: boolean;
|
|
281
|
+
};
|
|
282
|
+
/**
|
|
283
|
+
* Per-call skills control on generate()/stream(). Only effective when the
|
|
284
|
+
* instance was constructed with skills enabled; per-call wins over instance
|
|
285
|
+
* config (same precedence convention as per-call credentials).
|
|
286
|
+
*/
|
|
287
|
+
export type SkillsCallOptions = {
|
|
288
|
+
/** Master toggle for this call (prompt index only — tools stay registered). Default: true. */
|
|
289
|
+
enabled?: boolean;
|
|
290
|
+
/** Per-call override of SkillsConfig.promptIndex. */
|
|
291
|
+
promptIndex?: boolean;
|
|
292
|
+
/** Scope filter for the prompt index on this call. Overrides defaultScopeId. */
|
|
293
|
+
scopeId?: string;
|
|
294
|
+
/** Restrict the prompt index to skills carrying at least one of these tags. */
|
|
295
|
+
tags?: string[];
|
|
296
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native skills support — versioned, discoverable instruction packs
|
|
3
|
+
* (SOPs, playbooks, workflows) exposed to the model via progressive
|
|
4
|
+
* disclosure: a cheap name+description index up front, full instructions
|
|
5
|
+
* loaded on demand through built-in skill tools.
|
|
6
|
+
*
|
|
7
|
+
* Architecture mirrors the Hippocampus memory subsystem: a `skills` config
|
|
8
|
+
* on the NeuroLink constructor lazily initializes a SkillsManager, which
|
|
9
|
+
* auto-registers built-in tools (search_skills / list_skills, plus gated
|
|
10
|
+
* mutation tools) and optionally injects a compact skills index into the
|
|
11
|
+
* system prompt of each generate()/stream() call.
|
|
12
|
+
*
|
|
13
|
+
* Naming: every exported type carries a `Skill` prefix to satisfy the
|
|
14
|
+
* `unique-type-names` ESLint rule.
|
|
15
|
+
*/
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=skills.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AIProviderName } from "../constants/enums.js";
|
|
2
2
|
import type { EvaluationData } from "./evaluation.js";
|
|
3
3
|
import type { RAGConfig } from "./rag.js";
|
|
4
|
+
import type { SkillsCallOptions } from "./skills.js";
|
|
4
5
|
import type { AnalyticsData, ToolExecutionEvent, ToolExecutionSummary } from "../types/index.js";
|
|
5
6
|
import type { MiddlewareFactoryOptions, OnChunkCallback, OnErrorCallback, OnFinishCallback } from "../types/middleware.js";
|
|
6
7
|
import type { TokenUsage } from "./analytics.js";
|
|
@@ -534,6 +535,13 @@ export type StreamOptions = {
|
|
|
534
535
|
};
|
|
535
536
|
/** @deprecated Use `piiDetection`, `responseValidation`, and `inputValidation` instead. */
|
|
536
537
|
processors?: ProcessorPipelineConfig;
|
|
538
|
+
/**
|
|
539
|
+
* Per-call skills control. Only effective when the instance was
|
|
540
|
+
* constructed with `skills.enabled: true`. Lets a call disable the
|
|
541
|
+
* prompt index, or narrow it by scope/tags. Per-call wins over
|
|
542
|
+
* instance config.
|
|
543
|
+
*/
|
|
544
|
+
skills?: SkillsCallOptions;
|
|
537
545
|
};
|
|
538
546
|
/**
|
|
539
547
|
* Stream function result type - Primary output format for streaming
|
|
@@ -444,6 +444,47 @@ function isMetadataLine(lines) {
|
|
|
444
444
|
}
|
|
445
445
|
return false;
|
|
446
446
|
}
|
|
447
|
+
/**
|
|
448
|
+
* Split CSV text into logical rows for metadata detection and raw row limiting.
|
|
449
|
+
*
|
|
450
|
+
* Supports Unix (LF), Windows (CRLF), and classic Mac (CR) line endings, and is
|
|
451
|
+
* quote-aware: a line ending that appears *inside* a quoted field (RFC 4180) is
|
|
452
|
+
* kept as part of that field rather than treated as a row boundary. A doubled
|
|
453
|
+
* `""` inside quotes is the RFC-4180 escaped quote and does not toggle the quote
|
|
454
|
+
* state. For unquoted input this yields exactly the same result as a plain
|
|
455
|
+
* `split(/\r\n|\n|\r/)`.
|
|
456
|
+
*/
|
|
457
|
+
function splitCsvLines(csvString) {
|
|
458
|
+
const rows = [];
|
|
459
|
+
let current = "";
|
|
460
|
+
let inQuotes = false;
|
|
461
|
+
for (let i = 0; i < csvString.length; i++) {
|
|
462
|
+
const ch = csvString[i];
|
|
463
|
+
if (ch === '"') {
|
|
464
|
+
if (inQuotes && csvString[i + 1] === '"') {
|
|
465
|
+
// Escaped quote ("") — stays inside the field, quote state unchanged.
|
|
466
|
+
current += '""';
|
|
467
|
+
i++;
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
inQuotes = !inQuotes;
|
|
471
|
+
current += ch;
|
|
472
|
+
}
|
|
473
|
+
else if (!inQuotes && (ch === "\n" || ch === "\r")) {
|
|
474
|
+
// Row boundary outside quotes; collapse CRLF into a single boundary.
|
|
475
|
+
if (ch === "\r" && csvString[i + 1] === "\n") {
|
|
476
|
+
i++;
|
|
477
|
+
}
|
|
478
|
+
rows.push(current);
|
|
479
|
+
current = "";
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
current += ch;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
rows.push(current);
|
|
486
|
+
return rows;
|
|
487
|
+
}
|
|
447
488
|
/**
|
|
448
489
|
* CSV processor for converting CSV data to LLM-optimized formats
|
|
449
490
|
*
|
|
@@ -483,10 +524,10 @@ export class CSVProcessor {
|
|
|
483
524
|
includeHeaders,
|
|
484
525
|
});
|
|
485
526
|
const csvString = content.toString("utf-8");
|
|
486
|
-
// For raw format, return
|
|
487
|
-
// This preserves the
|
|
527
|
+
// For raw format, return CSV text with row limit (no parsing needed)
|
|
528
|
+
// This preserves the CSV shape while normalizing line endings for row handling.
|
|
488
529
|
if (formatStyle === "raw") {
|
|
489
|
-
const lines = csvString
|
|
530
|
+
const lines = splitCsvLines(csvString);
|
|
490
531
|
const hasMetadataLine = isMetadataLine(lines);
|
|
491
532
|
if (hasMetadataLine) {
|
|
492
533
|
logger.debug("[CSVProcessor] Detected metadata line, skipping first line");
|
|
@@ -649,7 +690,10 @@ export class CSVProcessor {
|
|
|
649
690
|
let buffer = "";
|
|
650
691
|
lineReader.on("data", (chunk) => {
|
|
651
692
|
buffer += chunk.toString();
|
|
652
|
-
const
|
|
693
|
+
const splitBuffer = buffer.endsWith("\r")
|
|
694
|
+
? buffer.slice(0, -1)
|
|
695
|
+
: buffer;
|
|
696
|
+
const lines = splitCsvLines(splitBuffer);
|
|
653
697
|
if (lines.length >= 2) {
|
|
654
698
|
firstLines.push(lines[0], lines[1]);
|
|
655
699
|
lineReader.destroy();
|
|
@@ -714,9 +758,11 @@ export class CSVProcessor {
|
|
|
714
758
|
maxRows: clampedMaxRows,
|
|
715
759
|
});
|
|
716
760
|
// Detect and skip metadata line
|
|
717
|
-
const lines = csvString
|
|
761
|
+
const lines = splitCsvLines(csvString);
|
|
718
762
|
const hasMetadataLine = isMetadataLine(lines);
|
|
719
|
-
const csvData = hasMetadataLine
|
|
763
|
+
const csvData = hasMetadataLine
|
|
764
|
+
? lines.slice(1).join("\n")
|
|
765
|
+
: lines.join("\n");
|
|
720
766
|
if (hasMetadataLine) {
|
|
721
767
|
logger.debug("[CSVProcessor] Detected metadata line in string, skipping");
|
|
722
768
|
}
|
|
@@ -773,7 +819,10 @@ export class CSVProcessor {
|
|
|
773
819
|
}
|
|
774
820
|
const headers = Object.keys(rows[0]);
|
|
775
821
|
// Escape backslashes, pipes, and sanitize newlines to keep rows intact
|
|
776
|
-
const escapePipe = (str) => str
|
|
822
|
+
const escapePipe = (str) => str
|
|
823
|
+
.replace(/\\/g, "\\\\")
|
|
824
|
+
.replace(/\|/g, "\\|")
|
|
825
|
+
.replace(/\r\n|\n|\r/g, " ");
|
|
777
826
|
let markdown = "";
|
|
778
827
|
if (includeHeaders) {
|
|
779
828
|
markdown = "| " + headers.map(escapePipe).join(" | ") + " |\n";
|
|
@@ -828,7 +877,9 @@ export class CSVProcessor {
|
|
|
828
877
|
const headers = Object.keys(rows[0]);
|
|
829
878
|
// Escape CSV values (wrap in quotes if contains comma, quote, or newline)
|
|
830
879
|
const escapeCSV = (value) => {
|
|
831
|
-
if (value.includes(",") ||
|
|
880
|
+
if (value.includes(",") ||
|
|
881
|
+
value.includes('"') ||
|
|
882
|
+
/\r\n|\n|\r/.test(value)) {
|
|
832
883
|
return `"${value.replace(/"/g, '""')}"`;
|
|
833
884
|
}
|
|
834
885
|
return value;
|
package/dist/neurolink.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type { RedisConversationMemoryManager } from "./core/redisConversationMem
|
|
|
12
12
|
import { ExternalServerManager } from "./mcp/externalServerManager.js";
|
|
13
13
|
import { MCPToolRegistry } from "./mcp/toolRegistry.js";
|
|
14
14
|
import type { DynamicOptions } from "./types/index.js";
|
|
15
|
+
import { SkillsManager } from "./skills/skillsManager.js";
|
|
15
16
|
import { TaskManager } from "./tasks/taskManager.js";
|
|
16
17
|
/**
|
|
17
18
|
* Curator P2-4 dedup (concurrency-safe): native providers emit
|
|
@@ -140,6 +141,8 @@ export declare class NeuroLink {
|
|
|
140
141
|
private cachedFileTools;
|
|
141
142
|
private memoryInstance?;
|
|
142
143
|
private memorySDKConfig?;
|
|
144
|
+
private skillsManagerInstance?;
|
|
145
|
+
private skillsConfig?;
|
|
143
146
|
/**
|
|
144
147
|
* Extract and set Langfuse context from options with proper async scoping
|
|
145
148
|
*/
|
|
@@ -264,6 +267,31 @@ export declare class NeuroLink {
|
|
|
264
267
|
* Only registered when Redis conversation memory is active.
|
|
265
268
|
*/
|
|
266
269
|
private registerMemoryRetrievalTools;
|
|
270
|
+
/**
|
|
271
|
+
* Lazy initialization for the skills subsystem — mirrors ensureMemoryReady().
|
|
272
|
+
* Returns null (and stays null) when skills are not configured or the
|
|
273
|
+
* store failed to initialize; read paths fail open on that null.
|
|
274
|
+
*/
|
|
275
|
+
private ensureSkillsReady;
|
|
276
|
+
/**
|
|
277
|
+
* Register the built-in skill tools (search_skills / list_skills, plus
|
|
278
|
+
* mutation tools when allowMutations is set). Follows the
|
|
279
|
+
* registerMemoryRetrievalTools() pattern: registered via registerTool()
|
|
280
|
+
* so they land in the "user-defined" category that reaches the LLM tool
|
|
281
|
+
* schema, with the manager resolved lazily at execution time.
|
|
282
|
+
*/
|
|
283
|
+
private registerSkillTools;
|
|
284
|
+
/**
|
|
285
|
+
* Append the compact skills index (names + descriptions, never
|
|
286
|
+
* instructions) to the system prompt for one generate()/stream() call.
|
|
287
|
+
* Fails open: any error leaves the prompt untouched.
|
|
288
|
+
*/
|
|
289
|
+
private applySkillsPromptIndex;
|
|
290
|
+
/**
|
|
291
|
+
* Programmatic access to the skills subsystem (search/list/get/mutations).
|
|
292
|
+
* Returns null when skills are not configured or failed to initialize.
|
|
293
|
+
*/
|
|
294
|
+
getSkillsManager(): SkillsManager | null;
|
|
267
295
|
/** Format memory context for prompt inclusion */
|
|
268
296
|
private formatMemoryContext;
|
|
269
297
|
/**
|
package/dist/neurolink.js
CHANGED
|
@@ -54,6 +54,8 @@ import { MCPToolRegistry } from "./mcp/toolRegistry.js";
|
|
|
54
54
|
import { resolveDynamicArgument } from "./dynamic/dynamicResolver.js";
|
|
55
55
|
import { initializeHippocampus } from "./memory/hippocampusInitializer.js";
|
|
56
56
|
import { createMemoryRetrievalTools } from "./memory/memoryRetrievalTools.js";
|
|
57
|
+
import { SkillsManager } from "./skills/skillsManager.js";
|
|
58
|
+
import { createSkillTools } from "./skills/skillTools.js";
|
|
57
59
|
import { getMetricsAggregator, MetricsAggregator, } from "./observability/metricsAggregator.js";
|
|
58
60
|
import { SpanStatus, SpanType, CircuitBreakerOpenError, ConversationMemoryError, ModelAccessDeniedError, } from "./types/index.js";
|
|
59
61
|
import { SpanSerializer } from "./observability/utils/spanSerializer.js";
|
|
@@ -458,6 +460,10 @@ export class NeuroLink {
|
|
|
458
460
|
// Memory instance and config
|
|
459
461
|
memoryInstance;
|
|
460
462
|
memorySDKConfig;
|
|
463
|
+
// Skills subsystem — lazily initialized manager + instance config.
|
|
464
|
+
// `undefined` = not yet attempted, `null` = init failed (stay disabled).
|
|
465
|
+
skillsManagerInstance;
|
|
466
|
+
skillsConfig;
|
|
461
467
|
/**
|
|
462
468
|
* Extract and set Langfuse context from options with proper async scoping
|
|
463
469
|
*/
|
|
@@ -830,6 +836,10 @@ export class NeuroLink {
|
|
|
830
836
|
this.initializeMCPEnhancements(config);
|
|
831
837
|
this.registerFileTools();
|
|
832
838
|
this.registerMemoryRetrievalTools();
|
|
839
|
+
if (config?.skills?.enabled) {
|
|
840
|
+
this.skillsConfig = config.skills;
|
|
841
|
+
this.registerSkillTools();
|
|
842
|
+
}
|
|
833
843
|
this.initializeLangfuse(constructorId, constructorStartTime, constructorHrTimeStart);
|
|
834
844
|
this.initializeMetricsListeners();
|
|
835
845
|
this.logConstructorComplete(constructorId, constructorStartTime, constructorHrTimeStart);
|
|
@@ -1243,6 +1253,107 @@ export class NeuroLink {
|
|
|
1243
1253
|
});
|
|
1244
1254
|
logger.info("[NeuroLink] Memory retrieval tools registered");
|
|
1245
1255
|
}
|
|
1256
|
+
/**
|
|
1257
|
+
* Lazy initialization for the skills subsystem — mirrors ensureMemoryReady().
|
|
1258
|
+
* Returns null (and stays null) when skills are not configured or the
|
|
1259
|
+
* store failed to initialize; read paths fail open on that null.
|
|
1260
|
+
*/
|
|
1261
|
+
ensureSkillsReady() {
|
|
1262
|
+
if (this.skillsManagerInstance !== undefined) {
|
|
1263
|
+
return this.skillsManagerInstance;
|
|
1264
|
+
}
|
|
1265
|
+
if (!this.skillsConfig?.enabled) {
|
|
1266
|
+
this.skillsManagerInstance = null;
|
|
1267
|
+
return null;
|
|
1268
|
+
}
|
|
1269
|
+
try {
|
|
1270
|
+
this.skillsManagerInstance = new SkillsManager(this.skillsConfig);
|
|
1271
|
+
}
|
|
1272
|
+
catch (error) {
|
|
1273
|
+
logger.warn("[NeuroLink] Skills initialization failed — skills disabled for this instance", { error: error instanceof Error ? error.message : String(error) });
|
|
1274
|
+
this.skillsManagerInstance = null;
|
|
1275
|
+
}
|
|
1276
|
+
return this.skillsManagerInstance;
|
|
1277
|
+
}
|
|
1278
|
+
/**
|
|
1279
|
+
* Register the built-in skill tools (search_skills / list_skills, plus
|
|
1280
|
+
* mutation tools when allowMutations is set). Follows the
|
|
1281
|
+
* registerMemoryRetrievalTools() pattern: registered via registerTool()
|
|
1282
|
+
* so they land in the "user-defined" category that reaches the LLM tool
|
|
1283
|
+
* schema, with the manager resolved lazily at execution time.
|
|
1284
|
+
*/
|
|
1285
|
+
registerSkillTools() {
|
|
1286
|
+
const canonicalTools = createSkillTools(() => this.ensureSkillsReady(), {
|
|
1287
|
+
allowMutations: this.skillsConfig?.allowMutations === true,
|
|
1288
|
+
});
|
|
1289
|
+
for (const [toolName, toolDef] of Object.entries(canonicalTools)) {
|
|
1290
|
+
this.registerTool(toolName, {
|
|
1291
|
+
name: toolName,
|
|
1292
|
+
description: toolDef.description ?? toolName,
|
|
1293
|
+
// Zod schema — registerTool() detects isZodSchema and preserves it
|
|
1294
|
+
// so ToolsManager gives the LLM full parameter types.
|
|
1295
|
+
inputSchema: toolDef
|
|
1296
|
+
.inputSchema,
|
|
1297
|
+
execute: async (params) => withTimeout(toolDef.execute(params, { toolCallId: "skill-tool", messages: [] }), TOOL_TIMEOUTS.EXECUTION_DEFAULT_MS, ErrorFactory.toolTimeout(toolName, TOOL_TIMEOUTS.EXECUTION_DEFAULT_MS)),
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1300
|
+
logger.info(`[NeuroLink] Registered ${Object.keys(canonicalTools).length} skill tools`, { allowMutations: this.skillsConfig?.allowMutations === true });
|
|
1301
|
+
}
|
|
1302
|
+
/**
|
|
1303
|
+
* Append the compact skills index (names + descriptions, never
|
|
1304
|
+
* instructions) to the system prompt for one generate()/stream() call.
|
|
1305
|
+
* Fails open: any error leaves the prompt untouched.
|
|
1306
|
+
*/
|
|
1307
|
+
async applySkillsPromptIndex(options) {
|
|
1308
|
+
if (!this.skillsConfig?.enabled || options.skills?.enabled === false) {
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
// Per-call promptIndex wins over instance config; default is on.
|
|
1312
|
+
const promptIndexEnabled = options.skills?.promptIndex ?? this.skillsConfig.promptIndex ?? true;
|
|
1313
|
+
if (!promptIndexEnabled) {
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
// Media-only modes have no meaningful text prompt to augment.
|
|
1317
|
+
const mode = options.output?.mode;
|
|
1318
|
+
if (mode === "avatar" ||
|
|
1319
|
+
mode === "music" ||
|
|
1320
|
+
mode === "video" ||
|
|
1321
|
+
mode === "ppt") {
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
try {
|
|
1325
|
+
const manager = this.ensureSkillsReady();
|
|
1326
|
+
if (!manager) {
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
1329
|
+
const block = await manager.buildPromptIndex({
|
|
1330
|
+
...(options.skills?.scopeId !== undefined
|
|
1331
|
+
? { scopeId: options.skills.scopeId }
|
|
1332
|
+
: {}),
|
|
1333
|
+
...(options.skills?.tags !== undefined
|
|
1334
|
+
? { tags: options.skills.tags }
|
|
1335
|
+
: {}),
|
|
1336
|
+
});
|
|
1337
|
+
if (block) {
|
|
1338
|
+
options.systemPrompt = options.systemPrompt
|
|
1339
|
+
? `${options.systemPrompt}\n\n${block}`
|
|
1340
|
+
: block;
|
|
1341
|
+
logger.debug("[NeuroLink] Skills prompt index injected", {
|
|
1342
|
+
blockLength: block.length,
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
catch (error) {
|
|
1347
|
+
logger.warn("[NeuroLink] Skills prompt index injection failed — continuing without it", { error: error instanceof Error ? error.message : String(error) });
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Programmatic access to the skills subsystem (search/list/get/mutations).
|
|
1352
|
+
* Returns null when skills are not configured or failed to initialize.
|
|
1353
|
+
*/
|
|
1354
|
+
getSkillsManager() {
|
|
1355
|
+
return this.ensureSkillsReady();
|
|
1356
|
+
}
|
|
1246
1357
|
/** Format memory context for prompt inclusion */
|
|
1247
1358
|
formatMemoryContext(memoryContext, currentInput) {
|
|
1248
1359
|
return `Context from previous conversations:
|
|
@@ -3499,6 +3610,9 @@ Current user's request: ${currentInput}`;
|
|
|
3499
3610
|
});
|
|
3500
3611
|
}
|
|
3501
3612
|
}
|
|
3613
|
+
// Skills: append the compact skills index to the system prompt so the
|
|
3614
|
+
// model knows which skills exist (bodies load via search_skills).
|
|
3615
|
+
await this.applySkillsPromptIndex(options);
|
|
3502
3616
|
// Media-only modes (avatar, music, video, ppt) do not have a meaningful
|
|
3503
3617
|
// text prompt to augment with memory — skip injection to avoid corrupting
|
|
3504
3618
|
// the empty/synthesized input.text that was set for these modes.
|
|
@@ -7037,6 +7151,9 @@ Current user's request: ${currentInput}`;
|
|
|
7037
7151
|
logger.warn("Memory retrieval failed:", error);
|
|
7038
7152
|
}
|
|
7039
7153
|
}
|
|
7154
|
+
// Skills: append the compact skills index to the system prompt so the
|
|
7155
|
+
// model knows which skills exist (bodies load via search_skills).
|
|
7156
|
+
await this.applySkillsPromptIndex(options);
|
|
7040
7157
|
// Apply orchestration if enabled and no specific provider/model requested
|
|
7041
7158
|
if (this.enableOrchestration && !options.provider && !options.model) {
|
|
7042
7159
|
try {
|