feat(p7): /v1/ai REST + kernel-client AI methods + @insignia/iios-ai-web SDK
Task 7.5: AiController exposes POST /v1/ai/jobs, GET /v1/ai/artifacts(+/:id), POST accept/reject (session-auth). kernel-client RestClient gains runAiJob/ listArtifacts/getArtifact/acceptArtifact/rejectArtifact + AiArtifact/AiClaim/ AiEvidenceLink/AiModelRun types. New @insignia/iios-ai-web (AiProvider, useRunJob, useAiProposals, useArtifact). Boundary allowlist: ai-web -> contracts + kernel-client (fails on ai-web->service). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import { BadRequestException, Body, Controller, Get, Headers, Param, Post, Query } from '@nestjs/common';
|
||||
import { IiosAiJobType } from '@prisma/client';
|
||||
import { AiJobService } from './ai.service';
|
||||
import { SessionVerifier } from '../platform/session.verifier';
|
||||
import type { MessagePrincipal } from '../identity/actor.resolver';
|
||||
import { RunJobDto } from './ai.dto';
|
||||
|
||||
@Controller('v1/ai')
|
||||
export class AiController {
|
||||
constructor(
|
||||
private readonly ai: AiJobService,
|
||||
private readonly session: SessionVerifier,
|
||||
) {}
|
||||
|
||||
@Post('jobs')
|
||||
async runJob(@Body() body: RunJobDto, @Headers('authorization') auth?: string) {
|
||||
return this.ai.runJob({
|
||||
interactionId: body.interactionId,
|
||||
jobType: body.jobType as IiosAiJobType,
|
||||
principal: this.principal(auth),
|
||||
});
|
||||
}
|
||||
|
||||
@Get('artifacts')
|
||||
async listArtifacts(
|
||||
@Query('interactionId') interactionId?: string,
|
||||
@Query('status') status?: string,
|
||||
@Headers('authorization') auth?: string,
|
||||
) {
|
||||
this.principal(auth);
|
||||
return this.ai.listArtifacts({ interactionId, status });
|
||||
}
|
||||
|
||||
@Get('artifacts/:id')
|
||||
async getArtifact(@Param('id') id: string, @Headers('authorization') auth?: string) {
|
||||
this.principal(auth);
|
||||
return this.ai.getArtifact(id);
|
||||
}
|
||||
|
||||
@Post('artifacts/:id/accept')
|
||||
async accept(@Param('id') id: string, @Headers('authorization') auth?: string) {
|
||||
return this.ai.accept(id, this.principal(auth));
|
||||
}
|
||||
|
||||
@Post('artifacts/:id/reject')
|
||||
async reject(@Param('id') id: string, @Headers('authorization') auth?: string) {
|
||||
return this.ai.reject(id, this.principal(auth));
|
||||
}
|
||||
|
||||
private principal(authorization?: string): MessagePrincipal {
|
||||
const token = (authorization ?? '').replace(/^Bearer\s+/i, '');
|
||||
if (!token) throw new BadRequestException('Authorization bearer token is required');
|
||||
return this.session.verify(token);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { IsIn, IsString } from 'class-validator';
|
||||
|
||||
const JOB_TYPES = ['CLASSIFY', 'SUMMARIZE', 'EXTRACT'] as const;
|
||||
|
||||
export class RunJobDto {
|
||||
@IsString() interactionId!: string;
|
||||
@IsIn(JOB_TYPES) jobType!: (typeof JOB_TYPES)[number];
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { INFERENCE_PORT, LocalDeterministicInference } from './inference.port';
|
||||
import { AiBudgetGuard } from './ai-budget.guard';
|
||||
import { AiJobService } from './ai.service';
|
||||
import { AiProjector } from './ai.projector';
|
||||
import { AiController } from './ai.controller';
|
||||
|
||||
/**
|
||||
* AI Interaction SDK (P7). Proposal layer only. `INFERENCE_PORT` binds the local
|
||||
@@ -11,6 +12,7 @@ import { AiProjector } from './ai.projector';
|
||||
*/
|
||||
@Module({
|
||||
imports: [OutboxModule],
|
||||
controllers: [AiController],
|
||||
providers: [
|
||||
{ provide: INFERENCE_PORT, useClass: LocalDeterministicInference },
|
||||
AiBudgetGuard,
|
||||
|
||||
Reference in New Issue
Block a user