How to integrate Icepick agents with NestJS applications
npm install @hatchet-dev/icepick
npm install @nestjs/config # For environment configuration
src/agents/my-agent.ts
):
import { icepick } from "@hatchet-dev/icepick";
import z from "zod";
const MyAgentInput = z.object({
message: z.string(),
userId: z.string().optional(),
});
const MyAgentOutput = z.object({
response: z.string(),
processedAt: z.string(),
});
export const myAgent = icepick.agent({
name: "my-nestjs-agent",
description: "Agent for NestJS application",
inputSchema: MyAgentInput,
outputSchema: MyAgentOutput,
executionTimeout: "5m",
fn: async (input, ctx) => {
ctx.logger.info(`Processing message for user: ${input.userId}`);
// Your agent logic here
return {
response: `Processed: ${input.message}`,
processedAt: new Date().toISOString(),
};
},
});
src/agents/agent.service.ts
):
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Hatchet } from '@hatchet-dev/typescript-sdk';
import { myAgent } from '@/agents/my-agent';
@Injectable()
export class AgentService implements OnModuleInit {
private readonly logger = new Logger(AgentService.name);
private hatchet: Hatchet;
constructor(private configService: ConfigService) {}
async onModuleInit() {
try {
this.hatchet = Hatchet.init({
token: this.configService.get<string>('HATCHET_CLIENT_TOKEN'),
serverUrl: this.configService.get<string>('HATCHET_SERVER_URL'),
});
await this.hatchet.registerWorkflows([myAgent]);
this.logger.log('Hatchet workflows registered successfully');
} catch (error) {
this.logger.error('Failed to initialize Hatchet', error);
throw error;
}
}
async runAgent(input: { message: string; userId?: string }) {
try {
this.logger.log(`Running agent with input: ${JSON.stringify(input)}`);
const result = await myAgent.run(input);
this.logger.log(`Agent completed successfully`);
return result;
} catch (error) {
this.logger.error('Agent execution failed', error);
throw new Error('Agent execution failed');
}
}
async runAgentAsync(input: { message: string; userId?: string }) {
try {
this.logger.log(`Starting async agent with input: ${JSON.stringify(input)}`);
const workflowRef = await myAgent.runNoWait(input);
this.logger.log(`Agent started with run ID: ${workflowRef.runId}`);
return { runId: workflowRef.runId };
} catch (error) {
this.logger.error('Failed to start agent', error);
throw new Error('Failed to start agent');
}
}
async getAgentResult(runId: string) {
try {
// Implementation depends on your Hatchet client setup
// This is a placeholder for getting results by run ID
this.logger.log(`Getting result for run ID: ${runId}`);
// You would implement this based on your Hatchet client
// return await this.hatchet.getWorkflowResult(runId);
throw new Error('Method not implemented');
} catch (error) {
this.logger.error('Failed to get agent result', error);
throw new Error('Failed to get agent result');
}
}
}
src/agents/agent.controller.ts
):
import {
Controller,
Post,
Body,
Get,
Param,
HttpException,
HttpStatus,
Logger,
} from '@nestjs/common';
import { AgentService } from './agent.service';
export class RunAgentDto {
message: string;
userId?: string;
}
export class AgentResponse {
response: string;
processedAt: string;
}
export class AsyncAgentResponse {
runId: string;
}
@Controller('agents')
export class AgentController {
private readonly logger = new Logger(AgentController.name);
constructor(private readonly agentService: AgentService) {}
@Post('run')
async runAgent(@Body() dto: RunAgentDto): Promise<AgentResponse> {
try {
this.logger.log(`Running agent for message: ${dto.message}`);
const result = await this.agentService.runAgent({
message: dto.message,
userId: dto.userId,
});
return result;
} catch (error) {
this.logger.error('Agent execution failed', error);
throw new HttpException(
'Agent execution failed',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@Post('run-async')
async runAgentAsync(@Body() dto: RunAgentDto): Promise<AsyncAgentResponse> {
try {
this.logger.log(`Starting async agent for message: ${dto.message}`);
const result = await this.agentService.runAgentAsync({
message: dto.message,
userId: dto.userId,
});
return result;
} catch (error) {
this.logger.error('Failed to start agent', error);
throw new HttpException(
'Failed to start agent',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@Get('result/:runId')
async getAgentResult(@Param('runId') runId: string) {
try {
this.logger.log(`Getting result for run ID: ${runId}`);
const result = await this.agentService.getAgentResult(runId);
return result;
} catch (error) {
this.logger.error('Failed to get agent result', error);
throw new HttpException(
'Failed to get agent result',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
}
src/agents/agent.module.ts
):
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { AgentService } from './agent.service';
import { AgentController } from './agent.controller';
@Module({
imports: [ConfigModule],
controllers: [AgentController],
providers: [AgentService],
exports: [AgentService],
})
export class AgentModule {}
src/app.module.ts
):
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { AgentModule } from './agents/agent.module';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
}),
AgentModule,
],
})
export class AppModule {}
.env
):
HATCHET_CLIENT_TOKEN=your_token_here
HATCHET_SERVER_URL=https://app.hatchet.run
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { UseGuards, UseInterceptors } from '@nestjs/common';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
// Your authentication logic
const request = context.switchToHttp().getRequest();
return !!request.headers.authorization;
}
}
@Controller('agents')
@UseGuards(AuthGuard)
export class AgentController {
// Your controller methods
}
import { Test, TestingModule } from '@nestjs/testing';
import { ConfigService } from '@nestjs/config';
import { AgentService } from './agent.service';
describe('AgentService', () => {
let service: AgentService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
AgentService,
{
provide: ConfigService,
useValue: {
get: jest.fn((key: string) => {
const config = {
HATCHET_CLIENT_TOKEN: 'test-token',
};
return config[key];
}),
},
},
],
}).compile();
service = module.get<AgentService>(AgentService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
// Add more tests here
});