import { Body, Controller, ForbiddenException, Get, Headers, Inject, Param, Post, UseGuards, } from '@nestjs/common'; import { AuthGuard } from '../auth/auth.guard.js'; import { CurrentUser } from '../auth/current-user.decorator.js'; import { scopeFromUser, type AuthenticatedUserLike } from '../auth/session-scope.js'; import type { RuntimeProviderRequestContext } from '../agent/runtime-provider-registry.service.js'; import type { InteractionCoordinationObservationDto, InteractionCoordinationResponseDto, InteractionCoordinationResultDto, CreateHandoffDto, } from './interaction-coordination.dto.js'; import { InteractionCoordinationService } from './interaction-coordination.service.js'; /** Authenticated interaction-plane boundary for the handoff/observe/result-only interaction coordination contract. */ /** `api/coord/interaction` is canonical; the Mos path remains a compatibility alias. */ @Controller(['api/coord/interaction', 'api/coord/mos']) @UseGuards(AuthGuard) export class InteractionCoordinationController { constructor( @Inject(InteractionCoordinationService) private readonly coordination: InteractionCoordinationService, ) {} @Post('handoff') async handoff( @Body() request: CreateHandoffDto, @CurrentUser() user: AuthenticatedUserLike, @Headers('x-correlation-id') correlationId?: string, ): Promise { return { receipt: await this.coordination.handoff(request, this.context(user, correlationId)) }; } @Get(':handoffId/observe') async observe( @Param('handoffId') handoffId: string, @CurrentUser() user: AuthenticatedUserLike, @Headers('x-correlation-id') correlationId?: string, ): Promise { return { observation: await this.coordination.observe(handoffId, this.context(user, correlationId)), }; } @Get(':handoffId/result') async result( @Param('handoffId') handoffId: string, @CurrentUser() user: AuthenticatedUserLike, @Headers('x-correlation-id') correlationId?: string, ): Promise { return { result: await this.coordination.result(handoffId, this.context(user, correlationId)) }; } private context( user: AuthenticatedUserLike, correlationId?: string, ): RuntimeProviderRequestContext { const requestCorrelationId = correlationId?.trim(); if (!requestCorrelationId) throw new ForbiddenException('X-Correlation-Id is required'); return { actorScope: scopeFromUser(user), channelId: 'cli', correlationId: requestCorrelationId, }; } }