it0/packages/services/agent-service/src/interfaces/rest/controllers/agent-instance.controller.ts

130 lines
4.3 KiB
TypeScript

import {
Controller,
Get,
Post,
Put,
Delete,
Body,
Param,
NotFoundException,
BadRequestException,
} from '@nestjs/common';
import * as crypto from 'crypto';
import { AgentInstanceRepository } from '../../../infrastructure/repositories/agent-instance.repository';
import { AgentInstanceDeployService } from '../../../infrastructure/services/agent-instance-deploy.service';
import { AgentInstance } from '../../../domain/entities/agent-instance.entity';
import { ConfigService } from '@nestjs/config';
@Controller('api/v1/agent/instances')
export class AgentInstanceController {
private readonly claudeApiKey: string;
constructor(
private readonly instanceRepo: AgentInstanceRepository,
private readonly deployService: AgentInstanceDeployService,
private readonly configService: ConfigService,
) {
this.claudeApiKey = this.configService.get<string>('ANTHROPIC_API_KEY', '');
}
@Get()
async list() {
return this.instanceRepo.findAll();
}
@Get(':id')
async getOne(@Param('id') id: string) {
const inst = await this.instanceRepo.findById(id);
if (!inst) throw new NotFoundException(`Instance ${id} not found`);
return this.sanitize(inst);
}
@Post()
async create(@Body() body: {
name: string;
agentType?: string;
userId: string;
// Pool-server deployment (no server creds needed)
usePool?: boolean;
// User-owned server deployment
serverHost?: string;
sshPort?: number;
sshUser?: string;
sshKey?: string; // plaintext private key, only used if usePool=false
}) {
if (!body.name || !body.userId) {
throw new BadRequestException('name and userId are required');
}
const instance = new AgentInstance();
instance.id = crypto.randomUUID();
instance.userId = body.userId;
instance.name = body.name;
instance.agentType = body.agentType ?? 'openclaw';
instance.containerName = `openclaw-${instance.id.slice(0, 8)}`;
instance.status = 'deploying';
instance.config = {};
instance.hostPort = 0; // Will be set by deploy service
if (body.usePool !== false) {
await this.instanceRepo.save(instance);
this.deployService.deployFromPool(instance, this.claudeApiKey)
.then(() => this.instanceRepo.save(instance))
.catch(async (err) => {
instance.status = 'error';
instance.errorMessage = err.message;
await this.instanceRepo.save(instance);
});
} else {
if (!body.serverHost || !body.sshUser || !body.sshKey) {
throw new BadRequestException('serverHost, sshUser, sshKey required for user-owned server');
}
instance.serverHost = body.serverHost;
instance.sshPort = body.sshPort ?? 22;
instance.sshUser = body.sshUser;
await this.instanceRepo.save(instance);
this.deployService.deployToUserServer(instance, body.sshKey, this.claudeApiKey)
.then(() => this.instanceRepo.save(instance))
.catch(async (err) => {
instance.status = 'error';
instance.errorMessage = err.message;
await this.instanceRepo.save(instance);
});
}
return this.sanitize(instance);
}
@Put(':id/stop')
async stop(@Param('id') id: string) {
const inst = await this.instanceRepo.findById(id);
if (!inst) throw new NotFoundException(`Instance ${id} not found`);
await this.deployService.stopInstance(inst);
inst.status = 'stopped';
return this.sanitize(await this.instanceRepo.save(inst));
}
@Put(':id/name')
async rename(@Param('id') id: string, @Body() body: { name: string }) {
const inst = await this.instanceRepo.findById(id);
if (!inst) throw new NotFoundException(`Instance ${id} not found`);
inst.name = body.name;
return this.sanitize(await this.instanceRepo.save(inst));
}
@Delete(':id')
async remove(@Param('id') id: string) {
const inst = await this.instanceRepo.findById(id);
if (!inst) throw new NotFoundException(`Instance ${id} not found`);
await this.deployService.removeInstance(inst);
inst.status = 'removed';
await this.instanceRepo.save(inst);
return { message: 'Instance removed', id };
}
private sanitize(inst: AgentInstance) {
const { openclawToken, openclawTokenIv, ...safe } = inst;
return { ...safe, hasToken: !!openclawToken };
}
}