@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/dist/index.js ADDED
@@ -0,0 +1,498 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ApprovalRequiredError: () => ApprovalRequiredError,
34
+ AuthError: () => AuthError,
35
+ DomainLoader: () => DomainLoader,
36
+ DomainRegistry: () => DomainRegistry,
37
+ EagiRunner: () => EagiRunner,
38
+ EagiServerBuilder: () => EagiServerBuilder,
39
+ HookEngine: () => HookEngine,
40
+ approvalMiddleware: () => approvalMiddleware,
41
+ auditMiddleware: () => auditMiddleware,
42
+ authMiddleware: () => authMiddleware,
43
+ defineConfig: () => defineConfig,
44
+ defineHooks: () => defineHooks,
45
+ definePrompt: () => definePrompt,
46
+ defineResource: () => defineResource,
47
+ defineService: () => defineService,
48
+ defineTool: () => defineTool,
49
+ sortServices: () => sortServices
50
+ });
51
+ module.exports = __toCommonJS(index_exports);
52
+
53
+ // src/hooks/engine.ts
54
+ var HookEngine = class {
55
+ actions = /* @__PURE__ */ new Map();
56
+ filters = /* @__PURE__ */ new Map();
57
+ addAction(hookName, handler, priority = 10) {
58
+ if (!this.actions.has(hookName)) {
59
+ this.actions.set(hookName, []);
60
+ }
61
+ this.actions.get(hookName).push({ handler, priority });
62
+ this.actions.get(hookName).sort((a, b) => a.priority - b.priority);
63
+ }
64
+ addFilter(hookName, handler, priority = 10) {
65
+ if (!this.filters.has(hookName)) {
66
+ this.filters.set(hookName, []);
67
+ }
68
+ this.filters.get(hookName).push({ handler, priority });
69
+ this.filters.get(hookName).sort((a, b) => a.priority - b.priority);
70
+ }
71
+ async doAction(hookName, context) {
72
+ const handlers = this.actions.get(hookName) || [];
73
+ for (const { handler } of handlers) {
74
+ await handler(context);
75
+ }
76
+ }
77
+ async applyFilters(hookName, data, context) {
78
+ let result = data;
79
+ const handlers = this.filters.get(hookName) || [];
80
+ for (const { handler } of handlers) {
81
+ result = await handler(result, context);
82
+ }
83
+ return result;
84
+ }
85
+ };
86
+
87
+ // src/domain/loader.ts
88
+ var import_node_fs = require("fs");
89
+ var import_node_path = require("path");
90
+ var import_js_yaml = __toESM(require("js-yaml"));
91
+ var DomainLoader = class {
92
+ async load(domainDir) {
93
+ const manifestPath = (0, import_node_path.join)(domainDir, "domain.yaml");
94
+ if (!(0, import_node_fs.existsSync)(manifestPath)) {
95
+ throw new Error(`Domain manifest not found at ${manifestPath}`);
96
+ }
97
+ const manifestContent = (0, import_node_fs.readFileSync)(manifestPath, "utf8");
98
+ const manifest = import_js_yaml.default.load(manifestContent);
99
+ const services = await this.loadDirectory(domainDir, "services");
100
+ const tools = await this.loadDirectory(domainDir, "tools");
101
+ const resources = await this.loadDirectory(domainDir, "resources");
102
+ const prompts = await this.loadDirectory(domainDir, "prompts");
103
+ let hooksModule;
104
+ const hooksPath = (0, import_node_path.join)(domainDir, "hooks", "index.ts");
105
+ const hooksPathJs = (0, import_node_path.join)(domainDir, "hooks", "index.js");
106
+ if ((0, import_node_fs.existsSync)(hooksPath) || (0, import_node_fs.existsSync)(hooksPathJs)) {
107
+ hooksModule = await import((0, import_node_path.join)(domainDir, "hooks", "index"));
108
+ }
109
+ return {
110
+ manifest,
111
+ services,
112
+ tools,
113
+ resources,
114
+ prompts,
115
+ hooksModule,
116
+ dir: domainDir
117
+ };
118
+ }
119
+ async loadDirectory(domainDir, subDir) {
120
+ const dirPath = (0, import_node_path.join)(domainDir, subDir);
121
+ if (!(0, import_node_fs.existsSync)(dirPath)) return [];
122
+ const files = (0, import_node_fs.readdirSync)(dirPath).filter((f) => f.endsWith(".ts") || f.endsWith(".js"));
123
+ const loaded = [];
124
+ for (const file of files) {
125
+ const module2 = await import((0, import_node_path.join)(dirPath, file));
126
+ if (module2.default) {
127
+ loaded.push(module2.default);
128
+ }
129
+ }
130
+ return loaded;
131
+ }
132
+ };
133
+ function sortServices(services) {
134
+ const sorted = [];
135
+ const visited = /* @__PURE__ */ new Set();
136
+ const visiting = /* @__PURE__ */ new Set();
137
+ const serviceMap = new Map(services.map((s) => [s.name, s]));
138
+ function visit(name) {
139
+ if (visited.has(name)) return;
140
+ if (visiting.has(name)) {
141
+ throw new Error(`Circular dependency detected involving service: ${name}`);
142
+ }
143
+ visiting.add(name);
144
+ const service = serviceMap.get(name);
145
+ if (service && service.deps) {
146
+ for (const dep of service.deps) {
147
+ if (!serviceMap.has(dep)) {
148
+ throw new Error(`Service ${name} depends on unknown service ${dep}`);
149
+ }
150
+ visit(dep);
151
+ }
152
+ }
153
+ visiting.delete(name);
154
+ visited.add(name);
155
+ if (service) sorted.push(service);
156
+ }
157
+ for (const service of services) {
158
+ visit(service.name);
159
+ }
160
+ return sorted;
161
+ }
162
+
163
+ // src/domain/registry.ts
164
+ var DomainRegistry = class {
165
+ domains = /* @__PURE__ */ new Map();
166
+ register(domain) {
167
+ this.domains.set(domain.manifest.name, domain);
168
+ }
169
+ get(name) {
170
+ return this.domains.get(name);
171
+ }
172
+ getAll() {
173
+ return Array.from(this.domains.values());
174
+ }
175
+ getAllTools() {
176
+ return this.getAll().flatMap((d) => d.tools);
177
+ }
178
+ getAllResources() {
179
+ return this.getAll().flatMap((d) => d.resources);
180
+ }
181
+ getAllPrompts() {
182
+ return this.getAll().flatMap((d) => d.prompts);
183
+ }
184
+ };
185
+
186
+ // src/define/tool.ts
187
+ function defineTool(definition) {
188
+ return definition;
189
+ }
190
+
191
+ // src/define/resource.ts
192
+ function defineResource(definition) {
193
+ return definition;
194
+ }
195
+
196
+ // src/define/prompt.ts
197
+ function definePrompt(definition) {
198
+ return definition;
199
+ }
200
+
201
+ // src/define/service.ts
202
+ function defineService(definition) {
203
+ return definition;
204
+ }
205
+
206
+ // src/define/hooks.ts
207
+ function defineHooks(hooks) {
208
+ return hooks;
209
+ }
210
+
211
+ // src/define/config.ts
212
+ function defineConfig(config) {
213
+ return config;
214
+ }
215
+
216
+ // src/server/builder.ts
217
+ var import_server = require("@modelcontextprotocol/sdk/server/index.js");
218
+ var import_types = require("@modelcontextprotocol/sdk/types.js");
219
+ var import_zod_to_json_schema = require("zod-to-json-schema");
220
+
221
+ // src/middleware/auth.ts
222
+ var AuthError = class extends Error {
223
+ constructor(message) {
224
+ super(message);
225
+ this.name = "AuthError";
226
+ }
227
+ };
228
+ async function authMiddleware(tool, ctx) {
229
+ if (!tool.auth || !tool.auth.roles) return;
230
+ const userRole = ctx.identity.role;
231
+ const domainRoles = ctx.domain.roles || {};
232
+ const allowedRoles = new Set(tool.auth.roles);
233
+ if (allowedRoles.has(userRole)) return;
234
+ const userRoleDef = domainRoles[userRole];
235
+ if (userRoleDef && userRoleDef.includes) {
236
+ for (const included of userRoleDef.includes) {
237
+ if (allowedRoles.has(included)) return;
238
+ }
239
+ }
240
+ throw new AuthError(`User role '${userRole}' is not authorized to execute tool '${tool.name}'`);
241
+ }
242
+
243
+ // src/middleware/audit.ts
244
+ async function auditMiddleware(tool, input, output, ctx) {
245
+ if (!tool.audit) return;
246
+ let auditedInput = { ...input };
247
+ if (typeof tool.audit === "object" && tool.audit.redactFields) {
248
+ for (const field of tool.audit.redactFields) {
249
+ if (field in auditedInput) {
250
+ auditedInput[field] = "[REDACTED]";
251
+ }
252
+ }
253
+ }
254
+ const entry = {
255
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
256
+ tool: tool.name,
257
+ identity: ctx.identity.userId,
258
+ role: ctx.identity.role,
259
+ input: auditedInput,
260
+ success: !output.isError
261
+ };
262
+ const finalEntry = await ctx.hooks.applyFilters("filter:audit:entry", entry, ctx);
263
+ if (ctx.logger) {
264
+ ctx.logger.info("AUDIT", finalEntry);
265
+ }
266
+ }
267
+
268
+ // src/middleware/approval.ts
269
+ var ApprovalRequiredError = class extends Error {
270
+ constructor(message) {
271
+ super(message);
272
+ this.name = "ApprovalRequiredError";
273
+ }
274
+ };
275
+ async function approvalMiddleware(tool, input, ctx) {
276
+ if (!tool.approval || !tool.approval.required) return;
277
+ const hasApprovalToken = false;
278
+ if (!hasApprovalToken) {
279
+ const message = typeof tool.approval.message === "function" ? tool.approval.message(input) : tool.approval.message;
280
+ throw new ApprovalRequiredError(message);
281
+ }
282
+ }
283
+
284
+ // src/server/builder.ts
285
+ var EagiServerBuilder = class {
286
+ constructor(config, registry, hooks, servicesMap) {
287
+ this.config = config;
288
+ this.registry = registry;
289
+ this.hooks = hooks;
290
+ this.servicesMap = servicesMap;
291
+ }
292
+ config;
293
+ registry;
294
+ hooks;
295
+ servicesMap;
296
+ build() {
297
+ const server = new import_server.Server(
298
+ { name: this.config.name, version: this.config.version },
299
+ { capabilities: { tools: {}, resources: {}, prompts: {} } }
300
+ );
301
+ this.registerTools(server);
302
+ this.registerResources(server);
303
+ this.registerPrompts(server);
304
+ return server;
305
+ }
306
+ registerTools(server) {
307
+ server.setRequestHandler(import_types.ListToolsRequestSchema, async () => {
308
+ const tools = this.registry.getAllTools().map((t) => ({
309
+ name: t.name,
310
+ description: t.description,
311
+ inputSchema: (0, import_zod_to_json_schema.zodToJsonSchema)(t.input)
312
+ }));
313
+ return { tools };
314
+ });
315
+ server.setRequestHandler(import_types.CallToolRequestSchema, async (request) => {
316
+ const { name, arguments: args } = request.params;
317
+ const tool = this.registry.getAllTools().find((t) => t.name === name);
318
+ if (!tool) {
319
+ throw new Error(`Tool not found: ${name}`);
320
+ }
321
+ const identity = { userId: "system", role: "admin", claims: {} };
322
+ const domain = this.registry.getAll().find((d) => d.tools.includes(tool)).manifest;
323
+ const ctx = {
324
+ identity,
325
+ domain,
326
+ hooks: this.hooks,
327
+ logger: console,
328
+ // Temporary logger
329
+ services: this.servicesMap,
330
+ tools: {
331
+ call: async (toolName, input2) => {
332
+ return null;
333
+ }
334
+ },
335
+ resources: {
336
+ get: async (uri) => {
337
+ return null;
338
+ }
339
+ }
340
+ };
341
+ let input = await this.hooks.applyFilters("filter:tool:input", args, ctx);
342
+ const parsedInput = tool.input.parse(input);
343
+ await this.hooks.doAction("before:tool:call", { toolName: name, input: parsedInput, identity, domain });
344
+ if (tool.auth) await authMiddleware(tool, ctx);
345
+ if (tool.approval) await approvalMiddleware(tool, parsedInput, ctx);
346
+ const startTime = Date.now();
347
+ let output = await tool.handler(parsedInput, ctx);
348
+ const duration = Date.now() - startTime;
349
+ output = await this.hooks.applyFilters("filter:tool:output", output, ctx);
350
+ await this.hooks.doAction("after:tool:call", { toolName: name, input: parsedInput, output, identity, duration });
351
+ if (tool.audit) await auditMiddleware(tool, parsedInput, output, ctx);
352
+ return output;
353
+ });
354
+ }
355
+ registerResources(server) {
356
+ server.setRequestHandler(import_types.ListResourcesRequestSchema, async () => {
357
+ const resources = this.registry.getAllResources().map((r) => ({
358
+ uri: r.uri,
359
+ name: r.name,
360
+ description: r.description,
361
+ mimeType: r.mimeType
362
+ }));
363
+ return { resources };
364
+ });
365
+ server.setRequestHandler(import_types.ReadResourceRequestSchema, async (request) => {
366
+ const { uri } = request.params;
367
+ const resource = this.registry.getAllResources().find((r) => r.uri === uri);
368
+ if (!resource) {
369
+ throw new Error(`Resource not found: ${uri}`);
370
+ }
371
+ const identity = { userId: "system", role: "admin", claims: {} };
372
+ const domain = this.registry.getAll().find((d) => d.resources.includes(resource)).manifest;
373
+ const ctx = {
374
+ identity,
375
+ domain,
376
+ hooks: this.hooks,
377
+ logger: console,
378
+ services: this.servicesMap
379
+ };
380
+ await this.hooks.doAction("before:resource:read", { uri, identity });
381
+ let data = await resource.handler({}, ctx);
382
+ data = await this.hooks.applyFilters("filter:resource:data", data, ctx);
383
+ await this.hooks.doAction("after:resource:read", { uri, data, identity });
384
+ return {
385
+ contents: [{ uri, mimeType: resource.mimeType, text: data }]
386
+ };
387
+ });
388
+ }
389
+ registerPrompts(server) {
390
+ server.setRequestHandler(import_types.ListPromptsRequestSchema, async () => {
391
+ const prompts = this.registry.getAllPrompts().map((p) => ({
392
+ name: p.name,
393
+ description: p.description,
394
+ arguments: []
395
+ // Should extract from Zod schema
396
+ }));
397
+ return { prompts };
398
+ });
399
+ server.setRequestHandler(import_types.GetPromptRequestSchema, async (request) => {
400
+ const { name, arguments: args } = request.params;
401
+ const prompt = this.registry.getAllPrompts().find((p) => p.name === name);
402
+ if (!prompt) {
403
+ throw new Error(`Prompt not found: ${name}`);
404
+ }
405
+ const identity = { userId: "system", role: "admin", claims: {} };
406
+ const domain = this.registry.getAll().find((d) => d.prompts.includes(prompt)).manifest;
407
+ const ctx = {
408
+ identity,
409
+ domain,
410
+ hooks: this.hooks,
411
+ logger: console,
412
+ services: this.servicesMap
413
+ };
414
+ const parsedArgs = prompt.arguments.parse(args);
415
+ const result = await prompt.handler(parsedArgs, ctx);
416
+ return {
417
+ description: prompt.description,
418
+ messages: result.messages
419
+ };
420
+ });
421
+ }
422
+ };
423
+
424
+ // src/server/lifecycle.ts
425
+ var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
426
+ var EagiRunner = class {
427
+ constructor(config) {
428
+ this.config = config;
429
+ }
430
+ config;
431
+ registry = new DomainRegistry();
432
+ hooks = new HookEngine();
433
+ servicesMap = {};
434
+ async start(domainDirs) {
435
+ if (this.config.hooks) {
436
+ for (const [hookName, handler] of Object.entries(this.config.hooks)) {
437
+ if (hookName.startsWith("filter:")) {
438
+ this.hooks.addFilter(hookName, handler);
439
+ } else {
440
+ this.hooks.addAction(hookName, handler);
441
+ }
442
+ }
443
+ }
444
+ const loader = new DomainLoader();
445
+ for (const dir of domainDirs) {
446
+ const domain = await loader.load(dir);
447
+ this.registry.register(domain);
448
+ if (domain.hooksModule && domain.hooksModule.default) {
449
+ const domainHooks = domain.hooksModule.default;
450
+ for (const [hookName, handler] of Object.entries(domainHooks)) {
451
+ if (hookName.startsWith("filter:")) {
452
+ this.hooks.addFilter(hookName, handler);
453
+ } else {
454
+ this.hooks.addAction(hookName, handler);
455
+ }
456
+ }
457
+ }
458
+ await this.hooks.doAction("on:domain:load", { domain: domain.manifest });
459
+ }
460
+ const allServices = this.registry.getAll().flatMap((d) => d.services);
461
+ const sortedServices = sortServices(allServices);
462
+ for (const service of sortedServices) {
463
+ const deps = {};
464
+ if (service.deps) {
465
+ for (const dep of service.deps) {
466
+ deps[dep] = this.servicesMap[dep];
467
+ }
468
+ }
469
+ const instance = await service.factory({ env: process.env }, deps);
470
+ this.servicesMap[service.name] = instance;
471
+ }
472
+ const builder = new EagiServerBuilder(this.config, this.registry, this.hooks, this.servicesMap);
473
+ const server = builder.build();
474
+ const transport = new import_stdio.StdioServerTransport();
475
+ await server.connect(transport);
476
+ await this.hooks.doAction("on:server:start", { config: this.config, domains: this.registry.getAll().map((d) => d.manifest) });
477
+ }
478
+ };
479
+ // Annotate the CommonJS export names for ESM import in node:
480
+ 0 && (module.exports = {
481
+ ApprovalRequiredError,
482
+ AuthError,
483
+ DomainLoader,
484
+ DomainRegistry,
485
+ EagiRunner,
486
+ EagiServerBuilder,
487
+ HookEngine,
488
+ approvalMiddleware,
489
+ auditMiddleware,
490
+ authMiddleware,
491
+ defineConfig,
492
+ defineHooks,
493
+ definePrompt,
494
+ defineResource,
495
+ defineService,
496
+ defineTool,
497
+ sortServices
498
+ });