@bpinhosilva/agent-orchestrator 0.0.1
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/.github/ISSUE_TEMPLATE/bug_report.md +25 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +18 -0
- package/.github/workflows/ci.yml +34 -0
- package/.github/workflows/gitleaks.yml +22 -0
- package/.github/workflows/release.yml +38 -0
- package/.husky/commit-msg +1 -0
- package/.husky/pre-commit +6 -0
- package/.prettierrc +4 -0
- package/.releaserc.json +31 -0
- package/CONTRIBUTING.md +64 -0
- package/LICENSE +21 -0
- package/README.md +75 -0
- package/TODO.md +14 -0
- package/commitlint.config.js +3 -0
- package/dist/agents/agents.controller.d.ts +8 -0
- package/dist/agents/agents.controller.js +39 -0
- package/dist/agents/agents.controller.js.map +1 -0
- package/dist/agents/agents.module.d.ts +2 -0
- package/dist/agents/agents.module.js +23 -0
- package/dist/agents/agents.module.js.map +1 -0
- package/dist/agents/agents.service.d.ts +10 -0
- package/dist/agents/agents.service.js +33 -0
- package/dist/agents/agents.service.js.map +1 -0
- package/dist/agents/dto/agent-request.dto.d.ts +3 -0
- package/dist/agents/dto/agent-request.dto.js +21 -0
- package/dist/agents/dto/agent-request.dto.js.map +1 -0
- package/dist/agents/implementations/gemini.agent.d.ts +8 -0
- package/dist/agents/implementations/gemini.agent.js +59 -0
- package/dist/agents/implementations/gemini.agent.js.map +1 -0
- package/dist/agents/interfaces/agent.interface.d.ts +8 -0
- package/dist/agents/interfaces/agent.interface.js +2 -0
- package/dist/agents/interfaces/agent.interface.js.map +1 -0
- package/dist/app.controller.d.ts +6 -0
- package/dist/app.controller.js +33 -0
- package/dist/app.controller.js.map +1 -0
- package/dist/app.module.d.ts +2 -0
- package/dist/app.module.js +31 -0
- package/dist/app.module.js.map +1 -0
- package/dist/app.service.d.ts +3 -0
- package/dist/app.service.js +19 -0
- package/dist/app.service.js.map +1 -0
- package/dist/cli.js +21 -0
- package/dist/main.d.ts +1 -0
- package/dist/main.js +15 -0
- package/dist/main.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/docker-compose.yml +21 -0
- package/eslint.config.mjs +35 -0
- package/nest-cli.json +8 -0
- package/package.json +98 -0
- package/public/index.html +20 -0
- package/src/agents/agents.controller.spec.ts +46 -0
- package/src/agents/agents.controller.ts +17 -0
- package/src/agents/agents.module.ts +11 -0
- package/src/agents/agents.service.spec.ts +44 -0
- package/src/agents/agents.service.ts +23 -0
- package/src/agents/dto/agent-request.dto.ts +7 -0
- package/src/agents/implementations/gemini.agent.spec.ts +41 -0
- package/src/agents/implementations/gemini.agent.ts +50 -0
- package/src/agents/interfaces/agent.interface.ts +9 -0
- package/src/app.controller.spec.ts +22 -0
- package/src/app.controller.ts +12 -0
- package/src/app.module.ts +19 -0
- package/src/app.service.ts +8 -0
- package/src/cli.ts +23 -0
- package/src/main.ts +18 -0
- package/test/app.e2e-spec.ts +56 -0
- package/test/jest-e2e.json +9 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
version: '3.8'
|
|
2
|
+
services:
|
|
3
|
+
db:
|
|
4
|
+
image: postgres:15-alpine
|
|
5
|
+
container_name: agent-orchestrator-db
|
|
6
|
+
environment:
|
|
7
|
+
POSTGRES_USER: orchestrator
|
|
8
|
+
POSTGRES_PASSWORD: orchestrator_password
|
|
9
|
+
POSTGRES_DB: agent_orchestrator
|
|
10
|
+
ports:
|
|
11
|
+
- '5432:5432'
|
|
12
|
+
volumes:
|
|
13
|
+
- postgres-data:/var/lib/postgresql/data
|
|
14
|
+
healthcheck:
|
|
15
|
+
test: ["CMD-SHELL", "pg_isready -U orchestrator -d agent_orchestrator"]
|
|
16
|
+
interval: 5s
|
|
17
|
+
timeout: 5s
|
|
18
|
+
retries: 5
|
|
19
|
+
|
|
20
|
+
volumes:
|
|
21
|
+
postgres-data:
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import eslint from '@eslint/js';
|
|
3
|
+
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
|
|
4
|
+
import globals from 'globals';
|
|
5
|
+
import tseslint from 'typescript-eslint';
|
|
6
|
+
|
|
7
|
+
export default tseslint.config(
|
|
8
|
+
{
|
|
9
|
+
ignores: ['eslint.config.mjs'],
|
|
10
|
+
},
|
|
11
|
+
eslint.configs.recommended,
|
|
12
|
+
...tseslint.configs.recommendedTypeChecked,
|
|
13
|
+
eslintPluginPrettierRecommended,
|
|
14
|
+
{
|
|
15
|
+
languageOptions: {
|
|
16
|
+
globals: {
|
|
17
|
+
...globals.node,
|
|
18
|
+
...globals.jest,
|
|
19
|
+
},
|
|
20
|
+
sourceType: 'commonjs',
|
|
21
|
+
parserOptions: {
|
|
22
|
+
projectService: true,
|
|
23
|
+
tsconfigRootDir: import.meta.dirname,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
rules: {
|
|
29
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
|
30
|
+
'@typescript-eslint/no-floating-promises': 'warn',
|
|
31
|
+
'@typescript-eslint/no-unsafe-argument': 'warn',
|
|
32
|
+
"prettier/prettier": ["error", { endOfLine: "auto" }],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
);
|
package/nest-cli.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bpinhosilva/agent-orchestrator",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "An open-source AI agent orchestrator platform built with NestJS.",
|
|
5
|
+
"author": "bpinhosilva",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/bpinhosilva/agent-orchestrator.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/bpinhosilva/agent-orchestrator/issues"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/bpinhosilva/agent-orchestrator#readme",
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "nest build",
|
|
17
|
+
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
|
18
|
+
"start": "nest start",
|
|
19
|
+
"start:dev": "nest start --watch",
|
|
20
|
+
"start:debug": "nest start --debug --watch",
|
|
21
|
+
"start:prod": "node dist/main",
|
|
22
|
+
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
|
23
|
+
"test": "jest",
|
|
24
|
+
"test:watch": "jest --watch",
|
|
25
|
+
"test:cov": "jest --coverage",
|
|
26
|
+
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
|
27
|
+
"test:e2e": "jest --config ./test/jest-e2e.json",
|
|
28
|
+
"prepare": "husky",
|
|
29
|
+
"cli:build": "tsc src/cli.ts --outDir dist --esModuleInterop --skipLibCheck --target es2020 --moduleResolution node --module commonjs --experimentalDecorators --emitDecoratorMetadata"
|
|
30
|
+
},
|
|
31
|
+
"bin": {
|
|
32
|
+
"agent-orchestrator": "./dist/cli.js"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@google/generative-ai": "^0.24.1",
|
|
36
|
+
"@nestjs/common": "^11.0.1",
|
|
37
|
+
"@nestjs/core": "^11.0.1",
|
|
38
|
+
"@nestjs/platform-express": "^11.0.1",
|
|
39
|
+
"@nestjs/serve-static": "^5.0.4",
|
|
40
|
+
"class-transformer": "^0.5.1",
|
|
41
|
+
"class-validator": "^0.15.1",
|
|
42
|
+
"commander": "^14.0.3",
|
|
43
|
+
"reflect-metadata": "^0.2.2",
|
|
44
|
+
"rxjs": "^7.8.1"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=24.0.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@commitlint/cli": "^20.5.0",
|
|
51
|
+
"@commitlint/config-conventional": "^20.5.0",
|
|
52
|
+
"@eslint/eslintrc": "^3.2.0",
|
|
53
|
+
"@eslint/js": "^9.18.0",
|
|
54
|
+
"@nestjs/cli": "^11.0.0",
|
|
55
|
+
"@nestjs/schematics": "^11.0.0",
|
|
56
|
+
"@nestjs/testing": "^11.0.1",
|
|
57
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
58
|
+
"@semantic-release/git": "^10.0.1",
|
|
59
|
+
"@types/express": "^5.0.0",
|
|
60
|
+
"@types/jest": "^30.0.0",
|
|
61
|
+
"@types/node": "^22.10.7",
|
|
62
|
+
"@types/supertest": "^6.0.2",
|
|
63
|
+
"eslint": "^9.18.0",
|
|
64
|
+
"eslint-config-prettier": "^10.0.1",
|
|
65
|
+
"eslint-plugin-prettier": "^5.2.2",
|
|
66
|
+
"gitleaks": "^1.0.0",
|
|
67
|
+
"globals": "^16.0.0",
|
|
68
|
+
"husky": "^9.1.7",
|
|
69
|
+
"jest": "^30.0.0",
|
|
70
|
+
"prettier": "^3.4.2",
|
|
71
|
+
"semantic-release": "^25.0.3",
|
|
72
|
+
"source-map-support": "^0.5.21",
|
|
73
|
+
"supertest": "^7.0.0",
|
|
74
|
+
"ts-jest": "^29.2.5",
|
|
75
|
+
"ts-loader": "^9.5.2",
|
|
76
|
+
"ts-node": "^10.9.2",
|
|
77
|
+
"tsconfig-paths": "^4.2.0",
|
|
78
|
+
"typescript": "^5.7.3",
|
|
79
|
+
"typescript-eslint": "^8.20.0"
|
|
80
|
+
},
|
|
81
|
+
"jest": {
|
|
82
|
+
"moduleFileExtensions": [
|
|
83
|
+
"js",
|
|
84
|
+
"json",
|
|
85
|
+
"ts"
|
|
86
|
+
],
|
|
87
|
+
"rootDir": "src",
|
|
88
|
+
"testRegex": ".*\\.spec\\.ts$",
|
|
89
|
+
"transform": {
|
|
90
|
+
"^.+\\.(t|j)s$": "ts-jest"
|
|
91
|
+
},
|
|
92
|
+
"collectCoverageFrom": [
|
|
93
|
+
"**/*.(t|j)s"
|
|
94
|
+
],
|
|
95
|
+
"coverageDirectory": "../coverage",
|
|
96
|
+
"testEnvironment": "node"
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Agent Orchestrator Dashboard</title>
|
|
7
|
+
<style>
|
|
8
|
+
body { font-family: sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background-color: #f4f4f9; color: #333; }
|
|
9
|
+
.container { text-align: center; padding: 2rem; background: #fff; box-shadow: 0 4px 6px rgba(0,0,0,0.1); border-radius: 8px; }
|
|
10
|
+
h1 { color: #5a67d8; }
|
|
11
|
+
</style>
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<div class="container">
|
|
15
|
+
<h1>Agent Orchestrator</h1>
|
|
16
|
+
<p>This is a placeholder for the future React SPA admin dashboard.</p>
|
|
17
|
+
<p>The API is currently serving at <code>/api/v1</code>.</p>
|
|
18
|
+
</div>
|
|
19
|
+
</body>
|
|
20
|
+
</html>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Test, TestingModule } from '@nestjs/testing';
|
|
2
|
+
import { AgentsController } from './agents.controller';
|
|
3
|
+
import { AgentsService } from './agents.service';
|
|
4
|
+
import { AgentRequestDto } from './dto/agent-request.dto';
|
|
5
|
+
import { AgentResponse } from './interfaces/agent.interface';
|
|
6
|
+
|
|
7
|
+
describe('AgentsController', () => {
|
|
8
|
+
let controller: AgentsController;
|
|
9
|
+
let service: AgentsService;
|
|
10
|
+
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
const module: TestingModule = await Test.createTestingModule({
|
|
13
|
+
controllers: [AgentsController],
|
|
14
|
+
providers: [
|
|
15
|
+
{
|
|
16
|
+
provide: AgentsService,
|
|
17
|
+
useValue: {
|
|
18
|
+
processRequest: jest.fn(),
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
}).compile();
|
|
23
|
+
|
|
24
|
+
controller = module.get<AgentsController>(AgentsController);
|
|
25
|
+
service = module.get<AgentsService>(AgentsService);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should be defined', () => {
|
|
29
|
+
expect(controller).toBeDefined();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('processText', () => {
|
|
33
|
+
it('should call service with the correctly validated dto', async () => {
|
|
34
|
+
const dto: AgentRequestDto = { input: 'hello AI' };
|
|
35
|
+
const expectedResponse: AgentResponse = { content: 'hello human' };
|
|
36
|
+
const processRequestSpy = jest
|
|
37
|
+
.spyOn(service, 'processRequest')
|
|
38
|
+
.mockResolvedValue(expectedResponse);
|
|
39
|
+
|
|
40
|
+
const result = await controller.processText(dto);
|
|
41
|
+
|
|
42
|
+
expect(processRequestSpy).toHaveBeenCalledWith(dto);
|
|
43
|
+
expect(result).toBe(expectedResponse);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Controller, Post, Body, HttpCode, HttpStatus } from '@nestjs/common';
|
|
2
|
+
import { AgentsService } from './agents.service';
|
|
3
|
+
import { AgentRequestDto } from './dto/agent-request.dto';
|
|
4
|
+
import { AgentResponse } from './interfaces/agent.interface';
|
|
5
|
+
|
|
6
|
+
@Controller('agents')
|
|
7
|
+
export class AgentsController {
|
|
8
|
+
constructor(private readonly agentsService: AgentsService) {}
|
|
9
|
+
|
|
10
|
+
@Post('process')
|
|
11
|
+
@HttpCode(HttpStatus.OK)
|
|
12
|
+
async processText(
|
|
13
|
+
@Body() requestDto: AgentRequestDto,
|
|
14
|
+
): Promise<AgentResponse> {
|
|
15
|
+
return this.agentsService.processRequest(requestDto);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { AgentsController } from './agents.controller';
|
|
3
|
+
import { AgentsService } from './agents.service';
|
|
4
|
+
import { GeminiAgent } from './implementations/gemini.agent';
|
|
5
|
+
|
|
6
|
+
@Module({
|
|
7
|
+
controllers: [AgentsController],
|
|
8
|
+
providers: [AgentsService, GeminiAgent],
|
|
9
|
+
exports: [AgentsService],
|
|
10
|
+
})
|
|
11
|
+
export class AgentsModule {}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Test, TestingModule } from '@nestjs/testing';
|
|
2
|
+
import { AgentsService } from './agents.service';
|
|
3
|
+
import { GeminiAgent } from './implementations/gemini.agent';
|
|
4
|
+
|
|
5
|
+
describe('AgentsService', () => {
|
|
6
|
+
let service: AgentsService;
|
|
7
|
+
let geminiAgent: GeminiAgent;
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
const module: TestingModule = await Test.createTestingModule({
|
|
11
|
+
providers: [
|
|
12
|
+
AgentsService,
|
|
13
|
+
{
|
|
14
|
+
provide: GeminiAgent,
|
|
15
|
+
useValue: {
|
|
16
|
+
getName: jest.fn().mockReturnValue('MockedGeminiAgent'),
|
|
17
|
+
processText: jest.fn(),
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
}).compile();
|
|
22
|
+
|
|
23
|
+
service = module.get<AgentsService>(AgentsService);
|
|
24
|
+
geminiAgent = module.get<GeminiAgent>(GeminiAgent);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should be defined', () => {
|
|
28
|
+
expect(service).toBeDefined();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('processRequest', () => {
|
|
32
|
+
it('should delegate processText to the default agent', async () => {
|
|
33
|
+
const expectedResponse = { content: 'test response' };
|
|
34
|
+
const processTextSpy = jest
|
|
35
|
+
.spyOn(geminiAgent, 'processText')
|
|
36
|
+
.mockResolvedValue(expectedResponse);
|
|
37
|
+
|
|
38
|
+
const result = await service.processRequest({ input: 'test input' });
|
|
39
|
+
|
|
40
|
+
expect(processTextSpy).toHaveBeenCalledWith('test input');
|
|
41
|
+
expect(result).toBe(expectedResponse);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Injectable, Logger } from '@nestjs/common';
|
|
2
|
+
import { GeminiAgent } from './implementations/gemini.agent';
|
|
3
|
+
import { AgentResponse, Agent } from './interfaces/agent.interface';
|
|
4
|
+
import { AgentRequestDto } from './dto/agent-request.dto';
|
|
5
|
+
|
|
6
|
+
@Injectable()
|
|
7
|
+
export class AgentsService {
|
|
8
|
+
private readonly logger = new Logger(AgentsService.name);
|
|
9
|
+
private defaultAgent: Agent;
|
|
10
|
+
|
|
11
|
+
constructor(private readonly geminiAgent: GeminiAgent) {
|
|
12
|
+
this.defaultAgent = geminiAgent;
|
|
13
|
+
this.logger.log(
|
|
14
|
+
`Initialized AgentsService with default agent: ${this.defaultAgent.getName()}`,
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async processRequest(requestDto: AgentRequestDto): Promise<AgentResponse> {
|
|
19
|
+
this.logger.debug(`Processing input using default agent`);
|
|
20
|
+
// Eventually, logic to select agent based on user request/task could go here.
|
|
21
|
+
return this.defaultAgent.processText(requestDto.input);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { GeminiAgent } from './gemini.agent';
|
|
2
|
+
|
|
3
|
+
jest.mock('@google/generative-ai', () => {
|
|
4
|
+
return {
|
|
5
|
+
GoogleGenerativeAI: jest.fn().mockImplementation(() => ({
|
|
6
|
+
getGenerativeModel: jest.fn().mockReturnValue({
|
|
7
|
+
generateContent: jest.fn().mockResolvedValue({
|
|
8
|
+
response: {
|
|
9
|
+
text: () => 'mocked text output',
|
|
10
|
+
},
|
|
11
|
+
}),
|
|
12
|
+
}),
|
|
13
|
+
})),
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('GeminiAgent', () => {
|
|
18
|
+
let agent: GeminiAgent;
|
|
19
|
+
const originalEnv = process.env;
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
jest.resetModules();
|
|
23
|
+
process.env = { ...originalEnv, GEMINI_API_KEY: 'test_key' };
|
|
24
|
+
agent = new GeminiAgent();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterAll(() => {
|
|
28
|
+
process.env = originalEnv;
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should throw an error if API key is not set', () => {
|
|
32
|
+
delete process.env.GEMINI_API_KEY;
|
|
33
|
+
expect(() => new GeminiAgent()).toThrow('GEMINI_API_KEY is required');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should generate content with gemini-2.5-flash-lite', async () => {
|
|
37
|
+
const response = await agent.processText('test query');
|
|
38
|
+
expect(response.content).toBe('mocked text output');
|
|
39
|
+
expect(response.metadata?.model).toBe('gemini-2.5-flash-lite');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
2
|
+
import { Agent, AgentResponse } from '../interfaces/agent.interface';
|
|
3
|
+
import { Injectable, Logger } from '@nestjs/common';
|
|
4
|
+
|
|
5
|
+
@Injectable()
|
|
6
|
+
export class GeminiAgent implements Agent {
|
|
7
|
+
private readonly logger = new Logger(GeminiAgent.name);
|
|
8
|
+
private genAI: GoogleGenerativeAI;
|
|
9
|
+
|
|
10
|
+
constructor() {
|
|
11
|
+
const apiKey = process.env.GEMINI_API_KEY;
|
|
12
|
+
if (!apiKey) {
|
|
13
|
+
this.logger.error('GEMINI_API_KEY environment variable is not set');
|
|
14
|
+
throw new Error('GEMINI_API_KEY is required to initialize GeminiAgent');
|
|
15
|
+
}
|
|
16
|
+
this.genAI = new GoogleGenerativeAI(apiKey);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getName(): string {
|
|
20
|
+
return 'GeminiAgent';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async processText(input: string): Promise<AgentResponse> {
|
|
24
|
+
this.logger.debug(`Processing input with GeminiAgent`);
|
|
25
|
+
try {
|
|
26
|
+
const model = this.genAI.getGenerativeModel({
|
|
27
|
+
model: 'gemini-2.5-flash-lite',
|
|
28
|
+
});
|
|
29
|
+
const result = await model.generateContent(input);
|
|
30
|
+
const output = result.response.text();
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
content: output,
|
|
34
|
+
metadata: {
|
|
35
|
+
model: 'gemini-2.5-flash-lite',
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (error instanceof Error) {
|
|
40
|
+
this.logger.error(
|
|
41
|
+
`Error processing text: ${error.message}`,
|
|
42
|
+
error.stack,
|
|
43
|
+
);
|
|
44
|
+
} else {
|
|
45
|
+
this.logger.error(`Error processing text: ${String(error)}`);
|
|
46
|
+
}
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Test, TestingModule } from '@nestjs/testing';
|
|
2
|
+
import { AppController } from './app.controller';
|
|
3
|
+
import { AppService } from './app.service';
|
|
4
|
+
|
|
5
|
+
describe('AppController', () => {
|
|
6
|
+
let appController: AppController;
|
|
7
|
+
|
|
8
|
+
beforeEach(async () => {
|
|
9
|
+
const app: TestingModule = await Test.createTestingModule({
|
|
10
|
+
controllers: [AppController],
|
|
11
|
+
providers: [AppService],
|
|
12
|
+
}).compile();
|
|
13
|
+
|
|
14
|
+
appController = app.get<AppController>(AppController);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('root', () => {
|
|
18
|
+
it('should return "Hello World!"', () => {
|
|
19
|
+
expect(appController.getHello()).toBe('Hello World!');
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Controller, Get } from '@nestjs/common';
|
|
2
|
+
import { AppService } from './app.service';
|
|
3
|
+
|
|
4
|
+
@Controller()
|
|
5
|
+
export class AppController {
|
|
6
|
+
constructor(private readonly appService: AppService) {}
|
|
7
|
+
|
|
8
|
+
@Get()
|
|
9
|
+
getHello(): string {
|
|
10
|
+
return this.appService.getHello();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { ServeStaticModule } from '@nestjs/serve-static';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { AgentsModule } from './agents/agents.module';
|
|
5
|
+
import { AppController } from './app.controller';
|
|
6
|
+
import { AppService } from './app.service';
|
|
7
|
+
|
|
8
|
+
@Module({
|
|
9
|
+
imports: [
|
|
10
|
+
ServeStaticModule.forRoot({
|
|
11
|
+
rootPath: join(__dirname, '..', 'public'),
|
|
12
|
+
exclude: ['/api/(.*)'],
|
|
13
|
+
}),
|
|
14
|
+
AgentsModule,
|
|
15
|
+
],
|
|
16
|
+
controllers: [AppController],
|
|
17
|
+
providers: [AppService],
|
|
18
|
+
})
|
|
19
|
+
export class AppModule {}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { NestFactory } from '@nestjs/core';
|
|
4
|
+
import { AppModule } from './app.module';
|
|
5
|
+
|
|
6
|
+
const program = new Command();
|
|
7
|
+
|
|
8
|
+
program
|
|
9
|
+
.name('agent-orchestrator')
|
|
10
|
+
.description('An open-source AI agent orchestrator platform')
|
|
11
|
+
.version('0.0.1');
|
|
12
|
+
|
|
13
|
+
program
|
|
14
|
+
.command('run')
|
|
15
|
+
.description('Start the orchestrator server')
|
|
16
|
+
.action(async () => {
|
|
17
|
+
console.log('Starting Agent Orchestrator...');
|
|
18
|
+
const app = await NestFactory.create(AppModule);
|
|
19
|
+
await app.listen(process.env.PORT ?? 3000);
|
|
20
|
+
console.log(`Orchestrator is running on: ${await app.getUrl()}`);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
program.parse(process.argv);
|
package/src/main.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { NestFactory } from '@nestjs/core';
|
|
2
|
+
import { ValidationPipe } from '@nestjs/common';
|
|
3
|
+
import { AppModule } from './app.module';
|
|
4
|
+
|
|
5
|
+
async function bootstrap() {
|
|
6
|
+
const app = await NestFactory.create(AppModule, {
|
|
7
|
+
logger: ['log', 'error', 'warn', 'debug', 'verbose'],
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
app.setGlobalPrefix('api/v1');
|
|
11
|
+
app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }));
|
|
12
|
+
|
|
13
|
+
await app.listen(process.env.PORT ?? 3000);
|
|
14
|
+
}
|
|
15
|
+
bootstrap().catch((err) => {
|
|
16
|
+
console.error('Application failed to start', err);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Test, TestingModule } from '@nestjs/testing';
|
|
2
|
+
import { INestApplication, ValidationPipe } from '@nestjs/common';
|
|
3
|
+
import request from 'supertest';
|
|
4
|
+
import { App } from 'supertest/types';
|
|
5
|
+
import { AppModule } from './../src/app.module';
|
|
6
|
+
import { AgentsService } from '../src/agents/agents.service';
|
|
7
|
+
import { GeminiAgent } from '../src/agents/implementations/gemini.agent';
|
|
8
|
+
|
|
9
|
+
describe('AppController (e2e)', () => {
|
|
10
|
+
let app: INestApplication<App>;
|
|
11
|
+
const originalEnv = process.env;
|
|
12
|
+
|
|
13
|
+
beforeAll(() => {
|
|
14
|
+
process.env = { ...originalEnv, GEMINI_API_KEY: 'test_key' };
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterAll(() => {
|
|
18
|
+
process.env = originalEnv;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
beforeEach(async () => {
|
|
22
|
+
const moduleFixture: TestingModule = await Test.createTestingModule({
|
|
23
|
+
imports: [AppModule],
|
|
24
|
+
})
|
|
25
|
+
.overrideProvider(GeminiAgent)
|
|
26
|
+
.useValue({
|
|
27
|
+
getName: () => 'MockedGeminiAgent',
|
|
28
|
+
processText: () =>
|
|
29
|
+
Promise.resolve({ content: 'mocked agent response' }),
|
|
30
|
+
})
|
|
31
|
+
.compile();
|
|
32
|
+
|
|
33
|
+
app = moduleFixture.createNestApplication();
|
|
34
|
+
app.setGlobalPrefix('api/v1');
|
|
35
|
+
app.useGlobalPipes(
|
|
36
|
+
new ValidationPipe({ transform: true, whitelist: true }),
|
|
37
|
+
);
|
|
38
|
+
await app.init();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('/api/v1/agents/process (POST)', () => {
|
|
42
|
+
// We mock Gemini agent response since providing a fake api key still attempts a real network request.
|
|
43
|
+
const agentsService = app.get(AgentsService);
|
|
44
|
+
jest
|
|
45
|
+
.spyOn(agentsService, 'processRequest')
|
|
46
|
+
.mockResolvedValue({ content: 'mocked output from e2e' });
|
|
47
|
+
|
|
48
|
+
return request(app.getHttpServer())
|
|
49
|
+
.post('/api/v1/agents/process')
|
|
50
|
+
.send({ input: 'hello' })
|
|
51
|
+
.expect(200)
|
|
52
|
+
.expect((res) => {
|
|
53
|
+
expect(res.body).toEqual({ content: 'mocked output from e2e' });
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "nodenext",
|
|
4
|
+
"moduleResolution": "nodenext",
|
|
5
|
+
"resolvePackageJsonExports": true,
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"isolatedModules": true,
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"removeComments": true,
|
|
10
|
+
"emitDecoratorMetadata": true,
|
|
11
|
+
"experimentalDecorators": true,
|
|
12
|
+
"allowSyntheticDefaultImports": true,
|
|
13
|
+
"target": "ES2023",
|
|
14
|
+
"sourceMap": true,
|
|
15
|
+
"outDir": "./dist",
|
|
16
|
+
"baseUrl": "./",
|
|
17
|
+
"incremental": true,
|
|
18
|
+
"skipLibCheck": true,
|
|
19
|
+
"strictNullChecks": true,
|
|
20
|
+
"forceConsistentCasingInFileNames": true,
|
|
21
|
+
"noImplicitAny": false,
|
|
22
|
+
"strictBindCallApply": false,
|
|
23
|
+
"noFallthroughCasesInSwitch": false
|
|
24
|
+
}
|
|
25
|
+
}
|