52ee3c1f3e
PR 1 of 2 (#381, фаза 1.5 #184): серверный реестр SSE-стримов агентских ранов, чтобы любая вкладка могла подключиться к живому рану с реплеем кадров + живым хвостом. «Спящий» — весь провод за флагом AI_CHAT_RESUMABLE_STREAM (off по умолчанию); клиент (PR 2) ещё не написан. - ai-chat-stream-registry.service.ts: in-memory реестр (open/bind/abortEntry/ attach). attach — снапшот+подписка в ОДНОМ синхронном блоке (инвариант 4: нет await между `subscribers.add` и `frames.slice()`), paused-подписчик, overflow, retention с identity-guard (инвариант 2), open поверх live entry даёт ровно один onEnd (инвариант 3), anchor против кросс-ранового реплея (инвариант 6). - ai-chat.controller.ts: begin-хук open(chatId, runId) + GET-attach эндпоинт (403 чужой чат; 204 нет-entry/finished/anchor-мисматч; cleanup до первой записи + recheck req.raw.destroyed; cap→destroy). - ai-chat.service.ts: tee SSE-кадров в реестр (consumeSseStream + generateMessageId, гейт на runId && flag) + abortEntry из внешнего catch. - environment.service.ts: флаг isAiChatResumableStreamEnabled(). Флаг OFF ⇒ байт-в-байт legacy И #184-фаза-1 (нет start.messageId, нет tee). Инжектируемые провайдеры НЕ @Optional() → поломка вайринга роняет старт, а не тихо выключает фичу. Тесты: registry unit (16), controller.attach (9), service pipe-options (4, вкл. flag-off-with-runId негатив), int-spec ai-chat-attach (6, реальный MockLanguageModelV3). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
56 lines
2.3 KiB
TypeScript
56 lines
2.3 KiB
TypeScript
import { Module } from '@nestjs/common';
|
|
import { AiModule } from '../../integrations/ai/ai.module';
|
|
import { TokenModule } from '../auth/token.module';
|
|
import { AiChatController } from './ai-chat.controller';
|
|
import { AiChatService } from './ai-chat.service';
|
|
import { AiChatRunService } from './ai-chat-run.service';
|
|
import { AiChatStreamRegistryService } from './ai-chat-stream-registry.service';
|
|
import { AiTranscriptionService } from './ai-transcription.service';
|
|
import { AiChatToolsService } from './tools/ai-chat-tools.service';
|
|
import { EmbeddingModule } from './embedding/embedding.module';
|
|
import { ExternalMcpModule } from './external-mcp/external-mcp.module';
|
|
import { AiAgentRolesModule } from './roles/ai-agent-roles.module';
|
|
import { ShareModule } from '../share/share.module';
|
|
import { SearchModule } from '../search/search.module';
|
|
import { PublicShareChatController } from './public-share-chat.controller';
|
|
import { PublicShareChatService } from './public-share-chat.service';
|
|
import { PublicShareChatToolsService } from './tools/public-share-chat-tools.service';
|
|
|
|
/**
|
|
* Per-user AI chat module (§6.1).
|
|
*
|
|
* AiModule supplies AiService + AiSettingsService. TokenModule supplies
|
|
* TokenService for minting the per-user loopback access token (§15[C1]). The
|
|
* AiChatRepo / AiChatMessageRepo / PageEmbeddingRepo / SpaceMemberRepo /
|
|
* PagePermissionRepo come from the global DatabaseModule; the UserThrottlerGuard
|
|
* + AI_CHAT throttler come from the global ThrottleModule registered in
|
|
* AppModule. EmbeddingModule hosts the vector-RAG indexer + AI_QUEUE consumer
|
|
* (§6.7 stage D); importing it here boots the processor with the app.
|
|
*
|
|
* ShareModule (ShareService) + SearchModule (SearchService) are imported for the
|
|
* ANONYMOUS public-share assistant (PublicShareChatController), whose read-only
|
|
* tools scope every lookup to a single share tree.
|
|
*/
|
|
@Module({
|
|
imports: [
|
|
AiModule,
|
|
TokenModule,
|
|
EmbeddingModule,
|
|
ExternalMcpModule,
|
|
AiAgentRolesModule,
|
|
ShareModule,
|
|
SearchModule,
|
|
],
|
|
controllers: [AiChatController, PublicShareChatController],
|
|
providers: [
|
|
AiChatService,
|
|
AiChatRunService,
|
|
AiChatStreamRegistryService,
|
|
AiTranscriptionService,
|
|
AiChatToolsService,
|
|
PublicShareChatService,
|
|
PublicShareChatToolsService,
|
|
],
|
|
})
|
|
export class AiChatModule {}
|