@dory-agentic/dory-agentic-sdk 0.2.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/LICENSE +21 -0
- package/README.md +331 -0
- package/dist/agent/createLfsAgent.d.ts +27 -0
- package/dist/agent/createLfsAgent.d.ts.map +1 -0
- package/dist/agent/createLfsAgent.js +51 -0
- package/dist/agent/loopConfig.d.ts +6 -0
- package/dist/agent/loopConfig.d.ts.map +1 -0
- package/dist/agent/loopConfig.js +5 -0
- package/dist/agent/runLfsTurn.d.ts +10 -0
- package/dist/agent/runLfsTurn.d.ts.map +1 -0
- package/dist/agent/runLfsTurn.js +20 -0
- package/dist/history/dorycodeAdapter.d.ts +16 -0
- package/dist/history/dorycodeAdapter.d.ts.map +1 -0
- package/dist/history/dorycodeAdapter.js +18 -0
- package/dist/history/formatForModel.d.ts +19 -0
- package/dist/history/formatForModel.d.ts.map +1 -0
- package/dist/history/formatForModel.js +22 -0
- package/dist/history/lanes.d.ts +3 -0
- package/dist/history/lanes.d.ts.map +1 -0
- package/dist/history/lanes.js +3 -0
- package/dist/history/messageManifest.d.ts +45 -0
- package/dist/history/messageManifest.d.ts.map +1 -0
- package/dist/history/messageManifest.js +231 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/lfs/adaptiveCoefficients.d.ts +13 -0
- package/dist/lfs/adaptiveCoefficients.d.ts.map +1 -0
- package/dist/lfs/adaptiveCoefficients.js +16 -0
- package/dist/lfs/attenuationCompiler.d.ts +16 -0
- package/dist/lfs/attenuationCompiler.d.ts.map +1 -0
- package/dist/lfs/attenuationCompiler.js +123 -0
- package/dist/lfs/chunkManager.d.ts +6 -0
- package/dist/lfs/chunkManager.d.ts.map +1 -0
- package/dist/lfs/chunkManager.js +142 -0
- package/dist/lfs/config.d.ts +23 -0
- package/dist/lfs/config.d.ts.map +1 -0
- package/dist/lfs/config.js +34 -0
- package/dist/lfs/contextAdapter.d.ts +5 -0
- package/dist/lfs/contextAdapter.d.ts.map +1 -0
- package/dist/lfs/contextAdapter.js +161 -0
- package/dist/lfs/memoryEngine.d.ts +108 -0
- package/dist/lfs/memoryEngine.d.ts.map +1 -0
- package/dist/lfs/memoryEngine.js +322 -0
- package/dist/lfs/memoryParser.d.ts +121 -0
- package/dist/lfs/memoryParser.d.ts.map +1 -0
- package/dist/lfs/memoryParser.js +743 -0
- package/dist/lfs/paritySwapper.d.ts +20 -0
- package/dist/lfs/paritySwapper.d.ts.map +1 -0
- package/dist/lfs/paritySwapper.js +317 -0
- package/dist/lfs/preflightRouter.d.ts +14 -0
- package/dist/lfs/preflightRouter.d.ts.map +1 -0
- package/dist/lfs/preflightRouter.js +24 -0
- package/dist/lfs/runtimeConfig.d.ts +31 -0
- package/dist/lfs/runtimeConfig.d.ts.map +1 -0
- package/dist/lfs/runtimeConfig.js +40 -0
- package/dist/lfs/sessionStore.d.ts +14 -0
- package/dist/lfs/sessionStore.d.ts.map +1 -0
- package/dist/lfs/sessionStore.js +52 -0
- package/dist/lfs/tokenCounter.d.ts +90 -0
- package/dist/lfs/tokenCounter.d.ts.map +1 -0
- package/dist/lfs/tokenCounter.js +26 -0
- package/dist/lfs/types.d.ts +44 -0
- package/dist/lfs/types.d.ts.map +1 -0
- package/dist/lfs/types.js +1 -0
- package/dist/lfs/vectorDb.d.ts +19 -0
- package/dist/lfs/vectorDb.d.ts.map +1 -0
- package/dist/lfs/vectorDb.js +158 -0
- package/dist/mcp/client.d.ts +23 -0
- package/dist/mcp/client.d.ts.map +1 -0
- package/dist/mcp/client.js +60 -0
- package/dist/mcp/config.d.ts +10 -0
- package/dist/mcp/config.d.ts.map +1 -0
- package/dist/mcp/config.js +72 -0
- package/dist/providers/ollama.d.ts +2 -0
- package/dist/providers/ollama.d.ts.map +1 -0
- package/dist/providers/ollama.js +7 -0
- package/dist/providers/registry.d.ts +5 -0
- package/dist/providers/registry.d.ts.map +1 -0
- package/dist/providers/registry.js +9 -0
- package/dist/tools/registry.d.ts +14 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +26 -0
- package/dist/tools/remind.d.ts +15 -0
- package/dist/tools/remind.d.ts.map +1 -0
- package/dist/tools/remind.js +53 -0
- package/dist/tools/skillLoader.d.ts +12 -0
- package/dist/tools/skillLoader.d.ts.map +1 -0
- package/dist/tools/skillLoader.js +38 -0
- package/package.json +66 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type { AdaptiveMessage as LfsMessage, MemoryChunk, TurnMemorySnapshot, AllocationMap } from "./tokenCounter";
|
|
2
|
+
export type { ContextShift } from "./memoryParser";
|
|
3
|
+
export type LfsLane = "standard" | "optimized";
|
|
4
|
+
export type SessionStoreState = {
|
|
5
|
+
sessionId: string;
|
|
6
|
+
fullHistory: import("./tokenCounter").AdaptiveMessage[];
|
|
7
|
+
longTermMemory: Record<string, import("./tokenCounter").MemoryChunk>;
|
|
8
|
+
forgetScores: Record<string, number>;
|
|
9
|
+
agentWeights: Record<string, number>;
|
|
10
|
+
contextMax: number;
|
|
11
|
+
memoryCapFraction: number;
|
|
12
|
+
turnIndex: number;
|
|
13
|
+
};
|
|
14
|
+
export type PrepareStepInput = {
|
|
15
|
+
sessionId: string;
|
|
16
|
+
lane: LfsLane;
|
|
17
|
+
incomingMessages: import("./tokenCounter").AdaptiveMessage[];
|
|
18
|
+
contextMax: number;
|
|
19
|
+
memoryCapFraction?: number;
|
|
20
|
+
benchmarkSuite?: boolean;
|
|
21
|
+
stepNumber?: number;
|
|
22
|
+
query?: string;
|
|
23
|
+
turnIndex?: number;
|
|
24
|
+
agentWeights?: Record<string, number>;
|
|
25
|
+
hydrationBeacon?: string;
|
|
26
|
+
enableReflection?: boolean;
|
|
27
|
+
useRemindTool?: boolean;
|
|
28
|
+
/** When true, incomingMessages replace session history instead of merging. */
|
|
29
|
+
replaceSessionHistory?: boolean;
|
|
30
|
+
};
|
|
31
|
+
export type PrepareStepOutput = {
|
|
32
|
+
lane: LfsLane;
|
|
33
|
+
executionLane: "PASS_THRU" | "ELASTIC_SWAP";
|
|
34
|
+
isMmuActive: boolean;
|
|
35
|
+
messagesForModel: Array<{
|
|
36
|
+
role: string;
|
|
37
|
+
content: string;
|
|
38
|
+
}>;
|
|
39
|
+
systemBlocks: string[];
|
|
40
|
+
session: SessionStoreState;
|
|
41
|
+
budget?: number;
|
|
42
|
+
remindClause?: string;
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lfs/types.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,eAAe,IAAI,UAAU,EAAE,WAAW,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpH,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,MAAM,MAAM,OAAO,GAAG,UAAU,GAAG,WAAW,CAAC;AAE/C,MAAM,MAAM,iBAAiB,GAAG;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,OAAO,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACxD,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,gBAAgB,EAAE,WAAW,CAAC,CAAC;IACrE,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,OAAO,CAAC;IACd,gBAAgB,EAAE,OAAO,gBAAgB,EAAE,eAAe,EAAE,CAAC;IAC7D,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,8EAA8E;IAC9E,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,OAAO,CAAC;IACd,aAAa,EAAE,WAAW,GAAG,cAAc,CAAC;IAC5C,WAAW,EAAE,OAAO,CAAC;IACrB,gBAAgB,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3D,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,EAAE,iBAAiB,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare function generateLocalEmbedding(text: string): Promise<number[]>;
|
|
2
|
+
export interface ChunkRecord {
|
|
3
|
+
chunkId: string;
|
|
4
|
+
parentMessageId: string;
|
|
5
|
+
vector: number[];
|
|
6
|
+
isArchived: boolean;
|
|
7
|
+
timestamp: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function upsertChunkVector(chunkId: string, parentMessageId: string, text: string): Promise<void>;
|
|
10
|
+
export declare function setChunkArchiveStatus(chunkId: string, isArchived: boolean): void;
|
|
11
|
+
export declare function clearVectorCache(): void;
|
|
12
|
+
export declare function queryChunkVectorCache(queryText: string, limit?: number): Promise<{
|
|
13
|
+
chunkId: string;
|
|
14
|
+
parentMessageId: string;
|
|
15
|
+
score: number;
|
|
16
|
+
}[]>;
|
|
17
|
+
export declare function upsertVector(messageId: string, text: string): Promise<void>;
|
|
18
|
+
export declare function queryVectorCache(queryText: string, limit?: number): Promise<string[]>;
|
|
19
|
+
//# sourceMappingURL=vectorDb.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vectorDb.d.ts","sourceRoot":"","sources":["../../src/lfs/vectorDb.ts"],"names":[],"mappings":"AAkFA,wBAAsB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAkC5E;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAID,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,eAAe,EAAE,MAAM,EACvB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CASf;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG,IAAI,CAIhF;AAED,wBAAgB,gBAAgB,IAAI,IAAI,CAIvC;AAWD,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,MAAM,EACjB,KAAK,SAAI,GACR,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,CAexE;AAED,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEjF;AAED,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAGtF"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { estimateTextTokens } from "./tokenCounter";
|
|
2
|
+
/**
|
|
3
|
+
* 128-dimensional deterministic character-hash-based vectorizer.
|
|
4
|
+
* Guarantees fast offline execution; dot product equals cosine similarity after L2 norm.
|
|
5
|
+
*/
|
|
6
|
+
function generateHashEmbedding(text) {
|
|
7
|
+
const dims = 128;
|
|
8
|
+
const vector = new Array(dims).fill(0);
|
|
9
|
+
if (!text || !text.trim())
|
|
10
|
+
return vector;
|
|
11
|
+
const words = text
|
|
12
|
+
.toLowerCase()
|
|
13
|
+
.replace(/[^\w\s-]/g, " ")
|
|
14
|
+
.split(/\s+/)
|
|
15
|
+
.filter((w) => w.length > 1);
|
|
16
|
+
if (words.length === 0)
|
|
17
|
+
return vector;
|
|
18
|
+
for (const word of words) {
|
|
19
|
+
let hash = 5381;
|
|
20
|
+
for (let i = 0; i < word.length; i++) {
|
|
21
|
+
hash = (hash * 33) ^ word.charCodeAt(i);
|
|
22
|
+
}
|
|
23
|
+
const idx = Math.abs(hash) % dims;
|
|
24
|
+
vector[idx] += 1;
|
|
25
|
+
}
|
|
26
|
+
let sumSq = 0;
|
|
27
|
+
for (let i = 0; i < dims; i++) {
|
|
28
|
+
sumSq += vector[i] * vector[i];
|
|
29
|
+
}
|
|
30
|
+
const norm = Math.sqrt(sumSq);
|
|
31
|
+
if (norm > 0) {
|
|
32
|
+
for (let i = 0; i < dims; i++) {
|
|
33
|
+
vector[i] /= norm;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return vector;
|
|
37
|
+
}
|
|
38
|
+
function sliceTextIntoSubTokens(text, maxTokens = 512) {
|
|
39
|
+
if (!text || !text.trim())
|
|
40
|
+
return [];
|
|
41
|
+
const lines = text.split("\n");
|
|
42
|
+
const slices = [];
|
|
43
|
+
let currentSlice = "";
|
|
44
|
+
for (const line of lines) {
|
|
45
|
+
const lineWithNL = line + "\n";
|
|
46
|
+
if (estimateTextTokens(currentSlice + lineWithNL) <= maxTokens) {
|
|
47
|
+
currentSlice += lineWithNL;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
if (currentSlice.trim()) {
|
|
51
|
+
slices.push(currentSlice);
|
|
52
|
+
}
|
|
53
|
+
currentSlice = lineWithNL;
|
|
54
|
+
if (estimateTextTokens(currentSlice) > maxTokens) {
|
|
55
|
+
const words = currentSlice.split(/\s+/);
|
|
56
|
+
let part = "";
|
|
57
|
+
for (const word of words) {
|
|
58
|
+
const proposed = part ? part + " " + word : word;
|
|
59
|
+
if (estimateTextTokens(proposed) <= maxTokens) {
|
|
60
|
+
part = proposed;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
if (part.trim())
|
|
64
|
+
slices.push(part);
|
|
65
|
+
part = word;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
currentSlice = part;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (currentSlice.trim()) {
|
|
73
|
+
slices.push(currentSlice);
|
|
74
|
+
}
|
|
75
|
+
return slices;
|
|
76
|
+
}
|
|
77
|
+
export async function generateLocalEmbedding(text) {
|
|
78
|
+
const tokenEst = estimateTextTokens(text);
|
|
79
|
+
if (tokenEst > 512) {
|
|
80
|
+
const slices = sliceTextIntoSubTokens(text, 512);
|
|
81
|
+
if (slices.length > 1) {
|
|
82
|
+
const vectors = await Promise.all(slices.map((s) => generateLocalEmbedding(s)));
|
|
83
|
+
const dims = vectors[0].length;
|
|
84
|
+
const centroid = new Array(dims).fill(0);
|
|
85
|
+
for (const vec of vectors) {
|
|
86
|
+
for (let i = 0; i < dims; i++) {
|
|
87
|
+
centroid[i] += vec[i];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
for (let i = 0; i < dims; i++) {
|
|
91
|
+
centroid[i] /= vectors.length;
|
|
92
|
+
}
|
|
93
|
+
let sumSq = 0;
|
|
94
|
+
for (let i = 0; i < dims; i++) {
|
|
95
|
+
sumSq += centroid[i] * centroid[i];
|
|
96
|
+
}
|
|
97
|
+
const norm = Math.sqrt(sumSq);
|
|
98
|
+
if (norm > 0) {
|
|
99
|
+
for (let i = 0; i < dims; i++) {
|
|
100
|
+
centroid[i] /= norm;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return centroid;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return generateHashEmbedding(text);
|
|
107
|
+
}
|
|
108
|
+
const chunkVectorCache = {};
|
|
109
|
+
export async function upsertChunkVector(chunkId, parentMessageId, text) {
|
|
110
|
+
const vector = await generateLocalEmbedding(text);
|
|
111
|
+
chunkVectorCache[chunkId] = {
|
|
112
|
+
chunkId,
|
|
113
|
+
parentMessageId,
|
|
114
|
+
vector,
|
|
115
|
+
isArchived: false,
|
|
116
|
+
timestamp: Date.now(),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
export function setChunkArchiveStatus(chunkId, isArchived) {
|
|
120
|
+
if (chunkVectorCache[chunkId]) {
|
|
121
|
+
chunkVectorCache[chunkId].isArchived = isArchived;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
export function clearVectorCache() {
|
|
125
|
+
for (const key in chunkVectorCache) {
|
|
126
|
+
delete chunkVectorCache[key];
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function cosineSimilarity(a, b) {
|
|
130
|
+
if (a.length !== b.length)
|
|
131
|
+
return 0;
|
|
132
|
+
let dotProduct = 0;
|
|
133
|
+
for (let i = 0; i < a.length; i++) {
|
|
134
|
+
dotProduct += a[i] * b[i];
|
|
135
|
+
}
|
|
136
|
+
return dotProduct;
|
|
137
|
+
}
|
|
138
|
+
export async function queryChunkVectorCache(queryText, limit = 5) {
|
|
139
|
+
const queryVector = await generateLocalEmbedding(queryText);
|
|
140
|
+
const matches = [];
|
|
141
|
+
for (const record of Object.values(chunkVectorCache)) {
|
|
142
|
+
const score = cosineSimilarity(queryVector, record.vector);
|
|
143
|
+
matches.push({
|
|
144
|
+
chunkId: record.chunkId,
|
|
145
|
+
parentMessageId: record.parentMessageId,
|
|
146
|
+
score,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
matches.sort((a, b) => b.score - a.score);
|
|
150
|
+
return matches.slice(0, limit).filter((m) => m.score > 0.05);
|
|
151
|
+
}
|
|
152
|
+
export async function upsertVector(messageId, text) {
|
|
153
|
+
await upsertChunkVector(`${messageId}_chunk_0`, messageId, text);
|
|
154
|
+
}
|
|
155
|
+
export async function queryVectorCache(queryText, limit = 5) {
|
|
156
|
+
const matches = await queryChunkVectorCache(queryText, limit);
|
|
157
|
+
return Array.from(new Set(matches.map((m) => m.parentMessageId)));
|
|
158
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ToolSet } from "ai";
|
|
2
|
+
export type McpClientOptions = {
|
|
3
|
+
command: string;
|
|
4
|
+
args: string[];
|
|
5
|
+
allowlist?: {
|
|
6
|
+
command: string;
|
|
7
|
+
argsPrefix?: string[];
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
export type McpClientHandle = {
|
|
11
|
+
tools: () => Promise<ToolSet>;
|
|
12
|
+
close: () => Promise<void>;
|
|
13
|
+
};
|
|
14
|
+
export declare function createFetchMcpClient(opts: McpClientOptions): Promise<McpClientHandle>;
|
|
15
|
+
export declare function listMcpTools(opts: McpClientOptions): Promise<ToolSet>;
|
|
16
|
+
export declare function callMcpTool(opts: McpClientOptions, toolName: string, args: Record<string, unknown>): Promise<{
|
|
17
|
+
ok: true;
|
|
18
|
+
result: unknown;
|
|
19
|
+
} | {
|
|
20
|
+
ok: false;
|
|
21
|
+
error: string;
|
|
22
|
+
}>;
|
|
23
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/mcp/client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAGlC,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,SAAS,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CACxD,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9B,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B,CAAC;AAqBF,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAU3F;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,gBAAgB,oBAOxD;AAED,wBAAsB,WAAW,CAC/B,IAAI,EAAE,gBAAgB,EACtB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAmBvE"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { experimental_createMCPClient as createMcpClient } from "@ai-sdk/mcp";
|
|
2
|
+
import { validateFetchUrl } from "./config";
|
|
3
|
+
function validateMcpSpawn(opts) {
|
|
4
|
+
if (opts.allowlist) {
|
|
5
|
+
const { command, argsPrefix = [] } = opts.allowlist;
|
|
6
|
+
if (opts.command !== command) {
|
|
7
|
+
throw new Error(`MCP command not allowed: ${opts.command}`);
|
|
8
|
+
}
|
|
9
|
+
for (let i = 0; i < argsPrefix.length; i++) {
|
|
10
|
+
if (opts.args[i] !== argsPrefix[i]) {
|
|
11
|
+
throw new Error("MCP args do not match allowlist prefix");
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const combined = `${opts.command} ${opts.args.join(" ")}`;
|
|
16
|
+
if (/[;&|`$<>]/.test(combined)) {
|
|
17
|
+
throw new Error("MCP command contains disallowed shell metacharacters");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export async function createFetchMcpClient(opts) {
|
|
21
|
+
validateMcpSpawn(opts);
|
|
22
|
+
const client = await createMcpClient({
|
|
23
|
+
transport: {
|
|
24
|
+
type: "stdio",
|
|
25
|
+
command: opts.command,
|
|
26
|
+
args: opts.args,
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
return client;
|
|
30
|
+
}
|
|
31
|
+
export async function listMcpTools(opts) {
|
|
32
|
+
const client = await createFetchMcpClient(opts);
|
|
33
|
+
try {
|
|
34
|
+
return await client.tools();
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
await client.close();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export async function callMcpTool(opts, toolName, args) {
|
|
41
|
+
if (typeof args.url === "string") {
|
|
42
|
+
const checked = validateFetchUrl(args.url);
|
|
43
|
+
if (!checked.ok)
|
|
44
|
+
return { ok: false, error: checked.error };
|
|
45
|
+
}
|
|
46
|
+
const client = await createFetchMcpClient(opts);
|
|
47
|
+
try {
|
|
48
|
+
const tools = await client.tools();
|
|
49
|
+
const tool = tools[toolName];
|
|
50
|
+
if (!tool?.execute)
|
|
51
|
+
return { ok: false, error: `Unknown MCP tool: ${toolName}` };
|
|
52
|
+
return { ok: true, result: await tool.execute(args) };
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
return { ok: false, error: error.message };
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
await client.close();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const MCP_TOOL_RESULT_MAX_CHARS = 8000;
|
|
2
|
+
export declare function getFetchUrlAllowlist(): Set<string> | null;
|
|
3
|
+
export declare function isPrivateHost(hostname: string): boolean;
|
|
4
|
+
export declare function validateFetchUrl(url: string): {
|
|
5
|
+
ok: true;
|
|
6
|
+
} | {
|
|
7
|
+
ok: false;
|
|
8
|
+
error: string;
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/mcp/config.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAE9C,wBAAgB,oBAAoB,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAWzD;AAiBD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAmBvD;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAqBzF"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { isIP } from "node:net";
|
|
2
|
+
export const MCP_TOOL_RESULT_MAX_CHARS = 8000;
|
|
3
|
+
export function getFetchUrlAllowlist() {
|
|
4
|
+
const raw = process.env.DORY_AGENTIC_MCP_FETCH_ALLOWLIST?.trim() ||
|
|
5
|
+
process.env.MCP_FETCH_URL_ALLOWLIST?.trim();
|
|
6
|
+
if (!raw)
|
|
7
|
+
return null;
|
|
8
|
+
return new Set(raw
|
|
9
|
+
.split(",")
|
|
10
|
+
.map((h) => h.trim().toLowerCase())
|
|
11
|
+
.filter(Boolean));
|
|
12
|
+
}
|
|
13
|
+
function isPrivateIpv4(host) {
|
|
14
|
+
const parts = host.split(".").map((p) => Number(p));
|
|
15
|
+
if (parts.length !== 4 || parts.some((n) => !Number.isFinite(n) || n < 0 || n > 255)) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
const [a, b] = parts;
|
|
19
|
+
if (a === 10)
|
|
20
|
+
return true;
|
|
21
|
+
if (a === 127)
|
|
22
|
+
return true;
|
|
23
|
+
if (a === 0)
|
|
24
|
+
return true;
|
|
25
|
+
if (a === 169 && b === 254)
|
|
26
|
+
return true;
|
|
27
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
28
|
+
return true;
|
|
29
|
+
if (a === 192 && b === 168)
|
|
30
|
+
return true;
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
export function isPrivateHost(hostname) {
|
|
34
|
+
const host = hostname.toLowerCase().replace(/\.$/, "");
|
|
35
|
+
if (host === "localhost" || host.endsWith(".local") || host.endsWith(".internal")) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
if (host === "::1" || host === "0:0:0:0:0:0:0:1") {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
const ipVersion = isIP(host);
|
|
42
|
+
if (ipVersion === 4) {
|
|
43
|
+
return isPrivateIpv4(host);
|
|
44
|
+
}
|
|
45
|
+
if (ipVersion === 6) {
|
|
46
|
+
const h = host.toLowerCase();
|
|
47
|
+
if (h.startsWith("fc") || h.startsWith("fd") || h.startsWith("fe80")) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
export function validateFetchUrl(url) {
|
|
54
|
+
let parsed;
|
|
55
|
+
try {
|
|
56
|
+
parsed = new URL(url);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return { ok: false, error: "Invalid URL" };
|
|
60
|
+
}
|
|
61
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
62
|
+
return { ok: false, error: "Only http(s) URLs allowed" };
|
|
63
|
+
}
|
|
64
|
+
if (isPrivateHost(parsed.hostname)) {
|
|
65
|
+
return { ok: false, error: "Private/local URLs blocked" };
|
|
66
|
+
}
|
|
67
|
+
const allowlist = getFetchUrlAllowlist();
|
|
68
|
+
if (allowlist && !allowlist.has(parsed.hostname.toLowerCase())) {
|
|
69
|
+
return { ok: false, error: `Host not in allowlist: ${parsed.hostname}` };
|
|
70
|
+
}
|
|
71
|
+
return { ok: true };
|
|
72
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ollama.d.ts","sourceRoot":"","sources":["../../src/providers/ollama.ts"],"names":[],"mappings":"AAEA,wBAAgB,oBAAoB,CAAC,OAAO,SAA8B,gGAKzE"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function createAgentSdkProviderRegistry(): import("ai").ProviderRegistryProvider<{
|
|
2
|
+
ollama: import("@ai-sdk/openai-compatible").OpenAICompatibleProvider<string, string, string, string>;
|
|
3
|
+
openai: import("@ai-sdk/openai").OpenAIProvider;
|
|
4
|
+
}, ":">;
|
|
5
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/providers/registry.ts"],"names":[],"mappings":"AAIA,wBAAgB,8BAA8B;;;QAK7C"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
2
|
+
import { createProviderRegistry } from "ai";
|
|
3
|
+
import { createOllamaProvider } from "./ollama";
|
|
4
|
+
export function createAgentSdkProviderRegistry() {
|
|
5
|
+
return createProviderRegistry({
|
|
6
|
+
ollama: createOllamaProvider(),
|
|
7
|
+
openai: createOpenAI(),
|
|
8
|
+
});
|
|
9
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ToolSet } from "ai";
|
|
2
|
+
import type { SessionStoreState } from "../lfs/types";
|
|
3
|
+
import { type SkillLoaderOptions } from "./skillLoader";
|
|
4
|
+
import { type McpClientOptions } from "../mcp/client";
|
|
5
|
+
export type ToolRegistryResult = {
|
|
6
|
+
tools: ToolSet;
|
|
7
|
+
dispose: () => Promise<void>;
|
|
8
|
+
};
|
|
9
|
+
export declare function createToolRegistry(params: {
|
|
10
|
+
session: SessionStoreState;
|
|
11
|
+
mcp?: McpClientOptions;
|
|
12
|
+
skillLoader?: SkillLoaderOptions;
|
|
13
|
+
}): Promise<ToolRegistryResult>;
|
|
14
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/tools/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEtD,OAAO,EAAyB,KAAK,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAC/E,OAAO,EAAwB,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAE5E,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B,CAAC;AAEF,wBAAsB,kBAAkB,CAAC,MAAM,EAAE;IAC/C,OAAO,EAAE,iBAAiB,CAAC;IAC3B,GAAG,CAAC,EAAE,gBAAgB,CAAC;IACvB,WAAW,CAAC,EAAE,kBAAkB,CAAC;CAClC,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA0B9B"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createRemindTool } from "./remind";
|
|
2
|
+
import { createSkillLoaderTool } from "./skillLoader";
|
|
3
|
+
import { createFetchMcpClient } from "../mcp/client";
|
|
4
|
+
export async function createToolRegistry(params) {
|
|
5
|
+
const tools = {
|
|
6
|
+
remind: createRemindTool(params.session),
|
|
7
|
+
};
|
|
8
|
+
const skillTool = createSkillLoaderTool(params.skillLoader);
|
|
9
|
+
if (skillTool) {
|
|
10
|
+
tools.skillLoader = skillTool;
|
|
11
|
+
}
|
|
12
|
+
let mcpClient = null;
|
|
13
|
+
if (params.mcp) {
|
|
14
|
+
mcpClient = await createFetchMcpClient(params.mcp);
|
|
15
|
+
const mcpTools = await mcpClient.tools();
|
|
16
|
+
Object.assign(tools, mcpTools);
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
tools,
|
|
20
|
+
dispose: async () => {
|
|
21
|
+
if (mcpClient) {
|
|
22
|
+
await mcpClient.close();
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { SessionStoreState } from "../lfs/types";
|
|
2
|
+
type RemindResult = {
|
|
3
|
+
hydratedIds: string[];
|
|
4
|
+
restored: Array<{
|
|
5
|
+
id: string;
|
|
6
|
+
content: string;
|
|
7
|
+
}>;
|
|
8
|
+
};
|
|
9
|
+
export declare function buildLfsRemindClause(state: SessionStoreState): string | undefined;
|
|
10
|
+
export declare function hydrateLfsTarget(state: SessionStoreState, target: string): RemindResult;
|
|
11
|
+
export declare function createRemindTool(state: SessionStoreState): import("ai").Tool<{
|
|
12
|
+
target: string;
|
|
13
|
+
}, RemindResult>;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=remind.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remind.d.ts","sourceRoot":"","sources":["../../src/tools/remind.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEtD,KAAK,YAAY,GAAG;IAClB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClD,CAAC;AAWF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,GAAG,SAAS,CAQjF;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,GAAG,YAAY,CAyBvF;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,iBAAiB;;iBAQxD"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { tool } from "ai";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
function isChunkMatch(chunkId, messageIdOrChunkId, parentMessageId) {
|
|
4
|
+
return (chunkId === messageIdOrChunkId ||
|
|
5
|
+
chunkId.startsWith(`${messageIdOrChunkId}_`) ||
|
|
6
|
+
parentMessageId === messageIdOrChunkId ||
|
|
7
|
+
parentMessageId.startsWith(`${messageIdOrChunkId}_`));
|
|
8
|
+
}
|
|
9
|
+
export function buildLfsRemindClause(state) {
|
|
10
|
+
const hasArchived = Object.values(state.longTermMemory).some((chunk) => chunk.isArchived);
|
|
11
|
+
if (!hasArchived)
|
|
12
|
+
return undefined;
|
|
13
|
+
return [
|
|
14
|
+
"REMIND TOOL (LFS): Archived context appears as attenuated placeholders.",
|
|
15
|
+
"Call remind with chunkId or messageId to hydrate exact raw text.",
|
|
16
|
+
"Hydrate only what is needed for the current turn.",
|
|
17
|
+
].join(" ");
|
|
18
|
+
}
|
|
19
|
+
export function hydrateLfsTarget(state, target) {
|
|
20
|
+
const hydratedIds = [];
|
|
21
|
+
const restored = [];
|
|
22
|
+
for (const [chunkId, chunk] of Object.entries(state.longTermMemory)) {
|
|
23
|
+
if (!isChunkMatch(chunkId, target, chunk.parentMessageId))
|
|
24
|
+
continue;
|
|
25
|
+
chunk.isArchived = false;
|
|
26
|
+
chunk.forgetScore = 0;
|
|
27
|
+
hydratedIds.push(chunkId);
|
|
28
|
+
restored.push({ id: chunkId, content: chunk.content });
|
|
29
|
+
delete state.longTermMemory[chunkId];
|
|
30
|
+
}
|
|
31
|
+
for (const msg of state.fullHistory) {
|
|
32
|
+
if (!msg.chunks)
|
|
33
|
+
continue;
|
|
34
|
+
for (const chunk of msg.chunks) {
|
|
35
|
+
if (!isChunkMatch(chunk.chunkId, target, chunk.parentMessageId))
|
|
36
|
+
continue;
|
|
37
|
+
chunk.isArchived = false;
|
|
38
|
+
chunk.forgetScore = 0;
|
|
39
|
+
hydratedIds.push(chunk.chunkId);
|
|
40
|
+
restored.push({ id: chunk.chunkId, content: chunk.content });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return { hydratedIds: [...new Set(hydratedIds)], restored };
|
|
44
|
+
}
|
|
45
|
+
export function createRemindTool(state) {
|
|
46
|
+
return tool({
|
|
47
|
+
description: "Restore archived LFS chunk(s) by chunkId or messageId.",
|
|
48
|
+
inputSchema: z.object({
|
|
49
|
+
target: z.string().min(1).describe("chunkId or messageId to hydrate"),
|
|
50
|
+
}),
|
|
51
|
+
execute: async ({ target }) => hydrateLfsTarget(state, target),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type SkillLoaderOptions = {
|
|
2
|
+
allowedRoots: string[];
|
|
3
|
+
};
|
|
4
|
+
export declare function resolveSkillPath(rawPath: string, allowedRoots: string[]): string;
|
|
5
|
+
export declare function loadSkillMarkdown(skillPath: string, allowedRoots: string[]): Promise<string>;
|
|
6
|
+
export declare function createSkillLoaderTool(options?: SkillLoaderOptions): import("ai").Tool<{
|
|
7
|
+
path: string;
|
|
8
|
+
}, {
|
|
9
|
+
path: string;
|
|
10
|
+
content: string;
|
|
11
|
+
}> | null;
|
|
12
|
+
//# sourceMappingURL=skillLoader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skillLoader.d.ts","sourceRoot":"","sources":["../../src/tools/skillLoader.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB,CAAC;AAQF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,MAAM,CAQhF;AAED,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAGlG;AAED,wBAAgB,qBAAqB,CAAC,OAAO,CAAC,EAAE,kBAAkB;;;;;UAiBjE"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { tool } from "ai";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
function isPathUnderRoot(resolved, root) {
|
|
6
|
+
const normalizedRoot = path.resolve(root);
|
|
7
|
+
const relative = path.relative(normalizedRoot, resolved);
|
|
8
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
9
|
+
}
|
|
10
|
+
export function resolveSkillPath(rawPath, allowedRoots) {
|
|
11
|
+
const resolved = path.resolve(rawPath);
|
|
12
|
+
for (const root of allowedRoots) {
|
|
13
|
+
if (isPathUnderRoot(resolved, root)) {
|
|
14
|
+
return resolved;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
throw new Error(`Skill path not under allowed roots: ${rawPath}`);
|
|
18
|
+
}
|
|
19
|
+
export async function loadSkillMarkdown(skillPath, allowedRoots) {
|
|
20
|
+
const resolved = resolveSkillPath(skillPath, allowedRoots);
|
|
21
|
+
return readFile(resolved, "utf8");
|
|
22
|
+
}
|
|
23
|
+
export function createSkillLoaderTool(options) {
|
|
24
|
+
if (!options?.allowedRoots?.length) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const allowedRoots = options.allowedRoots;
|
|
28
|
+
return tool({
|
|
29
|
+
description: "Load a SKILL.md file from disk (paths must be under configured skill roots)",
|
|
30
|
+
inputSchema: z.object({
|
|
31
|
+
path: z.string().min(1).describe("Path to SKILL.md under an allowed root"),
|
|
32
|
+
}),
|
|
33
|
+
execute: async ({ path: skillPath }) => {
|
|
34
|
+
const content = await loadSkillMarkdown(skillPath, allowedRoots);
|
|
35
|
+
return { path: skillPath, content };
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
}
|