130 lines
4.3 KiB
TypeScript
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 };
|
|
}
|
|
}
|