@eagi/sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +18 -0
- package/LICENSE +28 -0
- package/dist/index.d.mts +272 -0
- package/dist/index.d.ts +272 -0
- package/dist/index.js +498 -0
- package/dist/index.mjs +452 -0
- package/package.json +32 -0
- package/src/define/config.ts +5 -0
- package/src/define/hooks.ts +11 -0
- package/src/define/index.ts +6 -0
- package/src/define/prompt.ts +8 -0
- package/src/define/resource.ts +7 -0
- package/src/define/service.ts +11 -0
- package/src/define/tool.ts +8 -0
- package/src/domain/index.ts +3 -0
- package/src/domain/loader.ts +103 -0
- package/src/domain/registry.ts +30 -0
- package/src/domain/types.ts +112 -0
- package/src/hooks/engine.ts +38 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/types.ts +45 -0
- package/src/index.ts +5 -0
- package/src/middleware/approval.ts +30 -0
- package/src/middleware/audit.ts +36 -0
- package/src/middleware/auth.ts +30 -0
- package/src/middleware/index.ts +3 -0
- package/src/server/builder.ts +185 -0
- package/src/server/index.ts +2 -0
- package/src/server/lifecycle.ts +73 -0
- package/tsconfig.json +8 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
// src/hooks/engine.ts
|
|
2
|
+
var HookEngine = class {
|
|
3
|
+
actions = /* @__PURE__ */ new Map();
|
|
4
|
+
filters = /* @__PURE__ */ new Map();
|
|
5
|
+
addAction(hookName, handler, priority = 10) {
|
|
6
|
+
if (!this.actions.has(hookName)) {
|
|
7
|
+
this.actions.set(hookName, []);
|
|
8
|
+
}
|
|
9
|
+
this.actions.get(hookName).push({ handler, priority });
|
|
10
|
+
this.actions.get(hookName).sort((a, b) => a.priority - b.priority);
|
|
11
|
+
}
|
|
12
|
+
addFilter(hookName, handler, priority = 10) {
|
|
13
|
+
if (!this.filters.has(hookName)) {
|
|
14
|
+
this.filters.set(hookName, []);
|
|
15
|
+
}
|
|
16
|
+
this.filters.get(hookName).push({ handler, priority });
|
|
17
|
+
this.filters.get(hookName).sort((a, b) => a.priority - b.priority);
|
|
18
|
+
}
|
|
19
|
+
async doAction(hookName, context) {
|
|
20
|
+
const handlers = this.actions.get(hookName) || [];
|
|
21
|
+
for (const { handler } of handlers) {
|
|
22
|
+
await handler(context);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async applyFilters(hookName, data, context) {
|
|
26
|
+
let result = data;
|
|
27
|
+
const handlers = this.filters.get(hookName) || [];
|
|
28
|
+
for (const { handler } of handlers) {
|
|
29
|
+
result = await handler(result, context);
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// src/domain/loader.ts
|
|
36
|
+
import { readFileSync, existsSync, readdirSync } from "fs";
|
|
37
|
+
import { join } from "path";
|
|
38
|
+
import yaml from "js-yaml";
|
|
39
|
+
var DomainLoader = class {
|
|
40
|
+
async load(domainDir) {
|
|
41
|
+
const manifestPath = join(domainDir, "domain.yaml");
|
|
42
|
+
if (!existsSync(manifestPath)) {
|
|
43
|
+
throw new Error(`Domain manifest not found at ${manifestPath}`);
|
|
44
|
+
}
|
|
45
|
+
const manifestContent = readFileSync(manifestPath, "utf8");
|
|
46
|
+
const manifest = yaml.load(manifestContent);
|
|
47
|
+
const services = await this.loadDirectory(domainDir, "services");
|
|
48
|
+
const tools = await this.loadDirectory(domainDir, "tools");
|
|
49
|
+
const resources = await this.loadDirectory(domainDir, "resources");
|
|
50
|
+
const prompts = await this.loadDirectory(domainDir, "prompts");
|
|
51
|
+
let hooksModule;
|
|
52
|
+
const hooksPath = join(domainDir, "hooks", "index.ts");
|
|
53
|
+
const hooksPathJs = join(domainDir, "hooks", "index.js");
|
|
54
|
+
if (existsSync(hooksPath) || existsSync(hooksPathJs)) {
|
|
55
|
+
hooksModule = await import(join(domainDir, "hooks", "index"));
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
manifest,
|
|
59
|
+
services,
|
|
60
|
+
tools,
|
|
61
|
+
resources,
|
|
62
|
+
prompts,
|
|
63
|
+
hooksModule,
|
|
64
|
+
dir: domainDir
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
async loadDirectory(domainDir, subDir) {
|
|
68
|
+
const dirPath = join(domainDir, subDir);
|
|
69
|
+
if (!existsSync(dirPath)) return [];
|
|
70
|
+
const files = readdirSync(dirPath).filter((f) => f.endsWith(".ts") || f.endsWith(".js"));
|
|
71
|
+
const loaded = [];
|
|
72
|
+
for (const file of files) {
|
|
73
|
+
const module = await import(join(dirPath, file));
|
|
74
|
+
if (module.default) {
|
|
75
|
+
loaded.push(module.default);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return loaded;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
function sortServices(services) {
|
|
82
|
+
const sorted = [];
|
|
83
|
+
const visited = /* @__PURE__ */ new Set();
|
|
84
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
85
|
+
const serviceMap = new Map(services.map((s) => [s.name, s]));
|
|
86
|
+
function visit(name) {
|
|
87
|
+
if (visited.has(name)) return;
|
|
88
|
+
if (visiting.has(name)) {
|
|
89
|
+
throw new Error(`Circular dependency detected involving service: ${name}`);
|
|
90
|
+
}
|
|
91
|
+
visiting.add(name);
|
|
92
|
+
const service = serviceMap.get(name);
|
|
93
|
+
if (service && service.deps) {
|
|
94
|
+
for (const dep of service.deps) {
|
|
95
|
+
if (!serviceMap.has(dep)) {
|
|
96
|
+
throw new Error(`Service ${name} depends on unknown service ${dep}`);
|
|
97
|
+
}
|
|
98
|
+
visit(dep);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
visiting.delete(name);
|
|
102
|
+
visited.add(name);
|
|
103
|
+
if (service) sorted.push(service);
|
|
104
|
+
}
|
|
105
|
+
for (const service of services) {
|
|
106
|
+
visit(service.name);
|
|
107
|
+
}
|
|
108
|
+
return sorted;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/domain/registry.ts
|
|
112
|
+
var DomainRegistry = class {
|
|
113
|
+
domains = /* @__PURE__ */ new Map();
|
|
114
|
+
register(domain) {
|
|
115
|
+
this.domains.set(domain.manifest.name, domain);
|
|
116
|
+
}
|
|
117
|
+
get(name) {
|
|
118
|
+
return this.domains.get(name);
|
|
119
|
+
}
|
|
120
|
+
getAll() {
|
|
121
|
+
return Array.from(this.domains.values());
|
|
122
|
+
}
|
|
123
|
+
getAllTools() {
|
|
124
|
+
return this.getAll().flatMap((d) => d.tools);
|
|
125
|
+
}
|
|
126
|
+
getAllResources() {
|
|
127
|
+
return this.getAll().flatMap((d) => d.resources);
|
|
128
|
+
}
|
|
129
|
+
getAllPrompts() {
|
|
130
|
+
return this.getAll().flatMap((d) => d.prompts);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// src/define/tool.ts
|
|
135
|
+
function defineTool(definition) {
|
|
136
|
+
return definition;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/define/resource.ts
|
|
140
|
+
function defineResource(definition) {
|
|
141
|
+
return definition;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// src/define/prompt.ts
|
|
145
|
+
function definePrompt(definition) {
|
|
146
|
+
return definition;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/define/service.ts
|
|
150
|
+
function defineService(definition) {
|
|
151
|
+
return definition;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/define/hooks.ts
|
|
155
|
+
function defineHooks(hooks) {
|
|
156
|
+
return hooks;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// src/define/config.ts
|
|
160
|
+
function defineConfig(config) {
|
|
161
|
+
return config;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/server/builder.ts
|
|
165
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
166
|
+
import {
|
|
167
|
+
ListToolsRequestSchema,
|
|
168
|
+
CallToolRequestSchema,
|
|
169
|
+
ListResourcesRequestSchema,
|
|
170
|
+
ReadResourceRequestSchema,
|
|
171
|
+
ListPromptsRequestSchema,
|
|
172
|
+
GetPromptRequestSchema
|
|
173
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
174
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
175
|
+
|
|
176
|
+
// src/middleware/auth.ts
|
|
177
|
+
var AuthError = class extends Error {
|
|
178
|
+
constructor(message) {
|
|
179
|
+
super(message);
|
|
180
|
+
this.name = "AuthError";
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
async function authMiddleware(tool, ctx) {
|
|
184
|
+
if (!tool.auth || !tool.auth.roles) return;
|
|
185
|
+
const userRole = ctx.identity.role;
|
|
186
|
+
const domainRoles = ctx.domain.roles || {};
|
|
187
|
+
const allowedRoles = new Set(tool.auth.roles);
|
|
188
|
+
if (allowedRoles.has(userRole)) return;
|
|
189
|
+
const userRoleDef = domainRoles[userRole];
|
|
190
|
+
if (userRoleDef && userRoleDef.includes) {
|
|
191
|
+
for (const included of userRoleDef.includes) {
|
|
192
|
+
if (allowedRoles.has(included)) return;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
throw new AuthError(`User role '${userRole}' is not authorized to execute tool '${tool.name}'`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/middleware/audit.ts
|
|
199
|
+
async function auditMiddleware(tool, input, output, ctx) {
|
|
200
|
+
if (!tool.audit) return;
|
|
201
|
+
let auditedInput = { ...input };
|
|
202
|
+
if (typeof tool.audit === "object" && tool.audit.redactFields) {
|
|
203
|
+
for (const field of tool.audit.redactFields) {
|
|
204
|
+
if (field in auditedInput) {
|
|
205
|
+
auditedInput[field] = "[REDACTED]";
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const entry = {
|
|
210
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
211
|
+
tool: tool.name,
|
|
212
|
+
identity: ctx.identity.userId,
|
|
213
|
+
role: ctx.identity.role,
|
|
214
|
+
input: auditedInput,
|
|
215
|
+
success: !output.isError
|
|
216
|
+
};
|
|
217
|
+
const finalEntry = await ctx.hooks.applyFilters("filter:audit:entry", entry, ctx);
|
|
218
|
+
if (ctx.logger) {
|
|
219
|
+
ctx.logger.info("AUDIT", finalEntry);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// src/middleware/approval.ts
|
|
224
|
+
var ApprovalRequiredError = class extends Error {
|
|
225
|
+
constructor(message) {
|
|
226
|
+
super(message);
|
|
227
|
+
this.name = "ApprovalRequiredError";
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
async function approvalMiddleware(tool, input, ctx) {
|
|
231
|
+
if (!tool.approval || !tool.approval.required) return;
|
|
232
|
+
const hasApprovalToken = false;
|
|
233
|
+
if (!hasApprovalToken) {
|
|
234
|
+
const message = typeof tool.approval.message === "function" ? tool.approval.message(input) : tool.approval.message;
|
|
235
|
+
throw new ApprovalRequiredError(message);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/server/builder.ts
|
|
240
|
+
var EagiServerBuilder = class {
|
|
241
|
+
constructor(config, registry, hooks, servicesMap) {
|
|
242
|
+
this.config = config;
|
|
243
|
+
this.registry = registry;
|
|
244
|
+
this.hooks = hooks;
|
|
245
|
+
this.servicesMap = servicesMap;
|
|
246
|
+
}
|
|
247
|
+
config;
|
|
248
|
+
registry;
|
|
249
|
+
hooks;
|
|
250
|
+
servicesMap;
|
|
251
|
+
build() {
|
|
252
|
+
const server = new Server(
|
|
253
|
+
{ name: this.config.name, version: this.config.version },
|
|
254
|
+
{ capabilities: { tools: {}, resources: {}, prompts: {} } }
|
|
255
|
+
);
|
|
256
|
+
this.registerTools(server);
|
|
257
|
+
this.registerResources(server);
|
|
258
|
+
this.registerPrompts(server);
|
|
259
|
+
return server;
|
|
260
|
+
}
|
|
261
|
+
registerTools(server) {
|
|
262
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
263
|
+
const tools = this.registry.getAllTools().map((t) => ({
|
|
264
|
+
name: t.name,
|
|
265
|
+
description: t.description,
|
|
266
|
+
inputSchema: zodToJsonSchema(t.input)
|
|
267
|
+
}));
|
|
268
|
+
return { tools };
|
|
269
|
+
});
|
|
270
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
271
|
+
const { name, arguments: args } = request.params;
|
|
272
|
+
const tool = this.registry.getAllTools().find((t) => t.name === name);
|
|
273
|
+
if (!tool) {
|
|
274
|
+
throw new Error(`Tool not found: ${name}`);
|
|
275
|
+
}
|
|
276
|
+
const identity = { userId: "system", role: "admin", claims: {} };
|
|
277
|
+
const domain = this.registry.getAll().find((d) => d.tools.includes(tool)).manifest;
|
|
278
|
+
const ctx = {
|
|
279
|
+
identity,
|
|
280
|
+
domain,
|
|
281
|
+
hooks: this.hooks,
|
|
282
|
+
logger: console,
|
|
283
|
+
// Temporary logger
|
|
284
|
+
services: this.servicesMap,
|
|
285
|
+
tools: {
|
|
286
|
+
call: async (toolName, input2) => {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
resources: {
|
|
291
|
+
get: async (uri) => {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
let input = await this.hooks.applyFilters("filter:tool:input", args, ctx);
|
|
297
|
+
const parsedInput = tool.input.parse(input);
|
|
298
|
+
await this.hooks.doAction("before:tool:call", { toolName: name, input: parsedInput, identity, domain });
|
|
299
|
+
if (tool.auth) await authMiddleware(tool, ctx);
|
|
300
|
+
if (tool.approval) await approvalMiddleware(tool, parsedInput, ctx);
|
|
301
|
+
const startTime = Date.now();
|
|
302
|
+
let output = await tool.handler(parsedInput, ctx);
|
|
303
|
+
const duration = Date.now() - startTime;
|
|
304
|
+
output = await this.hooks.applyFilters("filter:tool:output", output, ctx);
|
|
305
|
+
await this.hooks.doAction("after:tool:call", { toolName: name, input: parsedInput, output, identity, duration });
|
|
306
|
+
if (tool.audit) await auditMiddleware(tool, parsedInput, output, ctx);
|
|
307
|
+
return output;
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
registerResources(server) {
|
|
311
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
312
|
+
const resources = this.registry.getAllResources().map((r) => ({
|
|
313
|
+
uri: r.uri,
|
|
314
|
+
name: r.name,
|
|
315
|
+
description: r.description,
|
|
316
|
+
mimeType: r.mimeType
|
|
317
|
+
}));
|
|
318
|
+
return { resources };
|
|
319
|
+
});
|
|
320
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
321
|
+
const { uri } = request.params;
|
|
322
|
+
const resource = this.registry.getAllResources().find((r) => r.uri === uri);
|
|
323
|
+
if (!resource) {
|
|
324
|
+
throw new Error(`Resource not found: ${uri}`);
|
|
325
|
+
}
|
|
326
|
+
const identity = { userId: "system", role: "admin", claims: {} };
|
|
327
|
+
const domain = this.registry.getAll().find((d) => d.resources.includes(resource)).manifest;
|
|
328
|
+
const ctx = {
|
|
329
|
+
identity,
|
|
330
|
+
domain,
|
|
331
|
+
hooks: this.hooks,
|
|
332
|
+
logger: console,
|
|
333
|
+
services: this.servicesMap
|
|
334
|
+
};
|
|
335
|
+
await this.hooks.doAction("before:resource:read", { uri, identity });
|
|
336
|
+
let data = await resource.handler({}, ctx);
|
|
337
|
+
data = await this.hooks.applyFilters("filter:resource:data", data, ctx);
|
|
338
|
+
await this.hooks.doAction("after:resource:read", { uri, data, identity });
|
|
339
|
+
return {
|
|
340
|
+
contents: [{ uri, mimeType: resource.mimeType, text: data }]
|
|
341
|
+
};
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
registerPrompts(server) {
|
|
345
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
346
|
+
const prompts = this.registry.getAllPrompts().map((p) => ({
|
|
347
|
+
name: p.name,
|
|
348
|
+
description: p.description,
|
|
349
|
+
arguments: []
|
|
350
|
+
// Should extract from Zod schema
|
|
351
|
+
}));
|
|
352
|
+
return { prompts };
|
|
353
|
+
});
|
|
354
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
355
|
+
const { name, arguments: args } = request.params;
|
|
356
|
+
const prompt = this.registry.getAllPrompts().find((p) => p.name === name);
|
|
357
|
+
if (!prompt) {
|
|
358
|
+
throw new Error(`Prompt not found: ${name}`);
|
|
359
|
+
}
|
|
360
|
+
const identity = { userId: "system", role: "admin", claims: {} };
|
|
361
|
+
const domain = this.registry.getAll().find((d) => d.prompts.includes(prompt)).manifest;
|
|
362
|
+
const ctx = {
|
|
363
|
+
identity,
|
|
364
|
+
domain,
|
|
365
|
+
hooks: this.hooks,
|
|
366
|
+
logger: console,
|
|
367
|
+
services: this.servicesMap
|
|
368
|
+
};
|
|
369
|
+
const parsedArgs = prompt.arguments.parse(args);
|
|
370
|
+
const result = await prompt.handler(parsedArgs, ctx);
|
|
371
|
+
return {
|
|
372
|
+
description: prompt.description,
|
|
373
|
+
messages: result.messages
|
|
374
|
+
};
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
// src/server/lifecycle.ts
|
|
380
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
381
|
+
var EagiRunner = class {
|
|
382
|
+
constructor(config) {
|
|
383
|
+
this.config = config;
|
|
384
|
+
}
|
|
385
|
+
config;
|
|
386
|
+
registry = new DomainRegistry();
|
|
387
|
+
hooks = new HookEngine();
|
|
388
|
+
servicesMap = {};
|
|
389
|
+
async start(domainDirs) {
|
|
390
|
+
if (this.config.hooks) {
|
|
391
|
+
for (const [hookName, handler] of Object.entries(this.config.hooks)) {
|
|
392
|
+
if (hookName.startsWith("filter:")) {
|
|
393
|
+
this.hooks.addFilter(hookName, handler);
|
|
394
|
+
} else {
|
|
395
|
+
this.hooks.addAction(hookName, handler);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
const loader = new DomainLoader();
|
|
400
|
+
for (const dir of domainDirs) {
|
|
401
|
+
const domain = await loader.load(dir);
|
|
402
|
+
this.registry.register(domain);
|
|
403
|
+
if (domain.hooksModule && domain.hooksModule.default) {
|
|
404
|
+
const domainHooks = domain.hooksModule.default;
|
|
405
|
+
for (const [hookName, handler] of Object.entries(domainHooks)) {
|
|
406
|
+
if (hookName.startsWith("filter:")) {
|
|
407
|
+
this.hooks.addFilter(hookName, handler);
|
|
408
|
+
} else {
|
|
409
|
+
this.hooks.addAction(hookName, handler);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
await this.hooks.doAction("on:domain:load", { domain: domain.manifest });
|
|
414
|
+
}
|
|
415
|
+
const allServices = this.registry.getAll().flatMap((d) => d.services);
|
|
416
|
+
const sortedServices = sortServices(allServices);
|
|
417
|
+
for (const service of sortedServices) {
|
|
418
|
+
const deps = {};
|
|
419
|
+
if (service.deps) {
|
|
420
|
+
for (const dep of service.deps) {
|
|
421
|
+
deps[dep] = this.servicesMap[dep];
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
const instance = await service.factory({ env: process.env }, deps);
|
|
425
|
+
this.servicesMap[service.name] = instance;
|
|
426
|
+
}
|
|
427
|
+
const builder = new EagiServerBuilder(this.config, this.registry, this.hooks, this.servicesMap);
|
|
428
|
+
const server = builder.build();
|
|
429
|
+
const transport = new StdioServerTransport();
|
|
430
|
+
await server.connect(transport);
|
|
431
|
+
await this.hooks.doAction("on:server:start", { config: this.config, domains: this.registry.getAll().map((d) => d.manifest) });
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
export {
|
|
435
|
+
ApprovalRequiredError,
|
|
436
|
+
AuthError,
|
|
437
|
+
DomainLoader,
|
|
438
|
+
DomainRegistry,
|
|
439
|
+
EagiRunner,
|
|
440
|
+
EagiServerBuilder,
|
|
441
|
+
HookEngine,
|
|
442
|
+
approvalMiddleware,
|
|
443
|
+
auditMiddleware,
|
|
444
|
+
authMiddleware,
|
|
445
|
+
defineConfig,
|
|
446
|
+
defineHooks,
|
|
447
|
+
definePrompt,
|
|
448
|
+
defineResource,
|
|
449
|
+
defineService,
|
|
450
|
+
defineTool,
|
|
451
|
+
sortServices
|
|
452
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@eagi/sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Enterprise AGI - Core Framework SDK",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@modelcontextprotocol/sdk": "^1.0.1",
|
|
17
|
+
"js-yaml": "^4.1.1",
|
|
18
|
+
"zod": "^3.22.4",
|
|
19
|
+
"zod-to-json-schema": "^3.22.4"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/js-yaml": "^4.0.9",
|
|
23
|
+
"tsup": "^8.0.2",
|
|
24
|
+
"typescript": "^5.4.0",
|
|
25
|
+
"vitest": "^1.5.0"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
29
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
30
|
+
"test": "vitest run --passWithNoTests"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { HookContextMap, FilterDataMap, ActionHandler, FilterHandler } from '../hooks/types';
|
|
2
|
+
|
|
3
|
+
type HookRegistration = {
|
|
4
|
+
[K in keyof HookContextMap]?: ActionHandler<K>;
|
|
5
|
+
} & {
|
|
6
|
+
[K in keyof FilterDataMap]?: FilterHandler<K>;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function defineHooks(hooks: HookRegistration): HookRegistration {
|
|
10
|
+
return hooks;
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ServiceDefinition } from '../domain/types';
|
|
2
|
+
|
|
3
|
+
export function defineService<
|
|
4
|
+
TName extends string,
|
|
5
|
+
TDeps extends readonly string[],
|
|
6
|
+
TInstance
|
|
7
|
+
>(
|
|
8
|
+
definition: ServiceDefinition<TName, TDeps, TInstance>
|
|
9
|
+
): ServiceDefinition<TName, TDeps, TInstance> {
|
|
10
|
+
return definition;
|
|
11
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { readFileSync, existsSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import yaml from 'js-yaml';
|
|
4
|
+
import type { DomainManifest, ServiceDefinition, ToolDefinition, ResourceDefinition, PromptDefinition } from './types';
|
|
5
|
+
|
|
6
|
+
export interface LoadedDomain {
|
|
7
|
+
manifest: DomainManifest;
|
|
8
|
+
services: ServiceDefinition[];
|
|
9
|
+
tools: ToolDefinition[];
|
|
10
|
+
resources: ResourceDefinition[];
|
|
11
|
+
prompts: PromptDefinition[];
|
|
12
|
+
hooksModule?: any;
|
|
13
|
+
dir: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class DomainLoader {
|
|
17
|
+
async load(domainDir: string): Promise<LoadedDomain> {
|
|
18
|
+
const manifestPath = join(domainDir, 'domain.yaml');
|
|
19
|
+
if (!existsSync(manifestPath)) {
|
|
20
|
+
throw new Error(`Domain manifest not found at ${manifestPath}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const manifestContent = readFileSync(manifestPath, 'utf8');
|
|
24
|
+
const manifest = yaml.load(manifestContent) as DomainManifest;
|
|
25
|
+
|
|
26
|
+
const services = await this.loadDirectory<ServiceDefinition>(domainDir, 'services');
|
|
27
|
+
const tools = await this.loadDirectory<ToolDefinition>(domainDir, 'tools');
|
|
28
|
+
const resources = await this.loadDirectory<ResourceDefinition>(domainDir, 'resources');
|
|
29
|
+
const prompts = await this.loadDirectory<PromptDefinition>(domainDir, 'prompts');
|
|
30
|
+
|
|
31
|
+
let hooksModule;
|
|
32
|
+
const hooksPath = join(domainDir, 'hooks', 'index.ts');
|
|
33
|
+
const hooksPathJs = join(domainDir, 'hooks', 'index.js');
|
|
34
|
+
if (existsSync(hooksPath) || existsSync(hooksPathJs)) {
|
|
35
|
+
hooksModule = await import(join(domainDir, 'hooks', 'index'));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
manifest,
|
|
40
|
+
services,
|
|
41
|
+
tools,
|
|
42
|
+
resources,
|
|
43
|
+
prompts,
|
|
44
|
+
hooksModule,
|
|
45
|
+
dir: domainDir
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private async loadDirectory<T>(domainDir: string, subDir: string): Promise<T[]> {
|
|
50
|
+
const dirPath = join(domainDir, subDir);
|
|
51
|
+
if (!existsSync(dirPath)) return [];
|
|
52
|
+
|
|
53
|
+
const files = readdirSync(dirPath).filter(f => f.endsWith('.ts') || f.endsWith('.js'));
|
|
54
|
+
const loaded: T[] = [];
|
|
55
|
+
|
|
56
|
+
for (const file of files) {
|
|
57
|
+
// Dynamic import
|
|
58
|
+
const module = await import(join(dirPath, file));
|
|
59
|
+
if (module.default) {
|
|
60
|
+
loaded.push(module.default);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return loaded;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function sortServices(services: ServiceDefinition[]): ServiceDefinition[] {
|
|
69
|
+
const sorted: ServiceDefinition[] = [];
|
|
70
|
+
const visited = new Set<string>();
|
|
71
|
+
const visiting = new Set<string>();
|
|
72
|
+
|
|
73
|
+
const serviceMap = new Map(services.map(s => [s.name, s]));
|
|
74
|
+
|
|
75
|
+
function visit(name: string) {
|
|
76
|
+
if (visited.has(name)) return;
|
|
77
|
+
if (visiting.has(name)) {
|
|
78
|
+
throw new Error(`Circular dependency detected involving service: ${name}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
visiting.add(name);
|
|
82
|
+
|
|
83
|
+
const service = serviceMap.get(name);
|
|
84
|
+
if (service && service.deps) {
|
|
85
|
+
for (const dep of service.deps) {
|
|
86
|
+
if (!serviceMap.has(dep)) {
|
|
87
|
+
throw new Error(`Service ${name} depends on unknown service ${dep}`);
|
|
88
|
+
}
|
|
89
|
+
visit(dep);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
visiting.delete(name);
|
|
94
|
+
visited.add(name);
|
|
95
|
+
if (service) sorted.push(service);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for (const service of services) {
|
|
99
|
+
visit(service.name);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return sorted;
|
|
103
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { LoadedDomain } from './loader';
|
|
2
|
+
import type { ToolDefinition, ResourceDefinition, PromptDefinition, ServiceDefinition } from './types';
|
|
3
|
+
|
|
4
|
+
export class DomainRegistry {
|
|
5
|
+
private domains: Map<string, LoadedDomain> = new Map();
|
|
6
|
+
|
|
7
|
+
register(domain: LoadedDomain) {
|
|
8
|
+
this.domains.set(domain.manifest.name, domain);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
get(name: string): LoadedDomain | undefined {
|
|
12
|
+
return this.domains.get(name);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
getAll(): LoadedDomain[] {
|
|
16
|
+
return Array.from(this.domains.values());
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getAllTools(): ToolDefinition[] {
|
|
20
|
+
return this.getAll().flatMap(d => d.tools);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
getAllResources(): ResourceDefinition[] {
|
|
24
|
+
return this.getAll().flatMap(d => d.resources);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
getAllPrompts(): PromptDefinition[] {
|
|
28
|
+
return this.getAll().flatMap(d => d.prompts);
|
|
29
|
+
}
|
|
30
|
+
}
|