Skip to content
This repository was archived by the owner on Jul 7, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,385 changes: 1,306 additions & 79 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

59 changes: 59 additions & 0 deletions services/agents/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# compiled output
/dist
/node_modules
/build

# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# OS
.DS_Store

# Tests
/coverage
/.nyc_output

# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# temp directory
.temp
.tmp

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Sqlite DB
db/
2 changes: 2 additions & 0 deletions services/agents/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
save-exact = true
auto-install-peers = true
4 changes: 4 additions & 0 deletions services/agents/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "all"
}
35 changes: 35 additions & 0 deletions services/agents/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// @ts-check
import eslint from '@eslint/js';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
import globals from 'globals';
import tseslint from 'typescript-eslint';

export default tseslint.config(
// {
// ignores: ['eslint.config.mjs'],
// },
// eslint.configs.recommended,
// ...tseslint.configs.recommendedTypeChecked,
// eslintPluginPrettierRecommended,
// {
// languageOptions: {
// globals: {
// ...globals.node,
// ...globals.jest,
// },
// ecmaVersion: 5,
// sourceType: 'module',
// parserOptions: {
// projectService: true,
// tsconfigRootDir: import.meta.dirname,
// },
// },
// },
// {
// rules: {
// '@typescript-eslint/no-explicit-any': 'off',
// '@typescript-eslint/no-floating-promises': 'warn',
// '@typescript-eslint/no-unsafe-argument': 'warn'
// },
// },
);
8 changes: 8 additions & 0 deletions services/agents/nest-cli.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
}
}
77 changes: 77 additions & 0 deletions services/agents/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
{
"name": "agents",
"version": "0.0.1",
"description": "Agents service to run various agents",
"author": "Abdulla Faraz <[email protected]>",
"private": true,
"license": "MIT",
"scripts": {
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"dev": "nest start --watch",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@aws-sdk/client-bedrock-runtime": "3.744.0",
"@nestjs/common": "11.0.1",
"@nestjs/config": "4.0.0",
"@nestjs/core": "11.0.1",
"@nestjs/platform-express": "11.0.1",
"@nestjs/schedule": "5.0.1",
"cron": "3.5.0",
"reflect-metadata": "0.2.2",
"rxjs": "7.8.1"
},
"devDependencies": {
"@eslint/eslintrc": "3.2.0",
"@eslint/js": "9.18.0",
"@nestjs/cli": "11.0.0",
"@nestjs/schematics": "11.0.0",
"@nestjs/testing": "11.0.1",
"@types/cron": "2.4.3",
"@types/express": "5.0.0",
"@types/jest": "29.5.14",
"@types/node": "22.10.7",
"@types/supertest": "6.0.2",
"eslint": "9.18.0",
"eslint-config-prettier": "10.0.1",
"eslint-plugin-prettier": "5.2.2",
"jest": "29.7.0",
"prettier": "3.4.2",
"source-map-support": "0.5.21",
"supertest": "7.0.0",
"ts-jest": "29.2.5",
"ts-loader": "9.5.2",
"ts-node": "10.9.2",
"tsconfig-paths": "4.2.0",
"typescript": "5.7.3",
"typescript-eslint": "8.20.0"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
},
"packageManager": "[email protected]"
}
56 changes: 56 additions & 0 deletions services/agents/src/agents/volume.agent.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Test, TestingModule } from '@nestjs/testing';
import { VolumeAgent } from './volume.agent';
import { Bedrock } from '../utils/bedrock';
import { Logger } from '@nestjs/common';

describe('VolumeAgent', () => {
let volumeAgent: VolumeAgent;
let bedrockClient: Bedrock;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
VolumeAgent,
{
provide: Bedrock,
useValue: {
query: jest.fn(),
},
},
{
provide: Logger,
useValue: {
log: jest.fn(),
},
},
],
}).compile();

volumeAgent = module.get<VolumeAgent>(VolumeAgent);
bedrockClient = module.get<Bedrock>(Bedrock);
});

it('should be defined', () => {
expect(volumeAgent).toBeDefined();
});

it('should call bedrockClient.query with the correct query', async () => {
const querySpy = jest
.spyOn(bedrockClient, 'query')
.mockResolvedValue('BTC');
await volumeAgent.runAgent();
expect(querySpy).toHaveBeenCalledWith(
expect.stringContaining(
'Given the following volumes for tokens over the last 6 hours',
),
);
});

it('should call contractSwap if response is not null', async () => {
jest.spyOn(bedrockClient, 'query').mockResolvedValue('BTC');
// eslint-disable-next-line
const contractSwapSpy = jest.spyOn(volumeAgent as any, 'contractSwap');
await volumeAgent.runAgent();
expect(contractSwapSpy).toHaveBeenCalledWith('BTC');
});
});
43 changes: 43 additions & 0 deletions services/agents/src/agents/volume.agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Injectable, Logger } from '@nestjs/common';
import { Bedrock } from '../utils/bedrock';

@Injectable()
export class VolumeAgent {
private logger = new Logger(VolumeAgent.name);

constructor(private readonly bedrockClient: Bedrock) {}

private async getVolumeForLast12Periods(token: string) {
this.logger.log(`Getting volume for ${token}`);
return [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
}

private async contractSwap(token: string) {
// do contract swap
this.logger.log(`Swapping to ${token}`);
}

async runAgent() {
const tokenList = ['BTC', 'ETH', 'DOGE', 'SOL']; // max 72 tokens

const volumelist = {};

for (const token of tokenList) {
const volumes = await this.getVolumeForLast12Periods(token);
this.logger.log(`Volume for ${token}: ${volumes}`);
volumelist[token] = volumes;
}

const volumeStr = JSON.stringify(volumelist);

const query = `Given the following volumes for tokens over the last 6 hours: ${volumeStr}, what is the best token to invest in? If nothing shows significant growth return null`; // 26 words

const response = await this.bedrockClient.query(query);

if (response === 'null' || response === null) {
this.logger.log('No significant growth detected');
} else {
await this.contractSwap(response);
}
}
}
10 changes: 10 additions & 0 deletions services/agents/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { ScheduleModule } from '@nestjs/schedule';
import { Runner } from './runner';

@Module({
imports: [ScheduleModule.forRoot()],
providers: [Runner],
exports: [Runner],
})
export class AppModule {}
12 changes: 12 additions & 0 deletions services/agents/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Runner } from './runner';

async function bootstrap() {
const app = await NestFactory.createApplicationContext(AppModule);
const runner = app.get(Runner);
runner.run();
await app.close();
}

bootstrap().then(() => {});
18 changes: 18 additions & 0 deletions services/agents/src/runner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Injectable } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { VolumeAgent } from './agents/volume.agent';

@Injectable()
export class Runner {
constructor(private readonly volumeAgent: VolumeAgent) {}

@Cron(CronExpression.EVERY_30_MINUTES)
async handleEvery30Minutes() {
console.log('Task executed every 10 minutes');
await this.volumeAgent.runAgent();
}

run() {
console.log('Task executed manually');
}
}
47 changes: 47 additions & 0 deletions services/agents/src/utils/bedrock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Injectable, Logger } from '@nestjs/common';
import {
BedrockRuntimeClient,
ConverseCommand,
Message,
} from '@aws-sdk/client-bedrock-runtime';

@Injectable()
export class Bedrock {
private logger: Logger = new Logger(Bedrock.name);
constructor() {}

async query(userMessage: string): Promise<string | null> {
this.logger.log(`User query: ${userMessage}`);

const client = new BedrockRuntimeClient({ region: 'us-west-2' });
const modelId = 'anthropic.claude-3-5-sonnet-20241022-v2:0';

const conversation: Message[] = [
{
role: 'user',
content: [{ text: userMessage }],
},
];

const command = new ConverseCommand({
modelId,
messages: conversation,
inferenceConfig: { maxTokens: 900, temperature: 0.5, topP: 0.9 },
});

try {
const response = await client.send(command);

const responseText = response?.output?.message?.content?.[0].text;
if (!responseText) {
this.logger.error(`ERROR: Can't get response from '${modelId}'`);
return null;
}
this.logger.log(`Model response: ${responseText}`);
return responseText;
} catch (err) {
this.logger.error(`ERROR: Can't invoke '${modelId}'. Reason: ${err}`);
return null;
}
}
}
Loading