@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.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,5 @@
1
+ import type { EagiConfig } from '../domain/types';
2
+
3
+ export function defineConfig(config: EagiConfig): EagiConfig {
4
+ return config;
5
+ }
@@ -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,6 @@
1
+ export * from './tool';
2
+ export * from './resource';
3
+ export * from './prompt';
4
+ export * from './service';
5
+ export * from './hooks';
6
+ export * from './config';
@@ -0,0 +1,8 @@
1
+ import type { ZodType } from 'zod';
2
+ import type { PromptDefinition } from '../domain/types';
3
+
4
+ export function definePrompt<TArgs extends ZodType>(
5
+ definition: PromptDefinition<TArgs>
6
+ ): PromptDefinition<TArgs> {
7
+ return definition;
8
+ }
@@ -0,0 +1,7 @@
1
+ import type { ResourceDefinition } from '../domain/types';
2
+
3
+ export function defineResource<TUri extends string>(
4
+ definition: ResourceDefinition<TUri>
5
+ ): ResourceDefinition<TUri> {
6
+ return definition;
7
+ }
@@ -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,8 @@
1
+ import type { ZodType } from 'zod';
2
+ import type { ToolDefinition } from '../domain/types';
3
+
4
+ export function defineTool<TInput extends ZodType>(
5
+ definition: ToolDefinition<TInput>
6
+ ): ToolDefinition<TInput> {
7
+ return definition;
8
+ }
@@ -0,0 +1,3 @@
1
+ export * from './types';
2
+ export * from './loader';
3
+ export * from './registry';
@@ -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
+ }