import { Controller, Get, HttpException, HttpStatus, Inject, Param, 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 { HarnessAdapterUnavailableError, HarnessRegistry } from './harness.registry.js'; import { HARNESS_REGISTRY } from './harness.tokens.js'; import { readContextFromScope, toHarnessSummary, toSafeCatalog, type HarnessCatalogDto, type HarnessSummaryDto, } from './harness.dto.js'; /** * Generic harness catalog surface. It exposes only harness-neutral, browser-safe * fields (identity, capabilities, provider/model catalog) — never executables, * native paths, home/cwd, env, or secrets. There is NO provider-probe route here; * `/api/providers` and `POST /api/providers/test` are intentionally out of scope. */ @Controller('api/harnesses') @UseGuards(AuthGuard) export class HarnessController { constructor(@Inject(HARNESS_REGISTRY) private readonly registry: HarnessRegistry) {} @Get() async list(@CurrentUser() user: AuthenticatedUserLike): Promise { const context = readContextFromScope(scopeFromUser(user)); const summaries: HarnessSummaryDto[] = []; for (const adapter of this.registry.list()) { summaries.push(toHarnessSummary(await adapter.describe(context))); } return summaries; } @Get(':harnessId/catalog') async catalog( @CurrentUser() user: AuthenticatedUserLike, @Param('harnessId') harnessId: string, ): Promise { const context = readContextFromScope(scopeFromUser(user)); let adapter; try { adapter = this.registry.get(harnessId); } catch (error) { if (error instanceof HarnessAdapterUnavailableError) { // Typed failure — NEVER a fallback catalog for an unknown harness id. throw new HttpException( { code: error.code, message: error.message, harnessId }, HttpStatus.NOT_FOUND, ); } throw error; } return toSafeCatalog(await adapter.catalog(context)); } }